<?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.'; } } ?>
这是一个猜字符给flag的游戏。从获得FLAG那里开始溯源:
if (hash_equals($secret, $guess)) { $message = 'Congratulations! The flag is: ' . FLAG; }
hash_equals:比较两个字符串,无论它们是否相等,本函数的时间消耗是恒定的。 本函数可以用在需要防止时序攻击的字符串比较场景中,例如,可以用在比较 crypt() 密码哈希值的场景。
这个函数是非常安全的,再来看$secret:
$secret = bin2hex(random_bytes(64));
random_bytes(int $length):生成适合于加密使用的任意长度的加密随机字节字符串,例如在生成salt、密钥或初始化向量时,一般配合bin2hex()函数使用。
bin2hex():把ASCII字符串转换为十六进制值
⚪参考:
这道题生成的随机字符串是64位,也是非常难爆破的,所以在hash_equals和random_bytes上做文章比较困难,再往上看,如果存在source参数,则读取路径中的文件名部分。
if (isset($_GET['source'])) { highlight_file(basename($_SERVER['PHP_SELF'])); exit(); }
$_SERVER[‘PHP_SELF’]:相对于网站根目录的路径及 PHP 程序名称。
basename():返回路径中的文件名部分。
这里题外话,记录一下$_SERVER[‘PHP_SELF’]、$_SERVER[‘SCRIPT_NAME’] 与 $_SERVER[‘REQUEST_URI’]的差别:
网址:https://www.shawroot.cc/php/index.php/test/foo?username=root
$_SERVER[‘PHP_SELF’] 得到:/php/index.php/test/foo
$_SERVER[‘SCRIPT_NAME’] 得到:/php/index.php
$_SERVER[‘REQUEST_URI’] 得到:/php/index.php/test/foo?username=root
要想“highlight_file”这个config.php,必须要让basename($_SERVER[‘PHP_SELF’])==config.php。所以此题构造/index.php/config.php?source,这样的话,$_SERVER[‘PHP_SELF’]就会等于/index.php/config.php,经过basename()函数后就变成了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 :)"); }
结尾\/*$的意思是出现0或多个“/”然后结束字符串,所以此正则本意是不允许config.php作为$_SERVER[‘PHP_SELF’]的结尾,但我们可以利用空字符串绕过正则:basename()会去掉不可见字符,使用超过ascii码范围的字符就可以绕过:
因此,最终payload:
/index.php/config.php/%ff?source