Thursday, 2 April 2020

Posting the cross-site request forgery in JMeter for dotnet core

I played with JMeter a long time ago, it is a very mature product for load-testing but with its power comes a certain level of complexity that takes a while to get your head round. The tree-view of everything is particularly hard to line-up with how things work but don't let that put you off.

When creating a test for .Net (or most things), the easiest way is usually to record it via the browser. You can get a Chrome plugin to do this without messing with proxy settings, otherwise you need to setup JMeter, start the recording and then setup your browser to use the JMeter proxy. You might fiddle a bit with host names and stuff to only capture what you want but the easiest way is to close all other tabs while you do it.

What can be confusing is that when you run your test, you might see things like login and logout failing but other pages seem to fail when run from JMeter (even though they work in the browser). This is almost certainly related to cross-site forgery protection.

Csrf is a significant risk in web applications and is mitigated in all modern frameworks using a combination of a hidden field in a form and a cookie value. When anything is posted, both are sent to the server which can validate that the cookie matches the hidden value before allowing the operation to take place. If this wasn't in place, it would be possible for evil code on a site to call e.g. POST www.mybank.com/transfer/to/bad-person (you get the idea). Cookies are automatically sent to any domain that matches them so an attacker could do bad things. What the bad person cannot do it read the value of a hidden field in another page so it wouldn't be able to send a valid form field to the taregt server. In some systems, the value in the cookie is an exact match which means you must make the cookie httponly to make sure it is not readable by an attacker from script.

Anyway...

In your JMeter test, it will have recorded the csrf token from the original test run rather than the one that is dynamically generated from the application, which is why login and logout (and anything else that uses POST) will have failed. Why didn't the GET methods failed when not logged in? Simple, they did a 302 to the login page so they didn't fail as such!

How do we use the correct value? We need to make sure that we call a GET on the login page first, which will return the csrf token in a hidden field. We then need to use a Regex Extractor as a post-processor to extract this value from the body and assign it to a variable.



The variable name can be whatever you want. The Regex needs to correctly match the field. The above example is from dotnet core 3, which is probably the same as earlier dotnet core but not the same as earlier versions of .Net. Be VERY careful with your regex. Not only did I mis-type "hidden" but I also didn't carefully see that the value I was extracting included dashes, which didn't work when I used "(\w+)" as the regex selector.

If it doesn't work and you don't set a default value, then the variable name itself is passed as the parameter (confusing, but basically the variable won't be created). If you set a default of blank, you will see blank passed, which gives a clearer hint that your regex is wrong. Note that we are getting the value from body, you can parse a number of different pieces of data for the result.

Once you have this variable, you can use it in a subsequent call by modifying the parameters of a post and passing in the variable name like this:



Note that in some frameworks, the same value is used for the entire session (so one extractor is all that is required for the whole test). In dotnet core however, this isn't the case and you will need to extract before each post is called. You can use the same variable name each time.

And that's that. It was nice once it was all working, the weird problems I initially had, including some pages that didn't correctly have the authorize attribute on them made it look really flaky but it was just user error! What would be really nice would be a template that automatically does the extraction bit for you or at least highlights them for you to modify.