2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > JS逆向分析新浪某站登录处RSA加密

JS逆向分析新浪某站登录处RSA加密

时间:2022-07-31 15:06:30

相关推荐

JS逆向分析新浪某站登录处RSA加密

文章目录

前言RSA加解密核心思想Pyhon实现NoPadding 新浪网实战JS加密分析JS函数调试Py调用脚本 BurCrypto爆破插件介绍实战案例 总结

前言

在渗透测试过程中,经常会遇到 Web 站点、H5 网页(手机端APP)、微信小程序等系统使用 JS 对用户登录密码或数据包参数进行加密,此时对目标系统进行 JS 逆向分析并破解加密算法就成为无法回避的问题了。

前面写过一篇文章:渗透测试-Python破解前端JS密码加密,记录了对某站点登录处对用户密码进行 DES 前端加密后如何编写 Python 脚本进行自动化加密和暴力破解。

本文将记录对新浪某站点登录处 RSA 加密的 JS 逆向分析,介绍如何调试分析 JS 加密代码,并使用 BurpSuite 的 BurpCrypto 插件调用编写好的 JS 脚本对明文字典进行自动化加密并开始暴力破解。

RSA加解密

先来回顾下 RSA 加密算法的核心思想。RSA算法可以说是地球上最重要的算法之一,是数据通信和网络安全的基石。

核心思想

DES、AES 这类对称加密算法要求通信双方使用相同的规则和密钥进行加解密,此时如何安全地传递对称加密的密钥和规则则成为其短板。Diffie-Hellman 迪菲-赫尔曼密钥交换算法为非对称加密算法指出了明路:双方不一定要直接交换密钥。迪菲-赫尔曼密钥交换算法中通信双方并没有真正交换密钥,而是通过计算生成出一个相同的共享密钥。

RSA 非对称加密算法借鉴了这种思想,使用公钥和私钥来完成加解密,避免了对称密钥的传输,RSA算法的公钥是公开的,使用公钥加密的信息,必须使用对应的私钥才能解密。RSA 加密算法的加解密过程如下图所示。

其中 RSA 算法的密钥生成过程如下图(请留意公钥组(E,N),下文会涉及):

此处不再深入展开研究 RSA 算法,了解更多信息读者可阅读:图解|什么是RSA算法。

Pyhon实现

来看看调用 Python 中 RSA 库的使用:

import rsa# rsa加密def rsaEncrypt(str):# 生成公钥、私钥(pubkey, privkey) = rsa.newkeys(512)print("生成的公钥:%s\n生成的私钥:%s" % (pubkey, privkey))# 明文编码格式content = str.encode("utf-8")# 公钥加密crypto = rsa.encrypt(content, pubkey)return (crypto, privkey)# rsa解密def rsaDecrypt(str, pk):# 私钥解密content = rsa.decrypt(str, pk)con = content.decode("utf-8")return conif __name__ == "__main__":myID="TrueBW"print("待加密明文:"+myID)str, pk = rsaEncrypt(myID)print("加密后密文:%s" % str)content = rsaDecrypt(str, pk)print("解密后明文:%s" % content)

运行效果:

待加密明文:TrueBW生成的公钥:PublicKey(10629030079459395463144400675253112447452294596086105484430230848664443823586425985345133492174341536207039796419852567881955393923750255832544103665012653, 65537)生成的私钥:PrivateKey(10629030079459395463144400675253112447452294596086105484430230848664443823586425985345133492174341536207039796419852567881955393923750255832544103665012653, 65537, 7987707103520983352233469302178785131291834553791549524904608076774532285638924467981100833856007281648412621846426437080991757199772619881963205926642197, 7137659606359540844212485684387864319061600948479780819680893269775571858441319027, 1489147797127940820299165365930072508176474450558028288381712400209087839)加密后密文:b'\\\xf1\xe2h\x94\xa1u\x0c\xbe\xfbn\x96\xf0\xff\xfc%\x1a|H\xa1\xf9n\x13F\xce!&\xf3\x07\xdai\xa91\x17 hc?\x8d\xcb\xbcs\x1ds\xf47\xe7S@ku\xa9\xbd\xde\x8d1\x99\x84]\x08u\x05\x92\xe4'解密后明文:TrueBW

