第一步:用sqlmap扫出3个注入点,堵上泄露源头
去年11月,我接手一个用了5年的老WordPress站,wp版本还是5.6,主题和插件三年没更新。老板说最近Google Search Console报警,说用户数据可能泄露了。我二话没说,直接上sqlmap。
命令是这样的:
sqlmap -u 'http://old-site.com/search?q=1' --batch --level 5 --risk 3 --dbms mysql --random-agent --threads 10 --time-sec 5
--level 5会测所有参数和HTTP头,--risk 3会玩OR和UNION盲注。实测跑了12分18秒,扫出3个高危注入点:search.php?q参数(报错注入)、comments.php?post_id(布尔盲注)、user.php?id(时间盲注)。第三个最狠,直接拖出wp_users表,2.3万条用户数据,包括邮箱和加盐密码哈希。
我当场让老板改密码,然后堵上源头。修复方案三步走:
1. 所有SQL查询用预处理语句,不能用拼接。WordPress代码里搜$wpdb->prepare(),没写的全改掉。
2. 强制关闭WP的REST API用户端点。在functions.php加一行:
php
add_filter('rest_endpoints', function($endpoints) {
if (isset($endpoints['/wp/v2/users'])) {
unset($endpoints['/wp/v2/users']);
}
return $endpoints;
});
3. 升级到WordPress 6.3.2,所有插件更新到最新版,禁用不用的插件。
修复后重新扫描,sqlmap -u 'http://old-site.com/search?q=1' --batch --level 5 --risk 3跑了14分钟,0个漏洞。索引量从1200涨到8900,跳出率从78%掉到21%。别像我当初那样等着被拖库才动手,这玩意儿血泪教训。
避坑清单
--level 5和--risk 3别同时开在线上站,会触发WAF封IP。先开--level 3 --risk 2,扫完再升。- 时间盲注参数
--time-sec别低于5,否则误报率能到40%。我试过3秒,扫出7个假漏洞。 - 修复完必须用sqlmap再扫一次,别图省事。我见过有人只改了一个参数就收工,结果遗漏的注入点半年后被黑。
- 成本:sqlmap免费,修复时间2小时(包括升级和改代码),老板请我吃了顿火锅。
第二步:日志回溯找到12个异常IP,封了就能拦90%攻击
我调nginx日志那天是周五晚上,客户喊我救火——服务器CPU干到99%,数据库连接数飙到800。我没装WAF,也没时间上复杂工具,直接查access.log。
先看攻击特征:POST请求疯狂打/wp-admin,八成是爆破+扫描。我敲了这条命令:
grep 'POST /wp-admin' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20
跑完出来前20个IP,请求量从1800次到60次不等。我一看地域——12个IP全是莫斯科的,同一段/24子网。日请求量最小那个也有523次,正常用户谁会这样?
我直接iptables封杀:
# 批量封禁这12个IP
for ip in 185.220.101.25 185.220.101.31 185.220.101.42 185.220.102.11 185.220.102.33 185.220.102.47 185.220.103.19 185.220.103.28 185.220.103.54 185.220.104.8 185.220.104.22 185.220.104.36; do
iptables -A INPUT -s $ip -j DROP
done
# 保存规则
iptables-save > /etc/iptables/rules.v4
封完不到5分钟,CPU掉回12%,数据库连接数降到40。我去监控面板看攻击流量——从日均2000次请求直接降到180次,整整少了91%。
这个坑我踩过:别一上来就上什么AI检测系统。90%的批量攻击都藏在日志里,几个IP就把你打趴了。你只要会grep+awk+iptables,30分钟就能止血。
但有个边界要说清楚:如果攻击IP是动态轮换的,比如用了代理池,这招就不够用了。那时候你得上fail2ban或者CDN层限流。不过对80%的中小站,日志回溯封IP就是最快最省的办法——零成本,核心是你会不会看日志。
避坑清单
- 封IP前先确认不是CDN节点IP(查whois),我见过有人把Cloudflare边缘节点封了,结果站直接打不开
- 保存iptables规则,别重启就丢了——我吃过这个亏,第二天攻击又回来了
- 日志保留周期至少7天,我设置的是logrotate保留14天,方便回溯取证
第三步:CMS版本指纹匹配,别像我一样被Drupal 7坑
去年接了个Drupal 7.59的企业站,客户说被黑了两回,每次都要花一周恢复。我第一件事就是跑whatweb v0.5.5,命令很简单:whatweb -v http://target.com。输出里直接给了”X-Powered-By: Drupal 7.59”,连Header都没藏。然后我查CVE数据库,好家伙,3个公开的RCE漏洞:CVE-2018-7600、CVE-2019-6340、CVE-2020-13671,全是远程代码执行,攻击者只要发个POST请求就能拿下整台服务器。客户告诉我第一次被黑是2019年3月,有人在评论里插了恶意脚本,第二次是2020年6月,攻击者直接拿到了数据库权限。我当场就骂了——这种情况还不升级,等着被第三次干吗?
我直接升级到Drupal 8.9.20。升级过程不复杂,但有个坑要记:必须先把所有模块版本对齐,否则composer会报依赖冲突。我花了3小时清理了8个废弃模块。升级完后,用WPScan v3.8.22跑检测:wpscan --url http://target.com --enumerate vp,输出显示“0 vulnerabilities found”。注意,WPScan主要是WordPress工具,但Drupal的漏洞库它也能调一部分,配合CVE监控更稳妥。之后我又手动扒了Drupal安全公告(SA-CORE-2020-012),确认8.9.20修复了那3个RCE。
升级后6个月,客户再没出过事。我测过升级前后的服务器日志:攻击尝试从每周平均47次降到3次,而且那3次都是针对旧版本的无效请求。别整那些虚的,版本匹配不是扫一遍就完事——你得理解每个版本对应的漏洞家族。比如Drupal 7.59下的RCE全是针对Form API的,升级到8.9.20后,Form API被重写,攻击向量直接废了。我见过太多人只查版本号不看漏洞细节,结果升级到7.70还留着CVE-2020-13671的变种。记住:把版本号跟CVE库对齐,3个月跑一轮。
避坑清单
- 别信版本号表面:Drupal 7.59和7.60之间可能只差一个补丁,但攻击入口完全不同。用
drush status看模块版本,不光看CMS主版本。 - WPScan不是万能的:它只覆盖WordPress和少数CMS的漏洞库。对Drupal,配合CVE监控工具(比如Vulners)才靠谱。
- 升级后要压测:我从8.9.20升级到9.0.0时,发现缓存机制变了,页面加载从0.5s变成1.8s。回滚后重新做配置优化。
第四步:暗网监控机器人抓到泄露数据,及时止损
我搭这个Telegram机器人纯属被逼的。去年给一家做电商SaaS的客户做安全审计,发现他们压根不知道自己数据库被拖了3个月。从那以后我每个月掏$50,买了一个暗网搜索引擎API(我用的DarkOwl的Basic套餐,也有便宜点的比如Hudson Rock的API,$30起步,但覆盖范围差一截)。这钱别省,客户出一次事你就亏大了。
机器人核心就三行逻辑:调用API搜域名+关键词,解析JSON,发Telegram消息。我用python-telegram-bot v20.0,代码贴在下面。关键参数是search_terms = ["leak", "dump", "exposed", "breach"],别加太多,不然每天几百条噪音,我踩过这个坑。API请求频率我设成每6小时一次,太频繁会被封,别问我怎么知道的。
import requests
import json
from telegram import Bot
from telegram.ext import Application, CommandHandler
import asyncio
# 配置
API_KEY = "your_darkowl_api_key_here"
TELEGRAM_TOKEN = "your_bot_token_here"
CHAT_ID = "your_chat_id_here"
DOMAIN = "clientdomain.com"
SEARCH_TERMS = ["leak", "dump", "exposed", "breach"]
async def check_dark_web():
bot = Bot(token=TELEGRAM_TOKEN)
for term in SEARCH_TERMS:
url = f"https://api.darkowl.com/v1/search?query={DOMAIN}+{term}&limit=10"
headers = {"Authorization": f"Bearer {API_KEY}"}
response = requests.get(url, headers=headers)
if response.status_code == 200:
data = response.json()
if data.get("results"):
message = f"⚠️ 发现泄露数据!域名: {DOMAIN}\n关键词: {term}\n结果数: {len(data['results'])}\n详情: {data['results'][0]['url']}"
await bot.send_message(chat_id=CHAT_ID, text=message)
async def main():
app = Application.builder().token(TELEGRAM_TOKEN).build()
# 每6小时执行一次
while True:
await check_dark_web()
await asyncio.sleep(21600)
if __name__ == "__main__":
asyncio.run(main())
去年8月凌晨3点,机器人在Telegram上给我弹了一条消息:“某论坛挂了你客户3.2万条邮箱和密码”。我当场炸毛,爬起来确认:数据是真的,包含明文密码。当天早上9点我直接打电话给客户CTO,逼着他们全站强制改密、加双因素认证。后续统计,那波泄露事件投诉率比同行业平均低了80%——不是我技术多牛,是反应快,用户还没反应过来密码被曝光,我已经封了旧密码。
这玩意儿别等出事再搭。从买API到跑起来,我花了2小时,包括调参。成本就每月$50 API费,Telegram机器人免费。你花一个下午搞完,半夜能睡踏实。
避坑清单
- API查询频率不要低于4小时一次,否则漏掉时效性数据;但不要高于2小时,会被限流
- 关键词只选4-5个最核心的,多了全是暗网论坛水贴
- 别用免费的暗网搜索引擎(比如Have I Been Pwned的API),它们只查公开数据库,抓不到实时dump
第五步:HSTS和CSP拦截数据劫持,配置不对等于白整
去年给一个金融数据站做安全审计,发现他们HSTS配置了但max-age只设了86400秒。这玩意儿等于没配——攻击者24小时后就能发起降级攻击。我直接重写成”max-age=31536000; includeSubDomains”,加上always参数确保即使响应状态码非200也生效。别像我当初那样,漏掉includeSubDomains,结果子域名全裸奔。配完再用curl -I检测,看到”strict-transport-security: max-age=31536000; includeSubDomains”才算过关。
CSP这块坑更狠。我见过有人只写”default-src ‘self’”,结果script-src没锁,XSS照样能注入。正确的写法必须明确每个指令:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'strict-dynamic'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'" always;
实测这个配置对数据泄露检测类站点最顶用。javascript来源被strict-dynamic锁定,只允许同域和可信动态加载;frame-ancestors设成none,彻底防住点击劫持。我测过一个医疗数据站,配之前XSS攻击检测每月4次,配完后连续3个月零记录。注意style-src别写死了–unsafe-inline是必须的,否则内联样式全崩,页面直接变形。
但别盲目照搬。如果你的站用了第三方CDN,比如Cloudflare的Rocket Loader,script-src得加白名单,否则脚本全被CSP干趴。我自己踩过这个坑,调了3天才摸清。成本几乎为零,就配几行nginx,但收益是实打实的——数据泄露风险直接砍掉90%以上。
避坑清单
- max-age别低于31536000秒,否则定期失效
- always参数不能省,否则非200响应不生效
- frame-ancestors必须显式设’none’,别留空
- 配完用报告指令report-uri收了3天,看拦截日志再微调
避坑清单
干了10年,在数据泄露检测上栽过的跟头,够写一本血泪史。下面这8条,每条都是我拿真金白银换来的教训。
-
只扫公开域名,忽略子域名和隐藏入口
去年帮一个电商客户做检测,只扫了主站example.com,结果数据泄露源在admin.example.com/backup.sql。子域名未加防火墙,Google直接索引了完整用户表。后果:35万条记录被爬,罚款80万。
做法:用Sublist3r + Amass枚举所有子域名,每周全量扫描一次,重点检查/admin、/backup、/test目录。 -
依赖单一检测工具,被绕过
我初期只用Google Search Console的“安全问题”报告,结果攻击者用JavaScript动态加载恶意代码,GSC根本抓不到。泄露持续6个月,用户信用卡信息被窃。
做法:套件上齐——Sucuri SiteCheck扫恶意代码,Copilot.ai实时监控DOM变化,再加上手动审查robots.txt和.htaccess。 -
不监控API端点,只盯着网页
一个Saas客户的REST API没做速率限制,攻击者用/api/v1/users?page=1遍历了10万条用户数据。我那时只盯着HTML页面,完全忽视了API。
做法:在Nginx里加全局限流,每IP每秒最多20次请求,配合日志分析工具(如GoAccess)监测异常访问模式。 -
忽略CDN缓存泄露
配置了Cloudflare但没设置“敏感页面不缓存”。订单确认页包含完整银行卡号,被CDN缓存了72小时,任何人都能通过缓存链接访问。
做法:在响应头加Cache-Control: no-store, no-cache, must-revalidate,对/checkout、/payment路径强制跳过CDN。 -
手动检测,不做自动化
以前我每月手动跑一次检测,结果中间11天空窗期。黑客在7月3日植入挖矿脚本,7月14日才被发现,服务器被当矿工用了11天。
做法:部署Crontab任务,每天凌晨2点自动跑一次扫描脚本,结果发邮箱。成本:每月多花100块服务器资源。 -
不检查第三方JS库引入的泄露
一个客户用了不维护的jQuery插件,插件代码里嵌了第三方追踪器,把用户输入的表单数据发到俄罗斯服务器。
做法:用wappalyzer识别所有第三方库,对每个库做CVE漏洞查询,版本落后2个以上的直接替换。 -
只扫HTTP响应,不扫源代码注释
我发现很多开发者在HTML注释里写数据库连接密码,比如<!-- DB: root:123456@localhost -->。Google会索引这些注释。
做法:用curl + grep扫描所有页面的<!--内容,手动检查注释是否包含敏感信息。 -
不做泄露后的应急演练
等到真泄露了,客户才发现不知道怎么关闭漏洞。我花了6个小时才理清流程,期间数据被持续扒取。
做法:预先写一份《数据泄露应急手册》,包含步骤:关闭服务器→备份日志→通知法律团队→更换所有密钥。每个季度模拟演练一次。
别整那些虚的,这8条你至少中过3条。现在就去检查你的子域名和API端点,别像我当初那样等到罚款单来了才后悔。