資安

java安全編碼指南之:基礎篇

簡介

作為一個程序員,只是寫出好用的代碼是不夠的,我們還需要考慮到程序的安全性。在這個不能跟陌生人說話世界,扶老奶奶過馬路都是一件很困難的事情。那麼對於程序員來說,尤其是對於開發那種對外可以公開訪問的網站的程序員,要承受的壓力會大很多。

任何人都可以訪問我們的系統,也就意味著如果我們的系統不夠健壯,或者有些漏洞,惡意攻擊者就會破門而入,將我們辛辛苦苦寫的程序蹂躪的體無完膚。

所以,安全很重要,今天本文將會探討一下java中的安全編碼指南。

java平臺本身的安全性

作為一個強類型語言,java平臺本身已經儘可能的考慮到了安全性的,為我們屏蔽了大多數安全性的細節。

比如可以為不同級別權限的代碼提供受限的執行環境。 java程序是類型安全的,並且在運行時提供了自動內存管理和數組邊界檢查,Java會儘可能的及早發現程序中的問題,從而使Java程序具有很高的抵抗堆棧破壞的能力。

儘管Java安全體系結構在許多情況下可以幫助保護用戶和系統免受惡意代碼或行為不當的攻擊,但它無法防禦可信任代碼中發生的錯誤。也就說如果是用戶本身代碼的漏洞,java安全體系是無法進行判斷的。

這些錯誤可能會繞過java本身的安全體系結構。在嚴重的情況下,可能會執行本地程序或禁用Java安全性。從而會被用來從計算機和Intranet竊取機密數據,濫用系統資源,阻止計算機的有用操作,協助進一步的攻擊以及許多其他惡意活動。

所以,最大的安全在程序員本身,不管外部機制如何強大,如果核心的程序員出了問題,那麼一切都將歸於虛無。

接下來,我們看下java程序員應該遵循一些什麼行為準則,來保證程序的安全性呢?

安全第一,不要寫聰明的代碼

我們可能會在很多教科書甚至是JDK的源代碼中,看到很多讓人驚歎的代碼寫法,如果你真的真的明白你在做什麼,那麼這樣寫沒什麼問題。但是很多情況下我們並不是很瞭解這樣寫的原理,甚至不知道這樣寫會出現什麼樣的問題。

並且現代系統是一個多人協作的過程,如果你寫了這樣的聰明代碼,很有可能別人看不懂,最後導致未知的系統問題。

給大家舉個例子:

:(){:|:&};:

上面是一個shell下面的fork炸彈,如果你在shell下面運行上面的代碼,幾秒之後系統就會宕機或者運行出錯。

怎麼分析上面的代碼呢?我們把代碼展開:

:()
{
    :|:&
};
:

還是不明白? 我們把:替換成函數名:

fork()
{
    fork|fork&
};
fork

上面的代碼就是無限的fork進程,通過幾何級數的增長,最後導致程序崩潰。

java設計的很多大神把他們跳躍般的思想寫到了JDK源代碼裡面,大神們的思想經過了千錘百煉,並且JDK是Java的核心,裡面的代碼再優化也不為過。

但是現在硬件技術的發展,代碼級別的優化可能作用已經比較少了。為了避免出現不可知的安全問題,還是建議大家編寫一眼就能看出邏輯的代碼。雖然可能不是那麼快,但是安全性有了保證。除非你真的知道你在做什麼。

在代碼設計之初就考慮安全性

安全性應該是一個在編寫代碼過程中非常重要的標準,我們在設計代碼的時候就應該考慮到相關的安全性問題,否則後面重構起來會非常費事。

舉個例子:

        public final class SensitiveClass {

            private final Behavior behavior;

            // Hide constructor.
            private SensitiveClass(Behavior behavior) {
                this.behavior = behavior;
            }

            // Guarded construction.
            public static SensitiveClass newSensitiveClass(Behavior behavior) {
                // ... validate any arguments ...

                // ... perform security checks ...

                return new SensitiveClass(behavior);
            }
        }

