记录一次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命令。
调用链分析:
novel.php接收$cz参数,根据其值(例如‘delthebook’)调用对应的函数。- 调用
delthebook函数,该函数从$_GET中获取$bookename。 - 将
$bookename直接拼接进SQL字符串,执行数据库操作。
审计思路:
- 查找用户输入点:追踪GET、POST、COOKIE等超全局变量。
- 追踪数据流:从输入点开始,查找数据未经处理(如使用
addslashes、mysql_real_escape_string或PDO预编译)即进入SQL语句拼接的位置。 - 关注执行点:定位
mysql_query、mysqli_query、PDO::query等数据库操作函数。
特殊限制:
本次审计中,虽然漏洞确实存在,但由于该程序的数据库默认安装脚本(.sql文件)中可能缺少novel表,导致sqlmap等自动化工具无法成功利用。这提示我们,在代码审计中确认漏洞存在后,还需结合实际的运行环境评估其可利用性。
2. 后台存储型XSS(盲打)
漏洞位置:
- 触发/输出点:
/admin/user.php文件。 - 输入/存储点:用户注册功能。
漏洞成因:
这是一个典型的存储型XSS,成因可拆解为“恶意输入入库”和“恶意数据输出”两个环节:
-
输入无过滤:
- 在用户注册功能中,
account和password参数从$_POST获取后,未经过任何HTML实体编码或过滤,便直接存入数据库。 - 注册逻辑中虽有
token校验,但token是硬编码在文件中的固定值,可被轻易绕过。
- 在用户注册功能中,
-
输出无编码:
- 在后台管理页面
/admin/user.php中,程序从数据库读取用户信息($users[]数组)。 - 在遍历输出用户信息时,直接使用
echo输出用户数据,没有使用htmlspecialchars()等函数进行编码转义。
- 在后台管理页面
漏洞利用流程(盲打):
- 构造恶意输入:攻击者无需登录后台,直接在用户注册点提交一个包含恶意脚本(如``)的
account。 - 数据存储:该恶意脚本作为用户账户名被存入数据库。
- 等待触发:当管理员登录后台,查看用户管理列表时,程序会从数据库读取包含恶意脚本的账户名并直接
echo输出,导致脚本在管理员浏览器中执行。
审计思路:
- 输出点审计:查找所有
echo、print、<?= $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 及相关后台入口。
漏洞成因:
程序采用了一种不安全的身份认证方式:
- 认证机制:用户登录成功后,服务端仅通过
setcookie()在客户端设置一个名为admin_keys(对管理员)或类似名称的Cookie,并将其值设为用户的身份标识(如用户ID)。 - 验证机制:在访问需要权限的页面(如
index.php、用户后台)时,程序直接检查客户端发送的Cookie中admin_keys的值,而没有在服务端开启会话(session_start())并与服务端存储的会话信息进行比对。
漏洞利用:
攻击者无需知道任何用户的账号密码,只需在浏览器中手动修改或添加一个Cookie,例如admin_keys=2(假设2是某个有效用户的ID),即可直接以该用户身份登录系统前台和后台。
审计思路:
- 审计认证与鉴权逻辑:追踪登录成功后的操作。如果代码只有
setcookie()而没有session_start()和$_SESSION的赋值、使用,则高度可疑。 - 检查权限验证点:在需要权限的页面入口,检查其验证逻辑是比对
$_SESSION中的值,还是直接读取$_COOKIE。后者是脆弱的。 - 理解Session与Cookie的区别:Session数据存储在服务器,相对安全;Cookie存储在客户端,用户可以任意修改,绝不可信,仅可用于存储不敏感的用户偏好或经过签名、加密的令牌。
三、 代码审计方法论与最佳实践总结
基于本次案例,我们可以总结出以下方法论:
- 通读与功能理解:首先了解程序的大致目录结构、入口文件和核心功能模块。本次审计的程序涉及小说管理、用户系统、安装程序等。
- 敏感函数/关键字追踪:
- 数据库操作:
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标签属性或脚本中输出的变量。
- 数据库操作:
- 数据流分析:从“源”(用户输入点)出发,跟踪数据在整个应用中的传递、处理过程,直到“汇”(危险函数执行点),分析中间过程是否有有效的过滤和校验。
- 功能点关联审计:不要孤立地看一个文件。例如,本次XSS漏洞,输入点在注册功能,输出点在后台管理,需要将两个功能关联起来才能发现完整的利用链。
- 框架与二开程序审计:
- 二开程序:特别注意二开部分与原始代码的衔接处。检查二开是否破坏了原有的安全逻辑,或引入了新的不安全函数。同时,原始程序中已修复的漏洞,在二开版本中可能因代码覆盖而重新出现。
- 默认配置:检查默认安装的SQL文件、默认管理员账号密码等,这些往往是突破口。
- 工具辅助与人工审计结合:自动化代码审计工具(如Fortify SCA、Checkmarx、RIPS、SonarQube等)可以快速定位潜在的“污点”,但存在误报和漏报。工具发现的点需要人工进行精准的数据流分析和可利用性判断,而像本次的XSS和逻辑漏洞,工具可能难以发现,更依赖于审计者的经验和对业务逻辑的理解。
- 站在开发者角度思考:理解功能实现的意图,有助于发现逻辑漏洞。例如,开发者为了方便,用Cookie直接存储用户ID进行认证,而未考虑其安全性,这就导致了严重的逻辑漏洞。
四、 漏洞修复建议
-
SQL注入:
- 首选:使用参数化查询(预编译语句),如PDO的
prepare()和bindParam(),或MySQLi的预处理。 - 次选:如果必须拼接,对输入进行严格的转义(如
mysqli_real_escape_string()),但要注意数据库连接的字符集。
- 首选:使用参数化查询(预编译语句),如PDO的
-
XSS:
- 输出时编码:根据输出位置,采用相应的编码。
- 输出到HTML正文:
htmlspecialchars($var, ENT_QUOTES, ‘UTF-8’)。 - 输出到HTML属性:同上,确保引号被转义。
- 输出到JavaScript:使用
json_encode()。
- 输出到HTML正文:
- 内容安全策略(CSP):在HTTP头中设置
Content-Security-Policy,可以有效缓解XSS的影响。
- 输出时编码:根据输出位置,采用相应的编码。
-
RCE(代码/命令注入):
- 避免动态生成代码/命令:尽可能使用安全的API替代
eval()、system()等函数。 - 严格白名单过滤:如果必须使用,对用户输入进行严格的白名单验证,只允许预期的字符集。
- 转义:对于命令执行,使用
escapeshellarg()等函数对参数进行转义。
- 避免动态生成代码/命令:尽可能使用安全的API替代
-
身份验证与会话管理:
- 使用服务端Session:用户登录状态等敏感信息必须存储在服务端Session中。
- Cookie仅用于传递Session ID:Cookie中应只存放无法被伪造的、随机的Session ID,并设置
HttpOnly、Secure(HTTPS下)属性。 - 防重放:对关键操作使用一次性令牌(CSRF Token)。
-
安装程序安全:
- 安装完成后必须生成一个锁文件(如
install.lock)。 - 在所有后续请求的入口文件(如
index.php)中,检查该锁文件是否存在。若存在,则重定向或屏蔽对安装目录(/install/)的访问。
- 安装完成后必须生成一个锁文件(如
通过本次系统的审计过程,我们不仅定位了具体的漏洞点,更重要的是掌握了追踪数据流、关联功能模块、分析业务逻辑的审计思维,这些是成为一名合格的安全研究员所必需的核心能力。
相似文章
相似文章