Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add vnc_auth module #138

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ The module is disabled by default - to enable it, rename `docker-compose.additio
### what-vpn
Uses https://github.com/dlenski/what-vpn under the hood. Identifies servers running various SSL VPNs and is licensed under GPL-3.0-or-later.

### vnc_auth
Uses https://nmap.org/nsedoc/scripts/vnc-brute.html under the hood. Performs a brute force attack on VNC servers to guess password. This is licensed under GPL-3.0-or-later.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we also use nmap, which has some weird license

https://nmap.org/npsl/


## Testing
To run the tests, run:

Expand Down Expand Up @@ -80,3 +83,4 @@ We kindly remind you that:
* by contributing to the `ssl_checks` module you agree that the AGPL-3.0 License shall apply to your input automatically, without the need for any additional declarations to be made.
* by contributing to the `sqlmap` module you agree that the GPL-2.0 License shall apply to your input automatically, without the need for any additional declarations to be made.
* by contributing to the `forti_vuln` module you agree that the GPL-3.0 License shall apply to your input automatically, without the need for any additional declarations to be made.
* by contributing to the `vnc_auth` module you agree that the GPL-3.0 License shall apply to your input automatically, without the need for any additional declarations to be made.
Empty file.
Binary file not shown.
Binary file not shown.
40 changes: 40 additions & 0 deletions autoreporter_addons/vnc_auth/reporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from pathlib import Path
from typing import Any, Dict, List

from artemis.reporting.base.language import Language
from artemis.reporting.base.report import Report
from artemis.reporting.base.report_type import ReportType
from artemis.reporting.base.reporter import Reporter
from artemis.reporting.base.templating import ReportEmailTemplateFragment
from artemis.reporting.utils import get_top_level_target


class VncAuthReporter(Reporter): # type: ignore
VNC_WEAK_PASS = ReportType("vnc_auth")

@staticmethod
def create_reports(task_result: Dict[str, Any], language: Language) -> List[Report]:

if task_result["headers"]["receiver"] != "vnc_auth":
return []

if not task_result["status"] == "INTERESTING":
return []

return [
Report(
top_level_target=get_top_level_target(task_result),
target="vnc://" + task_result["target_string"],
report_type=VncAuthReporter.VNC_WEAK_PASS,
timestamp=task_result["created_at"],
additional_data={"pass": task_result["result"]["password"][:3] + "*****"},
)
]

@staticmethod
def get_email_template_fragments() -> List[ReportEmailTemplateFragment]:
return [
ReportEmailTemplateFragment.from_file(
str(Path(__file__).parents[0] / "template_vnc_weak_pass.jinja2"), priority=10
),
]
14 changes: 14 additions & 0 deletions autoreporter_addons/vnc_auth/template_vnc_weak_pass.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% if "vnc_auth" in data.contains_type %}
<li>{% trans %}The following VNC servers allow authenticating with a simple password:{% endtrans %}
<ul>
{% for report in data.reports %}
{% if report.report_type == "vnc_auth" %}
<li>
{{ report.target }}: {% trans %}password{% endtrans %} {{ report.additional_data.pass }}
{{ report_meta(report) }}
</li>
{% endif %}
{% endfor %}
</ul>
</li>
{% endif %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#: autoreporter_addons/vnc_auth/template_vnc_weak_pass.jinja2:2
msgid "The following VNC servers allow authenticating with a simple password:"
msgstr ""

#: autoreporter_addons/vnc_auth/template_vnc_weak_pass.jinja2:7
msgid "password"
msgstr ""
7 changes: 7 additions & 0 deletions autoreporter_addons/vnc_auth/translations/messages.pot
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#: autoreporter_addons/vnc_auth/template_vnc_weak_pass.jinja2:2
msgid "The following VNC servers allow authenticating with a simple password:"
msgstr ""

#: autoreporter_addons/vnc_auth/template_vnc_weak_pass.jinja2:7
msgid "password"
msgstr ""
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#: autoreporter_addons/vnc_auth/template_vnc_weak_pass.jinja2:2
msgid "The following VNC servers allow authenticating with a simple password:"
msgstr ""
"Następujące serwery VNC umożliwiają uwierzytelnianie za pomocą prostego "
"hasła:"

#: autoreporter_addons/vnc_auth/template_vnc_weak_pass.jinja2:7
msgid "password"
msgstr "hasło"
11 changes: 11 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,21 @@ services:
restart: always
command: "python3 -m artemis.modules.karton_whatvpn"

karton-vnc_auth:
build:
context: Artemis-modules-extra
dockerfile: karton_vnc_auth/Dockerfile
volumes: ["./docker/karton.ini:/etc/karton/karton.ini", "${DOCKER_COMPOSE_ADDITIONAL_SHARED_DIRECTORY:-./shared}:/shared/"]
depends_on: [karton-system]
env_file: .env
restart: always
command: "python3 -m artemis.modules.karton_vnc_auth"

autoreporter:
volumes:
- ./Artemis-modules-extra/extra_modules_config.py:/opt/extra_modules_config.py
- ./Artemis-modules-extra/autoreporter_addons/dns_reaper/:/opt/artemis/reporting/modules/dns_reaper/
- ./Artemis-modules-extra/autoreporter_addons/forti_vuln/:/opt/artemis/reporting/modules/forti_vuln/
- ./Artemis-modules-extra/autoreporter_addons/sqlmap/:/opt/artemis/reporting/modules/sqlmap/
- ./Artemis-modules-extra/autoreporter_addons/ssl_checks/:/opt/artemis/reporting/modules/ssl_checks/
- ./Artemis-modules-extra/autoreporter_addons/vnc_auth/:/opt/artemis/reporting/modules/vnc_auth/
10 changes: 10 additions & 0 deletions extra_modules_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,13 @@ class ExtraModulesConfig:

# WPScan API key
WPSCAN_API_KEY = decouple.config("WPSCAN_API_KEY", default=None)

# List of VNC passwords for the vnc_auth module.
VNC_AUTH_PASSWORD_LIST = ["admin", "12345678", "password"]

# Timeout counted in seconds, after which the vnc_auth module starts checking passwords after it's launched.
# This is to wait after fingerprinting the service to avoid sending too many requests to the server.
VNC_AUTH_INITIAL_SLEEP = 60

# Number of seconds to wait between password guesses.
VNC_AUTH_BRUTE_DELAY = 30
11 changes: 11 additions & 0 deletions karton_vnc_auth/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM certpl/artemis:latest

RUN apk add nmap nmap-scripts

COPY karton_vnc_auth/requirements.txt /requirements_vnc_auth.txt
RUN pip install -r /requirements_vnc_auth.txt

WORKDIR /opt/

COPY extra_modules_config.py /opt/
COPY karton_vnc_auth/karton_vnc_auth.py /opt/artemis/modules/
110 changes: 110 additions & 0 deletions karton_vnc_auth/karton_vnc_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
import tempfile
import time
from typing import Any, Dict, Tuple

import nmap
from artemis import load_risk_class
from artemis.binds import Service, TaskStatus, TaskType
from artemis.module_base import ArtemisBase
from artemis.task_utils import get_target_host
from artemis.utils import throttle_request
from karton.core import Task

from extra_modules_config import ExtraModulesConfig


@load_risk_class.load_risk_class(load_risk_class.LoadRiskClass.LOW)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo medium, as it bruteforces passwords

class VncAuth(ArtemisBase): # type: ignore[misc]
"""
Performs a brute force attack on VNC servers to guess password.
"""

identity = "vnc_auth"
filters = [
{"type": TaskType.SERVICE.value, "service": Service.UNKNOWN.value},
]

def brute_vnc(self, host: str, port: int, passwords_file: str) -> Dict[Any, Any]:
np = nmap.PortScanner()
Matie26 marked this conversation as resolved.
Show resolved Hide resolved
result = np.scan(
host,
str(port),
arguments=" ".join(
[
"-Pn",
"--disable-arp-ping",
"--script vnc-brute",
"--script-args",
"'{}'".format(
",".join(
[
"brute.mode=pass",
"brute.useraspass=false",
f"brute.delay={ExtraModulesConfig.VNC_AUTH_BRUTE_DELAY}",
"brute.passonly=true",
"brute.threads=0",
"brute.start=1",
"brute.firstonly=true",
"brute.retries=1",
f"passdb={passwords_file}",
]
)
),
]
),
)

return dict(result)

def parse_result(self, result: Dict[Any, Any], host: str, port: int) -> Tuple[int, str]:
try:
script_result = result["scan"][host]["tcp"][port]["script"]["vnc-brute"]
except KeyError:
return -1, "ERROR - no script results found"

if "Valid credentials" in script_result:
return 1, script_result.split("\n")[2].removesuffix("- Valid credentials").strip()
elif "No valid accounts found" in script_result:
return 0, "No valid credentials found"
elif "ERROR" in script_result:
return -1, script_result[script_result.find("ERROR") :]
else:
return -1, "ERROR - could not parse the result"

def run(self, current_task: Task) -> None:
status = TaskStatus.OK
status_reason = None
valid_password = None

host = get_target_host(current_task)
port = current_task.get_payload("port")

tmp = tempfile.NamedTemporaryFile()
with open(tmp.name, "w") as f:
f.write("\n".join(ExtraModulesConfig.VNC_AUTH_PASSWORD_LIST))

time.sleep(ExtraModulesConfig.VNC_AUTH_INITIAL_SLEEP)

result = throttle_request(lambda: self.brute_vnc(host, port, tmp.name))
check_status, status_reason = self.parse_result(result, host, port)

if check_status == -1:
status = TaskStatus.ERROR
elif check_status == 0:
status = TaskStatus.OK
elif check_status == 1:
status = TaskStatus.INTERESTING
valid_password = status_reason
status_reason = f"Valid password: {valid_password}"

self.db.save_task_result(
task=current_task,
status=status,
status_reason=status_reason,
data={"script_result": result, "password": valid_password},
)


if __name__ == "__main__":
VncAuth().loop()
1 change: 1 addition & 0 deletions karton_vnc_auth/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
python_nmap==0.7.1
Loading