JS基础 变量声明 在JS中,可以通过var关键字来声明变量
1 2 3 var name;或 name = "S1nJa"
也可以一次定义多个变量
1 var name = "S1nJa" ,age=18
函数 1 2 3 4 5 function add (a,b ){ c = a+b return c } add (1 ,1 )
常用函数 length
返回字符串的长度。
indexOf
用于查找指定字符串在另一个字符串中第一次出现的位置。
slice
slice(start, end)
从一个字符串中返回指定起始索引和结束索引之间的子串
substring
substring(start, end)
substring方法类似于slice,不同的是substring方法无法接受负数索引。
substr
substr(start, length)
substr的第二个形参接受的不是结束为止,而是截取长度
replace
用于字符串替换
concat
用于字符串拼接
push
向数组中添加元素
join
将所有元素合并为一个字符串
toUpperCase() 用于将字符串转为大写
toLowerCase() 用于将字符串转为小写
trim() 删除字符串两端的空白符,同Python中的strip()一样的功能。
charAt() 方法返回字符串中指定下标(位置)的字符串
split() 分割字符串
pop() 从数组中删除最后一个元素
push() 向数组结尾处添加新的元素
shift() 删除数组最后一个元素
unshift() 向数组开头处添加元素
sort() 对数组进行排序(按照数字大小或者字母顺序)
reverse() 反转数组中的元素。
条件运算 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 if (condition){ 语句 } ................... if (condition){ 语句 }else { 语句 } ................... if (condition1){ 语句 }else if (condition2){ 语句 }else { 语句 } ................... switch (n){ case 1 : 代码块 1 break ; case 2 : 代码块 2 break ; default : 代码块3 }
循环语句 1 2 3 4 5 6 7 8 9 10 11 12 for (语句 1 ; 语句 2 ; 语句 3 ){ 语句 } while (condition){ 语句 } do { 语句 } while (condition);
JS调试 在JavaScript中,断点调试是一种常用的调试技术,它允许开发者在代码的特定位置暂停执行,以便检查和分析程序的状态。
当发现可疑函数时,可以通过在断点处暂停,查看和修改变量的当前值,观察每一步数值的变化。
当打下断点后,重新请求页面便会进入调试模式
主要分为以下功能:
①逐过程执行:常用存在多个断点,需要在多断点间进行调试使用
②逐语句执行:步过,遇到自定义函数也当成一个语句执行,而不会进入函数内部
③:与①一致
④:与②一致
⑤步入:遇到自定义函数就跟入到函数内部步出,跳出当前自定义函数
⑥步出:跳出当前自定义函数
除此外右侧部分可以查看作用域(变量的内容)和当前的调用栈等信息
加密方法定位 定位的方式有很多如提交表单时存在onClick寻找加密函数,Event Listeners定位,全局搜索等,就不一一介绍了,主要看下JS函数检测辅助工具
安装 下载解压后直接添加扩展即可
GitHub - cilame/v_jstools: 模仿着写一个 chrome 插件,用来快速调试前端 js 代码。
打开hook开关,根据需求进行配置即可
使用 这里使用的是师傅编写的JS逆向练习靶场,https://github.com/0ctDay/encrypt-decrypt-vuls/
开启后重新发送请求,控制台会提示inject start!即开启成功
1、此时发送请求,即可对我们设置的功能点进行hook,找到了明文请求的点并返回hook的位置
2、在hook点打断点调试,寻找将数据进行加密的函数
n即为原始的明文数据,当调试到t.data = l(n);
,发现经过l()函数,t.data的值发生了改变,并且对应的值就是我们传入数据加密后的值,因此l()函数一定就是要找的加密函数
3、跟进l()函数,t为传入的明文数据,f为秘钥,h为偏移量
4、同时在调用l()前,发现了requestID、时间戳、签名
修改数据包 定位到加密函数后,就要着手修改数据包了
JS-RPC https://github.com/jxhczhl/JsRpc
JS-RPC是一款远程调用浏览器方法的工具,通过websocket与本地python服务端相连。当python中想要执行代码时,只需要通过RPC即可调用控制台中的函数,不需要再本地还原。
使用 JsRpc/README.md at main · jxhczhl/JsRpc · GitHub
1、打开客户端,将JSRPC项目中的JsEnv.js中的代码复制到控制台中
2、输入var demo = new Hlclient("ws://127.0.0.1:12080/ws?group=S1nJa");
建立连接
3、经过刚才的调试我们已经知道了l()
函数即为加密函数,那便可打断点记录该函数进行调用
4、将其注册到JSRPC
1 2 3 4 demo.regAction ("enc" , function (resolve, param ) { var res = enc (String (param)); resolve (res); })
5、注册完成后即可进行本地调用了
1 http://127.0.0.1:12080/go?group=S1nJa&action=enc¶m=test
6、解密函数同理,通过调试发现解密函数为d(),直接记录注册
JS-RPC+MITM mitmproxy - an interactive HTTPS proxy
mitmproxy作为一款代理工具,可以通过python对请求进行拦截,篡改,从而达到hook的目的
经过前边的分析可知,我们的每次请求都包含以下参数
r:时间戳
n:Json格式的原始数据
i:requestId
s:签名信息
l:加密函数
d:解密函数
所以如果想要构造请求,就需要获取到以上参数,这里对明文请求进行加密,密文回显解密为例
JS-RPC 首先还是注册JS-RPC,对每个参数进行记录注册:
记录:
1 2 3 4 5 6 7 8 9 10 11 12 window .time = Date .parse window .id = pwindow .v1 = vwindow .m = a.a .MD5 window .enc = lwindow .dec = d
注册:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 demo.regAction ("req" , function (resolve,param ) { let timestamp = time (new Date ()); let requestid = id (); let v_data = JSON .stringify (v1 (param)); let sign = m (v_data + requestid + timestamp).toString (); let encstr = enc (v_data); let res = { "timestamp" :timestamp, "requestid" :requestid, "encstr" :encstr, "sign" :sign }; resolve (res); })
注册dec
1 2 3 4 5 6 demo.regAction ("dec" , function (resolve, param ) { var res = dec (String (param)); resolve (res); }) #测试 http :
测试
MITM 现在通过JS-RPC已经可以获取到,时间戳、requestid、sign、加解密内容,之后就可以通过Mitmproxy对请求响应进行hook了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import jsonfrom mitmproxy import httpimport requestsdef getHeader (method,body ): data = {"group" : "S1nJa" , "action" : method, "param" : body} res = requests.post("http://127.0.0.1:12080/go" , data=data) res_json = json.loads(res.text)["data" ] return res_json def request (flow: http.HTTPFlow ) -> None : if flow.request.pretty_url.startswith("http://127.0.0.1:8085/api/" ): original_body = flow.request.content.decode('utf-8' ) res_json = getHeader("req" ,original_body) data_json = json.loads(res_json) encrypted_body = data_json["encstr" ] request_id = data_json["requestid" ] timestamp = data_json["timestamp" ] sign = data_json["sign" ] flow.request.text = encrypted_body flow.request.headers["requestId" ] = request_id flow.request.headers["timestamp" ] = str (timestamp) flow.request.headers["sign" ] = sign def response (flow: http.HTTPFlow ) -> None : content_type = flow.response.headers.get("Content-Type" , "" ) if "application/json" in content_type: response_body = flow.response.get_text() res = getHeader("dec" ,response_body) flow.response.set_text(res)
运行mitmproxy
1 mitmproxy -p 8083 -s mit.py
将8083端口设为burp的上游代理
此时明文发包,可以看到数据会经过mitm加密处理,返回包也为明文数据
JS-RPC+autoDecoder GitHub - f0ng/autoDecoder: Burp插件
autoDecoder是f0ng师傅编写的一款Burp插件,同样可以与JS-RPC进行联动,达到hook的目的
插件中自带了一些常规的加密方式如aes、des、RSA、国密等
特殊加密方式可以通过修改项目中的flasktest.py与JS-RPC进行联动
autoDecoder JS-RPC注册跟MITM中方式一样,加密部分由于需要替换请求头时间戳、requestid、sign还未实现,暂贴解密部分代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 from flask import Flask, Response, requestimport requestsimport jsondef getHeader (method, body ): data = {"group" : "S1nJa" , "action" : method, "param" : body} res = requests.post("http://127.0.0.1:12080/go" , data=data) res_json = json.loads(res.text)["data" ] return res_json app = Flask(__name__) @app.route('/decode' , methods=["POST" ] ) def decrypt (): param = request.form.get('dataBody' ) param_headers = request.form.get('dataHeaders' ) param_requestorresponse = request.form.get('requestorresponse' ) res = getHeader("dec" , param) print (param) if param_requestorresponse == "request" : return param_headers + "\r\n\r\n\r\n\r\n" + param else : return res if __name__ == '__main__' : app.debug = True app.run(host="0.0.0.0" , port="8888" )
测试调试
成功后,便可抓包选择加密部分右键选择autodecoder->Decode-Autodecoder进行解密
JS-PRC+YAKIT 在前边MITM方式中,使用的是mitmproxy自带库,对原始http请求进行拦截修改,而yakit中拥有热加载功能,此功能中有两个魔方方法beforeRequest
和afterRequest
,他们分别可以在发送数据包前和对每一个请求的响应再做一次处理
而yakit的官方[poc库](poc | Yak Program Language (yaklang.com) ),可以对HTTP请求进行处理,这样结合beforeRequest
和afterRequest
便可对请求响应进行hook
JS-RPC跟前边一致
热加载加解密 热加载代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 func getHeader (method,body) { res,rep,err = poc.Post("http://127.0.0.1:12080/go" ,poc.replaceBody("group=S1nJa&action=" +method+"¶m=" +body, false ),poc.appendHeader("content-type" , "application/x-www-form-urlencoded" )) if (err){ return (err) } res_json = json.loads(res.GetBody())["data" ] return res_json } beforeRequest = func (req) { original_body = poc.GetHTTPPacketBody(req) res = getHeader("req" ,string (original_body)) res = json.loads(res) req = poc.ReplaceHTTPPacketHeader(req, "requestId" , res["requestid" ]) req = poc.ReplaceHTTPPacketHeader(req, "timestamp" , res["timestamp" ]) req = poc.ReplaceHTTPPacketHeader(req, "sign" , res["sign" ]) req = poc.ReplaceHTTPPacketBody(req, res["encstr" ]) return []byte (req) } afterRequest = func (rsp) { hd,body = poc.Split(rsp) rss = getHeader("dec" ,codec.EncodeUrl(string (body))) rsp = poc.ReplaceHTTPPacketBody(rsp,rss) return []byte (rsp) } mirrorHTTPFlow = func (req, rsp, params) { return params }
通过热加载,成功将明文数据加密并发送请求进行fuzz,响应体通过解密返回明文数据
添加插件解密 先注册解密函数
1 2 3 4 5 window .dec = ddemo.regAction ("dec" , function (resolve, param ) { var res = dec (String (param)); resolve (res); })
新建插件(插件—>本地—>新建插件)
选择Yak-codec
源码
1 2 3 4 5 6 7 handle = func (origin ) { group = "S1nJa" ; action = "dec" ; rsp,rep = poc.Post ("http://127.0.0.1:12080/go" ,poc.replaceBody ("group=" +group+"&action=" +action+"¶m=" +codec.EncodeUrl (origin), false ),poc.appendHeader ("content-type" , "application/x-www-form-urlencoded" ))~ return json.loads (rsp.GetBody ())["data" ]; }
保存后便可通过右键菜单中的JS解密,将密文转换为明文
参考链接 poc | Yak Program Language (yaklang.com)
奇安信攻防社区-在渗透的”幽灵模式”中开启”透视”外挂 (butian.net)
奇安信攻防社区-死磕某小程序 (butian.net)
奇安信攻防社区-某众测前端解密学习记录 (butian.net)
保姆级教程—前端加密的对抗(附带靶场) - 先知社区 (aliyun.com)