cutelyst 4.3.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
Cutelyst::CSRFProtection Class Reference

Protect input forms against Cross Site Request Forgery (CSRF/XSRF) attacks. More...

#include <Cutelyst/Plugins/CSRFProtection/CSRFProtection>

Inheritance diagram for Cutelyst::CSRFProtection:

Public Member Functions

 CSRFProtection (Application *parent)
 
 CSRFProtection (Application *parent, const QVariantMap &defaultConfig)
 
 ~CSRFProtection () override
 
void setCookieHttpOnly (bool httpOnly)
 
void setCookieName (const QByteArray &cookieName)
 
void setDefaultDetachTo (const QString &actionNameOrPath)
 
void setErrorMsgStashKey (const QString &keyName)
 
void setFormFieldName (const QByteArray &fieldName)
 
void setGenericErrorContentType (const QByteArray &type)
 
void setGenericErrorMessage (const QString &message)
 
void setHeaderName (const QByteArray &headerName)
 
void setIgnoredNamespaces (const QStringList &namespaces)
 
void setUseSessions (bool useSessions)
 
- Public Member Functions inherited from Cutelyst::Plugin
 Plugin (Application *parent)
 
- Public Member Functions inherited from QObject
 QObject (QObject *parent)
 
bool blockSignals (bool block)
 
const QObjectListchildren () const const
 
QMetaObject::Connection connect (const QObject *sender, const char *signal, const char *method, Qt::ConnectionType type) const const
 
void deleteLater ()
 
void destroyed (QObject *obj)
 
bool disconnect (const char *signal, const QObject *receiver, const char *method) const const
 
bool disconnect (const QObject *receiver, const char *method) const const
 
void dumpObjectInfo () const const
 
void dumpObjectTree () const const
 
QList< QByteArraydynamicPropertyNames () const const
 
virtual bool event (QEvent *e)
 
virtual bool eventFilter (QObject *watched, QEvent *event)
 
findChild (const QString &name, Qt::FindChildOptions options) const const
 
QList< T > findChildren (const QRegularExpression &re, Qt::FindChildOptions options) const const
 
QList< T > findChildren (const QString &name, Qt::FindChildOptions options) const const
 
QList< T > findChildren (Qt::FindChildOptions options) const const
 
bool inherits (const char *className) const const
 
void installEventFilter (QObject *filterObj)
 
bool isQuickItemType () const const
 
bool isWidgetType () const const
 
bool isWindowType () const const
 
void killTimer (int id)
 
virtual const QMetaObjectmetaObject () const const
 
void moveToThread (QThread *targetThread)
 
QString objectName () const const
 
void objectNameChanged (const QString &objectName)
 
QObjectparent () const const
 
QVariant property (const char *name) const const
 
 Q_CLASSINFO (Name, Value)
 
 Q_DISABLE_COPY (Class)
 
 Q_DISABLE_COPY_MOVE (Class)
 
 Q_EMIT Q_EMIT
 
 Q_ENUM (...)
 
 Q_ENUM_NS (...)
 
 Q_ENUMS (...)
 
 Q_FLAG (...)
 
 Q_FLAG_NS (...)
 
 Q_FLAGS (...)
 
 Q_GADGET Q_GADGET
 
 Q_GADGET_EXPORT (EXPORT_MACRO)
 
 Q_INTERFACES (...)
 
 Q_INVOKABLE Q_INVOKABLE
 
 Q_MOC_INCLUDE Q_MOC_INCLUDE
 
 Q_NAMESPACE Q_NAMESPACE
 
 Q_NAMESPACE_EXPORT (EXPORT_MACRO)
 
 Q_OBJECT Q_OBJECT
 
 Q_PROPERTY (...)
 
 Q_REVISION Q_REVISION
 
 Q_SET_OBJECT_NAME (Object)
 
 Q_SIGNAL Q_SIGNAL
 
 Q_SIGNALS Q_SIGNALS
 
 Q_SLOT Q_SLOT
 
 Q_SLOTS Q_SLOTS
 
qobject_cast (const QObject *object)
 
