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 detection for CVE-2024-9487 #123

Merged
merged 11 commits into from
Oct 18, 2024
87 changes: 87 additions & 0 deletions agent/exploits/cve_2024_9487.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""Agent Asteroid implementation for CVE-2024-9487"""

import re
from packaging import version
ybadaoui-ostorlab marked this conversation as resolved.
Show resolved Hide resolved
from agent.exploits import webexploit
from agent import exploits_registry
from agent import definitions
import requests
from urllib import parse as urlparse
from requests import exceptions as requests_exceptions


MAX_REDIRECTS = 2
DEFAULT_TIMEOUT = 90
ybadaoui-ostorlab marked this conversation as resolved.
Show resolved Hide resolved
VULNERABILITY_TITLE = "GITHUB ENTERPRISE SERVER AUTHENTICATION BYPASS"
VULNERABILITY_REFERENCE = "CVE-2024-9487"
VULNERABILITY_DESCRIPTION = """A cryptographic signature verification flaw in GitHub Enterprise Server allowed bypassing SAML SSO authentication,
leading to unauthorized user access. Exploitation required encrypted assertions, direct network access, and a signed SAML response or metadata.
It affected versions before 3.15 and was fixed in 3.11.16, 3.12.10, 3.13.5, and 3.14.2. The vulnerability was reported through GitHub's Bug Bounty program."""
RISK_RATING = "CRITICAL"
FIXED_VERSIONS = {
"3.11": "3.11.6",
"3.12": "3.12.10",
"3.13": "3.13.5",
"3.14": "3.14.2",
}


@exploits_registry.register
class CVE20249487Exploit(webexploit.WebExploit):
accept_request = definitions.Request(method="GET", path="/")
check_request = definitions.Request(method="GET", path="/")
accept_pattern = [re.compile("GitHub\sEnterprise\sServer\s\d+\.\d+\.\d+")]
version_pattern = re.compile("GitHub\sEnterprise\sServer\s(\d+\.\d+\.\d+)")

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 specific 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

vulnerabilities: list[definitions.Vulnerability] = []

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

try:
req = requests.Request(
method=self.check_request.method,
url=target_endpoint,
data=self.check_request.data,
).prepare()
if self.check_request.headers is not None:
self.check_request.headers.update(req.headers)
req.headers = self.check_request.headers # type: ignore

Check warning on line 68 in agent/exploits/cve_2024_9487.py

View check run for this annotation

Codecov / codecov/patch

agent/exploits/cve_2024_9487.py#L67-L68

Added lines #L67 - L68 were not covered by tests
resp = session.send(req, timeout=DEFAULT_TIMEOUT)
except requests_exceptions.RequestException:
return vulnerabilities

Check warning on line 71 in agent/exploits/cve_2024_9487.py

View check run for this annotation

Codecov / codecov/patch

agent/exploits/cve_2024_9487.py#L70-L71

Added lines #L70 - L71 were not covered by tests

if (matched := self.version_pattern.findall(resp.text)) is not None:
ybadaoui-ostorlab marked this conversation as resolved.
Show resolved Hide resolved
for extracted_version in matched:
if isinstance(extracted_version, tuple):
ybadaoui-ostorlab marked this conversation as resolved.
Show resolved Hide resolved
ybadaoui-ostorlab marked this conversation as resolved.
Show resolved Hide resolved
ybadaoui-ostorlab marked this conversation as resolved.
Show resolved Hide resolved
extracted_version = extracted_version[0]

Check warning on line 76 in agent/exploits/cve_2024_9487.py

View check run for this annotation

Codecov / codecov/patch

agent/exploits/cve_2024_9487.py#L76

Added line #L76 was not covered by tests
if version.parse(extracted_version) < version.parse("3.15"):
ybadaoui-ostorlab marked this conversation as resolved.
Show resolved Hide resolved
ybadaoui-ostorlab marked this conversation as resolved.
Show resolved Hide resolved
if version.parse(extracted_version) < version.parse("3.10"):
ybadaoui-ostorlab marked this conversation as resolved.
Show resolved Hide resolved
vulnerability = self._create_vulnerability(target)
vulnerabilities.append(vulnerability)

Check warning on line 80 in agent/exploits/cve_2024_9487.py

View check run for this annotation

Codecov / codecov/patch

agent/exploits/cve_2024_9487.py#L79-L80

Added lines #L79 - L80 were not covered by tests
version_prefix = ".".join(extracted_version.split(".")[:2])
if version.parse(extracted_version) < version.parse(
FIXED_VERSIONS[version_prefix]
):
vulnerability = self._create_vulnerability(target)
vulnerabilities.append(vulnerability)
ybadaoui-ostorlab marked this conversation as resolved.
Show resolved Hide resolved
return vulnerabilities
57 changes: 57 additions & 0 deletions tests/exploits/cve_2024_9487_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""Unit tests for Agent Asteroid: CVE_2024_9487"""

import requests_mock as req_mock

from agent import definitions
from agent.exploits import cve_2024_9487


def testCVE20249487_whenVulnerable_reportFinding(
requests_mock: req_mock.mocker.Mocker,
) -> None:
"""CVE_2024_9487 unit test: case when target is vulnerable."""
requests_mock.get(
"http://localhost:80/",
text="""
<div class="d-flex flex-justify-center py-2">
<span class="f6 color-fg-muted">GitHub Enterprise Server 3.14.1</span>
</div>
""",
status_code=200,
)
exploit_instance = cve_2024_9487.CVE20249487Exploit()
target = definitions.Target("http", "localhost", 80)

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

assert accept is True
vulnerability = vulnerabilities[0]
assert vulnerability.entry.title == "GITHUB ENTERPRISE SERVER AUTHENTICATION BYPASS"
assert vulnerability.technical_detail == (
"http://localhost:80 is vulnerable to CVE-2024-9487, "
"GITHUB ENTERPRISE SERVER AUTHENTICATION BYPASS"
)


def testCVE20249487_whenNotVulnerable_reportNothing(
requests_mock: req_mock.mocker.Mocker,
) -> None:
"""CVE_2024_9487 unit test: case when target is vulnerable."""
requests_mock.get(
"http://localhost:80/",
text="""
<div class="d-flex flex-justify-center py-2">
<span class="f6 color-fg-muted">GitHub Enterprise Server 3.14.2</span>
</div>
""",
status_code=200,
)
exploit_instance = cve_2024_9487.CVE20249487Exploit()
target = definitions.Target("http", "localhost", 80)

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

assert accept is True
assert len(vulnerabilities) == 0
Loading