此处重点注意一下,调用默认的 RSA 加密函数,在公钥相同的情况下,每次生成的密文是不一样的,可以修改代码做下如下测试:

import rsa# rsa加密def rsaEncrypt(str):# 生成公钥、私钥(pubkey, privkey) = rsa.newkeys(512)print("生成的公钥:%s\n生成的私钥:%s" % (pubkey, privkey))# 明文编码格式content = str.encode("utf-8")# 公钥加密crypto1 = rsa.encrypt(content, pubkey)print("加密密文1:%s" % crypto1)crypto2 = rsa.encrypt(content, pubkey)print("加密密文2:%s" % crypto2)return (crypto1, privkey)# rsa解密def rsaDecrypt(str, pk):# 私钥解密content = rsa.decrypt(str, pk)con = content.decode("utf-8")return conif __name__ == "__main__":myID="TrueBW"print("待加密明文:"+myID)str, pk = rsaEncrypt(myID)# print("加密后密文:%s" % str)content = rsaDecrypt(str, pk)print("解密后明文:%s" % content)

运行效果:

待加密明文:TrueBW生成的公钥:PublicKey(12799631736781583749267613999692907671357537427513729682266238757981559828293220895553661952392529860922339921990422746431015572268196682211432794709407227, 65537)生成的私钥:PrivateKey(12799631736781583749267613999692907671357537427513729682266238757981559828293220895553661952392529860922339921990422746431015572268196682211432794709407227, 65537, 221494153725132278408294566993480424646941166006123576493555417175441155270926332322082941415220762251836260568637417394218612233212648904255461625881, 7297983223936167075375218749182269503982880382764917665596739577563081078236513599, 1753858750291577497147380303113892174932849965115163298337511562148885573)加密密文1:b'\xc0\xc2\x1b\xdeN\x13\xa7\x81/\xe1g\xc0\x15H:??\x8cc\xd5\x06Y\x7fP~\x1f\x88W\x81x%Jr\xe4\xc6\xdb\xb68$\xa6g\xb3l>\x94>\xb3\xf6\x14d~\x17\x13S;O\xaa\xa6bY\x1aZ\x95\xe4'加密密文2:b'3\x93\xd8w\x93\x9d^\xb5\x04nL\x97\xf1\xed\xe2\xd3Q\xaa\xed\xf4Y}\x02)7\x18\xb5\xf9\xd3\x0b\xcc\x19\xd9gc\x9a_\x05I\x86\xd0\xdc`\xcb\x06%\x14U\xa6\xc3\xc6\xa20}\x98T\xf2\xeb\xfcQk|R\x96'解密后明文:TrueBW

产生以上现象的原因,可以先简单理解为:

Python 的 RSA 库中加密函数默认加入了随机数处理(即类似 MD5 算法的加盐-salt),这样就导致每次需要加密的明文都是不同的,那么显然密文就每次都不同了。 那么解密后怎么会是同一个呢? 答案很简单,那就是 客户端和服务器端同样解随机数。 具体如下方案:

1、 密文 =( random+明文) ^e mod n //publicKey 加密2、(random+明文) = 密文^d mod n // 服务器端利用privateKey 解密3、 明文 = (random+明文) - random //服务器端解码出random4、 明文和数据库中数据比较

NoPadding

下面从密码学算法的底层角度理解 RSA 算法相同公钥对同一明文进行加密可能产生不同密码的原因。

分组密码

DES、AES 都属于分组密码,它们只能加密固定长度的明文。如果需要加密任意长度的明文,就需要对分组密码进行迭代,而迭代方法就称为分组密码的 “加密模式” 。分组密码有很多种加密模式,主要有:ECB、CBC、CFB、OFB、CTR。如果模式选择不恰当,就无法保证机密性。

(1)ECB模式

ECB全称为Electronic CodeBook,电子密码本模式,是最简单的一种模式,它直接将明文分割成多个分组并逐个加密,如下图,其中,加密和解密是指用分组密码算法加密和解密,其中也省略了密钥的描述。

当最后一个明文分组的内容小于分组长度时,需要用一些特定的数据进行填充。

