A PHP one-liner is being bandied about as one test of the recently discovered Ghost vulnerability in gethostbyname()
. Taken from:
http://ma.ttias.be/quick-tests-ghost-gethostbyname-vulnerability-cve-2015-0235/
Here it is:
% php -r '$e="0";for($i=0;$i<2500;$i++){$e="0$e";} gethostbyname($e);’
What’s not being discussed is how it works. From the naive viewpoint of a PHP end-user, I’d expect this one-liner to have the same effect:
% php -r '$e="0$e"; gethostbyname($e);’
But it doesn’t. Can someone familiar with PHP’s internals explain why this code triggers the overflow, and whether it will actually do so reliably?
More importantly, does this indicate any problems with PHP? It seems like the loop should just be optimized away to a single assignment, but even if the engine isn’t smart enough to do that, I’d still expect that the same few bytes of memory at the same memory address would simply get set to the same value over and over. This code suggests that’s not the case, though, that there are side-effects. Also, just by lowering the counter to 2499, I get a completely different outcome on one particular server:
*** glibc detected *** double free or corruption (out): 0x0000000000acce20 ***
Aborted
FWIW, here’s some C that was provided to more directly check for the vulnerability:
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define CANARY "in_the_coal_mine"
struct {
char buffer[1024];
char canary[sizeof(CANARY)];
} temp = { "buffer", CANARY };
int main(void) {
struct hostent resbuf;
struct hostent *result;
int herrno;
int retval;
/*** strlen (name) = size_needed - sizeof (*host_addr) - sizeof (*h_addr_ptrs) - 1; */
size_t len = sizeof(temp.buffer) - 16sizeof(unsigned char) - 2sizeof(char *) - 1;
char name[sizeof(temp.buffer)];
memset(name, '0', len);
name[len] = '\0';
retval = gethostbyname_r(name, &resbuf, temp.buffer, sizeof(temp.buffer), &result, &herrno);
if (strcmp(temp.canary, CANARY) != 0) {
puts("vulnerable");
exit(EXIT_SUCCESS);
}
if (retval == ERANGE) {
puts("not vulnerable");
exit(EXIT_SUCCESS);
}
puts("should not happen");
exit(EXIT_FAILURE);
}
--
Bob Williams
Business Unit Information Officer and
Senior Vice President of Software Development
Newtek Business Services Corp.
(602) 263-0300 x12458 | http://www.thesba.com/
Notice: This communication, including attachments, may contain information that is confidential. It constitutes non-public information intended to be conveyed only to the designated recipient(s). If the reader or recipient of this communication is not the intended recipient, an employee or agent of the intended recipient who is responsible for delivering it to the intended recipient, or if you believe that you have received this communication in error, please notify the sender immediately by return e-mail and promptly delete this e-mail, including attachments without reading or saving them in any manner. The unauthorized use, dissemination, distribution, or reproduction of this e-mail, including attachments, is prohibited and may be unlawful. If you have received this email in error, please notify us immediately by e-mail or telephone and delete the e-mail and the attachments (if any).
Am 30.01.2015 19:43 schrieb "Robert Williams" rewilliams@thesba.com:
% php -r '$e="0";for($i=0;$i<2500;$i++){$e="0$e";} gethostbyname($e);’
What a funny way to say gethostbyname(str_repeat("0", 2501));
does this indicate any problems with PHP?
No.
best regards
Patrick
Am 30.01.2015 19:43 schrieb "Robert Williams" rewilliams@thesba.com:
% php -r '$e="0";for($i=0;$i<2500;$i++){$e="0$e";} gethostbyname($e);’
What a funny way to say gethostbyname(str_repeat("0", 2501));
does this indicate any problems with PHP?
No.
best regards
Patrick
Well, I guess in theory we should be limiting the size of input to
gethostbyname to 255 characters.
Hi!
does this indicate any problems with PHP?
No.
That said, it may make sense to put a cap on gethostbyname()
argument as
a public service, if we can find a good limit. IIRC, there are limits on
both FQDN and hostname component lengths, so if we check for these
limits, we may add protection for people that for unexplicable reasons
upgrade their PHP but not their glibc.
--
Stas Malyshev
smalyshev@gmail.com
% php -r '$e="0";for($i=0;$i<2500;$i++){$e="0$e";} gethostbyname($e);’
What a funny way to say gethostbyname(str_repeat("0", 2501));
Wow, I somehow missed the interpolation of $e into the value… <self-slap>. Guess I was too focused on looking to the loop as the important part when really, it’s just stupid code, as you point out, probably written by someone who knows little about PHP.
With that in mind, there is obviously no unintended side-effect at work here. Sorry for wasting everyone’s time… as you were.
--
Bob Williams
Business Unit Information Officer and
Senior Vice President of Software Development
Newtek Business Services Corp.
(602) 263-0300 x12458 | http://www.thesba.com/
Notice: This communication, including attachments, may contain information that is confidential. It constitutes non-public information intended to be conveyed only to the designated recipient(s). If the reader or recipient of this communication is not the intended recipient, an employee or agent of the intended recipient who is responsible for delivering it to the intended recipient, or if you believe that you have received this communication in error, please notify the sender immediately by return e-mail and promptly delete this e-mail, including attachments without reading or saving them in any manner. The unauthorized use, dissemination, distribution, or reproduction of this e-mail, including attachments, is prohibited and may be unlawful. If you have received this email in error, please notify us immediately by e-mail or telephone and delete the e-mail and the attachments (if any).
% php -r '$e="0";for($i=0;$i<2500;$i++){$e="0$e";} gethostbyname($e);’
What’s not being discussed is how it works. From the naive viewpoint of a PHP end-user, I’d expect this one-liner to have the same effect:
% php -r '$e="0$e"; gethostbyname($e);’
But it doesn’t. Can someone familiar with PHP’s internals explain why this code triggers the overflow, and whether it will actually do so reliably?
No need to be familiar with the internals, you just need to unroll the
loop properly in your head:
initialise: $e = "0"; => "0"
$i=0: $e = "0$e"; => "0" . "0" => "00"
$i=1: $e = "0$e"; => "0" . "00" => "000"
and so on until you have 2501 zeroes when $i=2499
As Patrick points out, this is a really weird way of initialising that
variable, and is presumably translated from another language by someone
who doesn't know PHP.
--
Rowan Collins
[IMSoP]