松花皮蛋的黑板報
  • 分享在京東工作的技術感悟,還有JAVA技術和業內最佳實踐,大部分都是務實的、能看懂的、可復現的

掃一掃
關注公眾號

JAVA安全編碼標準

博客首頁文章列表 松花皮蛋me 2019-05-07 21:47

以下內容摘取自《JAVA安全編碼標準》,略做修改和補充解釋,這是一個把書讀薄和知識串通的過程

一、輸入驗證和數據凈化

  • 1、凈化穿越受信邊界的非受信數據,比如使用PreparedStatement防止SQL注入漏洞
  • 2、驗證前規范化字符串,比如使用Unicode編碼防止XSS跨站腳本漏洞
  • 3、在驗證之前標準化路徑名,使用file.getCannonicalPath()特殊處理軟連接、”.”、“..”、相對路徑,避免目錄遍歷漏洞
  • 4、不要記錄未經凈化的用戶輸入,以免注入,從而讓管理員誤以為系統行為
  • 5、限制傳遞給ZipInputStream的文件大小,通過ZipEntry.getSize()在解壓前判斷,如果過大則拋出異常
  • 6、使用ASCII字符集的子集作為文件名和路徑名,當包括特殊字符如控制字符、空格、分隔符、命令行解釋器、腳本和解析器時,會引起不可預期的行為
  • 7、從格式字符串中排除用戶輸入,避免拒絕服務
  • 8、不要向Runtime.exec()方法傳遞非受信、未凈化的數據
  • 9、凈化傳遞給正則表達式的非受信數據
  • 10、如果沒有指定適當的locale,不要使用locale相關方法處理與locale相關的數據,最常見的就是大小寫轉換toUpperCase,正確做法是”title”.toUpperCase(Locale.ENGLISH)
  • 11、不要拆分兩種數據結構的字符串,因為有補充字符和合并字符的存在,需要避免多字節編碼問題
  • 12、移除或者替代任何字符串時,必須進行驗證,避免成為關鍵字
  • 13、確保在不同的字符編碼中無損轉換字符串數據,不推薦使用string.getBytes(charset),推薦使用charsetEncoder類
  • 14、在文件或者網絡IO兩端使用兼容的編碼方式

二、聲明和初始化

  • 1、防止類的循環初始化,因為聲明為static final的一個字段為并不能保證它在被讀之前已經完全初始化
  • 2、不要重用Java標準庫的已經公共的標識、公共的工具類、接口或者包,重用名稱和定義不良好的import會導致不可預期的行為
  • 3、將所有增強for語句的循環變量聲明為final類型,比如Iterator迭代時,直接修改next時會拋異常,聲明為final后會直接產生編譯器錯誤
public class Cycle {
    private final int balance;
    private static final Cycle c = new Cycle();

    private static final int deposit = (int) (Math.random()*100);

    public Cycle() {
        balance = deposit-10;
    }

    public static void main(String[] args) {

        //-10
        System.out.println("balance is:"+c.balance);
    }
}

三、表達式

  • 1、不要忽略方法的返回值
  • 2、不要解引用空指針
  • 3、使用兩個參數的Arrays.equals()方法來比較兩個數組的內容
  • 4、不要用相等操作符來比較兩個基礎數據類型的值
  • 5、確保使用正常的類型來自動封裝數值
  • 6、不要在一個表達式中對同一變量進行多次寫入
  • 7、不要在斷言assert中使用有副作用的表達式,因為當關閉斷言功能后,表達式將不會執行

四、數值類型與運算

  • 1、檢測和向上轉型避免整數溢出
  • 2、不要對同一數據進行位運算和數學運算,避免對變量數據解析的混淆
  • 3、確保除法運算和模運算的除數不為0
  • 4、使用可容納無符號數據合法取值范圍的整數類型
  • 5、不要使用浮點數float和double進行精細計算
  • 6、使用stricftp修飾符確保跨平臺浮點運算的一致性
  • 7、不要嘗試與無序的非數值NaN進行比較,因為表達式NaN==NaN總是返回false
  • 8、檢查浮點輸入特殊的數值,比如Double.isNan(double d)、Double.isinfinite(double d)
  • 9、不要使用浮點變量作為循環計數器
  • 10、不要從浮點字元構造BigDecimal對象,避免精度損失
  • 11、不要比較或者審查以字符串表達的浮點數值,除非顯式去除字符串額外尾隨的0
  • 12、需要慎重處理向下轉型,比如int類型轉成byte類型,避免精度損失
  • 13、需要慎重處理向上轉型,比如float類型轉成double類型,避免精度損失

