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