上面的例子中我們使用了final關鍵字來防止我們的某些關鍵類被繼承擴展。因為沒有擴展性,所以安全性判斷會更加容易。

同時,java提供了SecurityManager和一系列的Permission類,通過合理的配置,我們可以有效的控制java程序的訪問權限。

避免重複的代碼

和重複代碼相關的一個關鍵詞就是重構。為什麼會出現重複代碼呢?

很簡單,最開始我們在實現一個功能的時候寫了一段代碼邏輯。結果後面還有一個方法要使用這段代碼邏輯。然後我們為了圖方便,就把代碼邏輯拷貝過去了。

看起來問題好像解決了。但是一旦這段業務邏輯要修改,那可就是非常麻煩的一件事情。因為我們需要找到程序中所有出現這段代碼的地方,然後一個一個的修改。

為什麼不把這段代碼提取出來,做成一個單獨的方法來供其他的方法調用呢?這樣即使後面需要修改,也只用修改一處地方即可。

在現實的工作中,我們經常會遇到這種問題,尤其是那種年久失修的代碼,大家都不敢修改,因為牽一髮而動全身。往往是修改了這邊忘記了那邊,最後導致bug重重。

限制權限

JDK專門提供了一個SecurityManager類,來顯示的對安全性進行控制,我們看下SecurityManager是怎麼使用的:

SecurityManager security = System.getSecurityManager();
    if (security != null) {
      security.checkXXX(argument, ...);
   }

SecurityManager提供了一系列的check方法,來對權限進行控制。

權限分為以下類別:文件、套接字、網絡、安全性、運行時、屬性、AWT、反射和可序列化。管理各種權限類別的類是 :
  java.io.FilePermission、
  java.net.SocketPermission、
  java.net.NetPermission、
  java.security.SecurityPermission、
  java.lang.RuntimePermission、
  java.util.PropertyPermission、
  java.awt.AWTPermission、
  java.lang.reflect.ReflectPermission
  java.io.SerializablePermission

JDK本身已經使用了很多這些權限控制的代碼。比如說我們最常用的File:

    public boolean canRead() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(path);
        }
        if (isInvalid()) {
            return false;
        }
        return fs.checkAccess(this, FileSystem.ACCESS_READ);
    }

上面是File類的canRead方法,我們會首先去判斷是否配置了SecurityManager,如果配置了,則去檢查是否可以read。

如果我們在寫代碼中,遇到文件、套接字、網絡、安全性、運行時、屬性、AWT、反射和可序列化相關的操作時,也可以考慮使用SecurityManager來進行細粒度的權限控制。

構建可信邊界

什麼是可信邊界呢?邊界主要起攔截作用,邊界裡邊的我們可以信任,邊界外邊的我們就不能信任了。

對於不能信任的外邊界請求,我們需要進行足夠的安全訪問控制。

比如說web客戶端來訪問web服務器。web客戶端是在全球各地的,各種環境都有,並且是不可控的,所以web客戶端訪問web服務器端的請求需要進行額外的安全控制。

而web服務器訪問業務服務器又是不同的,因為web服務器是我們自己控制的,所以安全程度相對較高,我們需要針對不同的可信邊界做不同的控制。

封裝

封裝(Encapsulation)是指一種將抽象性函式接口的實現細節部份包裝、隱藏起來的方法。

封裝可以被認為是一個保護屏障,防止該類的代碼和數據被外部類定義的代碼隨機訪問。通過對接口進行訪問控制,可以嚴格的包含類中的數據和方法。

並且封裝可以減少耦合,並且隱藏實現細節。

寫文檔

最後一項也是非常非常重要的一項就是寫文檔。為什麼接別人的老項目那麼痛苦,為什麼讀源代碼那麼困難。根本的原因就是沒有寫文檔。

如果不寫文檔,可能你自己寫的代碼過一段時間之後也不知道為什麼當時這樣寫了。

所以,寫文檔很重要。

本文已收錄於 http://www.flydean.com/java-security-code-line-base/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程序那些事」,懂技術,更懂你!

阿里雲

Leave a Reply

Your email address will not be published. Required fields are marked *