这种模式的优点就是简单、快速,加密和解密都支持并行计算。而缺点也比较明显,因为每个明文分组都各自独立地进行加密和解密,如果明文中存在多个相同的明文分组,则这些分组最终会被转换为相同的密文分组。这样一来,只要观察一下密文,就可以知道明文中存在怎样的重复组合,并可以以此为线索来破译密码。另外,攻击者可以通过改变密文分组的顺序,或删除密文分组,或替换掉密文分组,就可以达到对明文操纵的目的,而无需破译密码。

(2)CBC模式

CBC全称为Cipher Block Channing,密文分组链接模式,是将前一个密文分组与当前明文分组的内容混合起来进行加密的。在CBC模式中,首先将明文分组与前一个密文分组进行XOR运算,然后再进行加密。加密第一个明文分组时,由于不存在“前一个密文分组”,因此需要事先准备一个长度为一个分组的比特序列来代替“前一个密文分组”,这个比特序列称为初始化向量 (initialization vector) ,通常缩写为 IV。一般来说,每次加密时都会随机产生一个不同的比特序列来作为初始化向量。CBC模式的加解密流程如下图:

CBC 模式避免了 ECB 模式的弱点,明文的重复排列不会反映在密文中。这是推荐使用的一种模式。

填充模式

分组密码的其他加密模式就不介绍了(读者可阅读我另一篇博文:深入理解密码学技术 来了解更多密码学技术),此处介绍分组加密的加密模式是为了引出“填充模式”。从上面可以看到,AES、DES 等对称加密算法都是基于固定长度的块,比如 AES 的块大小就固定是 16 字节,对超过 16 字节的数据进行加解密时,就需要使用各种分组模式对数据进行分组处理组合。然而并不是所有的数据都是 16 字节的整数倍长,因此会经常出现最后一个块不能被填满的场景,这个时候便会按照一定的“填充模式”规则进行字节填充。

RSA填充

和 AES 对称加密算法一样,RSA 也是一个块加密算法( block cipher algorithm),总是在一个固定长度的块上进行操作。但和AES不同的是,RSA block length 是跟 key length 有关的。每次 RSA 加密的明文的长度是受 RSA 填充模式限制的,但是 RSA 每次加密的块长度就是 key length。

假如你选择的秘钥长度为 1024 bit 共 128 个字节:

当你在客户端选择RSA_NO_PADDING填充模式时,如果你的明文不够 128 字节,加密的时候会在你的明文前面填充零。解密后的明文也会包括前面填充的零,这是服务器需要注意把解密后的字段前向填充的零去掉,才是真正之前加密的明文。当你选择RSA_PKCS1_PADDING填充模式时,如果你的明文不够128字节,加密的时候会在你的明文中随机填充一些数据,所以会导致对同样的明文每次加密后的结果都不一样。对加密后的密文,服务器使用相同的填充方式都能解密。解密后的明文也就是之前加密的明文。RSA_PKCS1_OAEP_PADDING填充模式没有使用过, 他是 PKCS#1 推出的新的填充方式,安全性是最高的,和前面 RSA_PKCS1_PADDING 的区别就是加密前的编码方式不一样。

由此我们可以看到,在使用 RSA 算法时,选择不同的填充模式,就会影响相同公钥情况下对同一明文进行加密的结果。如果选择NoPadding填充模式,则同一公钥对同一明文进行加密后的密文是固定的,下面演示的案例也将是这种类型。

新浪网实战

来看看本次 JS 加密逆向分析的实战目标——新浪经纪人平台:

随意输入账号密码 admin/123456 进行登录,抓包发现密码做了前端加密:

先记录下对于明文 “123456” 加密后的密文值:

password=8b4a85958ed1c427e35cce6f1d6cf49cf46d45acf6bce921179265b49dc3e0ef8880dd2be1c391fa1be16bb8ba171757fd0592b4192e1790e3ed6ad5c14d8458bcb8e7455e87aa4c847914957c12a6a077850e867acfee8533bb166f4b0800d285c90555c870ca4c62250bc7617999d7b20ae0d39e53f449293b118920fe4cb8

同时注意在该登录请求包的响应里包含了一个 PublicKey(多次发包,发现是固定值):

应该是 RSA 加密的公钥,先记录下来:

"pubkey":"BC087C7C00848CE8A349C9072C3229E0D595F817EDDE9ABF6FC72B41942A759E97956CE9CB7D1F2E99399EADBACC0531F16EAE8EFCB68553DE0E125B2231ED955ADBF5208E65DC804237C93EB23C83E7ECDA0B586ECF31839038EE6B640E0EEC5FF17D219FDEA33E730F287F0D384C74A53DFE1F91ACC63C7C92039A43AC6E97"

JS加密分析

1、浏览器调试器搜索encrypt关键字,寻找可能的加密点:

2、定位到第一处位置,结合上下文,猜测是此处调用加密函数没错了:

3、设置断点,重新点击登录,成功在断点处暂停:

点击“跨越”,此时出现上面一致的“123456”的密文(说明使用的是 RSA 的NoPadding模式),至此判定加密函数就是encryptedString

4、上层定位搜索找到encryptedString函数声明的地方,属于编号 305 函数里面的子函数:

看到这里,先来回顾下前面设置断点的位置附近的代码:

var t = $('#password'),i = $.trim(t.val());if (i.length < 100) {(0, r.setMaxDigits) (129);var n = new l.RSAKeyPair('10001', '', $('#pubkey').val()),o = (0, l.encryptedString) (n, i);$('input[name=\'password\']').val(o)}

其中 n 是调用了305函数里面的RSAKeyPair子函数生成的公钥,

var n = new l.RSAKeyPair('10001', '', $('#pubkey').val())代码中传递了公钥组(E,N)中的 E=‘10001’,而$('#pubkey').val()正是我们上面观察记录到的服务器返回的公钥值(公钥组(E,N)中的 N):

"pubkey":"BC087C7C00848CE8A349C9072C3229E0D595F817EDDE9ABF6FC72B41942A759E97956CE9CB7D1F2E99399EADBACC0531F16EAE8EFCB68553DE0E125B2231ED955ADBF5208E65DC804237C93EB23C83E7ECDA0B586ECF31839038EE6B640E0EEC5FF17D219FDEA33E730F287F0D384C74A53DFE1F91ACC63C7C92039A43AC6E97"

至此,整个 JS 文件对 RSA 加密函数的调用结构已经很清晰啦:

发送请求从服务端获得公钥公钥组(E,N)中的 N(公钥组中的E=‘10001’直接硬编码);调用305函数里面的RSAKeyPair子函数生成最终的公钥 n;调用305函数里面的encryptedString子函数生成最终的密文。

弄清楚 JS 文件的加密函数调用结构后,接下来的任务就是提取加密函数代码并进行 JS 调试,生成一个简洁的 JS 加密文件可实现与目标站点一样生成指定的密文,并提供一个对外调用的加密函数,用于供 Python 脚本或其他自动化工具直接调用。

JS函数调试

1、本地创建RSAEncrypt.js文件,首先将核心加密函数305函数整个拷贝过来:

发现头部报错,不要紧,修改下函数声明方式和函数名称即可,将其改写为:

function fun_305(e, t, i) {……代码……}

同时删除头部无用的几行代码,并新增一行t=this

2、接着,还注意到305函数里面还调用了228、306两个函数,故把这两个函数也整个打包带到RSAEncrypt.js脚本中来:

注意同上面一样修改下函数声明方式和函数名称:

当然了,还要修改下此时fun_305函数中的调用:

3、接着需要给上面三个函数每个末尾函数加一个返回值,在函数尾部加上return t(因为函数都是在操作参数 t):

4、然后来到 JS 脚本编写的最后一个环节——定义一个可对外调用的加密函数encrypt(),先来回顾下目标站点自身是如何调用的:

故自定义一个加密函数:

