Commit 6dbeba9d authored by William A. Rowe Jr's avatar William A. Rowe Jr
Browse files

Correctly parse an IPv6 literal host specification in an absolute URL

in the request line.

- Fix handling of brackets [ ] surrounding the IPv6 address.
- Skip parsing r->hostname again if not necessary.
- Do some checks that the IPv6 address is sane. This is not done by
  apr_parse_addr_port().

log client error at level debug, log broken Host header value

Backports: r1407006, r1426827
Submitted by: sf



git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x-merge-http-strict@1768035 13f79535-47bb-0310-9956-ffa450edef68
parent c40f5842
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -2,6 +2,9 @@

Changes with Apache 2.4.24

  *) core: Correctly parse an IPv6 literal host specification in an absolute
     URL in the request line. [Stefan Fritsch]

  *) core: New directive RegisterHttpMethod for registering non-standard
     HTTP methods. [Stefan Fritsch]

+114 −48
Original line number Diff line number Diff line
@@ -687,6 +687,66 @@ static int vhost_check_config(apr_pool_t *p, apr_pool_t *plog,
 * run-time vhost matching functions
 */

static apr_status_t fix_hostname_v6_literal(request_rec *r, char *host)
{
    char *dst;
    int double_colon = 0;

    for (dst = host; *dst; dst++) {
        if (apr_isxdigit(*dst)) {
            if (apr_isupper(*dst)) {
                *dst = apr_tolower(*dst);
            }
        }
        else if (*dst == ':') {
            if (*(dst + 1) == ':') {
                if (double_colon)
                    return APR_EINVAL;
                double_colon = 1;
            }
            else if (*(dst + 1) == '.') {
                return APR_EINVAL;
            }
        }
        else if (*dst == '.') {
            /* For IPv4-mapped IPv6 addresses like ::FFFF:129.144.52.38 */
            if (*(dst + 1) == ':' || *(dst + 1) == '.')
                return APR_EINVAL;
        }
        else {
            return APR_EINVAL;
        }
    }
    return APR_SUCCESS;
}

static apr_status_t fix_hostname_non_v6(request_rec *r, char *host)
{
    char *dst;

    for (dst = host; *dst; dst++) {
        if (apr_islower(*dst)) {
            /* leave char unchanged */
        }
        else if (*dst == '.') {
            if (*(dst + 1) == '.') {
                return APR_EINVAL;
            }
        }
        else if (apr_isupper(*dst)) {
            *dst = apr_tolower(*dst);
        }
        else if (*dst == '/' || *dst == '\\') {
            return APR_EINVAL;
        }
    }
    /* strip trailing gubbins */
    if (dst > host && dst[-1] == '.') {
        dst[-1] = '\0';
    }
    return APR_SUCCESS;
}

/* Lowercase and remove any trailing dot and/or :port from the hostname,
 * and check that it is sane.
 *
@@ -700,32 +760,38 @@ static int vhost_check_config(apr_pool_t *p, apr_pool_t *plog,
 * Instead we just check for filesystem metacharacters: directory
 * separators / and \ and sequences of more than one dot.
 */
static void fix_hostname(request_rec *r)
static void fix_hostname(request_rec *r, const char *host_header)
{
    const char *src;
    char *host, *scope_id;
    char *dst;
    apr_port_t port;
    apr_status_t rv;
    const char *c;

    /* According to RFC 2616, Host header field CAN be blank. */
    if (!*r->hostname) {
    src = host_header ? host_header : r->hostname;

    /* According to RFC 2616, Host header field CAN be blank.
     * XXX But only 'if the requested URI does not include an Internet host
     * XXX name'. Can this happen?
     */
    if (!*src) {
        return;
    }

    /* apr_parse_addr_port will interpret a bare integer as a port
     * which is incorrect in this context.  So treat it separately.
     */
    for (c = r->hostname; apr_isdigit(*c); ++c);
    if (!*c) {  /* pure integer */
    for (c = src; apr_isdigit(*c); ++c);
    if (!*c) {
        /* pure integer */
        r->hostname = src;
        return;
    }

    rv = apr_parse_addr_port(&host, &scope_id, &port, r->hostname, r->pool);
    if (rv != APR_SUCCESS || scope_id) {
    if (host_header) {
        rv = apr_parse_addr_port(&host, &scope_id, &port, src, r->pool);
        if (rv != APR_SUCCESS || scope_id)
            goto bad;
    }

        if (port) {
            /* Don't throw the Host: header's port number away:
               save it in parsed_uri -- ap_get_server_port() needs it! */
@@ -735,32 +801,24 @@ static void fix_hostname(request_rec *r)
            r->parsed_uri.port = port;
            r->parsed_uri.port_str = apr_itoa(r->pool, (int)port);
        }

    /* if the hostname is an IPv6 numeric address string, it was validated
     * already; otherwise, further validation is needed
     */
    if (r->hostname[0] != '[') {
        for (dst = host; *dst; dst++) {
            if (apr_islower(*dst)) {
                /* leave char unchanged */
            }
            else if (*dst == '.') {
                if (*(dst + 1) == '.') {
                    goto bad;
                }
        if (host_header[0] == '[')
            rv = fix_hostname_v6_literal(r, host);
        else
            rv = fix_hostname_non_v6(r, host);
    }
            else if (apr_isupper(*dst)) {
                *dst = apr_tolower(*dst);
    else {
        /*
         * Already parsed, surrounding [ ] (if IPv6 literal) and :port have
         * already been removed.
         */
        host = apr_pstrdup(r->pool, r->hostname);
        if (ap_strchr(host, ':') != NULL)
            rv = fix_hostname_v6_literal(r, host);
        else
            rv = fix_hostname_non_v6(r, host);
    }
            else if (*dst == '/' || *dst == '\\') {
    if (rv != APR_SUCCESS)
        goto bad;
            }
        }
        /* strip trailing gubbins */
        if (dst > host && dst[-1] == '.') {
            dst[-1] = '\0';
        }
    }
    r->hostname = host;
    return;

@@ -985,12 +1043,20 @@ static void check_serverpath(request_rec *r)

AP_DECLARE(void) ap_update_vhost_from_headers(request_rec *r)
{
    /* must set this for HTTP/1.1 support */
    if (r->hostname || (r->hostname = apr_table_get(r->headers_in, "Host"))) {
        fix_hostname(r);
    const char *host_header;

    if (r->hostname) {
        /*
         * If there was a host part in the Request-URI, ignore the 'Host'
         * header.
         */
        fix_hostname(r, NULL);
    }
    else if ((host_header = apr_table_get(r->headers_in, "Host")) != NULL ) {
        fix_hostname(r, host_header);
    }
    if (r->status != HTTP_OK)
        return;
    }
    /* check if we tucked away a name_chain */
    if (r->connection->vhost_lookup_data) {
        if (r->hostname)