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

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

Padding oracle attack 与CBC翻转字节攻击

异或(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过去,就会得到下一步了..

Hash长度扩展攻击

1 byte=8 bit

hash算法

Merkle–Damgård构造

易受hash长度扩展攻击的算法有 SHA系列与MD系列,因为他们都是基于Merkle–Damgård构造。具体它是怎么结构,从下文的MD5算法可以感受到

MD5算法

分组

MD5算法里,会先设置好一个一个的分组,每个组的大小是512bit或者说是64bytes。

每个组都包含2个部分:
1.数据区,占56bytes来记录需要被加密的字符串数据,当数据无法填满数据区时,会进行”补位”操作(请看下文解释)
2.长度描述符区,用于记录“非补位”数据的大小,占8个byte,其值为该组数据区种非补位数据的bit大小的16进制值。
比如非补位数据是admin,其占5个byte,40个bit,40的十六进制是28,所以其值会是28。
然后这个值会以一种叫做小端存储的方式记录到长度描述符区。
(非补位数据指该组的非填充的数据,即真正需要被加密的字符串)

补位

很简单,若某个组的数据长度小于56byte,该组的数据区不会被占满,那么就会自动补位来使数据区被填满。

其规则是在数据后先添加一个80字节,然后再用00字节填充完整个数据区。

分组与补位小结

再更进一步的了解MD5算法前,需要更加深刻地理解一下分组与补位,不然稍后的理解会非常困难。

QQ截图20210217152432

加密流程

这里放张自己画的加密流程的图

QQ截图20210217152445

字符串先分组,然后第一组与初始链进行复杂数学运算得到链1.
注意,这里的初始链是固定的,每个MD5运算的初始链都是固定的,其值就是图中所记录的。也就是说,无论对什么数据进行MD5加密,其初始链都是

0x67452301
0xefcdab89
0x98badcfe
0x10325476

ok。第一组数据与初始链进行复杂运算得到链1,然后链1与第二组数据进行复杂运算得到链2,如此往复,直到倒数第二条链与最后一组数据进行复杂运算得到最后一条链(链final)

然后链final进行高低位转换就得到最终hash,那么什么是高低位转换?

如果final链是

  1. A=0x20f4847a
  2. B=0x42e6abf8
  3. C=0xf9097423
  4. D=0x51a8dad4

那么其hash便是 7a84f420f8abe642237409f9d4daa851

hash长度扩展攻击

基础知识掌握了,就开始了解这个重头戏了。

以一道题为切入点(改了一下实验吧的一道题)

Your cookies don't match up! STOP HACKING THIS SITE. <?php
include "flag.php";
//$secret="XXXXXXXXXXXXXXX"; This secret is 15 characters long for security!
$username="admin";
$password = $_POST["password"];
if($_POST["getmein"] === md5($secret . urldecode($username . $password))){
if(is_numeric(strpos($password,"abc"))){
echo "Congratulations! You are a registered user.\n";
die ("The flag is ". $flag);
}
}else{
echo("Your cookies don't match up! STOP HACKING THIS SITE.");
}
highlight_file(__FILE__);
echo(md5($secret . urldecode($username . "admin")));
?> 93a5e7bea9c040065617b1a62ffc3d72

从中我们可以得知secret长度是15,md5($sercet.”adminadmin”)=93a5e7bea9c040065617b1a62ffc3d72
题目的意思很明显,我们需要传两个参数getmein和password使得
getmein=md5($secret.”admin”.password),且password包含abc字符

那么这就是hash长度扩展攻击经典的使用典例,即:
知道salt长度(这里secret变量的长度)
知道一组被加密字符串长度小于56的样本( md5($sercet.”adminadmin”) 的值)
即可知道某个值与salt一起被MD5加密后的hash

那这种攻击是如何实现的呢?
首先我们知道了一组样本,即等于我们知道了该样本的final链(高低位变换)。
同时我们知道了salt长度,即等于我们可以构造出该分组。
那么如果我们又构造出一个新的分组,同时其上一个组是已知样本,那么与新分组进行复杂运算的就是已知样本的final链。
已知样本的final链,新分组的待加密字符串,即可通过复杂运算,高低位变换获得最终hash。
故新分组待加密字符串的hash值是可以预测的。
这,就是hash长度扩展攻击的原理。
可能还是云里雾里的,那就看如何解题吧。

因为已知salt长度,那么可以预测一下样本的分组的情况
因为salt+adminadmin的长度是25,那么其bit就是200,转换为16进制就是c8.
那么假设salt字符全为x(仅仅是个假设而已,别想太多了),该分组的情况

QQ截图20210217152508

那么我们可以传参
password=admin%80%00*30abc
其中abc以前的字符会在被MD5运算的时候分配到前一个组,然后通过运算获得链1,这个链1就是我们已知样本的final链了。
然后我们就可以预知,这个final链与abc进行复杂运算,高低位变换得到的hash了。然后再把这个hash赋值给openmein,这个题就做出来了。

好的,大致原理就是这样了。
这时候可以自行写脚本来找到某明文对应的MD5密文,或者使用工具:hashpump.

![

](https://const27blog.oss-cn-beijing.aliyuncs.com/img/QQ截图20210217152515.png)