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.
TCsrfMiddleware expects 5 parameters,
ICsrfinstance which responsible to generate CSRF token and also check token validity,
ISessionManagerinterface instance which responsible to maintain CSRF token between request,
IRequestHandlerinterface instance responsible to generate response when CSRF token check is failed.
- Optional name of field for CSRF name, if not provided
- Optional name of field for CSRF token, if not provided
Read Working with Session for more information about
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')) );
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
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') ) );
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_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_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.
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
factory := TCsrfMiddlewareFactory.create() .nameField('my_cool_name') .tokenField('my_cool_token') .failureHandler(TMyOwnCsrfFailController.create());
Failure handler class must implements
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);