Skip to content
16 changes: 13 additions & 3 deletions ext/ftp/tests/server.inc
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ if ($pid) {
$host = "127.0.0.1";
$i=0;

if (empty($bug73457)) {
if (empty($bug73457) && empty($pasv_address_at_line_end)) {
if (!empty($ssl)) {
$soc = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $context);
} else {
Expand All @@ -450,9 +450,14 @@ if ($pid) {

$p2 = $pasv_port % ((int) 1 << 8);
$p1 = ($pasv_port-$p2)/((int) 1 << 8);
fputs($s, "227 Entering Passive Mode. (127,0,0,1,{$p1},{$p2})\r\n");
if (empty($pasv_address_at_line_end)) {
fputs($s, "227 Entering Passive Mode. (127,0,0,1,{$p1},{$p2})\r\n");
} else {
/* The address starts 14 bytes before the end of the 512 bytes the client reads the line into */
fputs($s, "227 " . str_repeat('.', 494) . "127,0,0,1,1,1)\r\n");
}

if (empty($bug73457)) {
if (empty($bug73457) && empty($pasv_address_at_line_end)) {
$pasvs = stream_socket_accept($soc,10);
} else {
$pasvs = null;
Expand Down Expand Up @@ -591,6 +596,11 @@ if ($pid) {
fclose($fs);
}elseif (preg_match('/^SIZE \/bug73457/', $buf)) {
fputs($s, "213 10\r\n");
}elseif (preg_match('/^SIZE \/mdtmnodigits/', $buf)) {
fputs($s, "213 10\r\n");
}elseif (preg_match('/^MDTM \/mdtmnodigits/', $buf)) {
// No digit in the whole 512 byte buffer the client reads the reply into
fputs($s, "213 " . str_repeat('x', 600) . "\r\n");
}elseif (preg_match("/^SITE/", $buf)) {
fputs($s, "500 Syntax error, command unrecognized.\r\n");
}else {
Expand Down
24 changes: 24 additions & 0 deletions ext/mysqli/tests/fake_server.inc
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,30 @@ function my_mysqli_test_stmt_response_row_short_length(
$conn->read(65536);
}

function my_mysqli_test_stmt_response_row_no_null_bitmap(my_mysqli_fake_server_conn $conn): void
{
$rh = $conn->packet_generator->server_stmt_execute_data_response('intval');

// The row packet ends right after the status byte, before the NULL bitmap
$row = new my_mysqli_fake_packet();
$row->packet_length = '010000';
$row->packet_number = '05';
$row->packet_type = '00';
$rh[4] = $row;

$conn->send_server_greetings();
$conn->read();
$conn->send_server_ok();
$conn->read();
$conn->send_server_stmt_prepare_data_response('intval');
$conn->read();
$conn->send(
$conn->packets_to_bytes($rh),
"Malicious Stmt Response for data intval [no NULL bitmap]"
);
$conn->read(65536);
}

function my_mysqli_test_stmt_response_row_short_length_date(my_mysqli_fake_server_conn $conn): void
{
my_mysqli_test_stmt_response_row_short_length($conn, 'datval');
Expand Down
45 changes: 45 additions & 0 deletions ext/mysqli/tests/mysqlnd_stmt_row_no_null_bitmap.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
--TEST--
mysqlnd rejects a binary protocol row packet too short for its NULL bitmap
--EXTENSIONS--
mysqli
--FILE--
<?php
require_once 'fake_server.inc';

$servername = "127.0.0.1";
$username = "root";
$password = "";

$process = run_fake_server_in_background('stmt_response_row_no_null_bitmap');
$process->wait();

$conn = new mysqli($servername, $username, $password, "", $process->getPort());

echo "[*] Prepare and execute on the fake server...\n";
$stmt = $conn->prepare("SELECT strval, intval FROM data");
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_row()) {
var_dump($row);
}
$stmt->close();
$conn->close();

$process->terminate();

print "done!";
?>
--EXPECTF--
[*] Server started on 127.0.0.1:%d
[*] Connection established
[*] Sending - Server Greeting: 580000000a352e352e352d31302e352e31382d4d6172696144420003000000473e3f6047257c6700fef7080200ff81150000000000000f0000006c6b55463f49335f686c6431006d7973716c5f6e61746976655f70617373776f7264
[*] Received: 6900000185a21a00000000c0080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f7264002c0c5f636c69656e745f6e616d65076d7973716c6e640c5f7365727665725f686f7374093132372e302e302e31
[*] Sending - Server OK: 0700000200000002000000
[*] Prepare and execute on the fake server...
[*] Received: 200000001653454c4543542073747276616c2c20696e7476616c2046524f4d2064617461
[*] Sending - Stmt prepare data intval: 0c0000010001000000020000000000003200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe00000200
[*] Received: 0a00000017010000000001000000
[*] Sending - Malicious Stmt Response for data intval [no NULL bitmap]: 01000001023200000203646566087068705f74657374046461746104646174610673747276616c0673747276616c0ce000c8000000fd01100000003200000303646566087068705f746573740464617461046461746106696e7476616c06696e7476616c0c3f000b00000003011000000005000004fe00002200010000050005000006fe00002200

Warning: mysqli_result::fetch_row(): Malformed server packet. No packet space left for the NULL bitmap in %s on line %d
done!
6 changes: 6 additions & 0 deletions ext/mysqlnd/mysqlnd_wireprotocol.c
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,12 @@ php_mysqlnd_rowp_read_binary_protocol(MYSQLND_ROW_BUFFER * row_buffer, zval * fi

end_field = (start_field = fields) + field_count;

/* The status byte and the NULL bitmap must fit in the packet */
if (UNEXPECTED(rbs < 1 + (field_count + 9) / 8)) {
php_error_docref(NULL, E_WARNING, "Malformed server packet. No packet space left for the NULL bitmap");
DBG_RETURN(FAIL);
}

/* skip the first byte, not EODATA_MARKER -> 0x0, status */
p++;
null_ptr= p;
Expand Down
96 changes: 76 additions & 20 deletions ext/soap/php_http.c
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,44 @@ static bool in_domain(const zend_string *host, const zend_string *domain)
}
}

/* Whether a cookie Domain attribute received from host may be accepted (RFC 6265 5.1.3) */
static bool cookie_domain_matches(const zend_string *host, const char *domain, size_t domain_len)
{
if (domain_len > 0 && domain[0] == '.') {
domain++;
domain_len--;
}

if (domain_len == 0 || domain_len > ZSTR_LEN(host)) {
return false;
}

if (strncasecmp(ZSTR_VAL(host) + ZSTR_LEN(host) - domain_len, domain, domain_len) != 0) {
return false;
}

return domain_len == ZSTR_LEN(host) || ZSTR_VAL(host)[ZSTR_LEN(host) - domain_len - 1] == '.';
}

/* The port of from must already be resolved, the port of to may still be 0 */
static bool same_origin(const php_uri *from, const php_uri *to)
{
if (from->scheme == NULL || to->scheme == NULL || from->host == NULL || to->host == NULL) {
return false;
}

if (!zend_string_equals_ci(from->scheme, to->scheme) || !zend_string_equals_ci(from->host, to->host)) {
return false;
}

zend_long to_port = to->port;
if (to_port == 0) {
to_port = zend_string_equals_literal_ci(to->scheme, "https") ? 443 : 80;
}

return from->port == to_port;
}

bool make_http_soap_request(
zval *this_ptr, zend_string *buf, zend_string *location, char *soapaction,
int soap_version, zend_string *uri_parser_class, zval *return_value
Expand Down Expand Up @@ -376,6 +414,8 @@ bool make_http_soap_request(
bool has_authorization = false;
bool has_proxy_authorization = false;
bool has_cookies = false;
/* Set once a redirect left the origin, credentials must not follow it */
bool strip_auth = false;

if (this_ptr == NULL || Z_TYPE_P(this_ptr) != IS_OBJECT) {
return false;
Expand Down Expand Up @@ -687,7 +727,7 @@ bool make_http_soap_request(

/* HTTP Authentication */
login = Z_CLIENT_LOGIN_P(this_ptr);
if (Z_TYPE_P(login) == IS_STRING) {
if (Z_TYPE_P(login) == IS_STRING && !strip_auth) {
zval *digest = Z_CLIENT_DIGEST_P(this_ptr);

has_authorization = true;
Expand Down Expand Up @@ -790,16 +830,16 @@ bool make_http_soap_request(
make_digest(response, hash);

smart_str_append_const(&soap_headers, "Authorization: Digest username=\"");
smart_str_append(&soap_headers, Z_STR_P(login));
soap_smart_str_append_header_value(&soap_headers, Z_STR_P(login), "Authorization");
if ((tmp = zend_hash_str_find(Z_ARRVAL_P(digest), "realm", sizeof("realm")-1)) != NULL &&
Z_TYPE_P(tmp) == IS_STRING) {
smart_str_append_const(&soap_headers, "\", realm=\"");
smart_str_append(&soap_headers, Z_STR_P(tmp));
soap_smart_str_append_header_value(&soap_headers, Z_STR_P(tmp), "Authorization");
}
if ((tmp = zend_hash_str_find(Z_ARRVAL_P(digest), "nonce", sizeof("nonce")-1)) != NULL &&
Z_TYPE_P(tmp) == IS_STRING) {
smart_str_append_const(&soap_headers, "\", nonce=\"");
smart_str_append(&soap_headers, Z_STR_P(tmp));
soap_smart_str_append_header_value(&soap_headers, Z_STR_P(tmp), "Authorization");
}
smart_str_append_const(&soap_headers, "\", uri=\"");
if (uri->path) {
Expand Down Expand Up @@ -829,12 +869,12 @@ bool make_http_soap_request(
if ((tmp = zend_hash_str_find(Z_ARRVAL_P(digest), "opaque", sizeof("opaque")-1)) != NULL &&
Z_TYPE_P(tmp) == IS_STRING) {
smart_str_append_const(&soap_headers, "\", opaque=\"");
smart_str_append(&soap_headers, Z_STR_P(tmp));
soap_smart_str_append_header_value(&soap_headers, Z_STR_P(tmp), "Authorization");
}
if ((tmp = zend_hash_str_find(Z_ARRVAL_P(digest), "algorithm", sizeof("algorithm")-1)) != NULL &&
Z_TYPE_P(tmp) == IS_STRING) {
smart_str_append_const(&soap_headers, "\", algorithm=\"");
smart_str_append(&soap_headers, Z_STR_P(tmp));
soap_smart_str_append_header_value(&soap_headers, Z_STR_P(tmp), "Authorization");
}
smart_str_append_const(&soap_headers, "\"\r\n");
} else {
Expand Down Expand Up @@ -899,7 +939,9 @@ bool make_http_soap_request(
smart_str_append_const(&soap_headers, "\r\n");
}

http_context_headers(context, has_authorization, has_proxy_authorization, has_cookies, &soap_headers);
/* Passing true for the credential headers makes it skip them */
http_context_headers(context, has_authorization || strip_auth, has_proxy_authorization,
has_cookies || strip_auth, &soap_headers);

smart_str_append_const(&soap_headers, "\r\n");
smart_str_0(&soap_headers);
Expand Down Expand Up @@ -1026,6 +1068,7 @@ bool make_http_soap_request(
if (eqpos != NULL && (sempos == NULL || sempos > eqpos)) {
zval zcookie;
size_t cookie_value_len;
bool foreign_domain = false;

if (sempos != NULL) {
cookie_value_len = sempos-(eqpos+1);
Expand All @@ -1048,7 +1091,13 @@ bool make_http_soap_request(
add_index_stringl(&zcookie, 1, eqpos, sempos?(size_t)(sempos-eqpos):strlen(eqpos));
} else if (strstr(options,"domain=") == options) {
eqpos = options + sizeof("domain=")-1;
add_index_stringl(&zcookie, 2, eqpos, sempos?(size_t)(sempos-eqpos):strlen(eqpos));
size_t domain_len = sempos ? (size_t)(sempos-eqpos) : strlen(eqpos);
/* A server must not set cookies for other hosts */
if (!cookie_domain_matches(uri->host, eqpos, domain_len)) {
foreign_domain = true;
break;
}
add_index_stringl(&zcookie, 2, eqpos, domain_len);
} else if (strstr(options,"secure") == options) {
add_index_bool(&zcookie, 3, 1);
}
Expand All @@ -1059,19 +1108,23 @@ bool make_http_soap_request(
}
}
}
if (!zend_hash_index_exists(Z_ARRVAL(zcookie), 1)) {
char *t = uri->path?ZSTR_VAL(uri->path):"/";
char *c = strrchr(t, '/');
if (c) {
add_index_stringl(&zcookie, 1, t, c-t);
if (foreign_domain) {
zval_ptr_dtor(&zcookie);
} else {
if (!zend_hash_index_exists(Z_ARRVAL(zcookie), 1)) {
char *t = uri->path?ZSTR_VAL(uri->path):"/";
char *c = strrchr(t, '/');
if (c) {
add_index_stringl(&zcookie, 1, t, c-t);
}
}
if (!zend_hash_index_exists(Z_ARRVAL(zcookie), 2)) {
add_index_str(&zcookie, 2, uri->host);
GC_ADDREF(uri->host);
}
}
if (!zend_hash_index_exists(Z_ARRVAL(zcookie), 2)) {
add_index_str(&zcookie, 2, uri->host);
GC_ADDREF(uri->host);
}

zend_symtable_update(Z_ARRVAL_P(cookies), name, &zcookie);
zend_symtable_update(Z_ARRVAL_P(cookies), name, &zcookie);
}
zend_string_release_ex(name, false);
}

Expand Down Expand Up @@ -1191,6 +1244,9 @@ bool make_http_soap_request(
}
}
}
if (!same_origin(uri, new_uri)) {
strip_auth = true;
}
uri = new_uri;

if (--redirect_max < 1) {
Expand All @@ -1210,7 +1266,7 @@ bool make_http_soap_request(
zval *password = Z_CLIENT_PASSWORD_P(this_ptr);
char *auth = get_http_header_value(http_headers, "WWW-Authenticate:");
if (auth && strstr(auth, "Digest") == auth && Z_TYPE_P(digest) != IS_ARRAY
&& Z_TYPE_P(login) == IS_STRING && Z_TYPE_P(password) == IS_STRING) {
&& Z_TYPE_P(login) == IS_STRING && Z_TYPE_P(password) == IS_STRING && !strip_auth) {
char *s;
zval digest;

Expand Down
4 changes: 2 additions & 2 deletions ext/soap/tests/bugs/cookie_parse_options_offset.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ if (php_ini_loaded_file()) {
// falsely matching it as a path attribute.
$code = <<<'PHP'
header("Content-Type: text/xml");
header("Set-Cookie: sessionkey=path=/evil;domain=good.com");
header("Set-Cookie: sessionkey=path=/evil;domain=localhost");
echo <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="test-uri">
Expand Down Expand Up @@ -58,4 +58,4 @@ echo "domain: " . $cookies['sessionkey'][2] . "\n";
--EXPECT--
value: path=/evil
path: /test
domain: good.com
domain: localhost
Loading
Loading