web安全淺析

發表是最好的記憶發表於2014-07-10

就之前本人主持開發的金融產品所遇到的安全問題,設計部分請參見:http://www.cnblogs.com/shenliang123/p/3835072.html

這裡就部分web安全防護就簡單的交流:

1.1系統安全

1.1.1  客戶端指令碼安全

(1)跨站指令碼攻擊(XSS):

       XSS攻擊,通常指黑客通過“html注入” 篡改了網頁,插入了惡意的指令碼,從而在使用者瀏覽網頁的時候,控制使用者瀏覽器的一種攻擊。

       最常見的XSS攻擊就是通過讀取瀏覽器的Cookie物件,從而發起“cookie劫持”,當前使用者的登入憑證儲存於伺服器的session中,而在瀏覽器中是以cookie的形式進行儲存的,cookie被劫持後,意味著攻擊者可以不通過密碼而直接登入系統。我們也可以直接在瀏覽器中輸入指令碼javascript:alert(document.cookie)來獲取當前cookie值。

       目前防止“cookie劫持”的方法大致有:a. 輸入檢查,使用filter來過濾敏感的關鍵字;b. 將cookie與使用者ip地址進行繫結;c. 為cookie植入HttpOnly標識。

   本系統採用的是第3種方式:為cookie植入HttpOnly標識。一旦這個HttpOnly被設定,你在瀏覽器的 document物件中就看不到Cookie了,而瀏覽器在瀏覽的時候不受任何影響,因為Cookie會被放在瀏覽器頭中傳送出去(包括ajax的時 候),應用程式也一般不會在js裡操作這些敏感Cookie的,對於一些敏感的Cookie我們採用HttpOnly,對於一些需要在應用程式中用js操作的cookie我們就不予設定,這樣就保障了Cookie資訊的安全也保證了應用。

具體程式碼實現:在web伺服器tomcat的配置檔案server.xml中新增:

<Context docBase="E:\tomcat\apache-tomcat-6.0.24/webapps/netcredit" path="/netcredit" reloadable="false" useHttpOnly="true"/>

或者在專案的web.xml 配置如下:

 <session-config>

         <cookie-config> 

           <http-only>true</http-only> 

        </cookie-config>

  </session-config>

 

 

圖1-1 瀏覽器cookie截圖

(2)跨站點請求偽造(CSRF):

       本系統採用了對抗CSRF最有效的防禦方法:驗證碼。

1.1.2 伺服器端應用安全

伺服器端的安全相對於客戶端來說顯的更加的重要,因為伺服器端的某個安全漏洞可能帶來的是致命的危險。因此我們對伺服器端的安全更為的慎重和重視。

(1)SQL隱碼攻擊:

Sql注入的的兩個關鍵條件:第一個是使用者能夠控制輸入;第二個是原本程式要執行的程式碼,拼接了使用者輸入的資料。

根據上面兩個關鍵條件,系統為防止sql注入使用了以下方法:

第一:使用預編譯語句,這也是防禦sql注入最有效的方法,完全摒棄程式碼的直接拼接所帶來的危險。

public List<T> findByPage(final String hql, final Object[] values, final int offset,

           final int pageSize) {

       List<T> list = getHibernateTemplate().executeFind(new HibernateCallback(){

           public Object doInHibernate(Session session)

              throws HibernateException, SQLException{

              Query query=session.createQuery(hql);

              for (int i = 0 ; i < values.length ; i++){

                  query.setParameter( i, values[i]);

              }

              if(!(offset==0 && pageSize==0)){

                  query.setFirstResult(offset).setMaxResults(pageSize);

              }

              List<T> result = query.list();

              return result;

           }

       });

    return list;

    }      

第二:關閉web伺服器的錯誤回顯功能,這樣可以防止攻擊者對系統進行攻擊後,通過回顯的詳細錯誤資訊對攻擊內容進行調整,對攻擊者提供極大的便利。我們在專案的web.xml檔案中新增以下示例程式碼:

    <error-page>

        <error-code>400</error-code>

        <location>/error400.jsp</location>

    </error-page>

    。。。。。

