Loading ...
PHP 代码审计指南
在 Web 安全领域,PHP 代码审计是识别和修复应用程序安全隐患的关键环节。由于 PHP 语言的灵活性和易用性,其代码中常存在各类可被攻击者利用的漏洞。本文将系统梳理 PHP 开发中常见的高风险漏洞类型,剖析其原理、示例及审计要点,为安全审计人员和开发人员提供参考。

一、代码执行漏洞

代码执行漏洞源于对可执行代码的函数滥用,攻击者可通过可控参数注入恶意逻辑。以下为需重点审计的函数及细节:

1. eval()

  • 定义:将字符串作为 PHP 代码执行,语法为 eval(string $code) : mixed
  • 关键特性
    • 返回值:默认返回NULL,若代码中有return则返回对应值。
    • 版本差异:PHP7 中解析错误会抛出ParseError异常;PHP5 中返回FALSE,但后续代码继续执行。
  • 漏洞示例
<?php
    $code = $_GET['code']; // 可控参数
    eval($code); // 注入"system('whoami');"即可执行命令
?>
  • 审计要点:检查$code是否直接由用户输入控制,是否存在过滤绕过可能。

2. assert()

  • 定义:断言检查,语法因版本而异:
    • PHP5:assert(mixed $assertion[, string $description]) : bool$assertion可为执行字符串)。
    • PHP7:assert(mixed $assertion[, Throwable $exception]) : bool(兼容字符串执行以保持向下兼容)。
  • 配置影响
配置项 默认值 风险场景
zend.assertions 1 设为 1 时(开发模式)会执行断言代码,存在风险
assert.exception 0 设为 0 时仅警告,不阻断执行
  • 漏洞示例
<?php
    $test = $_GET['test'];
    assert($test); // 传入"system('ipconfig')"执行命令
?>
  • 审计要点$assertion参数是否可控,是否包含用户输入。

3. preg_replace()

  • 定义:正则替换,语法为 preg_replace(mixed $pattern, mixed $replacement, mixed $subject [, int $limit = -1 [, int &$count ]])
  • 风险点$pattern/e模式时,$replacement会被当作 PHP 代码执行(PHP7 后废弃/e模式)。
  • 漏洞示例
<?php
    $regex = $_GET['regex'];
    $value = $_GET['value'];
    preg_replace('/(' . $regex . ')/ei', '\\1', $value); 
    // payload: ?regex=.*&value={${phpinfo()}}
?>
  • 审计要点:检查是否使用/e模式,$pattern$replacement是否可控。

4. create_function()

  • 定义:创建匿名函数(内部调用eval()),语法为 create_function(string $args, string $code) : string(PHP7.2 后废弃)。
  • 风险点$code参数可控时可注入恶意代码。
  • 漏洞示例
<?php
    $args = $_GET['args'];
    $code = $_GET['code'];
    $func = create_function($args, $code); // 注入$code="system('whoami');"
    $func();
?>
  • 衍生场景:通过字符串拼接构造$code,如排序逻辑中的代码注入:
<?php
    $sort_by = $_GET['sort_by'];
    $sort_func = "return strnatcasecmp(\$a['$sort_by'], \$b['$sort_by']);";
    usort($data, create_function('$a,$b', $sort_func));
    // payload: ?sort_by=';}phpinfo();/* 闭合并注入代码
?>

5. 回调函数类

所有接受回调函数的函数均需审计,若回调函数或其参数可控则存在风险:
函数 定义 漏洞示例
array_map() 为数组元素应用回调:array_map(callable $callback, array $array1 [, array $...]) array_map($_GET['func'], ['whoami']);func=system
call_user_func() 调用回调函数:call_user_func(callable $callback [, mixed $...]) call_user_func($_GET['func'], 'id');
call_user_func_array() 以数组为参数调用回调:call_user_func_array(callable $callback, array $param_arr) call_user_func_array('system', [$_GET['cmd']]);
array_filter() 用回调过滤数组:array_filter(array $array [, callable $callback [, int $flag ]]) array_filter([$_GET['cmd']], 'system');
usort() 用自定义函数排序:usort(array &$array, callable $value_compare_func) usort($data, $_GET['func']);func=assert&...
uasort() 排序并保持索引关联:uasort(array &$array, callable $value_compare_func) usort(),风险一致

