常見HTTPS攻擊及防禦方法詳解

wooyun知識庫發表於2015-01-02

0×00 背景

研究常見的https攻擊方法

Beast crime breach,並針對https的特性提出一些安全部署https的建議。

針對於HTTPS的攻擊,多存在於中間人攻擊的環境中,主要是針對於HTTPS所使用的壓縮演算法和CBC加密模式,進行side-channel-attack。這幾類攻擊的前置條件都比較苛刻,且都需要受害主機提交很多次請求來收集破譯關鍵資料的足夠資訊。

常見的攻擊方法,主要有,BEAST Lucky-13 RC4 Biases CRIME TIME BREACH等。主要對其中三中進行介紹。

0×01 CRIME

Compression Ratio Info-leak Made Easy

攻擊原理

攻擊者控制受害者傳送大量請求,利用壓縮演算法的機制猜測請求中的關鍵資訊,根據response長度判斷請求是否成功。

如下面的https頭,攻擊這可以控制的部分為get請求地址,想要猜測的部分為Cookie。那麼攻擊者只需要在GET地址處,不斷變換猜測字串,進行猜測。

GET /sessionid=a HTTP/1.1
Host: bank.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:16.0) 
Gecko/20100101 Firefox/16.0
Cookie: sessionid=d3b0c44298fc1c149afbf4c8996fb924

GET /sessionid=a HTTP/1.1
Host: bank.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:16.0)
Gecko/20100101 Firefox/16.0
Cookie: sessionid=d3b0c44298fc1c149afbf4c8996fb924

比如上面的情況Response長度為 1000byte。

GET /sessionid=d HTTP/1.1
Host: bank.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:16.0)
Gecko/20100101 Firefox/16.0
Cookie: sessionid=d3b0c44298fc1c149afbf4c8996fb924

當攻擊者猜對了cookie的第一個字母,Response的長度會縮小到9999byte。

當Response被SSL加密之後,如果使用RC4加密模式,長度並不會發生隨機改變。使用BCB加密模式時,因為padding的原因,長度會有略微的改變。

受影響的加密演算法

Deflate = LZ77 + HuffMan
GZip = Headers + Data Compressed using Deflate

攻擊前提

攻擊者可以獲取受害者的網路通訊包。(中間人攻擊,ISP供應商)

瀏覽器和伺服器支援均支援並使用壓縮演算法。

攻擊這可以控制受害者傳送大量請求並可以控制請求內容。

防禦方法

客戶端可以升級瀏覽器來避免這種攻擊。

Chrome: 21.0.1180.89 and above
• Firefox: 15.0.1 and above
• Opera: 12.01 and above
• Safari: 5.1.7 and above

伺服器端可以通過禁用一些加密演算法來防止此類攻擊。

Apache

• SSLCompression flag = “SSLCompression off”

• GnuTLSPriorities flag = “!COMP-DEFLATE”

禁止過於頻繁的請求。

修改壓縮演算法流程,使用者輸入的資料不進行壓縮。

隨機新增長度不定的垃圾資料。

影響範圍

TLS 1.0.
SPDY protocol (Google).
Applications that uses TLS compression.
Mozilla Firefox (older versions) that support SPDY.
Google Chrome (older versions) that supported both TLS and SPDY.

POC

這個poc並不是模擬真實環境下的中間人攻擊,只是在python中利用CRIME的思想驗證了攻擊的可行性。

import string
import zlib
import sys
import random

charset = string.letters + string.digits

COOKIE = ''.join(random.choice(charset) for x in range(30))

HEADERS = ("POST / HTTP/1.1\r\n"
           "Host: thebankserver.com\r\n"
           "Connection: keep-alive\r\n"
           "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1\r\n"
           "Accept: */*\r\n"
           "Referer: https://thebankserver.com/\r\n"
           "Cookie: secret="+COOKIE+"\r\n"
           "Accept-Encoding: gzip,deflate,sdch\r\n"
           "Accept-Language: en-US,en;q=0.8\r\n"
           "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3\r\n"
           "\r\n")
