우선 본 문제에 대한 설명은 다음과 같이 되어있는 것을 확인할 수 있다. 또한 문제 설명을 바탕으로 플래그는 ./flag.txt에 있을 것이라는 추측을 알 수 있었다.
본격적으로 문제 서버를 생성하여 홈페이지를 살펴보니 아래와 같이 입력할 수 있는 부분과 제출 부분이 있는 것을 확인할 수 있다.
우선 입력 칸에 임의의 값인 1234를 넣고 제출 버튼을 클릭하니 다음과 같이 가장 마지막 부분에 입력한 값이 출력되고 있는 것을 확인할 수 있었다.
문제에서 계산기 페이지라고 해주었기에 입력칸에 수식도 넣어보았더니 연산 값이 아래 부분에 출력되는 것을 확인할 수 있었다.
다음은 문제에서 제공해준 소스코드를 확인해보자
#!/usr/bin/python3
from flask import Flask, request, render_template
import string
import subprocess
import re
app = Flask(__name__)
def filter(formula):
w_list = list(string.ascii_lowercase + string.ascii_uppercase + string.digits)
w_list.extend([" ", ".", "(", ")", "+"])
if re.search("(system)|(curl)|(flag)|(subprocess)|(popen)", formula, re.I):
return True
for c in formula:
if c not in w_list:
return True
@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "GET":
return render_template("index.html")
else:
formula = request.form.get("formula", "")
if formula != "":
if filter(formula):
return render_template("index.html", result="Filtered")
else:
try:
formula = eval(formula)
return render_template("index.html", result=formula)
except subprocess.CalledProcessError:
return render_template("index.html", result="Error")
except:
return render_template("index.html", result="Error")
else:
return render_template("index.html", result="Enter the value")
app.run(host="0.0.0.0", port=8000)
이제 위 소스코드 분석을 통해 문제를 해결해보자
먼저 filter 함수가 정의되어 있는 것을 확인할 수 있다. 해당 함수는 파라미터로 formula라는 값을 받고 있다. w_list에는 소문자 알파벳, 대문자 알파벳, 0에서 9까지의 숫자, 그리고 공백과 '.', '(', ')', '+'를 더하여 리스트의 형태로 저장하고 있다. 이후 반복문에서는 입력받은 값(formula) 중 w_list에 없는 값이 존재할 경우 True를 반환하는 것을 확인할 수 있다.
def filter(formula):
w_list = list(string.ascii_lowercase + string.ascii_uppercase + string.digits)
w_list.extend([" ", ".", "(", ")", "+"])
if re.search("(system)|(curl)|(flag)|(subprocess)|(popen)", formula, re.I):
return True
for c in formula:
if c not in w_list:
return True
다음으로 index 함수에서는 POST 메소드로 fomula를 받고 있다. 이때, formula 값이 비어있지 않으면 filter 함수를 실행시킨다. True가 반환되면 Filtered를 출력하고 그렇지 않을 경우 formula를 eval 함수를 통해 실행하고 있는 것을 확인할 수 있다. 이후 최종적으로 결과를 페이지에 출력하며 만약 에러 상황인 경우 Error를 출력하게 된다.
def index():
if request.method == "GET":
return render_template("index.html")
else:
formula = request.form.get("formula", "")
if formula != "":
if filter(formula):
return render_template("index.html", result="Filtered")
else:
try:
formula = eval(formula)
return render_template("index.html", result=formula)
except subprocess.CalledProcessError:
return render_template("index.html", result="Error")
except:
return render_template("index.html", result="Error")
else:
return render_template("index.html", result="Enter the value")
위 코드들을 바탕으로 빈 값이 아닌 값을 입력할 경우 우선 filter 함수에서 필터링을 거치는 것을 알 수 있었다. 즉, filter 함수에서는 입력한 값이 알파벳 대소문자, 숫자, 공백, ., (, ), + 로만 이루어진 값인지, system, curl, flag, subprocess, popen이라는 문자열이 없는지를 확인하고 있다. 따라서 필터링에 통과할 경우 eval 함수를 통해 formula 값을 실행하여 출력하게 된다.
위 코드 내에서는 open 함수는 필터링 되어 있지 않기 때문에 이 함수를 이용하여 플래그 값을 구해볼 수 있다.
open 함수로 flag.txt 파일을 읽어오기 위해서는 open('flag.txt').read()를 사용해야 한다. 하지만 해당 코드에서는 문자열이 필터링 되어있고, 사용 가능한 입력 값은 숫자와 공백, 점, 괄호, + 뿐이므로 chr을 활용하여 아스키값으로 변환해서 사용할 수 있다.
이때, 일일히 해당 문자열을 아스키코드 값으로 변환하고 각각의 값에 chr(아스키 값)을 해주고 이 값을 모두 더해주어도 된다. 우선 나는 해당 방식으로 문제를 풀이했으나, 다른 사람들이 사용한 빠른 익스플로잇 코드도 아래 추가하려 한다.
📌 익스플로잇 코드
result = ""
for i in "open('flag.txt').read()":
result += f"char({ord(i)})+"
print(result[:-1])
이렇게 확인한 값을 eval 함수를 사용해서 아래와 같이 입력칸에 넣어줄 수 있다.
그러면 아래와 같이 플래그 값이 출력되는 것을 확인할 수 있다.
문제해결-!
'CTF' 카테고리의 다른 글
[Dreamhack] out of money (0) | 2024.05.22 |
---|---|
[Dreamhack] simple_sqli_chatgpt (0) | 2024.05.08 |
[CTFLearn] My Blog (0) | 2024.05.01 |
[CTFLearn] Basic Injection (0) | 2024.05.01 |
[Dreamhack] Command Injection Advanced write-up (0) | 2023.11.22 |