1
0
mirror of https://github.com/d47081/qBittorrent.git synced 2025-01-28 15:34:16 +00:00

[WebUI]: Implement CSRF defense

Bump API version
This commit is contained in:
Chocobo1 2017-05-12 14:46:32 +08:00 committed by sledgehammer999
parent 34c7465009
commit 087856d3d8
No known key found for this signature in database
GPG Key ID: 6E4A2D025B7CC9A2
4 changed files with 45 additions and 1 deletions

View File

@ -43,8 +43,12 @@ namespace Http
const char HEADER_CONTENT_SECURITY_POLICY[] = "Content-Security-Policy";
const char HEADER_CONTENT_TYPE[] = "Content-Type";
const char HEADER_DATE[] = "Date";
const char HEADER_HOST[] = "host";
const char HEADER_ORIGIN[] = "origin";
const char HEADER_REFERER[] = "referer";
const char HEADER_SET_COOKIE[] = "Set-Cookie";
const char HEADER_X_CONTENT_TYPE_OPTIONS[] = "X-Content-Type-Options";
const char HEADER_X_FORWARDED_HOST[] = "x-forwarded-host";
const char HEADER_X_FRAME_OPTIONS[] = "X-Frame-Options";
const char HEADER_X_XSS_PROTECTION[] = "X-XSS-Protection";

View File

@ -36,6 +36,7 @@
#include <QNetworkCookie>
#include <QTemporaryFile>
#include <QTimer>
#include <QUrl>
#include "base/preferences.h"
#include "base/utils/fs.h"
@ -113,6 +114,12 @@ Http::Response AbstractWebApplication::processRequest(const Http::Request &reque
header(Http::HEADER_X_CONTENT_TYPE_OPTIONS, "nosniff");
header(Http::HEADER_CONTENT_SECURITY_POLICY, "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; object-src 'none';");
// block cross-site requests
if (isCrossSiteRequest(request_)) {
status(401, "Unauthorized");
return response();
}
sessionInitialize();
if (!sessionActive() && !isAuthNeeded())
sessionStart();
@ -368,6 +375,38 @@ QString AbstractWebApplication::saveTmpFile(const QByteArray &data)
return QString();
}
bool AbstractWebApplication::isCrossSiteRequest(const Http::Request &request) const
{
// https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Verifying_Same_Origin_with_Standard_Headers
const auto isSameOrigin = [](const QUrl &left, const QUrl &right) -> bool
{
// [rfc6454] 5. Comparing Origins
return ((left.port() == right.port())
// && (left.scheme() == right.scheme()) // not present in this context
&& (left.host() == right.host()));
};
const QString targetOrigin = request.headers.value(Http::HEADER_X_FORWARDED_HOST, request.headers[Http::HEADER_HOST]);
const QString originValue = request.headers.value(Http::HEADER_ORIGIN);
const QString refererValue = request.headers.value(Http::HEADER_REFERER);
if (originValue.isEmpty() && refererValue.isEmpty()) {
if ((request.path == QLatin1String("/")) || (request.path == QLatin1String("/favicon.ico")))
return false; // normal request
return true;
}
// sent with CORS requests, as well as with POST requests
if (!originValue.isEmpty())
return !isSameOrigin(QUrl::fromUserInput(targetOrigin), originValue);
if (!refererValue.isEmpty())
return !isSameOrigin(QUrl::fromUserInput(targetOrigin), refererValue);
return true;
}
const QStringMap AbstractWebApplication::CONTENT_TYPE_BY_EXT = {
{ "htm", Http::CONTENT_TYPE_HTML },
{ "html", Http::CONTENT_TYPE_HTML },

View File

@ -101,6 +101,7 @@ private:
bool sessionInitialize();
QStringMap parseCookie(const Http::Request &request) const;
bool isCrossSiteRequest(const Http::Request &request) const;
static void translateDocument(QString &data);

View File

@ -50,7 +50,7 @@
#include "webapplication.h"
static const int API_VERSION = 14;
static const int API_VERSION_MIN = 13;
static const int API_VERSION_MIN = 14;
const QString WWW_FOLDER = ":/www/public/";
const QString PRIVATE_FOLDER = ":/www/private/";