五、面向對象

  • 1、只有受信子類能對具有不變性的類和方法進行擴展
  • 2、聲明數據成員為私有并提供可訪問的封裝器方法
  • 3、當改變基類時,保存子類之間的依賴,不能破壞子類所依賴的程序不可變性。比如說,假設HashMap一開始只提供get、set方法,突然有一天新增了個entrySet方法,子類通過entrySet繞過權限檢查進行修改
  • 4、在新代碼中,不要混用具有范型和非范性的原始數據類型。當一個參數化的數個類型要訪問一個對象,而這個對象又不是參數化數據類型時,會產生堆污染,未經檢查的警告在錯誤時排查較困難
  • 5、不可變類為可變實例(成員)提供復制功能,避免傳遞給非受信代碼時修改原來的實例,特別需要注意的是ThreadLocal是淺拷貝,避免引用逸出
  • 6、對可變輸入和可變的內部組件創建防御性復制。起因是著名的TOCTOU漏洞, 一個程序先通過 access 判斷用戶是否有權限訪問一個文件,然后通過 open 打開該文件,攻擊者可以在時間間隙中間改變這個文件。當元素為可變對象的索引時,需要進行深復制
  • 7、不允許敏感類復制其自身,也就是不應該實現Cloneable接口,也不應該提供復制構造方法
  • 8、不要在嵌套類中暴露外部類的私有字段
  • 9、不要使用公有靜態的非final變量
  • 10、在構造函數中盡可能的不出現異常

六、方法

  • 1、不要使用斷言驗證方法參數,斷言失敗后并不會拋出一個適當真實的異常
  • 2、進行安全檢測的方法必須聲明為private或final
  • 3、對類、接口、方法和數據成員的可訪問性進行限制,避免子類覆蓋后訪問權限過大
  • 4、確保構造函數不會調用可覆寫的方法,避免子類發起基類的創建時卻調用了子類的方法,得到一個未初始化的值
  • 5、不要在clone()中調用可覆寫的方法
  • 6、定義了equals()方法的類必須定義hashCode()方法
  • 7、實現compareTo()方法時遵守常規合約,滿足傳遞性等
  • 8、不要使用析構函數,因為它的執行是沒有固定時間的,不能保證有效性,它的調用是無序的,另外在停止運行前,JVM可能不會去調用孤立對象的析構函數,嘗試在析構函數中更新狀態會失敗也不會有警告

七、異常行為

  • 1、不要消除或勿略可檢查的異常
  • 2、不能允許異常泄漏敏感信息
  • 3、記錄日記時應避免異常
  • 4、在方法失敗時恢復對象先前的狀態
  • 5、不要在finally程序段非正常退出,比如使用return\break\continute\throw,非正常退出會導致try程序段非正常終止,從而消除從try\catch中拋出的任何異常
  • 6、不要在finally程序段遺漏可檢查異常
  • 7、不要拋出未聲明的可檢查異常
  • 8、不要拋出RuntimeException、Exception、Throwable,盡量拋出明確異常
  • 9、不要捕捉NullPointerException或任何它的基類

八、可見性和原子性

  • 1、當需要讀取共享基礎數據類型變量時,需要保證其他可見性,勿必聲明為volatile變量或者正確進行代碼同步
  • 2、認為只包含不可變對象的引用的類是不可變的,這樣的假設是錯誤的
  • 3、保證對于共享變量的組合操作(+=、++等)是原子性的
  • 4、即使每一個方法是相互獨立并且是原子性的,也不要假設一組調用是原子性的,因為它可能僅僅是曾經滿足條件而已
  • 5、保證串聯在一起的方法調用(Builder模式)是原子性的
  • 6、保證在讀寫64位的數值時的原子性

