Web Security für alle: HSTS Never gonna give you up

Web Security für alle: Redirect to https

Published on Wednesday, September 21, 2016 4:00:00 AM UTC in Programming & Security

Da wir nun ein glitzerndes Hochglanz-Zertifikat für den SSL-Betrieb der Webseite haben, mit dem die eigene Seite per https angesteuert werden kann und von den Browsern mit mehr oder weniger grünen Vorhängeschlössern präsentiert wird, ist ja alles in bester Ordnung - oder? Hmm..., mal sehen, was der Sicherheits-Check von Mozilla dazu sagt.

Nach einem ausgiebigen Test der eigenen Domäne erhält man dort eine Bewertung nach amerikanischem Schulnoten-System, von A wie "absolute Sahne" bis F wie "fergiss es". Nach dem Einrichten des Zertifikats von Let's Encrypt hatte meine Seite selbstverständlich eine Note von...

mozilla-observatory-f.png

Wuuuaaaaat? oO

Nun, trotz 4 von 10 geschafften Tests beträgt der Score nur 0/100. Etwas weiter unten liefert Mozilla die Erklärung für das allzu schlechte Abschneiden:

mozilla-observatory-f-scores.png

Diverse böhmische Dörfer geben hier anscheinend kräftig Punktabzug. Zeit, das zu ändern und sich schlau zu machen.

Redirect to https

Das offensichtlichste Problem nach der Installation eines SSL-Zertifikats ist, dass der Benutzer die schnöde http-Variante der Seite immer noch benutzen kann. Wenn er eine Url in die Adresszeile des Browsers tippt oder im Netz auf einen der sicherlich millionenfach kursierenden alten Links zur eigenen Seite klickt, liefert der Server klaglos alle Anfragen per http aus, obwohl auch eine gesicherte Verbindung möglich wäre.

Um das zu ändern, bemüht man für gewöhnlich eine Weiterleitung, die beim Aufruf eines http-Links zur entsprechenden https-Variante verweist. Für die Implementierung gibt es ganz verschiedene Möglichkeiten, beispielsweise per URL-Rewrite direkt auf IIS-Ebene. Da ich aber den Code meiner Webseite möglichst self-contained halten möchte und außerdem daran interessiert war, wie man eine entsprechende Funktion in ASP.NET Core umsetzen würde, habe ich dafür eine kleine Middleware geschrieben. Die Idee ist, recht früh in der Pipeline von ASP.NET Core zu prüfen, ob die angefragte Adresse das https-Schema nutzt, und ansonsten per 301-Redirect auf eben dieses weiterzuleiten. Eine 301-Weiterleitung (moved permanently) wird übrigens ausdrücklich empfohlen (kein 302 - temporarily moved), damit ggfs. im Netz herumlungernde Links nachhaltig aktualisiert werden können.

Eine Middleware in ASP.NET Core muss kein besonderes Interface implementieren, sondern lediglich eine öffentliche Methode Invoke anbieten, die einer passenden Signatur entspricht. Da ich die https-Weiterleitung an- und abschalten können möchte, habe ich mir noch ein kleines Konfigurationsobjekt erstellt, mit dem ich das steuern kann (dazu gleich noch mehr). Im Kern sieht die Implementierung so aus:

private readonly RequestDelegate _next;
private readonly HttpsRedirectOptions _options;

public HttpsRedirectMiddleware(RequestDelegate next, IOptions<HttpsRedirectOptions> options)
{
    _next = next;
    _options = options.Value ?? HttpsRedirectOptions.Default;
}

public async Task Invoke(HttpContext context)
{
    if (!_options.IsHttpsRedirectEnabled || context.Request.IsHttps)
    {
        await _next(context);
        return;
    }

    var httpsUri = CreateHttpsUri(context.Request);

    // loggin'n'stuff goes here...

    // permanent redirect
    context.Response.Redirect(httpsUri, true);
}

Die https-Uri wird recht simpel zusammengebastelt, wobei auch hier die Möglichkeit besteht, über die Konfiguration Einfluss zu nehmen:

private const int StandardHttpsPort = 443;

private string CreateHttpsUri(HttpRequest request)
{
    var port = _options.HttpsPort ?? StandardHttpsPort;

    var host = port != StandardHttpsPort 
        ? new HostString(request.Host.Host, port)
        : new HostString(request.Host.Host);

    var result = $"{Uri.UriSchemeHttps}{Uri.SchemeDelimiter}{host}{request.PathBase}{request.Path}{request.QueryString}";
    return result;
}

Letztendlich wird nur das Schema ersetzt und der restliche Teil des Requests unverändert übernommen. Der Kniff, den Port steuern zu können, erlaubt es, zur Entwicklungszeit die Middleware ebenfalls zu nutzen. Schließlich verwendet beispielsweise der IIS Express bei der Entwicklung standardmäßig eben nicht 443, sondern einen Port >=44300 für https. Über den Konfigurationsmechanismus von ASP.NET Core kann ich nun für die Umgebung Development eine passende Konfiguration anlegen, während produktiv der "richtige" Port verwendet wird, den die Browser standardmäßig nutzen. Die "appsettings.Development.json" sieht also etwa so aus:

"HttpsRedirectOptions": {
  "HttpsPort": 44337
}

Die Startup-Klasse fügt natürlich die Puzzleteile zusammen, indem sie in der Methode ConfigureServices die Options bindet...

services.AddOptions();
services.Configure<HttpsRedirectOptions>(options => Configuration.GetSection("HttpsRedirectOptions").Bind(options));

... und in der Configure-Methode die Middleware einbindet:

app.UseMiddleware<HttpsRedirectMiddleware>();

Fertig! Ab nun führt der Aufruf eines http-Links zur Weiterleitung auf https. Hat man den Rest der Seite sauber implementiert (also seiteninterne Verweise ohne Schema abgelegt), geht es von hier an ausschließlich per https weiter.

Ergebnis

Mit dieser deutlichen Verbesserung erlangt man im Observatory-Test von Mozilla dann die atemberaubende Wertung von...

mozilla-observatory-f-after-redirect.png

Gnaaaaa... 5/10 bzw. 20/100 erreichte Punkte sind wohl noch nicht genug, um übers "F" hinaus zu kommen... also Ärmel hochkrempeln und weiter geht's - das nächste Mal ;).

Tags: ASP.NET Core · Https