2022-05-31 15:36:54 +03:00
# Security
2023-07-21 14:28:26 +03:00
In the [previous part], we have tested our login workflow. So far, we have tested a "simple" form creation: each value of
2023-08-31 13:11:00 +03:00
the form is valid and sanitized, but what if the user put invalid data? We're going to test a user account creation and see
2023-07-21 14:28:26 +03:00
how we can check that our signup workflow is secure.
2022-05-31 15:36:54 +03:00
2022-12-19 23:30:08 +03:00
2022-05-31 15:36:54 +03:00
## Server Side Validation
2023-07-21 14:28:26 +03:00
In the browser, client-side validation is helping users to enter data and avoid unnecessary server load.
On the signup page, < http: / / localhost:3000 / signup > , we have an HTML form:
2022-05-31 15:36:54 +03:00
```html
2023-07-21 14:28:26 +03:00
< form class = "signup-form" method = "post" action = "/signup" >
...
< input type = "text" name = "username" id = "username" autocomplete = "off" minlength = "3" maxlength = "32" pattern = "[a-zA-Z0-9_-]{3,32}" title = "Username must use a-z, A-Z, 0-9 or _ -" required = "" >
...
< input type = "text" name = "name" id = "name" autocomplete = "off" minlength = "3" maxlength = "32" pattern = "[a-zA-Z \d\s-]{3,32}" required = "" >
...
< input type = "email" name = "email" id = "email" autocomplete = "off" minlength = "4" maxlength = "32" required = "" >
2022-05-31 15:36:54 +03:00
...
2023-07-21 14:28:26 +03:00
< input type = "password" name = "password" id = "password" autocomplete = "off" minlength = "6" maxlength = "32" required = "" >
...
< input type = "password" name = "password-confirm" id = "password-confirm" autocomplete = "off" minlength = "6" maxlength = "32" required = "" >
2022-05-31 15:36:54 +03:00
< / form >
```
2023-07-21 14:28:26 +03:00
The first input, username, has [validation HTML attributes]: `minlength="3"` , `maxlength="32"` , a pattern and `required` .
In a browser, these attributes will prevent the user from entering invalid data like a missing value or a name that is
too long. If your tests rely on a "headless" browser, it can stop you from testing your server-side validation.
Client-side validation can also use JavaScript, and it can be a challenge to send invalid data to your server.
2022-05-31 15:36:54 +03:00
But server-side validation is critical to secure your app. You must always validate and sanitize data on your backend,
and try to test it.
As Hurl is not a browser, but merely an HTTP runner on top of [curl], sending and testing invalid data is easy.
2023-08-31 13:11:00 +03:00
To do so, we're going to test the _nominal_ user account creation case, then we'll see how to test with invalid data.
2023-07-21 14:28:26 +03:00
### Valid user creation
2022-05-31 15:36:54 +03:00
2023-07-21 14:28:26 +03:00
1. Create a new file named `signup.hurl` . We're going to use a new REST API to give us an available username:
2022-05-31 15:36:54 +03:00
```hurl
2023-07-21 14:28:26 +03:00
# First we obtain an available username:
GET http://localhost:3000/api/usernames/available
HTTP 200
[Captures]
username: jsonpath "$.username"
```
2022-05-31 15:36:54 +03:00
2023-07-21 14:28:26 +03:00
Now, we can create a new user. As we have seen in the [previous part], first we have to get a
CSRF token from the signup part, then POST the form to create a user and finally
2022-05-31 15:36:54 +03:00
2023-07-21 14:28:26 +03:00
2. Go to the signup page, and create a new user:
```hurl
# First we obtain an available username:
2022-05-31 15:36:54 +03:00
# ...
2023-07-21 14:28:26 +03:00
# Create a new valid user: get the CSRF token the signup:
GET http://localhost:3000/signup
HTTP 200
[Captures]
csrf_token: xpath "string(//input[@name='_csrf']/@value)"
2022-05-31 15:36:54 +03:00
2023-07-21 14:28:26 +03:00
POST http://localhost:3000/signup
2022-05-31 15:36:54 +03:00
[FormParams]
_csrf: {{csrf_token}}
2023-07-21 14:28:26 +03:00
username: {{username}}
name: Bob
email: {{username}}@example.net
password: 12345678
HTTP 302
[Asserts]
header "Location" == "/my-movies"
2022-05-31 15:36:54 +03:00
2023-07-21 14:28:26 +03:00
# Go to my movies
GET http://localhost:3000/my-movies
2022-12-19 23:30:08 +03:00
HTTP 200
2022-05-31 15:36:54 +03:00
```
2023-07-21 14:28:26 +03:00
Writing each step of a redirection can be a little tedious so we can ask Hurl to automatically follow redirection
after the POST login. An [`[Options]` section][options] can be used to modify how a request is played:
3. Use an `[Options]` section to follow redirection on the user account creation:
2022-05-31 15:36:54 +03:00
```hurl
2023-07-21 14:28:26 +03:00
# First we obtain an available username:
2022-05-31 15:36:54 +03:00
# ...
2023-07-21 14:28:26 +03:00
# Create a new valid user: get the CSRF token the signup:
GET http://localhost:3000/signup
HTTP 200
[Captures]
csrf_token: xpath "string(//input[@name='_csrf']/@value)"
POST http://localhost:3000/signup
[Options]
location: true
[FormParams]
_csrf: {{csrf_token}}
username: {{username}}
name: Bob
email: {{username}}@example.net
password: 12345678
HTTP 200
[Asserts]
url endsWith "/my-movies"
```
Note that, when following redirection, asserts are run against the final HTTP response. That's why we must have a `200 OK`
instead of a `302 Found` . We can also use an [`url` assert] to check what's the final redirected URL.
4. Run `signup.hurl` and verify that everything is ok:
```shell
$ hurl --test signup.hurl
[1msignup.hurl [0m: [1;36mRunning [0m [1/1]
[1msignup.hurl [0m: [1;32mSuccess [0m (4 request(s) in 16 ms)
--------------------------------------------------------------------------------
Executed files: 1
Succeeded files: 1 (100.0%)
Failed files: 0 (0.0%)
Duration: 18 ms
```
### Invalid user creation
Now that we have tested a user creation, let's try to create a user with an invalid username. We can try to create a
two letters long username for instance. In that case, we should be redirected to the signup page, with an error message
displayed.
5. Add a POST user signup with `bo` as username:
```hurl
# First we obtain an available username:
2022-05-31 15:36:54 +03:00
# ...
2023-07-21 14:28:26 +03:00
# Create a new valid user: get the CSRF token the signup:
2022-05-31 15:36:54 +03:00
# ...
2023-07-21 14:28:26 +03:00
# Try an invalid username: too short. We should stay on signup
GET http://localhost:3000/signup
HTTP 200
[Captures]
csrf_token: xpath "string(//input[@name='_csrf']/@value)"
2022-05-31 15:36:54 +03:00
2023-07-21 14:28:26 +03:00
POST http://localhost:3000/signup
[Options]
location: true
2022-05-31 15:36:54 +03:00
[FormParams]
_csrf: {{csrf_token}}
2023-07-21 14:28:26 +03:00
username: bo
name: Bob
email: bob78@example.net
password: 12345678
2022-12-19 23:30:08 +03:00
HTTP 200
2022-05-31 15:36:54 +03:00
[Asserts]
2023-07-21 14:28:26 +03:00
url endsWith "/signup"
xpath "string(//div[@class='form-errors'])" contains "Username must be 3 to 32 chars long"
2022-05-31 15:36:54 +03:00
```
2023-07-21 14:28:26 +03:00
6. Finally, add a POST request with no CSRF token to test that our endpoint has CSRF protection:
2022-05-31 15:36:54 +03:00
```hurl
2023-07-21 14:28:26 +03:00
# First we obtain an available username:
2022-05-31 15:36:54 +03:00
# ...
2023-07-21 14:28:26 +03:00
# Create a new valid user: get the CSRF token the signup:
2022-05-31 15:36:54 +03:00
# ...
2023-07-21 14:28:26 +03:00
# Try an invalid username: too short. We should stay on signup
2022-05-31 15:36:54 +03:00
# ...
2023-07-21 14:28:26 +03:00
# Test CSRF token is mandatory:
POST http://localhost:3000/signup
2022-05-31 15:36:54 +03:00
[FormParams]
2023-07-21 14:28:26 +03:00
username: bob
name: Bob
email: bob78@example.net
password: 12345678
2022-12-19 23:30:08 +03:00
HTTP 403
2022-05-31 15:36:54 +03:00
```
2023-07-21 14:28:26 +03:00
This final test is also interesting because if you're testing your page with a headless browser, the CRSF token is always
created and sent and you don't test that your backend has CSRF protection.
2022-05-31 15:36:54 +03:00
2023-07-21 14:28:26 +03:00
7.Run `signup.hurl` and verify that everything is ok:
2022-05-31 15:36:54 +03:00
```shell
2023-07-21 14:28:26 +03:00
$ hurl --test signup.hurl
[1msignup.hurl [0m: [1;36mRunning [0m [1/1]
[1msignup.hurl [0m: [1;32mSuccess [0m (8 request(s) in 28 ms)
2022-05-31 15:36:54 +03:00
--------------------------------------------------------------------------------
2022-08-23 19:36:47 +03:00
Executed files: 1
Succeeded files: 1 (100.0%)
Failed files: 0 (0.0%)
2023-07-21 14:28:26 +03:00
Duration: 35 ms
2022-05-31 15:36:54 +03:00
```
## Comments
2023-07-21 14:28:26 +03:00
Hurl being close to the HTTP layer has no "browser protection" / client-side validation: it facilitates
2022-05-31 15:36:54 +03:00
the testing of your app's security with no preconception.
2023-07-21 14:28:26 +03:00
Another security use case is checking that your served HTML isn't leaking comments. Comments can reveal sensitive information
2022-05-31 15:36:54 +03:00
and [is it recommended] to trim HTML comments in your production files.
2023-07-21 14:28:26 +03:00
Popular front-end frameworks like [ReactJS] or [Vue.js] use client-side JavaScript rendering.
2022-09-28 11:24:24 +03:00
If you use one of these frameworks, and you inspect the DOM with the browser developer tools, you won't see any comments
2023-07-21 14:28:26 +03:00
because the framework managing the DOM is removing them.
2022-05-31 15:36:54 +03:00
2022-09-28 11:24:24 +03:00
But, if you look at the HTML page sent on the network, i.e. the real HTML document sent by the
2022-05-31 15:36:54 +03:00
server (and not _the document dynamically created by the framework_ ), you can still see those HTML comments.
With Hurl, you will be able to check the content of the _real_ network data.
2023-07-21 14:28:26 +03:00
1. In the second entry of `signup.hurl` , add a [XPath assert] when getting the quiz creation page:
2022-05-31 15:36:54 +03:00
```hurl
2023-07-21 14:28:26 +03:00
# First we obtain an available username:
# ...
2022-05-31 15:36:54 +03:00
2023-07-21 14:28:26 +03:00
# Create a new valid user: get the CSRF token the signup:
GET http://localhost:3000/signup
2022-12-19 23:30:08 +03:00
HTTP 200
2022-05-31 15:36:54 +03:00
[Captures]
csrf_token: xpath "string(//input[@name='_csrf']/@value)"
[Asserts]
xpath "//comment" count == 0 # Check that we don't leak comments
# ...
```
2023-07-21 14:28:26 +03:00
2. Run `signup.hurl` and verify that everything is ok:
2022-05-31 15:36:54 +03:00
```shell
2023-07-21 14:28:26 +03:00
$ hurl --test signup.hurl
[1msignup.hurl [0m: [1;36mRunning [0m [1/1]
[1msignup.hurl [0m: [1;32mSuccess [0m (8 request(s) in 28 ms)
2022-05-31 15:36:54 +03:00
--------------------------------------------------------------------------------
2022-08-23 19:36:47 +03:00
Executed files: 1
Succeeded files: 1 (100.0%)
Failed files: 0 (0.0%)
2023-07-21 14:28:26 +03:00
Duration: 31 ms
2022-05-31 15:36:54 +03:00
```
## Recap
2023-07-21 14:28:26 +03:00
So, our test file `signup.hurl` is now:
2022-05-31 15:36:54 +03:00
```hurl
2023-07-21 14:28:26 +03:00
# First we obtain an available username:
GET http://localhost:3000/api/usernames/available
HTTP 200
[Captures]
username: jsonpath "$.username"
2022-05-31 15:36:54 +03:00
2023-07-21 14:28:26 +03:00
# Create a new valid user: get the CSRF token the signup:
GET http://localhost:3000/signup
2022-12-19 23:30:08 +03:00
HTTP 200
2022-05-31 15:36:54 +03:00
[Captures]
csrf_token: xpath "string(//input[@name='_csrf']/@value)"
[Asserts]
xpath "//comment" count == 0 # Check that we don't leak comments
2022-12-19 23:30:08 +03:00
2023-07-21 14:28:26 +03:00
POST http://localhost:3000/signup
[Options]
location: true
2022-05-31 15:36:54 +03:00
[FormParams]
_csrf: {{csrf_token}}
2023-07-21 14:28:26 +03:00
username: {{username}}
name: Bob
email: {{username}}@example.net
password: 12345678
HTTP 200
2022-05-31 15:36:54 +03:00
[Asserts]
2023-07-21 14:28:26 +03:00
url endsWith "/my-movies"
2022-05-31 15:36:54 +03:00
2022-12-19 23:30:08 +03:00
2023-07-21 14:28:26 +03:00
# Play some checks on signup form: username too short
# email already taken, invalid pattern for username
GET http://localhost:3000/signup
2022-12-19 23:30:08 +03:00
HTTP 200
2023-07-21 14:28:26 +03:00
[Captures]
csrf_token: xpath "string(//input[@name='_csrf']/@value)"
2022-05-31 15:36:54 +03:00
2022-12-19 23:30:08 +03:00
2023-07-21 14:28:26 +03:00
# Create a new user, username too short
POST http://localhost:3000/signup
[Options]
location: true
2022-05-31 15:36:54 +03:00
[FormParams]
_csrf: {{csrf_token}}
2023-07-21 14:28:26 +03:00
username: bo
name: Bob
email: bob78@example.net
password: 12345678
2022-12-19 23:30:08 +03:00
HTTP 200
2022-05-31 15:36:54 +03:00
[Asserts]
2023-07-21 14:28:26 +03:00
url endsWith "/signup"
xpath "string(//div[@class='form-errors'])" contains "Username must be 3 to 32 chars long"
2022-05-31 15:36:54 +03:00
2022-12-19 23:30:08 +03:00
2023-07-21 14:28:26 +03:00
# Test CSRF is mandatory:
POST http://localhost:3000/signup
2022-05-31 15:36:54 +03:00
[FormParams]
2023-07-21 14:28:26 +03:00
username: bob
name: Bob
email: bob78@example.net
password: 12345678
2022-12-19 23:30:08 +03:00
HTTP 403
2022-05-31 15:36:54 +03:00
```
2023-07-21 14:28:26 +03:00
We have seen that Hurl can be used as a security tool to check your server-side validation.
2022-05-31 15:36:54 +03:00
Until now, we have done all our tests locally, and in the next session we are going to see how simple
it is to integrate Hurl in a CI/CD pipeline like [GitHub Action] or [GitLab CI/CD].
[curl]: https://curl.se
[the exist predicate]: /docs/asserting-response.md#predicates
2023-07-21 14:28:26 +03:00
[is it recommended]: https://owasp.org/www-project-web-security-testing-guide/v41/4-Web_Application_Security_Testing/01-Information_Gathering/05-Review_Webpage_Comments_and_Metadata_for_Information_Leakage
2022-05-31 15:36:54 +03:00
[DOM]: https://en.wikipedia.org/wiki/Document_Object_Model
[ReactJS]: https://reactjs.org
[Vue.js]: https://vuejs.org
[XPath assert]: /docs/asserting-response.md#xpath-assert
[GitHub Action]: https://github.com/features/actions
[GitLab CI/CD]: https://docs.gitlab.com/ee/ci/
2023-07-21 14:28:26 +03:00
[previous part]: /docs/tutorial/captures.md
[options]: /docs/request.md#options
[`url` assert]: /docs/asserting-response.md#url-assert
[validation HTML attributes]: https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation