My First CVE
date
‣
slug
PHP deserialization
status
Published
tags
PHP
CVEs
WebSec
summary
Remote Code Execution in HelpSpot
type
Post
CVE 2023-50978
Basic stuffs
__construct
: Called when an object is created.
__destruct
: Called when an object is about to be destroyed.
__call
: Called when an inaccessible method is called on an object.
__callStatic
: Called when an inaccessible static method is called on a class.
__get
: Called when an inaccessible property is accessed.
__set
: Called when an inaccessible property is set.
__isset
: Called whenisset()
orempty()
is called on an inaccessible property.
__unset
: Called whenunset()
is called on an inaccessible property.
__sleep
: Called before an object is serialized.
__wakeup
: Called after an object is unserialized.
__toString
: Called when an object is used as a string.
__invoke
: Called when an object is used as a function.
__set_state
: Called for classes exported byvar_export()
.
__clone
: Called when an object is cloned.
__debugInfo
: Called byvar_dump()
when dumping an object.
local Setup
The sanitize_serialize function trims the input string $var to remove leading and trailing whitespace, echoes the trimmed string for debugging, and then checks if it is a non-empty string. If so, it attempts to unserialize the string using unserialize() and returns the result; otherwise, it returns the specified default value $default. This function is designed to handle cases where the input variable is expected to be a serialized string, and it ensures proper sanitization and fallback to a default value if the input is not a valid serialized string.
in order to setup a local debug env i adopted this method to avoid any attack complexity and target directly the security mechanism
implemented and be straight to the point trying to mock this functionality
path : 1. helpspot/helpspot/pages/ajax_gateway.php
As we see here there’s a Post Parameter passed directly to an hs_unserialize function which is supposed to protect the application
against abitrary deseriliazation and sanitize user input
we can see in the function defenition that’s it’s still touches
unserialize()
hs_unserialize
PHP versions 7.1 and above are not case-sensitive when it comes to member properties in a class. This means that if the target server is running PHP 7.1 or later, you can modify all the properties involved in the class to be public. This eliminates the issue of null bytes, and you can still trigger deserialization.
OUR GADGET CHAIN WILL BE
⇒ __destruct → __call → read($length) → eof() → call_user_func()
Remote Code Execution - POP chain
- __destruct method in ⇒ ./vendor/laravel/framework/src/Illuminate/Broadcasting/PendingBroadcast.php.
Here, you can trigger any "__call" method. Following that, I found the "__call" method in
- ./vendor/laravel/framework/src/Illuminate/Collections/HigherOrderCollectionProxy.php.
Here, you can trigger any method of any class, and the passed parameter is an anonymous function.
So, in essence, you can invoke any class's method with arguments. At this point, we can call the "read($length)" method of the HashingStream class in
- ./vendor/aws/aws-sdk-php/src/HashingStream.php.
Then, the remaining steps are similar. Find a usable
eof()
method to bypass the if
statement. Use the __call
method from the Faker\DefaultGenerator
class to set the value of $result
. The use of call_user_func
eventually leads to Remote Code Execution (RCE) . My Exploit
Adopted Exploit for
hs_unserialize()
were having some null bytes that are being created during the serialization process of private and protected properties this will generate something like
s:3":%00a%00"
after the trim() we will get s:3:"a"
and this will affect the length of our serialized data so the unserialize will fail that's why we need this trick⇒ The gadgets found in vendors are not changed .