记录一次php代码审计
字数 4895
更新时间 2026-03-26 15:19:19

PHP代码审计实战案例教学文档

一、 案例概述

本文档基于先知社区发布的《记录一次php代码审计》文章,还原了一次完整的代码审计流程。本次审计的对象是一个PHP开发的Web应用程序,通过源代码分析发现了SQL注入、XSS、RCE、逻辑漏洞等多种安全问题。本教学文档将逐一剖析漏洞成因、审计思路和利用方法,旨在帮助安全从业者构建系统化的代码审计能力。

二、 审计发现的漏洞详情与审计思路

1. SQL注入漏洞

漏洞位置novel.php 文件中的 delthebook 函数。

漏洞代码与成因

  • 漏洞核心在于delthebook函数在处理$bookname参数时,未进行任何过滤或转义,直接将该参数拼接到SQL语句字符串中。
  • $bookname参数由GET请求直接获取,$bookename = $_GET[‘bookename’]
  • 最终的SQL语句形如:DELETE FROM novel WHERE bookname='{$bookename}',攻击者可以控制$bookename的内容,从而注入恶意SQL命令。

调用链分析

  1. novel.php接收$cz参数,根据其值(例如‘delthebook’)调用对应的函数。
  2. 调用delthebook函数,该函数从$_GET中获取$bookename
  3. $bookename直接拼接进SQL字符串,执行数据库操作。

审计思路

  • 查找用户输入点:追踪GET、POST、COOKIE等超全局变量。
  • 追踪数据流:从输入点开始,查找数据未经处理(如使用addslashesmysql_real_escape_string或PDO预编译)即进入SQL语句拼接的位置。
  • 关注执行点:定位mysql_querymysqli_queryPDO::query等数据库操作函数。

特殊限制
本次审计中,虽然漏洞确实存在,但由于该程序的数据库默认安装脚本(.sql文件)中可能缺少novel表,导致sqlmap等自动化工具无法成功利用。这提示我们,在代码审计中确认漏洞存在后,还需结合实际的运行环境评估其可利用性。

2. 后台存储型XSS(盲打)

漏洞位置

  1. 触发/输出点/admin/user.php 文件。
  2. 输入/存储点:用户注册功能。

漏洞成因
这是一个典型的存储型XSS,成因可拆解为“恶意输入入库”和“恶意数据输出”两个环节:

  1. 输入无过滤

    • 在用户注册功能中,accountpassword参数从$_POST获取后,未经过任何HTML实体编码或过滤,便直接存入数据库。
    • 注册逻辑中虽有token校验,但token是硬编码在文件中的固定值,可被轻易绕过。
  2. 输出无编码

    • 在后台管理页面/admin/user.php中,程序从数据库读取用户信息($users[]数组)。
    • 在遍历输出用户信息时,直接使用echo输出用户数据,没有使用htmlspecialchars()等函数进行编码转义。

漏洞利用流程(盲打)

  1. 构造恶意输入:攻击者无需登录后台,直接在用户注册点提交一个包含恶意脚本(如``)的account
  2. 数据存储:该恶意脚本作为用户账户名被存入数据库。
  3. 等待触发:当管理员登录后台,查看用户管理列表时,程序会从数据库读取包含恶意脚本的账户名并直接echo输出,导致脚本在管理员浏览器中执行。

审计思路

  • 输出点审计:查找所有echoprint<?= $var ?>等输出函数/语法,检查其输出的变量是否来自用户可控的输入(如数据库、请求参数)。若未使用htmlspecialchars($var, ENT_QUOTES),则存在风险。
  • 输入点审计:追踪进入数据库的数据,检查在入库前是否经过过滤。同时,要注意数据可能在不同功能模块间流转。
  • 功能关联:将“前台用户输入功能”与“后台数据展示功能”关联起来思考。

3. 远程代码执行(RCE)

漏洞位置/install/call.php 文件。

漏洞代码与成因

  • 在安装向导的call.php中,有一段将配置信息写入config.php的代码。
  • 代码将用户提交的数据库名($dbname)等参数,未经任何处理直接拼接到一个字符串中,然后通过file_put_contents()写入config.php
  • 由于config.php是一个PHP文件,攻击者可以在$dbname中注入PHP代码。例如,提交db’;phpinfo();?>作为数据库名。
  • 写入config.php的最终内容会包含这段恶意代码,当后续访问config.php时,其中的phpinfo();就会被服务器执行。

关键缺陷

  • call.php文件没有检查是否已安装(例如检查是否存在install.lock文件),导致攻击者可以在安装完成后,依然能访问此安装接口,覆盖或写入恶意配置。

审计思路

  • 关注文件写操作:重点审计file_put_contents()fwrite()fputs()等文件写入函数。
  • 检查写入内容是否用户可控:若写入内容(特别是写入.php文件)中包含了未经过滤的用户输入,则极有可能导致代码注入。
  • 检查安装逻辑:审计安装、初始化相关的脚本,检查其是否缺乏状态锁(如install.lock),是否存在重装、覆盖安装的风险。

4. 身份验证逻辑漏洞(Cookie伪造)

漏洞位置login.php 及相关后台入口。