第三:資料庫自身使用最小許可權原則,系統程式不使用最高許可權的root對資料庫進行連線,而是使用能滿足系統需求的最小許可權賬戶進行資料庫連線,而且多個資料庫之間使用不同的賬戶,保證每個資料庫都有獨立對應的賬戶。

(2)檔案上傳漏洞:

檔案上傳漏洞是指使用者上傳了一個可執行的指令碼檔案,並通過指令碼檔案獲得了執行伺服器端命令的能力,這樣將會導致嚴重的後果。而本系統內涉及到大量的圖片格式檔案上傳,因此對於上傳問題的處理非常謹慎,並儘可能的達到安全標準。

本系統主要通過對上傳檔案詳細的格式驗證:

第一步:通過字尾名來簡單判斷檔案的格式。

public static boolean isPicture(String pInput) throws Exception{

      // 獲得檔案字尾名

      String tmpName = pInput.substring(pInput.lastIndexOf(".") + 1,

                                  pInput.length());

      // 宣告圖片字尾名陣列

      String imgeArray [] = { "jpg", "png", "jpeg" };

      // 遍歷名稱陣列

      for(int i = 0; i<imgeArray.length;i++){ 

          // 判斷符合全部型別的場合

          if(imgeArray [i].equals(tmpName.toLowerCase())){

              return true;

          }

        }

      return false;

    }

第二步:通過讀取檔案的前兩個字元進行對比,例如png格式圖片的前兩個字元為8950,而jpg格式的圖片前兩個字元為ffd8。

public static String bytesToHexString(byte[] src) {

        StringBuilder stringBuilder = new StringBuilder();

        if (src == null || src.length <= 0) {

            return null;

        }

        for (int i = 0; i < src.length; i++) {

            int v = src[i] & 0xFF;//byte to int

            String hv = Integer.toHexString(v);

            if (hv.length() < 2) {

                stringBuilder.append(0);

            }

            stringBuilder.append(hv);

        }

        return stringBuilder.toString();

    }  

 

第三步:如果上傳的為圖片,則獲取相應的高度和寬度,如果存在相應的寬度和高度則可認為上傳的是圖片。

public static boolean isImage(File imageFile) {

        if (!imageFile.exists()) {

            return false;

        }

        Image img = null;

        try {

            img = ImageIO.read(imageFile);

            if (img == null || img.getWidth(null) <= 0 || img.getHeight(null) <= 0) {

                return false;

            }

            return true;

        } catch (Exception e) {

            return false;

        } finally {

            img = null;

        }

   }

 

如果以上驗證都成功通過,本系統在對檔案進行儲存時會將檔名進行重新命名處理,並且設定相應的web伺服器,預設不顯示目錄。因為檔案上傳如果需要執行程式碼,則需要使用者能夠訪問到這個檔案,因此使用隨機數改寫了檔名,將極大的增加攻擊的成本,甚至根本無法成功實施攻擊。

//對檔案的名稱進行重新命名

StringBuilder sb = new StringBuilder().append(new Date().getTime()).append(".").append(fileMsg[1]);

(3)認證與會話管理:

  認證實際上就是一個驗證憑證的過程,因此我們對登入密碼有著嚴格的規定:密碼要求長度在8位以上並且包含字母,數字,符號兩種以上的組合。並將密碼加密後再進行資料庫的儲存。為防止一些暴力破解手段,又出於使用者體驗的考慮,本系統採用使用者登入時預設不進行驗證碼的輸入,但密碼三次輸入失敗後需要進行驗證碼的輸入,連續五次輸入錯誤後,該使用者的ip地址將被封,可以在一段時間後自動解封或者後臺管理員手動解封。涉及到系統資金有關的操作,例如投標,我們還需要使用者設定並提供支付密碼,而提現等操作我們還配備簡訊驗證碼功能。以此來增強驗證的可靠性。

  當使用者登入完成後,在伺服器端會建立一個新的會話(Session),會話中儲存著使用者的狀態和相關資訊,伺服器端維護所有線上使用者的Session,而瀏覽器則是將SessionId加密後儲存在cookie中,這裡就出現了前面提到了“cookie劫持“問題,本系統通過將cookie中植入HttpOnly成功解決該問題。本系統還給Session設定了一個有效時間,來保證在有效時間後Session將自動銷燬,以防止Session長連線所帶來的安全隱患。在web.xml檔案中新增以下程式碼:

