Python代碼審計實戰(zhàn)案例總結(jié)之SQL和ORM注入
Python代碼審計方法多種多樣,但是總而言之是根據(jù)前人思路的遷移融合擴展而形成。目前Python代碼審計思路,呈現(xiàn)分散和多樣的趨勢。Python微薄研發(fā)經(jīng)驗以及結(jié)合實際遇到的思路和技巧進行總結(jié),以便于朋友們的學(xué)習(xí)和參考。
SQL注入和ORM注入
這兩者注入相似度較高,所以打算放在一起分析和總結(jié)。它們所用原理OWASP TOP TEN 中的描述非常合適,“將不受信任的數(shù)據(jù)作為命令或查詢的一部分發(fā)送到解析器時,會產(chǎn)生諸如SQL注入、NoSQL注入、OS注入和LDAP注入的注入缺陷。攻擊者的惡意數(shù)據(jù)可以誘使解析器在沒有適當(dāng)授權(quán)的情況下執(zhí)行非預(yù)期命令或訪問數(shù)據(jù)。”。
1. SQL注入
Python 中常見存在風(fēng)險SQL語句,在id或者Name可控的情況下存在安全隱患??煽貐?shù)可以將咱么期望他執(zhí)行的代碼按照語法進行拼接,從而執(zhí)行原本預(yù)期之外的代碼。
- sql = "select id,name from user_table where id = %s and name = %s" % (id, name)
- cur.execute(sql)
然而在實際案例中,這種執(zhí)行SQL語句并不多,比較典型的案例。實例代碼如下:
- import urllib
- import MySQLdb
- import SocketServer
- from SimpleHTTPServer import SimpleHTTPRequestHandler
- class MyHandler(SimpleHTTPRequestHandler):
- def _set_headers(self):
- self.send_response(200)
- self.send_header('Content-type', 'text/html')
- self.end_headers()
- def do_GET(self):
- print("got get request %s" % (self.path))
- hql = urllib.splitquery(self.path)[1]
- uri_c = str(hql)
- print('cmd===%s' % (uri_c))
- sql = "select id from user_table where id = %s" % uri_c
- db = MySQLdb.connect("localhost", "testuser", "test123", "TESTDB", charset='utf8')
- cursor = db.cursor()
- cursor.execute(sql)
- data = cursor.fetchone()
- self.wfile.write(data)
- def start_server():
- httpd = SocketServer.TCPServer(("127.0.0.1", 8090), MyHandler)
- print('Starting httpd...')
- httpd.serve_forever()
- if __name__ == "__main__":
- start_server()
這是一個簡單的HTTP服務(wù)器,目前在Python2中可以正常運行。通過urllib.splitquery獲取GET請求的參數(shù),uri_c 里面為請求參數(shù)的值。用值傳遞到SQL語句中拼接,從而產(chǎn)生注入問題。這是比較簡單的一種,正常情況下調(diào)用鏈可能會比較長,長短取決于平臺的設(shè)計架構(gòu)。
2. ORM注入
(1) sqlalchemy ORM注入(CNVD-2019-17301)
考慮到的理解上比較容易,用模塊進行舉例,并不涉及到框架。ORM注入是SQL注入的一種特殊情況,ORM模塊將SQL語句進行模板化,所以找SQL語句字符串的辦法不好用了。那么應(yīng)該怎么辦?根據(jù)模塊來找尋執(zhí)行方法,如果模塊存在問題和未妥善過濾或轉(zhuǎn)義、存在可控變量則可能會產(chǎn)生問題。如何去發(fā)現(xiàn)和查找Python ORM模塊,展現(xiàn)朋友們搜索技能的時候到了,不再老生常談。下面進入案例:
- from sqlalchemy import create_engine
- from sqlalchemy.orm import sessionmaker
- import sqlalchemy
- print("sqlalchemy_version:",sqlalchemy.__version__)
- engine = create_engine('mysql://root:123456@192.168.56.101:3306/mysql?charset=utf8')
- DB_Session = sessionmaker(bind=engine)
- session = DB_Session()
- session.execute('use mysql;')
- print(
- session.execute(
- """
- select * from user where User='root' and 11=1;
- """
- ).fetchall()
- )
這個是使用sqlalchemy的ORM注入,它存在任意執(zhí)行SQL語句的接口。道理上講這個是功能,實際情況大多數(shù)程序員都會認(rèn)為ORM是能夠防御SQL注入,這個可能會成為漏洞。通過轉(zhuǎn)義可以更好的解決問題,但是官方可能并不重視。另外還有sqlalchemy幾個問題利用order_by注入、利用”limit”和”offset”關(guān)鍵詞向”select()”函數(shù)傳遞注入等等,方法一樣利用模塊過濾不嚴(yán),暫不多論。
(2) Django JSON SQL注入(CVE-2019-14234)
咱們繼續(xù)看來 Django JSON SQL注入,關(guān)于這個漏洞已經(jīng)有前人分析過了。這個分析有些難度需要咱們了解Django和PostgreSQL,如果感覺吃力不妨先去學(xué)習(xí)一番。了解在PostgreSQL之中關(guān)于JSON數(shù)據(jù)的查詢主要使用ArrayField、JSONField、HStoreField,通過Django如何進行查詢PostgreSQL,Json.objects.filter()和QuerySet.filter()實現(xiàn),準(zhǔn)備工作就緒。
查詢使用方法如下:
- # 查詢方法
- # 查詢data數(shù)據(jù)下名稱為test的內(nèi)容為'user'的整個字段
- Json.objects.filter(data__test='user')
- or
- Json.objects.filter(**{"data__test":'user'})
通過補丁判斷實現(xiàn)方法使用了 self.key_name ,QuerySet.filter()的調(diào)用和self.key_name傳遞有關(guān)。
緊接著發(fā)現(xiàn)類 KeyTransformFactory 調(diào)用了 KeyTransform 傳入了 self.key_name ,后續(xù)是字符串拼接。這里不多詳細(xì)闡述感興趣的朋友跟下流程。
- class KeyTextTransform(KeyTransform):
- operator = '->>'
- ...
- # 字符串拼接
- (%s %s %s)" % (lhs, self.operator, lookup)
結(jié)合注入的知識進行實施測試,結(jié)果如下。
- # 使用注入
- # 拼接補全SQL語法
- Json.objects.filter(**{"""data__breed'='"a"') OR 11=1 OR('d""":'x',})
總結(jié)
本次總結(jié)Python的SQL注入和ORM注入的挖掘方法和相關(guān)案例,SQL注入方面沒有找到對應(yīng)的實際案例,咱們編寫簡單的案例作為參考。ORM注入為兩個案例,分別是關(guān)于模塊和框架。綜合作為實戰(zhàn)挖掘的參考,個人之力,恐有疏漏,盼斧正。