漏洞成因
程序采用了一种不安全的身份认证方式:

  1. 认证机制:用户登录成功后,服务端仅通过setcookie()在客户端设置一个名为admin_keys(对管理员)或类似名称的Cookie,并将其值设为用户的身份标识(如用户ID)。
  2. 验证机制:在访问需要权限的页面(如index.php、用户后台)时,程序直接检查客户端发送的Cookie中admin_keys的值,而没有在服务端开启会话(session_start())并与服务端存储的会话信息进行比对

漏洞利用
攻击者无需知道任何用户的账号密码,只需在浏览器中手动修改或添加一个Cookie,例如admin_keys=2(假设2是某个有效用户的ID),即可直接以该用户身份登录系统前台和后台。

审计思路

  • 审计认证与鉴权逻辑:追踪登录成功后的操作。如果代码只有setcookie()而没有session_start()$_SESSION的赋值、使用,则高度可疑。
  • 检查权限验证点:在需要权限的页面入口,检查其验证逻辑是比对$_SESSION中的值,还是直接读取$_COOKIE。后者是脆弱的。
  • 理解Session与Cookie的区别:Session数据存储在服务器,相对安全;Cookie存储在客户端,用户可以任意修改,绝不可信,仅可用于存储不敏感的用户偏好或经过签名、加密的令牌。

三、 代码审计方法论与最佳实践总结

基于本次案例,我们可以总结出以下方法论:

  1. 通读与功能理解:首先了解程序的大致目录结构、入口文件和核心功能模块。本次审计的程序涉及小说管理、用户系统、安装程序等。
  2. 敏感函数/关键字追踪
    • 数据库操作mysql_*, mysqli_*, PDO::query, PDO::prepare(注意检查预编译是否规范)。
    • 用户输入$_GET, $_POST, $_REQUEST, $_COOKIE, $_FILES
    • 文件操作include/require, file_get_contents, file_put_contents, fopen/fwrite
    • 命令执行exec, system, passthru, shell_exec, 反引号(`)。
    • 代码执行eval, assert, create_function, preg_replace/e 修饰符。
    • 输出函数echo, print, printf, 以及在HTML标签属性或脚本中输出的变量。
  3. 数据流分析:从“源”(用户输入点)出发,跟踪数据在整个应用中的传递、处理过程,直到“汇”(危险函数执行点),分析中间过程是否有有效的过滤和校验。
  4. 功能点关联审计:不要孤立地看一个文件。例如,本次XSS漏洞,输入点在注册功能,输出点在后台管理,需要将两个功能关联起来才能发现完整的利用链。
  5. 框架与二开程序审计
    • 二开程序:特别注意二开部分与原始代码的衔接处。检查二开是否破坏了原有的安全逻辑,或引入了新的不安全函数。同时,原始程序中已修复的漏洞,在二开版本中可能因代码覆盖而重新出现。
    • 默认配置:检查默认安装的SQL文件、默认管理员账号密码等,这些往往是突破口。
  6. 工具辅助与人工审计结合:自动化代码审计工具(如Fortify SCA、Checkmarx、RIPS、SonarQube等)可以快速定位潜在的“污点”,但存在误报和漏报。工具发现的点需要人工进行精准的数据流分析和可利用性判断,而像本次的XSS和逻辑漏洞,工具可能难以发现,更依赖于审计者的经验和对业务逻辑的理解。
  7. 站在开发者角度思考:理解功能实现的意图,有助于发现逻辑漏洞。例如,开发者为了方便,用Cookie直接存储用户ID进行认证,而未考虑其安全性,这就导致了严重的逻辑漏洞。

四、 漏洞修复建议

  1. SQL注入

    • 首选:使用参数化查询(预编译语句),如PDO的prepare()bindParam(),或MySQLi的预处理。
    • 次选:如果必须拼接,对输入进行严格的转义(如mysqli_real_escape_string()),但要注意数据库连接的字符集。
  2. XSS

    • 输出时编码:根据输出位置,采用相应的编码。
      • 输出到HTML正文:htmlspecialchars($var, ENT_QUOTES, ‘UTF-8’)
      • 输出到HTML属性:同上,确保引号被转义。
      • 输出到JavaScript:使用json_encode()
    • 内容安全策略(CSP):在HTTP头中设置Content-Security-Policy,可以有效缓解XSS的影响。
  3. RCE(代码/命令注入)

    • 避免动态生成代码/命令:尽可能使用安全的API替代eval()system()等函数。
    • 严格白名单过滤:如果必须使用,对用户输入进行严格的白名单验证,只允许预期的字符集。
    • 转义:对于命令执行,使用escapeshellarg()等函数对参数进行转义。
  4. 身份验证与会话管理

    • 使用服务端Session:用户登录状态等敏感信息必须存储在服务端Session中。
    • Cookie仅用于传递Session ID:Cookie中应只存放无法被伪造的、随机的Session ID,并设置HttpOnlySecure(HTTPS下)属性。
    • 防重放:对关键操作使用一次性令牌(CSRF Token)。
  5. 安装程序安全

    • 安装完成后必须生成一个锁文件(如install.lock)。
    • 在所有后续请求的入口文件(如index.php)中,检查该锁文件是否存在。若存在,则重定向或屏蔽对安装目录(/install/)的访问。

通过本次系统的审计过程,我们不仅定位了具体的漏洞点,更重要的是掌握了追踪数据流、关联功能模块、分析业务逻辑的审计思维,这些是成为一名合格的安全研究员所必需的核心能力。

相似文章
相似文章
 全屏