十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

前端JS逆向分析:从DOM操作到断点调试的完整思路

前端JS逆向分析:从DOM操作到断点调试的完整思路 很多朋友第一次接触JS逆向分析看到满屏压缩过的代码就头皮发麻——一行几万字符、变量全是无意义的缩写、方法名全部被替换完全不知道从哪下手。我当年也有这个阶段直到把思路理清楚才发现前端代码再花哨它终究要跑在浏览器里。只要抓住几个关键抓手JS应用的实际功能边界、DOM树的节点变化、加密编码库的调用特征配合断点调试一步步跟踪执行过程再复杂的前端逻辑也能被还原出来。这套内容是Web安全分析中非常核心的一环尤其是做接口测试、参数分析、业务逻辑审计甚至写自动化脚本时都离不开对前端JS的深度理解。它适合三类人一是刚入门的Web安全测试人员想把基础打牢二是前端开发工程师想从安全视角审视自己的代码三是做自动化爬虫或数据采集的朋友需要理解参数生成规则再构造请求。我下面把整个分析思路拆开讲包括DOM树怎么影响元素属性操作、常见加密库长什么样、断点调试用在哪几个点以及逆向分析时最容易踩的坑。1. 前端JS应用从“脚本代码”到“业务核心”1.1 现代Web应用中JS承担的职责以前的前端JS只是给页面做点特效弹个窗、校验一下表单。现在完全不同登录鉴权、支付信息组装、加密签名、数据渲染这些核心业务逻辑都放到了浏览器端。你打开任何一个站点按F12看Network面板几乎每个请求都有对应的JS代码在“指挥”它怎么发、参数怎么拼、头部怎么带。这也是为什么前端JS成为安全测试切入点的原因它相当于把整个业务的前半段流程摊开在你面前。现代前端框架比如Vue、React出来后编码方式变了但JS的核心职责没变操作DOM、处理事件、发送异步请求。框架只是把你的代码重组编译成浏览器能跑的JS该走的路一步不落。所以不管页面上用到的是什么框架调试器里看到的最终都是原生JS执行序列。理解这一点很重要因为很多人一看到“Vue打包后的代码”就觉得没法分析。其实那不是没法分析而是你没把它的执行链路拆开。1.2 前端代码里最容易暴露的敏感点在授权测试里我最常做的事情就是打开Sources面板搜索几个关键词api、secret、key、token、password、sign。不是搜索什么神秘的东西而是前端代码里真的经常出现这些东西。常见暴露点有这么几类接口地址与请求参数JS里一个个url、data直接把后端接口摸得透透的加密算法与密钥为了“防别人”程序员在前端放了自己的AES密钥、RSA公钥甚至私钥也有过业务逻辑比如优惠计算、价格判断、权限按钮显隐很多都写在JS里如果后端没有同等校验就可能被篡改利用第三方配置OSS Bucket、AccessKey等配置项一旦写在前端又被翻出来容易造成后端资源被滥用硬编码的调试口令部分开发环境会留下测试账号、调试开关这是最典型的信息泄露。这些内容听起来像是“找漏洞”但从开发者的角度想它暴露了一个事实前端代码是不可信的任何放在浏览器里的东西都会被用户看到。安全测试人员读前端代码本质上是在帮开发者提前发现这些暴露面。1.3 为什么“读前端代码”是安全分析的第一课很多人喜欢拿工具直接扫接口扫出来一堆路径就开始测却忽略了最直接的“地图”——前端JS。实际上一个熟悉JS分析的测试人员能通过断点调试把整个请求的组装过程复现出来然后精确地构造测试载荷而不是盲目扫描。另一个原因是很多服务端校验其实是在前端“假装”做了一遍如果后端没有做等价校验就会导致越权、价格篡改、状态绕过等问题。你通过JS逆向分析把前端参数逻辑搞明白再去对照后端行为问题往往就藏在这些差异里。可以说读前端代码不是“可选项”而是Web安全分析的基本功。2. DOM树与元素属性操作页面结构的底层视角2.1 什么是DOM树把HTML变成可编程的树形结构DOMDocument Object Model树就是浏览器把HTML文档解析成一棵由节点对象组成的树。document是根节点下面有html、head、bodybody里再有div、span、input这些元素节点还有纯文本组成的文本节点。每个元素节点都有自己的属性id、class、name、value、style等等。在安全分析里理解DOM树是因为它决定了你看到的页面状态不是静态的HTML而是JS执行后的“实时结果”。你现在在浏览器里看到的任何内容其实都是DOM树的当前状态。右键检查元素打开的Elements面板展示的就是这棵DOM树。当你通过JS动态改了一个input的valueDOM树会立即变化页面表现也随之改变。很多前端逻辑判断其实就是在读取或修改这棵树的某些节点。2.2 元素属性操作的常用接口与实际代码操作DOM属性的方式五花八门但核心就那么几类。我整理了一个表格方便对照记忆接口类型示例说明获取元素getElementById、querySelector、querySelectorAll根据ID或选择器拿节点修改属性setAttribute、removeAttribute直接改HTML属性直接访问属性element.id、element.className、element.href便捷写法操作的是Attribute的映射表单字段input.value、input.checked值的变化对提交结果影响很大事件触发click()、submit()、dispatchEvent模拟用户行为常用于自动化样式操作element.style.display none修改样式可能掩盖或暴露逻辑在调试的时候我经常直接在Console里执行document.querySelector(#target)来定位节点然后用.value、.innerHTML看内容再决定下一步断点打在哪里。比如下面的代码就是典型的元素属性操作// 获取元素 const username document.getElementById(username); // 读取属性 console.log(username.value); // 修改属性 username.setAttribute(readonly, true); // 触发事件 document.getElementById(loginBtn).click();这些操作看起来平平无奇但在逆向分析里它们就是你在DevTools Console里的“手”。想改表单值就改value想看某个元素有没有隐藏逻辑就移除hidden属性想触发某个按钮的事件就直接click。掌握这些基础接口操作起来会非常顺手。2.3 DOM操作是前端安全问题的高发区很多前端安全问题本质上就是“不可信的输入被当成了DOM属性或HTML内容”。比如内联事件处理、innerHTML拼接、location.hash没有被转义直接进入DOM都会带来风险。在实际分析中我会特别关注动态修改DOM的地方如果一段JS把接口返回的某个字段直接设置成element.innerHTML response.data.content那这个位置就是重点测试点因为该字段一旦被塞入HTML片段可能直接形成注入。这类问题不一定需要复杂的漏洞利用链往往只是少了一句转义。DOM树和元素属性操作这两块知识就是判断这类问题的底子。3. 加密编码库前端加密形态与识别思路3.1 编码类Base64、URL编码、Hex严格说编码不是加密但前端经常用它们做“看起来像加密”的处理。比如一段参数显示为aGVsbG8一眼就是Base64%E4%BD%A0%E5%A5%BD是URL编码48656c6c6f是Hex字符串。识别这些编码很简单Base64特征字母大小写混合 数字 末尾可能有Hex特征只有0-9a-f长度通常是偶数URL编码特征大量%开头。遇到这种情况不需要断点调试直接在浏览器Console里用内置方法就能还原atob(aGVsbG8) // hello decodeURIComponent(%E4%BD%A0%E5%A5%BD) // 你好编码类处理简单但它经常被放在真正的加密流程末尾作为最终输出的格式。比如AES加密后转成Base64是前端最常见的处理姿势。3.2 哈希类MD5、SHA系列的使用特征很多系统会把密码在前端做一次MD5或SHA1后再提交主要是为了“不给后端传明文”。但这其实只是给传输过程加了一道壳服务端拿到一串固定长度的十六进制字符串仍然能把它当密码做比对。前端哈希无法替代HTTPS也无法替代后端密码加盐。识别哈希有个很实用的技巧观察输出长度。MD5固定输出32位十六进制SHA1固定40位SHA256固定64位SHA512固定128位。在JS里看到CryptoJS.MD5(...)、crypto.createHash(md5).update(...).digest(hex)这类调用基本就能判断使用了哪个算法。Node环境下常见require(crypto)浏览器环境下更多是CryptoJS或者原生Web Crypto API。3.3 对称与非对称加密AES、RSA在JS中的调用方式前端调用AES通常长这样var encrypted CryptoJS.AES.encrypt(data, key, { iv: CryptoJS.enc.Utf8.parse(ivStr), mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }).toString();RSA则经常配合jsencrypt这个库代码通常是var encryptor new JSEncrypt(); encryptor.setPublicKey(pubKey); var encPwd encryptor.encrypt(password);看到这类代码分析的核心不是背代码而是提取几个关键参数加密模式CBC/ECB、密钥key、偏移量iv、填充方式Pkcs7/ZeroPadding、输出编码Base64/Hex。只要这些值确定你在任何语言里都能复现同样的加密结果这在构造测试请求时非常关键。这里有个常见误区很多人以为拿到加密算法就够了其实不够。AES的密钥和IV才是核心它们可能写死在JS里也可能由某个接口动态返回。分析时要顺着这两个值的来源一路追直到看清是常量还是变量。若是动态生成的还要找到生成规则。3.4 快速识别前端加密库的3种手段我常用的识别手段有三个看script标签直接搜文件名很多站点会直接引用crypto-js.js、jsencrypt.min.js文件名已经说明一切在Sources里全局搜索CtrlShiftF搜CryptoJS、JSEncrypt、encrypt(、AES、RSA等特征词在Console里测试全局对象如果window.CryptoJS存在说明已经加载了CryptoJSwindow.JSEncrypt同理。有个冷门但好用的点部分站点会把加密库打包进自己的bundle里文件名看不出来但你搜特征对象名仍然有效。配合断点调试看一眼调用栈加密入口就找到了。4. 断点调试与逆向分析把执行过程“冻结”下来4.1 DevTools断点调试的三个层次断点调试是JS逆向分析最有效的武器。Chrome DevTools的Sources面板提供了几种常用断点普通断点在代码行号上点一下执行到这一行自动暂停适合分析具体函数的内部逻辑XHR/fetch断点在Sources里的XHR/fetch Breakpoints中添加URL关键字只要页面发起匹配的请求就会自动断下来DOM断点在Elements面板选中节点右键Break on可以监听子节点变化、属性修改、节点移除。对于分析登录加密参数我的习惯是直接在XHR断点处添加登录接口的关键字比如login。页面一发起登录请求调试器立刻停在发起请求的那一行然后沿着调用栈往上翻就能看到参数从输入框到加密完成的整个过程。4.2 从请求参数反推前端加密逻辑很多人一上来就满代码打断点结果停下来也不知道接下来看哪。我的通用套路是先看请求再断参数最后顺调用栈。具体说Network面板找到目标请求看Form Data里哪些参数是需要关注的在XHR断点里设置URL关键字触发一次请求断点命中后先看右侧Call Stack调用栈从下往上找最接近当前逻辑的函数用Step intoF11逐行进入加密函数观察每个变量的变化一旦发现encrypt、sign、MD5这样的动词基本就锁定加密位置了在Console里手动执行加密函数传入不同值对比结果确认参数规则。这个流程是通用的难点其实在第三四步因为压缩混淆后的代码流程跳来跳去。这时候配合Watch面板和Console的临时输入一点点还原逻辑比单纯看代码硬猜要快得多。4.3 一个模拟案例密码加密参数的分析过程我拿一个内部测试环境举例不针对任何真实站点。页面登录时提交的参数是usernametestpasswordxxxx但实际发送的password是一串32位的十六进制字符。第一步我在XHR断点添加了目标接口的路径关键字。第二次点击登录后果然断到了发送请求的代码位置。第二步我看右边Call Stack发现上层是login()再上层是formSubmit()。于是插入一个普通断点到formSubmit内部重新触发。第三步在函数内部逐步执行发现密码变量在调用AES.encrypt(e.password, n, {iv: s, mode: ...})后被赋值给password。再看看n和s的来源是两个写死的常量。第四步我把这个AES加密的key、iv、mode、padding全部记下来直接在Console里用CryptoJS重新执行了一次结果和抓包得到的一致。到这一步登录请求的加密逻辑就被完整还原了。这整个过程一般不超过十分钟。真正花时间的反而是第一步之前的JS文件定位和关键词搜索。4.4 逆向分析的边界与原则我必须强调一点这类技术只应该用在你自己负责的项目、授权测试或CTF练习中。在没有授权的情况下分析他人线上业务既违反了网络安全相关法律法规也违背了从业者的基本职业道德。懂得原理是为了更好地防护自己的应用而不是去破坏别人的系统。实际工作中我拿到一个目标的第一步永远是确认授权范围。没有授权再简单的接口也不会去主动测试。这个习惯不但保护别人也是保护自己。5. 实战踩坑记录老手也会翻车的几个细节5.1 压缩混淆JS的可读性处理现在很少见到非压缩的线上JS了基本都是webpack打包、变量名替换甚至加了控制流平坦化。硬读不是不行但效率太低。我的做法是先把JS文件下载到本地用编辑器自带的美化功能把代码结构还原再搜索关键词。浏览器里可以用Chrome的Pretty-print按钮{}格式化整个文件会从一行变成正常缩进的代码可读性提升非常大。格式化之后虽然变量名还是乱的但函数调用关系清晰了配合断点会好处理很多。建议直接用“Search in all files”全局搜索特征词。不过需要注意全局搜索对超大JS文件会卡所以先下载到本地搜索是更稳妥的方案。5.2 断点失效的常见原因断点失效是我遇到最多的问题这里单独说一下代码经过了动态加载断点加在最初文件里但实际执行的是内存中变异后的版本需要用过一次请求后再去Sources面板里找到该文件重新断浏览器缓存改了代码但没生效试试强制刷新CtrlShiftR或者在Network里勾选Disable cache断点行不在执行路径上看起来代码换行后行号对不上或者该行是纯声明没有可执行语句浏览器不会停使用了Service Worker请求被Service Worker拦截不经过普通脚本路径需要在Application面板里查看并处理。遇到断点不生效时我的第一反应不是怀疑DevTools坏了而是问自己这条代码路径真的被执行了吗有没有可能走了另一个分支这个问题问完大多数断点失效都能解决。5.3 动态加载脚本和定时器怎么跟有些站点为了反调试会把业务代码拆成动态模块只有在点击按钮时才加载。这种情况下第一次点击前Sources里根本看不到那段代码。处理方式很简单先正常点击一次让代码加载完成然后去Sources面板左侧的目录树里找新出现的文件。如果代码是eval生成的可以在Console里搜eval(特征或者用debugger;语句主动停一下观察执行上下文。还有一类干扰是定时器反复执行比如每500ms检查环境一旦发现DevTools打开就跳转页面。这种反调试手段在普通场景里不算多但某些混淆框架会用到。最简单的应对是先忽略干扰代码只看关键请求的调用关系把注意力放在数据流而不是整段代码执行过程上。5.4 几条工作习惯建议最后分享几条我自己养成的习惯一分析JS之前先保存一份原始文件格式化之后另存一份。这样既保留现场又能对照查找改动位置。二从最小用例入手。不要试图一次看懂整个app.js而是聚焦当前需要分析的请求只跟踪它的调用链。看明白了再扩散。三随时记录。我会在本地维护一个笔记记录每个分析过的加密算法、key、iv、编码方式时间久了你就有了一个自己的“前端加密特征库”下次分析同类问题时能少走很多弯路。四多练习。CTF题目、开源项目都可以拿来练手不必非得找线上业务。练到给一个加密接口你能在三五分钟内定位关键函数这门手艺就真正上手了。我在带新人或者做安全分享时经常说的一句话是前端JS分析不是玄学它是一套有迹可循的工程方法。把DOM树当地图把元素属性操作当鼠标把加密编码库当密码本把断点调试当监控器四样东西组合起来再复杂的JS应用在你眼里也会变得清晰。最重要的是理解这些原理后你在编写自己项目的前端代码时才会知道哪些地方不该放密钥、哪些校验必须放到后端、哪些用户输入必须要过滤——这大概就是学习JS逆向分析过程中最实用也最值得的收获。
返回列表