Background
2 junior coworkers asked my boss about a statement I made regarding CSRF tokens and web app security. My boss was unable to answer that question but didn’t refer to me. So, there was a bunch of confusion, miscommunication, and duplicate work.
This is the internal documentation I wrote to answer that question. I wrote it as internal documentation so it can be reference in the future, so its searchable, and so future coworkers also have access.
I would like to believe this highlights the development environment at ITS, my understanding of information security, and my mentoring of junior staff.
~~~~~~~~~ The Documentation Follows ~~~~~~~~~
[Tagging coworkers #1 & #2] if you have questions about CSRF tokens and/or how you might ensure security of the ITS/ITA dashboard here’s some info.
CSRF Tokens
CSRF or Cross Site Request Forgery was born out of the early days of the internet. You could create a site, say a login to Facebook, on your own server, convince people to enter their credentials, and have that form submit to Facebook. Without a CSRF token the user would be logged into Facebook and think everything is fine, little do they know they actually entered their credentials on a malicious site so their account is compromised.
Similarly, I easily & successfully executed a CSRF attack on the current, old version of IICNet. I sent a link to an authenticated member, the link looked something like URL?form_data=… Because the user was authenticated the form_data… portion was treated as if the user had entered the data in a form on the actual site. There was no validation/verification that the information was legitimately entered and was what the user intended.
Side note, the other part of the reason the IICNet CSRF attack was successful was b/c the old developer used _REQUEST for everything. In PHP _REQUEST is the amalgamation of _GET and _POST variables. Using _REQUEST has for 10+ years been considered bad practice and insecure. To be honest, before IIC I don’t think I’d ever seen _REQUEST used. _POST data cannot be sent via a URL so at a bare minimum all forms should submit via _POST and API’s should only ever accept data in the expected, known, trusted format.
DDOS Attacks
DDOS or Distributed Denial of Service attacks occur when a malicious user sets multiple servers at multiple IP addresses to attack a web server. The purpose and result of the attack can be variable and are not material to this discussion. In the modern age all DOS attacks are distributed so you won’t hear about simple DOS attacks any more. It’s easy to block a single IP so a single server attack is no longer a threat.
DDOS attacks relate to CSRF because the attacking servers are not legitimate and therefore would not have a legitimate CSRF token. If a DDOS server has to request a new CSRF token before every attack it becomes much more difficult and cumbersome to script and execute an attack.
Side note, one of the purposes of a DDOS attack is a brute force password attack (try 1,000,000 different passwords in the hopes one of them works). So, in the event of an incorrect CSRF token or an incorrect login attempt the CSRF token should be wiped and the user forced to obtain a new token. This will, hopefully, slow down brute force attacks. Ideally, upon 3+ failed login attempts an IP/user is blocked. But that takes some additional code because _COOKIE is editable by the user so if you simply do _COOKIE[failed_attempts]=3 then an attacker would simply set that cookie to 0 or less than 3 and continue their attack.
0-trust, 0-trust, 0-trust
Rules # to #3 of software is never trust the user, never trust the user, never trust the user. The user could be a malicious human being, a malicious script, or multiple DDOS attack servers.
Side note, if you never trust the user then you should require a new CSRF token on a regular basis and tokens should have an expiry date. Again, _COOKIE is editable by the user so simply assigning an expiry date to the CSRF cookie is not valid since a malicious user will simply change that expiry to whatever suits them. 1 method is to hash something like the current hour or current day to make the token expiry hourly or daily. That is what my current CRSF token does. A better way is to build a CSRF token like a JWT; encode data in the payload and append a signature. That way you can trust the data but it doesn’t require DB storage and yet another DB call to verify/validate.
Bottom Line
The bottom line is, how do you as a web server or developer confirm that the data you are receiving is from a legitimate source? Whether that source is a web browser, mobile app, desktop app, or API request.
The quickest, easiest way to establish that trust is to initially provide a unique token to the user and subsequently confirm that token.
This unique token has 2 values:
Any request that fails or legitimacy is questionable the token can be wiped requiring new authentication for the user to proceed.
The provision of that token is a single point [of failure] where you can block an IP, browser user-agent, or geographic region. If you are subject to a successful DDOS attack all you have to do is block the attacker for obtaining new tokens and the attack is immediately shut down.
My preferred method of CSRF tokens is:
- User requests a new token.
- Server does whatever authentication/validation necessary to confirm the user is legitimate and not blocked.
- Server generates a new token.
- Server writes that token to _SESSION and _COOKIE.
- User must then provide that token in every _POST data submission.
_COOKIE is visible and write-able to the browser/user so _COOKIE alone cannot be relied upon as secure. _SESSION data on the other hand never leaves the server and is not writable by the [malicious] user.
By comparing _POST, _SESSION, and _COOKIE tokens you have 2 pieces of data from the user and 1 from the server. If a malicious user is to attack in some way with this data they have to put in some extra effort whereas using only _COOKIE makes it relatively easy to obtain a token then execute an attack.
New Token for Every Form
The ideal would be to generate a new unique token for every form. In the old days of pure PHP+HTML this was relatively easy. In the very old days you would simply write a new _COOKIE/_SESSION for each form when it was requested. This has a flaw though if a user opens multiple forms in different tabs then only the most recently opened form can be legitimately submitted. It was still relatively easy in the old days because you simply do _COOKIE[csrf][…url_here…] = token. By doing this each url gets its own token and a user can open/submit multiple urls.
In the modern days of React, Angular, Vue, and mobile apps it becomes a bit trickier. Taking a React SPA (single page app) as a simple example, the entire app is downloaded initially when the user logs in. The entire app contains all forms the user could potentially submit but its superfluous to issue all/multiple CSRF tokens at login. The solution to this could be an API end point, a single master CSRF token, and issuing subsequent child tokens for each form as its displayed. That’s a bit of extra work and not effort that I’ve put into ITS apps thus far.
ITS/ITA Dashboard Development
You have a few options for developing the front-end and back-end of the dashboard on separate servers. The quickest, easiest way is, in React, check if the CSRF _COOKIE is set and is valid. If not, direct the user to a page on the back-end server that simply writes the necessary _COOKIE and _SESSION tokens. Then, each React form loads that token from _COOKIE and sends it as an additional _POST input.
From there you could get slightly more complex in confirming the user is legitimate and writing the _SESSION and _COOKIE tokens via an API request.
The bottom line is that you have been tasked with building a secure web app that is fault tolerant including identifying and handling CSRF attacks, DDOS attacks, privilege/account escalation, account spoofing, etc.
Questions
Ask away and I’ll do my best to answer. I would ask that, when appropriate, document answers in a form that works for you to prevent duplicate questions. This Knowledge Is Power channel is a great way to document answers that is searchable, saved for posterity, and everyone can see.
If you sense frustration or if I’m short with you, it most likely not you (or your fault). Its often pain, a bad driver, or any number of other triggers that get me going these days.
If you sense I’m frustrated or short with you, please do feel free to ask me.
Even in the off chance I am mad it’d be very hard for me to remain mad when you ask me if I’m mad.
And, I apologize in advance for any frustration or shortness I express. You can blame ICBC and shitty drivers for that!