AES加密的安全问题

李肖遥

共 4604字,需浏览 10分钟

 · 2021-10-25

关注、星标公众号,直达精彩内容

来源:https://www.anquanke.com/post/id/173088

整理:技术让梦想更伟大 | 李肖遥

 

大家好,我是肖遥,最近又遇到了坑,AES加密,如果大家曾经搞过,欢迎交流,下面是我整理了一些AES加密的相关知识,不止于技术。


aes加密简介


AES算法全称Advanced Encryption Standard,是DES算法的替代者,旨在取代DES成为广泛使用的标准,于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。


AES是典型的对称加密算法,对称加密不同于md5 sha的哈希摘要算法,对称加密是可逆的,通常是明文+密钥,再利用算法来加密成密文,如果要还原也很简单,只要根据密钥+密文+生成算法的逆运算,即可解出,对称加密特点为可逆,并且加密解密都是使用同一个密钥,而非对称加密则是公钥私钥加解密模式这里不做讨论。

 

aes加密五种模式


aes加密的方式有五种工作体制。


1.电码本模式(Electronic Codebook Book (ECB))

这种模式主要是将明文划分为几个明文段,分块加密,但是加密密钥是相同的。


2.密码分组链接模式(Cipher Block Chaining (CBC))

这种模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。


3.计算器模式(Counter (CTR))


4.密码反馈模式(Cipher FeedBack (CFB))


5.输出反馈模式(Output FeedBack (OFB))


其中分组如,aes-128-ecb即为16字节为一组,16字节即为128位。


其他三种模式较为复杂,本文仅讨论前两种加密的安全性。

 

aes-ecb加密



aes-ecb加密是将一段明文,按照固定长度分组,然后对每一个分组,按照算法使用固定的密钥进行加密。假设123456加密。那么123为一组加密,456为一组加密,然后两段明文加密后的密文拼在一起,就算完整的密文。


注意:这里每一组的加密都是使用相同的密钥,相同的算法,所以在这种机制下,很可能出现安全问题。


比如:在身份认证中,查询用户是否是管理员还是普通用户,如果is_root=1则为管理员,如果不为1则为普通用户,如果采用aes-ecb加密,对原文进行分组加密。


明文:user_id:1.000000 is_root:0(其中is_root来判断是否为管理员。)  然后用一段密钥加算法进行加密。

这种提交的加密数据是在cookie中提交,明文不可控,但是密文是可控的,但由于是进行分组进行,所以我们可以推算出每一分组明文对应的密文,假设明文八个一组来进行加密,分组后变为 (提示:仅仅是假设理想情况八位,实际并不是)


第一组:is_user 
第二组:1.000000
第三组: is_root:
第四组:0(不够的八位自动填充)

其中user_id 通常情况下我们前端可以修改,进行修改为1.000000,此时原文被加密之后为四组 每组为八个数字的密文

假设加密后密文为

c4ca4238a0b923820dcc509a6f75849b 在cookie中被提交,将密文分为四组

c4ca4238
a0b92382
0dcc509a
6f75849b

此时密文我们是可控的,如果正常提交,服务器解密之后为user_id:1.000000 is_root:0,很显然我们不是管理员,但是如果将第二组密文和第四组密文替换呢,那么user_id就是0,is_root就是1.000000。服务器就解析为user_id:0xxxxxxx(xx为填充字符) is_root:1.000000,显然我们不需要知道密钥,同样可以进行绕过。


还有一则在转账中,如果采用aes-128-ecb加密,在cookie中使用ecb分组加密,比如


付款人账户:
XXX //假设密文abc
收款人账户:
XXX //假设密文efg


试想一下,一旦这个分组是刚好分为四组,我们仅仅将abc与efg交换,那不就造成了支付收款反转,几乎不需要什么技术就可以造成严重的攻击。


ctf-案例


接下来以真实题目来进行详解。


ctf address:https://mixer-f3834380.challenges.bsidessf.net/(国外的一道ctf)



首先尝试输入admin admin 登陆。



返回内容重点为红色框内的东西,需要使得第三个参数 is_admin=1即可获得flag,但是session cookie并不是这个题关注的点,接下来就是抓包分析参数。修改参数。



经测试修改url,get cookie post传参都不能改变is_admin的值,所以只有一种可能,是在cookie里的user参数里加密了,然后传递给服务器,我们get参数传入的账号密码被服务器端加密,然后服务器返回来加密后的user信息。


接下来测试是何种加密,测试为aes-ecb加密,那么是如何确定的呢,由于ecb是分组加密,所以一旦一组的密文我们修改了,其他组的密文解密之后是正常的,而被我们修改了的密文解密会是乱码,所以我们随便修改下user参数。



可以看到报错,并且第一组的密文解密后是乱码,而其他组的加密解密后为正常,所以猜测这一定是aes-ecb的分组加密的方式,此时,我们应该先确定分组,几个为一组,先破坏第一组加密然后破坏第二组加密,然后确定解密后json数据为,


{"first_name":"admin","last_name":"admin","is_admin":0}

总共为55个字符,


服务器密文为:d37c125ab4eae2ed02428d6d619016b06500bafffbeebe0c011977ad06c6946a45ba82569e93332195a36e61ae1fe26b325f7afd1eaa5ee8bb11efe6eebc5b54

为128个字符,五十五个字符补位为64个字符,分组测试破坏每一组,测试到一组明文16个字符,加密密文为32个字符。


明文分为四组,一组16个字符,密文分为四组,一组32个字符。


d37c125ab4eae2ed
02428d6d619016b0
6500bafffbeebe0c
011977ad06c6946a
45ba82569e933321
95a36e61ae1fe26b
325f7afd1eaa5ee8
bb11efe6eebc5b54


可控的范围是我们输入的账号密码 admin admin。


{“first_name”:” 为十五个字符,我们首先构造账号为 a1.0000000000000}

其中a是为了填充第一组,这样第一组就是{“first_name”:”a,这样剩下的1.0000000000000}就是十六个字符为一组,第二组就是1.0000000000000},这样服务器加密后返回的第33-64位加密就是1.0000000000000},我们让服务器帮我们加密,这样我们就不需要知道密钥和算法,让服务器帮我们加密任何我们想要的东西,提交数据。



可以看到服务器返回了加密后的内容。我们截取第33位-64位字符。即为1.0000000000000}的密文。


3af6e4a9e05c702b02f9f4288c1c605c


接下来就是需要填充位数。我们让服务器解密的json数据最后的0}为第65 66位,因为如果这样的话,前64位刚好是四组,65 66为一组,正好将它32位的密文替换成我们构造的密文。


{“first_name”:”admin”,”last_name”:”admin”,”is_admin”:0}


五十五位的字符串,我们让好账号变为admin12345678900,那么字符串就是66位,正好符合多余出来的两位是0},最后这两位被填充之后的密文同样是32位,这样就可以替换我们构造的32位密文。



可以看到服务器构造成功得到flag。


总结一下上面思路,我们根据每一组的加密密文长度固定明文长度固定,所以填充位数,然后让我们想要的数据成为单独的一组,让服务器进行加密,这样我们就可控制任意明文加密,然后修改cookie里提交的密文,填充字节,让我们需要的密文位置成为单独的一组,然后替换我们之前构造的一组数据,这样就可以绕过。

此题值得一题的是双引号单引号反斜线等被过滤了,所以师傅们其他需要引入双引号等的不用尝试了。

 

aes-cbc加密


这种模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。aes-



IV:用于随机化加密的比特块,保证即使对相同明文多次加密,也可以得到不同的密文。


秘钥:用于加密。


密文块0:第一组密文被加密后的内容。(同样也是第二组明文加密过程中的IV)

cbc加密方式不难理解,将一串明文进行分组,举例 123456789


123为第一组,456为第二组,789为第三组,将123与IV异或加密(加密中IV只在第一次异或有用),得到的异或后的密文与密钥加密,假设此时第一组加密的最终密文为abc,那么456先于第一组的密文abc异或加密,得到的异或密文在与密钥加密,假设第二组最终密文为def,往复循环,def与第三组明文异或,然后和密钥加密,假设密文ghi,那么最终密文就是


abcdefghi并且将iv发送。


其中值得一提的是初始始化向量IV每次随即初始化,所以即使相同的字符串也不会有相同的密文。


cbc字节反转攻击


那么这种在这种加密的方式下,并不安全,问题出在异或加密这里,在讲解字节反转攻击前先了解下异或加密。


异或 xor 符号表示为 ^ ,计算机中 两个数字异或,相同为0,不同为1。1^1=0 0^1=1


如果是字母异或加密,a^b,那么首先转化为ascii编码,然后二进制,对每一位进行异或得到的结果转为十进制,在ascii编码出来。



异或有一个特性,任意值与自己本身做异或运算的结果都是0,任意值与0做异或运算的结果都是自己。本身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字节反转攻击的核心,这样一来C的明文就完全可控了。


简单的登录-cbc字节反转


原理说了很多,那么接下来实战一下。


实验吧题目:http://ctf5.shiyanbar.com/web/jiandan/index.php



首先,输入框随便输入,然后发送请求抓包,看到返回包的头请求有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为数据格式,$iv随机生成的初始化向量。    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 '

Hello!'.$rows['username'].'

'
;
}
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 '
'; }}
?>


前提:这一关不是单纯注入饶过的,肯定要利用cbc字节反转攻击。


1.首先直接看在哪里可以得到flag,没传入ID参数的时候,如果cookie建立了iv 和 cipher参数,那么就可以调用show_homepage,执行sql查询,flag在数据库里查询。


2.但是肯定要传参id,先生成iv 和 cipher,将id=X该数组进行序列化之后,以序列化结果和一个bs64编码随机数iv进行cbc加密生成密文cipher,加密算法为aes-128-cbc,此时就要考虑cbc字节反转了,128位,按十六字节分组。生成iv和cipher之后url编码返回请求头,生成细节参考自定义login函数。


3.sql查询语句拼接了一个0,所以我们只要注释掉0便可进行我们的查询。所以可以利用cbc字节翻转攻击更改密文,更改解密后的id,从而绕过进行sqlwaf,cookie传入参数 cipher和iv,base64解码然后aes解密,php反序列化,如果不能反序列化,则输出base64编码,否则就sql语句拼接查询。如果有结果回显,否则输出hello。


综上,只要我们能够CBC进行字节反转就可以执行sql查询,就可以进行查询flag。


接下来第一步首先要cbc字节反转,修改密文中的id。不妨先测试下位数,如果传入id=12(因为我们要修改为1#),则序列化后内容为


a:1:{s:2:"id";s:2:"12";}


由于我们需要分组,aes-128-cbc,128位16字节分组


第一组:a:1:{s:2:"id";s:

第二组:2:"12";}


10中的0是第二组的第五个字符,所以需要更改第5个字符,右偏移四个字符,第一组也要向右偏移四个字符。接下来就是cbc字节反转脚本。


# -*- 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等变量对应上文所讲的参数。可参考上面cbc字节反转配合图来理解。然后生成反转后的密文:


fn060PFP/yLIGYrD9bi/lWWAS9RIWvEtALaV26kuB/8%3D


此时提交密文发送服务器会返回base64编码字符串无法反序列化。


原因为下面这句。



接下来我们需要修改IV,原理很简单,我们分为两组来进行加解密,第一组密文只参与第二组的异或,第一组修改完成后,第二组的解密是完全没有问题的,但是第一组被我们修改了一个字符,但是异或的IV还是原来的IV,必须要修改IV才能使第一组正常异或,得到结果。还是上述原理,三次异或,控制想要的结果。


这里在看图,



A:这里特别要说明注意,A是我们第一次字节反转之后的明文(序列化状态)
B:原来的IV
C:字节反转后解密后的第一组(未被异或)
D: 正常的序列化字符串 ‘a:1:{s:2:”id”;s:’
E:新的IV


A=B^C,因为我们A是字节反转这里我们可以看到,IV是原来的IV,但是A和C都是字节反转后的,所以A必然是个无法反序列化的明文,我们修改B也就是IV,使得异或得到正常的序列化字符串。


B=A^C,我们需要得到的结果是D=E^C,而C=B^A,所以D=E^B^A,那么E=B^A^D。//建议初学者自己多分析下逻辑,多写写,干想很头疼。


接下来是IV修改的脚本。

# -*- coding:utf8 -*-__author__='pcat@chamd5.org'
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反转我们已经完成了,剩下的注入原理一样,注入不是本题的目的,也就不再发剩下的脚本了。CBC还是要自己写一下用图理解一下。


其余加密问题,后续我会补充到本文。


参考:https://www.yourhome.ren/index.php/sec/366.html
参考:实验吧pcat师傅的writeup

‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧  END  ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧

关注我的微信公众号,回复“加群”按规则加入技术交流群。

欢迎关注我的视频号:


点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看。

浏览 25
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报