“百度杯”CTF比赛 九月场--Code

题目:
在这里插入图片描述读取index.php的文件得到一段代码
在这里插入图片描述base64解密得到一段php代码

<?php
/**
 * Created by PhpStorm.
 * Date: 2015/11/16
 * Time: 1:31
 */
header('content-type:text/html;charset=utf-8');
if(! isset($_GET['jpg']))
    header('Refresh:0;url=./index.php?jpg=hei.jpg');
$file = $_GET['jpg'];
echo '<title>file:'.$file.'</title>';
$file = preg_replace("/[^a-zA-Z0-9.]+/","", $file);
$file = str_replace("config","_", $file);
$txt = base64_encode(file_get_contents($file));

echo "<img src='data:image/gif;base64,".$txt."'></img>";

/*
 * Can you find the flag file?
 *
 */

?>

可以看出这个php文件是由PhpStorm创建的,而PhpStorm创建的项目下默认存在一个.idea文件夹用来放一些配置文件
在这里插入图片描述
在这里插入图片描述
访问一下.idea,它提醒我没有权限访问,说明存在该文件夹
在这里插入图片描述访问下里面的workspace.xml文件查看
在这里插入图片描述发现存在fl3g_ichuqiu.php
在这里插入图片描述
访问下fl3g_ichuqiu.php,好像不存在什么有价值的东西
在这里插入图片描述利用之前的图片查看下fl3g_ichuqiu.php的源代码
在这里插入图片描述这一次它并没有返回代码,但是之前用index.php是可以得到返回结果的,说明可能存在过滤条件
回头看一下我们得到的index.php的源代码

<?php
/**
 * Created by PhpStorm.
 * Date: 2015/11/16
 * Time: 1:31
 */
header('content-type:text/html;charset=utf-8');
if(! isset($_GET['jpg']))
    header('Refresh:0;url=./index.php?jpg=hei.jpg');
$file = $_GET['jpg'];
echo '<title>file:'.$file.'</title>';
$file = preg_replace("/[^a-zA-Z0-9.]+/","", $file);
$file = str_replace("config","_", $file);       
$txt = base64_encode(file_get_contents($file));

echo "<img src='data:image/gif;base64,".$txt."'></img>";

/*
 * Can you find the flag file?
 *
 */

?>

我们看下这行代码

$file = preg_replace("/[^a-zA-Z0-9.]+/","", $file);

正则表达式
^在[]外表示以什么开头
^在[]里表示取反
.表示除换行符(\n、\r)之外的任何单个字符
在这里插入图片描述所以这里我们的_被替换成空了,没有返回我们想要的结果

$file = str_replace("config","_", $file);  

代码里还有一行,把config替换成_
所以我们构造url为jpg=fl3gconfigichuqiu.php去访问
在这里插入图片描述老套路,拿去base64解密得到fl3g_ichuqiu.php的源代码

<?php
/**
 * Created by PhpStorm.
 * Date: 2015/11/16
 * Time: 1:31
 */
error_reporting(E_ALL || ~E_NOTICE);
include('config.php');
function random($length, $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz') {
    $hash = '';
    $max = strlen($chars) - 1;
    for($i = 0; $i < $length; $i++)	{
        $hash .= $chars[mt_rand(0, $max)];
    }
    return $hash;
}

function encrypt($txt,$key){
    for($i=0;$i<strlen($txt);$i++){
        $tmp .= chr(ord($txt[$i])+10);
    }
    $txt = $tmp;
    $rnd=random(4);
    $key=md5($rnd.$key);
    $s=0;
    for($i=0;$i<strlen($txt);$i++){
        if($s == 32) $s = 0;
        $ttmp .= $txt[$i] ^ $key[++$s];
    }
    return base64_encode($rnd.$ttmp);
}
function decrypt($txt,$key){
    $txt=base64_decode($txt);
    $rnd = substr($txt,0,4);
    $txt = substr($txt,4);
    $key=md5($rnd.$key);

    $s=0;
    for($i=0;$i<strlen($txt);$i++){
        if($s == 32) $s = 0;
        $tmp .= $txt[$i]^$key[++$s];
    }
    for($i=0;$i<strlen($tmp);$i++){
        $tmp1 .= chr(ord($tmp[$i])-10);
    }
    return $tmp1;
}
$username = decrypt($_COOKIE['user'],$key);
if ($username == 'system'){
    echo $flag;
}else{
    setcookie('user',encrypt('guest',$key));
    echo "╮(╯▽╰)╭";
}
?>

开始代码审计(个人认为是这题的难点)
主程序
只要cookie里的user的值解密后为system,就会显示flag
如果不对就重设user的值,并显示╮(╯▽╰)╭
encrypt函数

function encrypt($txt,$key){
	#step1:把guest的ascii码逐个加10
    for($i=0;$i<strlen($txt);$i++){
        $tmp .= chr(ord($txt[$i])+10);
    }
    $txt = $tmp;
    #step2:从大小写字母和数字里随机生成4位
    $rnd=random(4);
    #step3:拼接rnd和key,md5后得到新key
    $key=md5($rnd.$key);
    #step4:逐位异或,特别注意,这里的$key[++$s]
    #也就是txt的第0位对应key的第1位进行异或
    $s=0;
    for($i=0;$i<strlen($txt);$i++){
        if($s == 32) $s = 0;
        $ttmp .= $txt[$i] ^ $key[++$s];
    }
    #step5:拼接rnd和异或后的结果,进行base64加密
    return base64_encode($rnd.$ttmp);
}

decrypt函数
其实就是encrypt的逆过程

function decrypt($txt,$key){
    $txt=base64_decode($txt);
    $rnd = substr($txt,0,4);
    $txt = substr($txt,4);
    $key=md5($rnd.$key);

    $s=0;
    for($i=0;$i<strlen($txt);$i++){
        if($s == 32) $s = 0;
        $tmp .= $txt[$i]^$key[++$s];
    }
    for($i=0;$i<strlen($tmp);$i++){
        $tmp1 .= chr(ord($tmp[$i])-10);
    }
    return $tmp1;
}

审计时的疑惑

$key=md5($rnd.$key);

新key是rnd和传参的key拼接后md5得到的,这个传进来的老key,我们怎么也不知道
先看下面这个代码,我们假设原来的key就是1234567890

<?php

function random($length, $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz') {
    $hash = '';
    $max = strlen($chars) - 1;
    for($i = 0; $i < $length; $i++)	{
        $hash .= $chars[mt_rand(0, $max)];
    }
    return $hash;
}

function encrypt($txt,$key){
    echo '原来的txt值为<br>'.$txt;
    echo '<br>';
    echo '原来的key值为<br>'.$key;
    echo '<br>';

    for($i=0;$i<strlen($txt);$i++){
        $tmp .= chr(ord($txt[$i])+10);
    }

    $txt = $tmp;
    echo 'txt的ascii加10后变为<br>'.$txt;
    echo '<br>';
    $rnd=random(4);
    echo '随机生成的rand为<br>'.$rnd;
    echo '<br>';

    $key=md5($rnd.$key);
    echo '新key值为<br>'.$key;
    echo '<br>';

    $s=0;
    for($i=0;$i<strlen($txt);$i++){
        if($s == 32) $s = 0;
        $ttmp .= $txt[$i] ^ $key[++$s];
    }
    echo 'txt和新key异或值为ttmp<br>'.$ttmp;
    echo '<br>';
    echo '对rnd + ttmp进行base64加密<br>'. base64_encode($rnd.$ttmp);
    echo '<br>';
    return base64_encode($rnd.$ttmp);
}
function decrypt($txt,$key){
    echo '原来的txt值为<br>'.$txt;
    echo '<br>';
    echo '原来的key值为<br>'.$key;
    echo '<br>';
    $txt=base64_decode($txt);
    echo 'base64解码后的txt值为<br>'.$txt;
    echo '<br>';
    $rnd = substr($txt,0,4);
    echo 'rnd值为<br>'.$rnd;
    echo '<br>';
    $txt = substr($txt,4);
    echo '新的txt值为<br>'.$txt;
    echo '<br>';
    $key=md5($rnd.$key);
    echo '新key值为<br>'.$key;
    echo '<br>';
    $s=0;
    for($i=0;$i<strlen($txt);$i++){
        if($s == 32) $s = 0;
        $tmp .= $txt[$i]^$key[++$s];
    }
    echo 'txt和新key异或值为tmp<br>'.$tmp;
    echo '<br>';

    for($i=0;$i<strlen($tmp);$i++){
        $tmp1 .= chr(ord($tmp[$i])-10);
    }
    echo 'tmp的ascii加10后变为<br>'.$tmp1;
    echo '<br>';
    return $tmp1;
}


$key = '1234567890';
$en = encrypt('guest', $key);
echo "╮(╯▽╰)╭<br>";
decrypt($en,$key);

运行结果如下
在这里插入图片描述
在这里插入图片描述

加密解密用的新key是一样的,我们不需要知道老key是什么样的
根据encrypt的函数的代码,我们逆向分析,可以解出新key的第2-6位(前面说过了,因为$key[++$s]
用python写个函数

def decrypt(rnd_ttmp):

    rnd_ttmp = base64.b64decode(rnd_ttmp)
    ttmp = rnd_ttmp[4:]

    txt = 'guest'

    key = ''
    for i in range(len(txt)):
        key += chr(ttmp[i] ^ (ord(txt[i])+10))

    return key

可以先试试这个函数,访问fl3g_ichuqiu.php,让它给我们的cookie设置一个user
在这里插入图片描述在这里插入图片描述
得出新key为 X4e84eXXXXXXXXXXXXXXXXX
X 表示未知,不是大写的x
下面的思路就是对system进行加密
因为比较长,也不想让你们网上再翻了,这里再贴一下

function encrypt($txt,$key){
    for($i=0;$i<strlen($txt);$i++){
        $tmp .= chr(ord($txt[$i])+10);
    }
    $txt = $tmp;
    $rnd=random(4);
    $key=md5($rnd.$key);
    $s=0;
    for($i=0;$i<strlen($txt);$i++){
        if($s == 32) $s = 0;
        $ttmp .= $txt[$i] ^ $key[++$s];
    }
    return base64_encode($rnd.$ttmp);
}

想要对system进行加密,我们需要知道新key的第7位,以及rnd
正向的看,rnd是随机生成的,我们是获取不到的,但是
反向的看,cookie里的user的值base64解密后就是rnd拼接ttmp
python脚本里我们逆向得出了ttmp,那当然也可以得到rnd
改进的代码
在这里插入图片描述
接下来爆破新key的第7位,因为新key是md5后的值,所以只能是abcdef0123456789中的一个

rnd,key = decrypt('V2M1bUUaV0kb')
seven = 'abcdef0123456789'

#为system加密做准备start
txt = 'system'
tmp = ''
for i in range(len(txt)):
    tmp += chr(ord(txt[i])+10)
txt = tmp
#为system加密做准备end

#为base64加密做准备,decrypt返回的rnd是字节流
#字节流转字符串可能因为各种原因出现乱码,所以这里就没用字符串
#全部转换成ascii码存在rnd_bytes里
rnd = rnd.decode()
rnd_bytes = []
for i in range(len(rnd)):
    rnd_bytes.append(ord(rnd[i]))

#爆破第七位key
for i in seven:
    keys = key + i;
    ttmp = []
    # 全部转换成ascii码存在ttmp里
    for j in range(len(txt)):
        ttmp.append(ord(txt[j]) ^ ord(keys[j]))

    print(base64.b64encode(bytes(rnd_bytes+ttmp)).decode())

结果为16行
在这里插入图片描述
打开bp截断请求,发送到intruder模块,就可以得到flag了
在这里插入图片描述
比较有争议的问题
爆破第7位key的时候,key不对会进入else分支,重新给user设置一个值,那之前爆破的是不是就没有意义了?

$username = decrypt($_COOKIE['user'],$key);
if ($username == 'system'){
    echo $flag;
}else{
    setcookie('user',encrypt('guest',$key));
    echo "╮(╯▽╰)╭";
}

用bp爆破的时候,如果不对,会重新设置user,但是这个对我们没有影响的,因为我们的请求包里面的Cookie:user=Y1hPcki0Hk1ZQA==是写死的那种,服务器那边返回的set-cookies对我们接下来的请求包没用的
扩展
现在我们可以看到不管现在的user的值是什么,我们以前得到的这三个解都能拿到flag
在这里插入图片描述
如果这个题只有唯一的一个解,
我的意思是,
当user的值为a的时候,只有一个解A能拿到flag
当user的值为b的时候,只有一个解B能拿到flag
思路也很简单,不用bp爆破(不访问服务器,user的值就不会变)
我们把得到的16行数据进行一个decrypt
得到system的那行就是我们要找的解

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值