Beyond


This is the beyond page that an additional post enumeration and assessment are conducted as the root user after compromising the target system.

crontab


root@soccer:~# crontab -l | grep -v '^#'
*/3 * * * * /bin/rm /var/www/html/tiny/uploads/*
*/3 * * * * /bin/rm /usr/local/share/dstat/*
*/3 * * * * /bin/rm -rf /home/player/.dstat
*/10 * * * * /usr/bin/mysql < /root/run.sql

The root user has 5 cronjobs;

  • For every 3 mins, wipe the /var/www/html/tiny/uploads/ directory
  • For every 3 mins, wipe the /usr/local/share/dstat/ directory
  • For every 3 mins, remove the /bin/rm -rf /home/player/.dstat directory
  • For every 10 mins, run mysql with a SQL script; /root/run.sql

/root/run.sql


root@soccer:~# cat /root/run.sql
delete from soccer_db.accounts where id != 1324;

Basically restoring the soccer_db.accounts table to contain only the player user with id=1324

Updated Soccer Club Web app


root@soccer:~# ll app
total 108
drwxr-xr-x  5 root root  4096 Dec 12  2022 ./
drwx------ 10 root root  4096 Dec 13  2022 ../
-rwxr-xr-x  1 root root  4979 Nov 28  2022 app.js*
-rw-r-----  1 root root   489 Nov 17  2022 ecosystem.config.js
drwxr-xr-x 92 root root  4096 Dec  1  2022 node_modules/
-rw-r--r--  1 root root 66511 Nov 28  2022 package-lock.json
-rw-r--r--  1 root root   401 Nov 17  2022 package.json
-rwxr-xr-x  1 root root  1179 Nov 29  2022 server.js*
drwxr-xr-x  5 root root  4096 Nov 28  2022 static/
drwxr-xr-x  2 root root  4096 Dec 12  2022 views/

It runs off /root/app

app.js


root@soccer:~/app# cat app.js 
const ejs = require('ejs');
const mysql = require('mysql');
const express = require('express');
const flash = require('connect-flash');
const session = require('express-session');
 
const path = require('path');
 
const connection = mysql.createConnection({
        host: 'localhost',
        user: 'player',
        password: 'PlayerOftheMatch2022',
        database: 'soccer_db'
});
 
const app = express();
 
app.use(session({
    secret: 'aVeryG00dS3cr3t',
    resave: false,
    saveuninitialized: false
}));
app.use(flash());
app.use(express.json());
app.use(express.urlencoded({
    extended: true
}));
 
app.use(express.static(path.join(__dirname, 'static')));
app.set('views', path.join(__dirname, '/views'));
app.set('view engine', 'ejs');
 
function setAuth(request) {
    if (typeof request.session.authenticated === 'undefined') {
        request.session.authenticated = false;
    }
}
 
app.get('/', function(request, response) {
    setAuth(request);
    response.render('index', {
        title: "Soccer", isLoggedIn: request.session.authenticated
    })
});
 
app.get('/match', function(request, response) {
    setAuth(request);
    response.render('match', {
        title: "Soccer - Matches", isLoggedIn: request.session.authenticated
    })
});
 
app.use('/signup', function(request, response) {
    setAuth(request);
    if (request.session.authenticated) {
        response.redirect('/check')
    } else {
        if (request.method == 'POST') {
            let username = request.body.username;
            let password = request.body.password;
            let email = request.body.email;
            if (username && password && email) {
                connection.query('SELECT * FROM accounts WHERE email = ?',[email], function(error, results, fields) {
                    if (error) throw error;
                    if (results.length > 0) {
                        response.send('Account Already Exists');
                    } else {
                        connection.query('INSERT INTO accounts (id, email, username, password) VALUES (FLOOR((RAND() * (99999-51891+1))+51891), ?, ?, ?)',[email, username, password], function(error, results, fields) {
                            if (error) throw error;
                            response.redirect('/login')
                        });
                    }
                });
            } else {
                response.end();
            }
        } else {
            response.render('signup', {
                title : "Soccer - Signup", isLoggedIn: request.session.authenticated
            });
        }
    }
});
 
