第一届好靶场MISC-SQL流量分析

第一届好靶场MISC-SQL流量分析

SQL流量分析,也是拖了三天才写出来(悲)

第一届好靶场MISC-SQL流量分析

SQL(Structured Query Language,结构化查询语言)是用于访问和管理关系型数据库(如 MySQL、PostgreSQL、SQL Server、Oracle 等)的标准语言。

学习 SQL,最核心的是掌握它的四大分类,其中最常用的是 DQL(数据查询) 和 DML(数据操作)。

下面为你系统梳理最常见、最核心的 SQL 语法:

1. SQL 基础规则

  • 不区分大小写:SELECT 和 select 效果一样,但为了可读性,通常关键字大写,表名和列名小写。
  • 分号结尾:每条完整的 SQL 语句通常以分号 ; 结束。
  • 单引号:文本字符串和日期类型的值需要用单引号括起来(例如 '张三'),数字不需要。

2. DQL:数据查询语言(最常用的部分)

这是 SQL 中使用频率最高的语法,用于从数据库中提取数据。

基础查询:

sql
SELECT column1, column2 
FROM table_name;

(如果想查询所有列,可以使用 SELECT \* FROM table_name;)

条件过滤 (WHERE):

sql
SELECT name, age 
FROM users 
WHERE age > 18 AND status = 'active';

排序 (ORDER BY):

sql
SELECT name, score 
FROM students 
ORDER BY score DESC; -- DESC 为降序,ASC 为升序(默认)

限制返回数量 (LIMIT):

sql
SELECT * 
FROM orders 
ORDER BY create_time DESC 
LIMIT 10; -- 只返回最新的 10 条记录

分组与聚合 (GROUP BY & 聚合函数):

常用的聚合函数包括 COUNT() 计数、SUM() 求和、AVG() 平均值、MAX()/MIN() 最大最小值。

sql
SELECT department, COUNT(*) as employee_count 
FROM employees 
GROUP BY department;

3. DML:数据操作语言(增、删、改)

用于向表中添加、更新或删除数据。

插入数据 (INSERT INTO):

sql
INSERT INTO users (username, email, age) 
VALUES ('张三', 'zhangsan@example.com', 25);

更新数据 (UPDATE):

⚠️ 危险警告:更新数据时一定要加 WHERE 条件!如果不加,会把整张表的所有数据都改掉。

sql
UPDATE users 
SET age = 26, status = 'inactive' 
WHERE username = '张三';

删除数据 (DELETE):

⚠️ 危险警告:同理,删除数据务必带上 WHERE 条件。

sql
DELETE FROM users 
WHERE id = 1001;

4. 多表连接 (JOIN)

在实际业务中,数据通常分散在不同的表里,需要用到连接查询。最常用的是内连接(INNER JOIN)和左连接(LEFT JOIN)。

SQL

sql
SELECT users.name, orders.order_no, orders.amount
FROM users
JOIN orders ON users.id = orders.user_id; 
-- 通过 user_id 将 users 表和 orders 表关联起来

5. DDL:数据定义语言(表结构操作)

用于创建、修改或删除数据库和表本身。

创建表 (CREATE TABLE):

sql
CREATE TABLE products (
    id INT PRIMARY KEY,         -- 主键,唯一标识
    name VARCHAR(100) NOT NULL, -- 字符串类型,不能为空
    price DECIMAL(10, 2),       -- 小数类型,保留两位
    created_at DATE             -- 日期类型
);

删除表 (DROP TABLE):

sql
DROP TABLE products; -- 直接把整张表和里面的数据一起抹除

常见的SQL注入

① 引号的用法

category = '计算机' ← 字符串用单引号包起来

攻击者利用这一点:如果网站直接把用户输入拼进 SQL:

-- 正常:

sql
SELECT * FROM books WHERE category = '计算机'

-- 攻击者输入: 计算机' OR 1=1-- -- 变成了:

sql
SELECT * FROM books WHERE category = '计算机' OR 1=1--'

注意看: ' 把前半的引号闭合了,后面的 OR 1=1 就成了可执行的 SQL 代码,-- 把剩下的引号变注释。

