On 2023-06-30 10:46 +02, Omar Polo <o...@omarpolo.com> wrote:
> On 2023/06/29 23:43:25 +0200, Omar Polo <o...@omarpolo.com> wrote:
>> On 2023/06/29 19:55:52 +0200, Florian Obser <flor...@openbsd.org> wrote:
>> > I'm worried that we pass un-sanitized input through to fcgi.
>> > Of course we are passing *a lot* of un-sanitized input through to fcgi,
>> > so does this matter in the grand scheme of things?
>> > But I'd like if server_http_parsehost() enforces syntactically valid
>> > hostnames first.
>> > 
>> > There is valid_domain() in usr.bin/ssh/misc.c or res_hnok in
>> > resolv.h. Which is probably fine to use since we don't care too much
>> > about portable.
>> 
>> right, httpd is happily accepting any nonsense as Host.  Here's an
>> adaptation of millert' valid_domain() from usr.bin/ssh/misc.c.
>> Hope noone is relying on using an empty/malformed Host :>
>
> actually this is wrong.  it breaks when an ipv6 address is used as
> Host.  this seems to be an issue in ssh too, although I haven't

IIRC ssh only uses valid_domain() when it already knows that it's not an
IPv6 address.

> checked if the ssh URI specification inherits the authority from
> rfc3986 as-is.
>
> here's a slightly revised version, but just for the sake of the
> discussion since its ipv6 parsing is too permissive.
>
> should I make it strictier (not done just because it's a bit long and
> definitely not fun to validate ipv6 addresses), or there's something
> else in base that I can steal fom? :)

getaddrinfo(3) with AI_NUMERICHOST.

>
> (while here I've tweaked slightly the nonsense in
> server_http_parsehost wrt ipv6 addresses and port numbers.)
>
>
> Thanks!
>
>
> diff /usr/src
> commit - 9832f5035d799ae12e9f848cff5650481b673260
> path + /usr/src
> blob - 091cc86fec946a4a9d422c04a48f5cbfae015ce0
> file + usr.sbin/httpd/server_http.c
> --- usr.sbin/httpd/server_http.c
> +++ usr.sbin/httpd/server_http.c
> @@ -831,6 +831,60 @@ char *
>       return (buf);
>  }
>  
> +static int
> +valid_domain(char *name, const char **errstr)
> +{
> +     size_t i;
> +     unsigned char c, last = '\0';
> +
> +     *errstr = NULL;
> +
> +     if (*name == '\0') {
> +             *errstr = "empty domain name";
> +             return 0;
> +     }
> +
> +     if (name[0] == '[') {
> +             for (i = 1; name[i] != '\0'; ++i) {
> +                     if (name[i] == ']')
> +                             break;
> +                     if (!isxdigit((unsigned char)name[i]) &&
> +                         name[i] != ':') {
> +                             *errstr = "invalid IPv6 address";
> +                             return 0;
> +                     }
> +             }
> +             if (name[i] != ']' || name[i + 1] != '\0') {
> +                     *errstr = "invalid IPv6 address";
> +                     return 0;
> +             }
> +             return 1;
> +     }
> +
> +     if (!isalpha((unsigned char)name[0]) &&
> +         !isdigit((unsigned char)name[0])) {
> +             *errstr = "domain name starts with an invalid character";
> +             return 0;
> +     }
> +     for (i = 0; name[i] != '\0'; ++i) {
> +             c = tolower((unsigned char)name[i]);
> +             name[i] = c;
> +             if (last == '.' && c == '.') {
> +                     *errstr = "domain name contains consecutive separators";
> +                     return 0;
> +             }
> +             if (c != '.' && c != '-' && !isalnum(c) &&
> +                 c != '_') /* technically invalid, but common */ {
> +                     *errstr = "domain name contains invalid characters";
> +                     return 0;
> +             }
> +             last = c;
> +     }
> +     if (name[i - 1] == '.')
> +             name[i - 1] = '\0';
> +     return 1;
> +}
> +
>  char *
>  server_http_parsehost(char *host, char *buf, size_t len, int *portval)
>  {
> @@ -847,13 +901,11 @@ server_http_parsehost(char *host, char *buf, size_t le
>  
>       if (*start == '[' && (end = strchr(start, ']')) != NULL) {
>               /* Address enclosed in [] with port, eg. [2001:db8::1]:80 */
> -             start++;

are you forgetting to skip over the opening '['?

> -             *end++ = '\0';
> -             if ((port = strchr(end, ':')) == NULL || *port == '\0')
> -                     port = NULL;
> -             else
> -                     port++;
> -             memmove(buf, start, strlen(start) + 1);
> +             end++;
> +             if (*end == ':') {
> +                     *end++ = '\0';
> +                     port = end;
> +             }
>       } else if ((end = strchr(start, ':')) != NULL) {
>               /* Name or address with port, eg. www.example.com:80 */
>               *end++ = '\0';
> @@ -863,6 +915,11 @@ server_http_parsehost(char *host, char *buf, size_t le
>               port = NULL;
>       }
>  
> +     if (!valid_domain(start, &errstr)) {
> +             log_debug("%s: %s: %s", __func__, errstr, host);
> +             return (NULL);
> +     }
> +
>       if (port != NULL) {
>               /* Save the requested port */
>               *portval = strtonum(port, 0, 0xffff, &errstr);
>
>

-- 
In my defence, I have been left unsupervised.

Reply via email to