CBC 字节反转攻击
一、CBC 简介
现代密码体制
现代密码中的加密体制一般分为对称加密体制(Symmetric Key Encryption)和非对称加密体制(Asymmetric Key Encryption)。对称加密又分为分组加密和序列密码。
分组密码:也叫块加密(block cyphers),一次加密明文中的一个块。是将明文按一定的位长分组,明文组经过加密运算得到密文组,密文组经过解密运算(加密运算的逆运算),还原成明文组,有 ECB、CBC、CFB、OFB 四种工作模式。
序列密码:也叫流加密(stream cyphers),一次加密明文中的一个位。是指利用少量的密钥(制乱元素)通过某种复杂的运算(密码算法)产生大量的伪随机位流,用于对明文位流的加密。解密是指用同样的密钥和密码算法及与加密相同的伪随机位流,用以还原明文位流。
CBC 模式
CBC (Cipher Block Chaining, 密码分组链接) 模式中每一个分组要先和前一个分组加密后的数据进行XOR异或操作,然后再进行加密。这样每个密文块依赖该块之前的所有明文块,为了保持每条消息都具有唯一性,第一个数据块进行加密之前需要用初始化向量IV进行异或操作。CBC模式是一种最常用的加密模式,它主要缺点是加密是连续的,不能并行处理,并且与ECB一样消息块必须填充到块大小的整倍数。
CBC 模式的优缺点
CBC算法优点:
串行运算、相同明文不同密文。
CBC算法缺点:
需要初始向量、加密是连续的,不能并行处理。
二、CBC 工作模式
Encryption
特殊名词
Plaintext:明文,待加密的数据。
IV :初始向量,用于随机化加密的比特块,保证即使对相同明文多次加密,也可以得到不同的密文。
Key:对称密钥,由AES,Blowfish,DES,Triple DES等对称加密算法使用。
Ciphertext:密文数据。
固定分组:CBC在一个固定长度的位组上工作,称为块。这里使用每个16字节的块进行讲解。
Encryption process
1、文字流程
Main:上一组密文块用来产生下一组密文块。
1、首先将明文分组(常见的以16字节为一组),位数不足的使用特殊字符填充。
2、生成一个随机的初始化向量(IV)和一个密钥。
3、将IV和第一组明文异或产生初步密文,再用密钥对初步密文加密生成最终密文块。
4、用3中产生的密文块对第二组明文进行xor操作产生初步密文,再用密钥对初步密文加密生成最终密文块。
5、重复4,到最后一组明文。
6、将IV和加密后的每个密文块拼接在一起,得到最终的密文。
从第一块 Plaintext 开始,首先与一个初始向量iv异或(iv只在第一处起作用),然后把异或的结果经过key进行加密,得到第一块的密文,并且把加密的结果与下一块的明文进行异或,一直这样进行下去。
2、公式描述:
Ciphertext-0 = Encrypt(Plaintext XOR IV)—只用于第一个组块
Ciphertext-N = Encrypt(Plaintext XOR Ciphertext-(N-1))—用于第二及剩下的组块 # N > 1
Decryption:
Decryption process
1、文字流程
Main:上一组密文块影响下一组密文块的还原。
1、从密文中提取出IV,然后将密文分组。
2、使用密钥对第一组的密文解密,然后和IV进行xor得到明文。
3、使用密钥对第二组密文解密,然后和2中的密文xor得到明文。
4、重复2-3,直到最后一组密文。
解密和加密的原理是一样的,都是
2、公式描述:
Plaintext-0 = Decrypt(Ciphertext) XOR IV—只用于第一个组块
Plaintext-N = Decrypt(Ciphertext) XOR Ciphertext-(N-1)—用于第二及剩下的组块 # N > 1
三、CBC 攻击原理
Attack 原理
1、在 CBC 解密的公式中可以注意到Ciphertext-(N-1)用来产生下一块明文,这就是字节翻转攻击发挥作用的地方。如果我们改变Ciphertext-N-1中的一个字节,然后和下一块解密后的密文xor,就可以得到一个不同的明文,而这个明文是我们可以控制的。
2、在1中的基础上,通过破坏密文中的字节来改变明文中的字节,由此在破坏的密文中添加单引号等恶意字符来绕过过滤器,或通过将用户ID更改为admin来提升权限,或者更改应用程序所需的明文造成其他后果。
Attack process
通过修改第一组的密文块字节,来构造自己想要的第二组明文块,当第一组密文块字节发生改变时会影响第一组明文块和第二组明文块。
四、漏洞复现
漏洞源码 (漏洞复现以 CTF 为例)
<?php
include 'sqlwaf.php';
define("SECRET_KEY", "Dfa5cUiJb2Xquhgv");
define("METHOD", "aes-128-cbc");
session_start(); function get_random_iv(){
$iv='';
for($i=0;$i<16;$i++){
$iv.=chr(rand(1,255));
}
return $iv;
}
function login($info){
$iv=get_random_iv();
$plain = serialize($info);
$cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);
$_SESSION['username'] = $info['username'];
setcookie("iv", base64_encode($iv));
setcookie("cipher", base64_encode($cipher));
}
function show_homepage(){
if ($_SESSION["username"]==='admin'){
echo '<p>Hello admin</p>';
echo '<p>Flag is ***************************</p>';
}else{
echo '<p>hello '.$_SESSION['username'].'</p>';
echo '<p>Only admin can see flag</p>';
}
echo '<p><a href="loginout.php">Log out</a></p>';
die();
}
function check_login(){
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("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>");
$_SESSION['username'] = $info['username'];
}else{
die("ERROR!");
}
}
} if (isset($_POST['username'])&&isset($_POST['password'])) {
$username=waf((string)$_POST['username']);
$password=waf((string)$_POST['password']);
if($username === 'admin'){
exit('<p>You are not real admin!</p>');
}else{
$info = array('username'=>$username,'password'=>$password);
login($info);
show_homepage();
}
}
else{
if(isset($_SESSION["username"])){
check_login();
show_homepage();
}
}
?>
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>Paper login form</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="login">
<form action="" method="post">
<h1>Sign In</h1>
<input name='username' type="text" placeholder="Username">
<input name='password' type="password" placeholder="Password">
<button>Sign in</button>
</div>
</body>
</html>
waf 源码
<?php
function waf($str){
$array=array("'","\""," ","or","and","(",")","<","?");
for ($i=0; $i < sizeof($array); $i++) {
if(strpos($str,$array[$i])){
echo "<script>alert('too young too simple,do not hack')</script>";
die();
}
}
return $str;
}
?>
题目Hint:Only admin can see flag!
初始页面显示
通过测试发现存在 Injection Bypass
测试
username:
admin' or '1'='1
password:
Random:**********
Result
测试发现存在注入绕过,通过扫描网站意外发现存在网页Bak文件
将Bak文件Down下来,进行代码审计
<?php
include 'sqlwaf.php';
define("SECRET_KEY", "Dfa5cUiJb2Xquhgv"); //密钥key
define("METHOD", "aes-128-cbc"); //使用AES算法128bit固定分组
session_start(); function get_random_iv(){ //初始化向量IV
$iv='';
for($i=0;$i<16;$i++){ //随机生成16字节长度的IV
$iv.=chr(rand(1,255));
}
return $iv;
}
function login($info){
$iv=get_random_iv(); //获取经过初始化的向量IV
$plain = serialize($info); //将用户提交的信息进行序列化
$cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv); //将变量$plain的值进行加密
$_SESSION['username'] = $info['username']; //获取用户提交的用户名
setcookie("iv", base64_encode($iv)); //设置cookie:iv、cipher并将其值进行base64编码
setcookie("cipher", base64_encode($cipher));
}
function show_homepage(){ //判断网页提交的用户是否是"admin"
if ($_SESSION["username"]==='admin'){ //只有admin用户才能查看Flag
echo '<p>Hello admin</p>';
echo '<p>Flag is *******************************</p>';
}else{
echo '<p>hello '.$_SESSION['username'].'</p>'; //如果不是admin用户,网页则会显示"hello <username>"
echo '<p>Only admin can see flag</p>'; //查询失败
}
echo '<p><a href="loginout.php">Log out</a></p>';
die();
}
function check_login(){ //检查cookie:iv、cipher并将其值进行base64解码
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)){ //将cipher进行解密
$info = unserialize($plain) or die("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>"); //如果对变量$plain的值反序列化失败则会退出整个程序的执行
$_SESSION['username'] = $info['username']; ////获取用户名
}else{
die("ERROR!");
}
}
} if (isset($_POST['username'])&&isset($_POST['password'])) { //判断用户的输入
$username=waf((string)$_POST['username']); //对用户名进行安全检测
$password=waf((string)$_POST['password']); //对用户密码进行安全检测
if($username === 'admin'){ //判断网页提交的用户是否是真实的"admin"
exit('<p>You are not real admin!</p>');
}else{
$info = array('username'=>$username,'password'=>$password);
login($info);
show_homepage();
}
}
else{
if(isset($_SESSION["username"])){
check_login();
show_homepage();
}
}
?>
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>Paper login form</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="login">
<form action="" method="post">
<h1>Sign In</h1>
<input name='username' type="text" placeholder="Username">
<input name='password' type="password" placeholder="Password">
<button>Sign in</button>
</div>
</body>
</html>
代码审计发现用户只能够使用admin进行查询Flag,但是代码会检测出你不是真实的admin,所以需要利用网页代理对抓取的网页数据包进行两次不同的利用。
第一次数据包的利用
由于刚开始用户的输入不能是admin所以提交用户名为"ddmin",但是只有是admin用户才能查看Flag,所以只能利用代码上的提示:define("METHOD", "aes-128-cbc"); 利用 CBC 字节反转攻击构造admin。
分析构造admin,明文分组16字节一组
原明文
a:2:{s:8:"username";s:5:"ddmin";s:8:"password";s:5:"12345"} 明文分组
第一组:a:2:{s:8:"userna
第二组:me";s:5:"ddmin";
第三组:s:8:"password";s
第四组::5:"12345";}
依据上述分组,通过修改第一组明文对应密文中的第10个字节来间接性修改第二组密文解密产生的明文,以此将"ddmin"修改为"admin"
python 脚本~1
import base64
import urllib.parse cipher = base64.b64decode(urllib.parse.unquote('mqyAyGTAv4dfyqwuz0mIu7HOBhqf9xStbNQgj4XKnnlIRBEc68i%2BV8hTS6IvQxYsEjzMBpMJO1s%2BFFmbw8jYxw%3D%3D')) //这里放burp放回的base64的cipher数据
x = cipher[0:9]+bytes([ord(chr(cipher[9]))^ord('d')^ord('a')])+cipher[10:]
x = urllib.parse.quote(base64.b64encode(x))
print(x)
php 脚本~1
<?php
header("Content-Type: text/html;charset=utf-8");
$cipher = base64_decode(urldecode('mqyAyGTAv4dfyqwuz0mIu7HOBhqf9xStbNQgj4XKnnlIRBEc68i%2BV8hTS6IvQxYsEjzMBpMJO1s%2BFFmbw8jYxw%3D%3D')); //这里放burp放回的base64的cipher数据
$temp = $cipher;
$cipher[9] = chr(ord($cipher[9]) ^ ord('d') ^ ord('a'));
echo urlencode(base64_encode($cipher));
?>
运行结果
mqyAyGTAv4dfz6wuz0mIu7HOBhqf9xStbNQgj4XKnnlIRBEc68i%2BV8hTS6IvQxYsEjzMBpMJO1s%2BFFmbw8jYxw%3D%3D
第二次数据包的利用
将username和password提交的数据清空,并且将之前的 iv 和 修改过的cipher 添加到cookie字段中。 此处利用的主要代码如下
对于发送的请求响应中看到对于密文解密出的明文反序列化失败,为了知道是怎么回事,我们将显示出来的base64代码进行解码。
从图中发现第一组的明文出现了问题,用户已经从"ddmin"成功修改为"admin",由于"第一次数据包利用"中修改了第一组密文,所以第一组和第二组的明文都会受到影响。要想第一组明文恢复正常,就要在"第一次数据包利用"中的基础上修改IV使之产生正确的明文。
python 脚本~2
# -*- coding:utf8 -*-
import base64
import urllib.parse cipher = base64.b64decode('+g9Uzo1waJpEYbdiV+DYOm1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjU6IjEyMzQ1Ijt9') //这里放burp放回的base64数据
iv = base64.b64decode(urllib.parse.unquote('tWgzNB61rHp%2BzFZCLB6KQA%3D%3D')) //这里放cookie中的iv
newiv = ''
right = 'a:2:{s:8:"userna'
for i in range(16):
newiv += chr(ord(right[i]) ^ ord(chr(iv[i])) ^ ord(chr(cipher[i]))) //产生新的向量IV newiv = newiv.encode(encoding="utf8")
print(urllib.parse.quote(base64.b64encode(newiv)))
php 脚本~2
<?php
#计算iv
$res = base64_decode('+g9Uzo1waJpEYbdiV+DYOm1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjU6IjEyMzQ1Ijt9'); //这里放burp放回的base64数据
$iv = base64_decode(urldecode('tWgzNB61rHp%2BzFZCLB6KQA%3D%3D')); //这里放cookie中的iv
$plaintext = 'a:2:{s:8:"userna';
$new_iv = '';
for ($i = 0; $i < 16; $i ++){
$new_iv = $new_iv . chr(ord($iv[$i]) ^ ord($res[$i]) ^ ord($plaintext[$i])); //产生新的向量IV
}
echo urlencode(base64_encode($new_iv));
?>
运行结果
Ll1VwOi2%2FtgAj5RTHow8Gw%3D%3D
重新发送第二次请求
get flag
Flag is CBC{CBC is a good thing}
End,CBC 字节反转攻击的原理和知识以及利用过程已经讲述完结,有疑惑的朋友,欢迎大家相互交流。
CBC 字节反转攻击的更多相关文章
- 实验吧之【简单的登录题(】CBC字节反转攻击)
开始刷ctf题吧 慢慢来. 实验吧---简单的登录题 题目地址:http://ctf5.shiyanbar.com/web/jiandan/index.php 随便提交一个id,看到后台set了两个 ...
- Padding Oracle 和 CBC字节翻转攻击学习
以前一直没时间来好好研究下这两种攻击方式,虽然都是很老的点了= =! 0x01:Padding oracle CBC加密模式为分组加密,初始时有初始向量,密钥,以及明文,明文与初始向量异或以后得到中间 ...
- session安全&&CBC字符反转攻击&&hash拓展攻击
session安全 p神写的: 在传统PHP开发中,$_SESSION变量的内容默认会被保存在服务端的一个文件中,通过一个叫"PHPSESSID"的Cookie来区分用户.这类se ...
- CBC字节翻转攻击
iscc2018线上赛开始两周多了,学到了很多,写几篇文章总结一下遇到的知识点,做一个归纳,方便以后查找. web300-----CBC字节翻转攻击 cbc是AES加密的cbc模式 即密码分组链模式: ...
- 关于AES-CBC模式字节翻转攻击(python3)
# coding:utf-8 from Crypto.Cipher import AES import base64 def encrypt(iv, plaintext): if len(plaint ...
- ISCC2018(web)
ISCC2018 web writeup (部分) #web1:比较数字大小 只要比服务器上的数字大就好了 限制了输入长度,更改长度就好 #web2: 普通的代码审计,数组绕过 #web3:本地的诱惑 ...
- ISCC 2018——write up
WEB Web1-比较数字大小 直接修改input 标签里的maxlength 为999突破长度限制,使得能输入大于999 的数,然后随便输一个数字就行了 或者post修改值 Web2-Web01 s ...
- 百道CTF刷题记录(一)
简介 最近在刷CTF题,主攻Web,兼职Misc Shiyanbar 0x01 简单的登陆题 简单概括: 考点: %00截断正则 CBC字节翻转攻击 难度: 难 WP:https://blog.csd ...
- BUGKUctf-web-writeup
---恢复内容开始--- 找到了个ctf平台.里面的web挺多的.终于将web题目写的差不多了. Web 签到题 加群就可以了 Web2 直接F12就看到了 文件上传测试 Burp抓包 文件名改成 1 ...
随机推荐
- vs2015 key
vs2015 企业版 专业版 密钥 亲测可用 专业版:HMGNV-WCYXV-X7G9W-YCX63-B98R2企业版:HM6NR-QXX7C-DFW2Y-8B82K-WTYJV
- java基础语法3
逻辑运算符 &:与,和有共同的,必须条件都满足才是true 有false就返回false,必须都是true才返回true |:或者,其中有一个满足条件就返回true ^亦或,相同是false, ...
- 爬虫之ssh证书警告错误
错误信息: 错误信息如下: requests.exceptions.SSLError: ("bad handshake: Error([('SSL routines', 'tls_proce ...
- Oracle12c中分区(Partition)新特性之TRUNCATEPARTITION和EXCHANGE PARTITION级联功能
TRUNCATE [SUB]PARTITION和EXCHANGE [SUB]PARTITION命令如今可以包括CASCADE子句,从而允许参照分区表向下级联这些操作.为确保该选项正常,相关外键也必须包 ...
- 向Oracle数据库插入中文乱码解决方法
解决方法: 第一步:sqlplus下执行:select userenv('language') from dual;//查看oracle字符集 注:如果oracle字符集与后台代码设置的 ...
- 【转】mysql 中int类型字段unsigned和signed的区别
转自https://www.cnblogs.com/wangzhongqiu/p/6424827.html 用法: mysql> CREATE TABLE t ( a INT UNSIGNED, ...
- 查找linux设备的uuid
[root@ ~]# blkid /dev/vdc /dev/vdc: UUID="bxxxx-xxx-41b9-8146-7da8bd645b92" TYPE="ext ...
- GitHub 系列之「Git 进阶」
1.用户名和邮箱 我们知道我们进行的每一次 commit 都会产生一条 log,这条 log 标记了提交人的姓名与邮箱,以便其他人方便的查看与联系提交人,所以我们在进行提交代码的第一步就是要设置自己的 ...
- 图片和base64相互转化
# -*- coding: utf-8 -*- import urllib2 as ulb import base64 #用urllib2库链接网络图像 response=ulb.Request('h ...
- 自动化测试基础二(Python基础)
1.为什么学习Python 1)简单.易学 2)强大:交互性.解释性.编译性.跨平台 3)市场需求上升快.顺应市场需要 4)自动化测试需要使用编程语言来写脚本 2.需要学习Python哪些内容? 1) ...