BODY =    ("POST / HTTP/1.1\r\n"
           "Host: thebankserver.com\r\n"
           "Connection: keep-alive\r\n"
           "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1\r\n"
           "Accept: */*\r\n"
           "Referer: https://thebankserver.com/\r\n"
           "Cookie: secret=")
cookie = ""

def compress(data):

    c = zlib.compressobj()
    return c.compress(data) + c.flush(zlib.Z_SYNC_FLUSH)
def getposset(perchar,chars):
    posset = []
    baselen = len(compress(HEADERS+perchar))
    for i in chars:
        t = len(compress(HEADERS+ perchar+i))
        if (t<=baselen):
            posset += i
    return posset
def doguess():
    global cookie
    while len(cookie)<30:
        posset = getposset(BODY+cookie,charset)
        trun = 1
        tem_posset = posset
        while 1<len(posset):
            tem_body = BODY[trun:]
            posset = getposset(tem_body+cookie,tem_posset)
            trun = trun +1
        if len(posset)==0:
            return False
        cookie += posset[0]
        print posset[0]
        return True

while BODY.find("\r\n")>=0:
    if not doguess():
        print "(-)Changebody"
        BODY = BODY[BODY.find("\r\n") + 2:]
print "(+)orign  cookie"+COOKIE
print "(+)Gotten cookie"+cookie

0×02 TIME

Timing Info-leak Made Easy

攻擊原理

攻擊者控制受害者傳送大量請求,利用壓縮演算法的機制猜測請求中的關鍵資訊,根據response響應時間判斷請求是否成功。其實TIME和CRIME一樣都利用了壓縮演算法,只不過CRIME是通過長度資訊作為輔助,而TIME是通過時間資訊作為輔助。

Unable to render embedded object: File (1.jpg) not found.

如上圖當資料長度,大於MTU時會截斷為兩個包傳送,這樣就會產生較大的相應時間差異。攻擊者吧包長控制在MTU左右,不斷嘗試猜測COOKIE。  Unable to render embedded object: File (QQ圖片20140724174303.jpg) not found.

如上圖所示,我們通過新增Padding來吧資料包大小增加到和MTU相等,Case 1中我們新增的extraByte和需要猜測的資料重合,因為壓縮演算法的原因,並不會增加包的長度,而Case 2中extraByte和需要猜測的資料並不一致,導致了分包。攻擊這可以通過響應時間的不同來區分Case1 Case2兩種情況。

攻擊前提

攻擊這可以控制受害者傳送大量請求並可以控制請求內容。

穩定的網路環境。

防禦方法

在解密Response過程中加入隨機的短時間延遲。

阻止短時間內的頻繁請求。

0×03 BEAST

Browser Exploit Against SSL/TLS

攻擊原理

攻擊者控制受害者傳送大量請求,利用CBC加密模式猜測關鍵資訊。

CBC模式工作的方法是當加密第i塊的時候,和第i-1塊的密文異或。更正式地表達如下:

Ci= E(Key, Ci-1 ⊕ Mi)

很顯然,當你加密第一塊的時候,沒有前一塊的密文和它異或,因此,標準的做法是產生一個隨機的初始化向量(IV),並且用它和第一塊明文異或。第一塊M0的加密如下:

C0= E(Key, IV ⊕ M0).

然後,接著第一塊M1加密如下:

C1= E(Key, C0 ⊕ M1).

現在,除非C0 碰巧和IV一樣(這是非常不可能的),那麼,即使M0 = M1,對於加密函式來說,兩個輸入是不同的,因此,C0≠ C1。 CBC有兩種的基本的使用方法:

1.        對於每條記錄都認為是獨立的;為每一個記錄產生一個IV

2.        把所有的記錄當作一個連結在一起的大物件,並且在記錄之間繼續使用CBC的狀態。這意味著最後一條記錄n的IV是n-1條記錄的密文。

