AES加密的安全問題
關(guān)注、星標(biāo)公眾號,直達(dá)精彩內(nèi)容
來源:https://www.anquanke.com/post/id/173088
整理:技術(shù)讓夢想更偉大?| 李肖遙
?
大家好,我是肖遙,最近又遇到了坑,AES加密,如果大家曾經(jīng)搞過,歡迎交流,下面是我整理了一些AES加密的相關(guān)知識,不止于技術(shù)。
aes加密簡介
AES算法全稱Advanced Encryption Standard,是DES算法的替代者,旨在取代DES成為廣泛使用的標(biāo)準(zhǔn),于2001年11月26日發(fā)布于FIPS PUB 197,并在2002年5月26日成為有效的標(biāo)準(zhǔn)。2006年,高級加密標(biāo)準(zhǔn)已然成為對稱密鑰加密中最流行的算法之一。
AES是典型的對稱加密算法,對稱加密不同于md5 sha的哈希摘要算法,對稱加密是可逆的,通常是明文+密鑰,再利用算法來加密成密文,如果要還原也很簡單,只要根據(jù)密鑰+密文+生成算法的逆運(yùn)算,即可解出,對稱加密特點(diǎn)為可逆,并且加密解密都是使用同一個密鑰,而非對稱加密則是公鑰私鑰加解密模式這里不做討論。
?
aes加密五種模式
aes加密的方式有五種工作體制。
1.電碼本模式(Electronic Codebook Book (ECB))
這種模式主要是將明文劃分為幾個明文段,分塊加密,但是加密密鑰是相同的。
2.密碼分組鏈接模式(Cipher Block Chaining (CBC))
這種模式是先將明文切分成若干小段,然后每一小段與初始塊或者上一段的密文段進(jìn)行異或運(yùn)算后,再與密鑰進(jìn)行加密。
3.計算器模式(Counter (CTR))
4.密碼反饋模式(Cipher FeedBack (CFB))
5.輸出反饋模式(Output FeedBack (OFB))
其中分組如,aes-128-ecb即為16字節(jié)為一組,16字節(jié)即為128位。
其他三種模式較為復(fù)雜,本文僅討論前兩種加密的安全性。
?
aes-ecb加密

aes-ecb加密是將一段明文,按照固定長度分組,然后對每一個分組,按照算法使用固定的密鑰進(jìn)行加密。假設(shè)123456加密。那么123為一組加密,456為一組加密,然后兩段明文加密后的密文拼在一起,就算完整的密文。
注意:這里每一組的加密都是使用相同的密鑰,相同的算法,所以在這種機(jī)制下,很可能出現(xiàn)安全問題。
比如:在身份認(rèn)證中,查詢用戶是否是管理員還是普通用戶,如果is_root=1則為管理員,如果不為1則為普通用戶,如果采用aes-ecb加密,對原文進(jìn)行分組加密。
明文:user_id:1.000000 is_root:0(其中is_root來判斷是否為管理員。) 然后用一段密鑰加算法進(jìn)行加密。
這種提交的加密數(shù)據(jù)是在cookie中提交,明文不可控,但是密文是可控的,但由于是進(jìn)行分組進(jìn)行,所以我們可以推算出每一分組明文對應(yīng)的密文,假設(shè)明文八個一組來進(jìn)行加密,分組后變?yōu)?(提示:僅僅是假設(shè)理想情況八位,實際并不是)
第一組:is_user
第二組:1.000000
第三組: is_root:
第四組:0(不夠的八位自動填充)
其中user_id 通常情況下我們前端可以修改,進(jìn)行修改為1.000000,此時原文被加密之后為四組 每組為八個數(shù)字的密文
假設(shè)加密后密文為
c4ca4238a0b923820dcc509a6f75849b 在cookie中被提交,將密文分為四組
c4ca4238
a0b92382
0dcc509a
6f75849b
此時密文我們是可控的,如果正常提交,服務(wù)器解密之后為user_id:1.000000 is_root:0,很顯然我們不是管理員,但是如果將第二組密文和第四組密文替換呢,那么user_id就是0,is_root就是1.000000。服務(wù)器就解析為user_id:0xxxxxxx(xx為填充字符) is_root:1.000000,顯然我們不需要知道密鑰,同樣可以進(jìn)行繞過。
還有一則在轉(zhuǎn)賬中,如果采用aes-128-ecb加密,在cookie中使用ecb分組加密,比如
付款人賬戶:
XXX //假設(shè)密文abc
收款人賬戶:
XXX //假設(shè)密文efg
試想一下,一旦這個分組是剛好分為四組,我們僅僅將abc與efg交換,那不就造成了支付收款反轉(zhuǎn),幾乎不需要什么技術(shù)就可以造成嚴(yán)重的攻擊。
ctf-案例
接下來以真實題目來進(jìn)行詳解。
ctf address:https://mixer-f3834380.challenges.bsidessf.net/(國外的一道ctf)

首先嘗試輸入admin admin 登陸。

返回內(nèi)容重點(diǎn)為紅色框內(nèi)的東西,需要使得第三個參數(shù) is_admin=1即可獲得flag,但是session cookie并不是這個題關(guān)注的點(diǎn),接下來就是抓包分析參數(shù)。修改參數(shù)。

經(jīng)測試修改url,get cookie post傳參都不能改變is_admin的值,所以只有一種可能,是在cookie里的user參數(shù)里加密了,然后傳遞給服務(wù)器,我們get參數(shù)傳入的賬號密碼被服務(wù)器端加密,然后服務(wù)器返回來加密后的user信息。
接下來測試是何種加密,測試為aes-ecb加密,那么是如何確定的呢,由于ecb是分組加密,所以一旦一組的密文我們修改了,其他組的密文解密之后是正常的,而被我們修改了的密文解密會是亂碼,所以我們隨便修改下user參數(shù)。

可以看到報錯,并且第一組的密文解密后是亂碼,而其他組的加密解密后為正常,所以猜測這一定是aes-ecb的分組加密的方式,此時,我們應(yīng)該先確定分組,幾個為一組,先破壞第一組加密然后破壞第二組加密,然后確定解密后json數(shù)據(jù)為,
{"first_name":"admin","last_name":"admin","is_admin":0}
總共為55個字符,
服務(wù)器密文為:d37c125ab4eae2ed02428d6d619016b06500bafffbeebe0c011977ad06c6946a45ba82569e93332195a36e61ae1fe26b325f7afd1eaa5ee8bb11efe6eebc5b54
為128個字符,五十五個字符補(bǔ)位為64個字符,分組測試破壞每一組,測試到一組明文16個字符,加密密文為32個字符。
明文分為四組,一組16個字符,密文分為四組,一組32個字符。
d37c125ab4eae2ed
02428d6d619016b0
6500bafffbeebe0c
011977ad06c6946a
45ba82569e933321
95a36e61ae1fe26b
325f7afd1eaa5ee8
bb11efe6eebc5b54
可控的范圍是我們輸入的賬號密碼 admin admin。
{“first_name”:” 為十五個字符,我們首先構(gòu)造賬號為 a1.0000000000000}
其中a是為了填充第一組,這樣第一組就是{“first_name”:”a,這樣剩下的1.0000000000000}就是十六個字符為一組,第二組就是1.0000000000000},這樣服務(wù)器加密后返回的第33-64位加密就是1.0000000000000},我們讓服務(wù)器幫我們加密,這樣我們就不需要知道密鑰和算法,讓服務(wù)器幫我們加密任何我們想要的東西,提交數(shù)據(jù)。

可以看到服務(wù)器返回了加密后的內(nèi)容。我們截取第33位-64位字符。即為1.0000000000000}的密文。
3af6e4a9e05c702b02f9f4288c1c605c
接下來就是需要填充位數(shù)。我們讓服務(wù)器解密的json數(shù)據(jù)最后的0}為第65 66位,因為如果這樣的話,前64位剛好是四組,65 66為一組,正好將它32位的密文替換成我們構(gòu)造的密文。
{“first_name”:”admin”,”last_name”:”admin”,”is_admin”:0}
五十五位的字符串,我們讓好賬號變?yōu)閍dmin12345678900,那么字符串就是66位,正好符合多余出來的兩位是0},最后這兩位被填充之后的密文同樣是32位,這樣就可以替換我們構(gòu)造的32位密文。

可以看到服務(wù)器構(gòu)造成功得到flag。
總結(jié)一下上面思路,我們根據(jù)每一組的加密密文長度固定明文長度固定,所以填充位數(shù),然后讓我們想要的數(shù)據(jù)成為單獨(dú)的一組,讓服務(wù)器進(jìn)行加密,這樣我們就可控制任意明文加密,然后修改cookie里提交的密文,填充字節(jié),讓我們需要的密文位置成為單獨(dú)的一組,然后替換我們之前構(gòu)造的一組數(shù)據(jù),這樣就可以繞過。
此題值得一題的是雙引號單引號反斜線等被過濾了,所以師傅們其他需要引入雙引號等的不用嘗試了。
?
aes-cbc加密
這種模式是先將明文切分成若干小段,然后每一小段與初始塊或者上一段的密文段進(jìn)行異或運(yùn)算后,再與密鑰進(jìn)行加密。aes-

IV:用于隨機(jī)化加密的比特塊,保證即使對相同明文多次加密,也可以得到不同的密文。
秘鑰:用于加密。
密文塊0:第一組密文被加密后的內(nèi)容。(同樣也是第二組明文加密過程中的IV)
cbc加密方式不難理解,將一串明文進(jìn)行分組,舉例 123456789
123為第一組,456為第二組,789為第三組,將123與IV異或加密(加密中IV只在第一次異或有用),得到的異或后的密文與密鑰加密,假設(shè)此時第一組加密的最終密文為abc,那么456先于第一組的密文abc異或加密,得到的異或密文在與密鑰加密,假設(shè)第二組最終密文為def,往復(fù)循環(huán),def與第三組明文異或,然后和密鑰加密,假設(shè)密文ghi,那么最終密文就是
abcdefghi并且將iv發(fā)送。
其中值得一提的是初始始化向量IV每次隨即初始化,所以即使相同的字符串也不會有相同的密文。
cbc字節(jié)反轉(zhuǎn)攻擊
那么這種在這種加密的方式下,并不安全,問題出在異或加密這里,在講解字節(jié)反轉(zhuǎn)攻擊前先了解下異或加密。
異或 xor 符號表示為 ^ ,計算機(jī)中 兩個數(shù)字異或,相同為0,不同為1。1^1=0 0^1=1
如果是字母異或加密,a^b,那么首先轉(zhuǎn)化為ascii編碼,然后二進(jìn)制,對每一位進(jìn)行異或得到的結(jié)果轉(zhuǎn)為十進(jìn)制,在ascii編碼出來。

異或有一個特性,任意值與自己本身做異或運(yùn)算的結(jié)果都是0,任意值與0做異或運(yùn)算的結(jié)果都是自己。本身a^b=亂七八糟,a^a則為空,但是a^a^任意字母=任意字母。


在CBC解密中,如圖A是第一組的密文,B是第二組被解密的密文(未異或),C是明文。C=A^B。那么B=C^A,且A^B^C=0。如果我們更改A,A為我們可控的密文,C=A^B,如果我們使A=B^X,B=C^A,所以A=C^A^X,C=C^A^X^B=B^X^B=X。這里X是我們需要的任意字符,這便是CBC字節(jié)反轉(zhuǎn)攻擊的核心,這樣一來C的明文就完全可控了。
簡單的登錄-cbc字節(jié)反轉(zhuǎn)
原理說了很多,那么接下來實戰(zhàn)一下。
實驗吧題目:http://ctf5.shiyanbar.com/web/jiandan/index.php

首先,輸入框隨便輸入,然后發(fā)送請求抓包,看到返回包的頭請求有tips,test.php。訪問test.php即可看到源碼。
define("SECRET_KEY", '***********');define("METHOD", "aes-128-cbc");error_reporting(0);include('conn.php');function sqliCheck($str){if(preg_match("/\|,|-|#|=|~|union|like|procedure/i",$str)){return 1;}return 0;}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);//$plain為要加密的明文,METHOD加密方法,SECRET_KEY是秘鑰,OPENSSL_RAW_DATA為數(shù)據(jù)格式,$iv隨機(jī)生成的初始化向量。setcookie("iv", base64_encode($iv));setcookie("cipher", base64_encode($cipher));}function show_homepage(){global $link;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("base64_decode('"
.base64_encode($plain)."') can't unserialize");$sql="select * from users limit ".$info['id'].",0";$result=mysqli_query($link,$sql);if(mysqli_num_rows($result)>0 or die(mysqli_error($link))){$rows=mysqli_fetch_array($result);echo '.$rows['username'].'';
Hello!' }else{echo '';
Hello! }}else{die("ERROR!");}}}if(isset($_POST['id'])){$id = (string)$_POST['id'];if(sqliCheck($id))die("");
sql inject detected! $info = array('id'=>$id);login($info);echo '';
Hello! }else{if(isset($_COOKIE["iv"])&&isset($_COOKIE['cipher'])){show_homepage();}else{echo 'Login Form
input id to login'" />';}}
前提:這一關(guān)不是單純注入饒過的,肯定要利用cbc字節(jié)反轉(zhuǎn)攻擊。
1.首先直接看在哪里可以得到flag,沒傳入ID參數(shù)的時候,如果cookie建立了iv 和 cipher參數(shù),那么就可以調(diào)用show_homepage,執(zhí)行sql查詢,flag在數(shù)據(jù)庫里查詢。
2.但是肯定要傳參id,先生成iv 和 cipher,將id=X該數(shù)組進(jìn)行序列化之后,以序列化結(jié)果和一個bs64編碼隨機(jī)數(shù)iv進(jìn)行cbc加密生成密文cipher,加密算法為aes-128-cbc,此時就要考慮cbc字節(jié)反轉(zhuǎn)了,128位,按十六字節(jié)分組。生成iv和cipher之后url編碼返回請求頭,生成細(xì)節(jié)參考自定義login函數(shù)。
3.sql查詢語句拼接了一個0,所以我們只要注釋掉0便可進(jìn)行我們的查詢。所以可以利用cbc字節(jié)翻轉(zhuǎn)攻擊更改密文,更改解密后的id,從而繞過進(jìn)行sqlwaf,cookie傳入?yún)?shù) cipher和iv,base64解碼然后aes解密,php反序列化,如果不能反序列化,則輸出base64編碼,否則就sql語句拼接查詢。如果有結(jié)果回顯,否則輸出hello。
綜上,只要我們能夠CBC進(jìn)行字節(jié)反轉(zhuǎn)就可以執(zhí)行sql查詢,就可以進(jìn)行查詢flag。
接下來第一步首先要cbc字節(jié)反轉(zhuǎn),修改密文中的id。不妨先測試下位數(shù),如果傳入id=12(因為我們要修改為1#),則序列化后內(nèi)容為
a:1:{s:2:"id";s:2:"12";}
由于我們需要分組,aes-128-cbc,128位16字節(jié)分組
第一組:a:1:{s:2:"id";s:
第二組:2:"12";}
10中的0是第二組的第五個字符,所以需要更改第5個字符,右偏移四個字符,第一組也要向右偏移四個字符。接下來就是cbc字節(jié)反轉(zhuǎn)腳本。
# -*- coding:utf8 -*-
from base64 import *
import urllib
cipher='fn060OBP%2FyLIGYrD9bi%2FlWWAS9RIWvEtALaV26kuB%2F8%3D'#加密后的密文
cipher_raw=b64decode(urllib.unquote(cipher))#首先urldecode解碼,然后base64解碼
cipher_raw_list=list(cipher_raw)#將解碼的密文分組
py=4#偏移量為4
A=cipher_raw_list[py]#要異或第二組密文的位置
C='2'#第二組被替換的明文
X='#'#將第二組替換掉的明文
cipher_raw_list[py]=chr(ord(A)^ord(C)^ord(X))#將偏移量為4的替換。
cipher_new=''.join(cipher_raw_list)#使用''將每一個字符連接起來,
cipher_new=urllib.quote(b64encode(cipher_new))#將替換完的密文base64編碼,urlencode編碼。
print cipher_new#打印出最終密文
其中特意將ACX等變量對應(yīng)上文所講的參數(shù)??蓞⒖忌厦鎐bc字節(jié)反轉(zhuǎn)配合圖來理解。然后生成反轉(zhuǎn)后的密文:
fn060PFP/yLIGYrD9bi/lWWAS9RIWvEtALaV26kuB/8%3D
此時提交密文發(fā)送服務(wù)器會返回base64編碼字符串無法反序列化。

原因為下面這句。

接下來我們需要修改IV,原理很簡單,我們分為兩組來進(jìn)行加解密,第一組密文只參與第二組的異或,第一組修改完成后,第二組的解密是完全沒有問題的,但是第一組被我們修改了一個字符,但是異或的IV還是原來的IV,必須要修改IV才能使第一組正常異或,得到結(jié)果。還是上述原理,三次異或,控制想要的結(jié)果。
這里在看圖,

A:這里特別要說明注意,A是我們第一次字節(jié)反轉(zhuǎn)之后的明文(序列化狀態(tài))
B:原來的IV
C:字節(jié)反轉(zhuǎn)后解密后的第一組(未被異或)
D: 正常的序列化字符串 ‘a(chǎn):1:{s:2:”id”;s:’
E:新的IV
A=B^C,因為我們A是字節(jié)反轉(zhuǎn)這里我們可以看到,IV是原來的IV,但是A和C都是字節(jié)反轉(zhuǎn)后的,所以A必然是個無法反序列化的明文,我們修改B也就是IV,使得異或得到正常的序列化字符串。
B=A^C,我們需要得到的結(jié)果是D=E^C,而C=B^A,所以D=E^B^A,那么E=B^A^D。//建議初學(xué)者自己多分析下邏輯,多寫寫,干想很頭疼。
接下來是IV修改的腳本。
# -*- coding:utf8 -*-__author__='[email protected]'from base64 import *import urllibiv='erUDGVSvM4Kab3ztg8vT8Q%3D%3D'B=b64decode(urllib.unquote(iv))D='a:1:{s:2:"id";s:'A=b64decode('eFoXA0j/x2Em/bhfgeLzXjI6IjEjIjt9')iv_new=''for i in range(16):iv_new+=chr(ord(A[i])^ord(D[i])^ord(B[i]))iv_new=urllib.quote(b64encode(iv_new))print iv_new
替換掉原來的IV,即可正常sql查詢。

至此,此題的cbc反轉(zhuǎn)我們已經(jīng)完成了,剩下的注入原理一樣,注入不是本題的目的,也就不再發(fā)剩下的腳本了。CBC還是要自己寫一下用圖理解一下。
其余加密問題,后續(xù)我會補(bǔ)充到本文。
參考:https://www.yourhome.ren/index.php/sec/366.html
參考:實驗吧pcat師傅的writeup
???????????????? ?END ????????????????? 關(guān)注我的微信公眾號,回復(fù)“加群”按規(guī)則加入技術(shù)交流群。
歡迎關(guān)注我的視頻號:
點(diǎn)擊“閱讀原文”查看更多分享,歡迎點(diǎn)分享、收藏、點(diǎn)贊、在看。
