Exploiting MD5 vulnerability in this PHP form?
Try sending a HEAD request.
I'm assuming that with ascii.txt included, the output of the script is just over a nice number like 4096 bytes, a common output_buffering
value. Once the script has written output_buffering
bytes, it needs to flush the output buffer before continuing.
Normally this works fine and the script continues, but if the request type is HEAD, there's nowhere for the output to go and execution stops, hopefully in the middle of the "wrong password" message which means admin_level
is never set back to 0.
If ascii.txt is a file you can control, you'll have to tweak the size so the numbers work out to exceed output_buffering
while writing the "wrong password" message.
Here's a paper on this technique. It might not always be applicable based on the PHP version/config, but the last example in the paper is so similar to this problem that I'd expect it to be the intended solution.
The security flaw isn't in MD5 in this case. And the idea isn't to get if (!isset($_POST['pass']) || md5($_POST['pass'])!='castle')
to evaluate in favor of the hacker. The vulnerability appears when you crash the script. The admin privileges are not conditional. No matter what the user gets their permission elevated by setting 1 to $_SESSION['admin_level']
. PHP is procedural. So if you can error out the following IF statement the permission is not only set but it remains persistent. This is now available to the rest of the program as it is stored in the session. Even if the script crashes and errors out. So if the IF statement only exists during a login the attacker can visit all the other restricted scripts as an admin_level set to 1.
So timeline of events:
- Attacker puts bad data or an overflow of data in the POST request
- Script runs
- Script gives admin_level of 1 which is stored in the session
- Script bombs at evaluation
- Attacker goes to another page/script that looks to see if
$_SESSION['admin_level']
is 1
This could of been avoided if the script made the session value part of the IF statement:
if (!isset($_POST['pass']) || md5($_POST['pass'])!='castle')
{
echo '<b>Wrong or empty password.</b><br>';
$_SESSION['admin_level']=0;
} else {
$_SESSION['admin_level']=1;
}
However there is still a lot more that should be done to secure this code.
I've seen a similar challenge somewhere. Try to send something like 'QNKCDZO'
as input. md5('QNKCDZO')
is '0e830400451993494058024219903391'
(note the 0e
prefix in the hash indicating scientific notation of a number) and since the code uses the !=
operator (instead of the type sensitive !==
) PHP (older versions only?) will actually cast it to a number before comparing. Obviously, both will then evaluate to 0
and you win.
Edit: ruakh is right. This doesn't quite work, because both the hash and the string 'castle'
would need to look like a number to PHP for the cast to happen.