九、鎖

  • 1、通過私有final鎖對象可以同步那些與非受信代碼交互的類,因為它滿足不可變原則,JVM使盡優化也不會出現線程安全問題
  • 2、不要基于那些可能被重用的對象進行同步,比如說Boolean、Integer類型的鎖
  • 3、不要基于那些通過getClass()返回的類對象來實現同步
  • 4、不要基于高層并發對象的內置鎖來實現同步,java.util.concurrent.locks包中Lock和Condition接口的實現類,比如重入鎖ReetrantLock
  • 5、即使集合是可訪問的,也不要基于集合視圖使用同步,可以使用Collections.synchronizedMap(map)進行同步,不可以使用map.keySet()進行同步
  • 6、對那些可以被非受信代碼修改的靜態字段,需要同步進入
  • 7、不要使用一個實例鎖(非靜態的類成員)來保護共享靜態數據
  • 8、使用相同的方式請求和釋放鎖來避免死鎖
  • 9、在異常條件時,保證釋放已經持有的鎖
  • 10、不要執行那些持有鎖時會阻塞的操作
  • 11、不要使用不正確形式的雙重檢查慣用法,需要保證延遲初始化必須在多線程中是同步的
  • 12、當類方法和類成員使用不同的內置鎖時,需要明確鎖保護的是哪個對象,比如下面這段代碼是線程不安全的
public class ListHelper<E> {

public List<E> list = Collections.synchronizedList(new ArrayList<>());

public synchronized boolean putIfAbsent(E x) {
    boolean absent = !list.contains(x);
    if(absent) {
        list.add(x);
    }
    return absent;
}

十、線程API

  • 1、不要調用Thread.run(),因為run方法中的語句是由當前線程而不是由新創建的線程來執行的,正確的操作是Thread.start()
  • 2、不能調用ThreadGroup方法,它的API可能會導致競態、內存泄漏以及不一致的對象狀態
  • 3、通過(notify()、signal())所有等待中的線程而不是單一線程,因為不能保證哪一個線程會接到通知,除非所有線程的等候條件是一致的
  • 4、始終在循環體中調用wait()和await()方法,避免中間線程修改狀態、惡意的通知、誤送的通知、虛擬喚醒的漏洞
  • 5、確保可以終止受阻線程,比如readlIne()阻塞于網絡IO時,在IO完成前它無法對變更的標記做出響應,需要避免拒絕服務漏洞
  • 6、不要使用Thread.stop()來終止線程,stop會造成線程停止操作并拋出ThreadDeath異常,會使對象處于不一致的狀態

十一、線程池

  • 1、使用線程池處理流量突發情況以實現降低性能運行
  • 2、不要使用有限的線程池來執行相互依賴的任務,避免線程饑餓死鎖
  • 3、確保提交至線程池的任務是可中斷
  • 4、確保線程池中正在執行的任務不會失敗而不給出任何提示,不僅會造成資源泄漏,還會對失敗的診斷很困難,因為線程池中的線程是可回收的。可以覆寫ThreadPoolExecutor回調的afterExecute()方法或者Future.get()
  • 5、程序必須確保線程池中的線程執行的每一個任務只能見到正確初始化的ThreadLocal對象實例

十二、與線程安全相關的其他規則

  • 1、不要使用非線程安全方法來覆寫線程安全方法
  • 2、不要讓this引用在創建對象時泄漏,常見途徑有:
  • 2.1、從創建對象的構造函數中調用一個非私有的、可覆寫的方法時,該方法返回thirs
  • 2.2 、從可變類的一個非私有的方法返回this
  • 2.3、將this作為參數傳遞給一個在創建對象的構造函數中調用的外部方法
  • 2.4、使用內隱類,內隱類維護指向外部對象的this引用的一個副本
  • 2.5、在創建對象的構造函數中將this賦給公有的靜態變量,從而將其公開
  • 2.6、從構造函數中拋出一個異常
  • 2.7、傳遞內部對象狀態至一個外部方法
  • 3、不在在初始化類時使用后臺線程,避免初始化循環和死鎖
  • 4、不要發布部分初始化的對象,因為JMM允許多個線程在對象初始化開始后和結束后觀察到對象

十三、輸入輸出

