Dreamhack

[Dreamhack] BypassIF 풀이

minnggyuu 2024. 4. 3. 00:55

BypassIF

BypassIF이다. 

 

제목만 봐서는 IF문을 바이패스 해야될 것 같다.

문제 페이지

 

문제 페이지이다. 맞는 key를 submit 하면 될 것 같다. 

 

아무 값이나 넣어봤는데 아무 반응이 없다. 

 

감이 안잡히므로 소스코드를 보도록 하자. 

 

#!/usr/bin/env python3
import subprocess
from flask import Flask, request, render_template, redirect, url_for
import string
import os
import hashlib

app = Flask(__name__)

try:
    FLAG = open("./flag.txt", "r").read()
except:
    FLAG = "[**FLAG**]"

KEY = hashlib.md5(FLAG.encode()).hexdigest()
guest_key = hashlib.md5(b"guest").hexdigest()

# filtering
def filter_cmd(cmd):
    alphabet = list(string.ascii_lowercase)
    alphabet.extend([' '])
    num = '0123456789'
    alphabet.extend(num)
    command_list = ['flag','cat','chmod','head','tail','less','awk','more','grep']

    for c in command_list:
        if c in cmd:
            return True
    for c in cmd:
        if c not in alphabet:
            return True

@app.route('/', methods=['GET', 'POST'])
def index():
    # GET request
    return render_template('index.html')



@app.route('/flag', methods=['POST'])
def flag():
     # POST request
    if request.method == 'POST':
        key = request.form.get('key', '')
        cmd = request.form.get('cmd_input', '')
        if cmd == '' and key == KEY:
            return render_template('flag.html', txt=FLAG)
        elif cmd == '' and key == guest_key:
            return render_template('guest.html', txt=f"guest key: {guest_key}")
        if cmd != '' or key == KEY:
            if not filter_cmd(cmd):
                try:
                    output = subprocess.check_output(['/bin/sh', '-c', cmd], timeout=5)
                    return render_template('flag.html', txt=output.decode('utf-8'))
                except subprocess.TimeoutExpired:
                    return render_template('flag.html', txt=f'Timeout! Your key: {KEY}')
                except subprocess.CalledProcessError:
                    return render_template('flag.html', txt="Error!")
            return render_template('flag.html')
        else:
            return redirect('/')
    else: 
        return render_template('flag.html')


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000, debug=True)

 

별 거 없는 페이지에 비해 소스코드가 생각보다 길다(?)

 

guest_key = hashlib.md5(b"guest").hexdigest()

 

이런 부분이 있는데,  'guest' 라는 문자열을 바이트로 변환한 후, hexdigest로 16진수 문자열로 변환, hashlib.md5를 사용해서

해싱한 값을 guest_key에 저장한다.

 

이 값을 submit하면 어떻게 될까?

 

hashlib.md5(b"guest").hexdigest()

 

해싱된 값이 나왔다. 이 값을 한번 submit해보자.

 

guest_key를 submit한 결과

 

인사를 해준다. submit한 guest key도 출력해준다. 

 

혹시 admin를 해싱해서 넣어보면?

설마?
어디서 날로먹으려고

어림도 없다

 

@app.route('/flag', methods=['POST'])
def flag():
     # POST request
    if request.method == 'POST':
        key = request.form.get('key', '')
        cmd = request.form.get('cmd_input', '')
        if cmd == '' and key == KEY:
            return render_template('flag.html', txt=FLAG)
        elif cmd == '' and key == guest_key:
            return render_template('guest.html', txt=f"guest key: {guest_key}")
        if cmd != '' or key == KEY:
            if not filter_cmd(cmd):
                try:
                    output = subprocess.check_output(['/bin/sh', '-c', cmd], timeout=5)
                    return render_template('flag.html', txt=output.decode('utf-8'))
                except subprocess.TimeoutExpired:
                    return render_template('flag.html', txt=f'Timeout! Your key: {KEY}')
                except subprocess.CalledProcessError:
                    return render_template('flag.html', txt="Error!")
            return render_template('flag.html')
        else:
            return redirect('/')
    else: 
        return render_template('flag.html')

 

정직하게 소스코드 해석을 하고 풀도록 하자

 