<session-config>

     <session-timeout>30</session-timeout>

</session-config>

(4)訪問控制:

訪問控制實際上就是建立使用者和許可權之間的對應關係,本系統採用的是“基於角色的訪問控制”,根據相應功能模組的劃分相應的許可權,並將相應的許可權分配給不同的角色,再將角色分配給使用者,使用者就擁有了該角色所持有的許可權。具體的設計與實現分析請見1.2 Annotation和Struts2攔截器實現基於角色的垂直許可權管理。

1.2 Annotation和Struts2攔截器實現基於角色的垂直許可權管理

1.2.1 模組的設計

以下是本系統的垂直許可權管理模組的物理模型設計:

 

圖1-2 許可權管理模組物理模型

管理員與角色之間是多對多的關係,這樣可以實現為一個管理員分配多個角色進行管理,而角色與許可權之間也是多對多的關係,許可權是根據功能模組進行劃分的,使得角色可以進行細粒度的許可權分配。

4.3.2 模組的程式碼實現

首先宣告一個註解類,該註解類是自定義的,根據我們需要的實現的功能進行相應註解的定義,使得在之後需要註釋的方法上使用相應註解,程式碼如下:

@Retention(RetentionPolicy.RUNTIME)           //註解的存活時間,整個執行時都存活 

@Target(ElementType.METHOD)               //此註解表示只能在方法上面有效 

public @interface Permission { 

    /** 模組名 **/ 

    String model(); 

    /** 許可權值 **/ 

    String privilegeValue();

    /*用於區分當前頁面是dialog還是navTab*/

    String targetType();

}

然後定義相應的攔截器類,並在struts2的配置檔案struts.xml中進行攔截器的配置,為相應需要的類進行攔截驗證,定義攔截器部分的核心程式碼如下:

    /**

    * 校驗控制在方法上的攔截

    */

    @SuppressWarnings("unchecked")

    public boolean validate(Admin admin, ActionInvocation invocation, List<Role> roleList,

           Map session) {

       String methodName= "execute";                                  //定義預設的訪問方法

       Method currentMethod = null;

       methodName = invocation.getProxy().getMethod();                //通過actionProxy,得到當前正在執行的方法名

       try {

           //利用反射,通過方法名稱得到具體的方法

           currentMethod = invocation.getProxy().getAction().getClass().getMethod(methodName, new Class[] {});

       } catch (Exception e) {

           e.printStackTrace();

       }

       if (currentMethod != null && currentMethod.isAnnotationPresent(Permission.class)) {

           //得到方法上的Permission註解

           Permission permission = currentMethod.getAnnotation(Permission.class);

           //通過Permission註解構造出系統許可權

           Systemprivilege privilege = new Systemprivilege(new SystemprivilegeId(permission.privilegeValue(), permission.model()));

           session.put("targetType", permission.targetType());

           //迭代使用者所具有的具體許可權,如果包含,返回true

           for (Role role : roleList) {                 

              RolePrivilege rolePrivilege = new RolePrivilege(privilege, role);

              if (role.getRolePrivileges().contains(rolePrivilege)) {

                  return true;

              }

           }

           return false;

       }

       return true;

}

 

本系統需要向許可權表中新增相應的功能模組,為了有一個比較細的粒度,模組將根據功能進行劃分,而許可權則根據按鈕來進行細分:

 

圖1-3 資料庫截圖

在定義了相應的許可權後,我們需要在相應的執行方法體上面進行註釋,使得攔截器進行攔截驗證時能根據註釋獲取該執行方法體的模組和許可權型別:

@Permission(model="recharge", privilegeValue="recharge" ,targetType="dialog")

4.3.3 模組的實現效果

首先新增相應的員工:

 

然後新增相應的角色,填寫角色名稱並勾取相應的許可權:

 

然後新增相應的使用者,勾選該使用者相應的角色,可以選擇多個角色,並且選擇相應使用者對應的員工: