0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Seccon_Bignners_2022  参加

Posted at

Seccon_Bignners_2022

今回初めて本コンペに参加した。
しかし、1問も解けずに時間切れになってしまった。(Welcomeは誰でもできる)
それで、easyレベルの問題についての復習をするために本記事を記載した。(完全に自分用)

参考にしたサイトや解説

Web問題

1.Util

問題のサイトに行くと以下のようなサイトが表示される。
image.png
とりあえず、localhost(127.0.0.1)のアドレスを入力してみる。
image.png
pingの結果を返してくれている模様。
入力フォームがあるので、うまくコマンドインジェクションができそう。。。

メインのソースコードを見てみる。

util_main.go
func main() {
	r := gin.Default()

	r.LoadHTMLGlob("pages/*")

	r.GET("/", func(c *gin.Context) {
		c.HTML(200, "index.html", nil)
	})

	r.POST("/util/ping", func(c *gin.Context) {
		var param IP
		if err := c.Bind(&param); err != nil {
			c.JSON(400, gin.H{"message": "Invalid parameter"})
			return
		}

		commnd := "ping -c 1 -W 1 " + param.Address + " 1>&2"
		result, _ := exec.Command("sh", "-c", commnd).CombinedOutput()

		c.JSON(200, gin.H{
			"result": string(result),
		})
	})

	if err := r.Run(); err != nil {
		panic(err)
	}
}

commndという変数にparam.Addressという変数をつなげて、pingのコマンドとし実行している。
つまり、param.Addressにコマンドを埋め込めれば目的のflagがゲットできそう。
ここでのparam.AddressはWebページからのフォームから入力されたデータに該当している。(詳しくはWebページのHTMLとmain.goを参照。パラメータのaddressに該当していることが分かる。)

そしたら、入力フォームにそれっぽいコマンドを入れてみる。
「127.0.0.1;ls /」とか
image.png
無効なIPアドレスだと怒られました。
表示されているWebページのHTMLを見てみる。
image.png
しっかりと入力制限がかけられているおり、正しく入力されていない場合は「Invalid IP address」を返す制御をしている。
しかし、該当の処理はフロントエンド側(Webページ)で完結している感じがする、、、
(通信をしている痕跡がなく、メインのプログラムコードにも上記のような制御は存在しない)
つまり、リクエストをWebページからではなく別の方法で行えば、コマンドインジェクションが成立しそう。

正常に入力するとpingというリクエストが投げられているので、このリクエストをcURLでコピーしてターミナルで実行してみる。
image.png
image.png
ちゃんと、Webページと同じ結果が確認できた。
次に、入力フォームのデータに該当する「address:127.0.0.1」の部分に先ほどのコマンドを入力してみる。
image.png
そしたら、ファイル一覧の中に「flag_A74FIBkN9sELAjOc.txt」というflagっぽいtxtファイルを発見。
中身をcatで見てみる
image.png
flagをゲット

3.Gallery

問題のサイトに行くと以下のようなサイトが表示される。
image.png
Plese Select!の部分がドロップダウンリストになっている。
pngを選択すると、それに該当する.pngファイルが表示される。
image.png
ファイルをクリックすると、画像が表示される。
おそらく、ドロップダウンリストの選択肢にはないが、flagに関連する画像ファイルが用意されていると思われる。

とりあえず、メインのプログラムを見てみる。

main_1.go
func main() {
	r := mux.NewRouter()
	r.PathPrefix("/images/").Methods("GET").Handler(http.StripPrefix("/images/", http.FileServer(http.Dir(DIR))))

	r.HandleFunc("/", IndexHandler)

	http.ListenAndServe(":"+PORT, middleware()(r))
}

どうやら、Httpリクエストをもらって、IndexHandlerとmiddlewareって関数で処理をしてから出力を返してる模様。

IndexHandler関数の中身を見てみる。

handlers_1.go
func IndexHandler(w http.ResponseWriter, r *http.Request) {
	t, err := template.New("index.html").ParseFiles("./static/index.html")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		log.Println(err)
		return
	}

	// replace suspicious chracters
	fileExtension := strings.ReplaceAll(r.URL.Query().Get("file_extension"), ".", "")
	fileExtension = strings.ReplaceAll(fileExtension, "flag", "")
	if fileExtension == "" {
		fileExtension = "jpeg"
	}
	log.Println(fileExtension)

	data := Embed{}
	data.ImageList, err = getImageList(fileExtension)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		log.Println(err)
		return
	}

	if err := t.Execute(w, data); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		log.Println(err)
		return
	}
}

クエリのfile_extensionがflagだった場合は.jpegを返すような制御がされている。

さらに、次の処理でgetImageListという関数を呼び出して、file_extensionの内容に応じたfileを出力するように制御している。

handlers_2.go
func getImageList(fileExtension string) ([]string, error) {
	files, err := os.ReadDir("static")
	if err != nil {
		return nil, err
	}

	res := make([]string, 0, len(files))
	for _, file := range files {
		if !strings.Contains(file.Name(), fileExtension) {
			continue
		}
		res = append(res, file.Name())
	}

	return res, nil
}

getImageListでは、file_extensionの内容の中にファイル名に含めれているかを検証し、含まれていればそれに該当するファイル名を返している模様。

つまり、リクエストでfile_extensionにflagを入力しても.jpegしか表示されないが、f,l,a,gのどれかを入力すればflagに該当するファイル名のものを表示してくれるかもしれない。
とりあえず、file_extension=fでURLを入力してみる。
image.png
flagぽいpdfが表示された。該当のファイルを開いてみる。
image.png
全て「?」になってしまっている、、、

なんか暗号化されてるのかと思ったが、メインプログラムでIndexHandler関数とは別にmiddleware関数が呼ばれてたはず。
次に、middleware関数を見てみる。

middleware_1.go
func middleware() func(http.Handler) http.Handler {
	return func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			h.ServeHTTP(&MyResponseWriter{
				ResponseWriter: rw,
				lengthLimit:    10240, // SUPER SECURE THRESHOLD
			}, r)
		})
	}
}

lengthLimitって変数が10240に限定されている。
変数の名前的に何かしらの長さの限界値を示しているよう。
他にlengthLimitが使われているところがないか探してみる。
以下の関数で使用されている。

middleware_2.go
func (w *MyResponseWriter) Write(data []byte) (int, error) {
	filledVal := []byte("?")

	length := len(data)
	if length > w.lengthLimit {
		w.ResponseWriter.Write(bytes.Repeat(filledVal, length))
		return length, nil
	}

	w.ResponseWriter.Write(data[:length])
	return length, nil
}

データの長さがlengthLimit以上であったら全て「?」に書き換える処理を行なっている。
つまり、一定以上のデータの長さよりデータを受け取れないようにしている。
したがって、データを分割してもらってから復元すれば良さそう。(やり方はわからんが、、、)

どうやらHTTPには範囲指定してデータをもらえる方法があるっぽい。
https://developer.mozilla.org/ja/docs/Web/HTTP/Range_requests
こんな感じで指定できるらしい。

curl http://i.imgur.com/z4d4kWk.jpg -i -H "Range: bytes=0-1023"

image.png
いけたっぽい、これで0〜1023[bytes]まで抜き取れた。
ちまちま抜き取るのはめんどいから1回で10000[bytes]まで抜き取る。
image.png
2回で全て抜き取れた、データファイルは16084[bytes]だった。
(lengthLimitの10240より6000ぐらいオーバーしていた、、、)
分割した2つのファイルをcatでくっつける。

cat out1 out2 > out3

image.png
くっつけたら、しっかりとpdfとして認識された。
開いてみると、flagが記載されている。
image.png

最後

その他の問題の解説気が向いたら更新します、、、、

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?