app.use('/login', function(request, response) {
    setAuth(request);
    if (request.session.authenticated) {
        response.redirect('check');
    } else {
        if (request.method == 'POST') {
            let email = request.body.email;
            let password = request.body.password;
            if (email && password) {
                connection.query('SELECT * FROM accounts WHERE email = ? AND password = ?', [email , password], function(error, results, fields) {
                    if (error) throw error;
                    if (results.length > 0) {
                        var rows = JSON.parse(JSON.stringify(results[0]));
                        request.session.authenticated = true;
                        request.session.email = email;
                        response.redirect('/check')
                    } else {
                        response.send('Incorrect Email and/or Password!');
                    }
                    response.end();
                });
            } else {
                response.end();
            }
        } else {
            response.render('login', {
                title : "Soccer - Login", isLoggedIn: request.session.authenticated
            });
        }
    }
});
 
app.get('/logout', function(request, response) {
    request.session.destroy();
    response.redirect('/');
});
 
app.use('/check', function(request, response) {
    setAuth(request);
    (async ()=>{
        if (request.session.authenticated) {
            email=request.session.email;
            const result = await new Promise((resolve) => {
                connection.query("SELECT * FROM accounts WHERE email = ?", [email], (error, results) => {
                    if (error) throw error;
                    var row = JSON.parse(JSON.stringify(results[0]));
                    resolve(row);
                });
            });
            request.session.sessionID = result.id;
            request.flash('success', request.session.sessionID);
            response.locals.message = request.flash();
            response.render('check', {
                title : "Soccer - Check", isLoggedIn: request.session.authenticated
            });
        } else {
            response.send('Please login to view this page!');
        }
        response.end();
    })()
});
 
app.listen(3000, '127.0.0.1');
console.log("Web Application Started");

ecosystem.config.js


root@soccer:~/app# cat ecosystem.config.js
module.exports = {
  apps : [{
    script: 'index.js',
    watch: '.'
  }, {
    script: './service-worker/',
    watch: ['./service-worker']
  }],
 
  deploy : {
    production : {
      user : 'SSH_USERNAME',
      host : 'SSH_HOSTMACHINE',
      ref  : 'origin/master',
      repo : 'GIT_REPOSITORY',
      path : 'DESTINATION_PATH',
      'pre-deploy-local': '',
      'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env production',
      'pre-setup': ''
    }
  }
};

package.json


root@soccer:~/app# cat package.json 
{
  "name": "auth",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "ejs": "^3.1.8",
    "express": "^4.18.2",
    "express-flash": "^0.0.2",
    "express-session": "^1.17.3",
    "mysql": "^2.18.1",
    "path": "^0.12.7",
    "ws": "^8.9.0"
  }
}

server.js


root@soccer:~/app# cat server.js
const mysql = require('mysql');
const serv = require('ws');
const express  = require('express');
const server = express().listen(9091, '0.0.0.0')
const socket = new serv.Server({ server });
const connection = mysql.createConnection({
    host : "localhost",
    user : "player",
    password : 'PlayerOftheMatch2022',
    port: 3306,
    database : "soccer_db"
})
connection.connect();
socket.on('connection', ws=> {
  ws.on('message', function incoming(data) {
    try {
      var id = JSON.parse(data).id;
    } catch (e) {
      //console.log(e);
    }
    (async () => {
          try {
            const query = `Select id,username,password  FROM accounts where id = ${id}`;
            await connection.query(query, function (error, results, fields) {
                if (error) {
                  ws.send("Ticket Doesn't Exist");
                } else {
                  if (results.length > 0) {
                    ws.send("Ticket Exists")
                  } else {
                    ws.send("Ticket Doesn't Exist")
                  }
                }
              });
          } catch (error) {
            ws.send("Error");
          }
      })()
   });
});

This is the WebSocket API server

with the vulnerable SQL query

/views/


root@soccer:~/app# ll views
total 48
drwxr-xr-x 2 root root 4096 Dec 12  2022 ./
drwxr-xr-x 5 root root 4096 Dec 12  2022 ../
-rw-r--r-- 1 root root 3317 Nov 28  2022 check.ejs
-rw-r--r-- 1 root root 5189 Nov 28  2022 index.ejs
-rw-r--r-- 1 root root  458 Nov 28  2022 layout.ejs
-rw-r--r-- 1 root root 1739 Dec 12  2022 login.ejs
-rw-r--r-- 1 root root 8508 Nov 28  2022 match.ejs
-rw-r--r-- 1 root root 1602 Nov 28  2022 nav.ejs
-rw-r--r-- 1 root root 2168 Dec 12  2022 signup.ejs

frontend