[Dreamhack] BypassIF
문제 설명
-
Admin의
KEY
가 필요합니다! 알맞은KEY
값을 입력하여 플래그를 획득하세요.플래그 형식은 DH{…} 입니다.
풀이
문제 페이지에 접속하면 key를 입력하는 폼이 존재한다.
소스 코드는 아래와 같다.
#!/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)
윗 부분의 소스 코드를 분석해보면 KEY와 guest_key 변수에 각각 암호화하여 값을 생성하였고 filter_cmd
함수를 통해 알파벳의 대소문자와 extent를 통해 공백과 숫자도 추가하고, command_list의 리스트에 필터링 할 키워드들을 집어넣어 이 값이 입력하는 부분에 존재한다면 True를 반환하여 필터링 하는 것을 알 수 있다.
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
아랫 부분에는 /flag의 엔드포인트 부분을 POST로 되어있고 요청 메소드가 POST일 때 key와 cmd_input의 데이터를 찾아 key와 cmd에 넣어주고 비어있다면 ‘‘으로 아무것도 설정하지 않는다. 다음으로 cmd가 아무것도 존재하지 않고 key가 위 소스 코드에 있는 KEY 값과 일치하면 flag를 반환한다. 또한 아래에는 cmd에 값이 존재한다면 cmd값이 필터링에 걸리지 않으면 이를 쉘 명령으로 실행하는 것을 알 수 있다.
@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')
일단 먼저 KEY와 guest_key 값을 알아야 한다. KEY는 FLAG를 일단 알 수 없어 guest_key 부터 알아보면 md5 해시함수를 생성하고 이 해시값을 hdexdigest() 함수가 있으므로 16진수 문자열로 변환한다.
KEY = hashlib.md5(FLAG.encode()).hexdigest()
guest_key = hashlib.md5(b"guest").hexdigest()
이를 새로운 파일에 똑같이 guest_key 변수에 위 내용을 담고 print 해보면 값을 알 수 있다.
이를 문제 페이지에서 key값으로 집어넣어주면 flag 엔드포인트에서 hello guest 문자열을 확인할 수 있다. 여기서는 딱히 알 수 있는게 없는 것 같다.
위 소스 코드에서 cmd 부분과 key 부분이 KEY인 조건이 OR 이기 때문에 이 부분에서 key와 cmd 값이 key와 cmd_input 파라미터를 통해 값을 받아오므로 burp suite를 통해 한 번 시도해았다.
key = request.form.get('key', '')
cmd = request.form.get('cmd_input', '')
cmd_input
값으로 ls를 입력하니 파일 목록들이 잘 출력되는 것을 확인할 수 있었다. 그리고 cmd_input 파라미터만 & 연산자로 key 값 옆에 추가해주면 admin으로 인식하는 것도 확인할 수 있다.
이 cmd_input을 우회하기 위해서 command injection 우회 방법을 많이 찾아보았는데 쉽지 않은 것 같다. 소스 코드를 다시 잘 읽어보면 이 부분에서 timeout이 발생하면 KEY의 값을 알 수 있다. 위에 timeout을 5로 설정했으니 이보다 많이 설정해서 타임아웃을 발생시키면 KEY의 값을 알 수 있을 것이다.
except subprocess.TimeoutExpired:
return render_template('flag.html', txt=f'Timeout! Your key: {KEY}')
그럼 409ac0d96943d3da52f176ae9ff2b974
이라는 admin의 키값을 알아냈다. 다시 말하자면 해시함수는 일방향이기 때문에 같은 값을 해시함수로 생성하였을 때 값이 항상 일정하다.
그럼 이 키를 index 페이지에서 다시 집어넣으면 flag 값을 얻을 수 있다.
댓글남기기