代碼審計:如何在全新編程語言中發現漏洞?
為了直觀體現代碼審計思想,對漏洞情景進行了簡化。
一,安全標準不一致
一門新的編程語言,作為后端處理程序,肯定是需要與中間件/數據庫等其他模塊相聯系的,如果它們對待請求的安全標準不同,就可能導致安全問題。下面我們用一些已知語言的例子來演示這一點。
案例一 WSGI與中間件不一致
WSGI作為橋梁連接中間件和應用程序,而作為應用程序的這個全新的編程語言也會在這一環節安全問題。
WSGI與中間件具有重合的管轄領域,或者WSGI與應用程序具有重合的管控范圍,就可能出現問題。
以nginx+gunicorn為例。
gunicorn是在中間件和pytho之間的一個橋梁,它是圖中WSGI的一種,也可以處理http請求。
如果中間件是nginx,它和gunicorn都有權力檢查http請求,此時就可能出現問題。
python部分
nginx部分
此時,nginx對待請求和gunciron對待請求的標準不同。構造/privateHTTP/1.1/../../public。nginx會解析../返回上級目錄,認為該請求是訪問/public,安全地放行傳給gunicron,而gunicorn不會這樣解析,反而認為是發送了兩個包,解析為訪問/private和訪問/public。這樣就繞過了安全檢查。
案例二 數據類型安全標準不一致
這門全新的編程語言勢必有多種數據類型來滿足不同的需求,如列表、數組等等。這時安全標準不一致就可能導致問題。
no-sql一度認為不可被注入,最后卻敗于這一點。
以mongodb+js為例。
mongodb舍棄了sql語句,規范寫法不采用拼接方式調用執行。即使采用安全規范,與php組合也容易出現問題。
mongdb部分
js部分
這里是無法拼接跳出的,字符串就是字符串,然而,借助js與php類似的可以傳入數組參數的特性,構造/login?username=admin&password['$ne']=1可以讓mongdb解析為db.Users.find({username:'admin',password:{'$ne':'1'}});這里的$ne是mongodb的操作符,意思為不等于,此時語義使得admin密碼不為1即可登入成功。
案例三 多種注入防御機制不一致
這門新的編程語言往往需要在不同情景輸入/輸出,輸出在html可能導致xss注入,輸出在mysql可能導致sql注入。我們可以采用一些安全措施來限制它們的產生,但是這兩種防御機制不相容時就會出現問題。
以xss注入防御+sql注入防御為例。
xss防御部分:
- 刪去所有標簽
- sql防御部分:
- 刪去黑名單關鍵字
- 總體效果
在關鍵字插入<a>標簽即可繞過。
二,代碼與數據可轉換
一門新的編程語言,為了使用方便,常常需要把一些代碼轉化成數據,或者把一些數據轉化成代碼,這可能導致安全問題。下面我們將以幾個案例演示這一點,
案例一 不安全的模板渲染
模板渲染是編程語言常見的功能,有時具有一些安全問題。
前端部分
對應代碼
模板部分
我們可以看到,開發者已經殫精竭慮的做了安全限制,盡可能的避免漏洞,每一個變量的限制都在避免產生漏洞,然而,依舊產生了漏洞。這是因為這依舊沒能完全分離數據與代碼,導致安全問題。
我們可以在user部分輸入)/*
接著在punc部分輸入*/ 任意一個無字母數字的shell ?>
讓punc從數據變成代碼,跳出安全限制,順利getshell。
要知道,開發者已經殫精竭慮的做了安全限制,卻仍然被突破。錯誤的渲染方式可能導致數據與代碼沒有嚴格分離,造成漏洞。
案例二 跨語言的數據傳遞
這種新的編程語言有時需要與其他語言的腳本交互,傳輸數據時就可能采用標記語言,比如xml、json、yaml等等?;蛘呤鞘褂门渲梦募韮Υ嬉恍╆P鍵常量。這樣有時會造成安全問題。
yaml是一種可以儲存數組、對象、列表等各種數據類型用于書寫配置文件或者跨語言傳輸數據使用的標記語言。
以yaml反序列化漏洞為例。
python部分
功能是給在線解壓的壓縮包寫一個配置文件
yaml部分
當我們以某種方式覆蓋這個yaml文件,換成如下內容,就會形成反彈shell。
三,可預測的安全處理方式
一門新的編程語言,勢必會有一些邏輯代碼來提高安全性,當我們不是選擇拒絕非法輸入而是對非法輸入進行安全處理時,就可能造成安全問題。
案例一 人性化矯正輸入
有時我們會善意的為輸入者可能的錯誤輸入形式進行矯正,這可能為攻擊者提供便利。
以CVE-2022-30333為例
在unRAR小于 6.12的版本中,存在一個由于人性化矯正輸入引發的漏洞,簡單的來說,我們可以輸入解壓后的文件路徑,開發者已經在這里殫精竭慮的做了安全限制,會把../等嘗試目錄穿越的操作認為是危險。但是,仍然產生了漏洞。函數DosSlashToUnix()出于人性化的考慮把\(反斜杠)轉化為/(正斜杠),使得..\能夠變成../繞過安全檢查,導致目錄穿越。最終效果就是可以在任何目錄下寫入任意文件。
案例二 不安全的安全性過濾輸入
我們如果修改非法輸入而不是拒絕非法輸入,就很可能產生問題。
以sql注入的不成熟防御為例。
有的人可能會說黑名單不全,事實上就算把sql所有保留字列入黑名單依舊存在問題,因為你并不是拒絕輸入而是改寫輸入,這個情景下可以雙寫繞過。
輸入?id=' oorr 1=1#
因為輸入被改寫了,可預測的改寫形式能夠被利用,造成繞過。
案例三 可預測的密鑰加密
當我們把某個認為攻擊者不可能獲取的系統變量作為密鑰,為程序的安全性沾沾自喜時,也許就會翻車。
以flask模塊的session為例:
flask的session放在cookie中,通過密鑰加密保證其未i被篡改。而這里密鑰就是主機名,如果通過某種方式獲取了這一變量,就會導致session被攻擊者完全控制,攻陷網站所有的用戶以及管理員。
后續服務中提供的下載功能具有缺陷,組合拳導致session也淪陷。
四,意外的可控變量
這門全新變成語言肯定需要與用戶交互,從而控制一些變量。我們通常會對其進行安全檢查,所以,出現意外的可控變量(我們認為不可控但實際上用戶可控)就很容易導致安全問題。
案例一 把變量儲存在兩個地方
當我們把變量儲存在兩個地方,就可能導致安全檢查失效。
以二次注入為例:
這里實現了一個用戶登錄功能,開發者已經在這里殫精竭慮的做了安全限制,各種轉義處理。但是他把變量儲存在了兩個地方,導致漏洞仍然出現。
我們可以發現那個非法輸入藏在session逃過了安全檢查,如果構造username=' or 1=1#
就可以修改所有用戶的密碼。
案例二 認為某可控變量不可控
實際上編程語言中即使采用獲取常量的方式獲取一些變量,也不能大意,它們也許還是可控的。
以User-agent注入為例。
可以看到開發者已經在這里殫精竭慮的做了安全限制,安全意識很強,但是依舊出現問題。
這都是因為開發者使用的語言中,獲取變量的方式也許是常量形式,開發者認為其不可控引起的。
結語
具有安全意識的開發者仍然可能產生漏洞,因為很多開發用不到的特性、甚至編程語言官方非預期的情景不是開發者掌握的知識,代碼安全審計是必要的。這門全新的編程語言可能出現的問題卻是任何編程語言代碼安全審計需要注意的共通之處。