SSLV3和TLS1.0選擇的是第二個用法。這好像本來就是個錯誤

CBC有兩種的基本的使用方法:

1. 對於每條記錄都認為是獨立的;為每一個記錄產生一個IV

2. 把所有的記錄當作一個連結在一起的大物件,並且在記錄之間繼續使用CBC的狀態。這意味著最後一條記錄n的IV是n-1條記錄的密文。

SSL 3.0和TLS1.0選擇的是第二個用法。因此產生了加密演算法的安全問題。

攻擊者可以把想要猜測的資料段替換掉成:

X ⊕ Ci-1 ⊕ P

當這個注入的內容被加密,X會被異或,結果傳給加密演算法的明文塊如下:

Ci-1 ⊕ P

如果P==Mi , 新的密文塊將和Ci一樣,這意味著,你的猜測是正確的。

攻擊前提

攻擊者可以獲取受害者的網路通訊包。(中間人攻擊,ISP供應商)

攻擊者需要能得到傳送敏感資料端的一部分許可權。以便將自己的資訊插入SSL/TLS會話中。

攻擊者需要準確的找出敏感資料的密文段。

攻擊這可以控制受害者傳送大量請求並可以控制請求內容。

防禦方法

使用RC4加密模式代替BCB加密模式。

部署TLS 1.1或者更高階的版本,來避免SSL 3.0/TLS 1.0帶來的安全問題。

在服務端設定每傳輸固定位元組,就改變一次加密祕鑰。

影響範圍

TLS 1.0.
SPDY protocol (Google).
Applications that uses TLS compression.
Mozilla Firefox (older versions) that support SPDY.
Google Chrome (older versions) that supported both TLS and SPDY.

POC

僅在python上模擬了攻擊思想的實現,編碼中只實現了第一個字母的猜測。

import sys
import string
import random
from Crypto.Cipher import AES

key = 'lyp62/22Sh2RlXJF'
mode = AES.MODE_CBC
vi = '1234567812345678'
charset = string.letters + string.digits
cookie = ''.join(random.choice(charset) for x in range(30))
HEADERS = ("POST / HTTP/1.1\r\n"
           "Host: thebankserver.com\r\n"
           "Connection: keep-alive\r\n"
           "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1\r\n"
           "Accept: */*\r\n"
           "Referer: https://thebankserver.com/\r\n"
           "Cookie: secret="+cookie+"\r\n"
           "Accept-Encoding: gzip,deflate,sdch\r\n"
           "Accept-Language: en-US,en;q=0.8\r\n"
           "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3\r\n"
           "\r\n")
global pad_num
def add_padding(plaintext):
    global pad_num
    pad_num = 16 - len(plaintext) % 16
    for i in range(0,pad_num):
        plaintext += chr(pad_num)
    return plaintext
def check_padding(plaintext):
    global pad_num
    for i in range(1,pad_num+1):
        if (plaintext[-i]!=chr(pad_num)):
            return False
    return True

def encrypto(plaintext):
    global pad_num
    obj = AES.new(key,mode,vi)
    if (len(plaintext) % 16):
        plaintext = add_padding(plaintext)
    else:
        pad_num=0
    ciphertext = obj.encrypt(plaintext)
    if (check_padding(ciphertext)):
        return ciphertext
    else:
        return 0

def decrypto(ciphertext):
    obj = AES.new(key,mode,vi)
    plaintext = obj.decrypt(ciphertext)
    return plaintext

def findcookie():
    global HEADERS
    return HEADERS.find('secret=')+7

