Single

[Zer0pts2020]Can you guess it?1 min read

<?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字符串转换为十六进制值

⚪参考:

https://blog.csdn.net/Mr_cq_/article/details/100573221

这道题生成的随机字符串是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’]的差别:

网址:http://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