LINE CTF 2023 - Simple Blogger (memcpy, +tmi)
This is my Admin simple blogger. Please take a look and tell me if there are components need to be improved.
./client_nix 34.146.54.86 10007
- [25 solves / 186 points]
약속 있어서 대회 때는 참여 못하고 대회 끝나고 풀이해보았다. 정말 열심히 분석한 것에 비해 플래그는 생각보다 쉽게 얻을 수 있었다.
Analysis
1 | . |
일단 문제에서 주어진 파일들을 살펴보자. 서버와 클라이언트가 존재한다. flag는 서버 측에 있는 것으로 보아.. 서버의 취약점을 이용해서 flag를 읽어오거나 쉘을 획득하는 문제일 것이라 생각했다. agent 폴더를 조금 더 빨리 봤으면 코드를 조금 더 빨리 작성할 수 있었을 텐데.. =ㅅ=
server_nix 을 분석해보자. 먼저 main 이다.
먼저 read 함수로 buf에 입력을 받고 sub_4014D2 함수에 input 변수와 함께 인자로 들어간다. 저 함수의 역할은 입력받은 buf 변수에서 input 변수로 내용을 복사한다. 그리고 switch 문으로 6가지의 기능을 실행시킬 수 있는데 get_flag 함수가 눈에 띈다. 최종적으로 저 함수를 실행시켜야할 것 같은데 입력값을 어떻게 주면 좋을지 sub_4014D2 함수를 분석했다.
memcpy 등으로 input 변수에다 여러 데이터들을 복사하는 것을 알 수 있고, 구조체의 구조는 parsing_header 함수를 먼저 분석해서 알 수 있었다.
위 함수에서 version, operation, length 오프셋을 알 수 있고 sub_4014D2 함수에서 토큰을 복사하는 과정에서 2번째 오프셋부터 16바이트가 토큰 값이라는 것을 알 수 있었다. 그리고 코드를 더 분석하다보니 length 뒤에는 데이터의 길이가 온다. 아참, length 길이 제한이 있는 것도 기억하자.
참고로 ((*(buf + 18) << 8) | *(buf + 19))은 빅엔디안으로 수를 표현한 것이다.
1 | msg = p64(0x4142434445464748) |
이런식으로 operation과 msg 값을 바꿔주면서 내가 원하는 기능을 실행시킬 수 있다. 분석하면서 내가 정리한 구조체는 아래와 같다. 참고로 구조체는 한 번에 정리할 수 있는 것이 아니라 다른 함수들도 보면서 정리할 수 있다.
1 | struct InputMessage |
이제 get_flag 함수를 보고 flag를 실행시켜야할 조건 같은 것이 있는지 살펴보자.
check_auth(token_input)이 1이면 flag를 출력해주는데 이는 priv가 1인 경우, 즉 admin인 경우를 의미한다. 참고로 해당 함수에서 priv를 가져오는 것은 database에서 쿼리를 날려서 가져오는데 아래와 같이 생겼다.
1 | SELECT priv FROM sess WHERE token = ? |
한편, admin의 priv가 1이다? 이것은 어디서 세팅될까. login 기능에서 super_admin이면 priv가 0, admin이면 priv가 1, 일반 계정이면 priv를 2로 세팅해주는 함수가 존재한다. 또한 토큰 값을 생성해주고 출력까지 해준다. 만약 admin으로 로그인에 성공하면 자연스럽게 토큰 값을 릭해서 get_flag 함수를 실행시키면 되지 않을까? 라고 생각했다.
1 | INSERT INTO sess(token, priv) VALUES(?, ?) |
이쯤되어서 문제에서 제공된 init.sql을 살펴보자.
1 | CREATE TABLE blog(id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(20), message VARCHAR(500)); |
아쉽게도 우리는 guest밖에 로그인을 하지 못한다. HEX(RANDOMBLOB(16)은 init.sql이 실행될 때 랜덤한 값으로 전부 다 다르게 들어간다. 그래서 앞에서 한 가설은 사용하지 못했다. guest밖에 로그인을 하지 못한다는 사실을 인지하고 read_msg와 write_msg 기능을 살펴봤는데 본격적인 구현에 앞서 토큰 값과 priv를 확인한다. 즉, 해당 기능을 이용할 수 있는지 권한을 확인하고 있었다. admin이어야 두 기능을 이용할 수 있다.. 그래서 두 기능은 볼 필요가 없었다.
자 마지막으로 ping 기능..만 남았다.
1 | SELECT token FROM sess WHERE rowid == 1 |
위 쿼리로 token을 하나 읽어와서 지역 변수에 저장한다. 그리고 취약점은 바로 아래에서 발생한다.
1 | *(ptr + 1) = size; |
size 값은 우리의 input이기 때문에 overflow가 발생할 가능성이 있다. overflow가 발생하면 dest 뒤에 있는 것도 출력할 수 있다. 마침 dest 뒤에 있는 건 아래와 같이 admin_token이다! 바로 익스 코드를 작성하러 가자.
1 | char dest[4]; // [rsp+20h] [rbp-40h] BYREF |
Solve
ping기능을 이용해admin_tokenleakadmin_token을 가지고get_flag실행!
Exploit Code
1 | from pwn import * |
tmi
처음에 agent 폴더를 확인했으면 문제 풀이가 더 빨랐을 것이라 판단된다. agent 폴더에서는 cron을 이용해서 매분마다 admin으로 로그인해서 database 내용을 지우는 역할을 한다. 참고로 이 기능은 ping 함수에 있다. 아무튼 저런 역할을 하는데, 저걸 수행하기 위해 파이썬 스크립트가 하나 들어있었다.
1 | def auth(): |
일부분만 가져왔는데, 이걸 빨리 봤으면 구조체를 더 쉽고 빠르게 분석해서 서버의 기능을 조금 더 빨리 트리거할 수 있지 않았을까 라는 생각이 든다. 하지만 덕분에 분석에 대한 자신감을 얻었으니.. 나쁜건 없다고 생각된다.
그리고 이 cron으로 처음에 익스 방향을 헷갈렸는데, 저 파이썬 스크립트를 실행하면 메모리에 admin의 password가 남아서 ping 함수를 통해 leak할 수 있었다. (물론 로컬에서만.. 도커도 아니고..) 로컬에서만 가능했던 이유는 .. 저 파이썬 스크립트를 익스 코드에 포함시켜 cron하는 것처럼 해줬다. 이렇게 하면 큰일난다. ㅎ 애초에 말도 안되는 생각이니 이해하려고 하지 말자.
아 그리고 일주일 전에 대회 서버로 문제를 풀 때 guest로도 로그인이 안되는 이슈가 있었다. 대회 중에는 문제 푸는데 지장이 없다고 공지하였다고 한다. 대회 때 풀이하려고 했으면.. ping 함수만 열심히 봤겠지..? 아무튼 이번 기회를 통해 ctf 기간에 열심히 참여해야할 이유를 느꼈다. 기간 내에 풀었으면 더 기분 좋았을 것 같다. 사실 끝나고 풀어도 기분좋긴 하다.
이 문제를 풀고 그 다음 문제도 열어봤는데 사실 아직까지 익스 코드를 스스로 작성하지 못하였다. 요즘 다른거에 빠져서.. 아무튼 comming soon..