
Php Injection Audit
- 11 installs
- 1.6k repo stars
- Updated July 19, 2026
- wgpsec/aboutsecurity
Helps with ai & agent building tasks during AI-assisted development.
About
php-injection-audit is a Claude Code skill for ai & agent building. It helps developers move faster with AI-assisted coding.
- php-injection-audit
- AI & Agent Building
- AI-coding skill
Php Injection Audit by the numbers
- 11 all-time installs (skills.sh)
- Ranked #11,769 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/wgpsec/aboutsecurity --skill php-injection-auditAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 11 |
|---|---|
| repo stars | ★ 1.6k |
| Last updated | July 19, 2026 |
| Repository | wgpsec/aboutsecurity ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
PHP 注入类漏洞源码审计
本 skill 聚焦源码层面判断"注入是否成立",核心是验证 Source→Sink 路径上的过滤是否充分。构造 payload、绕 WAF 等运行时利用技术属于对应黑盒 exploit skill 范畴。
深入参考
- 6 种注入的危险模式 / 安全模式代码对比 / EVID 证据示例 → references/injection-patterns.md
---
6 种注入速查表
| 类型 | 典型 Sink | 危险模式 | 严重度 |
|---|---|---|---|
| SQL 注入 | PDO::query, mysqli_query, DB::raw, whereRaw | 字符串拼接进 SQL | Critical-High |
| 命令注入 | exec, system, shell_exec, passthru, proc_open, 反引号 | 用户输入拼入命令串 | Critical |
| SSRF | curl_exec, file_get_contents, SoapClient | 用户可控 URL 发起服务端请求 | High-Medium |
| 表达式注入 | eval, assert, preg_replace(/e), create_function | 用户输入进入代码执行上下文 | Critical |
| NoSQL 注入 | MongoDB find(), $where, 操作符 $ne/$gt/$regex | 数组/对象参数绕过等值比较 | High |
| LDAP 注入 | ldap_search, ldap_list, ldap_bind | 用户输入拼入过滤器字符串 | High-Medium |
通用审计流程(4 步)
Step 1 -- 确认 EVID 证据点: 从 php-audit-pipeline Phase 3 产出的 EVID_ 证据中,筛选属于注入类的条目(EVID_SQL_、EVID_CMD_、EVID_SSRF_、EVID_EXPR_、EVID_LDAP_)。没有 EVID 证据的 Sink 只能标"待验证"。
Step 2 -- 判断过滤有效性: 追踪 Source→Sink 路径上的每一步过滤/转义操作,评估其是否对当前注入类型有效。常见陷阱: addslashes 对 GBK 宽字节无效、intval 对数组参数无效、escapeshellarg 在特定 locale 下可绕过。
Step 3 -- 评估绕过可能性: 过滤存在但不充分时,分析具体绕过路径(编码差异、类型混淆、二次处理等)。能给出绕过思路则标"已确认",否则标"待验证"并记录已知过滤方式。
Step 4 -- 确定严重度: 使用 php-audit-pipeline 的三维度评分公式 Score = R*0.40 + I*0.35 + C*0.25。注入类漏洞的 Impact 通常较高(命令/表达式注入 I=3, SQL 注入 I=2-3),但需结合可达性和利用复杂度综合判断。
SQL 注入审计要点
- PDO 预编译: 确认
prepare()+execute()的占位符绑定完整。PDO::query($sql)和PDO::exec($sql)是直接执行,拼接即危险 - ORM 陷阱:
whereRaw/havingRaw/orderByRaw/selectRaw中的变量拼接不受 ORM 参数化保护 - 动态 ORDER BY / LIMIT: 这两个子句无法使用参数化绑定,
intval()或白名单是唯一安全方案 - sprintf %s 拼接:
sprintf("SELECT * FROM t WHERE id='%s'", $input)本质仍是拼接 - 二次注入: 数据入库时转义、出库时未转义再拼入新查询,追踪需跨越存储边界
命令注入审计要点
- escapeshellarg 有效性: 单独使用通常有效,但与
escapeshellcmd同时使用反而会引入绕过(引号配对被破坏) - 多参数拼接:
exec("convert " . escapeshellarg($src) . " " . $dest)— 如果$dest未转义则仍可注入 - 反引号: 容易被忽视的命令执行方式,全局搜索反引号中的变量引用
- proc_open 数组模式:
proc_open([$cmd, $arg1, $arg2], ...)避免了 shell 解析,是安全的命令执行方式
SSRF 审计要点
- 协议限制: 检查是否限制了
http/https以外的协议(gopher://可打内网服务,file://可读本地文件) - IP 黑名单绕过: 十进制
2130706433、八进制0177.0.0.1、IPv6[::1]、DNS Rebinding 都能绕过简单的 IP 检查 - 重定向跟随:
curl默认不跟随但CURLOPT_FOLLOWLOCATION=true时,可通过 302 跳转绕过 URL 白名单 - DNS 解析时序: 先解析检查再发起请求的模式存在 TOCTOU 竞争(DNS Rebinding 利用点)
表达式注入审计要点
- eval/assert: 任何用户可控数据进入
eval()都是 Critical。assert()在 PHP < 8.0 可执行字符串 - preg_replace /e: PHP 7.0 已移除,但遗留系统中仍可能存在。替代方案
preg_replace_callback是安全的 - create_function: 本质是
eval,PHP 8.0 已移除但老项目常见 - 可控回调:
array_map/usort/call_user_func的回调参数如果用户可控,等价于代码执行
NoSQL / LDAP 审计要点(简要)
- MongoDB 操作符注入: PHP 数组参数
$_GET['user'][$ne]=1可绕过等值匹配,检查是否对输入做了类型强制转换 - $where 注入: MongoDB 的
$where接受 JavaScript 表达式,用户可控时等价于代码执行 - LDAP 过滤器拼接:
(&(uid=$input)(pass=$pass))中注入*)(uid=*))(|(uid=*可修改查询逻辑 - ldap_escape 缺失: PHP 5.6+ 提供
ldap_escape()但很多项目未使用,检查(,),*,\,NUL是否被转义
检测清单
- [ ] 所有注入类 EVID_* 证据点已逐一审查
- [ ] SQL 拼接点均已验证是否使用参数化/预编译
- [ ] ORM Raw 方法中的变量拼接已全部标记
- [ ] 命令执行函数的每个参数段都已检查转义
- [ ] SSRF Sink 的 URL 来源和协议限制已确认
- [ ] eval/assert/preg_replace /e 的参数可控性已追踪
- [ ] NoSQL 查询参数的类型强制已检查
- [ ] LDAP 过滤器的转义处理已确认
- [ ] 过滤不充分的点已给出具体绕过思路或标"待验证"
- [ ] 严重度评分使用了统一公式,与 pipeline 一致
PHP 注入类漏洞审计模式参考
6 种注入的危险代码 / 安全代码对比 + EVID_* 证据格式示例。
---
1. SQL 注入
1.1 PDO/MySQLi 危险模式 vs 安全模式
// 危险: PDO::query/exec 拼接
$pdo->query("SELECT * FROM users WHERE email = '" . $_GET['email'] . "'");
$pdo->exec(sprintf("SELECT * FROM orders WHERE status = '%s'", $_POST['status']));
// 危险: MySQLi 拼接
mysqli_query($conn, "SELECT * FROM products WHERE id = " . $_GET['id']);
// 安全: 预编译
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
$stmt->execute(['email' => $_GET['email']]);
$stmt = $conn->prepare("SELECT * FROM products WHERE id = ?");
$stmt->bind_param("i", $_GET['id']); $stmt->execute();1.2 ORM 陷阱(Laravel)
// 危险: Raw 方法中拼接变量,ORM 参数化不覆盖 Raw 内部
DB::table('users')->whereRaw("name = '" . $request->name . "'")->get();
Item::orderByRaw($request->input('sort'))->get();
DB::table('orders')->groupBy('user_id')->havingRaw("count(*) > " . $request->min_count)->get();
// 安全: Raw + 绑定参数
DB::table('users')->whereRaw("name = ?", [$request->name])->get();1.3 动态 ORDER BY / LIMIT
ORDER BY/LIMIT 不支持参数化(标识符/语法元素),只能白名单或强转:
// 危险
$sql = "SELECT * FROM items ORDER BY " . $_GET['order'] . " LIMIT " . $_GET['limit'];
// 安全
$allowed = ['id', 'name', 'created_at'];
$order = in_array($_GET['order'], $allowed) ? $_GET['order'] : 'id';
$sql = "SELECT * FROM items ORDER BY $order LIMIT " . intval($_GET['limit']);1.4 二次注入
入库时转义,出库后被信任为"安全"再拼入新查询:
$pdo->exec("INSERT INTO users(name) VALUES('" . addslashes($_POST['name']) . "')"); // 入库安全
$row = $pdo->query("SELECT name FROM users WHERE id=1")->fetch();
$pdo->query("SELECT * FROM logs WHERE operator = '" . $row['name'] . "'"); // 出库拼接→注入审计要跨越存储边界: 入库过滤不能替代出库参数化。
1.5 SQL EVID 证据示例
[EVID_SQL_EXEC_POINT] app/Models/Order.php:142 | $this->db->query($sql)
[EVID_SQL_STRING_CONSTRUCTION] app/Models/Order.php:139-141
$sql = "SELECT * FROM orders WHERE user_id=" . $uid . " ORDER BY " . $sort
方式: $uid 和 $sort 均拼接
[EVID_SQL_USER_PARAM_TO_SQL_FRAGMENT]
Source: OrderController.php:55 — $uid=$_GET['uid']
过滤: $uid→intval(安全) | $sort→无过滤(可注入)---
2. 命令注入
2.1 Sink 函数参数危险位
| 函数 | 危险位 | 说明 |
|---|---|---|
exec / system / shell_exec / passthru | 第 1 参数 | 完整命令串 |
proc_open | 第 1 参数(字符串时) | 数组模式安全(PHP 7.4+) |
popen | 第 1 参数 | 打开进程管道 |
pcntl_exec | 第 1、2 参数 | 直接 execve |
` $cmd ` | 整个表达式 | 容易忽视 |
2.2 escapeshellarg 绕过场景
// 场景 1: 与 escapeshellcmd 同时使用 → 引号配对被破坏
$broken = escapeshellcmd(escapeshellarg($input)); // 某些组合下引号逃逸
// 场景 2: 多字节/locale — GBK 中 0x5c 是双字节字符的第二字节
setlocale(LC_CTYPE, "C");
$escaped = escapeshellarg($gbk_input); // 可能截断
// 场景 3: 参数注入(非 shell 元字符)
exec("sendmail " . escapeshellarg($to));
// $to="-OQueueDirectory=/tmp -X/var/www/shell.php" → 不含 shell 元字符但滥用命令参数2.3 proc_open 数组模式(安全)
// 字符串模式(危险): 经过 shell 解析
proc_open("convert $src $dst", $desc, $pipes);
// 数组模式(安全): 直接 execve,; | & 等元字符不被解释
proc_open(['convert', $src, $dst], $desc, $pipes);2.4 CMD EVID 证据示例
[EVID_CMD_EXEC_POINT] app/Services/ImageService.php:78 | exec($cmd)
[EVID_CMD_COMMAND_STRING_CONSTRUCTION] :75-77
$cmd = "convert " . escapeshellarg($source) . " -resize " . $size . " " . $output
$source 已转义 | $size 和 $output 直接拼接
[EVID_CMD_USER_PARAM_TO_CMD_FRAGMENT]
Source: ImageController.php:31 — $size=$_POST['size'] | 无过滤 → 可注入---
3. SSRF
3.1 URL 可控检测
// curl_exec
$ch = curl_init($_GET['url']); curl_exec($ch); // 完全可控
$ch = curl_init("http://internal-api/" . $_GET['endpoint']); // 路径拼接
// file_get_contents
file_get_contents($_POST['feed_url']);
file_get_contents($user->avatar_url); // avatar_url 用户设定时同样危险
// SoapClient
new SoapClient($_GET['wsdl']);
new SoapClient(null, ['location' => $url, 'uri' => $uri]);3.2 协议 scheme 与 IP 限制
| 协议 | 风险 |
|---|---|
gopher:// | 构造任意 TCP 包,攻击 Redis/SMTP/FastCGI |
file:// | 读本地文件 |
dict:// | 端口探测 |
安全: parse_url 后白名单限制 ['http','https']。
IP 黑名单绕过: 十进制 2130706433、八进制 0177.0.0.1、IPv6 [::1]/[::ffff:127.0.0.1]、DNS Rebinding(TOCTOU)。
重定向风险: CURLOPT_FOLLOWLOCATION=true 时可通过 302 跳转绕过 URL 白名单。安全做法是禁用跟随或手动校验 Location。
3.3 SSRF EVID 证据示例
[EVID_SSRF_URL_NORMALIZATION] WebhookService.php:45 | $url=$request->input('callback_url'),无协议限制
[EVID_SSRF_FINAL_URL_HOST_PORT] :52 | curl_setopt($ch, CURLOPT_URL, $url),完全可控
[EVID_SSRF_DNSIP_AND_INNER_BLOCK] 无内网IP黑名单 | FOLLOWLOCATION=true → 可 gopher/302 绕过---
4. 表达式注入
4.1 eval / assert
// eval — 间接进入(模板场景)
$tpl = str_replace('{{name}}', $user_input, file_get_contents($path));
eval('?>' . $tpl); // $user_input 含 <?php 标签即可执行
// assert (PHP < 8.0) — 字符串表达式
assert("strlen('$input') > 0");
// $input = "') || system('id') || ('" → 代码执行
// PHP 8.0+ assert 只接受布尔表达式,此风险消失4.2 preg_replace /e 与 create_function
// /e 标志 (PHP < 7.0): 替换内容作为代码执行
preg_replace('/(.*)/e', 'strtolower("\\1")', $input);
// $input = '{${phpinfo()}}' → 执行 phpinfo()
// 安全替代: preg_replace_callback
// create_function (PHP < 8.0): 本质是 eval
create_function('$a', 'return $a . "' . $input . '";');
// $input = '"; system("id"); //' → 注入
// 安全替代: 匿名函数 Closure4.3 可控回调
// call_user_func / array_map / usort 的回调参数用户可控时等价于代码执行
call_user_func($_GET['callback'], $_GET['data']); // callback=system, data=id
array_map($_GET['func'], $_GET['arr']);
// 安全: 白名单
$allowed = ['strtolower', 'strtoupper', 'trim'];
$func = in_array($_GET['callback'], $allowed) ? $_GET['callback'] : 'trim';4.4 EXPR EVID 证据示例
[EVID_EXPR_EVAL_CALL] TemplateEngine.php:112 | eval('?>' . $compiled)
[EVID_EXPR_STRING_CONSTRUCTION] :105-111 | str_replace 将用户变量值替入模板后进入 eval
[EVID_EXPR_USER_INPUT_INTO_EXPR]
Source: PageController.php:87 — $values=$request->input('vars') | 无标签过滤 → Critical---
5. NoSQL 注入
5.1 MongoDB $where
// $where 接受 JS 表达式,用户可控时等价于代码执行
$collection->find(['$where' => "this.username == '" . $_GET['user'] . "'"]);
// user = "' || 1==1 || '" → 全部返回 | "'; sleep(5000); '" → 时间盲注5.2 操作符注入
PHP $_GET/$_POST 可传数组,直接进入 MongoDB 查询时注入操作符:
// 危险
$collection->findOne(['username'=>$_POST['username'], 'password'=>$_POST['password']]);
// POST: password[$ne]=1 → {password:{$ne:"1"}} → 绕过密码
// 其他: [$gt]="" | [$regex]=.* | [$in][]=admin
// 安全: 类型强转
$collection->findOne(['username'=>(string)$_POST['username'], 'password'=>(string)$_POST['password']]);JSON body 同理: {"filter":{"$ne":null}} 注入操作符,需校验 is_string。
5.3 NoSQL EVID 证据示例
[EVID_NOSQL_QUERY_POINT] UserRepo.php:34 | $collection->findOne($query)
[EVID_NOSQL_PARAM_CONSTRUCTION] :31-33 | $token 来自请求参数,未类型强转
[EVID_NOSQL_USER_INPUT_INTO_QUERY] token[$ne]=1 可绕过校验---
6. LDAP 注入
6.1 过滤器拼接与通配符利用
// 危险: 直接拼入过滤器
$filter = "(&(uid=" . $_POST['username'] . ")(userPassword=" . $_POST['password'] . "))";
ldap_search($conn, $base_dn, $filter);
// username = *)(uid=*))(|(uid=* → 匹配所有用户
// 通配符枚举: search=* → 全部 | admin* → 前缀匹配 | 逐字符 a*→ad*→adm*6.2 ldap_escape 检测
// 安全: ldap_escape (PHP 5.6+) 转义 ( ) * \ NUL
$safe = ldap_escape($_POST['username'], '', LDAP_ESCAPE_FILTER);
$filter = "(&(uid=$safe)(userPassword=$safe_pass))";审计: 所有 ldap_search/ldap_list/ldap_read 调用点,回溯过滤器构造过程,确认用户输入是否经过 ldap_escape。
6.3 LDAP EVID 证据示例
[EVID_LDAP_QUERY_CALL] LdapAuth.php:56 | ldap_search($conn, $dn, $filter)
[EVID_LDAP_FILTER_CONSTRUCTION] :52-55 | 两参数直接拼接,未调用 ldap_escape
[EVID_LDAP_USER_INPUT_INTO_FILTER] Source: LoginController.php:28 | 无过滤 → 可注入