目录

[鹤城杯 2021]Middle magic

目录

[鹤城杯 2021]Middle magic

题目:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php
highlight_file(__FILE__);
include "./flag.php";
include "./result.php";
if (isset($_GET['aaa']) && strlen($_GET['aaa']) < 20) {

    $aaa = preg_replace('/^(.*)level(.*)$/', '${1}<!-- filtered -->${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 '<p>The level 2 can not pass!</p>';
            // 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 '<p>out!</p>';
        }

    } else {
        echo 'nonono!';
    }

    echo '<hr>';
}

?>

分析代码,要获得 flag 需要让代码执行到: echo "success:" . $flag;

大体上可以分成三层:

第一层:

1
2
3
if (isset($_GET['aaa']) && strlen($_GET['aaa']) < 20)
    $aaa = preg_replace('/^(.*)level(.*)$/', '${1}<!-- filtered -->${2}', $_GET['aaa']);
	if (preg_match('/pass_the_level_1#/', $aaa))
  • preg_replace() 会把 aaa 中符合正则匹配的部分替换成后面的内容
  • 正则表达式: /^(.*)level(.*)$/ 会在 aaa 字符串的开头和结尾中匹配是否包含字符串 “level”
    • ^ 用于匹配字符串的开头、$ 用于匹配字符串的结尾
    • .* 用于匹配任意字符任意次,不包括换行符(关键信息) 加上括号 (.*) 表示为捕获组
  • ${1}<!-- filtered -->${2} 作为替换的字符串,${1}${2} 分别引用正则表达式中的第一个和第二个捕获组
  • 在 aaa 字符串匹配是否包含字符串 “pass_the_level_1#”

示例

1
2
3
<?php
echo preg_replace('/^(.*)level(.*)$/', '${1}<!-- filtered -->${2}', "This is a high-level example.");
?>

输出的结果:

1
This is a high-<!-- filtered --> example.

乍一看这两条代码有点冲突:

1
2
$aaa = preg_replace('/^(.*)level(.*)$/', '${1}<!-- filtered -->${2}', $_GET['aaa']);
if (preg_match('/pass_the_level_1#/', $aaa))

由于 .* 不匹配换行符,可以借此来绕过正则匹配替换

第一层 Bypass :

GET 方式提交:aaa=%0apass_the_level_1%23%0a 为换行符、%23 为井字符)

第二层:

1
2
3
4
if (isset($_POST['admin']) and isset($_POST['root_pwd']))
    if ($_POST['admin'] == $_POST['root_pwd'])
        echo '<p>The level 2 can not pass!</p>';
    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 加密后的值相同

1
2
3
4
5
6
7
8
<?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));
?>

运行结果:

1
2
bool(false)
bool(true)

数组绕过:

sha1() 在处理数组时会报错并返回 “NULL”,而 NULL===NULL 的结果为真:

1
2
3
4
5
<?php
$a = array(1);
@var_dump(sha1($a));
var_dump(NULL === NULL);
?>

运行结果:

1
2
NULL
bool(true)

第二层 Bypass :

POST 方式提交:admin[]=1&root_pwd[]=2

第三层:

1
2
3
4
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 变量的值相等,这里的比较为弱类型比较

示例:

1
2
3
4
5
<?php
$a = '{"a":1,"b":2,"c":3}';
var_dump(json_decode($a));
var_dump(json_decode($a, true));
?>

运行结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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 开头的字符串

示例:

1
2
3
4
5
6
<?php
var_dump(0 == '');
var_dump(0 == '0');
var_dump(0 == '0a'); 
var_dump(0 == 'he110 w0rd!');
?>

运行结果:

1
2
3
4
bool(true)
bool(true)
bool(true)
bool(true)

https://pic.imgdb.cn/item/65804d52c458853aef803de1.jpg

https://pic.imgdb.cn/item/65804e77c458853aef850ff3.jpg