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_token
leakadmin_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..