二、命令执行漏洞

直接调用系统命令的函数,参数可控时可导致命令注入,需逐个审计:

1. system()

  • 定义:执行命令并输出结果,语法 system(string $command [, int &$return_var ]) : string
  • 示例system($_GET['cmd']);(传入cmd=whoami执行)。
  • 特点:直接输出命令结果,易被发现。

2. exec()

  • 定义:执行命令,返回最后一行输出,语法 exec(string $command [, array &$output [, int &$return_var ]]) : string
  • 示例exec($_GET['cmd'], $output); var_dump($output);(结果存于数组)。
  • 特点:不直接输出,需通过$output获取结果。

3. shell_exec()

  • 定义:通过 shell 执行命令,返回完整输出字符串,语法 shell_exec(string $cmd) : string
  • 示例echo shell_exec($_GET['cmd']);(执行并输出完整结果)。

4. passthru()

  • 定义:执行命令并显示原始输出(适合二进制数据),语法 passthru(string $command [, int &$return_var ]) : void
  • 示例passthru($_GET['cmd']);(直接输出原始结果)。

5. pcntl_exec()

  • 定义:在当前进程空间执行程序,语法 pcntl_exec(string $path [, array $args [, array $envs ]]) : void
  • 示例pcntl_exec('/bin/sh', ['-c', $_GET['cmd']]);(执行 shell 命令)。
  • 特点:需pcntl扩展,常用于子进程命令执行。

6. popen () 与 proc_open ()

  • 定义
    • popen(string $command, string $mode) : resource:打开进程文件指针。
    • proc_open(string $cmd, array $descriptorspec, array &$pipes [, string $cwd [, array $env [, array $other_options ]]]):更复杂的进程控制。
  • 示例
<?php
    $handle = popen($_GET['cmd'], 'r');
    echo fread($handle, 1024);
    pclose($handle);
?>

三、文件包含漏洞

通过包含文件执行代码,所有文件包含函数均需审计:

1. include () 与 include_once ()

  • 定义
    • include(string $filename):包含并执行文件,出错仅警告。
    • include_once(string $filename):确保文件只被包含一次。
  • 漏洞示例
<?php
    $file = $_GET['file'];
    include $file; // 传入?file=../../etc/passwd 或 php://filter伪协议