function getPwd(pass) {t = fun_350(); //调用加密核心:350t.setMaxDigits(129); //往下的所有函数、对象的调用,都是对象`t`里面的了var n = t.RSAKeyPair("10001", "", "BC087C7C00848CE8A349C9072C3229E0D595F817EDDE9ABF6FC72B41942A759E97956CE9CB7D1F2E99399EADBACC0531F16EAE8EFCB68553DE0E125B2231ED955ADBF5208E65DC804237C93EB23C83E7ECDA0B586ECF31839038EE6B640E0EEC5FF17D219FDEA33E730F287F0D384C74A53DFE1F91ACC63C7C92039A43AC6E97")var enPass = t.encryptedString(t, pass); //这里如果传入n的话,会提示'chunkSize' 为 null 或不是对象,传入t的话就OK了,因为所有函数、属性都在`t`里面return enPass;}

文件结构如下:

5、接下来我们需要动态调试下该脚本文件RSAEncrypt.js能否正常使用,可以使用发条JS调试工具,请自行百度下载:

6、将上面完整的 JS 代码复制粘体进工具中,点击加载代码,然后调用getPwd()函数,计算表达式,报错如下:

7、删除后重新加载代码,然后计算表达式,报错如下:

8、增加代码后重新加载代码,然后计算表达式,报错如下:

9、修改后重新加载代码,计算表达式,成功得到密文,调试结束:

经对比,与前面浏览器计算出来的密文一致,脚本编写成功。最终代码如下:

function fun_305(e, t, i) {t=this;t.encryptedString = t.RSAKeyPair = void 0;var n = fun_228(e, t, i),o = fun_306(e, t, i),s = {};s.NoPadding = 'NoPadding',s.PKCS1Padding = 'PKCS1Padding',s.RawEncoding = 'RawEncoding',s.NumericEncoding = 'NumericEncoding',t.RSAKeyPair = function (e, t, i, s) {this.e = (0, n.biFromHex) (e),this.d = (0, n.biFromHex) (t),this.m = (0, n.biFromHex) (i),this.chunkSize = 'number' != typeof s ? 2 * (0, n.biHighIndex) (this.m) : s / 8,this.radix = 16,this.barrett = new o.BarrettMu(this.m)},t.encryptedString = function (e, t, i, o) {var a,r,l,c,u,d,p,h,f,g = new Array,m = t.length,y = '';for (c = 'string' == typeof i ? i == s.NoPadding ? 1 : i == s.PKCS1Padding ? 2 : 0 : 0, u = 'string' == typeof o && o == s.RawEncoding ? 1 : 0, 1 == c ? m > e.chunkSize && (m = e.chunkSize) : 2 == c && m > e.chunkSize - 11 && (m = e.chunkSize - 11), a = 0, r = 2 == c ? m - 1 : e.chunkSize - 1; a < m; ) c ? g[r] = t.charCodeAt(a) : g[a] = t.charCodeAt(a),a++,r--;for (1 == c && (a = 0), r = e.chunkSize - m % e.chunkSize; r > 0; ) {if (2 == c) {for (d = Math.floor(256 * Math.random()); !d; ) d = Math.floor(256 * Math.random());g[a] = d} else g[a] = 0;a++,r--}for (2 == c && (g[m] = 0, g[e.chunkSize - 2] = 2, g[e.chunkSize - 1] = 0), p = g.length, a = 0; a < p; a += e.chunkSize) {for (h = new n.BigInt, r = 0, l = a; l < a + e.chunkSize; ++r) h.digits[r] = g[l++],h.digits[r] += g[l++] << 8;f = e.barrett.powMod(h, e.e),y += 1 == u ? biToBytes(f) : 16 == e.radix ? (0, n.biToHex) (f) : biToString(f, e.radix)}return y}return t;}function fun_306(e, t, i) {t.BarrettMu_powMod = t.BarrettMu_multiplyMod = t.BarrettMu_modulo = t.BarrettMu = void 0;var n = fun_228(e, t, i);function o(e) {var t = (0, n.biDivideByRadixPower) (e, this.k - 1),i = (0, n.biMultiply) (t, this.mu),o = (0, n.biDivideByRadixPower) (i, this.k + 1),s = (0, n.biModuloByRadixPower) (e, this.k + 1),a = (0, n.biMultiply) (o, this.modulus),r = (0, n.biModuloByRadixPower) (a, this.k + 1),l = (0, n.biSubtract) (s, r);l.isNeg && (l = biAdd(l, this.bkplus1));for (var c = (0, n.biCompare) (l, this.modulus) >= 0; c; ) l = (0, n.biSubtract) (l, this.modulus),c = (0, n.biCompare) (l, this.modulus) >= 0;return l}function s(e, t) {var i = (0, n.biMultiply) (e, t);return this.modulo(i)}function a(e, t) {var i = new n.BigInt;i.digits[0] = 1;for (var o = e, s = t; 0 != (1 & s.digits[0]) && (i = this.multiplyMod(i, o)), 0 != (s = (0, n.biShiftRight) (s, 1)).digits[0] || 0 != (0, n.biHighIndex) (s); ) o = this.multiplyMod(o, o);return i}t.BarrettMu = function (e) {this.modulus = (0, n.biCopy) (e),this.k = (0, n.biHighIndex) (this.modulus) + 1;var t = new n.BigInt;t.digits[2 * this.k] = 1,this.mu = (0, n.biDivide) (t, this.modulus),this.bkplus1 = new n.BigInt,this.bkplus1.digits[this.k + 1] = 1,this.modulo = o,this.multiplyMod = s,this.powMod = a},t.BarrettMu_modulo = o,t.BarrettMu_multiplyMod = s,t.BarrettMu_powMod = areturn t;}function fun_228(e, t, i) {var n,o;function s(e) {n = new Array(e);for (var t = 0; t < n.length; t++) n[t] = 0;new a,(o = new a).digits[0] = 1}s(20);l(1000000000000000);function a(e) {this.digits = 'boolean' == typeof e && 1 == e ? null : n.slice(0),this.isNeg = !1}function r(e) {var t = new a(!0);return t.digits = e.digits.slice(0),t.isNeg = e.isNeg,t}function l(e) {var t = new a;t.isNeg = e < 0,e = Math.abs(e);for (var i = 0; e > 0; ) t.digits[i++] = 65535 & e,e >>= 16;return t}function c(e) {for (var t = '', i = e.length - 1; i > - 1; --i) t += e.charAt(i);return t}new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');var u = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f');function d(e) {for (var t = '', i = 0; i < 4; ++i) t += u[15 & e],e >>>= 4;return c(t)}function p(e) {return e >= 48 && e <= 57 ? e - 48 : e >= 65 && e <= 90 ? 10 + e - 65 : e >= 97 && e <= 122 ? 10 + e - 97 : 0}function h(e) {for (var t = 0, i = Math.min(e.length, 4), n = 0; n < i; ++n) t <<= 4,t |= p(e.charCodeAt(n));return t}function f(e, t) {var i;if (e.isNeg != t.isNeg) t.isNeg = !t.isNeg,i = g(e, t),t.isNeg = !t.isNeg;else {i = new a;for (var n, o = 0, s = 0; s < e.digits.length; ++s) n = e.digits[s] + t.digits[s] + o,i.digits[s] = 65535 & n,o = Number(n >= 65536);i.isNeg = e.isNeg}return i}function g(e, t) {var i;if (e.isNeg != t.isNeg) t.isNeg = !t.isNeg,i = f(e, t),t.isNeg = !t.isNeg;else {var n,o;i = new a,o = 0;for (var s = 0; s < e.digits.length; ++s) n = e.digits[s] - t.digits[s] + o,i.digits[s] = 65535 & n,i.digits[s] < 0 && (i.digits[s] += 65536),o = 0 - Number(n < 0);if ( - 1 == o) {o = 0;for (s = 0; s < e.digits.length; ++s) n = 0 - i.digits[s] + o,i.digits[s] = 65535 & n,i.digits[s] < 0 && (i.digits[s] += 65536),o = 0 - Number(n < 0);i.isNeg = !e.isNeg} else i.isNeg = e.isNeg}return i}function m(e) {for (var t = e.digits.length - 1; t > 0 && 0 == e.digits[t]; ) --t;return t}function y(e) {var t,i = m(e),n = e.digits[i],o = 16 * (i + 1);for (t = o; t > o - 16 && 0 == (32768 & n); --t) n <<= 1;return t}function v(e, t) {for (var i, n, o, s = new a, r = m(e), l = m(t), c = 0; c <= l; ++c) {i = 0,o = c;for (var u = 0; u <= r; ++u, ++o) n = s.digits[o] + e.digits[u] * t.digits[c] + i,s.digits[o] = 65535 & n,i = n >>> 16;s.digits[c + r + 1] = i}return s.isNeg = e.isNeg != t.isNeg,s}function b(e, t) {var i,n,o,s = new a;i = m(e),n = 0;for (var r = 0; r <= i; ++r) o = s.digits[r] + e.digits[r] * t + n,s.digits[r] = 65535 & o,n = o >>> 16;return s.digits[1 + i] = n,s}function w(e, t, i, n, o) {for (var s = Math.min(t + o, e.length), a = t, r = n; a < s; ++a, ++r) i[r] = e[a]}var k = new Array(0, 32768, 49152, 57344, 61440, 63488, 64512, 65024, 65280, 65408, 65472, 65504, 65520, 65528, 65532, 65534, 65535);function C(e, t) {var i = Math.floor(t / 16),n = new a;w(e.digits, 0, n.digits, i, n.digits.length - i);for (var o = t % 16, s = 16 - o, r = n.digits.length - 1, l = r - 1; r > 0; --r, --l) n.digits[r] = n.digits[r] << o & 65535 | (n.digits[l] & k[o]) >>> s;return n.digits[0] = n.digits[r] << o & 65535,n.isNeg = e.isNeg,n}var $ = new Array(0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535);function x(e, t) {var i = Math.floor(t / 16),n = new a;w(e.digits, i, n.digits, 0, e.digits.length - i);for (var o = t % 16, s = 16 - o, r = 0, l = r + 1; r < n.digits.length - 1; ++r, ++l) n.digits[r] = n.digits[r] >>> o | (n.digits[l] & $[o]) << s;return n.digits[n.digits.length - 1] >>>= o,n.isNeg = e.isNeg,n}function _(e, t) {var i = new a;return w(e.digits, 0, i.digits, t, i.digits.length - t),i}function S(e, t) {if (e.isNeg != t.isNeg) return 1 - 2 * Number(e.isNeg);for (var i = e.digits.length - 1; i >= 0; --i) if (e.digits[i] != t.digits[i]) return e.isNeg ? 1 - 2 * Number(e.digits[i] > t.digits[i]) : 1 - 2 * Number(e.digits[i] < t.digits[i]);return 0}function j(e, t) {var i,n,s = y(e),l = y(t),c = t.isNeg;if (s < l) return e.isNeg ? ((i = r(o)).isNeg = !t.isNeg, e.isNeg = !1, t.isNeg = !1, n = g(t, e), e.isNeg = !0, t.isNeg = c) : (i = new a, n = r(e)),new Array(i, n);i = new a,n = e;for (var u = Math.ceil(l / 16) - 1, d = 0; t.digits[u] < 32768; ) t = C(t, 1),++d,++l,u = Math.ceil(l / 16) - 1;n = C(n, d),s += d;for (var p = Math.ceil(s / 16) - 1, h = _(t, p - u); - 1 != S(n, h); ) ++i.digits[p - u],n = g(n, h);for (var v = p; v > u; --v) {var w = v >= n.digits.length ? 0 : n.digits[v],k = v - 1 >= n.digits.length ? 0 : n.digits[v - 1],$ = v - 2 >= n.digits.length ? 0 : n.digits[v - 2],j = u >= t.digits.length ? 0 : t.digits[u],A = u - 1 >= t.digits.length ? 0 : t.digits[u - 1];i.digits[v - u - 1] = w == j ? 65535 : Math.floor((65536 * w + k) / j);for (var T = i.digits[v - u - 1] * (65536 * j + A), O = 4294967296 * w + (65536 * k + $); T > O; ) --i.digits[v - u - 1],T = i.digits[v - u - 1] * (65536 * j | A),O = 65536 * w * 65536 + (65536 * k + $);(n = g(n, b(h = _(t, v - u - 1), i.digits[v - u - 1]))).isNeg && (n = f(n, h), --i.digits[v - u - 1])}return n = x(n, d),i.isNeg = e.isNeg != c,e.isNeg && (i = c ? f(i, o) : g(i, o), n = g(t = x(t, d), n)),0 == n.digits[0] && 0 == m(n) && (n.isNeg = !1),new Array(i, n)}t.setMaxDigits = s,t.biFromHex = function (e) {for (var t = new a, i = e.length, n = 0; i > 0; i -= 4, ++n) t.digits[n] = h(e.substr(Math.max(i - 4, 0), Math.min(i, 4)));return t},t.biHighIndex = m,t.biCopy = r,t.BigInt = a,t.biDivide = function (e, t) {return j(e, t) [0]},t.biMultiply = v,t.biDivideByRadixPower = function (e, t) {var i = new a;return w(e.digits, t, i.digits, 0, i.digits.length - t),i},t.biModuloByRadixPower = function (e, t) {var i = new a;return w(e.digits, 0, i.digits, 0, t),i},t.biSubtract = g,t.biCompare = S,t.biShiftRight = x,t.biToHex = function (e) {for (var t = '', i = (m(e), m(e)); i > - 1; --i) t += d(e.digits[i]);return t}return t;}function getPwd(pass) {t = fun_305();//调用加密核心:350t.setMaxDigits(129); //往下的所有函数、对象的调用,都是对象`t`里面的了var n = t.RSAKeyPair("10001", "", "BC087C7C00848CE8A349C9072C3229E0D595F817EDDE9ABF6FC72B41942A759E97956CE9CB7D1F2E99399EADBACC0531F16EAE8EFCB68553DE0E125B2231ED955ADBF5208E65DC804237C93EB23C83E7ECDA0B586ECF31839038EE6B640E0EEC5FF17D219FDEA33E730F287F0D384C74A53DFE1F91ACC63C7C92039A43AC6E97")var enPass = t.encryptedString(t, pass); //这里如果传入n的话,会提示'chunkSize' 为 null 或不是对象,传入t的话就OK了,因为所有函数、属性都在`t`里面return enPass;}

Py调用脚本

上面使用调试工具调试出加密函数脚本RSAEncrypt.js,下面看看如何编写 Python 脚本调用RSAEncrypt.js文件的对外加密函数getPwd()计算密文:

import execjsdef encrypted(password):"""use JavaScript to encrypt the password:return:"""with open("RSAEncrypt.js", "r", encoding="utf-8") as f:ctx = pile(f.read())finalpassword = ctx.call("getPwd", password)print('RSA加密后的密码是:\n' + finalpassword)if __name__ == '__main__':encrypted("123456")

运行结果如下:

进一步地还可以编写 Python 脚本调用 JS 文件进行模拟登录站点、暴力破解等,本文不演示。可参考前面写过一篇文章:渗透测试-Python破解前端JS密码加密。

BurCrypto爆破

在Web渗透测试中有一个关键的测试项:密码爆破。目前越来越多的网站系统在登录接口中加入各式各样的加密算法,依赖于 BurpSuite 中的那些编码方式、Hash 算法已经远远不够,这里给大家介绍一款支持 AES/RSA/DES加密算法,甚至可以直接将加密算法的 Javascript 脚本运行与 BurpSuite 中的插件:BurpCrypto。

插件介绍

BurpCrypto 可从其 官方Github 页面进行下载已编译好的版本,或下载源代码本地编译,然后在BurpSuite的扩展列表中添加插件即可。

具体用法请读者自己看官方介绍文档:BurpCrypto: 万能网站密码爆破测试工具。

实战案例

我们经过逆向分析、动态调试获得 JS 加密的脚本文件后,可以直接利用 BurpCrypto 对 JS 脚本进行调用,对明文字典进行自动加密,同时结合 Burp 爆破模块开始进行暴力破解。由于本文演示的新浪网做了防止暴力破解的措施,下面给读者看看一个我在实际渗透测试过程中利用该插件对前端同样做了 RSA 加密的一个站点进行暴力破解的案例。

1、将调试好的 JS 文件加载到插件中,指定对外调用的加密函数名称,并定义自动化加密的进程名:

2、在 BurpSuite 爆破模块加载明文字典,然后指定 Payload 的加密进行,接着即可开始暴力破解,明文字典会被自动加密,成功爆破出密码:

3、选中成功爆破出出来的密码明文,BurpSuite 右键选择解密 Payload,即可获得密码明文:

可以看到,BurpCrypto 插件用起来极其舒适!可以帮助我们省略编写 Python 脚本进行暴力破解。

总结

本文最后演示的爆破案例,神奇的是其 JS 加密脚本跟上面演示的新浪网是一致的!只需要改下公钥参数值就可以了!这说明了这段 JS 加密脚本代码还是具有一定普遍性的,读者遇到前端 JS 脚本对用户密码进行 RSA 加密且对相同明文的每次加密后密文一致(即NoPadding填充模式下)的话,可以直接调用该 JS 脚本是啥!当然了,如果试用不成功,那就需要按照本文所述的方法、工具自行调试出对应的 JS 加密脚本了。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。