一般來說牛逼點的地方都會通過安全設備來確保網絡環境的安全,所以之前我們也都認為程序員不需要過多的考慮網站安全問題。實際上隨着做了幾個事業單位的網站之后,也逐漸發現有些方面還是需要程序員注意的。
1. SQL注入 安全等級★★★★★
幾乎每一個網站后台開發人員都聽到的一個詞,並且都很敏感,但是不知道是什么原因造成的很多程序員卻在實際開發過程中經常忽視這個問題。前段時間部門一位新同事,據說是5年工作經驗,在對他的代碼做評審時,我們發現所有的DAO層實現都是直接拼接SQL和參數,總監多次提醒他這個問題,但他也沒有發現,直到總監說出SQL注入這個詞。
實際上這個漏洞很嚴重,一旦被注入成功,后果不堪設想,但這類問題處理起來還是蠻簡單的,下面以JAVA為例舉例說明
方案一: 編寫攔截器過濾請求(不推薦),此方案建議只在對維護中項目或者代碼結構比較亂的情況下使用,底層不容易修改或者不方便修改。此方案效率低,效果也不佳。
/** * 校驗參數,判斷是否包含sql關鍵字表達式 * 此方法會有誤傷 */ private static boolean sqlValidate(String str) { str = str.toLowerCase();//統一轉為小寫 String badSqlStr = "'|exec|execute|insert|select|delete|update|count|drop|chr|mid|master|truncate|char|declare|sitename|net user|xp_cmdshell|create|drop|" +"table|from|grant|use|group_concat|column_name|1=1|=|" +"information_schema.columns|table_schema|union|where|*|" +"chr|mid|master|truncate|char|declare|or|;|,|like|//|/|%|#|-|--|+|"; String badXssStr = "frame|iframe|script|javascript|=|<|>"; //sql 關鍵詞 有空格分隔 String[] badSqlStrs = badSqlStr.split("\\|"); for (int i = 0; i < badSqlStrs.length; i++) { if (str.indexOf(" "+badSqlStrs[i]) >= 0 || str.indexOf(badSqlStrs[i]+" ") >= 0) { return true; } } } return false; }
方案二:推薦方案,使用預編譯的prepareStatement代替statement;使用框架中的setParameter設置參數,此方案可有效處理SQL注入問題。
@Override public List<NewsColumn> getColumnListByType(String columnType) { String jql = " FROM "+NewsColumn.class.getName(,)+" WHERE type=:type "; List<NewsColumn> list = entityManager.createQuery(jql).setParameter("type", columnType).getResultList(); return list; }
2. 驗證碼必須后台校驗 安全等級★★★★
前段時間一客戶說后台管理看到了一堆這樣用戶,/etc/init.d /1=1 ./././ …. Windows/ ,本該截圖的,后來處理了就給忘了~~~~,反正從注冊內容來看,可以確定兩點,通過注冊機注冊,想通過注冊注入攻擊。
通過機器注冊:直接跳過了前端的表單校驗,而恰巧這個項目在開發的時候,驗證碼只在前端做了校驗,提交到后台沒有做再一次的校驗,也就是這個漏洞導致了這堆垃圾注冊。
解決方案:前台提交數據到后台后做進一步校驗,如驗證碼校驗、數據格式校驗、驗重校驗。
回想我也曾經用過這個漏洞…老東家海航集團2014年的時候,OA系統添加了登錄需要手機短信驗證,當時系統更新后第一個版本就是僅做了前台校驗,這個漏洞無意中被我發現了,因為每天都要登錄OA,每次都要短信實在太麻煩了,我就嘗試模擬了個表單請求,重寫原登錄系統表單提交的腳本,在所有的驗證我都直接返回true。很激動的是,一次就成功了,后來也分享給我們同事了,大家都很開心。但隨后不久,系統就升級了,后台驗證,你們懂得。當然對當時的信息部同事來說,我就是一個壞人……
3. 防止表單重復提交 安全等級★★★
防止表單重復提交其實網上有很多解決方案,並且現在主流的前端框架都可以在頁面上做按鈕控制,不過做為一個程序員,你們懂得,這並沒有什么卵用。個人還是建議采用實際的后台驗證法處理。從網上爬文,看到的靠譜的解決方案如下。
解決方案:token驗證,請求頁面時生成token並放在session中,提交表單到后台驗證token,業務邏輯處理完之后,清除token。如果表單提交了一次,token就沒了,再次提交就無法通過了。
方案分析:此方法和驗證碼基本上一致,如果驗證碼在每次表單提交后都清除一次,也能達到這樣的效果。
其他建議:重要的表單頁面提交后重定向,取消表單的autocomplete。
4. 文件上傳格式校驗 安全等級★★★★
黑客攻擊網站還有一個常見的方式就是通過文件上傳漏洞,比如網站上傳圖片的功能沒有嚴格校驗后綴名。黑客可以通過此功能上傳一些腳本文件,上傳成功后,通過請求這些腳本文件運行腳本中的功能達到攻擊的目的。
那么如果驗證了上傳文件的后綴名就可以嗎?實際上並不是,舉例說我們知道頁面引入script標簽時src寫啥都行,比如http://www.baidu.com/123.jpg,也是可以的,攻擊者只需要把一個script文件后綴名改為jpg即可通過后綴驗證,后面一路暢通。所以這就提到了驗證文件的真實格式。如何驗證,網上一大堆…
解決方案:設置php文件、jsp文件不可直接被訪問(不知道php可以不,jsp放在WEB-INF即可),這樣攻擊者上傳此類文件也無法執行;通過文件頭信息嚴格驗證文件格式,從上傳功能開始防范。
5. 熟悉使用框架或數據庫版本情況 安全等級★★
實際開發中,我們都使用一些開源框架,但這些框架也不是百分百完善的,比如webwork,struts2就經常爆出一些漏洞,這個需要開發者自行關注。
解決方案:根據官方發布的方案進行版本升級;根據公開的漏洞執行方式編寫攔截器。
Struts2 s2-016 漏洞處理實例(項目結構不允許版本升級),攔截器,實際上官方的版本也是升級后過濾了一些參數。
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse)response; //禁止頁面被frame res.addHeader("x-frame-options","SAMEORIGIN"); Map parameterMap = req.getParameterMap(); for (Iterator iterator = parameterMap.keySet().iterator(); iterator.hasNext();) { String key = (String) iterator.next(); if ((key.contains("redirect:")) || (key.contains("redirectAction:")) || (key.contains("action:"))) { res.sendError(HttpServletResponse.SC_FORBIDDEN, "forbidden"); System.out.println("----------非法操作-----------"); return; } } filterChain.doFilter(request, response); }
6. 嚴禁在生產環境下使用缺省密碼 安全等級★★
很多時候一些小網站,新人練手的網站(往往價格很便宜),在開發的過程中都要求很快,往往這些網站問題還是蠻多的。后台驗證碼處於關閉狀態、賬號密碼常用admin/admin組合,看似方便了操作,實際是危險重重。甚至有時候,數據庫鏈接密碼都是root/空,這個危害大家都知道。由於沒有驗證碼,用戶密碼又使用的缺省的,黑客爆破的概率異常的高,一旦獲取了后台管理權限,剩下的就交給你了。
解決方案:通過配置測試模式和生產模式控制驗證碼驗證,管理員賬戶必須使用非缺省加密處理,必要時使用物理驗證。
7. 服務器端口盡可能少開 安全等級★★★
六月份一個客戶的服務器被掛馬了,服務器一直是他們自行維護,只在項目更新時給我們開放遠程。無意中發現服務器防火牆竟然是關閉狀態~~~~我的天吶,是不是所有端口都處於開放狀態吶~~~~你們以為這就是驚喜,驚喜還在后面呢,客戶提供的MySQL數據庫用戶名明碼是root root,個人認為這個才是驚喜。root/root和root/空是一樣的效果。分析了一下,防火牆關閉了,3306也就開了,黑客發現3306開着,root/root能連接,通過windows漏洞提升root用戶為系統用戶,bingo,竟然可以登錄這台服務器欸,簡直棒棒噠。
解決方案:防護牆打開,僅開放必要的端口如80,13389,設置遠程登錄IP白名單。再次強調不要用缺省賬戶。
8. Options方法過濾 安全等級★
這個問題網上提到的都是很嚴重,但現在並沒有發現多少案例,或許處理方案比較簡單吧。
解決方案:在web.xml添加配置
<!-- 過濾不安全的請求方法 -->
<security-constraint>
<web-resource-collection>
<url-pattern>/*</url-pattern>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>HEAD</http-method>
<http-method>OPTIONS</http-method>
<http-method>TRACE</http-method>
</web-resource-collection>
<auth-constraint>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
</login-config>
9. XSS攻擊、CSRF攻擊 安全等級★
XSSS攻擊處理方案一般來說也是通過攔截器過濾請求參數,都是常規的處理方案。
package com.interceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; /** * <p> * 類說明 * </p> * @author shy * @date 2016-4-21 下午03:34:26 * @vesion $Revision$ $Date$ */ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { HttpServletRequest orgRequest = null; public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); orgRequest = request; } /** * 覆蓋getParameter方法,將參數名和參數值都做xss過濾。<br/> * 如果需要獲得原始的值,則通過super.getParameterValues(name)來獲取<br/> * getParameterNames,getParameterValues和getParameterMap也可能需要覆蓋 */ @Override public String getParameter(String name) { String value = super.getParameter(xssEncode(name)); if (value != null) { value = xssEncode(value); } return value; } /** * 覆蓋getHeader方法,將參數名和參數值都做xss過濾。<br/> * 如果需要獲得原始的值,則通過super.getHeaders(name)來獲取<br/> * getHeaderNames 也可能需要覆蓋 */ @Override public String getHeader(String name) { String value = super.getHeader(xssEncode(name)); if (value != null) { value = xssEncode(value); } return value; } /** * 將容易引起xss漏洞的半角字符直接替換成全角字符 * * @param s * @return */ private static String xssEncode(String s) { if (s == null || "".equals(s)) { return s; } StringBuilder sb = new StringBuilder(s.length() + 16); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); switch (c) { case '>': sb.append('>');//全角大於號 break; case '<': sb.append('<');//全角小於號 break; case '\'': sb.append('‘');//全角單引號 break; case '\"': sb.append('“');//全角雙引號 break; case '&': sb.append('&');//全角 break; case '\\': sb.append('\');//全角斜線 break; case '#': sb.append('#');//全角井號 break; default: sb.append(c); break; } } return sb.toString(); } /** * 獲取最原始的request * * @return */ public HttpServletRequest getOrgRequest() { return orgRequest; } /** * 獲取最原始的request的靜態方法 * * @return */ public static HttpServletRequest getOrgRequest(HttpServletRequest req) { if (req instanceof XssHttpServletRequestWrapper) { return ((XssHttpServletRequestWrapper) req).getOrgRequest(); } return req; } }
CSRF攻擊(這個還在學習中)
解決方案:從網上爬文看到的基本上是一致的,校驗Referer,添加請求token驗證,個人覺得此漏洞在大型系統中比較重視,小網站就呵呵了。
10. frame引入控制 安全等級★
這個不知道為什么會被列入網站安全問題中,一個客戶網站找了第三方安全檢測公司檢測網站有這個漏洞,所以就不得不處理。網上抄來的。
Java代碼(攔截器中使用):
response.addHeader("x-frame-options","SAMEORIGIN");
Nginx配置:
add_header X-Frame-Options SAMEORIGIN
Apache配置:
Header always append X-Frame-Options SAMEORIGIN
為毛沒有tomcat的配置方式呢???
