hurl/docs/tutorial/security.md

341 lines
10 KiB
Markdown
Raw Normal View History

# 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-12-19 23:30:08 +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:
```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="">
...
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="">
</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.
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
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:
```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"
```
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
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:
# ...
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)"
2023-07-21 14:28:26 +03:00
POST http://localhost:3000/signup
[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"
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
```
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:
```hurl
2023-07-21 14:28:26 +03:00
# First we obtain an available username:
# ...
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
signup.hurl: Running [1/1]
signup.hurl: Success (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:
# ...
2023-07-21 14:28:26 +03:00
# Create a new valid user: get the CSRF token the signup:
# ...
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)"
2023-07-21 14:28:26 +03:00
POST http://localhost:3000/signup
[Options]
location: true
[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
[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"
```
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:
```hurl
2023-07-21 14:28:26 +03:00
# First we obtain an available username:
# ...
2023-07-21 14:28:26 +03:00
# Create a new valid user: get the CSRF token the signup:
# ...
2023-07-21 14:28:26 +03:00
# Try an invalid username: too short. We should stay on signup
# ...
2023-07-21 14:28:26 +03:00
# Test CSRF token is mandatory:
POST http://localhost:3000/signup
[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
```
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.
2023-07-21 14:28:26 +03:00
7.Run `signup.hurl` and verify that everything is ok:
```shell
2023-07-21 14:28:26 +03:00
$ hurl --test signup.hurl
signup.hurl: Running [1/1]
signup.hurl: Success (8 request(s) in 28 ms)
--------------------------------------------------------------------------------
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
```
## 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
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
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-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
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:
```hurl
2023-07-21 14:28:26 +03:00
# First we obtain an available username:
# ...
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
[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:
```shell
2023-07-21 14:28:26 +03:00
$ hurl --test signup.hurl
signup.hurl: Running [1/1]
signup.hurl: Success (8 request(s) in 28 ms)
--------------------------------------------------------------------------------
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
```
## Recap
2023-07-21 14:28:26 +03:00
So, our test file `signup.hurl` is now:
```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"
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
[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
[FormParams]
_csrf: {{csrf_token}}
2023-07-21 14:28:26 +03:00
username: {{username}}
name: Bob
email: {{username}}@example.net
password: 12345678
HTTP 200
[Asserts]
2023-07-21 14:28:26 +03:00
url endsWith "/my-movies"
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-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
[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
[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-12-19 23:30:08 +03:00
2023-07-21 14:28:26 +03:00
# Test CSRF is mandatory:
POST http://localhost:3000/signup
[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
```
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.
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
[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