前言
在渗透测试过程中,我们经常会碰到登录处用js加密字段的情况。在大多数情况下,看到这种加密方式,我们都会放弃对该登录处进行暴力破解。本文主要讲解对js加密进行绕过,以达到爆破或绕反爬的目的。
对登录处使用sm2国密算法的某网站进行爆破
该网站图形验证码失效,只要能对密码字段进行相应的加密,就可以爆破。
访问网站,输入用户名:admin、密码:123456 以及正确的图形验证码进行登录。
抓包后看到密码字段被加密为很长的一段字符。
F12打开开发者调试模式,切换到Network选项卡。重新登录一遍,可以看到password字段进行了加密。
切换到Source选项卡,ctrl+shift+F 调出全局搜索框,全局搜索 password 字段。
跳到 checkuser.js 文件,分析password字段经过两次加密。
var password = hex_md5($("#password").val());
password = sm2Encrypt(password, publicKey_).toLocaleUpperCase();
第一步的加密很简单,就是调用hex_md5加密函数对password进行加密。通过全局搜索hex_md5,在md5.js文件中找到了该函数。
function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
第二步加密调用了 sm2Encrypt() 函数对第一步加密后的字符串再进行加密。
我们在全局搜索sm2Encrypt,最终在sm2.js文件中找到了该加密函数。通过百度搜索sm2加密算法,发现该算法是国密加密算法。
SM2是国家密码管理局于2010年12月17日发布的椭圆曲线公钥密码算法。SM2算法和RSA算法都是公钥密码算法,SM2算法是一种更先进安全的算法,在我们国家商用密码体系中被用来替换RSA算法。
随着密码技术和计算机技术的发展,目前常用的1024位RSA算法面临严重的安全威胁,我们国家密码管理部门经过研究,决定采用SM2椭圆曲线算法替换RSA算法。
以上来自百度百科,更多的关于椭圆曲线的加密方法就不细讲。
我们需要对sm2Encrypt加密函数进行模拟,这里使用nodejs来进行模拟。
1、本地创建sm2.js文件,把网站上sm2.js文件中的sm2Encrypt()加密函数复制进来。
2、末尾加一个console.log()打印,便于我们查看结果。
3、再把网站的md5.js文件拷贝到sm2.js同目录下。publickey则在sm2.js全局定义了 。
var md5=require("./md5.js") var publicKey = "0469623686396c766185cd705cbd517714b377ae80b4b919a9de2b688f1cfa3edb60f67a13b6ecc8eef422577083d90844d635a675efef9cb6fa48386045a94518"; var data="123456" /** * [SM2Encrypt description] * @param {[type]} data [待加密数据] * @param {[type]} publickey [公钥 hex] * @param {[type]} cipherMode [加密模式 C1C3C2:1, C1C2C3:0] * @return {[type]} [返回加密后的数据 hex] */ function sm2Encrypt(data, publickey, cipherMode) { cipherMode = cipherMode == 0 ? cipherMode : 1; //msg = SM2.utf8tob64(msg); data=md5.hex_md5(data); var msgData = CryptoJS.enc.Utf8.parse(data); var pubkeyHex = publickey; if (pubkeyHex.length > 64 * 2) { pubkeyHex = pubkeyHex.substr(pubkeyHex.length - 64 * 2); } var xHex = pubkeyHex.substr(0, 64); var yHex = pubkeyHex.substr(64); var cipher = new SM2Cipher(cipherMode); var userKey = cipher.CreatePoint(xHex, yHex); msgData = cipher.GetWords(msgData.toString()); var encryptData = cipher.Encrypt(userKey, msgData); return '04' + encryptData; } console.log(sm2Encrypt(data,publicKey))
安装crypto-js模块。
安装完之后,再次运行。提示 SM2Cipher is not defined 。提示这个报错是因为该函数里面用到的一些其他函数我们没有复制出来。所以得一个个把相关的依赖函数复制出来。
在加密的地方打断点,F11进行跟进。
一步一步跳,找到了SM2Cipher函数,将其复制到我们的 js 文件中。
再次运行,这次提示 KJUR is not defined.
百度了下发现需要安装 jsrsasign.
于是安装该模块,并且在脚本的开头加入引入语句 var KJUR=require("jsrsasign");
再次运行,提示 unregistered EC curve name: sm2,这是引入的 jsrsasign.js 文件报的异常。
于是猜测需要去注册sm2曲线名称。我们继续翻阅网站的sm2.js文件,终于找到了注册该sm2曲线名称的代码。将其复制到我们的代码中。
再次运行,则提示如下的错:TypeError: ECPointFp.decodeFromHex is not a function.
我们在导入的模块里面全局搜索该函数 ECPointFp.decodeFromHex,发现导入的模块中其实是有该函数的。
于是我们将之前的这条语句 var KJUR=require("jsrsasign"); 改为 var jsrsasign=require("jsrsasign"); 。然后再次运行,对运行报错的函数,全局搜索。如果在导入的模块中含有该函数,则在其前面加上jsrsasign.
如果到导入的模块中不含有该函数,则说明该函数是该网站自己定义的,我们到网站的sm2.js中把该函数复制下来就行。比如function SM3Digest函数,我们导入的模块中不含有该函数,但是在网站中是定义了该函数的,我们将其复制下来到我们自己的代码中即可。
最后,经过一个函数一个函数的跟踪其依赖,最终将其加密算法模拟了出来,如下:
sm2.js
var CryptoJS = require("crypto-js"); var jsrsasign=require("jsrsasign"); var md5=require("./md5.js") var publicKey = "0469623686396c766185cd705cbd517714b377ae80b4b919a9de2b688f1cfa3edb60f67a13b6ecc8eef422577083d90844d635a675efef9cb6fa48386045a94518"; var data="123456" /** * [SM2Encrypt description] * @param {[type]} data [待加密数据] * @param {[type]} publickey [公钥 hex] * @param {[type]} cipherMode [加密模式 C1C3C2:1, C1C2C3:0] * @return {[type]} [返回加密后的数据 hex] */ function sm2Encrypt(data, publickey, cipherMode) { cipherMode = cipherMode == 0 ? cipherMode : 1; //msg = SM2.utf8tob64(msg); data=md5.hex_md5(data); var msgData = CryptoJS.enc.Utf8.parse(data); var pubkeyHex = publickey; if (pubkeyHex.length > 64 * 2) { pubkeyHex = pubkeyHex.substr(pubkeyHex.length - 64 * 2); } var xHex = pubkeyHex.substr(0, 64); var yHex = pubkeyHex.substr(64); var cipher = new SM2Cipher(cipherMode); var userKey = cipher.CreatePoint(xHex, yHex); msgData = cipher.GetWords(msgData.toString()); var encryptData = cipher.Encrypt(userKey, msgData); return '04' + encryptData; } function SM2Cipher(cipherMode) { this.ct = 1; this.p2 = null; this.sm3keybase = null; this.sm3c3 = null; this.key = new Array(32); this.keyOff = 0; if (typeof(cipherMode) != 'undefined') { this.cipherMode = cipherMode } else { this.cipherMode = SM2CipherMode.C1C3C2 } } SM2Cipher.prototype = { getHexString: function(h) { if((h.length & 1) == 0){ return h; }else { return "0" + h; } }, Encrypt: function(pubKey, plaintext) { var data = new Array(plaintext.length); Array.Copy(plaintext, 0, data, 0, plaintext.length); var c1 = this.InitEncipher(pubKey); this.EncryptBlock(data); var c3 = new Array(32); this.Dofinal(c3); var hexString; switch(this.cipherMode) { case SM2CipherMode.C1C3C2: hexString = this.getHexString(c1.getX().toBigInteger().toRadix(16)) + this.getHexString(c1.getY().toBigInteger().toRadix(16)) + this.GetHex(c3).toString() + this.GetHex(data).toString(); //hexString = this.getHexString(c1.getX().toBigInteger().toRadix(16)) + this.getHexString(c1.getY().toBigInteger().toRadix(16)) + this.GetHex(c3).toString() + this.GetHex(data).toString(); //hexString = this.getHexString(c1.getX().toBigInteger().toRadix(16)) + this.getHexString(c1.getY().toBigInteger().toRadix(16)) + this.GetHex(c3).toString() + this.GetHex(data).toString(); break; case SM2CipherMode.C1C2C3: hexString = c1.getX().toBigInteger().toRadix(16) + c1.getY().toBigInteger().toRadix(16) + this.GetHex(data).toString() + this.GetHex(c3).toString(); break; default: throw new Error("[SM2:Decrypt]invalid type cipherMode("+ this.cipherMode +")"); } return hexString }, GetHex: function(arr) { var words = new Array(32); var j = 0; for (var i = 0; i < arr.length * 2; i += 2) { words[i >>> 3] |= parseInt(arr[j]) << (24 - (i % 8) * 4); j++ } var wordArray = new CryptoJS.lib.WordArray.init(words, arr.length); return wordArray }, Reset: function() { this.sm3keybase = new SM3Digest(); this.sm3c3 = new SM3Digest(); var xWords = this.GetWords(this.p2.getX().toBigInteger().toRadix(16)); var yWords = this.GetWords(this.p2.getY().toBigInteger().toRadix(16)); this.sm3c3.BlockUpdate(xWords, 0, xWords.length); this.sm3keybase.BlockUpdate(xWords, 0, xWords.length); this.sm3keybase.BlockUpdate(yWords, 0, yWords.length); this.ct = 1; this.NextKey() }, NextKey: function() { var sm3keycur = new SM3Digest(this.sm3keybase); sm3keycur.Update(this.ct >> 24 & 0xff); sm3keycur.Update(this.ct >> 16 & 0xff); sm3keycur.Update(this.ct >> 8 & 0xff); sm3keycur.Update(this.ct & 0xff); sm3keycur.DoFinal(this.key, 0); this.keyOff = 0; this.ct++ }, EncryptBlock: function(data) { this.sm3c3.BlockUpdate(data, 0, data.length); for (var i = 0; i < data.length; i++) { if (this.keyOff == this.key.length) { this.NextKey() } data[i] ^= this.key[this.keyOff++] } }, Dofinal: function(c3) { var yWords = this.GetWords(this.p2.getY().toBigInteger().toRadix(16)); this.sm3c3.BlockUpdate(yWords, 0, yWords.length); this.sm3c3.DoFinal(c3, 0); this.Reset() }, InitEncipher: function(userKey) { var k = null; var c1 = null; var ec = new jsrsasign.KJUR.crypto.ECDSA({ "curve": "sm2" }); var keypair = ec.generateKeyPairHex(); k = new jsrsasign.BigInteger(keypair.ecprvhex, 16); var pubkeyHex = keypair.ecpubhex; c1 = jsrsasign.ECPointFp.decodeFromHex(ec.ecparams['curve'], pubkeyHex); this.p2 = userKey.multiply(k); this.Reset(); return c1; }, GetWords: function(hexStr) { var words = []; var hexStrLength = hexStr.length; for (var i = 0; i < hexStrLength; i += 2) { words[words.length] = parseInt(hexStr.substr(i, 2), 16) } return words }, CreatePoint: function(x, y) { var ec = new jsrsasign.KJUR.crypto.ECDSA({ "curve": "sm2" }); var ecc_curve = ec.ecparams['curve']; var pubkeyHex = '04' + x + y; var point = jsrsasign.ECPointFp.decodeFromHex(ec.ecparams['curve'], pubkeyHex); return point } }; jsrsasign.KJUR.crypto.ECParameterDB.regist( "sm2", // name 256, "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", // p "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", // a "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", // b "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", // n "1", // h "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", // gx "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", // gy ["sm2", "SM2"] ); // alias Array.Copy = function (sourceArray, sourceIndex, destinationArray, destinationIndex, length) { var cloneArray = sourceArray.slice(sourceIndex, sourceIndex + length); for (var i = 0; i < cloneArray.length; i++) { destinationArray[destinationIndex] = cloneArray[i]; destinationIndex++ } }; function SM3Digest() { this.BYTE_LENGTH = 64; this.xBuf = new Array(); this.xBufOff = 0; this.byteCount = 0; this.DIGEST_LENGTH = 32; //this.v0 = [0x7380166f, 0x4914b2b9, 0x172442d7, 0xda8a0600, 0xa96f30bc, 0x163138aa, 0xe38dee4d, 0xb0fb0e4e]; //this.v0 = [0x7380166f, 0x4914b2b9, 0x172442d7, -628488704, -1452330820, 0x163138aa, -477237683, -1325724082]; this.v0 = [1937774191, 1226093241, 388252375, -628488704, -1452330820, 372324522, -477237683, -1325724082]; this.v = new Array(8); this.v_ = new Array(8); this.X0 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; this.X = new Array(68); this.xOff = 0; this.T_00_15 = 0x79cc4519; this.T_16_63 = 0x7a879d8a; if (arguments.length > 0) { this.InitDigest(arguments[0]) } else { this.Init() } } SM3Digest.prototype = { Init: function () { this.xBuf = new Array(4); this.Reset() }, InitDigest: function (t) { this.xBuf = new Array(t.xBuf.length); Array.Copy(t.xBuf, 0, this.xBuf, 0, t.xBuf.length); this.xBufOff = t.xBufOff; this.byteCount = t.byteCount; Array.Copy(t.X, 0, this.X, 0, t.X.length); this.xOff = t.xOff; Array.Copy(t.v, 0, this.v, 0, t.v.length) }, GetDigestSize: function () { return this.DIGEST_LENGTH }, Reset: function () { this.byteCount = 0; this.xBufOff = 0; Array.Clear(this.xBuf, 0, this.xBuf.length); Array.Copy(this.v0, 0, this.v, 0, this.v0.length); this.xOff = 0; Array.Copy(this.X0, 0, this.X, 0, this.X0.length) }, GetByteLength: function () { return this.BYTE_LENGTH }, ProcessBlock: function () { var i; var ww = this.X; var ww_ = new Array(64); for (i = 16; i < 68; i++) { ww[i] = this.P1(ww[i - 16] ^ ww[i - 9] ^ (roateLeft(ww[i - 3], 15))) ^ (roateLeft(ww[i - 13], 7)) ^ ww[i - 6] } for (i = 0; i < 64; i++) { ww_[i] = ww[i] ^ ww[i + 4] } var vv = this.v; var vv_ = this.v_; Array.Copy(vv, 0, vv_, 0, this.v0.length); var SS1, SS2, TT1, TT2, aaa; //roateLeft for (i = 0; i < 16; i++) { aaa = roateLeft(vv_[0], 12); SS1 = aaa + vv_[4] + roateLeft(this.T_00_15, i); SS1 = roateLeft(SS1, 7); SS2 = SS1 ^ aaa; TT1 = this.FF_00_15(vv_[0], vv_[1], vv_[2]) + vv_[3] + SS2 + ww_[i]; TT2 = this.GG_00_15(vv_[4], vv_[5], vv_[6]) + vv_[7] + SS1 + ww[i]; vv_[3] = vv_[2]; vv_[2] = roateLeft(vv_[1], 9); vv_[1] = vv_[0]; vv_[0] = TT1; vv_[7] = vv_[6]; vv_[6] = roateLeft(vv_[5], 19); vv_[5] = vv_[4]; vv_[4] = this.P0(TT2) } for (i = 16; i < 64; i++) { aaa = roateLeft(vv_[0], 12); SS1 = aaa + vv_[4] + roateLeft(this.T_16_63, i); SS1 = roateLeft(SS1, 7); SS2 = SS1 ^ aaa; TT1 = this.FF_16_63(vv_[0], vv_[1], vv_[2]) + vv_[3] + SS2 + ww_[i]; TT2 = this.GG_16_63(vv_[4], vv_[5], vv_[6]) + vv_[7] + SS1 + ww[i]; vv_[3] = vv_[2]; vv_[2] = roateLeft(vv_[1], 9); vv_[1] = vv_[0]; vv_[0] = TT1; vv_[7] = vv_[6]; vv_[6] = roateLeft(vv_[5], 19); vv_[5] = vv_[4]; vv_[4] = this.P0(TT2) } for (i = 0; i < 8; i++) { vv[i] ^= (vv_[i]) } this.xOff = 0; Array.Copy(this.X0, 0, this.X, 0, this.X0.length) }, ProcessWord: function (in_Renamed, inOff) { var n = in_Renamed[inOff] << 24; n |= (in_Renamed[++inOff] & 0xff) << 16; n |= (in_Renamed[++inOff] & 0xff) << 8; n |= (in_Renamed[++inOff] & 0xff); this.X[this.xOff] = n; if (++this.xOff == 16) { this.ProcessBlock() } }, ProcessLength: function (bitLength) { if (this.xOff > 14) { this.ProcessBlock() } this.X[14] = (this.URShiftLong(bitLength, 32)); this.X[15] = (bitLength & (0xffffffff)) }, IntToBigEndian: function (n, bs, off) { bs[off] = (n >>> 24 & 0xFF); bs[++off] = (n >>> 16 & 0xFF); bs[++off] = (n >>> 8 & 0xFF); bs[++off] = (n & 0xFF); }, DoFinal: function (out_Renamed, outOff) { this.Finish(); for (var i = 0; i < 8; i++) { this.IntToBigEndian(this.v[i], out_Renamed, outOff + i * 4) } this.Reset(); return this.DIGEST_LENGTH }, Update: function (input) { this.xBuf[this.xBufOff++] = input; if (this.xBufOff == this.xBuf.length) { this.ProcessWord(this.xBuf, 0); this.xBufOff = 0 } this.byteCount++ }, BlockUpdate: function (input, inOff, length) { while ((this.xBufOff != 0) && (length > 0)) { this.Update(input[inOff]); inOff++; length-- } while (length > this.xBuf.length) { this.ProcessWord(input, inOff); inOff += this.xBuf.length; length -= this.xBuf.length; this.byteCount += this.xBuf.length } while (length > 0) { this.Update(input[inOff]); inOff++; length-- } }, Finish: function () { var bitLength = (this.byteCount << 3); this.Update((128)); while (this.xBufOff != 0) this.Update((0)); this.ProcessLength(bitLength); this.ProcessBlock() }, ROTATE: function (x, n) { return (x << n) | (this.URShift(x, (32 - n))) }, P0: function (X) { return ((X) ^ roateLeft((X), 9) ^ roateLeft((X), 17)) }, P1: function (X) { return ((X) ^ roateLeft((X), 15) ^ roateLeft((X), 23)) }, FF_00_15: function (X, Y, Z) { return (X ^ Y ^ Z) }, FF_16_63: function (X, Y, Z) { return ((X & Y) | (X & Z) | (Y & Z)) }, GG_00_15: function (X, Y, Z) { return (X ^ Y ^ Z) }, GG_16_63: function (X, Y, Z) { return ((X & Y) | (~X & Z)) }, URShift: function (number, bits) { console.error(number); if (number > Int32.maxValue || number < Int32.minValue) { //number = Int32.parse(number) console.error(number); number = IntegerParse(number); } if (number >= 0) { return number >> bits } else { return (number >> bits) + (2 << ~bits) } }, URShiftLong: function (number, bits) { var returnV; var big = new jsrsasign.BigInteger(); big.fromInt(number); if (big.signum() >= 0) { returnV = big.shiftRight(bits).intValue() } else { var bigAdd = new BigInteger(); bigAdd.fromInt(2); var shiftLeftBits = ~bits; var shiftLeftNumber = ''; if (shiftLeftBits < 0) { var shiftRightBits = 64 + shiftLeftBits; for (var i = 0; i < shiftRightBits; i++) { shiftLeftNumber += '0' } var shiftLeftNumberBigAdd = new BigInteger(); shiftLeftNumberBigAdd.fromInt(number >> bits); var shiftLeftNumberBig = new BigInteger("10" + shiftLeftNumber, 2); shiftLeftNumber = shiftLeftNumberBig.toRadix(10); var r = shiftLeftNumberBig.add(shiftLeftNumberBigAdd); returnV = r.toRadix(10) } else { shiftLeftNumber = bigAdd.shiftLeft((~bits)).intValue(); returnV = (number >> bits) + shiftLeftNumber } } return returnV }, GetZ: function (g, pubKeyHex) { var userId = CryptoJS.enc.Utf8.parse("1234567812345679"); var len = userId.words.length * 4 * 8; this.Update((len >> 8 & 0x00ff)); this.Update((len & 0x00ff)); var userIdWords = this.GetWords(userId.toString()); this.BlockUpdate(userIdWords, 0, userIdWords.length); var aWords = this.GetWords(g.curve.a.toBigInteger().toRadix(16)); var bWords = this.GetWords(g.curve.b.toBigInteger().toRadix(16)); var gxWords = this.GetWords(g.getX().toBigInteger().toRadix(16)); var gyWords = this.GetWords(g.getY().toBigInteger().toRadix(16)); var pxWords = this.GetWords(pubKeyHex.substr(0, 64)); var pyWords = this.GetWords(pubKeyHex.substr(64, 64)); this.BlockUpdate(aWords, 0, aWords.length); this.BlockUpdate(bWords, 0, bWords.length); this.BlockUpdate(gxWords, 0, gxWords.length); this.BlockUpdate(gyWords, 0, gyWords.length); this.BlockUpdate(pxWords, 0, pxWords.length); this.BlockUpdate(pyWords, 0, pyWords.length); var md = new Array(this.GetDigestSize()); this.DoFinal(md, 0); return md }, GetWords: function (hexStr) { var words = []; var hexStrLength = hexStr.length; for (var i = 0; i < hexStrLength; i += 2) { words[words.length] = parseInt(hexStr.substr(i, 2), 16) } return words }, GetHex: function (arr) { var words = []; var j = 0; for (var i = 0; i < arr.length * 2; i += 2) { words[i >>> 3] |= parseInt(arr[j]) << (24 - (i % 8) * 4); j++ } var wordArray = new CryptoJS.lib.WordArray.init(words, arr.length); return wordArray } }; Array.Clear = function (destinationArray, destinationIndex, length) { for (elm in destinationArray) { destinationArray[elm] = null } }; function roateLeft(n, distance) { //return ((n << distance) | (n >>> (32 - distance))); return (n << distance)|(n >>> -distance); } SM2CipherMode = { C1C2C3: 0, C1C3C2: 1 }; console.log(sm2Encrypt(data,publicKey))
最后我们可以用burpsuite的插件对这个 js 加密函数进行调用爆破,如下:
相关文章:分析网站登录处的加密算法(一)
来源:谢公子的博客
责编:godunt
本文始发于微信公众号(谢公子学安全):分析网站登录处的加密算法(二)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论