就之前本人主持開發的金融產品所遇到的安全問題,設計部分請參見: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 模塊的實現效果
首先添加相應的員工:
然后新增相應的角色,填寫角色名稱並勾取相應的權限:
然后新增相應的用戶,勾選該用戶相應的角色,可以選擇多個角色,並且選擇相應用戶對應的員工: