Dreamhack CTF Season 7 Round #12 (🌱Div2)

Google CTFには年齢の関係で参加できなかったので1人で問題は解いていたがDreamhackに参加し、4位でした。

Hello, go!

first blood!!

GoのWebサイトです。

Go
package main

import (
	"html/template"
	"net/http"
	"bytes"
	"fmt"
	"strings"
	"github.com/labstack/echo/v4"
)


func greetHandler(c echo.Context) error {
	name := c.QueryParam("name")

	if name == "" {
		name = "go"
	}

	if strings.Contains(strings.ToLower(name),"flag"){
		return c.String(http.StatusBadRequest, "flag is not allowed.")
	}

	t, err := template.New("page").Parse(
		fmt.Sprintf(
			<html>
			<body>
				<h1>Hello, %s!</h1>
			</body>
			</html>, name))
	if err != nil {
		return c.String(http.StatusInternalServerError, "Template parse error: "+err.Error())
	}

	buf := new(bytes.Buffer)
	err = t.Execute(buf, c) 
	if err != nil {
		return c.String(http.StatusInternalServerError, "Template execution error: "+err.Error())
	}

	return c.HTMLBlob(http.StatusOK, buf.Bytes())
}

func main() {
	e := echo.New()
	e.GET("/", greetHandler)
	e.Start(":8000")
}   

fmt.Sprintf で動的に組み立てたテンプレート文字列をそのまま template.ParseExecute に流しているため、SSTIが存在する。

Execute(buf, c) の際、テンプレート中のドット(.)は echo.Context インスタンス c を指すため、.QueryParam.Fileといった任意メソッドの呼び出しが可能となっている。

一応strings.Contains(strings.ToLower(name),"flag"){とflagという文字列を含むものをはじいているが、print (“../f” “lag”)のように動的に生成してやるとよい。

よって、nameに{{.File (print “../f” “lag”)}}を入れると、Execute(buf,c)実行時に.echo.Context インスタンスを参照して、フラグが取得できる。

photographer

もうrevではchatGPTに勝てません!!!

Common things between us

Python
from Crypto.Util.number import getPrime, GCD
import os
import random

random.seed(os.urandom(10))

with open("flag", "rb") as f:
    flag = f.read()

assert 1 < len(flag) < 128

plaintexts = [os.urandom(len(flag)) for _ in range(999)]
last_pt = list(flag)
for i in range(999):
    for j in range(len(flag)):
        last_pt[j] ^= plaintexts[i][j]
plaintexts.append(bytes(last_pt))

mods = [[] for _ in range(1000)]
select = list(range(1000))


e = 0x10001
for _ in range(1000):
    while True:
        prime = getPrime(256)
        if GCD(e, prime - 1) == 1:
            break

    random.shuffle(select)
    choices = select[:4]

    for ch in choices:
        mods[ch].append(prime)
        if len(mods[ch]) == 4:
            select.remove(ch)

mods = [a[0] * a[1] * a[2] * a[3] for a in mods]

for pt, mod in zip(plaintexts, mods):
    pt = int.from_bytes(pt, "big")
    ct = pow(pt, e, mod)

    print(ct, mod)

1000個の平文すべてのXORを取ることでフラグを得ることができる。

各平文はそれぞれ異なる法でRSA暗号化されています。

各法は4つの素数の積で作られていますが、その素数は複数の法の間で共有されているのが脆弱性です。

よって、output.txtに出力されているmod同士をGCDして共通の因数を取り出します。

まれに、2つの法で複数の素因数が共通している場合があるのに注意が必要です。

各法の4つの素因数がわかったらRSAを複合して、p_iを得ることができます。

Python
from Crypto.Util.number import GCD

def solve():
    with open("output.txt", "r") as f:
        lines = f.readlines()
    ct = [int(line.split()[0]) for line in lines]
    n = [int(line.split()[1]) for line in lines]
    
    #素因数分解
    factors = [set() for _ in range(len(n))]
    for i in range(len(n)):
        for j in range(i + 1, len(n)):
            g = GCD(n[i], n[j])
            if g > 1:
                factors[i].add(g)
                factors[j].add(g)

    for i in range(len(n)):
        minimal_factors = {f1 for f1 in factors[i] if all(f1 == f2 or f1 % f2 != 0 for f2 in factors[i])}
        if minimal_factors:
            prod = 1
            for p in minimal_factors: prod *= p
            last_factor = n[i] // prod
            if last_factor > 1: minimal_factors.add(last_factor)
        factors[i] = minimal_factors

    plaintexts = []
    for i, f_set in enumerate(factors):
        if len(f_set) == 4:
            p1, p2, p3, p4 = list(f_set)
            phi = (p1 - 1) * (p2 - 1) * (p3 - 1) * (p4 - 1)
            try:
                d = pow(0x10001, -1, phi)
                pt = pow(ct[i], d, n[i])
                plaintexts.append(pt)
            except ValueError:
                continue

    byte_list = [p.to_bytes((p.bit_length() + 7) // 8, 'big') for p in plaintexts]
    max_len = max(len(b) for b in byte_list if b)
    flag = bytearray(max_len)
    for pt_bytes in byte_list:
        padded_pt = pt_bytes.rjust(max_len, b'\x00')
        for i in range(max_len):
            flag[i] ^= padded_pt[i]

    print(f"{flag.decode()}")

solve()

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール