1. 程式人生 > >CBC翻轉攻擊,瞭解一下!

CBC翻轉攻擊,瞭解一下!

通過一道題了解CBC翻轉攻擊

CBC翻轉攻擊方法的精髓在於:

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

下面介紹一下CBC位元組翻轉攻擊的原理:
這裡寫圖片描述
上圖CBC加密的原理圖

  1. Plaintext:待加密的資料。
  2. IV:用於隨機化加密的位元塊,保證即使對相同明文多次加密,也可以得到不同的密文。
  3. Ciphertext:加密後的資料。

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

整個加密的過程簡單說來就是:

  1. 首先將明文分組(常見的以16位元組為一組),位數不足的使用特殊字元填充。
  2. 生成一個隨機的初始化向量(IV)和一個金鑰。
  3. 將IV和第一組明文異或。
  4. 用金鑰對3中xor後產生的密文加密。
  5. 用4中產生的密文對第二組明文進行xor操作。
  6. 用金鑰對5中產生的密文加密。
  7. 重複4-7,到最後一組明文。
  8. 將IV和加密後的密文拼接在一起,得到最終的密文。

從第一塊開始,首先與一個初始向量iv異或(iv只在第一處作用),然後把異或的結果配合key進行加密,得到第一塊的密文,並且把加密的結果與下一塊的明文進行異或,一直這樣進行下去。因此這種模式最重要的特點就是:前一塊的密文用來產生後一塊的密文。
這裡寫圖片描述
這是解密過程,解密的過程其實只要理解了加密,反過來看解密過程就也很簡單了,同樣的,前一塊密文參與下一塊密文的還原

  1. 從密文中提取出IV,然後將密文分組。
  2. 使用金鑰對第一組的密文解密,然後和IV進行xor得到明文。
  3. 使用金鑰對第二組密文解密,然後和2中的密文xor得到明文。
  4. 重複2-3,直到最後一組密文。

這幅圖是我們進行翻轉攻擊的原理圖:
這裡寫圖片描述
這裡可以注意到前一塊Ciphertext用來產生下一塊明文,如果我們改變前一塊Ciphertext中的一個位元組,然後和下一塊解密後的密文xor,就可以得到一個不同的明文,而這個明文是我們可以控制的。利用這一點,我們就欺騙服務端或者繞過過濾器。

具體怎麼翻轉呢,因為涉及到異或,這裡稍微介紹下異或的概念:

當我們的一個值C是由A和B異或得到
C = A XOR B
那麼
A XOR B XOR C很明顯是=0的
當我們知道B和C之後,想要得到A的值也很容易
A = B XOR C
因此,A XOR B XOR C等於0。有了這個公式,我們可以在XOR運算的末尾處設定我們自己的值,即可改變。

下面以一道例題作為說明
http://118.89.219.210:49168/index.php
首先,嘗試用常見的使用者名稱測試一下,root,user,admin等
這裡寫圖片描述
當登入其他的使用者名稱時,返回來了提示only admin can see flag
這裡寫圖片描述
當嘗試用admin登入時,卻說admin不允許登入
這裡寫圖片描述
接著嘗試看看是否存在注入,可是都返回一樣的資訊,這時候思路需要轉換一下,肯定有條路走,不然這道題做不下去,這時候要考慮是不是存在某些提示,比如原始碼,掃一遍常見的敏感路徑,最後發現.index.php.swp存在,下載下來。
這裡寫圖片描述

關於swp檔案:
使用vi,經常可以看到swp這個檔案。那這個檔案是怎麼產生的呢,當開啟一個檔案,vi就會生成這麼一個.(filename)swp檔案 以備不測(比如非正常退出),如果你正常退出,那麼這個這個swp檔案將會自動刪除 。

怎麼恢復.swp:
可以使用
vi -r {your file name}
來恢復檔案,然後用下面的命令刪除swp檔案,不然每一次編輯時總是有這個提示。
rm .{your file name}.swp
這裡寫圖片描述
這裡寫圖片描述
輸入7,即可恢復
看到該題的程式碼

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">;
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Login Form</title>
<link href="static/css/style.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="static/js/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
    $(".username").focus(function() {
        $(".user-icon").css("left","-48px");
    });
    $(".username").blur(function() {
        $(".user-icon").css("left","0px");
    });
    $(".password").focus(function() {
        $(".pass-icon").css("left","-48px");
    });
    $(".password").blur(function() {
        $(".pass-icon").css("left","0px");
    });
});
</script>
</head>
<?php
define("SECRET_KEY", file_get_contents('/root/key'));
define("METHOD", "aes-128-cbc");
session_start();
function get_random_iv(){
    $random_iv='';
    for($i=0;$i<16;$i++){
        $random_iv.=chr(rand(1,255));
    }
    return $random_iv;
}
function login($info){
    $iv = get_random_iv();
    $plain = serialize($info);
    $cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);
    $_SESSION['username'] = $info['username'];
    setcookie("iv", base64_encode($iv));
    setcookie("cipher", base64_encode($cipher));
}
function check_login(){
    if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){
        $cipher = base64_decode($_COOKIE['cipher']);
        $iv = base64_decode($_COOKIE["iv"]);
        if($plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)){
            $info = unserialize($plain) or die("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>");
            $_SESSION['username'] = $info['username'];
        }else{
            die("ERROR!");
        }
    }
}
function show_homepage(){
    if ($_SESSION["username"]==='admin'){
        echo '<p>Hello admin</p>';
        echo '<p>Flag is $flag</p>';
    }else{
        echo '<p>hello '.$_SESSION['username'].'</p>';
        echo '<p>Only admin can see flag</p>';
    }
    echo '<p><a href="loginout.php">Log out</a></p>';
}
if(isset($_POST['username']) && isset($_POST['password'])){
    $username = (string)$_POST['username'];
    $password = (string)$_POST['password'];
    if($username === 'admin'){
        exit('<p>admin are not allowed to login</p>');
    }else{
        $info = array('username'=>$username,'password'=>$password);
        login($info);
        show_homepage();
    }
}else{
    if(isset($_SESSION["username"])){
        check_login();
        show_homepage();
    }else{
        echo '<body class="login-body">
                <div id="wrapper">
                    <div class="user-icon"></div>
                    <div class="pass-icon"></div>
                    <form name="login-form" class="login-form" action="" method="post">
                        <div class="header">
                        <h1>Login Form</h1>
                        <span>Fill out the form below to login to my super awesome imaginary control panel.</span>
                        </div>
                        <div class="content">
                        <input name="username" type="text" class="input username" value="Username" onfocus="this.value=\'\'" />
                        <input name="password" type="password" class="input password" value="Password" onfocus="this.value=\'\'" />
                        </div>
                        <div class="footer">
                        <input type="submit" name="submit" value="Login" class="button" />
                        </div>
                    </form>
                </div>
            </body>';
    }
}
?>
</html>

