Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

base64Decode behaviour against payload which contain + and / #3327

Open
touchweb-vincent opened this issue Jan 10, 2025 · 9 comments
Open

base64Decode behaviour against payload which contain + and / #3327

touchweb-vincent opened this issue Jan 10, 2025 · 9 comments
Labels
2.x Related to ModSecurity version 2.x

Comments

@touchweb-vincent
Copy link

Hello,

It might be impossible, but if someone has some time to spare, your help would be greatly appreciated.

We are currently working with the following payload:

PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRQoKd2JsUXRnTFJZCQkJCVsgPCFFTEVNRU5UIHdibFF0Z0xSWSBBTlk+PCFFTlRJVFkKDQoNCg0lCg1GcXhaWUxQIFNZU1RFTQoNCg0KDQoNCg0iZmlsZTovLzBwMEdUbTk0M0lCMjhyTiI+ICVGcXhaWUxQOyAlRVVBaGFYSFk7IF0+PHdibFF0Z0xSWT4mcEtCcGJXbDs8L3dibFF0Z0xSWT4=

This is a random XXE payload encoded in Base64. Note the presence of / and + characters in the payload.

The issue arises when mod_security2 on Apache2 processes it during phase 2. At this stage, the + characters are automatically converted into spaces, which corrupts the Base64 sequence and causes the base64Decode transformation to fail.

We cannot apply transformations like t:urlEncode, as they would encode the / and = characters, further corrupting the Base64 sequence.

Do you have any suggestions on how to properly handle this without resorting to a custom exec solution (as described in the ModSecurity reference manual)?

Please, do not reply by telling us that handling high-entropy payloads is a bottomless pit—we know. There might be no solution and it's okay.

Thank you

Vincent

@touchweb-vincent touchweb-vincent added the 2.x Related to ModSecurity version 2.x label Jan 10, 2025
@airween
Copy link
Member

airween commented Jan 10, 2025

Hi @touchweb-vincent,

so the mentioned base64 encoded string above is in the URL as a GET parameter? Or is that a POST variable?

@touchweb-vincent
Copy link
Author

Hi @airween, we did our tests with a POST variable :

curl -v 'https://X' -d 'id=PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRQoKd2JsUXRnTFJZCQkJCVsgPCFFTEVNRU5UIHdibFF0Z0xSWSBBTlk+PCFFTlRJVFkKDQoNCg0lCg1GcXhaWUxQIFNZU1RFTQoNCg0KDQoNCg0iZmlsZTovLzBwMEdUbTk0M0lCMjhyTiI+ICVGcXhaWUxQOyAlRVVBaGFYSFk7IF0+PHdibFF0Z0xSWT4mcEtCcGJXbDs8L3dibFF0Z0xSWT4='

@airween
Copy link
Member

airween commented Jan 10, 2025

Thanks - we'll try to figure out something.

@touchweb-vincent
Copy link
Author

touchweb-vincent commented Jan 13, 2025

Hello @airween, in case you need another payload, here is one about an XSS category 1 encoded in base64 (which can be base64_decode in PHP without problem with default configuration)

id=P~.F$.~N~~Dc~.mlwd~Ao.+~IG~$FsZ.XJ0.K.D$c5Nik8L1$N$~D$cmlwdD.4~7$$

@airween
Copy link
Member

airween commented Jan 13, 2025

Hi @touchweb-vincent,

Hello @airween, in case you need another payload, here is one about an XSS category 1 encoded in base64 (which can be base64_decode in PHP without problem with default configuration)

id=P~.F$.~N~~Dc~.mlwd~Ao.+~IG~$FsZ.XJ0.K.D$c5Nik8L1$N$~D$cmlwdD.4~7$$

thanks for showing this - now I'm a bit confused. As I know a base64 encoded string can't contain these characters: $, ~, ..

I tried this with PHP as you mentioned, but I'm afraid PHP base64_decode() has some bug (or some unspecified tolerance). See my script:

<?php

$s = 'P~.F$.~N~~Dc~.mlwd~Ao.+~IG~$FsZ.XJ0.K.D$c5Nik8L1$N$~D$cmlwdD.4~7$$';

$d = base64_decode($s);
var_dump($s);

$e = base64_encode($s);
var_dump($e);

(I assumed the id is the name of argument, and the = is the "operator").

This script decodes the given string and encodes again:

string(30) "<SCript
> alert(796)</SCript>;"
string(40) "PFNDcmlwdAo+IGFsZXJ0KDc5Nik8L1NDcmlwdD47"

It seems like PHP's base64_decode() ignores unnecessary characters, like $, ~ and ..

ModSecurity's implementation (you mentioned here mod_security2's reference, so I assume you use this version) uses Apache's solution, which probably behaves differently. (ModSecurity 3 uses mbedtls' base64decode, which seems does not allow any strange character).

Anyway: what's the problem with this payload? You haven't wrote that.

@touchweb-vincent
Copy link
Author

Hello @airween ,

I just wanted to show you another payload with the + character at the beginning of the string.

Additionally, in case you haven't considered it, I also showed how to tweak Base64 decoding in PHP to generate base64 which can fool WAF.

Yes, PHP's native base64_decode function automatically removes all characters that are not part of a valid Base64 string, as described in the documentation: https://www.php.net/manual/en/function.html-entity-decode.php: "Otherwise invalid characters will be silently discarded."

The t:base64DecodeExt transformation in ModSecurity2 does address this need.

However, when the + character is included in the payload, it complicates things further like the first payload.

You can ignore the second payload if you find a solution for the first one—that would be good, it's the same problem.

Thanks you

Vincent

@airween
Copy link
Member

airween commented Jan 13, 2025

Hi @touchweb-vincent,

I just wanted to show you another payload with the + character at the beginning of the string.

right,

The t:base64DecodeExt transformation in ModSecurity2 does address this need.

okay, I checked t:base64Decode because you mentioned that above - never mind.

However, when the + character is included in the payload, it complicates things further like the first payload.

You can ignore the second payload if you find a solution for the first one—that would be good, it's the same problem.

Unfortunately that's not the engine's issue. Apache makes an URI decoding and it passes the arguments after decoding:

Second phase starting (dcfg 7fb54bd31ce0).
Input filter: Reading request body.
Input filter: Bucket type HEAP contains 265 bytes.
Input filter: Bucket type EOS contains 0 bytes.
Adding request argument (BODY): name "id", value "PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRQoKd2JsUXRnTFJZCQkJCVsgPCFFTEVNRU5UIHdibFF0Z0xSWSBBTlk PCFFTlRJVFkKDQoNCg0lCg1GcXhaWUxQIFNZU1RFTQoNCg0KDQoNCg0iZmlsZTovLzBwMEdUbTk0M0lCMjhyTiI ICVGcXhaWUxQOyAlRVVBaGFYSFk7IF0 PHdibFF0Z0xSWT4mcEtCcGJXbDs8L3dibFF0Z0xSWT4="
Input filter: Completed receiving request body (length 265).

As you can see when the web server passes the REQUEST_BODY, the payload is already decoded - which means the + characters are replaced by (space). (With curl request from your comment above.)

So it will hard to find a solution.

@touchweb-vincent
Copy link
Author

Yes, that’s our conclusion as well.

Thank you for taking a look at it. It’s greatly appreciated.

I was hoping for a clever trick in phase 1 to capture the raw body content.

It would be helpful to have a replace transformation that allows us to substitute specific characters, enabling more flexible transformation computations, like t.replace(/\s/+/g). This could also be useful in other situations.

@marcstern
Copy link

The only clean solution is to have 2 url decode transformation, one for URL and one for payload

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2.x Related to ModSecurity version 2.x
Projects
None yet
Development

No branches or pull requests

3 participants