参考: https://www.freebuf.com/articles/database/151167.html
https://www.jianshu.com/p/7f171477a603

因为是涉及密码学的东西,所以看的我头大,记录一下吧。

异或(XOR)

何为异或?
异或是一种运算方法,简要概括就是同假异真

true xor true = false; true xor false = true;

在二进制中:
011 xor 110 = 101; 二进制数每位数分别进行xor运算
上面算式的运算步骤是这样的 0 xor 1 = 1;1 xor 1 = 0;1 xor 0=1
然后把运算结果写在一起就是101了

在更高的进制中:
以十进制为例: 65 xor 42 = 107; 原理就是把十进制数字先变为二进制数进行异或,再将异或得到的二进制数结果变为十进制数

在字符中:
对字符进行异或运算其实就是对字符的ascii码进行异或计算,计算得到的结果视作新的一个ascii码再将其转换为字符。

xor还有一个性质,就是
已知 a xor b = c 那么
b xor c =a; a xor c = b.即满足异或运算里只需知道任意两个数就能得到另一个数。

CBC模式

CBC是一种加密模型,采用的是分组链接模式。把明文分为一组一组进行加密

Padding oracle attack详细解析

上图是CBC加密流程。最开始一个特别分组IV去和第一段密文XOR,得到的结果被密钥加密,加密得到该组的密文,同时这个密文会充当最开始的特别分组IV的作用去参与下一组的加密,以此类推。

CBC的每一个分组的加密结果都可以影响到下一个分组的加密结果, 使原本独立的分组密码加密过程形成迭代 , 这可以强化加密算法的”敏感性”,即实现所谓的”雪崩效应”,在香浓理论中这就是”扰乱原则” 。

CBC只是一种模式,它经常把aes或des作为加密使用的算法。 DES分组长度是八字节而AES分组长度是十六字节 。

接下来是CBC解密

Padding oracle attack详细解析

顺序依旧是从左到右,第一组密文被KEY解密后与IV xor得到第一组明文,同时第一组密文参与下一组的解密充当IV作用。

PKCS #5

竟然CBC模式涉及到分组,那么就会出现最后一组字节没有被占满的情况。
比如原本一个分组是8字节,我们有15个字节的明文需要被加密,此时最后一个分组就不会被占满(还差一个字节占满),那么这个时候要怎么办呢?

这时候就需要对最后一个分组进行填充,将其填充满。
对于采用des算法加密的内容,填充规则是PKC #5,而AES是 PKC #7.
这两者唯一区别是 PKCS #5填充是八字节分组而PKCS #7是十六字节 ,还记得上面我们说过的 DES分组长度是八字节而AES分组长度是十六字节 吗?就是这个分组字节数影响了填充方式。

那么具体是怎么填充的呢,我们以PKC #5为例

Padding oracle attack详细解析

当最后一组还剩n个字节未被填充时,就会填充n个 0xn字符上去.
上图是PKCS #5,其实PKCS #7和PKCS #5原理是一样的,不过是分组字节数大了点罢了(0~16)

Padding oracle attack

上面只是铺垫,建议学懂了再来看这个攻击方式。不然会非常头大。

Padding oracle attack 攻击场景举例

首先我们假设一个场景,从而引出这个攻击。

假设我们有一个任意文件包含场景

url?file=/etc/passwd

黑客们看见了就会很轻而易举的去包含想要的文件。管理者发现了这个问题,对file参数采用了CBC加密,即当提交请求时,file参数的值是被加密的,然后服务器用算法解密得到其想包含的文件,然后返回给客户端。

url?file=e28b2e3c972edab8 其中前8位数是IV,后八位数是密文。(这里是我瞎写的密文= =,你只需要理解到这里是一个CBC加密后的密文就行了)

那么如何去实现我们的任意文件包含呢? padding oracle attack 出现了。

攻击原理

假设我们向刚刚那个任意文件包含的提交了一段密文。服务器就会尝试解密,就会出现三种结果。

1.密文不能正常解密,这种原因是在于最后一组的填充字节出现了错误
2.密文能正常解密但解密出来的文件路径不存在
3.密文能正常解密且能成功包含

其中第1种情况和2.3种情况网页返回的内容肯定是不同的。
比如说第一种情况可能就直接返回500了,2.3可能就是302跳转啥啥的,通过这个网页返回的信息,我们就有了可乘之机。

我们先通过一个图感受一下第一组的解密流程

Padding oracle attack详细解析

好的好的,感受了这个解密流程后,我们来说说攻击的事。
如果我们得到了 Intermediary Value(中间值),并且可以手动修改IV,那么我们岂不是可以构造任意Decrypted Value(明文)了?
所以padding oracle attack 的核心就是去获得中间值。
那么我们怎么去获取呢?

还记得刚刚提到的3种情况吗?我们可以通过修改IV,通过判断网页返回内容来判断中间值,具体做法如下:

我们先把IV全部设置为0x00,然后修改IV的最后一个数,当其与中间值XOR后的值为0x01则此时解密就会成功,若不是0x01解密就会失败,网页会返回不同的内容,以此来判断何时解密成功。然后把解密成功时的IV的最后一位数与0x01进行异或计算,即可得到中间值的最后一位

Padding oracle attack详细解析

然后我们把IV最后一位数设置为能和中间值最后一位数异或后值为0x02的数,穷举IV倒数第二个数看看哪个数能和中间值倒数第二个数异或运算后值为0x02,然后我们就可得到中间值倒数第二个数,以此类推可以获得第一组的中间值(有点绕)
然后破解到了中间值我们再用最开始的IV(不是我们后面构造的IV)去和中间值异或就得到明文了
当然,你也可以再次构造IV,从而构造解密出来的字符(通过中间值与IV异或)

[NPUCTF2020]web🐕中的Padding oracle Attack

我们以这道题为切入点,详细看看如何实现攻击。
本题就是以下代码,我们要想方设法先拿到$flag

<?php 
error_reporting(0);
include('config.php'); # $key,$flag
define("METHOD", "aes-128-cbc"); //定义加密方式
define("SECRET_KEY", $key); //定义密钥
define("IV","6666666666666666"); //定义初始向量 16个6
define("BR",'<br>');
if(!isset($_GET['source']))header('location:./index.php?source=1');


#var_dump($GLOBALS); //听说你想看这个?
function aes_encrypt($iv,$data)
{
echo "--------encrypt---------".BR;
echo 'IV:'.$iv.BR;
return base64_encode(openssl_encrypt($data, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)).BR;
}
function aes_decrypt($iv,$data)
{
return openssl_decrypt(base64_decode($data),METHOD,SECRET_KEY,OPENSSL_RAW_DATA,$iv) or die('False'); //只能返回1或false
}
if($_GET['method']=='encrypt')
{
$iv = IV;
$data = $flag;
echo aes_encrypt($iv,$data);
} else if($_GET['method']=="decrypt")
{
$iv = @$_POST['iv'];
$data = @$_POST['data'];
echo aes_decrypt($iv,$data);
}
echo "我摊牌了,就是懒得写前端".BR;

if($_GET['source']==1)highlight_file(__FILE__);
?>

我们先拿到密文 ly7auKVQCZWum/W/4osuPA==
然后对其进行base64解密,发现其刚好是有16个字节,那么我们可以直接穷举IV得到中间值,然后凭此与初始IV(16个6)进行异或得到明文$flag.

话不多说,爆破中间值的脚本安排上。(借鉴了一个大佬的WP)

import requests
import base64
import time
Intermediary=""
url="http://de1650aa-2b24-40e0-bb51-736ff5d38269.node3.buuoj.cn//index.php?source=1&method=decrypt"
iv=""
hexs=""
IV="6666666666666666"
def xor(a,b):
return "".join([chr(ord(a[i])^ord(b[i])) for i in range(len(a))])

