資安宣導 — 用 secure_filename 強化檔案上傳的安全
Posted on Jan 18, 2024 in Python 程式設計 - 中階 , Python 資訊安全 by Amo Chen ‐ 2 min read
現代很多應用都需要實作檔案上傳的功能,例如上傳使用者圖像、上傳 CSV 表格等等,不過檔案上傳也是很容易造成資安漏洞的一項功能,一旦寫得不好,就會給惡意人士製造機會入侵系統,甚至成為散佈惡意軟體的中繼站。
本文將說明 1 個檔案上傳功能的資安問題以及如何進行防禦。
本文環境
- Python 3
- Flask
造成資安問題的檔案上傳寫法
以下是 1 個極其簡單的 Flask 檔案上傳的程式碼,而且具有資安漏洞:
import os
from flask import Flask, request, redirect, url_for
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB
@app.route('/upload', methods=['POST'])
def upload():
file = request.files['file']
if file:
file.save(os.path.join('/tmp', file.filename))
return redirect(url_for('success'))
return redirect(url_for('index'))
上述程式碼的問題在於,我們未對 file.filename
進行檢查。
因為檔案是由不受信任來源所上傳的,如果惡意人士知道 /var/www/
是可以從外部存取的路徑,假設舉例 /var/www/index.html
是你的官網首頁,那麽惡意人士可以上傳檔案名稱為 ../var/www/index.html
的 HTML 檔案,上述程式碼在儲存檔案時,完整路徑就會變成 file.save("/tmp/../var/www/index.html")
,如果剛好 Flask 應用的具有讀寫 /var/www
的權限,那麼惡意人士就能夠輕鬆覆蓋你的官網首頁。
官網首頁被覆蓋事小,惡意人士甚至可能上傳惡意軟體到你的系統之後,透過其他手段執行取得系統權限,或者上傳惡意軟體到 /var/www
供其他人下載,你的官網就會莫名變為惡意網站⋯⋯。
使用 secure_filename 強化檔案上傳安全
如果你是 Python 的開發者,可以安裝 Werkzeug 套件,該套件提供 WSGI web 應用的相關函式庫,可以使用 werkzeug.utils
的 secure_filename
進行檔案名稱的過濾:
from werkzeug.utils import secure_filename
...(略)...
@app.route('/upload', methods=['POST'])
def upload():
file = request.files['file']
if file:
filename = secure_filename(file.filename)
file.save(os.path.join('/tmp', filename))
return redirect(url_for('success'))
return redirect(url_for('index'))
如果一來,再遇到 ../var/www/index.html
這類的檔案名稱的話,就會被轉為 index.html
, 提高檔案上傳的資訊安全!
p.s. secure_filename
也有實作針對 Windows 環境的防禦,有興趣可以閱讀原始碼
總結
本文所介紹的僅是其中 1 種強化資訊安全的方式,要寫出較安全的檔案上傳功能,不只需要對檔案名稱進行檢查,還需要對副檔名、檔案的 magic bytes 等進行檢查,如果有興趣的話,可以進一步閱讀 Flask 檔案上傳及測試範例, 該文不僅使用 secure_filename
對檔案名稱進行檢查之外,也對副檔名以及檔案的 magic bytes 進行檢查。
只要了解這些概念,不管是使用什麼程式語言、框架,都能夠寫出相對安全的檔案上傳功能!
以上!
Enjoy!
References
werkzeug/src/werkzeug/utils.py at main · pallets/werkzeug