【摘 要】 源代碼是軟件的根本,其存在的安全缺陷是導致軟件出現漏洞的根源所在,人為因素的影響使得每個應用程序的源代碼都可能存在安全漏洞。從根本意義上來說,代碼審計就是挖掘源代碼中存在的代碼安全問題。本文對源代碼安全問題進行分析,闡述了源代碼審計方法和流程,通過研究Java Web漏洞產生的原因及源代碼表現形式,給出了源代碼安全編碼原則,總結出消除相應漏洞的安全編碼規范。
【關鍵詞】 源代碼安全審計 漏洞檢測 編碼規范 靜態檢測
1 引言
隨著計算機信息技術的發展,應用軟件呈現開放化、智能化、融合化、多元化等發展趨勢。應用軟件由于源代碼編寫不規范或引用的第三方框架、開源組件存在安全漏洞,從而導致軟件中隱藏著安全漏洞或質量缺陷。這些安全漏洞或質量缺陷一旦被攻擊者利用,將會威脅軟件系統和其應用數據的機密性、完整性、可用性。開放式Web應用程序安全項目(Open Web Application Security Project,OWASP)2017年發布的Web應用安全十大漏洞中有8項和源代碼缺陷相關。
應用軟件和信息系統的安全問題必須從底層源頭出發,進行源代碼安全審計,檢查代碼的安全缺陷,編寫是否遵循安全編程規范,開發是否使用了不安全的第三方組件,從而在安全事件發生前或漏洞隱患尚未被利用前有效規避大部分應用程序的代碼問題,提高系統的主動安全防御能力。
2 源代碼安全審計的方法和流程
2.1 源代碼安全審計方法
源代碼安全審計的主要目的是提高源代碼質量,通過對程序源代碼進行檢查和分析,發現源代碼在軟件設計、測試、應用部署等各階段中可能存在的安全缺陷或安全漏洞,從源頭上避免潛在的安全風險。源代碼審計技術可分為靜態檢測(Static Analysis Security Testing,SAST)、動態檢測(Dynamic Analysis Security Testing,DAST)及動靜結合檢測(Interactive Application Security Testing,IAST)。
靜態檢測是指在不運行程序代碼的情況下,對程序中數據流、控制流、語義等信息進行分析,配合數據流分析和污點分析等技術,對程序代碼進行抽象和建模,分析程序的控制依賴、數據依賴和變量受污染狀態等信息,通過安全規則檢查、模式匹配等方式挖掘程序源代碼中存在的漏洞。源代碼漏洞靜態檢測方法一般是將源代碼轉化為單詞(token)、樹、圖等代碼中間表示形式,結合不同的檢測算法和檢測模型進行檢測。
動態檢測是指向程序輸入人為構造的測試數據,根據系統功能或數據流向,對比實際輸出結果與預想結果,分析程序的正確性、健壯性等性能,判斷程序是否存在漏洞。動態檢測技術主要分為3種:模糊測試、動態符號執行和動態污點分析。
動靜結合檢測是一種將靜態分析和動態分析相結合的混合式漏洞檢測方法,先使用靜態檢測方法對大規模的軟件源代碼進行檢測,對大規模的軟件源代碼進行切分,有針對性地進行檢測。再使用動態檢測方法對已劃分的程序代碼進行數據輸入,根據數據流向來判斷漏洞是否存在。
2.2 源代碼靜態審計思路
源代碼審計的思路有以下3個。根據敏感函數來逆向追蹤參數的傳遞過程,即檢查敏感函數的參數,然后回溯變量,判斷變量是否可控且是否經過嚴格過濾;正向追蹤變量傳遞過程,觀察是否有變量輸入到高風險函數中,或傳遞的過程中是否有代碼邏輯漏洞;通讀全文代碼,根據自身的經驗判斷漏洞位置,直接挖掘功能點漏洞。
2.3 源代碼靜態審計流程
常見的靜態分析工具有: CodeQL、Fortify、CoBOT、Coverity、 RIPS、FindBugs、Cppcheck等。依據分析目標的不同,靜態分析可分為:面向源代碼的靜態分析和面向二進制代碼的靜態分析。面向源代碼的靜態分析以程序的源代碼作為輸入,對其進行語法分析、語義分析,并轉換為某種特定形式的中間表示,基于該中間表示進行數據流分析、控制流分析等。面向二進制代碼的靜態分析則是以經過反匯編等手段處理后的二進制代碼作為輸入,運用模式匹配或補丁對比等方式實現漏洞檢測,當程序是以二進制形式發布的,需要使用面向二進制代碼的漏洞分析。本文主要研究面向源代碼的靜態檢測技術的原理和流程。
源代碼通過編譯器進行詞法分析、語法分析和語義分析,生成抽象的中間表示,這些中間表示蘊含了源代碼的特定信息。例如,在語法分析階段,語法分析器會產生蘊含豐富語法信息的抽象語法樹。抽象語法樹中葉子節點用于表示操作數,非葉子節點用于表示操作符,樹的層次結構表示代碼語句間的嵌套關系。為了更有效地捕獲代碼的語法、語義及上下文信息,學術界提出使用抽象語法樹、控制流程圖、程序依賴圖等代碼的中間抽象表示建立漏洞挖掘模型。源代碼審計流程如圖1所示。
圖1 源代碼審計流程
2.3.1 詞法分析
詞法分析階段是編譯前端的第一個階段,這個階段的任務就是從左到右逐個字符對構成源程序的字符串進行掃描,即對源代碼進行掃描,并按照定義好的詞法規則進行解析,識別出一個個token,將源代碼分割成由一個個token組成的數組,即形成一個token流。
2.3.2 語法分析
語法分析是在詞法分析生成的token序列基礎上,識別token序列之間的關系,并表示成一種后續程序處理過程中更容易理解和訪問的中間表示形式,我們也稱為抽象語法樹。同時,將有關源代碼信息存放在符號表的數據結構中,符號表和中間表示形式一起用來構造目標程序。
2.3.3語義分析
語義分析在抽象語法樹的基礎上進行分析,分析時考慮被檢測程序的基本語義,根據上下文環境檢測源代碼中是否存在語義錯誤。
2.3.4控制流圖構建
控制流圖反映了程序中各語句之間的先后執行順序?刂屏鲌D是一個有向圖G=(V,E),其中V代表節點的集合,E是有向邊的集合。為了更清楚地標識一個控制流圖,額外加入2個控制流節點:START和STOP,其中START節點為控制流圖的入口節點,STOP節點為控制流圖的出口節點?刂屏鲌D中的節點V代表程序的1條語句,有向邊E表示語句的控制流走向。
2.3.5數據流圖構建
數據流圖可以通過分析抽象語法樹直接獲得,數據流圖能夠表示語句和變量之間的數據依賴關系。通過從控制流圖中提取控制依賴關系,從數據流圖中提取數據依賴關系,將2種關系融合到同一張圖中,進而形成程序依賴圖。
2.3.6函數調用圖構建
函數調用圖是對程序中函數調用關系的一種靜態描述,在函數調用圖中,節點表示函數,邊表示函數之間的調用關系。通過函數調用圖可以了解程序中的函數及函數之間的調用關系。由于面向對象程序設計的多態性,程序的函數調用圖只能大致表示出實際運行時函數的調用關系。
2.3.7數據流分析
數據流分析是指用來獲取有關數據如何沿著程序執行路徑流動的相關信息的集合。數據流分析的對象是控制流圖,通過對控制流圖的遍歷,從中收集變量的數值產生位置及使用情況等信息,檢測數據的賦值與使用是否發生不合理現象,從而檢測源代碼中潛在的安全漏洞。
2.3.8污點分析
污點分析是一種信息流分析技術,通過對程序中的敏感數據進行標記,跟蹤標記數據在程序中的傳播,從而檢測系統中存在的安全問題。污點分析被定義為三元組<Source, Sink, Sanitizer>,Source即污染源,表示程序從外界獲取的不可信輸入;Sink即污點匯聚點,通常為一組安全敏感函數,通過數據流分析跟蹤程序中污點數據的傳播;Sanitizer即無害處理,代表通過移除危害操作等手段使數據傳播不再對程序的安全性產生危害。污點分析則是在不運行和不修改代碼的情況下,對程序進行語句分析,運用程序變量之間的數據依賴關系,找出污點源(Sources)和污點匯聚點(Sink)之間的傳播路徑,從而找出程序潛在的漏洞隱患,再通過驗證數據方法進行無害處理(Sanitizer)。
3 安全編碼原則和規范
本文給出了源代碼安全編碼原則和源代碼安全編碼規范,分析了Java Web漏洞的代碼缺陷及產生原因,給出了預防結構化查詢語言(SQL)注入、跨站腳本攻擊、跨站請求偽造、文件上傳、命令執行等漏洞的安全編碼規范。
3.1 安全編碼原則
3.1.1數據輸入驗證原則
首先假設所有的輸入是惡意的,除非能夠證明輸入數據是安全的,包括來自傳感器、文件、用戶或數據庫等的輸入,不僅要限制輸入數據的類型、長度、格式和范圍,還要驗證輸入數據是否包含有害信息。
3.1.2身份驗證原則
為防止身份信息被竊取,用戶身份驗證信息在存儲、傳輸等過程中應采取保護措施。在通信通道采取身份驗證信息加密保護機制,如使用安全套接層(SSL)方式進行傳輸;口令的存儲不應直接存儲密碼,而應存儲密碼的單向散列值,使用用戶提供的密碼重新計算哈希值,從而減輕字典攻擊的威脅。同時限制用戶的口令長度和復雜度,以及限制用戶登錄次數。
3.1.3 最小授權原則
如果惡意程序被注入軟件中,進程被賦予的權限大小很大程度上決定了用戶能夠執行操作的類型。因此,軟件中特定對象如進程、用戶或計算機程序應被賦予最小權限,任何需要提權的操作,應盡可能保持最短的時間,一旦任務完成,應該立刻收回權限,從而限制潛在的安全風險。
3.1.4配置管理原則
配置管理功能應只有被授權的操作員和管理員才能訪問,若確需遠程管理的,應使用SSL或虛擬專業網(VPN)等加密通道進行遠程管理。對于配置文件、注冊表、數據庫等關鍵配置數據應采取數據保護措施,保護關鍵配置數據的存儲和訪問的安全。
3.1.5會話管理原則
Cookie可能包括敏感信息,為防止攻擊者查看或修改Cookie內容,使用密碼對Cookie內容進行加密,從而避免數據受到未經授權的操作。盡量減少會話時長,會話時間越短,攻擊者在捕獲會話Cookie后,能使用Cookie來訪問應用程序的時間就越少,因此可以減少重放攻擊和會話劫持風險。
3.1.6組件安全原則
在使用第三方開源組件時,應對開源組件安全性進行評估,避免使用存在已知漏洞的開源組件或對安全漏洞進行修補后使用。
3.1.7內存安全原則
在編程過程中應避免內存出現緩沖區溢出、整數溢出、字符串格式化等問題。
3.2 常見漏洞分析及安全編碼規范
3.2.1 SQL注入
3.2.1.1 SQL注入描述
SQL注入漏洞是程序將攻擊者的輸入參數拼接到了SQL語句中,從而構造、改變SQL語義對數據庫進行攻擊。
3.2.1.2 SQL注入安全編碼規范
(1)加強用戶輸入驗證
在數據請求提交數據庫之前,使用處理函數或正則表達式匹配安全字符串的方法過濾用戶輸入內容中不合法字符。如果返回值屬于特定的類型或有具體的格式,那么在拼接SQL語句之前就要進行校驗,驗證其有效性。未經過濾的用戶輸入參數不能進行SQL拼接或可擴展語言(XML)拼接。如圖2所示,SQL語句將查詢字符串常量與用戶輸入進行拼接來動態構建SQL查詢命令。僅當id不包含單引號時,上述查詢語句才會是正確的。
圖2 禁止未經過濾的參數進行SQL拼接示例
(2)參數化查詢
在使用JDBC或Hibernate框架時,使用預編譯SQL語句的應用程序接口(API)進行參數化SQL查詢。使用MyBatis框架時,使用#{}代替${}進行參數化查詢,因為#{}會調用preparedStatement的set方法來賦值;而MyBatis只是將${}替換成變量的值,直接拼接到SQL語句中執行。當無法使用參數化查詢,可使用創建白名單規定拼接到SQL查詢語句中的數據集合,或使用正則校驗限定拼接到SQL查詢語句中的數據中可包含的字符集。
使用預編譯語句prepared-Statement確保輸入值在數據庫中當作字符串、數字、日期或布爾值(boolean)等類型,而不是被作為SQL語法的一部分去執行,如圖3所示。
圖3 SQL語句參數化查詢示例
(3)避免直接向用戶顯示數據庫錯誤信息
避免直接向用戶顯示數據庫錯誤,如類型錯誤、字段不匹配等,防止攻擊者利用這些錯誤信息進一步判斷數據庫的有關信息。
(4)控制訪問權限
將普通用戶與系統管理員用戶的權限嚴格區分開,堅持最小授權原則,從而最大程度減少因越權而導致的SQL注入攻擊。
3.2.2 跨站腳本攻擊
3.2.2.1 跨站腳本攻擊描述
跨站腳本攻擊即XSS攻擊,是指前端和后端有交互但沒有做輸入輸出過濾,應用程序將用戶發送的不可信賴數據在未經過濾、轉義,直接存入數據庫或直接輸出到頁面,從而導致用戶Cookie被劫持、構建Get和Post請求、獲取用戶信息、惡意的JavaScript執行、XSS蠕蟲攻擊等后果。
3.2.2.2 跨站腳本安全編碼規范
跨站腳本攻擊可分為持久型XSS、非持久型XSS和DOM型XSS3種類型。攻擊者通過XSS漏洞進行劫持用戶Cookie、構建Get和Post請求、獲取用戶信息、XSS蠕蟲攻擊等。
(1)加強用戶輸入驗證
通過正則表達式限制輸入數據中可接受的字符集合。如果輸入數據為數字型參數,進行強制類型轉換來校驗數據的合法性,當輸入數據為字符型,則應限制輸入數據的長度。如果輸入數據存在惡意字符,則拒絕請求。
(2)客戶端輔助驗證
輸入數據的驗證首先要在服務端進行,在客戶端只能作為輔助手段進行輸入數據的驗證。
(3)編碼處理
在不可信數據輸出到頁面之前,進行編碼處理。使用addslashes()函數對字符串“’”“””“\”字符進行轉義。使用htmispecialchars()函數對字符串“&”“””“<”“>”進行html編碼。
(4)防止盜取Cookie
在重要的Cookie中加入HttpOnly屬性,使得通過JavaScript腳本無法讀取到Cookie信息,有效防止跨站腳本XSS攻擊。
3.2.3 跨站請求偽造
3.2.3.1跨站請求偽造描述
跨站請求偽造(Cross Site Request Forgery,CSRF)是指攻擊者通過偽裝成來自受信任用戶的請求,對受信任的網站執行操作的一種攻擊方式。
3.2.3.2跨站請求偽造規范要求
(1)驗證HTTP請求的Referer字段。Referer用于表明HTTP請求的來源地址。對每個HTTP請求驗證其Referer值,如果Referer值是同一個域名下,則接受該請求;如果 Referer是其他網站,則有可能是CSRF攻擊,拒絕該請求。
(2)在請求地址中添加token并驗證。攻擊者制造CSRF攻擊所使用的用戶驗證信息均來自Cookie中,攻擊者可以在不知道用戶信息的情況下直接利用用戶Cookie來通過安全驗證。要抵御CSRF,可在HTTP請求中加入1個隨機產生的token值,并在服務端校驗token。正常訪問時,客戶端瀏覽器能夠正確得到并傳回這個隨機數;而通過CSRF傳來的欺騙性攻擊中沒有token或者token內容不正確,則拒絕該請求。
3.2.4 XML外部實體注入
3.2.4.1 外部實體注入描述
當開發人員配置XML解析功能允許外部實體引用時,攻擊者通過發送惡意構造的XML數據對應用系統造成文件讀取、命令執行、內網端口探測、命令執行、拒絕服務攻擊等,從而導致XML外部實體注入。
3.2.4.2 XML外部實體注入規范要求
XML文檔結構包括XML聲明、DTD文檔類型定義(可選)、文檔元素。DTD文檔類型定義的作用是定義XML文檔的合法構建模塊,它使用一系列合法的元素來定義文檔的結構,約束了XML文檔的結構。DTD可以在XML文檔內聲明,也可以外部引用。
(1)在應用程序的所有XML解析器中禁用DTD文檔類型定義,如圖4所示。
圖4 禁用DTD文檔類型定義示例
(2)如果不可能完全禁用DTD文檔類型定義,則禁用外部實體和參數實體,如圖5所示。
圖5 禁用外部實體和參數實體示例
3.2.5 文件上傳漏洞
3.2.5.1文件上傳漏洞描述
文件上傳過程中,通常因為未校驗上傳文件后綴類型,導致用戶可上傳jsp等webshell文件。代碼審計時重點關注上傳文件類型校驗及文件大小的限制。
3.2.5.2文件上傳漏洞規范要求
(1)使用白名單方式限制可上傳的文件類型,如圖6所示,只允許上傳擴展名為“gif”“jpg”“jpeg”“png”的圖片文件。
圖6 限制上傳文件類型示例
(2)限制允許上傳的文件大小,如圖7所示,限制上傳文件大小最大為100KB。
圖7 限制上傳文件大小示例
(3)限制文件上傳目錄的權限,對于文件上傳目錄設置可讀、可寫、不可執行權限,禁止用戶上傳的文件在后臺執行。
3.2.6 命令執行漏洞
3.2.6.1命令執行漏洞描述
命令執行漏洞就是攻擊者可以直接在應用中執行系統命令,從而獲取敏感信息或者拿到shell權限。命令執行漏洞形成的原因是服務器對用戶輸入的命令沒有進行驗證和過濾,導致惡意代碼被執行。
3.2.6.2命令執行漏洞規范要求
(1)盡量不使用系統執行命令,避免通過指定cmd /c或/bin/bash -c參數執行系統命令。
(2)應避免從客戶端獲取命令,無法避免時,應使用白名單對可執行的命令進行限制;白名單過大時,應使用正則校驗限定用戶輸入數據中可包含的字符集。在系統命令需要接受用戶輸入時,未對輸入參數作限制,允許任意命令輸入。攻擊者可以利用這個漏洞控制服務器,如圖8所示。
圖8 命令執行漏洞示例
(3)對用戶輸入參數中的“&&”“|”“;”等字符進行過濾。
3.2.7安全配置缺陷
3.2.7.1安全配置缺陷問題描述
安全配置缺陷通常是由于不安全的默認配置、不完整的臨時配置、開源云存儲、錯誤的HTTP標頭配置,以及包含敏感信息的詳細錯誤信息等問題造成的。
3.2.7.2安全配置缺陷規范要求
(1)刪除不需要的配置和文件夾,刪除Web目錄下存在敏感信息的備份文件、測試文件、臨時文件、舊版本文件等,關閉多余端口,停用不用的服務,如FTP、Telnet、SMTP、SSH等,改默認口令。
(2)對所使用的第三方組件進行安全配置,使得第三方組件不存在已知漏洞。
3.2.8 敏感信息泄露
3.2.8.1敏感信息泄露描述
當開發人員缺乏一定的安全意識,未按照安全規范進行編碼時,會造成用戶敏感信息泄露和應用程序信息的泄露。
3.2.8.2敏感信息泄露規范要求
(1)不在錯誤信息頁泄露系統詳細信息、會話標識符、用戶賬號信息、物理路徑、數據庫版本及路徑、SQL語句等相關信息。
(2)刪除注釋代碼,避免注釋代碼中存在遺留的測試賬號信息、敏感接口地址以及第三方服務的敏感信息泄露。
(3)禁止使用Get方法傳遞敏感參數(會話標識、身份證號等),因為Get方法會將參數顯示在URL中,傳輸過程中所有的代理及緩存服務器都可以直接獲取用戶數據。
(4)避免在前端代碼中存放敏感信息,如Hidden字段存在管理員賬號密碼等。
(5)禁止帶有敏感數據的Web頁面緩存,可以通過函數設置強制瀏覽器不進行緩存,如圖9所示。
圖9 強制瀏覽器不進行緩存示例
3.2.9第三方組件安全
3.2.9.1第三方組件安全
為節約成本、提高開發效率,大量開源組件被引入企業軟件開發過程中,因此避免使用存在安全隱患的組件對于保證軟件安全具有重大意義。
3.2.9.2第三方組件規范要求
從官方渠道獲取第三方組件,定期對第三方組件進行安全性檢測,確保組件不存在已知安全漏洞,并及時升級組件版本;建立第三方組件庫,統一進行維護和管理,避免個人從其他渠道獲;對第三方組件進行完整性驗證,確保所使用的第三方組件未被篡改。
4 結語
隨著云計算的普及、微服務等基礎架構的成熟,企業開發模式也從傳統的瀑布模型演變到持續集成安全防護理念開發安全運維一體化(Dev-SecOps)。DevSecOps理念將安全貫穿設計、開發、測試、運營生命周期的每個環節,使源代碼安全漏洞能夠得到盡早發現、盡快解決。源代碼靜態審計技術在理論上性能優異,自動化程度高,可挖掘層次深,但實際使用中存在漏報率和誤報率偏高的缺陷。近年來,機器學習技術取得了重大突破,為靜態審計技術注入了新的活力,基于深度學習的漏洞檢測方法將會顯著提高漏洞檢測效率,減少誤報率,是源代碼安全審計下一步的研究方向。
(原載于《保密科學技術》雜志2023年2月刊)