程式碼審計:如何在全新程式語言中發現漏洞?

語言: CN / TW / HK

為了直觀體現程式碼審計思想,對漏洞情景進行了簡化。

一,安全標準不一致

一門新的程式語言,作為後端處理程式,肯定是需要與中介軟體/資料庫等其他模組相聯絡的,如果它們對待請求的安全標準不同,就可能導致安全問題。下面我們用一些已知語言的例子來演示這一點。

案例一 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等等。或者是使用配置檔案來儲存一些關鍵常量。這樣有時會造成安全問題。

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注入為例。

可以看到開發者已經在這裡殫精竭慮的做了安全限制,安全意識很強,但是依舊出現問題。

這都是因為開發者使用的語言中,獲取變數的方式也許是常量形式,開發者認為其不可控引起的。

結語

具有安全意識的開發者仍然可能產生漏洞,因為很多開發用不到的特性、甚至程式語言官方非預期的情景不是開發者掌握的知識,程式碼安全審計是必要的。這門全新的程式語言可能出現的問題卻是任何程式語言程式碼安全審計需要注意的共通之處。