ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

XSS跨站脚本攻击实战指南:从靶场通关到过滤器绕过与安全防护

XSS跨站脚本攻击实战指南:从靶场通关到过滤器绕过与安全防护 最近把 XSS Labs 这个靶场从第一关磕到最后一关前后折腾了两个晚上加一个周末总算把所有关卡都打通了。趁着思路还热乎把整个通关过程、碰到的问题和绕过的思路整理成一篇笔记分享给正在刷靶场或者刚开始接触 XSS 的朋友。如果你还不知道 XSS Labs 是什么简单说就是一个专门用来练 XSS 漏洞的闯关靶场每一关都会给你一个输入点你需要通过构造不同的 payload 来触发弹窗或者执行 JavaScript从而过关。它涵盖的知识点非常全从最基本的反射型 XSS、存储型 XSS到 DOM 型 XSS再到各种花式过滤绕过基本把 XSS 实战中会遇到的路子都过了一遍。这篇笔记适合三类人看一是刚学完 XSS 基础理论、想在靶场上上手实操的新手二是刷了一些关卡但卡在过滤绕过、不知道怎么构造 payload 的选手三是想系统性查漏补缺、看看自己 XSS 知识体系有没有盲区的安全从业者。我会把每一类漏洞的原理、判别方法、payload 构造思路、常见的坑全部揉碎了讲照着这个思路走你能少走不少弯路。1. 项目整体认识XSS 靶场到底在练什么1.1 靶场定位与学习价值很多人刷靶场有一个误区就是从头到尾在抄 payload每关网上搜一下答案就过了刷完之后感觉都会了换个真实场景照样不会。我这次重刷 XSS Labs 的目标很明确每一关不仅要过关还要搞清楚这个 payload 为什么能过、服务端大概做了什么过滤、还有没有别的绕过方式。XSS 全称是 Cross-Site Scripting中文叫跨站脚本攻击核心原理就是攻击者把恶意的 JavaScript 代码注入到网页中当其他用户访问这个页面的时候恶意脚本就在他们的浏览器里执行了。它能做到的事情很多偷 Cookie、会话劫持、钓鱼、键盘记录、篡改页面甚至在某些场景下可以配合 CSRF 做更多操作。靶场的价值在于它把 XSS 攻击从理论到实战中间那层窗户纸捅破了。你在书上看一百遍反射型 XSS 是参数被服务端拼接进 HTML 返回不如自己在靶场里亲手试一遍。因为你真的去试的时候才会发现光知道拼接两个字远远不够你还得考虑拼接到了什么标签里面、处在什么上下文位置、哪些字符被过滤了、浏览器怎么解析这段输出这些细节才是 XSS 实战的关键。1.2 靶场关卡设计逻辑XSS Labs 的关卡设计是循序渐进的。我把它分成几个梯队来看第一梯队是没有任何过滤的最基础环境你只要知道 XSS 的基本格式就能过。这类关卡的目标是让新手建立信心熟悉最基本的 payload 格式比如scriptalert(1)/script这种。第二梯队开始引入简单的过滤比如把script标签直接替换成空字符串、把关键字过滤掉、把引号转义掉。这时候你就要开始考虑标签替代方案了script不行了可以用img配合onerror事件或者a配合onclick事件甚至svg标签里的script子标签。第三梯队是编码和混淆的利用。服务端可能把、这些尖括号做了 HTML 实体编码或者把引号转义了这时候你就得想 URL 编码、HTML 实体编码、JavaScript 的 Unicode 转义这些手段。很多时候你以为字符被过滤了其实只是被编码了浏览器解析到对应上下文的时候还是会还原成原来的字符。第四梯队就是我说的拦路虎过滤策略比较复杂的情况。比如只保留某些标签、对事件属性做限制、或者把危险关键字做正则匹配。遇到这种关卡光靠几个现成 payload 是过不去的你得学会分析过滤规则找到正则的漏洞或者逻辑的漏洞然后针对性地构造输入。最后还有 DOM 型 XSS 的关卡这种是最阴的因为你往 URL 里输入的 payload 根本不经过服务端直接在浏览器的 JavaScript 代码里被取出来用。这种漏洞在代码层面很难发现需要你能看懂 JavaScript 的逻辑才知道哪些输入是不可信的。1.3 通关前的环境准备我建议你准备这几样东西不要一上来就空手刷靶场浏览器用 Chrome 或者 Firefox开发者工具F12习惯性常开尤其是 Sources 面板和 Console 面板。Sources 面板可以看页面加载的 JavaScript 源码分析 DOM 型 XSS 的时候这个面板是主力工具。Console 面板就不用说了你还要靠它看报错或者输出日志。Burp Suite 最好装一个社区版就够了主要是用来抓包看服务端返回的原始数据。很多时候你在浏览器看到的页面是被渲染过的看不到服务端到底返回了什么、有没有做过滤用 Burp 看原始响应一眼就明白了。我在刷靶场的时候习惯把 Burp 的 Intercept 打开每提交一次 payload 都看一眼响应包。另外准备一个本地的 XSS 平台也是有用的。搜索热词里出现了蓝莲花 xss 平台安装配置这个是好多人关心的。这个平台的作用是接收 XSS 打出来的数据你在 payload 里把 Cookie 或者其他敏感信息传到这个平台上面就能在后台看到。不过刷靶场的时候其实用不上平台你只需要一个alert()弹窗就能过关但如果你以后要往实战走提前熟悉一下 XSS 平台还是有必要的。安装配置主要就是部署一套 Web 应用加数据库把平台的源码丢到 PHP 环境或者 Docker 里然后设置好接收地址和项目名就可以了这里不展开。最后一个准备是你要有一个能测编码的小工具。Windows 计算器旁边的程序员模式可以算十六进制但更推荐你直接用浏览器 Console 里跑encodeURIComponent()和decodeURIComponent()或者用一个在线编码转换工具URL 编码、HTML 实体编码、Base64 在刷靶场的时候都会用到。2. 三大漏洞类型拆解先搞懂再上手2.1 反射型 XSS关注参数如何被拼接反射型 XSS 是最容易理解的一种它的流程是用户把带有恶意脚本的 URL 发给受害者受害者点击这个 URL恶意脚本作为参数发送到服务端服务端把参数拼接进页面 HTML 返回给受害者脚本在受害者浏览器里执行。这种类型的核心要点在于你输入的 payload 会被服务端直接拼接到响应里。你看到的搜索框、留言框、URL 参数只要服务端把输入原样拼回页面就有可能出现反射型 XSS。它是一次性的不会存储到数据库里所以危害相对存储型要小一些但在钓鱼场景下依然很危险。刷靶场的时候你要形成一种直觉看到输入点先不要急着丢大 payload先丢一个简单的、不会触发错误的值比如随便一串字母加数字然后看响应里这个值出现在了哪个位置。是出现在标签的文本内容里出现在标签的属性值里还是出现在 JavaScript 代码里这三个位置的利用方式完全不一样。出现在文本内容里你要先试试能不能闭合标签比如/divscriptalert(1)/script。出现在属性值里你要先试着闭合引号比如scriptalert(1)/script。出现在 JS 代码里你要考虑怎么闭合当前的字符串和语句比如;alert(1);//。2.2 存储型 XSS持久化危害是重点存储型 XSS 的过程是恶意脚本被保存到服务端的数据库里之后任何用户访问包含这段脚本的页面时脚本都会被加载执行。典型的场景就是留言板、评论功能、个人信息修改这类功能你提交的内容会被展示给其他用户看。这类漏洞的危害比反射型大得多因为它影响的是所有访问这个页面的用户而不只是点了恶意链接的人。你在靶场里遇到的存储型 XSS 关卡通常就是一个能把 payload 存进去的输入框过关条件和反射型一样也是要弹窗但你要意识到真实的存储型 XSS 远不止弹窗那么简单。它可以把被攻击者的 Cookie 发送到攻击者服务器上可以篡改页面内容造成钓鱼甚至可以在后台执行管理操作。存储型 XSS 在绕过技术上跟反射型没有本质区别过滤规则和上下文位置是核心。唯一的区别就是请求方式不同存储型通常是 POST 请求提交表单。刷靶场的时候要注意POST 提交的 payload 在 Burp 的 Repeater 里更好调试你需要在 Repeater 里修改请求参数观察响应变化。我用 DVWA就是热搜里出现的那个靶场上的存储型 XSS 中级关卡举例它的过滤方式是把script字符串替换成空字符串。注意这种替换是有问题的因为它只替换一次而且不区分大小写不DVWA 中级是直接替换了script没有做大小写处理。这时候你可以用scrscriptipt这种方式替换掉中间的script后剩下的部分就拼成了完整的script。这种绕过思路叫递归过滤缺陷在真实环境里偶尔也能碰到。2.3 DOM 型 XSS纯前端的坑DOM 型 XSS 跟前两种有本质区别它的 payload 不经过服务端而是在浏览器的 JavaScript 代码执行过程中把 URL 参数或者document.referrer等数据取出来通过innerHTML、document.write()、eval()这类危险的 API 写进了页面。这种漏洞用一句话概括就是前端代码把不可信的数据当成了可执行的代码。你在靶场里刷 DOM 型关卡的时候会发现所有数据都在本地处理Burp 抓包看到的响应都是固定的 HTML 和 JS跟你在 URL 里输入的内容完全没关系。这时候你的分析工具就不是 Burp 了而是浏览器 DevTools 的 Sources 面板。拿到 DOM 型 XSS 的关卡我的习惯是先阅读页面的 JavaScript 源码找到数据流的入口和出口。入口通常是location.search、location.hash、document.URL这些可以受 URL 控制的地方出口就是document.write()、innerHTML、outerHTML、eval()、setTimeout的字符串参数这些地方。入口到出口之间如果没有任何验证和过滤那这个洞就出来了。2.4 三种类型的判定与区分技巧我总结一套快速判定 XSS 类型的方法刷靶场的时候很实用你输入的内容会不会在响应包的 HTML 里出现如果出现而且每次请求都会带上你的输入那就是反射型或存储型。如果不会那就往 DOM 型方向考虑。你输入的内容是 GET 参数还是 POST 表单GET 参数多数是反射型POST 表单要再看数据有没有入库入库的就是存储型没入库就是反射型。刷新页面之后你的输入还在不在如果还在说明它是从数据库或者其他持久化存储里读出来的是存储型如果不在那就是反射型或者 DOM 型。关掉 JavaScript 再试一次。如果关掉 JS 之后弹窗消失但页面显示的内容里还有你的输入那是反射型或存储型如果关掉 JS 之后整个功能就不正常了那大概率是 DOM 型。这个判定方法在真实渗透测试里也很好用它能把排查范围快速缩小。3. 通关实操从最基础到绕过过滤3.1 第一梯队基础 payload 的结构先说明一下 XSS payload 的基本结构。一个完整的 XSS payload 通常包含三个部分触发载体、事件或执行点、恶意代码。以img srcx onerroralert(1)为例img是载体标签srcx是为了让图片加载失败从而触发onerror事件onerroralert(1)就是事件和执行代码。理解了这三要素你以后构造 payload 就不靠硬背了。第一梯队的关卡基本没有过滤直接输scriptalert(1)/script就能过。但你别小看这种最简单的场景这里有几个值得注意的细节第一alert()方法的参数可以是数字可以不加引号比如alert(1)。但在某些浏览器上如果你用了数字后面接分号的时候要注意写法个别情况下数字后面直接跟分号会出现语法歧义所以更稳的写法是alert(1)这种没有歧义的。第二script标签里面的内容不是 HTML 上下文而是纯 JavaScript 上下文所以你在 script 标签里不需要担心和被 HTML 解析器误解直接写 JS 代码就行。但如果是onerror这类事件属性里你就要考虑引号包裹的问题。第三这些基本 payload 里alert这个函数名是可以被覆盖的有些页面自己定义了alert函数那你用alert(1)就不会弹窗了。刷靶场的时候如果遇到这种情况先改用console.log或者throw 1来验证是否执行然后用confirm()、prompt()这类原生的弹窗函数替代。3.2 第二梯队标签黑名单与大小写绕过当服务端开始做过滤的时候第一个常见的操作就是关键词过滤把script、alert、onerror这些字符串替换掉或者直接把script标签删掉。这时候你就要扩展自己的标签库。我用一个实际的通关过程来演示。某关的过滤策略是把script关键字替换成空字符串。我先输入scriptalert(1)/script响应里 script 消失了变成了alert(1)确定是关键字过滤。然后我用scrscriptipt试了一次服务端把里面那个script替换成空之后剩下的字符串变成了script这样浏览器解析的时候就拿到了一个完整的 script 标签。这就是前面提到的递归过滤缺陷。还有一关过滤了onerror这个字符串同时把script标签整个删掉。我试了img srcx onerroralert(1)响应里onerror直接没了。这时候我可以改用onclick但onclick需要用户点击才能触发不够自动。换个思路svg onloadalert(1)里的onload事件碰到的人更少在这个靶场里没有被过滤直接用onload就能过。这说明事件属性有很多onload、onclick、onmouseover、onfocus、onerror、oninput、onchange每个都可能被漏掉。大小写绕过也是必须要会的因为很多正则表达式写的时候没有加i修饰符或者用的匹配模式只匹配小写。遇到这种情况sCrIpt、IMG SRCx onerroralert(1)这类混合大小写的写法就能直接绕过。这个技巧在靶场的特定关卡里非常管用通常给个提示说某些字符被替换的时候你就可以先试大小写。3.3 第三梯队编码与引号绕过的经典套路过滤升级到编码层面之后情况会变得复杂很多。这里我重点说三种编码第一种是 HTML 实体编码。的实体编码是#60;或者lt;是#62;或者gt;。如果服务端把尖括号做了实体编码你直接输script是不会被当成标签的浏览器会把它渲染成文本。但这里有个核心技巧HTML 属性值内的实体编码会被浏览器解码后再交给 JavaScript 引擎。比如img srcx onerror#97;lert(1)#97;是字母a的十进制实体编码浏览器解析 HTML 属性时会把#97;lert还原成alert然后 JavaScript 执行的就是alert(1)。第二种是 URL 编码。URL 编码主要用于绕过那些先解码再过滤或者只过滤了明文关键字的场景。你输入的 payload 如果是放在 URL 参数里服务端拿到的可能是 URL 解码后的值。如果服务端只对解码后的值做了过滤你再进行一次编码提交过滤就可能被绕过。实际操作中我很少单独用 URL 编码绕过因为它需要服务端把编码数据放到某个会自动解码的上下文中这个场景比较窄。第三种是 JavaScript 的 Unicode 转义。alert可以用\u0061lert这种形式写JavaScript 引擎在执行的时候会把\u0061解码成a。如果服务端的过滤逻辑只匹配了明文alert字符串你就可以用 Unicode 转义绕过。不过要注意\u0061lert这种写法只有在eval()或者类似的可执行字符串上下文里才会被解码直接写在onerror事件属性里是不成立的。更常见的用法是在 DOM 型 XSS 配合eval()函数时使用。引号绕过这个技巧几乎每一关都会用到。如果过滤规则把双引号转义成\而且你的 payload 又必须在属性值里闭合引号这时候有几种思路一是看单引号是不是没被过滤改用单引号闭合二是如果单双引号都被过滤了尝试用反引号在 JavaScript 里反引号也可以定义字符串三是看能不能不用引号比如onerroralert(1)里的数字 1 就不需要引号或者onerroralert(document.cookie)里document.cookie是表达式也不需要引号。3.4 第四梯队事件触发与伪协议利用当你发现script标签不能用了、onerror被过滤了、引号也被限制了这时候你手里还有几张牌伪协议和更多的事件类型。伪协议我指的是javascript:协议。最常见的利用场景是a hrefjavascript:alert(1)用户点击链接的时候执行 JavaScript。还有iframe srcjavascript:alert(1)这种不需要点击页面加载 iframe 的时候就会自动执行。但有个前提iframe 的src支持javascript:协议而现代浏览器在某些安全策略下会对 iframe 的javascript:协议做一些限制所以这个技巧在靶场里能用但在实战中最好多测几个浏览器。事件触发的思路则是尽可能多地尝试不同的事件。我这里列几个我实际用过且验证有效的details open ontogglealert(1)是一种比较冷门的触发方式。details标签在open属性存在的时候会触发toggle事件不需要用户交互就能弹窗。meter onmouseoveralert(1)这种需要把鼠标移上去才触发可以用来应付那些限定标签白名单的场景但通关要求如果是自动弹窗就不太合适。input onfocusalert(1) autofocus是我特别推荐的一种因为autofocus属性可以让页面加载时自动聚焦到该元素上onfocus事件随之触发也不需要用户交互。video srcx onerroralert(1)跟img onerror原理一样但是 video 标签在某些过滤规则里容易被漏掉。靶场里有一关我印象很深刻它把script和onerror都过滤了而且单双引号都被转义了我只能用不带引号的 payload。我最后用的是input autofocus onfocusalert(1)整个 payload 里没有引号、没有 script、没有 onerror照样弹窗。这关对我的启发很大payload 不是越长越复杂越好关键是找到了过滤规则的盲区用最少的关键字完成触发。3.5 第五梯队DOM 型 XSS 的链式操作DOM 型 XSS 的通关思路跟前四种完全不同你得先静态分析源码再构造触发链。我以一道典型的 DOM 关卡举例。页面上有一个输入框有一个按钮点击按钮后页面里插入了一段内容。我先打开 DevTools 的 Sources 面板找到点击事件绑定的 JS 文件看到代码大概是这样的逻辑从 URL 参数name里取值然后用document.write()写入页面。这就非常典型name参数是入口document.write()是出口。如果我给name传svg onloadalert(1)写出来的内容就是一个 svg 标签onload事件触发弹窗。另一种常见的 DOM 型漏洞模式是innerHTML赋值。比如document.getElementById(xxx).innerHTML location.hash.substring(1)这种你在 URL 的锚点后面直接放img srcx onerroralert(1)就能触发。要注意的是location.hash取到的值是带#的所以要用substring(1)去掉你自己构造 payload 的时候不需要管这个浏览器解析到 hash 的时候会把#后面的内容原样传给代码。刷 DOM 型关卡的时候最痛苦的事情是不知道 JS 在哪个文件里。我的技巧是先用 CtrlShiftF 在 Sources 面板里全局搜索关键字比如innerHTML、document.write、eval、location这些可能被用到的敏感 API一下子就能定位到漏洞代码。3.6 一个实战风格的组合绕过案例除了上面这些按部就班的关卡我把一个综合运用多种技巧的案例写出来方便你理解这些绕过思路是怎么组合的。某关目标一个搜索页面搜索关键词会显示在输入框的value属性里同时也会作为标题的一部分显示在页面上。服务端的策略是把script过滤掉把和做了 HTML 实体编码输入框的 value 是单引号包裹的。我首先尝试scriptalert(1)/script响应里script变成了lt;scriptgt;而且中间的script被替换成了空字符串。直接输入肯定不行。注意看编码后的结果lt;scriptgt;浏览器在渲染的时候会把它显示成文本script并不会当成标签执行。所以我需要通过 HTML 实体编码来绕过但不是绕过这里的编码而是利用编码本身。我最终用了这个 payload onmouseoveralert(1)。拆解一下第一个单引号用来闭合输入框 value 属性的单引号空格和onmouseover是添加一个鼠标悬停事件属性第二个单引号是为了把后面原本的闭合单引号配对让它不破坏整个标签结构。输入之后页面 HTML 变成input value onmouseoveralert(1) ...事件直接写进标签属性里。服务端虽然把编码了但单引号和空格没有被处理我的 payload 根本不需要尖括号就能执行。这个案例说明一个很重要的思路XSS 不一定要构造标签闭合和注入完整标签在属性值里注入事件属性是更常见也更容易绕过过滤的方式。你只需要闭合属性值本身不需要闭合整个 HTML 标签。4. 常见问题与排查技巧实录4.1 弹窗不出现先看这几个地方我刷靶场的时候最烦的情况就是明明 payload 写得没问题就是不弹窗。后来我把排查思路整理成一套固定的流程效率提升了很多。第一步打开浏览器 Console看有没有报错。如果报错了十有八九是你的 payload 语法有问题或者某个关键字被覆盖了。有一次我用了alert(1)不弹窗Console 里显示alert is not defined说明页面里定义了自己的alert函数挡住了我直接换用prompt(1)就过了。第二步用 Burp 或者开发者工具的 Network 面板看服务端返回的原始响应确认你的 payload 到底变成了什么样子。很多时候你以为你发送的是script但服务端过滤后返回的可能已经是空字符串或者实体编码后的内容你在浏览器页面上根本看不出差别。第三步看你的 payload 被插入到了哪个上下文。我在刷靶场的时候发现有些关卡把输入放在了两个不同的地方一个地方有过滤另一个地方没有过滤。你注入的位置不对换一个有回显的位置也许就触发了。第四步看清楚过滤和编码的顺序。服务端可能是先过滤后编码也可能是先编码后过滤不同顺序下你绕过的方式不一样。这个没法直接从页面看出来只能通过尝试不同的 payload 来反推。4.2 payload 被过滤的排查顺序当你的 payload 被过滤的时候我的排查顺序是先做最小化测试。输入一个最简单的字符串比如ABC123确认它出现在响应的什么位置。然后逐步添加特使字符一次只加一个先加双引号看它有没有被转义或删除再加单引号同理然后加看它有没有被编码再加。这个过程能帮你迅速摸清服务端的过滤规则。我曾经花了一个小时卡在一关上最后发现只是过滤了空格把空格替换成/或者 Tab 键就能过了。这种细节你不做字符级的逐个测试根本发现不了。然后是替换等价元素。script被过滤了换img onerroronerror被过滤了换onload或onfocusalert被过滤了换confirm或prompt引号被过滤了看能不能不加引号。每一步都试一下不要一个思路走到黑。最后是编码混淆。如果字符的过滤规则比较严格用 HTML 实体、URL 编码、Unicode 转义分别试一下。但要注意编码和上下文的匹配关系#97;lert只有在 HTML 属性里能被还原在 script 标签内部是不行的\u0061lert只有在 JavaScript 字符串上下文里才有效在 HTML 属性里反而不行。4.3 编码混淆导致的触发失败编码绕过本身也容易踩坑这里我把常见问题集中说一下。最常见的坑是把 HTML 实体编码用在 JavaScript 上下文里。比如你想绕过alert的明文过滤写成了onerror#97;lert(1)但onerror属性里的内容是 JavaScript 引擎解析的JavaScript 并不认识#97;这种 HTML 实体它会把#97;lert当成一个未定义的变量执行直接报错。所以 HTML 实体编码只能用在 HTML 上下文中JS 上下文要用 JS 层面的混淆。反过来JavaScript 的 Unicode 转义用在 HTML 上下文也不行。你用\u0061lert写在onerror里不经过 eval 的话JavaScript 引擎不会把\u0061lert解码成alert它只会当成一个变量名去解析结果也是报错。Unicode 转义要生效通常要靠eval()函数或者Function构造函数配合。还有一种是双层编码的坑。你为了绕过过滤把 payload 做了两次 URL 编码但服务端只解码了一次就把数据存进去了。这样的话浏览器拿到的还是编码后的内容根本不会还原成可执行的代码。遇到这种情况你要先确认服务端解码了几层用 Burp 看最终的响应是最直接的验证方式。4.4 常见问题速查表我把刷靶场过程中实际碰到的问题和解决办法整理成一张表方便你遇到问题的时候直接查现象可能原因排查与解决办法弹窗不出现Console 无报错输入被插入了文本节点没有在可执行上下文中检查响应中 payload 出现的位置考虑闭合标签改为属性注入输入中的和变成lt;和gt;服务端做了 HTML 实体编码尝试不依赖尖括号的 payload比如属性事件注入script被替换成空字符串关键词过滤但未做递归用scrscriptipt递归绕过onerror被过滤事件属性黑名单换onload、onfocus、ontoggle等双引号被转义对属性值引号做了转义用单引号或反引号或者构造无引号 payload页面定义了自己的 alert 函数函数名被覆盖改用prompt(1)、confirm(1)或console.log验证只有点击某个按钮才弹窗输入被存进 JS 变量随事件输出阅读 JS 源码判断触发条件刷新后输入消失反射型或无持久化的 DOM 型恢复请求用 Burp 观察每次响应刷新后输入还在但弹窗不再触发存储型 XSS 内容被二次编码或过滤用 Burp 查看请求与响应的编码差异数字和字母组成的随机字符串被修改服务端有 WAF 或正则规则逐个字符测试确认过滤的字符集4.5 独家避坑心得再分享几个我在刷通关过程中摸索出来的小技巧这些是网上大多数通关笔记里不会写但实际很有用的第一善用 DevTools 的 Overrides 功能。这个功能可以让我在浏览器端临时修改页面加载的 JavaScript 文件。刷 DOM 型 XSS 的时候我会把页面的 JS 文件保存到本地加上几行console.log日志来观察数据流然后再覆盖回去这样调试起来快得多。第二一定要学会使用 Burp 的 Repeater 和 Intruder。Repeater 用来反复调试单个请求我每次构造新 payload 都会先在 Repeater 里看返回结果不直接在浏览器里盲试。Intruder 可以用来做简单的 payload 枚举比如把常见 XSS payload 列表放进去跑一遍看看哪些没有被过滤效率比手动试高得多。第三payload 要准备几套不同思路的不要只会script和img onerror这两种。我自己整理了一个常用 payload 备忘分成标签注入属性注入事件注入伪协议和DOM 利用几个分类每个分类下有几条验证过的 payload。这个备忘录在靶场和实战里都很实用。第四注意浏览器差异。同一个 payload 在 Chrome 里不触发换到 Firefox 里可能就触发了尤其是javascript:协议和某些事件类型的支持情况不一样。靶场没有规定必须用哪个浏览器遇到卡关的时候换一个浏览器试一下没有坏处。5. 从靶场到实战过滤器视角的延伸5.1 服务端过滤器是怎么工作的刷完靶场我对服务端过滤器的常见实现方式有了一个很立体的理解。现在网上搜springboot 项目全局过滤器处理上传 pdf 文件时 xss 攻击这类问题非常热说明很多开发者已经在实际项目中遇到了 XSS 防护的刚需。服务端的全局过滤器处理 XSS 攻击原理一般是拦截所有请求把请求中的参数、请求头、body 里的内容做一遍干净化处理把script、onerror这类危险字符串替换掉或者编码掉然后再交给 Controller 处理。这种方案的好处是接入成本低一个 Filter 就能覆盖全站。但它的缺点也很明显因为它是全局统一处理的不知道每个参数最终会被用在什么上下文里所以只能按最保守的规则来处理容易误杀或者漏过。靶场里你看到的很多绕过技巧放在这种全局过滤器里很多依然适用。比如过滤规则只处理了script标签那img onerror就没被拦截过滤规则把关键字替换成空字符串但没做递归那scrscriptipt就能穿过去过滤规则对参数值做了 HTML 实体编码但如果你绕过了过滤器在后续代码里插入了别的数据照样是漏洞。5.2 过滤器的两个常见误区在实际的 SpringBoot 项目里做 XSS 过滤器有两个误区我见得特别多。第一个误区是只过滤已知的恶意关键字。很多过滤器写了一大堆正则来匹配 XSS payload比如把/script/i、/onerror/i、/javascript:/i全部替换掉。这种黑名单方案的天然缺陷是永远都跟不上攻击者的思路你过滤了onerror攻击者还有onload、onfocus、ontoggle等几十个别的事件可以用你过滤了javascript:攻击者还可以用无协议链接配合事件触发。我在靶场里能过那些关卡靠的就是黑名单的覆盖不全面。第二个误区是过滤器处理了请求参数却没有考虑请求体。很多全局过滤器只处理了 URL 参数和表单字段对于 body 里的 JSON 数据直接放行。而现在的应用大多是前后端分离POST 请求的 payload 都放在 JSON body 里这些数据如果没经过过滤就直接入库存储型 XSS 的机会就来了。你在靶场里玩存储型 XSS 的时候应该能体会到漏洞的关键不是过滤规则本身而是过滤范围覆盖不完整。5.3 自动化检测 XSS 的辅助思路除了手工构造 payload刷靶场的时候配合一些自动化手段能扩大覆盖面。我在通关过程中用过几种方式一是使用浏览器插件比如 XSStrike 或者类似工具的靶场模式输入目标 URL 之后自动测试参数点。不过我不建议完全依赖这类工具因为它们的 payload 库再全也不可能覆盖所有上下文你手工分析发现的数据流特色是自动化工具发现不了的。二是自己维护一个简单的 payload 字典通过 Burp Intruder 批量发包测试。先把常见的 XSS 测试 payload 收集起来放到一个文件里然后选择你怀疑的参数位置跑一遍 Intruder一般在返回结果里就能看出哪些 payload 没有被过滤。然后再对着这些没被过滤的 payload 做手工精调。三是写一个简单的 Python 脚本来自动化判断响应。比如你要测试某个参数是否存在反射型 XSS就向目标发送一个唯一标识字符串加上 XSS 特征的 payload然后检查响应里是否包含这个标识以及是否出现了弹窗连接的证据。这个思路我在自动化批量检测的时候用过比自己一个参数一个参数地在浏览器里试快得多。但对于 DOM 型 XSS自动化脚本就没什么用了还是要靠人肉看 JS 源码。5.4 实战与靶场的差距最后说点实际的靶场跟真实环境之间还是有比较大的差距这也是你刷完靶场之后需要清醒认识到的。靶场的每一关都有一个明确的回显点你输入的内容至少会被打印到页面的某个地方所以你有充足的反馈信息来调整 payload。但真实环境里你输入的内容可能不会直接回显你根本看不到它被存储到数据库之后在哪个页面以什么形式渲染出来只能通过盲打、监听、判断延迟等方法来确认漏洞是否存在。靶场的过滤规则是单层的大多数关卡只做一种过滤方式换个绕过思路就能解决。真实环境往往是 WAF 加应用层过滤加开发框架自带的防护组合在一起防护是多层叠加的你要考虑怎么穿透每一层每一层的过滤规则还可能互相冲突或者互相补充。这种复杂性在靶场里体验不到。靶场的最终目标就是弹窗只要能执行 JavaScript 就算过关。但实战中你拿到一个 XSS 之后要考虑的是这个脚本能做什么、受害者是谁、有没有 CSRF token 可以窃取、能不能升级为存储型、能不能配合其他漏洞扩大危害这些都是弹窗之外的深层价值挖掘。不过话又说回来靶场依然是最好的起点因为你连弹窗都弹不出来的时候后面那些深度的利用也无从谈起。把 XSS Labs 里的每一关都吃透尤其是那些卡了你很久的关卡你会发现后面去分析真实漏洞的时候思路会比别人清晰很多。我个人在实际操作中最深的一个体会是XSS 通关练的不是 payload 的背诵量而是分析问题的思路。你拿着 payload 列表一个个试能过几关但遇到没见过的过滤规则就卡住了。反过来你掌握了字符级测试、上下文分析和过滤反推这三板斧就算没有 payload 库也能一步步推出绕过方案。我建议你刷的时候不要急着找答案每一关至少自己试出一个解再对比网上别人的解看看还有没有更精妙的思路这样进步会快得多。
返回列表