Flask debug 模式 PIN 碼生成機制安全性研究筆記

0x00 前言

前幾天我整理了一個筆記:Flask開啟debug模式等於給黑客留了後門,就Flask在生產網路中開啟debug模式可能產生的安全問題做了一個簡要的分析。其中有一個比較嚴重的安全問題是,可以在互動式Python shell中執行自定義Python代碼。就這一點來講,在舊版本的Flask中是不需要輸入PIN碼認證就可以執行代碼,其危害不言而喻。

在新版本的Flask中需要輸入PIN碼進行認證,才能執行自定義代碼,於攻擊者來說,這顯然有點雞肋了。

而後,偶然中發現,在同一台機器上,多次重啟Flask服務,PIN碼值不改變。也就是說PIN碼是一個固定值,這極大的引起的我的興趣。

於是,筆者就PIN碼的生成機製做了一些學習研究,便有了本文。

0x01 基礎環境

Windows 7 x64

Python 2.7.14

Flask 0.12.2

pdb

0x02 PIN 碼生成流程分析

最開始在是在周會上,幾位大佬就PIN碼可能的生成方式發表了自己的看法。會後 @Royal.師傅 指出了PIN碼生成的關鍵函數,提點了我一發。

奈何靜態分析起來有些吃力,要是能有個工具可以在程序執行時,對其下斷點,一步一步的跟蹤,那還是極好的。 後來向ph師傅@周佩雨 請教後,其向我推薦了pdb,簡單理解pdb就是一個調試Python用的調試器(牆裂推薦!玩出了二進位安全的快感!2333)。

so,在分析Flask程序執行流程,直到定位到PIN碼生成函數這段過程,都會大量依賴pdb,來梳理函數間的調用關係。

示例代碼依舊使用上一篇文章中的測試代碼:

# -*- coding: utf-8 -*-import pdbfrom flask import Flaskapp = Flask(__name__)@app.route("/")def hello(): return Helloif __name__ == "__main__": pdb.set_trace() app.run(host="127.0.0.1", port=80, debug=True)

值得注意的是,我在第1行import pdb,第11行pdb.set_trace(),就是在app.run()函數前下斷點。關於pdb的常用命令,不需要再去其他博文中補充知識。用到哪個,我都會簡單介紹下。

第1步:啟動該Flask應用(其會在app.run()函數前斷掉)

第2步:使用s命令,進入app.run()函數中(C:Python27Libsite-packagesflaskapp.py 第782-846行),多次輸入n命令(執行下一行),抵達第841行的run_simple()函數

按s命令,進入run_simple()函數。多次執行n命令,抵達C:Python27Libsite-packageswerkzeugserving.py 第736行,創建DebuggedApplication對象的位置(即創建對象的過程會執行DebuggedApplication類的__init__構造方法)。

按s命令,步入DebuggedApplication類的實現代碼(C:Python27Libsite-packageswerkzeugdebug\__init__.py 第199-468行)中:

根據文件名稱、類名稱等可以推斷出,這部分中就會有生成PIN碼的關鍵代碼。

順便提一句,

Flask is a microframework for Python based on Werkzeug, Jinja 2 and good intentions.

Flask是基於Werkzeug和Jinja 2的Web框架。研究Flask的PIN碼生成機制,就是研究Werkzeug的PIN碼生成機制。

繼續向下跟,第251行和第262行之間

有一個判斷操作,如果PIN啟用的話,及self.pin存在值,則會通過_log()函數,將PIN碼列印到終端。

ok,那我們現在只要在程序執行到if self.pin is None:時。進入self.pin,查看其實現方式即可(這裡用到了Property的概念,簡單理解在Python的類中,針對類中的成員變數,提供了Property,方便定義get和set方法,方便對該變數取值和賦值。詳細內容可以在參考鏈接中查看)。

第266行,通過get_pin_and_cookie_name()函數對PIN碼進行賦值

繼續跟進get_pin_and_cookie_name()函數(第115-196行),重頭戲來了!

