2

I haven't seen this anywhere in the wild so I am wondering if it is safe to inject into same-origin forms the hidden input containing the XSRF token using JavaScript like this:

document.addEventListener('DOMContentLoaded', () => {
    // Get token from <html> tag
    const XSRF_TOKEN = document.documentElement.getAttribute('data-xsrf-token');
    const uri = new URI(document.URL);

    // Loop through all forms
    Array.from(document.getElementsByTagName('form')).forEach(el => {
        let actionUri = new URI(el);

        // Only append hidden input to same-origin forms with method POST
        if (actionUri.origin() === uri.origin() && el.method === 'post') {
            let tokenInput = document.createElement('input');
            tokenInput.name = 'xsrf_token';
            tokenInput.type = 'hidden';
            tokenInput.value = XSRF_TOKEN;
            el.appendChild(tokenInput);
        }
    })
});

The token would be generated per user per session and delivered with the HTML page as an attribute value on the <html> element.

Of course this would only work in the parts of the application that we require JavaScript for. It is a lot easier than putting the hidden input everywhere.

Many sites suggest using a framework for this, however this isn't a feasible option and if it must be hidden inputs on page load then the best I can do is create a helper Twig function to output the tag but that still requires that sprinkled everywhere. And if this is the case then how would dynamically created forms work?

I don't think the JavaScript method is any less secure than <input type=hidden> everywhere because the token is retrievable from the form in the event of XSS anyways, but is there something else that I am missing?

0

Dynamically injected CSRF tokens are a field-tested approach without inherent security flaws.

E.g., the OWASP CSRFGuard project supports that feature:

OWASP CSRFGuard 3 supports the ability to dynamically inject CSRF prevention tokens throughout the DOM currently loaded in the user's browser. This strategy is extremely valuable with regards to server-side performance as it simply requires the serving of a dynamic JavaScript file.

The iFixit tech blog also has an article on their own "minimalist" implementation of that idea:

We use a small amount of javascript to automatically set a cookie and add a form field immediately before a form is submitted. The server then compares the form field with the cookie and ignores the request if they don't match. This frees us from the hassle of ensuring every form includes a hidden csrf <input> in every template.

In their code sample you can see that the token is added just before submission which is probably safer than adding it on document load (since something could cause the form target to change in the meantime):

window.addEvent('domready', function() {
   /**
    * Setup triggers on the .submit() function and the `submit` event so we
    * can add csrf inputs immediately before the form is submitted.
    */
   $$('form').each(function(form) {
      // Ensure all forms submitted via a traditional 'submit' button have
      // an up-to-date CSRF input
      form.addEvent('submit', function() {
         ensureFormHasCSRFFormField(form);
      });
      // Ensure all forms submitted via form.submit() have an up-to-date CSRF
      // input
      var oldSubmit = form.submit;
      // Wrap the default submit() function with our own
      form.submit = function () {
         ensureFormHasCSRFFormField(form);
         return oldSubmit.apply(form, Array.from(arguments));
      }
   });
});

There are no inherent security problems with this approach as long as you ensure that the form target has the correct origin. It even mitigates some attacks, e.g. in this scenario. (The article is about an XSS flaw where the CSP prevents all inline scripts, so the attacker is limited to pure HTML injection. The author presents a method to change the form-action target without any JS in order to exfiltrate a CSRF token set in a hidden input field. Now if that token was instead added dynamically, the attack would have failed because your script would recognize that the form submits to an untrusted origin.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.