[Clear] CODEGATE2022 superbee Writeup
주어진 페이지에 접근해보자.
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}