在這個函數中,前幾行定義了pin、rv、num 3個變數值為None(在調試器中使用【pp 變數名】即可查看變數值)。其中根據函數的返回值,rv的值就是我們要重點關注的PIN碼,在這個函數的執行流程中,需要重點關注rv變數的賦值。

由於PIN的值為None,so第108-137行兩個if判斷均不會執行,繼續向下走。

modname變數被賦值為「flask.app」

繼續向下執行,在第145行username = getpass.getuser(),username變數被賦值為「當前登錄伺服器的用戶名」。向下執行,mod被賦值為<module flask.app from C:Python27libsite-packagesflaskapp.pyc>。

繼續向下執行,第151-168行,是生成PIN碼的儲備階段,對多個變數進行了賦值。

下圖為各變數此時的值

通過h.hexdigest()函數可以獲得h的MD5值(後面會用到)。

根據上圖的執行流程,先來看下169-174行的for循環,循環次數即為前面那一坨變數值得數量,共6次。

第1次循環,將「當前機器用戶名」的MD5形式存入h變數中。

第2次循環,將「當前機器用戶名」+flask.app的MD5形式存入h變數中。

...

第6次循環,將以上6個值的MD5形式存入h變數中

第175、182行將兩個固定的字元串加入其中。變數num的值為MD5值16進位的前9位,經過187-194行代碼處理,以111-222-333形式輸出。

0x03 PIN 碼的生成流程安全么?

通過0x02小結,現在已經摸清了PIN碼的生成流程。我們可以知道PIN碼的值由【當前計算機用戶名:XXX】、【flask.app】、【Flask】、【C:\Python27\lib\site-packages\flask\app.pyc】、【str(uuid.getnode())】、【get_machine_id()】組合獲得,缺一不可。

【flask.app】、【Flask】已知。

絕對路徑可以由debug頁面的報錯信息獲得,【C:\Python27\lib\site-packages\flask\app.pyc】也能拿到。

現在的問題是,如何獲得【當前計算機用戶名:XXX】、【str(uuid.getnode())】、【get_machine_id()】3個變數的值。

先來看下Flask自動義的get_machine_id()函數(C:Python27Libsite-packageswerkzeugdebug\__init__.py 第51-101行)

返回值rv由內部的_generate()函數獲得。

根據第60-65行,可以看到,

若/etc/machine-id,/proc/sys/kernel/random/boot_id文件存在,則返迴文件中的值。看到這裡就知道,想要預測這個值,那是沒戲了。

因為我的測試機用的Windows,看一下對Windows這塊是怎麼實現的。

歡笑中打出GG。至於獲取【當前計算機用戶名:XXX】、【str(uuid.getnode())】的實現代碼,我這裡就不做過多的分析了。

0x04 後記

通過這次分析,可以學習到Flask的開發人員在實現PIN生成機制的過程中還是非常嚴謹的。至少我這裡沒有辦法預測出指定機器的PIN碼。

文章記錄了我這次分析的過程,雖然沒有找到預測PIN碼的方法,但是學習到了Flask的PIN碼生成機制,以及通過pdb調試代碼。也算是一種收穫吧。

當然,如果對這方面有興趣的同學,恰好看到了我的這篇文章,希望你也能有所收穫。同時,如有謬誤,還請不吝賜教。

之後的話,我可能還會看下有沒有繞過PIN碼直接調用Python shell的方式、或者其他的安全問題。

隨著Python的廣泛應用,在機器學習、Web開發方面可以越來越多的看到Python的身影,Python相關的安全問題也越來越重要。我這裡拋磚引玉,記錄一下我的學習過程,期待各位大佬投入到相關安全問題的挖掘中來(可能很多人已經在做了),同時可以分享自己的研究成果。期待ing

0x05 參考鏈接

Python 代碼調試技巧

Flask (A Python Microframework)

Python中的property() 函數 和@property 裝飾符

Linux 內核參數詳解-KERNEL

推薦閱讀:

【Python爬蟲實戰】為啥學Python,BOSS告訴你
編程語言簡史:有人討厭花括弧,於是他發明了Python
50行Python代碼實現圖片轉字元畫,今晚直播不見不散
Flask最佳實踐
9個Python編程小貼士

TAG:網路安全 | Flask | Python |