for step in range(1,17):
padding=chr(step)*(step-1)
print("第%s轮"%step)
for i in range(0,256):
iv=chr(0)*(16-step)+chr(i)+xor(Intermediary,padding)
post={
"iv":iv,
"data":"ly7auKVQCZWum/W/4osuPA=="
}
r=requests.post(url=url,data=post,proxies={"http":"http://127.0.0.1:8080"})
time.sleep(0.1)
print(r.text+"第%s轮i=%s "%(step,i))
if "False" != r.text:
Intermediary=xor(chr(i),chr(step))+Intermediary
print(Intermediary)
break
for k in range(len(Intermediary)):
hexs="%"+str(ord(Intermediary[k]))+hexs
print(hexs)
print(xor(Intermediary,IV))

爆出了中间值和$flag明文

QQ截图20210217152650

这里有些疑惑,为啥中间值会是15位的…
发现$flag不是最终flag,至于接下来要做的东西,就是CBC字节翻转攻击了

CBC翻转字节攻击

在对CBC模式加密的数据进行解密时,若iv可控,则可以任意控制解密后的内容。
CBC翻转字节攻击不同于padding oracle attack,后者的核心是IV可控情况下获取中间值,从而可以获得明文或者任意控制密文解密后的数据。
而CBC翻转字节攻击的核心思想就不是获取中间值了,而是在IV可控的情况下,通过算法缺陷来直接控制密文解密后的数据。

现在假定有中间值A,明文B1,IV C1
那么就有

A^B1=c1

一点错误都没有对吧。
现在又假定有中间值A,我们想要解密出的明文B2和与之对应的IV C2
那么就有

A^B2=C2
结合以上两个式子,有
A=B1^C1=B2^C2
于是有
B2=B1^C1^C2
或C2=B1^B2^C1
若我们已知B1,C1,且C2可控,那么B2即可控
既满足刚刚我们说的,通过算法缺陷来直接控制密文解密后的数据。

这,就是CBC翻转字节攻击,没太懂没事,看例子就行了

[NPUCTF2020]web🐕中的CBC翻转字节攻击

跟进我们刚刚讲的,FlagIsHere.php,核心代码如下

X5uucFgPTVdCo9f3ZHGP8g==
<?php
#error_reporting(0);
include('config.php'); //$fl4g
define("METHOD", "aes-128-cbc");
define("SECRET_KEY", "6666666");
session_start();

function get_iv(){ //生成随机初始向量IV
$random_iv='';
for($i=0;$i<16;$i++){
$random_iv.=chr(rand(1,255));
}
return $random_iv;
}

$lalala = 'piapiapiapia';

if(!isset($_SESSION['Identity'])){
$_SESSION['iv'] = get_iv();

$_SESSION['Identity'] = base64_encode(openssl_encrypt($lalala, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $_SESSION['iv']));
}
echo base64_encode($_SESSION['iv'])."<br>";

if(isset($_POST['iv'])){
$tmp_id = openssl_decrypt(base64_decode($_SESSION['Identity']), METHOD, SECRET_KEY, OPENSSL_RAW_DATA, base64_decode($_POST['iv']));
echo $tmp_id."<br>";
if($tmp_id ==='weber')die($fl4g);
}

highlight_file(__FILE__);
?>

此时我们已知初始IV,初始密文,且IV可控,那么CBC翻转字节攻击条件成立,可以攻击。

这里贴一个别人写的python2 CBC字节反转攻击脚本
自己拿python3写了半天都没写出很好的效果...
import base64
def bxor(b1, b2): # use xor for bytes
parts = []
for b1, b2 in zip(b1, b2):
parts.append(bytes([b1 ^ b2]))
return b''.join(parts)
iv = base64.b64decode("h34HL5RbMPw8oTaQ+P58nw==")
text = b"piapiapiapia\x04\x04\x04\x04"
result = b"weber\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
middle = bxor(iv,text)
iv = bxor(middle,result)
print(base64.b64encode(iv))

把跑出来的结果POST过去,就会得到下一步了..