② 注释符(-- 和 #)

sql
--     SQL标准注释,后面所有内容忽略(注意后面要跟空格!)

\#     MySQL专属注释

/* */  多行注释

你在流量包里看到 -- xxxx(随机字符)就是注释,攻击者用它来"砍掉"原 SQL 语句后半截。

③ UNION / UNION ALL(合并查询)

sql
SELECT 1, 2, 3
UNION ALL
SELECT 'a', 'b', 'c'

规则: 上下两个 SELECT 的列数必须相同。攻击者用 UNION 往结果里夹带私货。


④ CHR() / ASCII 编码

CHR(113) || CHR(106) || CHR(118) -- 返回 'qjv'

CHR(数字) 把数字转成对应字符:

  • CHR(65) = 'A'
  • CHR(97) = 'a'
  • CHR(39) = ''`(单引号)

|| 是字符串拼接。攻击者为什么自找麻烦?——因为有些字符在 URL 里不能直接传,用数字就稳了。


⑤ information_schema(偷表结构)

-- 查所有数据库名

sql
SELECT schema_name FROM information_schema.schemata

-- 查某个库的所有表名

sql
SELECT table_name FROM information_schema.tables WHERE table_schema='数据库名'

-- 查某个表的所有列名

sql
SELECT column_name FROM information_schema.columns WHERE table_name='表名'

这是sqlmap最核心的操作——先枚举你有什么表,再枚举表有什么列,最后把数据掏空。

手法特征流量包里的例子
闭合测试' " ) '))test' → 看页面报不报错
布尔盲注AND 1=1 vs AND 1=2category=test' AND 4820=5440 AND 'DgnK'='DgnK
时间盲注SLEEP(5) PG_SLEEP(5)category=test';SELECT PG_SLEEP(5)--
UNION注入UNION ALL SELECT NULL,...category=-1319' UNION ALL SELECT CHR(113)...
报错注入CASE WHEN ... THEN ... ELSE ... ENDcategory=(CASE WHEN (8011=9069) THEN 8011 ELSE 8011(SELECT 8011...) END)
堆叠查询; SELECT ...分号隔开多条SQL语句

四、怎么区分不同的数据库?

不同数据库语法略有不同,攻击者往往先发几个探针来判断:

数据库特有函数/语法流量包里出现
MySQLSLEEP(5)、information_schema、#注释✅ 大量使用
PostgreSQLPG_SLEEP(5)、CHR()
SQL Serverxp_cmdshell、WAITFOR DELAY '0:0:5'✅ xp_cmdshell 也出现了
SQLitesqlite_master没出现

Q1:提交攻击者的IP

shell
192.168.9.25

可以发现全部流量都来自192.168.9.25这个地址,点击就送了

image-20260730182125315


Q2:攻击者使用了什工具进行攻击?请提交工具名称和版本

shell
sqlmap/1.0#stable

image-20260730182300612可以发现的是流量都是http协议,筛选HTTP协议明文信息

在No.62中我们发现出现了sqlmap

点击放大,点击空白处返回
点击放大,点击空白处返回

Q3:攻击者针对哪个参数进行了注入?

text
category

我们先研究一下sqlmap注入了什么参数信息吧::pic

点击放大,点击空白处返回
点击放大,点击空白处返回

%打头的都是url协议,我们需要进行转化

url
9937%20AND%201%3D1%20UNION%20ALL%20SELECT%201%2CNULL%2C%27%3Cscript%3Ealert%28%22XSS%22%29%3C%2Fscript%3E%27%2Ctable_name%20FROM%20information_schema.tables%20WHERE%202%3E1--%2F%2A%2A%2F%3B%20EXEC%20xp_cmdshell%28%27cat%20..%2F..%2F..%2Fetc%2Fpasswd%27%29%23

转义之后

sql
9937 AND 1=1 UNION ALL SELECT 1,NULL,'<script>alert("XSS")</script>',table_name FROM information_schema.tables WHERE 2>1--/**/; EXEC xp_cmdshell('cat ../../../../etc/passwd')#

这就是很典型的sql语法了,出现了关键词AND SELECT这些语句查询

片段解释
9937 AND 1=1凑语法用的,让前面的条件成立
UNION ALL SELECT关键操作——UNION 可以把查询结果合并,攻击者想通过它从数据库里偷数据
1, NULL, '<script>alert("XSS")</script>', table_name选了4列数据,其中第3列塞了段 XSS 代码,第4列直接从数据库里取表名
FROM information_schema.tablesMySQL 里存储所有表名的地方——攻击者在列举你有哪些表
WHERE 2>1总是真,相当于 "给我全部"
--/**/; EXEC xp_cmdshell(...)前半 --/**/ 是注释掉后面,但这里还塞了 xp_cmdshell——这是 SQL Server 执行系统命令的功能,想读服务器上的密码文件

仔细观察可以发现http请求和参数

category参数出现了多次

wireshark
http.request.uri contains "category="
点击放大,点击空白处返回
点击放大,点击空白处返回

Q4.该注入使用了什么数据库函数?

shell
PG_SLEEP

HTTP 基础

GET /search?q=&category=test HTTP/1.1

  • GET = 请求方式(去拿数据)
  • /search = 页面路径
  • ? 后面的 = 查询参数,格式:参数名=参数值,多个用 & 隔开
  • 参数会传给后端代码使用

HTTP 请求报文的结构非常严格,无论是用浏览器正常访问,还是用 Burp Suite 等工具抓包改包,面对的都是这四个核心部分:请求行、请求头、空行、请求体。


1. 请求行 (Request Line)

请求报文的第一行,由三个要素组成,中间用空格隔开:

  • 方法 (Method): 比如 GET、POST、OPTIONS 等。它决定了对资源的操作意图。
  • URL / URI: 请求的目标路径(这里是 /login.php)。
  • 协议版本: 声明通信使用的协议,通常是 HTTP/1.1 或 HTTP/1.0。

2. 请求头 (Request Headers)

紧跟在请求行之后,由多行 Key: Value 格式的字段组成,是客户端向服务器传递附加信息的载体。在 CTF 的 Web 安全方向中,这里往往是漏洞利用的“重灾区”:

  • Host: 指定目标服务器的域名和端口(HTTP/1.1 强制要求包含)。
  • User-Agent: 声明客户端类型、操作系统及版本(有时会成为 SQL 注入的测试点)。
  • Cookie: 携带会话凭证,用于维持状态。
  • X-Forwarded-For (XFF): 虽非标准字段,但常用于识别通过 HTTP 代理连接的客户端原始 IP(经常被用来伪造 IP 绕过限制)。
  • Content-Type / Content-Length: 告诉服务器请求体的数据格式(如 JSON、表单)和具体字节长度。

3. 空行 (Blank Line)

在协议解析中,这是极其关键的一环。 在最后一个请求头之后,必须有一个没有任何内容的空行。在底层内存和网络字节流中,它的十六进制表示是 0x0D 0x0A,即回车换行符(CRLF,\r\n)。

Web 服务器(如 Nginx、Apache)的解析器就是靠读取到这个连续的 \r\n\r\n 来判断“请求头结束了,接下来的数据属于请求体”。如果通过代理服务器转发时,攻击者巧妙地操控了数据包中的换行符,就可能引发 HTTP 请求走私 (HTTP Request Smuggling) 或 CRLF 注入攻击。


4.请求体 (Request Body)

实际传输的数据载荷。

  • 如果是 GET 请求,通常没有请求体(参数直接拼接在请求行的 URL 后面,例如 ?id=1)。
  • 如果是 POST 或 PUT 请求,这里就会存放真实的表单数据、JSON 字符串或上传的二进制文件。请求体的内容必须与请求头中的 Content-Type 和 Content-Length 严格匹配,否则服务器可能会拒绝解析或导致进程阻塞。

底层视角: 在 TCP/IP 协议栈中,这一整块有着特定格式的文本字符串,最终都会作为应用层的载荷(Payload),被打包塞进 TCP 的数据段中,等待 TCP 三次握手完成后,推送到服务器的指定端口。


URL 编码

URL 里有些特殊字符不能直接出现,需要编码:

字符编码原因
'%27会和 URL 边界混淆
空格%20URL 不允许有空格
=%3D参数值里的等号
--%2D%2DSQL 注释符号

Web 后端如何工作

用户输入 → HTTP请求 → 服务器代码 → SQL查询 → 数据库 → 返回结果 → 显示页面

典型代码(Python 伪代码):

用户通过URL传进来的 category 值

code
category = request.GET['category']   # ← 从HTTP参数取值

直接拼进SQL语句(有漏洞!)

sql
sql = "SELECT * FROM books WHERE type = '" + category + "'"

发送给数据库执行

result = database.query(sql)

关键: 如果 category = 计算机' OR 1=1--,拼出来的 SQL 就变成了:

SELECT * FROM books WHERE type = '计算机' OR 1=1--' ' 闭合了前面的引号,OR 1=1 成了有效代码,-- 把后面注释掉。这就是SQL注入的本质。


SQL 注入核心概念

1.引号闭合
sql
WHERE type = '用户输入'
用户输入 ' OR 1=1-- →
WHERE type = '' OR 1=1--'
       ↑闭合↑  ↑新代码↑↑注释↑
2. 注释符
字符编码原因
'%27会和 URL 边界混淆
空格%20URL 不允许有空格
=%3D参数值里的等号
--%2D%2DSQL 注释符号

上面这些都是科普,我们找常见用到的数据库函数就行

攻击者如果用了某个数据库函数,那函数名一定会出现在 HTTP 请求的 URL 里。你直接在 Wireshark 里搜函数名,命中的就是攻击者用过的。

你要搜的说明
SLEEPMySQL 时间盲注
PG_SLEEPPostgreSQL 时间盲注
CHR数字转字符
CASE条件判断
UNION合并查询
SUBSTRING截取字符串
LENGTH取长度
DATABASE当前数据库名
VERSION数据库版本
CONCAT拼接字符串

wireshark
http.request.uri contains "PG_SLEEP"

PG_SLEEP 次数7606

CHR 次数7401

CASE次数 7340

UNION次数170

SUBSTRING次数7260

LENGTH次数0

VERSION次数9

CONCART次数2

函数次数说明揭示的信息
CHR7401数字→字符数据提取靠 CHR() 拼字符串
SUBSTRING7260截取字符串逐字符地提取数据
PG_SLEEP7606PostgreSQL 延迟函数用的是时间盲注
CASE7340条件判断也用布尔盲注
UNION170合并查询UNION注入只试了一小部分
VERSION8数据库版本只查了几次版本(指纹识别)
CONCAT2字符串拼接只出现2次(MySQL风格,几乎没用)
LENGTH0取字符串长度没用

PG_SLEEP(7306) + CASE(7340) + SUBSTRING(7260) + CHR(7401) 都是 7600 级别,而 UNION 只有 170。

这说明什么?——攻击者绝大部分时间在一比特一比特地猜数据(盲注),而不是一次性把数据查出来(UNION注入只用了170次,试了试没成主力)。

盲注的典型流程:

  1. 用 SUBSTRING(字段, 位置, 1) 取第1个字符
  2. 用 CASE WHEN (取到的字符=某个值) THEN PG_SLEEP(5) ELSE 0 END
  3. 页面延迟5秒 → 猜对了;秒回 → 猜错了
  4. 换一个字符继续...把整段数据一位一位猜出来

结论2:目标数据库 = PostgreSQL

PG_SLEEP 是 PostgreSQL 专属函数(MySQL 的叫 SLEEP),而且用了 7300+ 次,是绝对主力。

反观 MySQL 风格的 CONCAT 只有 2 次——攻击者早期试探过 MySQL,发现不对就放弃了。

证据链: PG_SLEEP 7306次 ← PostgreSQL 专属,铁证 CHR + || 拼接 ← PostgreSQL/Oracle 风格 CONCAT 2次 ← MySQL 风格,几乎没被用

▎ PG_SLEEP()、CHR()、SUBSTRING()、CASE WHEN,指向 PostgreSQL 数据库的时间/布尔盲注。


Q5.攻击者枚举出数据库中共有几张表?

攻击者每提取一张表名,就会发一组"猜字符"的请求。你顺着请求里 OFFSET 的编号,就能数出有几张表。

shell
5
wireshark
http.request.uri contains "pg_tables"

http.request.uri:这是 Wireshark 定义的协议字段。它特指客户端发起的 HTTP 请求(Request)中的请求行里的 URI 部分。比如对于请求 GET /index.php?id=1 HTTP/1.1,这里的 URI 就是 /index.php?id=1。

contains:这是 Wireshark 的过滤操作符,表示“包含”。它不需要完全匹配整个字段,只要字段中出现了后面的字符串,就会把该数据包筛选出来。

"pg_tables":这是你要查找的特定目标字符串。

为什么是uri?uri又是什么?

所有的 URL 都是 URI,但并非所有的 URI 都是 URL。

核心区别:标识 (Identifier) vs 定位 (Locator)

  • URI (统一资源标识符):它的唯一目的是“证明这个资源是谁”。它不在乎你用什么方式去获取这个资源,也不关心它的物理位置,只要能唯一区分它就行。
    • 现实比喻:你的身份证号(能证明你是谁,但光看号码不知道你现在身处何地)。
  • URL (统一资源定位符):它不仅能证明资源是谁,还必须“提供具体的访问路径和协议”,明确告诉你用什么方法去哪里获取它。
    • 现实比喻:你的详细收货地址加上物流方式(比如:顺丰快递送到北京市海淀区某某路1号)。
概念在 HTTP 请求中的体现核心特征
URI请求行中的 /article.php?id=1仅标识了服务器内部的资源路径。它没有提供协议类型(如 HTTP)和目标主机名。
URL完整的 [http://192.168.1.100/article.php?id=1](http://192.168.1.100/article.php?id=1)包含了访问协议 (http://)、主机位置 (192.168.1.100) 和具体路径。这是一个完整的网络坐标。

对No.4663进行 info的url解析image-20260801142512507

SQL翻译是统计多少个不同的命名空间,还记得我们这个数据库是哪一个咩?PG_SLEEP是PostgreSQL的特有函数

tablename 就是 PostgreSQL 的 pg_tables 视图里,专门存放"表的名字"那一列

扩展一下别的数据库视图

数据库"名单"叫什么"表名字"列叫什么
PostgreSQLpg_tablestablename
MySQLinformation_schema.tablesTABLE_NAME
SQL Serversys.tables / INFORMATION_SCHEMA.TABLESname

所以我们找找语句包含tablename吧

wireshark
http.request.uri constains "tablename"

对No.7071的url进行解析image-20260801162248103

还得在了解一个语法就是SQL的跳转语法

SQL
OFFSET N  -- 跳过前 N 行

OFFSET 只是"跳过",它本身不取任何数据。 单看 OFFSET,它只是把游标往后挪。

LIMIT 是 SQL 中用来限制查询结果返回行数的语法。

sql
SELECT tablename FROM pg_tables
WHERE schemaname='public'
ORDER BY tablename
OFFSET 0 LIMIT 1    -- 跳过0行,然后取1行 → 第1行
OFFSET 1 LIMIT 1    -- 跳过1行,然后取1行 → 第2行
OFFSET 2 LIMIT 1    -- 跳过2行,然后取1行 → 第3行
Wireshark
http.request.uri contains "tablename" and http.request.uri contains "OFFSET%200"

%20在url是空格意思,%20 0是选出第一张表

可以发现存在%200-%204说明是5张表

对No.7195进行解析(需要右键选择复制再选择摘要为文本)

点击放大,点击空白处返回
点击放大,点击空白处返回
sql
GET /search?q=&category=test'          ← 闭合引号
AND 9996=(CASE WHEN (                   ← 布尔包装
    ASCII(SUBSTRING((
        SELECT tablename                ← 提取表名
        FROM pg_tables
        WHERE schemaname='public'
        ORDER BY tablename
        OFFSET 0 LIMIT 1                ← ① 第1张表
    )::text FROM 1 FOR 1))              ← ② 第1个字符
    > 341578)                           ← ③ 阈值(探路值)
THEN (SELECT 9996 FROM PG_SLEEP(1))     ← ④ 真→睡1秒
ELSE 9996 END)
AND 'obFL'='obFL                        ← 收尾凑语法
位置内容大白话含义
①OFFSET 0锁定表:跳过前面 0 张表,精准提取名单上的第 1 张表的名字。
②FROM 1 FOR 1锁定字符:这是 SUBSTRING(字符串 FROM 起始位置 FOR 长度) 的标准语法。意思是从第 1 个位置开始,只截取 1 个字符。
③> 341578猜数字游戏:设定的对比阈值(探路值)。在这个判断中,如果前一个字符转换成的值大于 341578,条件就成立。
④PG_SLEEP(1)信号回传:如果前面的条件成立,就让 PostgreSQL 数据库“睡” 1 秒钟,从而让网页加载变慢,以此作为“猜对了”的接头暗号。

观察可以发现7670之后请求间隔了1s,说明7670的语句判断正确触发了PG_SLEEP(1)image-20260801174839153

看看No.7670发生了什么语句吧

image-20260802150002837

第一个字符的ascii大于>64成立,二分查找通过

No.7685的ASCII > 96通过

No.7716 ASCII>104不成立

区间收缩96,104

No.7748 ASCII>98不成立

收缩区间96,98

No.7764 ASCII > 97成立

收缩区间97,98

No.7780 ASCII != 98 成立

说明这个值就是98 (b)

ASCII表格查询链接:

以此类推找到!=判断就行

No.7795 ASCII > 96

No.7827 ASCII > 104

No.7843 ASCII > 108

No.7859 ASCII > 110

No.7875 ASCII > 111 不成立

范围收缩110,111

No.7891 ASCII != 110 不成立

第二个ASCII值是111 (0)

No.7905 ASCII > 96成立

第三个字符是ASCII = 111 (0)

...以此类推(其实发现sleep之后发现ASCII突然变小了就回到上一条去看看是不是!=)

第四个字符是ASCII = 107 (k)

第五个字符是ASCII = 115 (s)

第六个字符是ASCII = 0 (空格)

结合就是books

以此类推

这五张表达的名字就是 Books,borrow_records,notices,secret_flag,users


Q6:攻击者从哪张表格获取了flag?提交表名

shell
secert_flag

从名字我们可以推断出是secret_flag了,不过找找证据看看

wireshark
http.request.uri contains "secret_flag"

筛选出来的内容对No.16426分析 image-20260802160537556

数一下 secret_flag 表有几行,看看结果数字的第一个字符是不是比'3'大。是的话睡1秒告诉我,不是就秒回。

No.16473告诉我们这个行只有1行

然后看到这个数据表是有434匹配的,所以我们要先知道这个表有哪些列

对当前的wireshark筛选选择文件-导出分组解析结果为csv,用excel打开分析好一些。

大概在这里你就可以发现筛选的是列信息了(当然你可以也可以使用代码转换),然后就可以发现是筛选出三个列信息是flag_value、id和created_at三个信息

点击放大,点击空白处返回
点击放大,点击空白处返回

Q7.提交 flag

wireshark
flag{8673db96fa2dceeb}

继续接着上面的数据

看看flag_value值有什么吧

wireshark
http.request.uri contains "secret_flag" && http.request.uri contains "flag_value"

手法就和前面的一样了,这里也不赘述了

点击放大,点击空白处返回
点击放大,点击空白处返回

Q8.攻击者获取了管理员账户信息,管理员密码是什么?

shell
123456

同样的方法去找user表

wireshark
http.request.uri contains "users"

然后还原的数据是这个(提取数据给ai找的,懒得一个个匹配数据了)

用户usernamepassword (MD5)emailphone
1admine10adc3949ba59abbe56e057f20f883eadmin@tushuguan.cn13800000001
2zhangweic33367701511b4f6020ec61ded352059zhangwei@tushuguan.cn13800000002
3liming96e79218965eb72c92a549dd5a330112liming@corp.cn15900001001
4wangfang25d55ad283aa400af464c76d713c07adwangfang@corp.cn15900001002
5chenxi5f4dcc3b5aa765d61d8327deb882cf99chenxi@corp.cn15900001003

然后把拿到的hash值进行解析

image-20260802174137234


新故事即将发生
第一届好靶场MISC-FTP流量分析

评论区

评论加载中...