-
[Clear] CODEGATE2022 superbee WriteupCTF/CODEGATE2022 2022. 3. 1. 14:21
주어진 페이지에 접근해보자.
404 Error page가 나와 다소 당황스럽지만, beego 2.0.0 을 사용한다는 점을 확인 하고 주어진 소스코드를 분석해보자.
우선 app.conf 파일에 글로벌 파라미터처럼 보이는 값들이 존재하지만, auth_key, password, flag 값은 숨겨져있다.
### app.conf app_name = superbee auth_key = [----------REDEACTED------------] id = admin password = [----------REDEACTED------------] flag = [----------REDEACTED------------]
아래와 같이 index.html과 login.html 페이지가 존재함을 알 수 있다.
index.html 페이지에 접근하면 위 글로벌 설정 값에서 flag를 읽어서 바로 출력해준다.
### index.html <html> <head> <title>{{.app_name}}</title> </head> <body> <h3>Index</h3> {{.flag}} </body> </html>
login.html 페이지가 별도로 존재하는것으로 보아, admin 계정으로 로그인하면 위 index.html 페이지에서 바로 플래그를 출력해주는 듯 하다.
### login.html <html> <head> <title>{{.app_name}}</title> </head> <body> <h3>Login</h3> <form action="./auth" method="post"> ID : <input type="text" name="id"><br> PW : <input type="password" name="password"><br> <input type="submit" value="login"> </form> </body> </html>
가장 핵심적인 main.go 페이지를 분석해보자.
package main import ( "crypto/aes" "crypto/cipher" "crypto/md5" "encoding/hex" "bytes" "github.com/beego/beego/v2/server/web" ) type BaseController struct { web.Controller controllerName string actionName string } type MainController struct { BaseController } type LoginController struct { BaseController } type AdminController struct { BaseController } var admin_id string var admin_pw string var app_name string var auth_key string var auth_crypt_key string var flag string func AesEncrypt(origData, key []byte) ([]byte, error) { padded_key := Padding(key, 16) block, err := aes.NewCipher(padded_key) if err != nil { return nil, err } blockSize := block.BlockSize() origData = Padding(origData, blockSize) blockMode := cipher.NewCBCEncrypter(block, padded_key[:blockSize]) crypted := make([]byte, len(origData)) blockMode.CryptBlocks(crypted, origData) return crypted, nil } func Padding(ciphertext []byte, blockSize int) []byte { padding := blockSize - len(ciphertext)%blockSize padtext := bytes.Repeat([]byte{byte(padding)}, padding) return append(ciphertext, padtext...) } func Md5(s string) string { h := md5.New() h.Write([]byte(s)) return hex.EncodeToString(h.Sum(nil)) } func (this *BaseController) Prepare() { ##### ......................................... (1) controllerName, _ := this.GetControllerAndAction() session := this.Ctx.GetCookie(Md5("sess")) ##### .......................... (1)-1 if controllerName == "MainController" { if session == "" || session != Md5(admin_id + auth_key) { ##### ....... (1)-2 this.Redirect("/login/login", 403) return } } else if controllerName == "LoginController" { if session != "" { this.Ctx.SetCookie(Md5("sess"), "") } } else if controllerName == "AdminController" { domain := this.Ctx.Input.Domain() if domain != "localhost" { this.Abort("Not Local") return } } } func (this *MainController) Index() { ##### ...................................... (2) this.TplName = "index.html" this.Data["app_name"] = app_name this.Data["flag"] = flag this.Render() } func (this *LoginController) Login() { ##### ..................................... (3) this.TplName = "login.html" this.Data["app_name"] = app_name this.Render() } func (this *LoginController) Auth() { ##### ...................................... (4) id := this.GetString("id") password := this.GetString("password") if id == admin_id && password == admin_pw { this.Ctx.SetCookie(Md5("sess"), Md5(admin_id + auth_key), 300) this.Ctx.WriteString("<script>alert('Login Success');location.href='/main/index';</script>") return } this.Ctx.WriteString("<script>alert('Login Fail');location.href='/login/login';</script>") } func (this *AdminController) AuthKey() { ##### ................................... (5) encrypted_auth_key, _ := AesEncrypt([]byte(auth_key), []byte(auth_crypt_key)) ##### .................................................................... (5)-1 this.Ctx.WriteString(hex.EncodeToString(encrypted_auth_key)) ##### ....... (5)-2 } func main() { ##### .............................................................. (6) app_name, _ = web.AppConfig.String("app_name") auth_key, _ = web.AppConfig.String("auth_key") auth_crypt_key, _ = web.AppConfig.String("auth_crypt_key") ##### ......... (6)-1 admin_id, _ = web.AppConfig.String("id") admin_pw, _ = web.AppConfig.String("password") flag, _ = web.AppConfig.String("flag") web.AutoRouter(&MainController{}) web.AutoRouter(&LoginController{}) web.AutoRouter(&AdminController{}) web.Run() }
(1)의 prepare() 부분을 보면, 이 서비스는 크게 3가지 controller로 구성되어있음을 확인 할 수 있다.
MainController : 특정 세션 관련 조건이 맞지 않으면(권한이 없으면) /login/login으로 튕겨낸다.
(1)-1, (1)-2 를 참고하면 쿠키의 파라미터명이 "sess" 문자열을 MD5 한 값이고 value가 "admin"+auth_key 값을 MD5 한 값이면 플래그를 획득 할 수 있음을 알 수 있다.
LoginController : 세션값이 비어있으면 특정 조건의 세션을 세팅한다.
AdminController : domain이 localhost가 아니면 Not local 을 출력해주며 Abort 해버린다.
그리고 (2)~(5) 의 선언부를 보면 각 기능이 특정 Controller로 선언되어있음을 알 수 있다.
MainController : Index()
LoginController : Login(), Auth()
AdminController : Authkey()
(6)의 main을 보면 각각 파라미터에 config 파일의 값을 불러옴을 알 수 있다.
그런데 이상한점이 있다.
(6)-1 을 보면 auth_crypt_key 를 app.conf파일에서 불러오는데 다시 살펴보면 해당 값은 선언되어있지 않다.
### app.conf app_name = superbee auth_key = [----------REDEACTED------------] id = admin password = [----------REDEACTED------------] flag = [----------REDEACTED------------]
그 후 각 컨트롤러에 대해 AutoRouter가 실행되는데 document를 살펴보면 아래와 같이 작동함을 알 수 있다.
https://github.com/beego/beedoc/blob/master/en-US/mvc/controller/router.md#auto-matching
GitHub - beego/beedoc: An open source project for beego documentation.
An open source project for beego documentation. Contribute to beego/beedoc development by creating an account on GitHub.
github.com
실제로 (5)-2를 통해 encrypted_auth_key 를 획득하기 위해서는 /admin/authkey 에 접근해야 한다.
(1)에서 AdminController의 조건을 맞춰주기 위해 Burp Suite를 활용하여 조건을 맞춰주면 정상적으로 encrypted_auth_key를 획득 할 수 있다.
(5)-1을 보면 아래 추출 된 값은 auth_key와 auth_crypt_key 로 aes 암호화가 된 값임을 알 수 있다.
00fb3dcf5ecaad607aeb0c91e9b194d9f9f9e263cebd55cdf1ec2a327d033be657c2582de2ef1ba6d77fd22784011607
따라서, 이를 decrypt하면 auth_key를 획득 할 수 있다.
aes.py from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad key = b"\x10"*16 iv = b"\x10"*16 #raw = pad(input_value) encrypted = bytes.fromhex("00fb3dcf5ecaad607aeb0c91e9b194d9f9f9e263cebd55cdf1ec2a327d033be657c2582de2ef1ba6d77fd22784011607") cipher = AES.new(key, AES.MODE_CBC, iv) #output_value = cipher.encrypt(raw).hex() decrypted_value = unpad(cipher.decrypt(encrypted),16) print(decrypted_value)
필요한 조건을 모두 갖추었으므로 admin의 cookie key/value를 구해보자.
"sess" 를 md5화 하면
f5b338d6bca36d47ee04d93d08c57861
"adminTh15_sup3r_s3cr3t_K3y_N3v3r_B3_L34k3d" 를 md5화 하면
e52f118374179d24fa20ebcceb95c2af
위 두 값을 활용하여 /main/index.html에 접근하면 플래그를 획득 할 수 있다.
FLAG = codegate2022{d9adbe86f4ecc93944e77183e1dc6342}
반응형'CTF > CODEGATE2022' 카테고리의 다른 글
[Study] CODEGATE2022 myblog Writeup (0) 2022.03.01 [Study] CODEGATE2022 babyfirst Writeup (0) 2022.03.01 [Clear] CODEGATE2022 CAFE Writeup (0) 2022.03.01