最近做了一道N1CTF2021的题目,学到了不少,分享给大家共同学习。
题目如下:
源码如下:
<?php
//flag is /flag
$path=$_POST['path'];
$time=(isset($_GET['time'])) ? urldecode(date(file_get_contents('php://input'))) : date("Y/m/d H:i:s");
$name="/var/www/tmp/".time().rand().'.txt';
$black="f|ht|ba|z|ro|;|,|=|c|g|da|_";
$blist=explode("|",$black);
foreach($blist as $b){
if(strpos($path,$b) !== false){
• die();
}
}
if(file_put_contents($name, $time)){
• echo "<pre class='language-html'><code class='language-html'>logpath:$name</code></pre>";
}
$check=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($path));
if(is_file($check)){
• echo "<pre class='language-html'><code class='language-html'>".file_get_contents($check)."</code></pre>";
}
页面下方输出的是日志文件。要拿到flag,我首先关注到下方的两个if语句,先看最下面这个:
if(is_file($check)){
echo "<pre class='language-html'><code class='language-html'>".file_get_contents($check)."</code></pre>";
}
如果$check是一个文件,那么读取他的内容,并输出。如果$check的值刚好是/flag,那这道题就解出来了。而$check是由这行代码来的:
$check=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($path));
此处使用了preg_replace函数,将$path文件内容里的换行空格等内容删除。而$path最初是通过POST请求提交的,之后进行了过滤,不能包含一些字符:
$black="f|ht|ba|z|ro|;|,|=|c|g|da|_";
$blist=explode("|",$black);
foreach($blist as $b){
if(strpos($path,$b) !== false){
die();
}
除了POST请求提交path参数我们可以控制,另外一个参数time通过GET方式和伪协议的方式我们也可以控制:
$time=(isset($_GET['time'])) ? urldecode(date(file_get_contents('php://input'))) : date("Y/m/d H:i:s");
$name="/var/www/tmp/".time().rand().'.txt';
if(file_put_contents($name, $time)){
echo "<pre class='language-html'><code class='language-html'>logpath:$name</code></pre>";
而$time的内容会被写入$name所在的文件中。
整个过程如下:
我们可控的地方有path参数和time参数,time参数的内容最终会写入到name文件中。name文件会在每次请求的时候输出,我们不可控,但能够得知name文件的路径。path参数对应的文件名可控,但其文件内容不可控,而check文件名来源于path文件的内容,最终会读取并输出check文件的内容。
所以,我们要想读取/flag的内容,我们就要使check文件内容等于/flag,即可直接在网页中显示出/flag的内容。check文件名可通过path参数进行控制,而name文件内容我们可以通过time参数控制,所以,我们要想方法把/flag这个字符串写入name文件内容中,然后将check文件名设置为name文件即可。
第一步,我们首先将/flag字符串写入time文件。
程序通过如下代码获得time的值:
$time=(isset($_GET['time'])) ? urldecode(date(file_get_contents('php://input'))) : date("Y/m/d H:i:s");
其中涉及到php的伪协议php://input,它可以读取我们POST请求的内容,time参数如果被设置,同时有POST数据,POST数据将会赋值给$time,我在本机进行调试如下:
内容写入到time对应的txt文件中:
为什么写入的内容不是/flag?因为有date函数并结果urldecode,所以,改成如下即可:
查看对应的文件即为/flag:
而我们将path参数设置为time对应的txt文件名,那么check文件名就会被设置为/flag,最终读取输出flag。
按照本地测试的效果,首先传递time参数,将/flag写入文件,然后记下页面输出的txt文件路径:
然后再次请求时,指定path为txt文件路径/var/www/tmp/16375737971145120615.txt,得到flag内容: