java 解密 对象序列化
序列化与反序列化 (Serialization vs Deserialization)
Serialization is the process of converting objects to a sequential stream of bytes so that it can be easily stored in a database or transmitted over a network. Deserialization is the exact opposite of serialization. It is the process of converting this sequential stream of bytes to a fully functional object.
序列化是将对象转换为顺序的字节流的过程,以便可以轻松地将其存储在数据库中或通过网络传输。 反序列化与序列化完全相反。 这是将顺序的字节流转换为功能完整的对象的过程。
The object’s state is also persisted which means that the object’s attributes are preserved, along with their assigned values. The process of preventing a field from being serialized varies from language to language.
对象的状态也将保持不变,这意味着将保留对象的属性及其分配的值。 防止字段序列化的过程因语言而异。
什么是不安全的反序列化? (What is insecure deserialization?)
Insecure deserialization is when user-controllable data is deserialized by an application. This allows an attacker to manipulate serialized objects and pass malicious data into the application code. It is possible to replace the serialized object with an object of a completely different class.
不安全的反序列化是指应用程序对用户可控制的数据进行反序列化时。 这使攻击者可以操纵序列化的对象并将恶意数据传递到应用程序代码中。 可以用完全不同类的对象替换序列化的对象。
It is virtually impossible to implement validation or sanitization to account for every eventuality. These checks are also fundamentally flawed as they rely on checking the data after it has been deserialized, which in many cases will be too late to prevent the attack as you will see in the exploitation examples later.
几乎不可能实施验证或消毒以解决所有可能的情况。 这些检查从根本上来说也是有缺陷的,因为它们依赖于对数据进行反序列化后再对其进行检查,这在很多情况下为时已晚,无法阻止攻击,这将在后面的利用示例中看到。
如何防止不安全的反序列化漏洞 (How to prevent insecure deserialization vulnerabilities)
Deserialization of user input should be avoided unless necessary. If you do need to deserialize data from untrusted sources, incorporate robust measures to make sure that the data has not been tampered with. For example, you could implement a digital signature to check the integrity of the data. However, remember that any checks must take place before beginning the deserialization process. Otherwise, they are of little use.
除非必要,否则应避免对用户输入进行反序列化。 如果您确实需要反序列化来自不受信任来源的数据,请采用可靠的措施以确保数据未被篡改。 例如,您可以实施数字签名来检查数据的完整性。 但是,请记住,在开始反序列化过程之前,必须进行任何检查。 否则,它们几乎没有用。
在PHP中利用不安全的反序列化 (Exploiting insecure deserialization in PHP)
PHP反序列化的基础 (Basics of PHP Deserialization)
Lines 2–15: Declaring a PHP class called Car which has 3 attributes model, manufacturer and colour. Each of them has different access specifiers for demonstration purposes. The parameterized constructor is used for initializing the attributes.
第2-15行:声明一个名为CarPHP类,该类具有3个属性模型,制造商和颜色。 为了演示目的,它们每个都有不同的访问说明符。 参数化的构造函数用于初始化属性。
Line 16: Creating an object of class Car.
第16行:创建Car类的对象。
Lines 18,19: Serializing the object created in Line #16. Serialization creates some non-printable characters like \x00 so we are replacing it with \\x00 so that we can view the output properly.
第18,19行:序列化第16行中创建的对象。 序列化会创建一些不可打印的字符,例如\ x00,因此我们将其替换为\\ x00,以便可以正确查看输出。
O:3:"Car":3:{...}
Objects start with upper case O, followed by the the length of the class name(which is 3 because length of the word car is 3), followed by name of the class i.e. Car, followed by number of attribues in the class which is 3 (model, manufaturer and colour).s:10:"\x00Car\x00model";s:6:"SELTOS";
This is an attribute of the class car with it's corresponding value. 's' means a string attribute followed by the length of the name of attribute. Notice how the access specifier(private) is appended before the attribute name while serialization. \x00ClassName\x00 is the format followed in case of private attributes. This is followed by the type (s: string), length (6 characters in SELTOS) and value (SELTOS) of the attribute (model).s:15:"\x00*\x00manufacturer";s:3:"KIA";This is also similar to the above example however, as this attribute is of protected type, \x00*\x00 is appended before the attribute name.s:6:"colour";s:5:"WHITE";For public attributes nothing is appended to the attribute name. The remaining part is similar to the other attributes.Some other types of data can also be present like i for integer and b for boolean. The lenghth of the value is not required in this case.
s:6:wheels;i:4; is similar to public int wheels = 4
s:10:twowheeler;b:0; is similar to public boolean twowheeler = false
处理序列化对象 (Manipulating serialized objects)
Suppose there is a web application which uses serialization-based session mechanism. The user details are present in the session cookie in a serialized form. It is possible to manipulate this data and escalate our privileges.
假设有一个Web应用程序使用基于序列化的会话机制。 用户详细信息以序列化形式显示在会话cookie中。 可以操纵这些数据并提升我们的特权。
After logging in to the application we can see that a session cookie is set and its base64 encoded. After decoding the value we can see it is a serialized PHP object.
登录到应用程序后,我们可以看到设置了会话cookie并对其base64进行了编码。 解码该值后,我们可以看到它是一个序列化PHP对象。
There is an interesting attribute: admin in this cookie which is set to 0. We modify this value to 1, encode it back to base64 and save the new cookie value.
有一个有趣的属性:此cookie中的admin设置为0。我们将此值修改为1,将其编码回base64,然后保存新的cookie值。
After refreshing the page now we can see that it is now possible to access the admin section of the page.
现在刷新页面后,我们可以看到现在可以访问页面的admin部分。
In the backend code, we can see that the application blindly trusts serialized data (Line 7) and provided access control as per the values of the object attributes (Line 21). As it is possible to modify this object attribute and escalate our privileges.
在后端代码中,我们可以看到应用程序盲目地信任序列化数据(第7行),并根据对象属性的值(第21行)提供了访问控制。 因为可以修改此对象属性并提升我们的特权。
处理PHP数据类型和比较 (Manipulating PHP data types and comparisons)
This code is basically a proof of concept of how PHP handles data types and loose comparisons and how it can be manipulated. After opening this page and clicking on the set/reset button a cookie is set. This cookie is a serialized object of the class Secret
which contains a username
and a token
. By default, it will show that the user is low privileged, however, if the username is admin and the token is set to the super-secret admin token it is possible to access the admin section. In the following section, we will see how we can bypass these requirements by manipulating how PHP compares data.
此代码基本上是PHP如何处理数据类型和松散比较以及如何对其进行操作的概念证明。 打开此页面并单击“设置/重置”按钮后,将设置一个cookie。 该cookie是Secret
类的序列化对象,其中包含username
和token
。 默认情况下,它将显示用户的特权较低,但是,如果用户名是admin且令牌设置为超级秘密admin令牌,则可以访问admin部分。 在下一节中,我们将了解如何通过操纵PHP比较数据的方式来绕过这些要求。
PHP’s loose comparison is quite strange due to the following characteristics:
由于以下特征,PHP的松散比较非常奇怪:
When comparing between an integer and a string, PHP will convert the string to an integer. This means
5 == "5"
在整数和字符串之间进行比较时,PHP会将字符串转换为整数。 这意味着
5 == "5"
When comparing an alphanumeric string with a number it will first check if the string begins with a number. If it starts with a number it will ignore the rest of the string. This means
2 == "2 some random string"
is equivalent to2 == "2"
.将字母数字字符串与数字进行比较时,它将首先检查字符串是否以数字开头。 如果以数字开头,它将忽略字符串的其余部分。 这意味着
2 == "2 some random string"
等价于2 == "2"
。If this string does not contain an integer then it’s converted to 0 probably because it has 0 numbers in it. So
0 == "Somestring"
is true.如果该字符串不包含整数,则可能会因为其中包含0个数字而将其转换为0。 因此
0 == "Somestring"
为true。
In this exploit, we are going to do the exact same thing. We will change the name to admin and change the token to an integer 0.
在此漏洞利用中,我们将做完全相同的事情。 我们将名称更改为admin并将令牌更改为整数0。
After reloading the page that the page now thinks we are admin and we have bypassed the authentication check without the super-secret access token.
重新加载页面后,该页面现在认为我们是管理员,并且我们绕过了没有超级秘密访问令牌的身份验证检查。
This only works for deserialized objects. It will not work if the password is directly fetched from the parameter, in that case, it would be converted to a string. This only works because data types are preserved in serialization and it is possible to change the type to integer.
这仅适用于反序列化的对象。 如果直接从参数获取密码,它将不起作用,在这种情况下,它将被转换为字符串。 这仅起作用,因为数据类型在序列化中保留,并且可以将类型更改为整数。
魔术方法和任意对象 (Magic methods and arbitrary objects)
Magic methods are special functions that are called automatically. These functions begin with double underscore e.g. __construct()
. The __construct()
method gets called as soon as a new object is created. A developer can implement his own __construct()
method for implementing a parameterized constructor. There are several other magic methods but we will focus on __destruct()
and __wakeup()
.
魔术方法是自动调用的特殊功能。 这些函数以双下划线开头,例如__construct()
。 创建新对象后,便会立即调用__construct()
方法。 开发人员可以实现自己的__construct()
方法来实现参数化的构造函数。 还有其他几种魔术方法,但我们将重点介绍__destruct()
和__wakeup()
。
__wakeup
will be called as soon as a serialized object of the class is deserialized. The intended use of __wakeup() is to reestablish any database connections that may have been lost during serialization and perform other reinitialization tasks. The __destruct()
method is called automatically for each PHP object at the end of the PHP code execution.
反序列化该类的序列化对象后,将立即调用__wakeup
。 __wakeup()的预期用途是重新建立在序列化过程中可能丢失的任何数据库连接,并执行其他重新初始化任务。 在PHP代码执行结束时,将为每个PHP对象自动调用__destruct()
方法。
In this code, there is a class called Serialkiller which takes the username as input in the constructor.
在这段代码中,有一个名为Serialkiller的类,该类将用户名作为构造函数中的输入。
After that, it initializes the file paths for log and cache. The files will be created inside the
/logs
and/cache
directory.之后,它将初始化日志和缓存的文件路径。 这些文件将在
/logs
和/cache
目录中创建。There is a helper method
createFile()
which creates files and alogEntry()
which writes data into the files.有一个帮助程序方法
createFile()
创建文件,以及一个logEntry()
将数据写入文件。The
__destroy()
magic method deletes the user’s cache file when the script execution finishes.脚本执行完成后,
__destroy()
魔术方法将删除用户的缓存文件。The
__wakeup()
magic method reinitializes the file log and cache files if it does not exist and then creates a log entry.__wakeup()
魔术方法重新初始化文件日志并缓存文件(如果不存在),然后创建日志条目。
After the class declaration, we create an instance of the Serialkiller class and write a test log. In lines 43–50, we print the serialized form of the instance created before. We replace \x00
with its URL encoded form %00
. We will modify this output for creating our exploits.
在类声明之后,我们创建Serialkiller类的实例并编写测试日志。 在第43-50行中,我们打印之前创建的实例的序列化形式。 我们将\x00
替换为其URL编码形式%00
。 我们将修改此输出以创建我们的攻击。
If the data parameter is set in a GET request the program deserializes whatever is give as input. We will use this to test our exploits.
如果在GET请求中设置了data参数,则程序将反序列化输入内容。 我们将使用它来测试我们的攻击。
After executing the script we get the serialized object which we will use for developing the exploit. For the first exploit, we will delete the test.txt file from the webroot by manipulating the __destruct function.
执行脚本后,我们将获得序列化的对象,将用于开发漏洞利用程序。 对于第一个漏洞利用,我们将通过操纵__destruct函数从webroot删除test.txt文件。
We will change the value of the cache_file attribute to test.txt in the serialized object and deserialize it by giving it as input to the data parameter. After deserialization, the cache_file attribute points to test.txt. When the __destruct() method is called it deletes test.txt (Line 27).
我们将序列化对象中的cache_file属性的值更改为test.txt,并通过将其作为data参数的输入来反序列化。 反序列化后,cache_file属性指向test.txt。 调用__destruct()方法时,它将删除test.txt(第27行)。
O:12:"Serialkiller":4:{s:24:"%00Serialkiller%00cache_file";s:8:"test.txt";s:22:"%00Serialkiller%00log_file";s:13:"logs/john.log";s:21:"%00Serialkiller%00content";s:12:"Starting log";s:18:"%00Serialkiller%00user";s:4:"john";}
We can verify from the next screenshot that the test.txt file has been deleted.
我们可以从下一个屏幕截图中验证test.txt文件已被删除。
For the next exploit, we are going to execute code on the server by creating a malicious PHP file on the server and executing it. For this, we are going to use the __wakeup()
function.
对于下一个漏洞利用,我们将通过在服务器上创建恶意PHP文件并执行它来在服务器上执行代码。 为此,我们将使用__wakeup()
函数。
We know that __wakeup()
is called as soon as an object is deserialized. Inside the function, it creates a log file and also adds a log entry. We will manipulate the values of the variables so that instead of creating a log file, a PHP file is created and instead of writing the log entry some malicious PHP command is written in the PHP file. We follow the steps below to exploit this
我们知道__wakeup()
会在对象反序列化后立即调用。 在函数内部,它创建了一个日志文件,还添加了一个日志条目。 我们将操纵变量的值,以便创建一个PHP文件来代替创建日志文件,而不是创建日志文件,而不是在日志文件中写入一些恶意PHP命令。 我们按照以下步骤来利用这一点
We modify the
log_file
attribute in the payload to the target path of the PHP RCE file we want to generate(logs/rce.php
in the example below). As soon as createFile() is called with it will check iflogs/rce.php
(which is the value oflog_file
variable) file exists or not and will create it if it's missing (Line 21,35).我们将有效负载中的
log_file
属性修改为我们要生成PHP RCE文件的目标路径(在以下示例中为logs/rce.php
)。 一旦调用createFile(),它将检查是否存在logs/rce.php
(log_file
变量的值)文件,如果缺少该文件,则会创建该文件(第21,35行)。Similarly, we will modify the
content
attribute in the payload to some PHP code like. The
logEntry()
method called in Line 22 takes the value of thecontent
variable and writes it tolog_file
. Thus malicious PHP code will be written to the/logs/rce.php
file.同样,我们将有效负载中的
content
属性修改为一些PHP代码,例如。 第22行中调用的
logEntry()
方法获取content
变量的值,并将其写入log_file
。 因此,恶意PHP代码将被写入/logs/rce.php
文件。
The payload should look similar to this:
有效负载应类似于以下内容:
O:12:"Serialkiller":4:{s:24:"%00Serialkiller%00cache_file";s:16:"cache/john.cache";s:22:"%00Serialkiller%00log_file";s:12:"logs/rce.php";s:21:"%00Serialkiller%00content";s:28:"";s:18:"%00Serialkiller%00user";s:4:"john";}
As this program deserializes anything which is passed in the GET parameter data
, we can execute the payload by opening the following page on the browser:
由于此程序反序列化GET参数data
传递的任何内容,因此我们可以通过在浏览器上打开以下页面来执行有效负载:
http://localhost/injection.php?data=O:12:"Serialkiller":4:{s:24:"%00Serialkiller%00cache_file";s:16:"cache/john.cache";s:22:"%00Serialkiller%00log_file";s:12:"logs/rce.php";s:21:"%00Serialkiller%00content";s:28:"";s:18:"%00Serialkiller%00user";s:4:"john";}
After the above payload is executed we navigate to the new PHP file created in http://localhost/logs/rce.php
. We can see that it is possible to execute code.
执行以上有效负载后,我们导航到在http://localhost/logs/rce.php
创建的新PHP文件。 我们可以看到可以执行代码。
There is also something known as Gadgets which are code snippets present in the code itself. These gadgets can be chained for exploitation however it is almost impossible to find them without the source code and takes a lot of time. We can use the PHPGGC tool to create payloads for such known gadget chains.
还有一种称为小工具的东西,它们是代码本身中存在的代码片段。 可以将这些小工具链接起来以进行利用,但是如果没有源代码,几乎不可能找到它们,并且要花费大量时间。 我们可以使用PHPGGC工具为此类已知的小工具链创建有效负载。
All the code used in the above examples can be found from my GitHub repository here. I plan on making similar codes for Java and .NET soon.
以上示例中使用的所有代码都可以在我的GitHub存储库中找到 。 我计划很快为Java和.NET编写类似的代码。
进一步阅读 (Further Reading)
https://www.netsparker.com/blog/web-security/untrusted-data-unserialize-php/
https://www.netsparker.com/blog/web-security/untrusted-data-unserialize-php/
https://portswigger.net/web-security/deserialization
https://portswigger.net/web-security/deserialization
https://blog.ripstech.com/2018/new-php-exploitation-technique/
https://blog.ripstech.com/2018/new-php-exploitation-technique/
翻译自: https://medium.com/bugbountywriteup/demystifying-insecure-deserialization-in-php-684cab9c4d24
java 解密 对象序列化