Allow subdomain wildcards to be specified for CORS origin headers using https.

Restrict default build to acceptable Netflix and YouTube origin headers; created new debug makefile target for full whitelist testing.
This commit is contained in:
Wesley Miaw
2020-06-02 17:19:53 -07:00
parent e82bda0149
commit d789ef37ff
7 changed files with 100 additions and 35 deletions

BIN
client/dialclient_debug Executable file

Binary file not shown.

View File

@@ -6,6 +6,8 @@ CC=$(TARGET)g++
includes = $(wildcard *.h)
OBJS := main.cpp DialServer.cpp DialDiscovery.cpp DialConformance.cpp DialClientInput.cpp
debug: dialclient_debug
# You may not need all these libraries. This example uses a build of curl that needs crypto, ssl, cares, and zlib
dialclient: $(OBJS) ${includes}
$(CC) -Wall -Werror -g $(OBJS) $(INCLUDES) $(LDFLAGS) -ldl -lpthread -lcurl -lz -lcrypto -lssl -o dialclient

View File

@@ -3,6 +3,8 @@ DIRS += server
all:
for dir in $(DIRS); do (make -C $$dir || exit 1) || exit 1; done
debug:
for dir in $(DIRS); do (make debug -C $$dir || exit 1) || exit 1; done
clean:
for dir in $(DIRS); do (make clean -C $$dir || exit 1) || exit 1; done

View File

