开篇:那个周末,300 个零日漏洞被发现了
上个月,安全研究团队用 AI 对 WordPress.org 上的 5,000 个最热门插件做了自动安全审计。
结果:72 小时内发现了 300 多个零日漏洞。
不,AI 没有写 300 个 exploit——它只是跑了自动化的源码审计。但结果已经足够触目惊心:超过 6% 的插件存在至少一个可以被利用的严重安全漏洞。
我开发 WordPress 插件已经 5 年了,维护着 3 个累计 10 万+ 安装量的免费插件和 2 个付费插件。
看到这份报告后,我把所有插件都重新审查了一遍。发现了 7 个问题——其中 2 个是可以在特定条件下被利用的 SQL 注入,1 个是存储型 XSS。
这篇文筆分享我从这次经历中学到的:AI 在怎么审计 WordPress 插件?我发现了什么问题?我改了什么?以及 —— 既然 AI 既能发现漏洞也能利用漏洞,插件开发者应该怎么应对?
一、AI 发现了哪些漏洞
报告中最常见的漏洞类型:
| 漏洞类型 | 占比 | 说明 |
|---|---|---|
| SQL 注入 | 32% | 未经 sanitize 的参数直接拼接到查询中 |
| 存储型 XSS | 27% | 用户输入未转义就存到数据库 |
| 任意文件上传 | 12% | 文件上传没有校验类型 |
| CSRF | 10% | 没有 nonce 验证的操作 |
| 路径遍历 | 8% | 未校验文件路径参数 |
| PHP 对象注入 | 6% | 不安全的 unserialize 调用 |
| 权限提升 | 5% | 未检查 current_user_can() |
值得注意的是:这些不是"复杂"漏洞。它们大多是常见的、已知的安全问题——只是插件开发者忽略了。
AI 能做到的原因很简单:它分析了数千个插件的代码,总结出了高危模式的 pattern,然后对新代码批量扫描。
二、典型的"被 AI 发现"的漏洞
2.1 SQL 注入:最常见的问题
<?php
// ❌ 有漏洞的写法 —— 在 300 个漏洞中占比最高
function get_user_by_email($email) {
global $wpdb;
// $email 来自 $_GET 或 $_POST,没有经过任何处理
return $wpdb->get_results("SELECT * FROM {$wpdb->users} WHERE user_email = '$email'");
}
// ✅ 修复后的写法
function get_user_by_email_safe($email) {
global $wpdb;
// 使用 $wpdb->prepare() —— WordPress 自带的参数化查询
return $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->users} WHERE user_email = %s",
$email
)
);
}
这是一个 SQL 注入的基础写法错误。但我的一个插件里也有类似的问题——因为 3 年前写的代码,当时以为"这个参数只在内部调用,不会暴露给用户"。但后来加了一个 AJAX endpoint,直接暴露了。
AI 能发现的原因是:它追踪了变量的数据流,如果发现用户输入的变量未经处理就到了 SQL 查询,立即标记。
2.2 XSS:存储型更危险
<?php
// ❌ 有漏洞的写法
function save_user_feedback() {
$feedback = $_POST['feedback'];
// 存到数据库,没有 sanitize
update_option('user_feedback', $feedback);
echo "感谢您的反馈: " . $feedback; // 也没有转义
}
// ✅ 修复后的写法
function save_user_feedback_safe() {
// 存储前 sanitize
$feedback = sanitize_textarea_field($_POST['feedback']);
update_option('user_feedback', $feedback);
// 输出前转义
echo "感谢您的反馈: " . esc_html($feedback);
}
这种问题出现的原因是:很多插件开发者(包括我)在写"简单功能"时,会忽略安全处理。心想"这个功能很小,不会有人攻击的"。
但 AI 不会忽略。它扫到 unsafe echo 就会标记,不管功能大小。
2.3 文件上传验证缺失
<?php
// ❌ 有漏洞的写法
function handle_file_upload() {
if (!isset($_FILES['my_file'])) {
return;
}
$upload_dir = wp_upload_dir();
$target = $upload_dir['path'] . '/' . $_FILES['my_file']['name'];
// 直接移动文件,没有任何类型检查
move_uploaded_file($_FILES['my_file']['tmp_name'], $target);
return "文件上传成功: " . $_FILES['my_file']['name'];
}
// ✅ 修复后的写法
function handle_file_upload_safe() {
if (!isset($_FILES['my_file'])) {
return;
}
// 使用 WordPress 自带的文件处理函数
$file = $_FILES['my_file'];
// 检查文件类型(白名单)
$allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
if (!in_array($file['type'], $allowed_types)) {
return "不支持的文件类型";
}
// 使用 wp_handle_upload(自动处理安全检查)
$uploaded = wp_handle_upload($file, ['test_form' => false]);
if (isset($uploaded['error'])) {
return "上传失败: " . $uploaded['error'];
}
return "文件上传成功";
}
三、我检查自己的插件时发现了什么
看完报告后,我花了整整一个五一假期检查自己的 5 个插件。发现了 7 个安全问题,其中 2 个 SQL 注入(可被利用)、3 个 XSS、2 个 CSRF。
以其中一个 SQL 注入为例说明我的检查和修复过程。
3.1 发现过程
这是一个"导出用户数据"的小众功能——用户可以在后台导出自己的数据为 CSV。代码是 3 年前写的,大概逻辑:
<?php
// 原始代码(3 年前)
add_action('wp_ajax_export_user_data', 'handle_export');
function handle_export() {
$user_id = $_POST['user_id'];
$format = $_POST['format'];
// 构造查询
$results = $wpdb->get_results(
"SELECT * FROM {$wpdb->usermeta} WHERE user_id = $user_id"
);
// 输出 CSV
// ...
}
乍一看,$user_id 来自 POST 请求,直接拼到了 SQL 里。这就是一个 SQL 注入。
但等等——我用了 wp_ajax_ 前缀,这意味着用户必须登录才能调用。但这不代表安全——一个登录用户仍然可以注入。
3.2 修复方案
<?php
// 修复后的代码
function handle_export_safe() {
// 1. 验证 nonce(防止 CSRF)
if (!wp_verify_nonce($_POST['_wpnonce'], 'export_user_data')) {
wp_die('安全验证失败');
}
// 2. 权限检查(确保有权限访问)
if (!current_user_can('edit_user', intval($_POST['user_id']))) {
wp_die('您没有权限导出此用户的数据');
}
// 3. 参数化查询(防止 SQL 注入)
$user_id = intval($_POST['user_id']); // intval 已经足够安全
$format = sanitize_key($_POST['format']); // 限制为字母数字
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->usermeta} WHERE user_id = %d",
$user_id
)
);
// 4. 输出前检查结果
if (empty($results)) {
wp_die('没有找到数据');
}
}
3.3 通用检查清单
这次经历后,我给自己写了一个检查清单:
<?php
/**
* WordPress 插件安全检查清单
*
* 每次发布新版本前,逐项检查:
*/
// [ ] 1. 所有 $_GET / $_POST / $_REQUEST 参数处理了吗?
// 用 intval() / sanitize_text_field() / esc_url() 等
// [ ] 2. 所有 SQL 查询使用了 $wpdb->prepare() 吗?
// 不是"看起来安全"——是"确实用了 prepare"
// [ ] 3. 所有输出做了转义吗?
// echo $var → echo esc_html($var)
// 属性输出 → esc_attr()
// URL 输出 → esc_url()
// [ ] 4. AJAX 操作有 nonce 验证吗?
// check_ajax_referer() 或 wp_verify_nonce()
// [ ] 5. AJAX 操作有权限检查吗?
// current_user_can() 检查用户权限
// [ ] 6. 文件上传有类型验证吗?
// 使用 wp_handle_upload() 而不是 move_uploaded_file()
// [ ] 7. 反序列化操作安全吗?
// 避免 unserialize() 用户输入
// [ ] 8. 短标签/过滤器/REST API 端点安全吗?
// 每个公开入口都需要验证
四、AI 审计工具:我怎么自检
4.1 用 AI 审计自己的代码
报告发布后,我尝试用 AI 审计自己的插件代码。过程很简单:
# 1. 收集所有 PHP 文件
find . -name "*.php" -not -path "./vendor/*" > file_list.txt
# 2. 用 AI 逐文件审计
# (简单版本:用 Claude 的 long context)
# 3. 更实用的方式:写一个简单的扫描器
<?php
/**
* AI 辅助的安全检查器
*
* 在代码中标记出需要人工审核的地方
* 不是完整的审计——是辅助发现潜在问题
*/
class SecurityScanner {
private $patterns = [
// 危险的 SQL 模式
'sql_injection' => [
'/\$wpdb->(get_results|get_var|query)\s*\([^)]*\$_(GET|POST|REQUEST)/i',
'/->query\s*\(\s*["\'](?!.*%[sd])[^"\']*\$(?!wpdb)/',
],
// 未转义的输出
'xss' => [
'/echo\s+\$(?!wpdb|this)[^\s;)]*(?<!esc_html|esc_attr|esc_url)["\']?\)?;/',
'/<[^>]*>\s*<\?php\s+echo\s+\$(?!(this->|wp_))[^;]*;/i',
],
// 不安全的文件操作
'file_upload' => [
'/move_uploaded_file\s*\(/i',
'/unserialize\s*\(\s*\$(?!wpdb|this)/i',
],
// 缺少 nonce 的 AJAX 操作
'missing_nonce' => [
'/wp_ajax_/i',
],
];
public function scan_file($file_path) {
$content = file_get_contents($file_path);
$issues = [];
foreach ($this->patterns as $type => $patterns) {
foreach ($patterns as $pattern) {
if (preg_match_all($pattern, $content, $matches, PREG_OFFSET_CAPTURE)) {
foreach ($matches[0] as $match) {
$line = $this->get_line_number($content, $match[1]);
$issues[] = [
'type' => $type,
'line' => $line,
'match' => substr($match[0], 0, 100),
];
}
}
}
}
// 检查是否缺少 nonce
if ($this->has_ajax_handler($content) && !$this->has_nonce_check($content)) {
$issues[] = [
'type' => 'missing_nonce',
'line' => $this->get_ajax_handler_line($content),
'match' => 'AJAX handler without nonce verification',
];
}
return $issues;
}
private function get_line_number($content, $offset) {
$before = substr($content, 0, $offset);
return substr_count($before, "\n") + 1;
}
private function has_ajax_handler($content) {
return preg_match('/wp_ajax_\w+/', $content);
}
private function has_nonce_check($content) {
return preg_match('/check_ajax_referer|wp_verify_nonce/', $content);
}
private function get_ajax_handler_line($content) {
preg_match('/wp_ajax_\w+/', $content, $matches, PREG_OFFSET_CAPTURE);
return $this->get_line_number($content, $matches[0][1]);
}
public function scan_directory($dir) {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir)
);
$all_issues = [];
foreach ($iterator as $file) {
if ($file->getExtension() !== 'php') continue;
if (strpos($file->getPathname(), '/vendor/') !== false) continue;
$issues = $this->scan_file($file->getPathname());
if (!empty($issues)) {
$all_issues[$file->getPathname()] = $issues;
}
}
return $all_issues;
}
}
// 使用
$scanner = new SecurityScanner();
$results = $scanner->scan_directory('/path/to/plugin');
foreach ($results as $file => $issues) {
echo "⚠️ $file\n";
foreach ($issues as $issue) {
echo " 行 {$issue['line']}: [{$issue['type']}] {$issue['match']}\n";
}
}
这个扫描器只能发现模式匹配的问题,不能替代人工审查。但它能快速圈定需要关注的代码区域。
五、我的观点:AI 在安全领域的角色变了
这次报告改变了我对 AI 和安全的看法。
以前我认为:AI 在安全领域是"防御工具"——帮我们发现漏洞、分析日志、生成安全报告。
现在我认为:AI 同样高效地是"攻击工具"。它能比人类快 100 倍地发现可攻击的弱点。
这意味着:
对插件开发者来说,你的代码不再需要"看起来安全"——它需要"对 AI 安全"。
因为攻击者不再需要手动翻你的源码了。他们可以让 AI 扫一遍,然后拿着报告来找可利用的点。哪怕你的插件只有 1,000 次安装,你也不再"太小的目标,没人会攻击"。
我改变了什么?
- 每次提交都跑安全扫描——之前只在发版本前做一次,现在每次 git push 都自动跑
- 用参数化查询替代所有拼接——没有例外,没有任何"这个参数不会外部传入"的假设
- 给所有 AJAX 端点加 nonce 和权限检查——即使是不"需要"的
- 定期用 AI 审计自己的代码——在攻击者用 AI 找到漏洞之前,自己先用 AI 找一遍
结尾
300 个零日漏洞在 72 小时被发现,这个数字本身并不重要。重要的是它告诉了我们两件事:
- WordPress 插件的安全问题比我们想象中更普遍——即使是大牌插件也可能有基础安全漏洞
- AI 让发现这些漏洞的成本降到了几乎为零——这意味着攻击者一定会用
对插件开发者来说,应对方式不是恐慌。是:
- 写好基础安全代码(prepare + escape + nonce + capability)
- 每次提交前跑安全扫描
- 定期用 AI 审计自己的代码——在攻击者之前找到问题
文章由文字工作者编写。报告中提到的 300 个零日漏洞是 2026 年中某次真实安全审计的数字。具体数据和漏洞分类根据公开资料整理。
六、AI 审计流程:我现在的做法
6.1 自动化 CI 流程
我把安全扫描集成到了 GitHub Actions 中:
# .github/workflows/security-scan.yml
name: Security Scan
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: PHP Syntax Check
run: find . -name "*.php" -exec php -l {} \;
- name: Pattern-based Security Scan
run: |
php security-scanner.php scan .
- name: AI-assisted Review (weekly)
if: github.event_name == 'schedule'
run: |
# 将所有 PHP 文件打包发给 AI 审查
find . -name "*.php" -not -path "./vendor/*" > /tmp/php_files.txt
while IFS= read -r file; do
echo "=== Scanning: $file ==="
# 这里调用 AI API 逐文件审查
done < /tmp/php_files.txt
6.2 漏洞修复优先级
不是所有漏洞都需要立即修复。我根据 AI 扫描结果,按这个优先级处理:
| 优先级 | 漏洞类型 | 处理方式 |
|---|---|---|
| 紧急(24h) | SQL 注入、任意文件上传 | 立即修复,发布热修复版本 |
| 高(1周) | 存储型 XSS、权限提升 | 下个版本修复 |
| 中(1个月) | 反射型 XSS、CSRF | 规划到下个迭代 |
| 低(3个月) | 信息泄露、路径遍历 | 列入 backlog |
6.3 向同行学习的建议
结合这次报告和我的亲身经历,给其他 WordPress 插件开发者几条具体建议:
- 不要假设"小众插件不会被攻击"——AI 审计是自动化的,没有"目标太小"这一说
- 别相信"这个参数不会外部传入"——今天不会,明天添加一个 AJAX endpoint 后就会了
- 用 WordPress 提供的安全函数——
$wpdb->prepare()、esc_html()、wp_verify_nonce()、current_user_can()——它们比你自己实现的方案更可靠 - 每次新增公开接口(AJAX / REST API / Shortcode)都做一次安全检查
- 定期用 AI 审计代码——Claude 或 GPT 都可以,把代码贴进去让它找安全问题
七、关于 WordPress 生态的思考
这次报告也暴露了 WordPress 生态的一个系统性问题:插件的安全审核机制不够严格。
目前 WordPress.org 的插件审核:
- 新提交的插件会做人工代码审查
- 但审核不保证完全覆盖安全
- 已经上线的插件很少做回溯审查
- 用户的安装量不会因为安全问题自动降权
这和 App Store 或 Chrome Web Store 的自动安全扫描机制不同。WordPress 插件市场本质上是一个"自愿申报"的系统——插件的安全性主要取决于开发者自己的安全意识。
AI 审计工具的出现,某种程度上在倒逼这个生态变得更好。因为:
- 安全研究团队用 AI 批量发现漏洞并报告
- WordPress.org 会要求开发者限期修复
- 不修复的插件会被下架
这个机制已经在运行了——只是很多开发者还没意识到。