OWASP JuiceShop with Raider [WIP]

This tutorial series will go through some of the OWASP Juiceshop challenges and show you how to solve them using Raider, so that you can learn it step by step and have a working Juiceshop setup where you can experiment.

Automating user registration

First I wanted to automate the registration process, so that I can easy create new user accounts in Juiceshop. To do this, I created a new user while capturing the traffic with ZAproxy. By the end of the registration process, the history tab looks like this:

Most of the requests are irrelevant for us, so after removing the static files, socket requests and everything else unrelated to the registration process, we’re left with this:

By looking closer at the requests, we can see that we need three HTTP requests to complete the registration (last request just gets the application configuration from the server). So now let’s replicate the requests step by step.

Getting the security questions

The original requests looks like this:

GET http://localhost:3000/api/SecurityQuestions/ HTTP/1.1
User-Agent: Mozilla/5.0 (iPhone; CPU OS 10_15_5 (Ergänzendes Update) like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Mobile/14E304 Safari/605.1.15
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
DNT: 1
Connection: keep-alive
Referer: http://localhost:3000/
Cookie: language=en; cookieconsent_status=dismiss
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Pragma: no-cache
Cache-Control: no-cache
Host: localhost:3000

And the response body is a JSON object with the questions and their IDs:

{
  "status": "success",
  "data":   [
        {
      "id": 1,
      "question": "Your eldest siblings middle name?",
      "createdAt": "2021-09-07T15:41:08.631Z",
      "updatedAt": "2021-09-07T15:41:08.631Z"
    },
        {
      "id": 2,
      "question": "Mother's maiden name?",
      "createdAt": "2021-09-07T15:41:08.632Z",
      "updatedAt": "2021-09-07T15:41:08.632Z"
    },
[...]

So now let’s set up Raider to send this request. But first, in order for Raider to actually run, we need to configure the users. Originally Raider was developed for the authentication only, but it can be used for anything that can be described using HTTP requests. Therefore, we create a dummy _user entry that we’ll later use. The resulting hyfile looks like this:

;; Use the _base_url so that Request objects can be created with :path instead
;; of :url
(setv _base_url "http://localhost:3000")

;; Define the first request
(setv get_questions
      (Flow
        :name "get_questions"  ;; Flow identifier
        :request
        (Request
          :method "GET"
          :path "/api/SecurityQuestions/")
        ;; Print the response body to see if it works
        :operations [(Print.body)]))
                   


;; Dummy non-existent user
(setv _users [{:username "[email protected]"
               :password "UserPassword"}])

;; Only a single step, which isn't authentication per se, but Raider requires
;; _authentication set up for it to run.
(setv _authentication [get_questions])

I saved the file as ~/.config/raider/projects/juiceshop/01_main.hy so that Raider can find it when running. Now let’s write the actual script that’ll start the “authentication” process in Raider:

#!/usr/bin/env python3

from raider import Raider

raider = Raider("juiceshop")
raider.config.proxy = "http://localhost:8080"
raider.authenticate()

Save the file, and if you’ve done everything correctly, you should see the new request we created in ZAProxy and the script will output the response body:

python script.py
INFO:root:Running stage get_questions

HTTP response body:
{"status":"success","data":[{"id":1,"question":"Your eldest siblings middle name?","createdAt":"2021-09-07T15:41:08.631Z","updatedAt":"2021-09-07T15:41:08.631Z"},{"id":2,"question":"Mother's maiden name?","createdAt":"2021-09-07T15:41:08.632Z","updatedAt":"2021-09-07T15:41:08.632Z"},{"id":3,"question":"Mother's birth date? (MM/DD/YY)","createdAt":"2021-09-07T15:41:08.633Z","updatedAt":"2021-09-07T15:41:08.633Z"},{"id":4,"question":"Father's birth date? (MM/DD/YY)","createdAt":"2021-09-07T15:41:08.633Z","updatedAt":"2021-09-07T15:41:08.633Z"},{"id":5,"question":"Maternal grandmother's first name?","createdAt":"2021-09-07T15:41:08.633Z","updatedAt":"2021-09-07T15:41:08.633Z"},{"id":6,"question":"Paternal grandmother's first name?","createdAt":"2021-09-07T15:41:08.633Z","updatedAt":"2021-09-07T15:41:08.633Z"},{"id":7,"question":"Name of your favorite pet?","createdAt":"2021-09-07T15:41:08.634Z","updatedAt":"2021-09-07T15:41:08.634Z"},{"id":8,"question":"Last name of dentist when you were a teenager? (Do not include 'Dr.')","createdAt":"2021-09-07T15:41:08.634Z","updatedAt":"2021-09-07T15:41:08.634Z"},{"id":9,"question":"Your ZIP/postal code when you were a teenager?","createdAt":"2021-09-07T15:41:08.634Z","updatedAt":"2021-09-07T15:41:08.634Z"},{"id":10,"question":"Company you first work for as an adult?","createdAt":"2021-09-07T15:41:08.634Z","updatedAt":"2021-09-07T15:41:08.634Z"},{"id":11,"question":"Your favorite book?","createdAt":"2021-09-07T15:41:08.635Z","updatedAt":"2021-09-07T15:41:08.635Z"},{"id":12,"question":"Your favorite movie?","createdAt":"2021-09-07T15:41:08.635Z","updatedAt":"2021-09-07T15:41:08.635Z"},{"id":13,"question":"Number of one of your customer or ID cards?","createdAt":"2021-09-07T15:41:08.635Z","updatedAt":"2021-09-07T15:41:08.635Z"},{"id":14,"question":"What's your favorite place to go hiking?","createdAt":"2021-09-07T15:41:08.635Z","updatedAt":"2021-09-07T15:41:08.635Z"}]}

Creating the user

Now let’s take a look at the second HTTP request:

POST http://localhost:3000/api/Users/ HTTP/1.1
User-Agent: Mozilla/5.0 (iPhone; CPU OS 10_15_5 (Ergänzendes Update) like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Mobile/14E304 Safari/605.1.15
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Content-Type: application/json
Content-Length: 265
Origin: http://localhost:3000
DNT: 1
Connection: keep-alive
Referer: http://localhost:3000/
Cookie: language=en; cookieconsent_status=dismiss
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Pragma: no-cache
Cache-Control: no-cache
Host: localhost:3000


{
  "email": "[email protected]",
  "password": "UserPassword",
  "passwordRepeat": "UserPassword",
  "securityQuestion":   {
    "id": 4,
    "question": "Father's birth date? (MM/DD/YY)",
    "createdAt": "2021-09-07T15:41:08.633Z",
    "updatedAt": "2021-09-07T15:41:08.633Z"
  },
  "securityAnswer": "10/10/1980"
}

Before we configure the actual Flow, we need to extract data from previous response.

Extract question

Here we can see that the actual contents of the question is being sent to the server, so we first need to extract those with Raider from the previous Flow. Since we want to automate the registration flow, we don’t really care which question gets selected, so I’ll just pick the first one. This can be done using the Json plugin:

(setv question
      (Json
        :name "question"
        :extract "data[0]"))

Now Raider has at its disposal a new variable which contains the data that we extracted from the first HTTP request. To see if it actually works and Raider extracted everything properly, we add the variable names as outputs and add them to the Print operation.

;; Define the first request
(setv get_questions
      (Flow
        :name "get_questions"  ;; Flow identifier
        :request
        (Request
          :method "GET"
          :path "/api/SecurityQuestions/")
        ;; Extract outputs below from the response
        :outputs [question]
        ;; Print those outputs on the command line
        :operations [(Print question)]))

And now if we rerun the same python script, we get:

INFO:root:Running stage get_questions
id = 1
question = Your eldest siblings middle name?
created_at = 2021-09-07T15:41:08.631Z
updated_at = 2021-09-07T15:41:08.631Z

Write second flow

At this point we have all the information we need to send the second request, so let’s configure it in the hyfile:

;; Register new user
(setv register_user
      (Flow
        :name "register_user"
        :request
        (Request
          :method "POST"
          :path "/api/Users/"
          :data
          (PostBody
            :encoding "json"
            {"email" email
             "password" password
             "passwordRepeat" password
             "securityQuestion" question
             "securityAnswer" answer}))
        :outputs [user_id]
        :operations [(Grep
                       :regex "email must be unique" ;; This message is returned when reusing the email
                       :action [(Print "Email already registered, use a new email")
                                (NextStage "register_user")]) ;; go back to the same stage
                     (Print.body)
                     (NextStage "security_answer")]))

Submit security answer

The last part necessary to finish registration is to submit the answer to the security question. In HTTP the request looks like this:

POST http://localhost:3000/api/SecurityAnswers/ HTTP/1.1
User-Agent: Mozilla/5.0 (iPhone; CPU OS 10_15_5 (Ergänzendes Update) like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Mobile/14E304 Safari/605.1.15
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Content-Type: application/json
Content-Length: 53
Origin: http://localhost:3000
DNT: 1
Connection: keep-alive
Referer: http://localhost:3000/
Cookie: language=en; welcomebanner_status=dismiss; cookieconsent_status=dismiss; continueCode=Jb4MrORV3wYJLXQnpxlPyKZerov6GmPxd5MNak489zBjE1Wm2bq7DOgkDEmB
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Host: localhost:3000


{
  "UserId": 22,
  "answer": "my answer",
  "SecurityQuestionId": 1
}

And the request translated in Hy:

;; Submit security answer
(setv security_answer
      (Flow
        :name "security_answer"
        :request
        (Request
          :method "POST"
          :path "/api/SecurityAnswers/"
          :data
          (PostBody
            :encoding "json"
            {"UserId" user_id
             "answer" answer
             "SecurityQuestionId" 1}))))

Now putting this all together, and adding a if clause asking whether to create a new user, we get the following configuration file:

;; Use the _base_url so that Request objects can be created with :path instead
;; of :url
(setv _base_url "http://localhost:3000")
(setv _authentication [])


(setv question
      (Json
        :name "question"
        :extract "data[0]"))

(setv email (Prompt "email"))
(setv password (Prompt "password"))
(setv answer (Prompt "answer"))

(setv user_id
      (Json
        :name "user_id"
        :extract "data.id"))

(if (= ((. (input "Register new user? Y/N: ") lower)) "y")
    (do
      ;; Define the first request
      (setv get_questions
            (Flow
              :name "get_questions"  ;; Flow identifier
              :request
              (Request
                :method "GET"
                :path "/api/SecurityQuestions/")
              ;; Extract outputs below from the response
              :outputs [question]
              ;; Print those outputs on the command line
              :operations [(Print question)
                           (NextStage "register_user")]))
      ((. _authentication append) get_questions)
      ;; Register new user
      (setv register_user
            (Flow
              :name "register_user"
              :request
              (Request
                :method "POST"
                :path "/api/Users/"
                :data
                (PostBody
                  :encoding "json"
                  {"email" email
                   "password" password
                   "passwordRepeat" password
                   "securityQuestion" question
                   "securityAnswer" answer}))
              :outputs [user_id]
              :operations [(Grep
                             :regex "email must be unique" ;; This message is returned when reusing the email
                             :action [(Print "Email already registered, use a new email")
                                      (NextStage "register_user")]) ;; go back to the same stage
                           (Print.body)
                           (NextStage "security_answer")]))
      ((. _authentication append) register_user)


      ;; Submit security answer
      (setv security_answer
            (Flow
              :name "security_answer"
              :request
              (Request
                :method "POST"
                :path "/api/SecurityAnswers/"
                :data
                (PostBody
                  :encoding "json"
                  {"UserId" user_id
                   "answer" answer
                   "SecurityQuestionId" 1}))))
      ((. _authentication append) security_answer)))

(setv _users [{:username "[email protected]"
               :password "UserPassword"}])

Now it’s possible to invoke raider and create a new user account in JuiceShop. Since it’s already inside _authentication variable, the registration can be run with the authenticate() method:

from raider import Raider
session=Raider('juiceshop')
session.authenticate()

If you want to use the username and password from the _users variable, you can replace the Prompt entries with:

(setv email (Variable "username"))
(setv password (Variable "password"))

and this will create the user for the active entry in _users variable.

Automating logins

Checking the traffic generated during the login process, we can see that only one request is needed, and it looks like this:

POST http://localhost:3000/rest/user/login HTTP/1.1
User-Agent: Mozilla/5.0 (iPhone; CPU OS 10_15_5 (Ergänzendes Update) like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Mobile/14E304 Safari/605.1.15
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Content-Type: application/json
Content-Length: 52
Origin: http://localhost:3000
DNT: 1
Connection: keep-alive
Referer: http://localhost:3000/
Cookie: language=en; welcomebanner_status=dismiss; cookieconsent_status=dismiss; continueCode=Jb4MrORV3wYJLXQnpxlPyKZerov6GmPxd5MNak489zBjE1Wm2bq7DOgkDEmB
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Host: localhost:3000


{
  "email": "[email protected]",
  "password": "UserPassword"
}

And the server responds back with a JSON object containing our data:

{"authentication": {
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6eyJpZCI6MjQsInVzZXJuYW1lIjoiIiwiZW1haWwiOiJ1c2VyMUBtYWlsLmNvbSIsInBhc3N3b3JkIjoiZDFhNWZmOGRiZWVkYWEzNDA2MzY4NzI0ZWJiZDNjYjAiLCJyb2xlIjoiY3VzdG9tZXIiLCJkZWx1eGVUb2tlbiI6IiIsImxhc3RMb2dpbklwIjoiMC4wLjAuMCIsInByb2ZpbGVJbWFnZSI6Ii9hc3NldHMvcHVibGljL2ltYWdlcy91cGxvYWRzL2RlZmF1bHQuc3ZnIiwidG90cFNlY3JldCI6IiIsImlzQWN0aXZlIjp0cnVlLCJjcmVhdGVkQXQiOiIyMDIxLTA5LTE0IDE0OjU0OjEzLjgwOCArMDA6MDAiLCJ1cGRhdGVkQXQiOiIyMDIxLTA5LTE0IDE0OjU0OjEzLjgwOCArMDA6MDAiLCJkZWxldGVkQXQiOm51bGx9LCJpYXQiOjE2MzE2MzEzMDYsImV4cCI6MTYzMTY0OTMwNn0.QSY7-xFrutEKZwTIeQYPYjsC3lZ9H6ODudtBT0YHjBa_5Ci2UqSXqqIl6whCVEmikhXQHQGhIX3D51dPD7lAbrgT03Lz2XJ0US3vEY6lzfajbha_mXpCbxIQ5DNeRR8AmeYq2prHXxxQCnAoWl4UO4XjQCQX5728YTbFJ0sWIPM",
  "bid": 6,
  "umail": "[email protected]"
}}

Assuming we already registered our only user inside _users, we can create a simple Flow that’ll replicate the login behavior:

(setv authtoken
      (Json
        :name "authtoken"
        :extract "authentication.token"))

(setv login
      (Flow
        :name "login"
        :request
        (Request
          :method "POST"
          :path "/rest/user/login"
          :data
          (PostBody
            :encoding "json"
            :data
            {"email" email
             "password" password}))
        :outputs [authtoken]
        :operations
        [(Print authtoken)]))

((. _authentication append) login)

And now, when combining all the pieces together, we can obtain valid tokens to do whatever we want as the authenticated user.