本文主要记录[安洵杯 2019]easy_web和[WUSTCTF2020]朴实无华的做题过程和相关思路
打开题目,发现在url上有两个参数,分别是img
和cmd
,发现毫无思路,直接看源码进行审计
对于img
的值其构成为大小写字母和数字,符合Base64编码的特征,尝试对其进行Base64解码
注:由于TXpVek5UTTFNbVUzTURabE5qYz0的长度为27不满足4的倍数,需要在其后面添加=,使长度为28,再进行解码
解码后的结果同样满足Base64编码规则,再次进行解码
得到的结果为一串16进制,将其转换为字符串为555.png
尝试将555.png
拼接到url进行访问,发现就是题目一开始所展示的那张图
在题目首页中查看该图片的加载方式,发现是通过Base64方式进行加载的
通过上面的分析,可以知道只要将需要读取的本地文件的文件名经过一次字符串转16进制,两次Base64编码,再传输到img
参数中,即可在题目首页看到对应文件的Base64编码
那么首先尝试读取index.php
将上面的的Base64编码进行解码后可以得到index.php
的源代码
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd']))
header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));
$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
echo '';
die("xixi~ no flag");
} else {
$txt = base64_encode(file_get_contents($file));
echo "";
echo "
";
}
echo $cmd;
echo "
";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~");
echo "
";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}
?>
<html>
<style>
body{
background:url(./bj.png) no-repeat center center;
background-size:cover;
background-attachment:fixed;
background-color:#CCCCCC;
}
</style>
<body>
</body>
</html>
那么现在看来img
参数的作用应该就是帮助我们获取index.php
源代码的,接下来的关键是cmd
参数,通过查看源代码,发现对cmd
参数的值进行了过滤,比如cat
、tac
等,并且下面还有更为重要的一段判断代码
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
这里由于使用了string
进行了强制转换,发现数组经string
强制转换后的值都为"Array"
,不能满足!==
,所以无法使用数组进行绕过
那么这里就要考虑使用md5强碰撞
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
注意,在使用hackbar发送post数据时会自动将数据进行一次url编码,所以我这里使用bp,由于没有过滤dir
命令,那我就使用dir
命令查看flag
的位置
使用cat
的命令直接读取flag
的内容
注意,在查看其他师傅WP时发现有师傅有疑惑,说\
为什么没有被检测到或者ca\t
为什么可以绕过,我在本地测试了一下,有一些收获:
1.首先我们可以看到在preg_match
函数的匹配模式$pattern中确实含有|\\|\\\\|
,一眼看上去像是检测了\
和\\
,其实不是,在php对于转义字符的说明中有一段话
由于preg_match
函数的匹配模式为字符串,那么首先它应该被当作是字符串进行处理
在 /x|\
\|\
\\
\|z/ 中三个高亮的反斜杠都是起到转义的作用,使得后面的反斜杠能够作为匹配模式中的内容
2.经过字符串的处理之后,实际上的匹配模式为 /x|\|\\|z/
,而匹配模式中又是允许转义字符存在的,那么其中的\
将再次发挥转义作用,即 /x|\
|\
\|z/ 中高亮的转义字符再次进行转义,最后实际的匹配模式为/x||\|z/
,即匹配x
或|\
或z
以下是一个小案例:
不匹配\
匹配|\
打开题目,只有一句话,看页面源代码也没有收获,直接用dirsearch
,发现有robots.txt
和404.html
访问robots.txt
发现提示有fAke_f1agggg.php
访问fAke_f1agggg.php
说不是flag,而且页面源代码中也没有线索
查看fAke_f1agggg.php
的请求头和响应头,发现在响应头中提示有fl4g.php
访问fl4g.php
,发现是一道代码审计题目,需要过三个关卡
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);
//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}
//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}
//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}
?>
第一关,其中涉及到intval
函数,可以使用科学计数法表示进行绕过
intval('2e4')
==>当遇到第一个非数字时停止,结果为2intval('2e4'+1)
==>2e4首先转换成20000再加1,结果为20001第二关,是==
弱类型比较
0e215962017
被看作是科学计数法0的21…次方,即为00e291242476940776845150308577824
被看作是科学计数法0的29…次方,也为0,故相等
第三关,!strstr($get_flag," ")
会检测是否含有空格,有的话就会退出,str_ireplace("cat", "wctf2020", $get_flag)
会将$get_flag
中的ctf
替换为wctf2020
,那么这里就不能直接用cat
命令,可以先用ls
看看当前目前都有哪些文件,发现一个文件名很长的文件
使用tac
来绕过cat
检测和使用$IFS$9
来绕过空格检测