qobject_cast (QObject *object)
 
 QT_NO_NARROWING_CONVERSIONS_IN_CONNECT QT_NO_NARROWING_CONVERSIONS_IN_CONNECT
 
void removeEventFilter (QObject *obj)
 
void setObjectName (const QString &name)
 
void setObjectName (QAnyStringView name)
 
void setParent (QObject *parent)
 
bool setProperty (const char *name, const QVariant &value)
 
bool signalsBlocked () const const
 
int startTimer (int interval, Qt::TimerType timerType)
 
int startTimer (std::chrono::milliseconds time, Qt::TimerType timerType)
 
QThreadthread () const const
 

Static Public Member Functions

static bool checkPassed (Context *c)
 
static QByteArray getToken (Context *c)
 
static QString getTokenFormField (Context *c)
 
- Static Public Member Functions inherited from QObject
QMetaObject::Connection connect (const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
 
QMetaObject::Connection connect (const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method, Qt::ConnectionType type)
 
QMetaObject::Connection connect (const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type)
 
QMetaObject::Connection connect (const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type)
 
QMetaObject::Connection connect (const QObject *sender, PointerToMemberFunction signal, Functor functor)
 
bool disconnect (const QMetaObject::Connection &connection)
 
bool disconnect (const QObject *sender, const char *signal, const QObject *receiver, const char *method)
 
bool disconnect (const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method)
 
bool disconnect (const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method)
 
QString tr (const char *sourceText, const char *disambiguation, int n)
 

Protected Member Functions

bool setup (Application *app) override
 
- Protected Member Functions inherited from QObject
virtual void childEvent (QChildEvent *event)
 
virtual void connectNotify (const QMetaMethod &signal)
 
virtual void customEvent (QEvent *event)
 
virtual void disconnectNotify (const QMetaMethod &signal)
 
bool isSignalConnected (const QMetaMethod &signal) const const
 
int receivers (const char *signal) const const
 
QObjectsender () const const
 
int senderSignalIndex () const const
 
virtual void timerEvent (QTimerEvent *event)
 

Additional Inherited Members

- Public Attributes inherited from QObject
typedef QObjectList
 
- Properties inherited from QObject
 objectName
 

Detailed Description

The CSRFProtection plugin implements a synchronizer token pattern (STP) to protect input forms against Cross Site Request Forgery (CSRF/XSRF) attacks. This type of attack occurs when a malicious website contains a link, a form button or some JavaScript that is intended to perform some action on your website, using the credentials of a logged-in user who visits the malicious site in their browser.

The first defense against CSRF attacks is to ensure that GET requests (and other safe methods, as defined by RFC 7231) are side effect free. Requests via unsafe methods, such as POST, PUT, and DELETE, can then be protected by this plugin.

This plugin has been inspired by the CSRF protection of the Django web framework.

Usage

For general usage and to protect any unsafe action, simply add the plugin to your application.

#include <Cutelyst/Plugins/CSRFProtection/CSRFProtection>
bool MyCutelystApp::init()
{
// other initialization stuff
auto csrfProtect = new CSRFProtection(this);
// optionally you can ignore complete namespaces from the protection
csrfProtect->setIgnoredNamespaces({QStringLiteral("foo")});
// more initialization stuff
}
Protect input forms against Cross Site Request Forgery (CSRF/XSRF) attacks.

To ignore an action from CSRF protection, add :CSRFIgnore to the attributes.

C_ATTR(index, :Path :AutoArgs :CSRFIgnore)
void index(Context *c);
The Cutelyst Context.
Definition context.h:42

If you have optionally ignored complete namespaces from the CSRF protection, you can require the protection for single namespace members by adding :CSRFRequire to the attributes.

class Foo : public Cutelyst::Controller
{
Q_OBJECT
C_NAMESPACE("foo")
public:
C_ATTR(index, :Path :AutoArgs)
void index(Context *c);
C_ATTR(edit, :Path :CSRFRequire)
void edit(Context *c);
}
Cutelyst Controller base class.
Definition controller.h:56

In your Cutelee template you should then use the {% c_csrf_token %} tag in your forms to add a hidden input field that contains the CSRF protection token.

<form method="post">
{% c_csrf_token %}
<input type="text" name="username">
<input type="password" name="password">
<button type="submit">Login</button>
</form>

You can optionally get the token by using CSRFProtection::getToken() static function if you want to add it to the stash or want to use it elsewhere. For example, if you don't have a form on your site but want to use the token for an AJAX request and want to add it therefore into a meta tag or something like that.

Handling failed checks

If the CSRF protection check fails, the return code will be set to 403 - Forbidden and an error message will be set to the stash key defined by setErrorMsgStashKey(). You can set a default action the application should detach to if the check failed via setDefaultDetachTo(), optionally there is the attribute :CSRFDetachTo that can be used to define a detach to action per method. When using an action to detach to if the check fails, do not forget to call Context::finalize() to escape the processing chain after that action. If the detach to action is not set or could not be found it will either set the response body to the content set by setGenericErrorMessage() of if that is absent it will generate a generic HTML content containing error information.

bool MyCutelystApp::init()
{
// constructs a new plugin and sets the default value for the
// log_failed_ip config file option to true
auto csrf = new CSRFProtection(this, {{"log_failed_ip", true}});
csrf->setDefaultDetachTo(QStringLiteral("csrffailed"));
}
class Foo : public Cutelyst::Controller
{
C_ATTR(foo, :Local :CSRFDetachTo(csrfDenied))
void foo(Context *c);
C_ATTR(csrfDenied, :Local :Private :AutoArgs :ActionClass(RenderView))
void csrfDenied(Context *c);
};
void Foo::csrfDenied(Context *c)
{
// handle the CSRF violation
c->res()->setStatus(403);
c->finalize();
}
void setDefaultDetachTo(const QString &actionNameOrPath)

AJAX and CSRF protection

If you are using ajax to submit form requests or if you use AJAX without a HTML form, you have to provide the CSRF token too. If you are using the normal way by setting a cookie, you can read the CSRF token from that cookie. If you use the session to store the token, you have to include the token somewhere into the DOM tree from where you can read it. You can then add the extracted token to the POST data of every POST request or you can can set a custom X-CSRFToken header to the value of the CSRF token. The latter method is often easier, because many JavaScript frameworks provide hooks that allow headers to be set on every request.

How it works

On every request, a secret token is set that is stored in a cookie or in the user session and has to be send back to the application when performing actions on unsafe methods like POST. The token stored in the cookie or in the session is salted with another random value. The same secret with a different salt has then to be sent to the application either via a hidden form field or via a HTTP request header.

To get the form field you can use the {% c_csrf_token %} tag in your Cutelee templates. If you are not using Cutelee or if you do not use a form but AJAX, you can use CSRFProtection::getToken() to place the token somewhere in your DOM tree so that you can read it with JavaScript.

Limitations

Subdomains within a site will be able to set cookies on the client for the whole domain. By setting the cookie and using a corresponding token, subdomains will be able to circumvent the CSRF protection. The only way to avoid this is to ensure that subdomains are controlled by trusted users (or, are at least unable to set cookies). Note that even without CSRF, there are other vulnerabilities, such as session fixation, that make giving subdomains to untrusted parties a bad idea, and these vulnerabilities cannot easily be fixed with current browsers.

Configuration file options

There are some options you can set in your application configuration file in the Cutelyst_CSRFProtection_Plugin section. You can override the defaults by setting a QVariantMap with selected default values to the constructor.

cookie_expiration

Type: string
Default: 1 year

The expiration time of the cookie. The value will be parsed by Utils::durationFromString(), so you can use one of the supported human readable time spans.

The reason for setting a long-lived expiration time is to avoid problems in the case of a user closing a browser or bookmarking a page and then loading that page from a browser cache. Without persistent cookies, the form submission would fail in this case.

Some browsers (specifically Internet Explorer) can disallow the use of persistent cookies or can have the indexes to the cookie jar corrupted on disk, thereby causing CSRF protection checks to (sometimes intermittently) fail. Change this setting to 0 to use session-based CSRF cookies, which keep the cookies in-memory instead of on persistent storage.

cookie_domain

Type: string
Default: empty

The domain to be used when setting the CSRF cookie. This can be useful for easily allowing cross-subdomain requests to be excluded from the normal cross site request forgery protection. It should be set to a string such as ".example.com" to allow a POST request from a form on one subdomain to be accepted by a view served from another subdomain.

Please note that the presence of this setting does not imply that the CSRF protection is safe from cross-subdomain attacks by default - please see the limitations section.

cookie_secure

Type: bool
Default: false

Whether to use a secure cookie for the CSRF cookie. If this is set to true, the cookie will be marked as secure, which means browsers may ensure that the cookie is only sent with an HTTPS connection.

cookie_same_site

Type: string
Default: strict
Acceptable values: default,none,lax,strict

Defines the SameSite attribute of the CSRF cookie. See MDN to learn more about SameSite cookies. See also QNetworkCookie::SameSite. This configuration key is available since Cutelyst 3.8.0.

trusted_origins

Type: string
Default: empty

A comma separated list of hosts which are trusted origins for unsafe requests (e.g. POST). For a secure unsafe request, the CSRF protection requires that the request have a Referer header that matches the origin present in the Host header. This prevents, for example, a POST request from subdomain.example.com from succeeding against api.example.com. If you need cross-origin unsafe requests over HTTPS, continuing the example, add "subdomain.example.com" to this list. The setting also supports subdomains, so you could add ".example.com", for example, to allow access from all subdomains of example.com.

log_failed_ip

Type: bool
Default: false

If this is set to true, the log output for failed checks will contain the IP address of the remote client.

Build options

This plugin is not enabled by default. Use -DPLUGIN_CSRFPROTECTION:BOOL=ON for your cmake configuration. To link it to your application use Cutelyst::CSRFProtection.

Logging category
cutelyst.plugin.csrfprotection
Logging with Cutelyst
Since
Cutelyst 1.12.0

Definition at line 233 of file csrfprotection.h.

Constructor & Destructor Documentation

◆ CSRFProtection() [1/2]

CSRFProtection::CSRFProtection ( Application parent)

Constructs a new CSRFProtection object with the given parent.

Definition at line 53 of file csrfprotection.cpp.

◆ CSRFProtection() [2/2]

CSRFProtection::CSRFProtection ( Application parent,
const QVariantMap &  defaultConfig 
)

Contructs a new CSRFProtection object with the given parent and defaultConfig.

Use the defaultConfig to set default values for the configuration entries from the configuration file.

Definition at line 59 of file csrfprotection.cpp.

◆ ~CSRFProtection()

CSRFProtection::~CSRFProtection ( )
overridedefault

Deconstructs the CSRFProtection object.

Member Function Documentation

◆ checkPassed()

bool CSRFProtection::checkPassed ( Context c)
static

Returns true if the CSRF protection check has successfully been passed. You can use this function in your contoller methods to handle CSRF protection results. For HTTP methods that are secure according to RFC 7231 (GET, HEAD, OPTIONS and TRACE) this will return always true. For all other methods it will return false if the CSRF protection check has failed.

Definition at line 264 of file csrfprotection.cpp.

References Cutelyst::Context::req, and Cutelyst::Context::stash().

◆ getToken()

QByteArray CSRFProtection::getToken ( Context c)
static

Returns the current token.

This method is also used by the Cutelee tag {% c_csrf_token_value %}.

Definition at line 228 of file csrfprotection.cpp.

References QByteArray::isEmpty(), Cutelyst::Context::setStash(), and Cutelyst::Context::stash().

Referenced by getTokenFormField().

◆ getTokenFormField()

QString CSRFProtection::getTokenFormField ( Context c)
static

Returns HTML code for a hidden input field that contains the current token and has the name set by setFormFieldName(). This method is also used by the Cutelee tag {% c_csrf_token %}.

Example output

<input type="hidden" name="csrfprotectiontoken"
value="e2RiYmI1YWJjLTJiZTctNDczYS1iMDM2ipApLPunLnnbkAQrzJWMo9GoyiQpzkeT">

Definition at line 248 of file csrfprotection.cpp.

References QString::arg(), QString::fromLatin1(), and getToken().

◆ setCookieHttpOnly()

void CSRFProtection::setCookieHttpOnly ( bool  httpOnly)

Whether to use HttpOnly flag on the CSRF cookie. If this is set to true, client-side JavaScript will not to be able to access the CSRF cookie. The default is false.

Designating the CSRF cookie as HttpOnly doesn’t offer any practical protection because CSRF is only to protect against cross-domain attacks. If an attacker can read the cookie via JavaScript, they’re already on the same domain as far as the browser knows, so they can do anything they like anyway. (XSS is a much bigger hole than CSRF.)

Although the setting offers little practical benefit, it’s sometimes required by security auditors.

If you enable this and need to send the value of the CSRF token with an AJAX request, your JavaScript must pull the value from a hidden CSRF token form input on the page instead of from the cookie.

Definition at line 198 of file csrfprotection.cpp.

◆ setCookieName()

void CSRFProtection::setCookieName ( const QByteArray cookieName)

The name of the cookie to use for the CSRF authentication token. The default is "csrftoken". This can be whatever you want (as long as it’s different from the other cookie names in your application).

Definition at line 204 of file csrfprotection.cpp.

◆ setDefaultDetachTo()

void CSRFProtection::setDefaultDetachTo ( const QString actionNameOrPath)

Sets a default action the application will detach to if the check for token and cookie failed. The default value is empty, so that there will be no detaching and a generic error page will be generated.

Definition at line 168 of file csrfprotection.cpp.

◆ setErrorMsgStashKey()

void CSRFProtection::setErrorMsgStashKey ( const QString keyName)

Sets the name of the stash key that will contain the error message if the CSRF protection check failed.

Definition at line 180 of file csrfprotection.cpp.

◆ setFormFieldName()

void CSRFProtection::setFormFieldName ( const QByteArray fieldName)

Sets the name for the hidden form field that takes the CSRF token. This field name is used by CSRFProtection::getTokenFormField(). The default value is "csrfprotectiontoken".

Definition at line 174 of file csrfprotection.cpp.

◆ setGenericErrorContentType()

void CSRFProtection::setGenericErrorContentType ( const QByteArray type)

Sets the content type for the error message set by setGenericErrorMessage(), defaults to text/plain; charset=utf-8.

Since
Cutelyst 2.2.0

Definition at line 222 of file csrfprotection.cpp.

◆ setGenericErrorMessage()

void CSRFProtection::setGenericErrorMessage ( const QString message)

Sets a generic error message that will be set to the Response::body() if the check fails and if there is no action defined it should be detached to.

See also
setGenericErrorContentType()
Since
Cuteyst 2.2.0

Definition at line 216 of file csrfprotection.cpp.

◆ setHeaderName()

void CSRFProtection::setHeaderName ( const QByteArray headerName)

The name of the request header used for CSRF authentication. The header can contain the token if you don't have a input form on your protected site. The default value is "X-CSRFTOKEN".

Definition at line 210 of file csrfprotection.cpp.

◆ setIgnoredNamespaces()

void CSRFProtection::setIgnoredNamespaces ( const QStringList namespaces)

Sets a list of namespaces that should be completely ignored by the CSRF protection. If you have single methods in your namespaces that should still be protected, use the :CSRFRequire attribute on this methods.

Definition at line 186 of file csrfprotection.cpp.

◆ setup()

◆ setUseSessions()

void CSRFProtection::setUseSessions ( bool  useSessions)

If this is set to true, the secret token will not be safed in a cookie but in the user’s session. For this, the Session plugin has to be available.

Storing the token in a cookie (the default) is safe, but storing it in the session is common practice in other web frameworks and therefore sometimes demanded by security auditors.

Definition at line 192 of file csrfprotection.cpp.