  • 1、不要操作共享目錄中的文件,因為強制文件鎖FileLock有很多的限制
  • 2、使用合適的訪問權限創建文件
  • 3、發現并處理與文件相關的錯誤,一般的文件操作方法通常使用返回值而不是拋出異常來指示其錯誤
  • 4、在終止前移除臨時文件
  • 5、在不需要時關閉資源,推薦使用try-with-resource方案
  • 6、不要使用Buffer中的wrap()或duplicate()創建緩存,并將緩存暴露給非受信代碼,對這些緩存區的修改會導致數值的修改
  • 7、不能在一個單獨的InputStream上創建多個緩存區封裝器,重定向InputStream會導致不可預期的錯誤,往征會拋出EOFException異常
  • 8、不要讓外部進程阻塞輸入和輸出流
  • 9、對讀取一個字符或者字節的方法,使用int類型的返回值,僅當讀取到末尾時會返回-1,不要過早將返回的值轉成byte或char類型
  • 10、不要使用write()方法輸出超過0~255的整數,超過后數值的高位部分會被截去
  • 11、使用read()方法保證填充一個數組,如果沒有達到len的要求,此方法會堵塞
  • 12、不要將原始的二進制數據作為字符數據讀入,比如說不指定編碼的情況下將BigInteger的字節數組轉換成字符串時會損失信息
  • 13、為小端數據的讀寫提供方法,不要使用java.io.DataInputStream中readShort()、readByte()等和對應的寫方法,它們僅針對大端字節序數據進行操作
  • 14、不要在受信邊界外記錄敏感信息
  • 15、在程序終止時執行正確的清理動作,避免在不確定的狀態下繼續執行,可利用addShutdownHook()

十四、序列化

  • 1、在類的演化過程中維護其序列化的兼容性,保證顯示指定serialVersionUID或者通過serialPersistenFields使用自定義的序列化
  • 2、不要偏離序列化方法的正確簽名,也就是readObject()、readObjectNoData()、writeObject()方法必須聲明為私有,而readResolve()、writeReplace()方法不能聲明為私有
  • 3、在將對象向信任邊界之外發送時,需要簽名并且封裝敏感對象
  • 4、不要序列化未經加密的敏感數據
  • 5、不要允許序列化和反序列化繞過安全管理器
  • 6、不能序列化內部類實例,當內部類被序列化時,包含在外部類的字段也會被序列化
  • 7、在反序列化時,必須在readObject()方法中對私有的可變組件進行防御性復制
  • 8、不要對實現定義的不可變因素使用默認的序列化格式,反序列會創建一個新的類實例但是不會調用它的構造函數
  • 9、不要從readObject()方法中調用可以被覆寫的方法,因為基類的反序列化發生在類反序列化前,所以在readObject()調用可覆寫方法會讀取到子類被完全創建之前的狀態
  • 10、在序列化時避免出現內存和資源泄漏,需要注意的是ObjectOutputStream維持了一個引用表來對先前序列化的對象進行跟蹤
  • 11、不要在(會進行序列化和反序列化的)POJO類上做業務邏輯

十五、平臺安全性

  • 1、不要允許特權代碼塊越過受信邊界泄漏敏感信息,比如從doPrivileged()代碼塊中返回指向敏感資源的引用
  • 2、不要在特權代碼塊中使用沒有驗證或者非受信的變量
  • 3、不要基于非受信源進行安全檢查,任何非受信對象或者參數必須在檢查之前做防御性深度復制
  • 4、使用安全管理器檢查來操作敏感操作
  • 5、不要使用反射來增加類、方法、字段的可訪問性
  • 6、不要依賴于默認的URLClassLoader和java.util.jar提供的自動化簽名檢查
  • 7、當編寫一個自定義的類裝載器時,在給源代碼覆予任何權限前,必須調用基類的getPermissions()方法獲知默認的系統規則

十六、其他

  • 1、在交換安全數據時,使用SSLSocket而不是Socket
  • 2、生成強隨機數,推薦使用SecureRandom類來生成高質量的隨機數也不是Random類
  • 3、不要硬編碼敏感信息
  • 4、當一個遍歷正在進行時,不要修改它對應的集合,正常的做法是封裝到同步集合中,如Collections.synchronizedList(list)或者new CopyOnWriteArrayList()
  • 5、防止多次實例化單例對象,需要確保設置構造方法為私有、跨線程的可見性、類不能被序列化、類不能被克隆,如果它是被一個自定義的類裝載器裝載,要防止類被垃圾回收
黑龙江6+1开奖结果查询