DownUnderCTF 2023 Writeup

DownUnderCTF 2023に0nePaddingというチームで参加して124 / 2549位でした。

今回は主にOSINTに取り組み、5 / 6問解けました。

[OSINT:Begginer] Excellent Vista!

OSINTによくある、写真を撮影した場所を答える問題です。

JPG画像が与えられます。

JPGなのでEXIFを確認すると、緯度経度が載っているのでGoogle Mapでその場所の地名を探すだけです。

exiftool ~/Downloads/ExcellentVista.jpg

下記の文字列をGoogle Mapで検索するだけですが、SEを忘れるとそれぞれ北緯、西経となり北太平洋に放置されるので注意です。

29°30'34.33"S,153°21'34.46E

DUCTF{Durrangan_Lookout}

[OSINT:Begginer] Bridget's Back!

同じく写真の撮影場所を答える問題です。

見える橋がゴールデンゲートブリッジということがわかったので周辺を探します。

ストリートビューで周辺を探索すると橋の見え方が同じビュースポットを発見しました。

この場所の名前がフラグになっていました。

DUCTF{H._Dana_Bowers_Rest_Area}

[OSINT:Easy] Comeacroppa

またまた写真の場所を特定する問題です。

画像の右上に4桁の数字(1800 or 1866)っぽいものが見えたのでAustralia 1800Australia 1866で検索してみました。

(このCTFはオーストラリアに関する問題の傾向があったので)

すると検索結果に似た画像が出ており、この建物がScotch Pie Houseという名前であることがわかります。

Australia Scotch Pie Houseで検索するとこの建物が存在する地名がわかります。

DUCTF{maldon}

ちなみにGoogle Lensにアップすることでも地名がわかります。

[OSINT:Medium] faraday

電話番号を基に用意されたAPIを使用して場所を特定する問題です。

オーストラリアのヴィクトリア内に当該電話番号を持った電話が存在するらしいので、

その町名を答える必要があります。

APIにはドキュメントが用意されていたのでドキュメントに沿ってAPIを使用します。

このAPIは緯度経度と検索範囲の半径を与えて、その円の中に該当の電話番号を持った電話が存在するかを返すAPIのようでした。

最初に試行した解法は、ヴィクトリアの町名と緯度経度の対応したリストを使用して総当りをする、でした。

下記のサイトからヴィクトリアの町のみを引っ張ってきてPythonスクリプトを書き、総当りしました。

simplemaps.com

ですが、ヒットする町が一意に絞れず、ヒットする町の全てをフラグとして提出してもIncorrectと言われ誤答をむやみに増やす結果になりました…。

なので方針を変更し、検索半径を徐々に絞っていき対象に到達するスクリプトを書きました。

与えた緯度経度周辺を探索し、ヒットした円の中心を新しい緯度経度に設定して半径を徐々に小さくしていきます。

import requests
import json
import math
import time
import urllib3
from urllib3.exceptions import InsecureRequestWarning
urllib3.disable_warnings(InsecureRequestWarning)

URL = "https://osint-faraday-9e36cbd6acad.2023.ductf.dev/verify"

# Victoria
lat = -37.036970870314754
long = 144.0646040878664

post_data = {
  "device": {
    "phoneNumber": "+61491578888"
  },
  "area": {
    "areaType": "Circle",
    "center": {
      "latitude": 0,
      "longitude": 0
    },
    "radius": 200000
  },
  "maxAge": 120
}

rad = 200000

while rad >= 2000:
    new_x_y = [(0,0),(rad/2,0),(0,rad/2),(rad/2,rad/2),(-rad/2,0),(0,-rad/2),(-rad/2,-rad/2)]
    for (x, y) in new_x_y:
        new_lat, new_long = (lat + x/111111, long + y/(111111 * math.cos(lat)))

        post_data["area"]["center"]["latitude"] = new_lat
        post_data["area"]["center"]["longitude"] = new_long
        post_data["area"]["radius"] = rad
        print(f"lat: {new_lat}, long: {new_long}, rad: {rad}")

        res = requests.post(
                URL,
                json.dumps(post_data),
                proxies={"http":"http://localhost:8080","https":"http://localhost:8080"},
                verify=False
        )

        if res.status_code == 200:
            res_data = res.json()
            if res_data["verificationResult"] == "TRUE":
                lat = new_lat
                long = new_long
                rad = int(rad / 1.75)
        time.sleep(10)

この際、緯度経度とメートル単位の計算があり、 1mを緯度経度換算する際に参考になったのが以下のサイトです。

緯度の1度と経度の1度は長さが違う。売上予測の基礎の基礎(2) | 売上予測 30年の実績

このサイトいわく精度を気にしないのであれば緯度経度は以下のように変換できるそうです。

  • 緯度 1° = 111111m
  • 経度 1° = 111111m * cos(緯度)

このスクリプトを実行すると最終的に以下の座標が得られます。

-36.46734230068619, 146.4276475126464

この座標をGoogle Mapで表示するとMilawaという地名だと言うことがわかります。

この地名がフラグになっていました。

DUCTF{milawa}

ちなみにMilawaは最初に使用した町名リストに載っていませんでした。

完全に時間の無駄…

[OSINT:Medium] monke bars

作問者がラップをリリースしたそうなのでその曲を探す問題です。

曲名が「monke bars」ということがわかっています。

しばらく調査をしていて、SoundCloudというサイトに該当する曲が存在することがわかりました。

monke bars results on SoundCloud - Listen to music

この曲のコメント欄を見るとフラグっぽい文言が投稿されていました。

これをフラグのフォーマットに沿って整形したものがフラグでした。

DUCTF{smackithackitdropthatpacketcrackthistrack}

[WEB:Easy] static file server

Python製のWebファイルサーバーだそうです。

アクセスすると、not_the_flagファイルが目に付きますのでアクセスするとflagの場所を教えてくれます。

この問題はソースコードが与えられるので読みます。

aiohttpweb.static()という関数にヒントがありそうです。

from aiohttp import web

async def index(request):
    return web.Response(body='''
        <header><h1>static file server</h1></header>
        Here are some files:
        <ul>
            <li><img src="/files/ductf.png"></img></li>
            <li><a href="/files/not_the_flag.txt">not the flag</a></li>
        </ul>
    ''', content_type='text/html', status=200)

app = web.Application()
app.add_routes([
    web.get('/', index),

    # this is handled by https://github.com/aio-libs/aiohttp/blob/v3.8.5/aiohttp/web_urldispatcher.py#L654-L690
    web.static('/files', './files', follow_symlinks=True)
])
web.run_app(app)

コメントに記載されているURLにアクセスし、内部の処理を見ると、ディレクトリトラバーサルに脆弱っぽいです。

joinpath(foo).resolve()foo/../../../../../../flag.txtのような文字列を与えると/flag.txtが返ってきます。

あとはペイロードを送信するだけでフラグが取得できます。