PHP反序列化-字符逃逸漏洞解读
以前在学习PHP反序列化字符逃逸的时候,找不到合适的文章来加深我对它的理解,现在写一篇方便后续拿来快速回忆;我这里用例题详细讲解了PHP反序列化中字符逃逸漏洞中字符增加和字符减少的两种情况
字符增加
1 |
|
php过滤函数会把P替换成两个W,导致字符串长度改变,S的值对应不上字符串真实长度i:0=username i:1=age
例如a:2:{i:0;s:7:"purplet";i:1;s:2:"10"}
->a:2:{i:0;s:7:"WWurWWlet";i:1;s:2:"10"}
a的值代表里面有两个数组、i代表第几个数组、s代表字符串长度和它的值
这样第一个数组 i:0;s:7:"WWurWWlet";
字符串长度s的值还是7,但是其实是9,相当于只要传一个p,就会多出一个字符。
思路
这样的话就会出现一个思路:例如,如果我们给username传进去的值长度为32位,组合为16个p再加上16个任意字符,那么在经过filter函数过滤后,16个p就会变成32个W,这样就满足了刚刚在username传进去的32位,如下:
a:2:{i:0;s:32:"WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW+16个其他字符";i:1;s:2:"10"}
这样的话就逃逸出16位字符,我们只需要加上一个双引号,就可以直接把 s:32:"32个W
,给闭合掉,再自己构造好i:1
的age值并用}
把真正的i:1
闭合掉即可。
总结
该思路仅为了方便理解,在实践时,应该提前构造好用来闭合的payload,再计算payload的总长度。闭合payload为16位是前因,传16个p是后果。
传p的个数为:
构造闭合payload的长度除以多出的字符数
例如,闭合payload为16位,那么就传入username为16个p(p变成WW,增加一位字符串的情况)+16位的payload总共32位。
实践
这道题的目的是把年龄10改成20
先构造好闭合payload";i:1;s:2:"20";}
他的长度是16位,想要把他传进去并闭合掉后面的age的值,就需要在username里面传入构造好的poyload
由于上面已经说了,每传进去一个p就会多出一个字符,我们构造好的数组是16位,因此就要传16个p,payload如下,并从username中传入
1 | pppppppppppppppp";i:1;s:2:"20";} |
得到结果
逐步解读
正常没有过滤函数的序列化会变成如下
它s的32代表 pppppppppppppppp";i:1;s:2:"20";}
;16个p加上长度为16的 ";i:1;s:2:"20";}
有过滤函数的序列化会变成如下
pppppppppppppppp";i:1;s:2:"20";}
——>WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW";i:1;s:2:"20";}
它s的32代表 32个W
32个W把S:32填充完毕,由于payload中p后边加上了双引号闭合,正好把i:0和i:1分开了,i:1就是我们传入的;i:1;s:2:”20”;} 。后面真正的i:1就被我们闭合掉了,就没有作用了
反序列化后的结果
这样就成功把age的值从10变成20了
注意点
这里的双引号其实就是i:0的,和真正的i:1都被闭合掉了
画横线的本质上是属于i:0 的 但是由于字符串增加了16位,总共48位
在反序列化过程中,由于S:32的限制 它只会从第一个W往后读32位就会结束,后面的就不管了,导致后面的;i:1;s:2:”20”;}被当成真的,并闭合掉了i:1;s:2:”10”}
总结
遇到字符增加的反序列化
1.先判断增加了几个字符(一个p变成一个WW,增加了一个)
2.构造要传入payload长度(";i:1;s:2:"20";}
总共16位)
3.计算要传入字符(p)的个数
4.和p一块传入即可,需要注意要传到哪个参数。
字符减少
1 |
|
思路
如果字符增加,就让它填充,同理我们在username传p,如果字符减少,那就让它向后吞噬,把真正的
i:1
数组前半部分吞掉,再从age参数传入一个我们提前构造好的age数组,就可以完成字符逃逸。
例如,在过滤函数过滤之前为,a:2:{i:0;s:7:”ppurlet”;i:1;s:2:”10”;}
过滤后变为 a:2:{i:0;s:7:”Wurlet”;i:1;s:2:”10”;} 而s:7:”Wurlet”中Wurlet长度是六位,在反序列化过程中,它就会继续往下读一位把单引号吃掉,因而导致了反序列化错误。
但是这也给我们引出了思路,在username参数多传几个p让s的值变大,从而继续往下吞噬。
因为还不确定要吞噬多少位,先随便传值看看
可以看到我们只要把12位";i:1;s:16:"
吞噬掉,再给age参数传入一个我们新写好的i:1就可以了。
注意点
这里的";}"
不用管,后续会把它闭合掉
实践
payload如下
1 | username=pppppppppppppppppppppppp |
payload解读
传入24个p的原因:因为每传两个p就会吞噬掉一个字符,我们需要吞噬掉";i:1;s:16:"
总共十二个字符,因此传入24个p
age参数";i:1;s:2:"20";}
中写";
的原因:用来闭合掉s:24:"
的双引号,分号后就是我们新写入的i:1
了;最后面的花括号就是用来闭合前文注意点所提到的a:2:{
的。
这样传入并反序列化后就已经成功写了age的值了。
最终结果
发现
文章写到最后发现,其实我这里payload(”;i:1;s:2:”20”;})中不用写双引号也行,前文是要吞噬掉";i:1;s:16:"
,我们只吞噬掉";i:1;s:16:
也行,最后这个双引号留着,用来闭合S:24:”的双引号,这样就不用再age中写双引号了。
把p减少两个,并把age中的双引号删除
反序列化也能成功
总结
遇到字符减少时的反序列化
1.先判断减少了几个字符(两个p变成一个W,减少了一个)
2.找到需要吞掉字符串的长度(本例题是i:1的前大半部分)
3.计算要传入字符(p)的个数
4.构造好要修改的数组并传入即可,需要注意要传到哪个参数。