從程式碼中可以看出考察的是cbc位元組反轉攻擊,而且是用了及其簡單的CBC。

仔細審計程式碼
我們先發送正常請求

username=zdmin&password=12345

bp檢視返回包
這裡寫圖片描述
把裡面的cipher進行翻轉下面是自己寫的指令碼

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2018-03-15 11:45:57
# @Author  : Mr.zhang(s4ad0w.protonmail.com)
# @Link    : http://blog.csdn.net/csu_vc
import base64
import requests
import urllib
iv_raw='%2F8iEm4jh%2BjbgVGwlQ31ycg%3D%3D'  #這裡填寫第一次返回的iv值
cipher_raw='8WdhbPxjZy9xYAgoCeghiOUQu0ri1Y3dv7cX44MbvOfIC6zZxCbR%2FPFpeMatL5qIgT%2BYA66tIdCBpxtWsWxV9Q%3D%3D'  #這裡填寫第一次返回的cipher值
print "[*]原始iv和cipher"
print "iv_raw:  " + iv_raw
print "cipher_raw:  " + cipher_raw
print "[*]對cipher解碼,進行反轉"
cipher = base64.b64decode(urllib.unquote(cipher_raw))
#a:2:{s:8:"username";s:5:"zdmin";s:8:"password";s:5:"12345"}
#s:2:{s:8:"userna
#me";s:5:"zdmin";
#s:8:"password";s
#:3:"12345";}
xor_cipher = cipher[0:9] +  chr(ord(cipher[9]) ^ ord('z') ^ ord('a')) + cipher[10:]  #請根據你的輸入自行更改,原理看上面的介紹
xor_cipher=urllib.quote(base64.b64encode(xor_cipher))
print "反轉後的cipher:" + xor_cipher

這裡寫圖片描述
然後再bp中的cookie中設定iv和翻轉後的cipher,注意,這裡要把post的資料清空,否則會重複開始的流程,就進不了這個流程了
這裡寫圖片描述
這次返回的結果如下
這裡寫圖片描述

伺服器提示反序列化失敗,但是其實我們這個時候只要對這個進行base64解碼就會發現,我們的username已經變成了admin,但是要注意的是,這裡得到的其實只是php將密文解密後並且異或後產生的一段錯誤的plaintext,並非編碼後的密文。
原因是在我們為了修改mdmin為admin的時候,是通過修改第一塊資料來修改的,所以第一個塊資料(16位元組)被破壞了。因為程式中要求username要等於admin所以不能利用文章裡的說的填充字元。 又因為是第一個塊資料被破壞,第一個塊資料是和IV有關,所以只要將在CBC字元翻轉攻擊,得到新的IV就可以修復第一塊資料。
具體辦法如下

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2018-03-15 11:56:20
# @Author  : csu_vc(s4ad0w.protonmail.com)
# @Link    : http://blog.csdn.net/csu_vc
import base64
import urllib
cipher = 'Bc6oENSSAEPpPdv/rbqRZG1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjU6IjEyMzQ1Ijt9'#填寫提交後所得的無法反序列化密文
iv = '%2F8iEm4jh%2BjbgVGwlQ31ycg%3D%3D'#一開始提交的iv
#cipher = urllib.unquote(cipher)
cipher = base64.b64decode(cipher)
iv = base64.b64decode(urllib.unquote(iv))
newIv = ''
right = 'a:2:{s:8:"userna'#被損壞前正確的明文
for i in range(16):
    newIv += chr(ord(right[i])^ord(iv[i])^ord(cipher[i])) #這一步相當於把原來iv中不匹配的部分修改過來
print urllib.quote(base64.b64encode(newIv))

這裡寫圖片描述
把得到的新的修復的iv值替換掉,cipher仍然為翻轉後的cipher
提交,就可以成功進去

這裡寫圖片描述

至於序列化的知識:
因為不是這個主題的內容就,不詳細介紹了,感興趣可以百度。

後記:
這道題涉及到的一些密碼學的概念和序列化的知識。本人通過反覆測試,對照程式碼,檢視自己進入到了哪一步的流程,嘗試了很多次,一步一步才得到最終答案,過程雖然麻煩,但是很有意思。菜雞上路,各位大佬勿噴,只是記錄下自己的思路,歡迎大佬們指出錯誤。