Cross-Site Request Forgery (CSRF)
What is CSRF?
According to OWASP
Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they’re currently authenticated. CSRF attacks specifically target state-changing requests, not theft of data, since the attacker has no way to see the response to the forged request. With a little help of social engineering (such as sending a link via email or chat), an attacker may trick the users of a web application into executing actions of the attacker’s choosing. If the victim is a normal user, a successful CSRF attack can force the user to perform state changing requests like transferring funds, changing their email address, and so forth. If the victim is an administrative account, CSRF can compromise the entire web application.
Protecting against CSRF with middleware
Fano Framework provides built-in middleware class TCsrfMiddleware
which is to simplify task for protecting against CSRF attack. Read Middlewares for more information about working with middlewares.
Constructor of TCsrfMiddleware
expects 5 parameters,
ICsrf
instance which responsible to generate CSRF token and also check token validity,ISessionManager
interface instance which responsible to maintain CSRF token between request,IRequestHandler
interface instance responsible to generate response when CSRF token check is failed.- Optional name of field for CSRF name, if not provided
crsf_name
is assumed, - Optional name of field for CSRF token, if not provided
crsf_token
is assumed.
Read Working with Session for more information about ISessionManager
.
Register global middleware list
container.add('globalMiddlewares', TMiddlewareListFactory.create());
globalMiddlewares := container.get('globalMiddlewares') as IMiddlewareList;
Register CSRF middleware with container
Fano Framework has TCsrfMiddlewareFactory
class which allows you to register TCsrfMiddleware
service container.
container.add(
'verifyCsrfToken',
TCsrfMiddlewareFactory.create(config.getString('secretKey'))
);
where config
is instance of IAppConfiguration
. Read Configuration for more information on how
to work with application configuration.
Register dispatcher with middleware and session support
We need to use dispatcher class which support middlewares and sessions, i.e TSessionDispatcher
.
container.add(
GuidToString(IDispatcher),
TSessionDispatcherFactory.create(
globalMiddlewares as IMiddlewareLinkList,
container.get(GuidToString(IRouteMatcher)) as IRouteMatcher,
TRequestResponseFactory.create(),
container.get(GuidToString(ISessionManager)) as ISessionManager,
(TCookieFactory.create()).domain(config.getString('cookie.domain')),
config.getInt('cookie.maxAge')
)
);
Read Dispatcher and Dependency Container for more information.
Attach CSRF middleware to application middleware
Attach CSRF middleware instance to application middleware collection to ensure CSRF middleware is executed for all application routes.
globalMiddlewares.add(container['verifyCsrfToken'] as IMiddleware);
Get current CSRF token
When you attach TCsrfMiddleware
middleware to application middleware list, everytime request is coming, new random token and name are generated and it can be read from current session variable.
function THomeController.handleRequest(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader
) : IResponse;
var sess : ISession;
begin
sess := fSessionManager[request];
viewParams['csrfName'] := sess['csrf_name'];
viewParams['csrfToken'] := sess['csrf_token'];
result := inherited handleRequest(request, response, args);
end;
By default, name and token field is csrf_name
and csrf_token
respectively. When you build your HTML form, you need to ensure correct name is used,
<form method="post" action="/">
<input type="hidden" name="csrf_name" value="[[csrfName]]">
<input type="hidden" name="csrf_token" value="[[csrfToken]]">
...
</form>
Verify CSRF token
CSRF token verification is done in CSRF middleware automatically for POST, PUT, DELETE and PATCH request by comparing csrf_name
and csrf_token
values in request against corresponding values stored in session.
If they are matched, execution continues to next middlewares otherwise it stops by calling failure request handler you set when creating CSRF middleware.
CSRF token is for one-time use only. After token verification, new token and name is generated and replace old token and name in session.
Configure CSRF middleware settings
TCsrfMiddlewareFactory
class provides several methods to help configure CSRF middleware.
Change name and token field
To change name and token field
var factory : IDependencyFactory;
...
factory := TCsrfMiddlewareFactory.create()
.nameField('my_cool_name')
.tokenField('my_cool_token');
You need to ensure correct name is used in HTML form and in code that read token
viewParams['csrfName'] := sess['my_cool_name'];
viewParams['csrfToken'] := sess['my_cool_token'];
<form method="post" action="/">
<input type="hidden" name="my_cool_name" value="[[csrfName]]">
<input type="hidden" name="my_cool_token" value="[[csrfToken]]">
...
</form>
Change token generator
TCsrfMiddlewareFactory
class, by default, uses TCsrf
class to generate and to verify token. TCsrf
uses createGUID()
. GUID and a secret key that you set are used to calculated HMACSHA1 hash which will become token.
If you need stronger token generator, you can replace with TUrandomCsrf
class which use /dev/urandom
to generate random bytes combined with secret key to generate HMACSHA1 hash.
You can also replace with your own implementation by creating class which implements ICsrf
interface and provides following methods
function generateToken(out tokenName : string; out tokenValue : string) : ICsrf;
function hasValidToken(
const request : IRequest;
const sess : ISession;
const nameKey : shortstring;
const valueKey : shortstring
) : boolean;
and then replace token generator
factory := TCsrfMiddlewareFactory.create(config.getString('secretKey'))
.nameField('my_cool_name')
.tokenField('my_cool_token')
.csrf(TMyOwnCsrf.create());
Change failure handler
By default, when CSRF token check is failed, it calls instance of TDefaultFailCsrfHandler
class which returns response with error code HTTP 400 (Bad Request) and message ‘Fail CSRF check’.
You may want to replace it with your own request handler by calling failureHandler()
method.
factory := TCsrfMiddlewareFactory.create()
.nameField('my_cool_name')
.tokenField('my_cool_token')
.failureHandler(TMyOwnCsrfFailController.create());
Failure handler class must implements IRequestHandler
interface.
Change session manager
Csrf middleware needs to get session from current request, thus requires access to ISessionManager
interface instance. By default if not set, factory class tries to get session manager by requesting service container as shown in following code
if fSessionManager = nil then
begin
fSessionManager := container.get(GuidToString(ISessionManager)) as ISessionManager;
end;
You can set it manually if required,
factory := TCsrfMiddlewareFactory.create()
.sessionManager(container['sessMgr'] as ISessionManager);