安全是軟件開發領域永遠的主題之一,隨著新技術浪潮的興起,安全的重要性愈發凸顯出來,對于金融等行業,甚至可以說安全是企業的生命線。不論是移動設備、普通 PC、小型機,還是大規模分布式系統,以及各種主流操作系統,Java 作為軟件開發的基礎平臺之一,可以說是無處不在,自然也就成為安全攻擊的首要目標之一。
今天我要問你的問題是,你了解 Java 應用開發中的注入攻擊嗎?
## 典型回答
注入式(Inject)攻擊是一類非常常見的攻擊方式,其基本特征是程序允許攻擊者將不可信的動態內容注入到程序中,并將其執行,這就可能完全改變最初預計的執行過程,產生惡意效果。
下面是幾種主要的注入式攻擊途徑,原則上提供動態執行能力的語言特性,都需要提防發生注入攻擊的可能。
首先,就是最常見的 SQL 注入攻擊。一個典型的場景就是 Web 系統的用戶登錄功能,根據用戶輸入的用戶名和密碼,我們需要去后端數據庫核實信息。
假設應用邏輯是,后端程序利用界面輸入動態生成類似下面的 SQL,然后讓 JDBC 執行。
~~~
Select * from use_info where username = “input_usr_name” and password = “input_pwd”
~~~
但是,如果我輸入的 input\_pwd 是類似下面的文本,
~~~
“ or “”=”
~~~
那么,拼接出的 SQL 字符串就變成了下面的條件,OR 的存在導致輸入什么名字都是復合條件的。
~~~
Select * from use_info where username = “input_usr_name” and password = “” or “” = “”
~~~
這里只是舉個簡單的例子,它是利用了期望輸入和可能輸入之間的偏差。上面例子中,期望用戶輸入一個數值,但實際輸入的則是 SQL 語句片段。類似場景可以利用注入的不同 SQL 語句,進行各種不同目的的攻擊,甚至還可以加上“;delete xxx”之類語句,如果數據庫權限控制不合理,攻擊效果就可能是災難性的。
第二,操作系統命令注入。Java 語言提供了類似 Runtime.exec(…) 的 API,可以用來執行特定命令,假設我們構建了一個應用,以輸入文本作為參數,執行下面的命令:
~~~
ls –la input_file_name
~~~
但是如果用戶輸入是 “input\_file\_name;rm –rf /\*”,這就有可能出現問題了。當然,這只是個舉例,Java 標準類庫本身進行了非常多的改進,所以類似這種編程錯誤,未必可以真的完成攻擊,但其反映的一類場景是真實存在的。
第三,XML 注入攻擊。Java 核心類庫提供了全面的 XML 處理、轉換等各種 API,而 XML 自身是可以包含動態內容的,例如 XPATH,如果使用不當,可能導致訪問惡意內容。
還有類似 LDAP 等允許動態內容的協議,都是可能利用特定命令,構造注入式攻擊的,包括 XSS(Cross-site Scripting)攻擊,雖然并不和 Java 直接相關,但也可能在 JSP 等動態頁面中發生。
## 考點分析
今天的問題是安全領域的入門題目,我簡單介紹了最常見的幾種注入場景作為示例。安全本身是個非常大的主題,在面試中,面試官可能會考察安全問題,但如果不是特定安全專家崗位,了解基礎的安全實踐就可以滿足要求了。
Java 工程師未必都要成為安全專家,但了解基礎的安全領域常識,有利于發現和規避日常開發中的風險。今天我會側重和 Java 開發相關的安全內容,希望可以起到一個拋磚引玉的作用,讓你對 Java 開發安全領域有個整體印象。
* 談到 Java 應用安全,主要涉及哪些安全機制?
* 到底什么是安全漏洞?對于前面提到的 SQL 注入等典型攻擊,我們在開發中怎么避免?
## 知識擴展
首先,一起來看看哪些 Java API 和工具構成了 Java 安全基礎。很多方面我在專欄前面的講解中已經有所涉及,可以簡單歸為三個主要組成部分:
第一,運行時安全機制。可以簡單認為,就是限制 Java 運行時的行為,不要做越權或者不靠譜的事情,具體來看:
* 在類加載過程中,進行字節碼驗證,以防止不合規的代碼影響 JVM 運行或者載入其他惡意代碼。
* 類加載器本身也可以對代碼之間進行隔離,例如,應用無法獲取啟動類加載器(Bootstrap Class-Loader)對象實例,不同的類加載器也可以起到容器的作用,隔離模塊之間不必要的可見性等。目前,Java Applet、RMI 等特性已經或逐漸退出歷史舞臺,類加載等機制總體上反倒在不斷簡化。
* 利用 SecurityManger 機制和相關的組件,限制代碼的運行時行為能力,其中,你可以定制 policy 文件和各種粒度的權限定義,限制代碼的作用域和權限,例如對文件系統的操作權限,或者監聽某個網絡端口的權限等。我畫了一個簡單的示意圖,對運行時安全的不同層次進行了整理。