@@ -492,24 +492,47 @@ static void handle_dial_data(struct mg_connection *conn,
ds_unlock(ds);
}
static int host_matches(const char *origin_host, const char *candidate) {
/**
* Returns true if the origin is acceptable based on the candidate value.
* The candidate may accept any subdomain if its domain begins with *. and
* may require an exact port number match if it includes a colon to
* indicate a port number.
*
* This function assumes that the candidate value is well-formed, meaning
* it will not include invalid characters or a non-numeric port number.
*
* @param origin the origin header value, which must begin with https://
* @param candidate the accepted origin whitelist value, which must begin
* with https://
* @return true if accepted and false if not.
*/
static int host_matches(const char *origin, const char *candidate) {
// Make sure there is something to compare.
if (!origin_host || !candidate)
if (!origin || !candidate)
return 0;
// Make sure the candidate also begins with HTTPS.
if (strncmp(candidate, gHttpsProto, strlen(gHttpsProto)) != 0)
// Make sure the origin and candidate both begin with HTTPS.
const size_t https_len= strlen(gHttpsProto);
if (strncmp(origin, gHttpsProto, https_len) != 0 ||
strncmp(candidate, gHttpsProto, https_len) != 0)
{
return 0;
}
// For the rest of the check, we only care about the hostname and optional
// port number.
const char *host = candidate + strlen(gHttpsProto);
// If the host contains a port number (indicated by a colon) require an
// exact match.
const char * origin_host = origin + https_len;
const char * host = candidate + https_len;
// Set the initial lengths for comparison.
size_t origin_len = strlen(origin_host);
size_t host_len = strlen(host);
// Look for port numbers.
const char * origin_colon = strrchr(origin_host, ':');
const char * host_colon = strrchr(host, ':');
// If the host contains a port number (indicated by a colon)...
if (host_colon != NULL) {
// If the host port number is 443, then accept the origin host if it
// does not have any port number under the assumption we already
@@ -518,27 +541,59 @@ static int host_matches(const char *origin_host, const char *candidate) {
strncmp(host_colon, ":443", 4) == 0 &&
origin_colon == NULL)
{
// Strip off the host port number and require an exact match.
size_t host_len = host_colon - host;
if (host_len == 0)
return 0;
return strncmp(origin_host, host, host_len) == 0;
// We will ignore the host port number of 443.
host_len = host_colon - host;
}
// Other port numbers must match exactly.
if (strlen(origin_host) != strlen(host))
return 0;
return strncmp(origin_host, host, strlen(host)) == 0;
// Other port numbers must match exactly. So leave the host length
// untouched.
}
// Otherwise strip off any port number in the origin host, and require an
// exact match.
size_t origin_len = (origin_colon == NULL) ? strlen(origin_host) : origin_colon - origin_host;
if (origin_len == 0)
// Otherwise ignore any port number in the origin.
else if (origin_colon != NULL) {
origin_len = origin_colon - origin_host;
}
// At this point, the origin length excludes any port number if the host
// does not specify one, and the host length excludes its port number if
// it was 443 and there is no origin port number.
//
// If either length is zero then fail.
if (origin_len == 0 || host_len == 0)
return 0;
if (origin_len != strlen(host))
return 0;
return strncmp(origin_host, host, origin_len) == 0;
// Check to see if the host permits subdomains.
const char * wildcard = "*.";
const int acceptSubdomain = (host_len > strlen(wildcard))
? strncmp(host, wildcard, strlen(wildcard)) == 0
: 0;
// If the host accepts subdomains, verify that the origin ends with the
// portion of the host that occurs after the subdomain wildcard.
if (acceptSubdomain) {
// The origin must be at least as long as the host.
if (origin_len < host_len)
return 0;
// Skip the subdomain of the origin, which should equate to the
// wildcard of the host. Likewise skip the wildcard of the host.
const char * origin_domain = strchr(origin_host, '.');
const char * host_domain = host + 1;
if (!origin_domain || !host_domain)
return 0;
// Remove from comparison the characters we skipped.
origin_len -= (origin_domain - origin_host);
host_len -= 1;
// The remainder must be an exact match.
return (origin_len == host_len &&
strncmp(origin_domain, host_domain, origin_len) == 0);
}
// Otherwise the host and origin must be an exact match.
return (origin_len == host_len &&
strncmp(origin_host, host, origin_len) == 0);
}
static int origin_matches(const char *origin, const char *candidate) {
@@ -559,7 +614,6 @@ static int is_uri_in_list(const char *origin, const char *list) {
return 0;
int isHttps = (strncmp(origin, gHttpsProto, strlen(gHttpsProto)) == 0);
const char * origin_host = (isHttps) ? origin + strlen(gHttpsProto) : NULL;
const char * scanPointer = list;
const char * spacePointer;
@@ -587,7 +641,7 @@ static int is_uri_in_list(const char *origin, const char *list) {
// If the URI begins with https://, perform a host comparison because
// any port numbers must be handled specially. Otherwise require an
// exact match.
if ((isHttps && host_matches(origin_host, candidate)) ||
if ((isHttps && host_matches(origin, candidate)) ||
(!isHttps && origin_matches(origin, candidate)))
{
free(candidate); candidate = NULL;
@@ -596,7 +650,7 @@ static int is_uri_in_list(const char *origin, const char *list) {
scanPointer = scanPointer + copyLength + 1; // assumption: only 1 character
}
free(candidate); candidate = NULL;
return ((isHttps && host_matches(origin_host, scanPointer)) ||
return ((isHttps && host_matches(origin, scanPointer)) ||
(!isHttps && origin_matches(origin, scanPointer)));
}

View File

@@ -288,8 +288,13 @@ void runDial(void)
struct DIALAppCallbacks cb_yt = {youtube_start, youtube_hide, youtube_stop, youtube_status};
struct DIALAppCallbacks cb_system = {system_start, system_hide, NULL, system_status};
#if defined DEBUG
if (DIAL_register_app(ds, "Netflix", &cb_nf, NULL, 1, "https://netflix.com https://www.netflix.com https://port.netflix.com:123") == -1 ||
DIAL_register_app(ds, "YouTube", &cb_yt, NULL, 1, "https://youtube.com https://www.youtube.com https://port.youtube.com:123 package:com.google.android.youtube package:com.google.ios.youtube") == -1 ||
DIAL_register_app(ds, "YouTube", &cb_yt, NULL, 1, "https://youtube.com https://www.youtube.com https://*.youtube.com:443 https://port.youtube.com:123 package:com.google.android.youtube package:com.google.ios.youtube") == -1 ||
#else
if (DIAL_register_app(ds, "Netflix", &cb_nf, NULL, 1, "https://netflix.com https://www.netflix.com") == -1 ||
DIAL_register_app(ds, "YouTube", &cb_yt, NULL, 1, "https://youtube.com https://*.youtube.com package:com.google.android.youtube package:com.google.ios.youtube") == -1 ||
#endif
DIAL_register_app(ds, "system", &cb_system, NULL, 1, "") == -1)
{
printf("Unable to register DIAL applications.\n");

View File

@@ -12,16 +12,18 @@ HEADERS := $(wildcard *.h)
# $(CC) -Wall -Werror -g -std=gnu99 $(CFLAGS) -c $*.c -o $*.o
$(CC) -Wall -g -fPIC -std=gnu99 $(CFLAGS) -c $*.c -o $*.o
all: dialserver test
all: dialserver
debug: dialserver_debug test
nf_callbacks_lib: nf_callbacks.o
# $(CC) -Wall -Werror -g nf_callbacks.o -o libnfCallbacks.so --shared
$(CC) -Wall -Werror -Wl,-undefined -Wl,dynamic_lookup -g nf_callbacks.o -o libnfCallbacks.so --shared
dialserver: nf_callbacks_lib $(OBJS)
$(CC) -Wall -Werror -Wl,-rpath,. -g $(OBJS) -ldl -lpthread -lrt -L. -lnfCallbacks -o dialserver
dialserver_debug: nf_callbacks_lib $(OBJS)
$(CC) -DDEBUG -Wall -Werror -Wl,-rpath,. -g $(OBJS) -ldl -lpthread -lrt -L. -lnfCallbacks -o dialserver
dialserver_with_ASAN: nf_callbacks_lib $(OBJS)
$(CC) -Wall -Werror -fsanitize=address -Wl,-rpath,. -g $(OBJS) -ldl -lpthread -lrt -L. -lnfCallbacks -o dialserver_with_ASAN

View File

@@ -21,7 +21,7 @@ then
fi
done
origins="https://www.youtube.com https://youtube.com https://port.youtube.com:123 https://www.youtube.com:80 https://www.youtube.com:123 package:com.google.android.youtube package:com.google.ios.youtube"
origins="https://www.youtube.com https://music.youtube.com https://youtube.com https://port.youtube.com:123 https://www.youtube.com:80 https://www.youtube.com:123 package:com.google.android.youtube package:com.google.ios.youtube"
for origin in $origins; do
curl --fail --silent --header "Origin:$origin" --data "v=QH2-TGUlwu4" http://$ip_address:$port/apps/YouTube || echo "failed: $origin should be accepted"
curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/YouTube || echo "failed: $origin should be accepted"
@@ -46,7 +46,7 @@ then
fi
done
origins="http://www.youtube-a.com http://www.youtube.com4 http://a-youtube.com https://ww.youtube-a.com http://www4.youtube.com https://port.youtube.com:1234 http://1.youtube.com https://www4.youtube.com https://www.youtube.com4 https://a-youtube.com http://youtube.com http://www.attack.com https://www.attack.com file://www.attack.com ftp://this.is.not.fine packagecom.google.android.youtube package:com.google.android.utube"
origins="http://www.youtube-a.com http://www.youtube.com4 https://.youtube.com http://a-youtube.com https://ww.youtube-a.com http://www4.youtube.com https://port.youtube.com:1234 http://1.youtube.com https://www.youtube.com4 https://a-youtube.com http://youtube.com http://www.attack.com https://www.attack.com file://www.attack.com ftp://this.is.not.fine packagecom.google.android.youtube package:com.google.android.utube"
for origin in $origins; do
curl --fail --silent --header "Origin:$origin" --data "v=QH2-TGUlwu4" http://$ip_address:$port/apps/YouTube && echo "failed: $origin should be rejected"
curl --fail --silent --header "Origin:$origin" -X OPTIONS http://$ip_address:$port/apps/YouTube && echo "failed: $origin should be rejected"