Show Notes

133 - Spring4Shell, PEAR Bugs, and GitLab Hardcoded Passwords

The title says it all, CSRF protection was disabled for a period of time on Stripe’s Dashboard. As the most sensitive actions required reentering the user’s password or solving a captcha the damage was limited but you could still change various account settings. Its a bit of a crazy vulnerability to have introduced, just straight up disabling CSRF checking.

This is a weird one, but easily understood; when using OmniAuth as the authentication provider (for like OAuth, LDAP, or SAML login) a hardcoded password would be associated with the account.

Whats weird is how this passed code review. The patch itself is trying to improve the password used for testing so their tooling doesn’t complain about weak keys. However in teh midst of several changes relating to that is using the test key as the actual password for a new account.

Sometimes vulnerabilities come from trying to be too generic/handle all the possibilities, this is one of those situations. What you have the Spring Framework letting users write simple Java classes with fields, getters/setters and setting those up as models for a particular endpoint. Spring will then try to map request parameters to class fields and set them appropriately, to do this it uses the java.beans.Introspection class which uses a bit of Java magic to look at the internal java representation for an arbitrary object and determine what fields have getters/setters and can be modified. It can also recurse into the nested objects.

While the fields you’d expect do come back, one unexpected property though is class. This is because any plain object/class in Java automatically inherits from Object.Class which has the getClass() method. Form there you can go on down the class tree, potentially leading to the ability to set some important and sensitive classes.

There was a 2010 attack taking advantage of this same issue, where the exploit targeted class.classLoader.URLs to trick the system into adding a remote address to the paths the class loader would look for classes in. This was patched by checking if the property name was classLoader and it was in the Class class.

This worked for awhile, it appears a few years later, they also blocked the “protectionDomain” field in this commit. Though I’m not sure about the details on that attack (if any).

So effectively the patches have been through blocklisting whatever means someone found to exploit it. That’s true for Spring4Shell too, Java 9 introduced Modules and with that the module field, which itself also exposed classLoader but as the module isn’t the Class class it bypasses the existing checks. Though, Java 9 doesn’t load remote JARs anymore, so the old exploit couldn’t be revived.

Instead, the exploitation takes a more round about way, through class.module.classLoader.resources.context.parent.pipeline.first which reminds me a lot of Python sandbox escaping where you work your way to the base python class then try and access different fields working your way to arbitrary classes. In this case it is working it way to some of Tomcat’s logging properties. A full exploit would use multiple requests to set several of these properties resulting in writting a shell to the server as a “log”.

class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bprefix%7Di%20[url safe JSP file here]%20%25%7Bsuffix%7Di
class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp
class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT
class.module.classLoader.resources.context.parent.pipeline.first.prefix=shell
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=

While this particular exploit requires Tomcat, it is likely other fields could be abused in other situations to gain code execution.

The patch once again is block listing but they are a bit more comprehensive this time. Only allowing access to name from within Class and denying access to the ClassLoader and ProtectionDomain classes all together.

Weak entropy in a password reset token, and an archive escape using symlinks to achieve code execution.

The first issue is in how the password reset token is generated.

$salt = md5(mt_rand(4,13) . $user . time() . $pass1);

Basically only two things are not exactly known by the attack, the output of mt_rand(4,13) which will be a value between 4 and 13 (inclusive). And time() which can likely be guessed with reasonable accuracy as this is simply seconds since 1970 (unix epoch) and does not include the miliseconds. So an attack could easily guess all possible reset tokens and gain access to the service.

The second issue is one we’ve covered many times before, a symlink in an archive. Create a symlink in the archive and then write a file to/through it, resulting in a write to a folder outside the archive’s root and hopefully into somewhere the webserver will execute PHP from in this case.

Patch

$random_bytes = openssl_random_pseudo_bytes(16, $strong);
if ($random_bytes === false || $strong === false) {
    $errors[] = "Could not generate a safe password token";
    return $errors;
}
$salt = md5($rand_bytes):

While they had the right idea here, getting some random bytes and using that, the key problem with this patch is that the random bytes are stored in $random_bytes but the value passed into md5 is $rand_bytes which is a variable that doesn’t exist. PHP will offer up a warning but otherwise carry on getting the hash of an empty string.