HTTP主要安全漏洞和解決思路
本部落格整理自圖解HTTP和眾多網路文章,對HTTP完全漏洞進行梳理,並介紹了java解決方案。
簡單的HTTP協議本身並不存在安全性問題,因此協議本身幾乎不會成為攻擊的物件,但是HTTP應用的服務端和客戶端以及web應用資源是主要的攻擊目標。
雖然HTTP協議本身不在安全性問題,但是因為協議本身不包含會話管理、加密處理的要求,因此使用HTTP開發的應用和伺服器容易成為攻擊物件。如遠端登入SSH協議,SSH具備協議級別的認證和會話管理等,比HTTP就安全性來說就做的更好。也因為如此HTTPS才在越來越多的場合替代HTTP協議,強制要求使用HTTPS。
一、攻擊模式
針對web應用的攻擊模式主要有: 主動攻擊 和 被動攻擊
- 以伺服器為目標的主動攻擊
指通過直接訪問web應用,把攻擊程式碼傳入的攻擊模式。典型的攻擊有:sql注入攻擊、os命令注入攻擊
- 以伺服器為目標的被動攻擊
被動攻擊(passive attack)是利用圈套策略執行攻擊程式碼的攻擊模式。典型攻擊為跨站指令碼攻擊和跨站請求偽造
- 誘使使用者觸發已經設定好的陷阱,啟動傳送已嵌入攻擊程式碼的http請求
- 使用者的瀏覽器和郵件客戶端會觸發陷阱
- 中招的使用者瀏覽器會把含有攻擊程式碼的http請求傳送給目標web應用
- 執行完攻擊程式碼存在安全漏洞的web應用會成為攻擊者的跳板,導致使用者所持cookie個人資訊被竊取,登陸裝填中的使用者許可權被惡意濫用。
二、典型攻擊
2.1、跨站指令碼攻擊( Cross-Site Scripting, XSS)
可以在正常的url中嵌入js指令碼,設定陷阱,引誘使用者點選此非法連線後,輸入自己的個人資訊,結果個人資訊被js指令碼傳遞給攻擊者。使用者很難意識到自己的登陸資訊已經洩露,利用跨站指令碼可以竊取使用者的cookie。
舉例:
http://www.test.com/msg.php?send=Hello,World!
接收者將會接收資訊並顯示Hello,Word
非正常傳送訊息:
http://www.test.com/msg.php?send=<script>alert('I am here!')</script>
接收者接收訊息顯示的時候將會彈出警告視窗
可以想像如果在script裡面嵌入一個遠端指令碼,那麼可能帶來很不好的後果。
分析:
過於信任客戶端提交的資料!因此解決思路就是不信任任何客戶端提交的資料,只要是客戶端提交的資料就應該先進行相應的過濾處理然後方可進行下一步的操作。
解決辦法:
Java Web應用的解決辦法:進行提交資訊的過濾。
1)web.xml中增加過濾器配置
<filter>
<filter-name>xssFilter</filter-name>
<filter-class>com.test.xss.XssFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>xssFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
1.建立XssHttpServletRequestWrapper
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 || s.isEmpty()) {
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;
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;
}
}
2.建立Filter
public class XssFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(
(HttpServletRequest) request);
chain.doFilter(xssRequest, response);
}
@Override
public void destroy() {
}
}
2.2、Sql注入攻擊
指對於web應用使用的資料庫,通過執行非法的sql產生攻擊,這種攻擊就叫做sql注入(sql Injection)。非法注入攻擊可能有如下影響:
1)非法檢視和篡改書庫內的資料
2)規避認證
3)執行和資料庫伺服器業務相關的程式
舉例來說:
在登陸頁面,填好正確的使用者名稱(admin)和密碼(admin123)後,點選提交,將會返回給我們“歡迎管理員”的介面。
因為根據我們提交的使用者名稱和密碼被合成到SQL查詢語句當中之後是這樣的:
select * from users where username='admin' and password=md5(admin123)
很明顯,使用者名稱和密碼都和我們之前給出的一樣,肯定能夠成功登陸。但是,如果我們輸入一個錯誤的使用者名稱或密碼呢?很明顯,肯定登入不了吧。恩,正常情況下是如此,但是對於有SQL注入漏洞的網站來說,只要構造個特殊的“字串”,照樣能夠成功登入。
比如:在使用者名稱輸入框中輸入:’or 1=1#,密碼隨便輸入,這時候的合成後的SQL查詢語句為:
select * from users where username='' or 1=1#' and password=md5('')
語義分析:“#”在mysql中是註釋符,這樣井號後面的內容將被mysql視為註釋內容,這樣就不會去執行了,換句話說,以下的兩句sql語句等價:
select * from users where username='' or 1=1#' and password=md5('')
等價於
select * from users where username='' or 1=1
因為1=1永遠都是成立的,即where子句總是為真,將該sql進一步簡化之後,等價如下select語句:
select * from users
沒錯,該sql語句的作用是檢索users表中的所有欄位
我們利用萬能語句(’or 1=1#)能夠登入!一個經構造後的sql語句竟有如此可怕的破壞力,相信你看到這後,開始對sql注入有了一個理性的認識了吧~
分析原因:
也是對使用者提交資料沒有進行校驗
解決辦法:
使用filter過濾器實現的程式碼如下:
1、web.xml增加過濾器
<filter>
<filter-name>antiSqlInjection</filter-name>
<filter-class>com.test.filter.AntiSqlInjectionfilter</filter-class>
</filter>
<filter-mapping>
<filter-name>antiSqlInjection</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2、過濾器實現
package com.test.filter;
import javax.servlet.Filter;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class AntiSqlInjectionfilter implements Filter {
public void destroy() {
// TODO Auto-generated method stub
}
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
public void doFilter(ServletRequest args0, ServletResponse args1,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) args0;
HttpServletResponse res = (HttpServletResponse) args1;
//獲得所有請求引數名
Enumeration params = req.getParameterNames();
String sql = "";
while (params.hasMoreElements()) {
//得到引數名
String name = params.nextElement().toString();
//得到引數對應值
String[] value = req.getParameterValues(name);
for (int i = 0; i < value.length; i++) {
sql = sql + value[i];
}
}
//有sql關鍵字,跳轉到error.html
if (sqlValidate(sql)) {
throw new IOException("您傳送請求中的引數中含有非法字元");
//String ip = req.getRemoteAddr();
} else {
chain.doFilter(args0, args1);
}
}
//效驗
protected static boolean sqlValidate(String str) {
str = str.toLowerCase();//統一轉為小寫
String badStr = "'|and|exec|execute|insert|select|delete|update|count|drop|*|%|chr|mid|master|truncate|" +
"char|declare|sitename|net user|xp_cmdshell|;|or|-|+|,|like'|and|exec|execute|insert|create|drop|" +
"table|from|grant|use|group_concat|column_name|" +
"information_schema.columns|table_schema|union|where|select|delete|update|order|by|count|*|" +
"chr|mid|master|truncate|char|declare|or|;|-|--|+|,|like|//|/|%|#";//過濾掉的sql關鍵字,可以手動新增
String[] badStrs = badStr.split("\\|");
for (int i = 0; i < badStrs.length; i++) {
if (str.indexOf(badStrs[i]) >= 0) {
return true;
}
}
return false;
}
}
上述例子中關鍵字or和 # 都滿足過濾條件,都會被過濾出來
2.3、OS命令注入攻擊
OS命令注入和SQL注入差不多,只不過SQL注入是針對資料庫的,而OS命令注入是針對作業系統的。OS命令注入即能夠在伺服器上執行任意命令。
如何防止OS命令注入:
不要呼叫外部程式。舉個例子,在UNIX系統上,有一個叫CGI的程式,可以執行sendmail命令來發送郵件。也許你的web應用程式也有傳送郵件的功能,通過直接呼叫CGI程式傳送郵件非常的簡單,但是不要這樣做,因為在執行sendmail命令的同時,也會混雜進其他OS命令,正確的做法是使用傳送郵件的library。
解決辦法:
過濾掉如下: 、; ,[ ,] ,| ,< ,> ,\ 之類的符號
2.4、http首部注入攻擊
在響應首部欄位內插入換行,新增影響首部或者主體的一種攻擊Web應用有時會把外部接收到的數值,賦給響應首部的location和set-cookie
首部注入攻擊造成的影響:
1)設定任務cookie資訊
2)重定向至任務url
3)顯示任意的主題( http響應截斷攻擊)
2.4.1 舉例
常見的sql注入一般都是通過表單或請求引數進行注入,但這裡給出的例子是通過HTTP協議頭部進行注入。
GET / HTTP/1.1
Host: www.example.com
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0'(select*from(select(sleep(20)))a) #
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,fr;q=0.6
HTTP協議的User-Agent頭部為“Mozilla/5.0’(select*from(select(sleep(20)))a) #”。其中“select * from (select(sleep(20)))”是不正常的,可能是攻擊者正在注入攻擊。
這裡的攻擊主要是讓資料庫什麼也不幹而睡眠20秒,從而浪費資料庫處理執行緒。這是一個簡單的注入,除此之外還能進行更多複雜的攻擊。一般獲取頭部的資訊用於資料分析,例如這裡獲取User-Agent就可以知道客戶都是通過什麼瀏覽器訪問系統的。jsp中直接通過request.getHeader("User-Agent")就可以獲取到該值,接著可能就直接儲存到資料庫了,Mozilla/5.0’(select*from(select(sleep(20)))a) #中的#號在資料庫中作為註釋符號,它產生的sql可能如下:
INSERT INTO visits (useragent, datetime) VALUES ('Mozilla/5.0', (select*from(select(sleep(20)))a)) #', '2016-06-13 13:00:06')
這時#號後面都被註釋掉了,於是資料庫睡眠了20秒。
2.4.2解決辦法:
避免這種攻擊的方法,就是過濾所有的response headers,除去header中出現的非法字元,尤其是CRLF。
2.5、Http響應截斷攻擊
利用兩個換行並排插入字串後傳送,利用連續的換行,可以做出首部和主體分隔所需的空行了。這樣就能偽造主體,達到攻擊的目的。
如何避免 HTTP 響應截斷
要避免HTTP響應截斷,需要注意以下幾點:
(1)對使用者的輸入進行合理驗證,對特殊字元(如<、>、’、”等)等進行編碼。
(2)建立一份安全字元白名單,只接受完全由這些受認可的字元組成的輸入出現在 HTTP 響應標頭檔案中。
(3)使用原始碼靜態分析工具,進行自動化的檢測,可以有效的發現原始碼中的 HTTP 響應截斷問題。如
在上述修復程式碼中,第38行使用 Refenence 類對環境變數值進行 decode,剔除特殊字元。
2.6、獲取Cookie攻擊
通過Java Script非常容易訪問到當前網站的cookie。你可以開啟任何網站,然後在瀏覽器位址列中輸 入:javascript:alert(doucment.cookie),立刻就可以看到當前站點的cookie(如果有的話)。攻擊者可以利用這個特 性來取得你的關鍵資訊。例如,和XSS攻擊相配合,攻擊者在你的瀏覽器上執行特定的Java Script指令碼,取得你的cookie。假設這個網站僅依賴cookie來驗證使用者身份,那麼攻擊者就可以假冒你的身份來做一些事情。
現在多數瀏覽器都支援在cookie上打上HttpOnly的標記,凡有這個標誌的cookie就無法通過Java Script來取得,如果能在關鍵cookie上打上這個標記,就會大大增強cookie的安全性
2.7、目錄遍歷攻擊
對本無意公開的檔案目錄,通過非法截斷其目錄路徑後,達成訪問目的的一種攻擊。這種攻擊也稱為Path Traversal攻擊
檔案互動是一種簡單的過程,但是由於檔名可以任意更改而伺服器支援“~/”,“../”等特殊符號的目錄回溯,從而使攻擊者越權訪問或者覆蓋敏感資料,如網站的配置檔案、系統的核心檔案,這樣的缺陷被命名為路徑遍歷漏洞