Summary
fsockopen()
doesn't regard hostname
as well, hostname
is terminated at the null byte. This can cause Server Side Request Forgery in general case.
Details
During fsockopen
is being called hostname
is passed directly to the low-level C function calls.
/etc/standard/fsock.c:28
static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent)
{
char *host;
size_t host_len;
zend_long port = -1;
zval *zerrno = NULL, *zerrstr = NULL;
double timeout;
bool timeout_is_null = 1;
#ifndef PHP_WIN32
time_t conv;
#else
long conv;
#endif
struct timeval tv;
char *hashkey = NULL;
php_stream *stream = NULL;
int err;
char *hostname = NULL;
size_t hostname_len;
zend_string *errstr = NULL;
ZEND_PARSE_PARAMETERS_START(1, 5)
Z_PARAM_STRING(host, host_len)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(port)
Z_PARAM_ZVAL(zerrno)
Z_PARAM_ZVAL(zerrstr)
Z_PARAM_DOUBLE_OR_NULL(timeout, timeout_is_null)
ZEND_PARSE_PARAMETERS_END();
// ...
stream = php_stream_xport_create(hostname, hostname_len, REPORT_ERRORS,
STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, hashkey, &tv, NULL, &errstr, &err);
When fsockopen()
is called, it retrieves hostname from first parameter, into host
and host_len
. host
can contain null bytes in the middle of the string, but host_len
can be used to prevent unexpected null termination. These two host
and host_len
is passed to php_stream_xport_create()
/main/streams/transports.c:_php_stream_xporet_create()
orig_path = name;
for (p = name; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) {
n++;
}
if ((*p == ':') && (n > 1) && !strncmp("://", p, 3)) {
protocol = name;
name = p + 3;
namelen -= n + 3;
} else {
protocol = "tcp";
n = 3;
}
After TCP factory is selected, php_network_getaddresses()
is called, but still null bytes or any control characters are not processed. This will lead Server Side Request Forgery.
While other url-related functions like parse_url has processing logic using zend_string
type and iscntrl()
check. This difference can be used to trigger SSRF in general case.
For example, one developer can write following reasonable code.
<?php
$connect_host = "(user_given_host):(user_given_port)";
$host = parse_url($connect_host, PHP_URL_HOST);
if(!str_ends_with($host, ".safedomain.com"))
die("Wrong host");
$fp = fsockopen($connect_host);
...
When $connect_host
is given as localhost\0.safedomain.com
, parse_url
will return localhost_.safedomain.com
as its host, which can pass the security check, but fsockopen()
will connect to localhost
and occurs server side request forgery.
PoC
<?php
$fp = fsockopen("localhost\0.some-domain-for-me.com, 4000);
fwrite($fp, "TEST\n");
fclose($fp);
This code will connect to localhost:4000
.
Impact
Server Side Request Forgery
Classification
PHP does not usually classify \0
as a security issue because users are expected to sanitize the input. Nevertheless this was considered as a low impact security issue as a precaution for users that do not do that. It also take into account that the patch is simple.
Summary
fsockopen()
doesn't regardhostname
as well,hostname
is terminated at the null byte. This can cause Server Side Request Forgery in general case.Details
During
fsockopen
is being calledhostname
is passed directly to the low-level C function calls./etc/standard/fsock.c:28
When
fsockopen()
is called, it retrieves hostname from first parameter, intohost
andhost_len
.host
can contain null bytes in the middle of the string, buthost_len
can be used to prevent unexpected null termination. These twohost
andhost_len
is passed tophp_stream_xport_create()
/main/streams/transports.c:_php_stream_xporet_create()
After TCP factory is selected,
php_network_getaddresses()
is called, but still null bytes or any control characters are not processed. This will lead Server Side Request Forgery.While other url-related functions like parse_url has processing logic using
zend_string
type andiscntrl()
check. This difference can be used to trigger SSRF in general case.For example, one developer can write following reasonable code.
When
$connect_host
is given aslocalhost\0.safedomain.com
,parse_url
will returnlocalhost_.safedomain.com
as its host, which can pass the security check, butfsockopen()
will connect tolocalhost
and occurs server side request forgery.PoC
This code will connect to
localhost:4000
.Impact
Server Side Request Forgery
Classification
PHP does not usually classify
\0
as a security issue because users are expected to sanitize the input. Nevertheless this was considered as a low impact security issue as a precaution for users that do not do that. It also take into account that the patch is simple.