Hack The Box - Perfection (Easy, Season 4)
Walkthrough / Write Up
by: ballzach
=====================
- Spin up the box on the “Arena” VPN so we have our own instance, for more stability.
- Add the ip address and host '10.x.x.x perfection.htb' to /etc/hosts
- Ping the instance to test activity:
- We're live:
Enumeration
- Start with a NMAP scan to view open ports and services running:
- We see a HTTPd running on port 80, so we'll brute force scan directories & files with feroxbuster:
- Visiting http://perfection.htb we see a simple site for:
Weighted Grade Calculator
A tool to calculate the total grade in a class based on category scores and percentage weights.
- Visiting http://perfection.htb/about we find information about potential users and thus; usernames:
- Visiting http://perfection.htb/weighted-grade we find a webapp that very well is our way to gain foothold:

- Let's find information about the dev/tech stack running:

- Wappalyzer is a really great Firefox plugin that shows full tech stacks running behind a site:
- We know nginx is running the HTTPd, from our NMAP scan, but it shows something interesting:
- It appears to be running a reverse proxy, also.
- A reverse proxy is used to call a service running on localhost (http://127.0.0.1, port: xxxx) - usually a webapp!
- We also see that it picked up Ruby 3.0.2 running.
- We can infer from this info that Weighted-Grade-Calculator is a Ruby-based webapp running on the site, via a reverse-proxy through nginx.
- Let's see if we can find more information on the site, and also the HTTP HEADERS:
- At the bottom of the /weighted-grade page we find:
- Firefox > F12 > Network > Headers > Reload Page:

- We pick up the HTTP Response Headers:
- We can see it picks up
Server: WEBrick/1.7.0 (Ruby/3.0.2/2021-07-07) - How does it have two “Server:” headers?
- We know that Wappalyzer & NMAP picked up nginx as the HTTPd, Wappalyzer showed a “reverse proxy” on nginx, with Ruby v3.0.2
- This confirms our previous inference that “Weighted-Grade-Calculator” is in fact a Ruby-based webapp, running on WEBrick, which is a Ruby-based HTTPd server, via the nginx reverse-proxy.
- We can safely assume that this is our path to foothold, since we're able to communicate with the WEBrick server / Ruby backend, via the nginx-hosted site.
Exploit Research
WEBrick is an HTTP server toolkit that can be configured as an HTTPS server, a proxy server, and a virtual-host server.
WEBrick features complete logging of both server operations and HTTP access.
WEBrick supports both basic and digest authentication in addition to algorithms not in RFC 2617.
A WEBrick server can be composed of multiple WEBrick servers or servlets to provide differing behavior on a per-host or per-path basis.
WEBrick includes servlets for handling CGI scripts, ERB pages, Ruby blocks and directory listings.
WEBrick also includes tools for daemonizing a process and starting a process at a higher privilege level and dropping permissions.
- https://github.com/ruby/webrick/releases/tag/v1.7.0
- Released on November 17, 2022 -
Google: “Ruby v3.0.2 release date changelog”
- https://www.ruby-lang.org/en/news/2021/07/07/ruby-3-0-2-released/
- Released on July 7, 2021 - Google: “Webrick 1.7.0 vulnerabilities” / “Webrick 1.7.0 exploits” / “Ruby 3.0.2 vulnerabilities” / “Ruby 3.0.2 exploits”
- Nothing really interesting comes up for CVEs/exploits on these versions.
- We would be looking for CVEs/exploits around the release dates of these versions above.
- There are some older ones: - https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=ruby
-CVE-2021-41817
-Date.parse in the date gem through 3.2.0 for Ruby allows ReDoS (regular expression Denial of Service) via a long string. The fixed versions are 3.2.1, 3.1.2, 3.0.2, and 2.0.1. - https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=webrick
- CVE-2020-25613
- An issue was discovered in Ruby through 2.5.8, 2.6.x through 2.6.6, and 2.7.x through 2.7.1. WEBrick, a simple HTTP server bundled with Ruby, had not checked the transfer-encoding header value rigorously. An attacker may potentially exploit this issue to bypass a reverse proxy (which also has a poor header check), which may lead to an HTTP Request Smuggling attack.
- CVE-2019-16254
- Ruby through 2.4.7, 2.5.x through 2.5.6, and 2.6.x through 2.6.4 allows HTTP Response Splitting. If a program using WEBrick inserts untrusted input into the response header, an attacker can exploit it to insert a newline character to split a header, and inject malicious content to deceive clients. NOTE: this issue exists because of an incomplete fix for CVE-2017-17742, which addressed the CRLF vector, but did not address an isolated CR or an isolated LF.
- CVE-2019-16201
- WEBrick::HTTPAuth::DigestAuth in Ruby through 2.4.7, 2.5.x through 2.5.6, and 2.6.x through 2.6.4 has a regular expression Denial of Service cause by looping/backtracking. A victim must expose a WEBrick server that uses DigestAuth to the Internet or a untrusted network.
- CVE-2019-11879
- ** DISPUTED ** The WEBrick gem 1.4.2 for Ruby allows directory traversal if the attacker once had local access to create a symlink to a location outside of the web root directory. NOTE: The vendor states that this is analogous to Options FollowSymlinks in the Apache HTTP Server, and therefore it is "not a problem."
- CVE-2020-25613
- https://security.snyk.io/vuln?search=webrick
- https://vuldb.com/?search | “ruby 3.0.2”
- https://vuldb.com/?id.199299
- VDB-199299 · CVE-2022-28738
- RUBY UP TO 3.0.3/3.1.1 REGEXP COMPILER DOUBLE FREE
- This affects some unknown functionality of the component Regexp Compiler. The manipulation with an unknown input leads to a double free vulnerability.
- The product calls free() twice on the same memory address, potentially leading to modification of unexpected memory locations.
- https://vuldb.com/?search | “webrick”
Foothold Research
- Nothing found thus far raises an eyebrow. Usually on an “easy” Hack The Box module, there should be a known CVE per their box creation requirements.
- However, none of these CVEs found really even apply to the versions of Webrick & Ruby that we're dealing with.
- CVE-2022-28738 is a “double free” vulnerbaility, which exploits memory buffers.
- CVE-2021-41817 is a DDoS vulnerability.
- Neither of these would apply to this (easy) box or situation. The memory buffer vuln, perhaps - but not an easy box.
- It is however, important to note the description of these vulnerabilites. “REGEX” or “Regular Expressions” stands out. We will see how this applies later.
- SO, let's take a look at how the weighted-grade calculator web application works:
- Remember the instructions said to ensure your “weights” total to 100.
- If you do not, then you'll get an error message saying “Please reenter! Weights do not add up to 100.”
- Otherwise, if you do not use a row, then use “N/A” for the Category, and “0" for the Grade and Weight.
- If you do it correctly, you'll get a working response:

- Let's look at the request parameters from the user arguments inputs being sent to the webapp:
-
This can be done one of two ways:
-
- Intercept the request with a Burpsuite proxy:

- Intercept the request with a Burpsuite proxy:
-
- F12 - Developer Tools, in Firefox or other browser. “F12” > Refresh the page & enter fields and submit >
- > Network tab > Click the correct HTTP “Status” (200 POST weighted-grade-calc) > Request > Click “Raw”

- You'll get the following request parameters being sent to the webapp:
-
-
- This leaves me with two suspected attack paths for a foothold:
- SQL Injection (SQLi)
- Command Injection / Server Side Template Injection (SSTI)
- SQL Injection won't be my first option, because we have 15 parameters (user arguments entered.) That would take forever to test.
- A Command injection / or SSTI vulnerability seems more likely, and here's my explained methodology of why:
- You can enumerate for a command injection vulnerability very easily.
- Similar to using
'for SQLi as the “escape character delimiter," you can usually use'or;or`symbols to do some basic enumeration - You can do so one of two ways:
- Via the URL:
- We'll try to visit http://perfection.htb/'1

- Hmm.. this is really interesting...
- AND - right-clicking the unloaded image thumbnail and choosing “Copy Image Link” we get:
http://127.0.0.1:3000/__sinatra__/404.png - This confirms our previous assumption about the nginx reverse-proxy hosting the Ruby app running on localhost.
- We'll try to visit http://perfection.htb/'1
- Or via the HTTP request parameters:
- We'll try to enter
'or;or`in any of the user argument fields in the calculator:

- Hmm.. another interesting outcome...
- We'll try to enter
- Via the URL:
- Similar to using
- This may seem bad, but it's great because we have more RELEVENT information.
- First, we see something called “Sinatra” running on the given error page, also verifying that it's the enging running behind the reverse-proxy.
- Secondly, we can see “Malicious input blocked” which tells us there is likely a “filter” that is filtering our input
- This goes back to the regex / regular expressions valnerabilities we saw on old CVEs for Ruby.
- If you're not familar with “Regex” I would suggest you get familar with it via GREP or otherwise.
- Essentially, it's a method of pattern or character matching. The program author can preset regex requirments for the user input.
- E.G: block characters like ' and if caught, tell you “Malicious input blocked.”
- So, what is Sinatra?
- Sinatra is a lightweight web application framework for Ruby. Like Flask or Django is to Python, except:
- Unlike Flask, Sinatra is a “DSL” or Domain-Specific Language, built to handle specific tasks with simpler syntax versus a GPL (General-Purpose Language.)
- https://sinatrarb.com/
- https://github.com/sinatra/sinatra
- Further digging reveals that Sinatra (like WEBrick) work with “TEMPLATING ENGINES” such as “ERB” aka “Embedded Ruby” aka Erubi
-
AHA! Template / Templating Engine.... such as Server Side Template Injection?
- You should be familiar with Jinja2, the templating engine for Python webapps such as Django ro Flask.
{{7*7}}ring a bell? This is the default command injection to test for to see if a webapp is vulnerable...- Let's see what's available for Ruby SSTI:
- https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#erb-ruby
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#ruby---basic-injections
<%= 7 * 7 %>is the recommended format- Except, I suspect that regex filter is going to give us “Malicious code blocked”...

- Mmhmm....
- SO, we need to look into SSTI / injection filer bypass or obfuscation, specific to Sinatra language regex / ERB templates:
- https://blog.devops.dev/ssti-bypass-filter-0-9a-z-i-08a5b3b98def
- SSTI Bypass Filter
/^[0–9a-z ]+$/i
- SSTI Bypass Filter
- https://codeclimate.com/blog/rails-insecure-defaults#:~:text=Regular%20Expression%20Anchors%20in%20Format%20Validations
- Insecure Defaults in Ruby on Rails: Regular Expression Anchors in Format Validations
- https://codeql.github.com/codeql-query-help/ruby/rb-regex-badly-anchored-regexp/#
- CodeQL query help for Ruby: Badly anchored regular expression
- https://davidhamann.de/2022/05/14/bypassing-regular-expression-checks/#:~:text=Bypassing%20regular%20expression%20checks%20with%20a%20line%20feed
- Bypassing regular expression checks with a line feed
- https://www.youtube.com/watch?v=E6uJQAVT6SM
- (Ruby SSTI | Bypass regex | TFC CTF| Diamonds walkthrough | 0xraminfosec | TamilCTF)
- https://blog.devops.dev/ssti-bypass-filter-0-9a-z-i-08a5b3b98def
- What does it mean?
- In the Ruby webapp, the developer put in a regex filter for accepting and filtering the user input fields.
- The regex is something like:
/^[0–9a-zA-Z ]+$/i- This tells the webapp to accept any user input characters that are 0-9, lowercase a-z, uppercase A-Z, or a
<space> - Anything else, block. Which is why we cannot get our special characters
'or;or`-- or<%= 7 * 7 %>-- by it, lest we get “Malicious code blocked”
- This tells the webapp to accept any user input characters that are 0-9, lowercase a-z, uppercase A-Z, or a
- How do we bypass the filter? As David Hamann explains in his blog, and Code Climate shows - linked above, and as you can see in the other links provided...
- In Ruby3.x, the
^and$match the start and end of each line, whereas\Aand\zmatch the start and end of a string. - These are the “anchors” telling the webapp the position of the user's argument inputted.
- THE PROBLEM IS: if the webapp regex passes the user argument from the first line, the filter ignores any other lines and they bypass the filter and get ran
- In Ruby3.x, the
- Case in point:
- YOU ABSOLUTELY MUST HAVE THE
%0AFOR “NEW LINE” - If we URL-encode this (To get the “new line” encoded correctly you have to use an editor like https://urlencoder.org or https://urluncoder.io *OR*
<Decoder>/<Highlight+CTRL+u>in Burpsuite) you get:
- Here it is done in Firefox:

- Here it is done in Burpsuite:

- Here ^^ We can see that the “ABC" satisfies the REGEX
/^[0–9a-zA-Z ]+$/iand since that line ends (with%0A), we escape/bypass the filter.- Anything after the first line gets processed or executed
- SO, if that's the case - HOW DO WE EXECUTE SYSTEM COMMANDS?!?!?!?!!
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#ruby---code-execution
- READ FILE:
<%= File.open('/etc/passwd').read %> - CODE EXECUTION:
- Testing File.open().read:

- Testing system() command execution:
- This does not transmit the command result over HTTP, only shows “true” if successful or “false” if unsuccessful.

- Testing backticks
``code execution:

- Testing Bash Reverse Shell via backticks command exec:

- No good, we have to call the bash command from
bash -c
FOOTHOLD
You MUST call bash command from the bash -c flag!

- SUCCESS!!! ^^^^ Reverse Shell!
- PRO TIP: If you are doing the above injections that you see working here, but you are still getting “Malicious input blocked," then you have to make sure your web parameters are chained in one line, and word-wrap themselves if they need to.
- MAKE SURE they are not manually broken up on different lines like:
- MAKE SURE they are not manually broken up on different lines like:
- If burpsuite does this naturally, that's fine - that's “wrapped-text” due to lack of space. It's still processed as “one line" and thus, works.
- When you have the injection UN-URL-Encoded, it will break up into multi-lines. When you URL-Encode it, it should revert.
- REMEMBER: the
%0Awhich is essentially\nfor “new line," is the key to this working. Unless you use an online-url-encoder (https://urlencoder.com) then you may have to insert this manually in burpsuite. - *DELETE ALL BLANK LINES/SPACES AFTER THE
weight5=xxALSO* or you will throw a “Malicious input blocked” error. - NOTE: You can URL-Encode all characters, instead of just "necessary characters. DO NOT double-url-encode characters.
User Enumeration
- Linpeas:


- Our user, susan, is part of the “sudo” group -- but we cannot run “sudo” without a password.
- Interestingly enough, linpeas picks up a database file in /home/susan/Migration/pupilpath_credentials.db
- We can view this with sqlite3
- Linpeas also picks up a /var/mail/susan email file that is in our user's name, and our user group “susan” owns and has read permissions.
- We'll take a look at the email.
- We'll take a look at the email.
- We pick up a password hash from the database file.
- The email indicates that the password format is: {firstname}{firstname backwards}{randomly generated integer between 1 and 1,000,000,000}
- So susan's password should be “susan_nasus_” ending with a random integer / number between 1 & 1,000,000
- This is an easy task for hashcat:
- Here we see that the hash is a SHA-256 cipher, that is mode 1400 on hashcat, or
-m 1400.
- We want to brute force the random integer, so we will use attack mode 3, or
-a 3
- A random integer between 1 and 1,000,000 could be up to 9 digits. Hashcat has a built in charset with
?dfor numbers 0-9, with each?drepresenting a digit.- So we just have to format this for nine digits, or nine times
?d - If susan's password has less than 9 digits, hashcat will crack the SHA-256 cipher accordingly, but we want a max of 9 digits just in case we need them.
- So we just have to format this for nine digits, or nine times
PASSWORD: susan_nasus_413759210
You can crack it in John The Ripper, also.
However, I would not suggest it. I tried it and it keeps a ~/.john/john.log file for cache, and trying this ate up over 10gb of space. So, if you use a VM with limited space, forget it. If you manage to get it to work, leave a comment, as I didn't get to finish it..
- https://security.stackexchange.com/questions/234447/john-the-ripper-find-password-when-you-know-a-part-of-it
- https://miloserdov.org/?p=4961
-
https://akimbocore.com/article/custom-rules-for-john-the-ripper/
- In
john.conf(make sure it's the correct one, I have multiple) you can add:
- Or locate your
../john/rules/*.rulefile, createAppedDigits.rule:
- Then, with the second option, you also need to add to
john.conf:
- In
echo -n 'abeb6f8eb5722b8ca3b45f6f72a0cf17c7028d62a15a30199347d9d74f39023f' > susan.hashecho -n 'susan_nasus_' > susan.passjohn --rule=AppendDigits --wordlist=susan.pass susan.hash
That should work.
Privilege Escalation
- Log in with SSH since we have the password
- Since our user susan is part of the “sudo” group, this means we can run sudo commands if we have the password.
- “sudo -l” shows us that we have no restrictions on which sudo commands we can run.
- So, GTFOBins shows
sudo sudo /bin/bashas a way to escalate to root - OR you can simply do
sudo su
$ ssh [email protected]
[email protected]'s password:
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-97-generic x86_64)
You have mail.
susan@perfection:~$ sudo -l
[sudo] password for susan:
Matching Defaults entries for susan on perfection:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User susan may run the following commands on perfection:
(ALL : ALL) ALL
susan@perfection:~$ sudo sudo /bin/bash
root@perfection:/home/susan# id
uid=0(root) gid=0(root) groups=0(root)
root@perfection:/home/susan# whoami
root
root@perfection:/home/susan# cat /root/root.txt
f941301ab52baab659f4e325aff57e7b
root@perfection:/home/susan# exit
susan@perfection:~$ sudo su
root@perfection:/home/susan# id
uid=0(root) gid=0(root) groups=0(root)
root@perfection:/home/susan# whoami
root
root@perfection:/home/susan# cat /root/root.txt
f941301ab52baab659f4e325aff57e7b
|
VOILA!
POST ROOT
nginx.conf shows the proxy running on port 3000 locally to call the Ruby App which running on port 3000.
- This can be confirmed by checking netstat:
- Also can be seen in the main.rb app:
/etc/nginx/sites-enabled/default shows the HTTP headers we found in the beginning.

main.rb -- Ruby App
- You can see the "sinatra" & "erb" packages loaded with "require"
- You can also the logic and REGEX filter in the app's code, as we noted before:
- If category1, category3, etc params
[a-zA-Z0-9/ ]do not equal REGEX:
- Then (else) give
@result = "Malicious input blocked" - And the
^= "start of line" and$= "end of line" for REGEX in Ruby v3.x -- it does not account for a "new line" which is why this filter bypass%0Aworks!

