[SUCTF 2018]HateIT CFB重放攻击~~

题目介绍

首先是扫目录得到.git源码泄露,我们用Git_Extract工具下载下来,有几个php文件和一个php的opcode,但是php文件都是经过so拓展加密,由于不会逆向,只能直接看着wp复现了,直接把师傅逆向好的代码拿来复现~~

index.php

<?php
    if(!isset($_SESSION))
    {
        session_start();
    }       
    include_once('func.php');    
    if(isset($_GET['username']))
    {
        $username = $_GET['username'];
        $md5 = md5(get_identify().$username);
        $admin = 0;
        $token = encrypt($username.'|'.$admin.'|'.$md5);
        $_SESSION['sign'] = $md5;
        $_SESSION['token'] = $token;
    }
    showImage();  
  	if(isset($_GET['token']) && isset($_GET['sign']))
    {
        $token = $_GET['token'];
        $sign = $_GET['sign'];        echo 'sign : '.$sign.'<br>';        echo 'token: '.$token.'<br>';
        $info = explode('|', decrypt($token));        echo decrypt($token);
        var_dump($info);        if(count($info) == 3)
        {            if(md5(get_identify().$info[0]) == $info[2])
            {
                $sign = $info[1];
                $admin = $info[1];	
            }
			else{
                $admin = $info[1];
            }
        }
        
    }
	else{        if(isset($_SESSION['token']) && isset($_SESSION['sign']))
        {            echo 'sign : '.$_SESSION['sign'].'<br>';            echo 'token: '.$_SESSION['token'].'<br>';
            $token = $_SESSION['token'];
            $sign = $_SESSION['sign'];
            $info = explode('|', decrypt($token));            if(count($info) == 3)
            {                if(md5(get_identify().$info[0]) == $info[2])
                {
                    $sign = $info[1];
                    $admin = $info[1];
                    
                }else{
                    $admin = $info[1];
                }                echo '<br>'.$admin;
            }
            
            
        }
    }    if(isset($admin) && $admin == 3)
    {
        $_SESSION['auth'] = 'admin';        echo "<a href='admin.php'>Admin</a>";
    }

func.php

<?php/**
 * Created by PhpStorm.
 * User: meizj
 * Date: 2018/2/2
 * Time: 下午9:25
 */include "class.php";
define("KEY","8690475385984657");
define("method","aes-128-cfb");
define("BS",16);
define("IDENTIFY","9850375038");
function get_token(){
    $token = '';    
	for($i=0;$i<16;$i++){
        $token .= chr(rand(1,255));
    }    return $token;
}
function enc($s){
    $token = get_token();
    $code1 = openssl_encrypt(string($s),method,key,OPENSSL_RAW_DATA,$token);
    $code2 = base64_encode(base64_encode($token."-".$code1));    return $code2;
}
function dec($s){    if($cc = base64_decode(base64_decode($s)))
    {        if($iv = substr($cc,0,16))
        {            if($d = substr($cc,17))
            {                if($s = openssl_decrypt($d, method, key, OPENSSL_RAW_DATA,$iv))
                {                    return $s;
                }                else
                    die("error");
            }            else
                return 0;
        }        else
            return 0;
    }    else
        return 0;
}
function uploadImage(){    if($_SESSION['auth'] !== "admin"){        die("Auth Failed");
    }
    $AllowedType = array(        "png",        "gif",        "jpg"
    );
    $filename = $_FILES['file']['name'];
    $filesize = $_FILES['file']['size'];    if($filesize > 1000000){        exit("Too large");
    }
    $fileext = substr($filename, strrpos($filename, '.')+1);    if(in_array($fileext,$AllowedType)){
        $file = "thumbs/images/".md5(time()."admin").".".$fileext;        if(file_exists($file)){            exit("File existed already");
        }else{
            move_uploaded_file($_FILES['file']['tmp_name'],$file);
        }
    }else{        exit("Not Allowed Ext");
    }
}
function viewImage($name){    if($_SESSION['auth'] !== "admin"){        die("Auth Failed");
    }    new ImageView($name);
}
function showImage(){
    $obj = new Home("thumbs/images/");
    $obj->showImg();

}function to($str) {    return $str . str_repeat(chr(BS - strlen($str) % BS), (BS - strlen($str) % BS));
}function re($str) {    return substr($str, 0, -ord(substr($str, -1, 1)));
}function getkey(){    return KEY;
}function get_identify(){    return IDENTIFY;
}function encrypt($str){
    $key = getkey();
    srand(time() / 300);
    $token = get_token();
    $cipher = bin2hex(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, to($str), MCRYPT_MODE_CFB, $token));    
	return base64_encode($cipher);
}
function decrypt($str){
    $decode = base64_decode($str);
    $key = getkey();
    srand(time() / 300);
    $token = get_token();
    $bin = hex2bin($str);
    $plain = re(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key,$bin , MCRYPT_MODE_CFB, $token));    return $plain;
}

我们可以看到,我们传入一个username参数就会得到一个sign和一个token,其中加密是使用了cfb加密,这种加密方法可以用重放攻击,后面再介绍这个

关键代码

$md5 = md5(get_identify().$username);
$admin = 0;
$token = encrypt($username.'|'.$admin.'|'.$md5);
$_SESSION['sign'] = $md5;
$_SESSION['token'] = $token;

加密的形式是$username.'|'.$admin.'|'.$md5其中$admin为0,根据代码我们知道,$admin必须为3,我们才能访问到admin.php
那么我们如何构造admin的值呢

cfb重放攻击

cfb的加密方式和cbc加密不同,当密文中某这个值改变后,这个值后面一个组不能解密,其它正常

我们举一个例子

from Crypto.Cipher import AES
from os import urandom
from Crypto.Util.strxor import strxor

class AES_CFB:
    def __init__(self):
        self.key = urandom(16)
        self.iv = urandom(16)

    def encrypt(self, plain):
        aes = AES.new(self.key, AES.MODE_CFB, self.iv)
        return aes.encrypt(plain)

    def decrypt(self, cipher):
        aes = AES.new(self.key, AES.MODE_CFB, self.iv)
        return aes.decrypt(cipher)

plain ='1'*32
aes = AES_CFB()
cipher = aes.encrypt(plain)
print aes.decrypt(cipher)

ct = cipher[:3]+strxor(strxor(cipher[3:9], '1'*6), '3'*6)+cipher[9:]
print aes.decrypt(ct)

我们原来的明文是'1'*32
我们的输出结果为
在这里插入图片描述可以看见,我们要更改的明文第一个值为3,剩下10个1没有改变
10=32-6-16(其中6代表我们更改的值,但是只有第一位我们能决定是什么值,16代表下一组密文被更改)

那么我们假设注册一个username为
xxxxxxxxxxxxxxx
那么我们我们的的明文则为
xxxxxxxxxxxxxxx|0|d30ef4a963f6cadc8a453977ccbbc626
我们观察到最后sign的长度刚好为32
我们先拓展一下token

$user|$admin|$md5

变为

$user|$admin|$md5$user|$admin|$md5

最后的值为
xxxxxxxxxxxxxxx|0|d30ef4a963f6cadc8a453977ccbbc626xxxxxxxxxxxxxxx|0|d30ef4a963f6cadc8a453977ccbbc626

由于index.php会校验info[0]info[2],所以我们必须保持xxxxxxxxxxxxxxxd30ef4a963f6cadc8a453977ccbbc626分别在第一部分和第三部分
这儿再利用一个php的弱类型我们知道在php中3aaaaaaaaa==3
而我们前面也说了,我们刚好能控制第一个值,所以我们将第一个值改为3就行了
我们就要决定改多长的长度,而改了后后面恰好又要留下一个分组
我们这儿贴出代码

from Crypto.Cipher import AES
from os import urandom
from Crypto.Util.strxor import strxor
import base64

plain = 'xxxxxxxxxxxxxxx|0|d30ef4a963f6cadc8a453977ccbbc626'  # 最原始的明文
token = 'ZjVhYWI3NjA3ZTZiOGM4Y2YyMWUxOTRiNzhiMzFmNzkwYjYyMjAzYjZlZTgxMDAwNTY0ZDMyNzlkYTFiODk3YmQ1MDFiYjk2NzkyYmJhMzgwYzQ4OTgzMTlhYWQ5Nzk0OGExOGEwM2VkZDU4NjIwYzdhMTIxNmQ1ODY2OWI3YzM='    #对应的密文
token = base64.b64decode(token)
cipher = token.decode("hex")
ct = cipher[:16]+strxor(strxor(cipher[16:50], '0'*34), '3'*34)+cipher			# 我们从第17个字符改,即0,改的长度为34,则后面的异或的长度也必须是34,而且第一个异或的值必须明文第十七的字符,第二个异或是我们想要更改的第一个值,由于admin要求是3,所以我们填3就行,最后我们+cipher是拓展我们的token,这样我们就更改了0|d30ef4a963f6cadc8a453977ccbbc626,下一组密文也是乱码即xxxxxxxxxxxxxxx|,这样剩下的一个0无关影响,因为我们只剩下两个||,刚好符合要求~~
ct =  "".join(ct).encode('hex')
print(base64.b64encode(ct))

这样传参sign,token就能是admin,然后就能访问admin.php了,剩下的比较简单,这儿就不做过多的说明了~~

非预期

这道题目还有一个非预期,由于源码中泄露了密匙,而且随机种子五分钟变一下,所以我们直接照这代码运行一下就行了~~

额外的东西

这儿再记录一个东西,以前不知道了,主要是自己太菜了,大佬请略过

srand(time() / 300)

这儿是给rand设定随机种子,只要设定了种子
rand(1,255)的顺序也是固定不变的,虽然有范围限制

而且windowslinux的随机种子的随机数顺序不一样,这个要注意~~

参考链接

官方wp
对字节反转攻击的深入研究
【技术分享】常见的Web密码学攻击方式汇总

发布了81 篇原创文章 · 获赞 10 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/a3320315/article/details/104365345