POST 요청이 오면 key value를 key라는 변수에 저장하고, cmd_input value를 cmd라는 변수에 저장한다. 

cmd값이 빈 값이고, key값이 KEY이면,(여기서 KEY는 FLAG를 16진수로 변환하고, md5로 암호화 한 값이다.)

flag.html을 띄워주고, txt에 FLAG를 적어주는 것 같다. 

 

eilf, cmd 값이 비고, key 값이 guest_key와 동일하면, guest.html을 띄워주고, guest key를 출력해준다. (아까 했던거)

 

거기서 if cmd가 빈 값이거나, key가 KEY값일 경우, cmd값을 filter_cmd함수에 전달한다. 

만약 filter_cmd에서 FALSE가 반환되면,(if not FALSE == TRUE) /bin/sh -c cmd 를 실행해서 txt에 써서 보여준다.

 

결국 filter_cmd 함수를 우회해서 서버 내 커맨드를 실행시켜, 서버 내에 있는 FLAG를 찾아서 인코딩하면 될 것 같다. 

 

그럼 filter_cmd를 어떻게 우회하냐?

def filter_cmd(cmd):
    alphabet = list(string.ascii_lowercase)
    alphabet.extend([' '])
    num = '0123456789'
    alphabet.extend(num)
    command_list = ['flag','cat','chmod','head','tail','less','awk','more','grep']

    for c in command_list:
        if c in cmd:
            return True
    for c in cmd:
        if c not in alphabet:
            return True

 

내가 입력한 값이 command_list 리스트 중에 있으면 True를 반환 (True가 나오면 안됨)

내가 입력한 값이 alphabet 값에 없으면 True 반환 (alphabet은 string과 숫자로만 이루어져 있다.)

 

이 함수에서 False를 받아서 커맨드를 실행시켜서 서버 내에 있는 FLAG를 받아내보자. 

burp suite으로 해결해보자

burp suite를 이용해 key에 아무 값이나 실어 보낸 걸 잡아서 key를 input_cmd로 바꿔서 ls를 실행시켜보았다.

key -> cmd_input 으로 변경해 ls를 보내보았다
ls 결과

 

ls 가 실행되었다. flag.txt가 있다. 이걸 command_list를 피하고, 알파벳과 숫자만을 이용해서 읽어보자.

 

cat, head, tail을 필터링 해놓은 건 rev를 통해서 우회하면 되고, (rev는 텍스트를 거꾸로 읽어서 보여준다)

 

flag.txt를 어떻게 알파벳과 숫자만을 사용해서 우회할 것인가.. * 를 사용할 수가 없어서 우회하기가 힘들어 보인다. 

 

혹시 숨겨진 파일이 있을까? 라고도 생각해보았지만 ls -al 를 사용할 수도 없어서 하지 못한다. 

 

1단계 문제도 쉽게 풀지 못해서 좌절함과 동시에 댓글을 슬쩍 봤다.

고수분들

고수분들의 조언.. 소스 코드를 잘 봐라..

if cmd != '' or key == KEY:
    if not filter_cmd(cmd):
        try:
            output = subprocess.check_output(['/bin/sh', '-c', cmd], timeout=5)
            return render_template('flag.html', txt=output.decode('utf-8'))
        except subprocess.TimeoutExpired:
            return render_template('flag.html', txt=f'Timeout! Your key: {KEY}')
        except subprocess.CalledProcessError:
            return render_template('flag.html', txt="Error!")
    return render_template('flag.html')
else:
    return redirect('/')

 

이런 부분이 있다. 

filter_cmd 에서 바이패스에 성공하면, 밑의 구문을 실행한다. 

try 첫 번째는 cmd 커맨드를 실행한다. timeout=5을 써서 명령이 실행되는 시간을 5초로 제한했다. 5초를 넘어가면, Timeout 시킨다.

 

그럼 Timeout되면 어떻게 되냐?

밑에 달린 except 구문을 보면, timeout이 발생하면, 'Timeout! Your key: {KEY}' 를 반환한다.

ㅇ... 그냥 KEY.. FLAG를 던져준다. 

 

cmd_input = sleep 10

 

sleep 10으로 10초 기다리게 해보았다.

유레카

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

 

앞으로는 소스코드를 잘 보도록 하자...