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

  1. __construct: Called when an object is created.
  1. __destruct: Called when an object is about to be destroyed.
  1. __call: Called when an inaccessible method is called on an object.
  1. __callStatic: Called when an inaccessible static method is called on a class.
  1. __get: Called when an inaccessible property is accessed.
  1. __set: Called when an inaccessible property is set.
  1. __isset: Called when isset() or empty() is called on an inaccessible property.
  1. __unset: Called when unset() is called on an inaccessible property.
  1. __sleep: Called before an object is serialized.
  1. __wakeup: Called after an object is unserialized.
  1. __toString: Called when an object is used as a string.
  1. __invoke: Called when an object is used as a function.
  1. __set_state: Called for classes exported by var_export().
  1. __clone: Called when an object is cloned.
  1. __debugInfo: Called by var_dump() when dumping an object.
 
notion image
 

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
notion image
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
notion image
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.
notion image
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.
notion image
Here, you can trigger any "__call" method. Following that, I found the "__call" method in
  • ./vendor/laravel/framework/src/Illuminate/Collections/HigherOrderCollectionProxy.php.
notion image
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.
notion image
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
notion image
⇒ The gadgets found in vendors are not changed .
notion image

© Amine Elsassi 2021 - 2024