可以看到,Java 的安全模型是以代碼為中心的,貫穿了從類加載,如 URLClassLoader 加載網絡上的 Java 類等,到應用程序運行時權限檢查等全過程。
* 另外,從原則上來說,Java 的 GC 等資源回收管理機制,都可以看作是運行時安全的一部分,如果相應機制失效,就會導致 JVM 出現 OOM 等錯誤,可看作是另類的拒絕服務。
第二,Java 提供的安全框架 API,這是構建安全通信等應用的基礎。例如:
* 加密、解密 API。
* 授權、鑒權 API。
* 安全通信相關的類庫,比如基本 HTTPS 通信協議相關標準實現,如[TLS 1.3](http://openjdk.java.net/jeps/332);或者附屬的類似證書撤銷狀態判斷([OSCP](https://en.wikipedia.org/wiki/Online_Certificate_Status_Protocol))等協議實現。
注意,這一部分 API 內部實現是和廠商相關的,不同 JDK 廠商往往會定制自己的加密算法實現。
第三, 就是 JDK 集成的各種安全工具,例如:
* [keytool](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html),這是個強大的工具,可以管理安全場景中不可或缺的秘鑰、證書等,并且可以管理 Java 程序使用的 keystore 文件。
* [jarsigner](https://docs.oracle.com/javase/9/tools/jarsigner.htm#JSWOR-GUID-925E7A1B-B3F3-44D2-8B49-0B3FA2C54864),用于對 jar 文件進行簽名或者驗證。
在應用實踐中,如果對安全要求非常高,建議打開 SecurityManager,
~~~
-Djava.security.manager
~~~
請注意其開銷,通常只要開啟 SecurityManager,就會導致 10% ~ 15% 的性能下降,在 JDK 9 以后,這個開銷有所改善。
理解了基礎 Java 安全機制,接下來我們來一起探討安全漏洞([Vulnerability](https://en.wikipedia.org/wiki/Vulnerability_(computing)))。
按照傳統的定義,任何可以用來**繞過系統安全策略限制**的程序瑕疵,都可以算作安全漏洞。具體原因可能非常多,設計或實現中的疏漏、配置錯誤等,任何不慎都有可能導致安全漏洞出現,例如惡意代碼繞過了 Java 沙箱的限制,獲取了特權等。如果你想了解更多安全漏洞的信息,可以從[通用安全漏洞庫](https://cve.mitre.org/)(CVE)等途徑獲取,了解安全漏洞[評價](https://www.first.org/cvss/calculator/3.0)標準。
但是,要達到攻擊的目的,未必都需要繞過權限限制。比如利用哈希碰撞發起拒絕服務攻擊(DOS,Denial-Of-Service attack),常見的場景是,攻擊者可以事先構造大量相同哈希值的數據,然后以 JSON 數據的形式發送給服務器端,服務器端在將其構建成為 Java 對象過程中,通常以 Hastable 或 HashMap 等形式存儲,哈希碰撞將導致哈希表發生嚴重退化,算法復雜度可能上升一個數量級(HashMap 后續進行了改進,我在[專欄第 9 講](http://time.geekbang.org/column/article/8053)介紹了樹化機制),進而耗費大量 CPU 資源。
像這種攻擊方式,無關于權限,可以看作是程序實現的瑕疵,給了攻擊者以低成本進行進攻的機會。
我在開頭提到的各種注入式攻擊,可以有不同角度、不同層面的解決方法,例如針對 SQL 注入:
* 在數據輸入階段,填補期望輸入和可能輸入之間的鴻溝。可以進行輸入校驗,限定什么類型的輸入是合法的,例如,不允許輸入標點符號等特殊字符,或者特定結構的輸入。
* 在 Java 應用進行數據庫訪問時,如果不用完全動態的 SQL,而是利用 PreparedStatement,可以有效防范 SQL 注入。不管是 SQL 注入,還是 OS 命令注入,程序利用字符串拼接生成運行邏輯都是個可能的風險點!
* 在數據庫層面,如果對查詢、修改等權限進行了合理限制,就可以在一定程度上避免被注入刪除等高破壞性的代碼。
在安全領域,有一句準則:安全傾向于 “明顯沒有漏洞”,而不是“沒有明顯漏洞”。所以,為了更加安全可靠的服務,我們最好是采取整體性的安全設計和綜合性的防范手段,而不是頭痛醫頭、腳痛醫腳的修修補補,更不能心存僥幸。
一個比較普適的建議是,盡量使用較新版本的 JDK,并使用推薦的安全機制和標準。如果你有看過 JDK release notes,例如[8u141](http://www.oracle.com/technetwork/java/javase/8u141-relnotes-3720385.html),你會發現 JDK 更新會修復已知的安全漏洞,并且會對安全機制等進行增強。但現實情況是,相當一部分應用還在使用很古老的不安全版本 JDK 進行開發,并且很多信息處理的也很隨意,或者通過明文傳輸、存儲,這些都存在暴露安全隱患的可能。
今天我首先介紹了典型的注入攻擊,然后整理了 Java 內部的安全機制,并探討了到底什么是安全漏洞和典型的表現形式,以及如何防范 SQL 注入攻擊等,希望對你有所幫助。
## 一課一練
關于今天我們討論的題目你做到心中有數了嗎?今天的思考題是,你知道 Man-In-The-Middle(MITM)攻擊嗎?有哪些常見的表現形式?如何防范呢?
*
- 前言
- 開篇詞
- 開篇詞 -以面試題為切入點,有效提升你的Java內功
- 模塊一 Java基礎
- 第1講 談談你對Java平臺的理解?
- 第2講 Exception和Error有什么區別?
- 第3講 談談final、finally、 finalize有什么不同?
- 第4講 強引用、軟引用、弱引用、幻象引用有什么區別?
- 第5講 String、StringBuffer、StringBuilder有什么區別?
- 第6講 動態代理是基于什么原理?
- 第7講 int和Integer有什么區別?
- 第8講 對比Vector、ArrayList、LinkedList有何區別?
- 第9講 對比Hashtable、HashMap、TreeMap有什么不同?
- 第10講 如何保證集合是線程安全的? ConcurrentHashMap如何實現高效地線程安全?
- 第11講 Java提供了哪些IO方式? NIO如何實現多路復用?
- 第12講 Java有幾種文件拷貝方式?哪一種最高效?
- 第13講 談談接口和抽象類有什么區別?
- 第14講 談談你知道的設計模式?
- 模塊二 Java進階
- 第15講 synchronized和ReentrantLock有什么區別呢?
- 第16講 synchronized底層如何實現?什么是鎖的升級、降級?
- 第17講 一個線程兩次調用start()方法會出現什么情況?
- 第18講 什么情況下Java程序會產生死鎖?如何定位、修復?
- 第19講 Java并發包提供了哪些并發工具類?
- 第20講 并發包中的ConcurrentLinkedQueue和LinkedBlockingQueue有什么區別?
- 第21講 Java并發類庫提供的線程池有哪幾種? 分別有什么特點?
- 第22講 AtomicInteger底層實現原理是什么?如何在自己的產品代碼中應用CAS操作?
- 第23講 請介紹類加載過程,什么是雙親委派模型?
- 第24講 有哪些方法可以在運行時動態生成一個Java類?
- 第25講 談談JVM內存區域的劃分,哪些區域可能發生OutOfMemoryError?
- 第26講 如何監控和診斷JVM堆內和堆外內存使用?
- 第27講 Java常見的垃圾收集器有哪些?
- 第28講 談談你的GC調優思路?
- 第29講 Java內存模型中的happen-before是什么?
- 第30講 Java程序運行在Docker等容器環境有哪些新問題?
- 模塊三 Java安全基礎
- 第31講 你了解Java應用開發中的注入攻擊嗎?
- 第32講 如何寫出安全的Java代碼?
- 模塊四 Java性能基礎
- 第33講 后臺服務出現明顯“變慢”,談談你的診斷思路?
- 第34講 有人說“Lambda能讓Java程序慢30倍”,你怎么看?
- 第35講 JVM優化Java代碼時都做了什么?
- 模塊五 Java應用開發擴展
- 第36講 談談MySQL支持的事務隔離級別,以及悲觀鎖和樂觀鎖的原理和應用場景?
- 第37講 談談Spring Bean的生命周期和作用域?
- 第38講 對比Java標準NIO類庫,你知道Netty是如何實現更高性能的嗎?
- 第39講 談談常用的分布式ID的設計方案?Snowflake是否受冬令時切換影響?
- 周末福利
- 周末福利 談談我對Java學習和面試的看法
- 周末福利 一份Java工程師必讀書單
- 結束語
- 結束語 技術沒有終點
- 結課測試 Java核心技術的這些知識,你真的掌握了嗎?