?>
  • 审计要点$filename是否可控,是否限制协议(如禁止php://file://)。

2. require () 与 require_once ()

  • 定义
    • require(string $filename):包含文件,出错终止脚本。
    • require_once(string $filename):确保文件只被包含一次。
  • 风险与include类似,但因出错终止特性,漏洞利用更易被发现。

四、文件读取漏洞

可读取文件内容的函数,需审计参数是否可控:
函数 定义 漏洞示例
file_get_contents() 读取文件为字符串:file_get_contents(string $filename [, int $use_include_path = 0 [, resource $context [, int $offset = 0 [, int $maxlen ]]]]) echo file_get_contents($_GET['file']);
fopen() + fread() fopen打开文件指针,fread读取:fread(resource $handle, int $length) $f = fopen($_GET['file'], 'r'); echo fread($f, 1024);
fgets() 读取文件一行:fgets(resource $handle [, int $length ]) while(($line = fgets($f)) !== false) { echo $line; }
fgetss() 读取一行并过滤 HTML/PHP 标签:fgetss(resource $handle [, int $length [, string $allowable_tags ]]) 风险同fgets(),但过滤标签不影响文件读取
readfile() 读取文件并输出:readfile(string $filename [, int $use_include_path = 0 [, resource $context ]]) readfile($_GET['file']);(直接输出文件内容)
file() 按行读取文件为数组:file(string $filename [, int $use_include_path = 0 [, resource $context ]]) print_r(file($_GET['file']));
parse_ini_file() 解析 INI 文件为数组:parse_ini_file(string $filename [, bool $process_sections = false [, int $scanner_mode = INI_SCANNER_NORMAL ]]) print_r(parse_ini_file($_GET['ini']));
show_source()/highlight_file() 高亮显示 PHP 文件:show_source(string $filename [, bool $return = false ]) show_source($_GET['phpfile']);(读取 PHP 源码)

五、文件上传漏洞

核心审计函数为move_uploaded_file()
  • 定义move_uploaded_file(string $filename, string $destination) : bool(移动上传文件到新位置)。
  • 漏洞点
    • 未验证文件类型:如允许.php后缀。
    • 文件名可控:如$destination = 'uploads/' . $_FILES['file']['name']
  • 示例
<?php
    $dest = 'uploads/' . $_FILES['file']['name']; // 文件名可控
    move_uploaded_file($_FILES['file']['tmp_name'], $dest);
?>

六、文件删除漏洞

需审计的函数:

1. unlink()

  • 定义:删除文件,语法 unlink(string $filename [, resource $context ]) : bool
  • 漏洞示例unlink($_GET['file']);(传入file=../config.php删除配置文件)。

2. session_destroy()

  • 定义:销毁会话数据,语法 session_destroy() : bool
  • 风险点:仅清空会话数据,不删除会话文件,但若会话 ID 可控,可能间接影响会话安全。

七、变量覆盖漏洞

导致变量被意外重赋值的函数及场景:

1. extract()

  • 定义:从数组导入变量到当前作用域,语法 extract(array &$array [, int $flags = EXTR_OVERWRITE [, string $prefix = '' ]]) : int
  • 关键参数$flagsEXTR_OVERWRITE(默认)时会覆盖已有变量。
  • 示例
<?php
    $user = 'admin';
    extract($_POST); // POST传入user=hacker 覆盖$user
    echo $user; // 输出hacker
?>

2. parse_str()

  • 定义:解析字符串为变量,语法 parse_str(string $encoded_string [, array &$result ])
  • 风险点:未指定$result时,变量直接存入当前作用域,覆盖已有值。
  • 示例
<?php
    $id = 1;
    parse_str($_GET['data']); // 传入data=id=2 覆盖$id
    echo $id; // 输出2
?>

3. import_request_variables()

  • 定义:导入 GET/POST/Cookie 变量到全局作用域(PHP5.4 后废弃),语法 import_request_variables(string $types [, string $prefix ]) : bool
  • 示例import_request_variables('g');(导入 GET 变量,覆盖全局变量)。

4. foreach 与 $$ 可变变量

  • 场景:通过foreach遍历用户可控数组,结合$$可变变量覆盖全局变量。
  • 示例
<?php
    $role = 'user';
    foreach($_GET as $k => $v) { $$k = $v; } // 传入?role=admin 覆盖$role
    echo $role; // 输出admin
?>

八、弱类型比较漏洞

PHP 弱类型特性导致的逻辑绕过,需关注以下场景:

1. md5 () 与 sha1 () 绕过

  • 原理0E开头的哈希值被解析为 0,如md5('s878926199a') = 0e545993274517709034328855841020
  • 示例
<?php
    if(md5($_GET['a']) == md5($_GET['b']) && $_GET['a'] != $_GET['b']){
        echo 'success';
    }
    // 传入a=s878926199a&b=s155964671a 绕过
?>
  • 数组绕过md5(['x']) === md5(['y']) 结果为true(均返回NULL)。

2. is_numeric () 绕过

  • 原理:十六进制字符串(如0x123)被识别为数字。
  • 示例
<?php
    if(is_numeric($_GET['id'])){
        $sql = "SELECT * FROM users WHERE id = {$_GET['id']}";
    }
    // 传入id=0x31206f722031 注入SQL:SELECT * FROM users WHERE id = 1 or 1
?>

3. in_array () 绕过

  • 原理:非严格模式($strict = false)下,字符串会强制转换为数字。
  • 示例
<?php
    $whitelist = ['admin', 'user'];
    if(in_array($_GET['role'], $whitelist)){
        echo 'allowed';
    }
    // 传入role=0 绕过('admin'转数字为0)
?>

九、XSS 漏洞

未过滤用户输入直接输出的函数,均可能导致 XSS:
函数 示例 风险
echo() echo $_GET['x']; 直接输出 HTML/JS 代码
print() print($_GET['x']); echo
print_r() print_r($_GET['x']); 输出数组时包含用户输入
printf()/sprintf() printf($_GET['x']); 格式化字符串包含用户输入
die()/exit() die($_GET['x']); 退出前输出用户输入
var_dump() var_dump($_GET['x']); 打印变量时包含 HTML
var_export() var_export($_GET['x']); 输出变量结构,含用户输入

十、反序列化漏洞

需审计unserialize()函数及所有触发反序列化的场景,重点关注魔术方法:

1. 核心魔术方法

方法 触发时机 利用示例
__wakeup() 反序列化时 篡改属性个数(如O:5:"Test":2:{...})可跳过执行(PHP5<5.6.25/PHP7<7.0.10)
__destruct() 对象销毁时 若含system()等命令执行函数,可直接触发
__construct() 对象创建时 初始化对象时执行,若参数可控则注入代码
__toString() 对象被当作字符串时 echo $obj触发,若含eval()则执行代码
__call() 调用不存在的方法时 $obj->test()触发,可执行预设逻辑
__callStatic() 调用不存在的静态方法时 Test::test()触发,风险同__call()
__get() 访问私有属性时 $obj->prop触发,可读取 / 修改敏感属性
__set() 设置私有属性时 $obj->prop = 1触发,可注入恶意值
__isset() isset()检测私有属性时 可触发自定义逻辑
__unset() unset()删除私有属性时 可触发自定义逻辑
__invoke() 对象被当作函数调用时 $obj()触发,可执行命令

2. PHAR 反序列化

  • 原理:PHAR 文件的meta-data以序列化格式存储,通过phar://伪协议访问时触发反序列化。
  • 示例file_get_contents('phar://malicious.phar') 触发meta-data中对象的反序列化。

十一、其他高风险函数

1. basename():路径截断漏洞

  • 函数作用:从路径中提取文件名(如 basename("/path/to/file.php") 返回 file.php)。
  • 核心风险:自动过滤非 ASCII 字符(如 %ff\0),导致路径绕过。
    • 示例
<?php
$file = $_GET['file']; // 用户传入:../../etc/passwd%ff
$filename = basename($file); // 过滤%ff后变为 ../../etc/passwd
readfile($filename); // 读取敏感文件
?>
  • 攻击逻辑:利用非 ASCII 字符截断路径过滤,访问预期外的文件。

2. curl_setopt():SSRF 漏洞入口

  • 函数作用:设置 cURL 请求参数(如 CURLOPT_URL 控制请求 URL)。
  • 核心风险CURLOPT_URL 可控时可发起任意协议请求。
    • 示例 1:读取本地文件
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_GET['url']); // 可控参数
curl_exec($ch);
?>
  • Payload:?url=file:///etc/passwd
  • 示例 2:攻击内网服务
    • Payload:?url=http://192.168.1.1/admin(探测内网地址)。

3. urldecode():二次编码绕过

  • 函数作用:对 URL 编码字符串解码(如 urldecode("%3C") 转为 <)。
  • 核心风险:多次解码导致过滤失效(如二次编码 %253C → 第一次解码 %3C → 第二次解码 <)。
    • 示例:XSS 绕过过滤
<?php
$input = urldecode($_GET['x']); // 一次解码:%253C → %3C
echo $input; // 浏览器二次解码为<,触发XSS
?>
  • Payload:?x=%253Cscript%253Ealert(1)%253C/script%253E

总结

PHP 代码审计需要结合语言特性与漏洞原理,重点关注用户可控参数的流向,以及高风险函数的使用场景。通过本文梳理的漏洞类型和审计要点,开发人员可在编码阶段规避风险,安全人员可更高效地开展审计工作,共同提升 Web 应用的安全性。在实际审计中,还需结合具体业务逻辑和框架特性,进行全面细致的检查。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