guess_cookie=''
pos_cookie=findcookie()
pos_block_s = pos_cookie + 16 - pos_cookie%16
HEADERS = HEADERS[:pos_cookie] + (16 - pos_cookie % 16 + 15)*'a' +HEADERS[pos_cookie:]
encry_head = encrypto(add_padding(HEADERS))
per_per_block = encry_head[pos_block_s - 16:pos_block_s]   #Ci-1
per_block = encry_head[pos_block_s:pos_block_s+16]         #x
aft_block = encry_head[pos_block_s+16:pos_block_s+32]      #Ci+1
for i in charset:
    guess_block = 'a' * 15 + i
    insert_block = ''.join(chr(ord(a) ^ ord(b) ^ ord(c)) for a,b,c in zip(per_block,per_per_block,guess_block))
    temp_header = HEADERS[:pos_block_s+16] + insert_block + HEADERS[pos_block_s+16:]
    encry_temp_header = encrypto(add_padding(temp_header))
    if (aft_block == encry_temp_header[pos_block_s+32:pos_block_s+48]):
        print "(+)first byte is:"+i
print "(+)orign cookie:"+cookie

攻擊者首先使用降級攻擊,來讓瀏覽器使用ssl v3.0,再通過ssl v3.0 CBC-mode 存在的缺陷,竊取到使用者傳輸的明文。

0×04 POODLE

降級攻擊

ssl v3.0是一個存在了很久的協議了,現在大多數瀏覽器為了相容性都會支援這個協議,但是並不會首先使用這個協議,中間人攻擊者可以駁回瀏覽器協商高版本協議的請求,只放行ssl v3.0協議。

Padding Oracle攻擊

針對於CBC的攻擊之前已經有一些了,比如,Beast,Lucky17之類的,詳細可以看這裡

首先來看CBC-mod的加解密流程。

enter image description here

解密流程

enter image description here

加密流程

enter image description here

校驗流程

MAC1 = hash(明文)

密文 = Encode(明文+MAC1+Padding,K)   明文 = Decode(密文,k) – MAC1-Padding(padding的長度由最後一個位元組標識)

MAC2 = hash(明文)   如果 MAC1 == MAC2 則校驗成功 否則失敗

知二求三

Padding Oracle 攻擊一般都會滿足一個知二求三的規律,如下圖

(1) VI

(2) 解密後的資料,叫它 midText把

(3) Plaintext

這三個值我們得到其中兩個就可以推出另外一個,因為他們在一起Xor了嘛。

http://drops.wooyun.org/wp-content/uploads/2014/12/file0004.jpg

在Poodle攻擊中,我們會把最後一個資料塊替換成我們想要猜測的資料塊。如下圖所示。

enter image description here

這樣導致的直接後果就是,CBC完整性驗證失敗,資料包被駁回。我們假設最後一個資料塊均為padding組成(其實我們可以通過控制包的長度來達到這一目的,比如增加path的長度)

那麼當且僅當Plaintext[7] == 7(block為16為時為15) 的時候CBC完整性校驗才會通過。如果不為7,多刪或者少刪的padding,都會影響到MAC的正確取值,從而導致校驗失敗。

那麼,我們只需要不斷地更改(1) IV 最後一位的值 ,直到(3) Plaintext最後一位為 7 (CBC驗證通過)的時候,我們就可以推出 (2) mid text 的最後一位。

POODLE BEAST Lucky-13 RC4 Biases
Padding Oracle On Downgraded Legacy Encryption text-base-side-channel-attack time-base-side-channel-attack time-base-side-channel-attack
低版本SSL,中間人,大量資料包,BCB模式 低版本SSL,中間人,大量資料包,傳送內容可控,BCB模式 響應時間,大量資料包,傳送內容可控 響應時間,大量資料包,傳送內容可控,RC4模式

0×05 安全配置建議

此處的安全配置以nginx為例,主要在Nginx.conf中配置。

使用較為安全的SSL加密協議。

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

使用嚴格的加密方法設定。

ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4';

優先依賴伺服器密碼。

ssl_prefer_server_ciphers on;

啟用HSTS協議。

add_header Strict-Transport-Security max-age=15768000;

重定向的配置

server {
    listen 80;
    add_header Strict-Transport-Security max-age=15768000;
    return 301 https://www.yourwebsite.com$request_uri;
}

使用2048位的數字證照

openssl dhparam -out dhparam.pem 2048
ssl_dhparam /path/to/dhparam.pem;

相關文章