From 62aec5d68171ed1f51e3a43f35040e664f9a4b04 Mon Sep 17 00:00:00 2001 From: MarineLeM Date: Tue, 3 Dec 2024 10:44:35 +0100 Subject: [PATCH] [backend] send encoded command into implant (#1831) Signed-off-by: Marine LM --- .../injectors/caldera/CalderaExecutor.java | 10 +- .../service/CalderaInjectorService.java | 10 +- .../V3_49__Drop_injects_payloads_table.java | 19 +++ .../io/openbas/rest/document/DocumentApi.java | 2 +- .../io/openbas/rest/exercise/ExerciseApi.java | 1 + .../rest/inject/ExerciseInjectApi.java | 2 +- .../io/openbas/rest/inject/InjectApi.java | 16 +- .../rest/inject/ScenarioInjectApi.java | 2 +- .../service/ExecutableInjectService.java | 139 ++++++++++++++++++ .../inject}/service/InjectService.java | 2 +- .../io/openbas/rest/payload/PayloadApi.java | 4 +- .../io/openbas/rest/report/ReportApi.java | 2 +- .../rest/scenario/ScenarioImportApi.java | 2 +- .../openbas/service/AtomicTestingService.java | 3 +- .../java/io/openbas/rest/InjectApiTest.java | 73 ++++++++- .../java/io/openbas/rest/MapperApiTest.java | 2 +- .../java/io/openbas/rest/ReportApiTest.java | 2 +- .../rest/scenario/ScenarioImportApiTest.java | 2 +- .../openbas/utils/fixtures/InjectFixture.java | 31 +++- .../fixtures/InjectorContractFixture.java | 27 ++++ .../utils/fixtures/PayloadFixture.java | 51 +++++-- .../openbas/integrations/PayloadService.java | 2 +- openbas-front/src/utils/api-types.d.ts | 1 - .../io/openbas/database/model/Inject.java | 10 -- .../io/openbas/database/model/Payload.java | 5 + 25 files changed, 358 insertions(+), 62 deletions(-) create mode 100644 openbas-api/src/main/java/io/openbas/migration/V3_49__Drop_injects_payloads_table.java create mode 100644 openbas-api/src/main/java/io/openbas/rest/inject/service/ExecutableInjectService.java rename openbas-api/src/main/java/io/openbas/{ => rest/inject}/service/InjectService.java (99%) create mode 100644 openbas-api/src/test/java/io/openbas/utils/fixtures/InjectorContractFixture.java diff --git a/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaExecutor.java b/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaExecutor.java index 6aee0764f5..0716067c5e 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaExecutor.java +++ b/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaExecutor.java @@ -145,15 +145,15 @@ public ExecutionProcess process( List injectExpectationSignatures = new ArrayList<>(); if (injectorContract.getPayload() != null) { - switch (injectorContract.getPayload().getType()) { - case "Command": + switch (injectorContract.getPayload().getTypeEnum()) { + case PayloadType.COMMAND: injectExpectationSignatures.add( InjectExpectationSignature.builder() .type(EXPECTATION_SIGNATURE_TYPE_PROCESS_NAME) .value(executionEndpoint.getProcessName()) .build()); break; - case "Executable": + case PayloadType.EXECUTABLE: Executable payloadExecutable = (Executable) Hibernate.unproxy(injectorContract.getPayload()); injectExpectationSignatures.add( @@ -163,7 +163,7 @@ public ExecutionProcess process( .build()); // TODO File hash break; - case "FileDrop": + case PayloadType.FILE_DROP: FileDrop payloadFileDrop = (FileDrop) Hibernate.unproxy(injectorContract.getPayload()); injectExpectationSignatures.add( @@ -173,7 +173,7 @@ public ExecutionProcess process( .build()); // TODO File hash break; - case "DnsResolution": + case PayloadType.DNS_RESOLUTION: DnsResolution payloadDnsResolution = (DnsResolution) Hibernate.unproxy(injectorContract.getPayload()); diff --git a/openbas-api/src/main/java/io/openbas/injectors/caldera/service/CalderaInjectorService.java b/openbas-api/src/main/java/io/openbas/injectors/caldera/service/CalderaInjectorService.java index 0102911c80..18ab15d33c 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/caldera/service/CalderaInjectorService.java +++ b/openbas-api/src/main/java/io/openbas/injectors/caldera/service/CalderaInjectorService.java @@ -62,8 +62,8 @@ public Ability createAbility(Payload payload) { if (payload.getCleanupCommand() != null) { cleanupCommands.add(payload.getCleanupCommand()); } - switch (payload.getType()) { - case "Command": + switch (payload.getTypeEnum()) { + case PayloadType.COMMAND: Command payloadCommand = (Command) Hibernate.unproxy(payload); Arrays.stream(payloadCommand.getPlatforms()) .forEach( @@ -84,7 +84,7 @@ public Ability createAbility(Payload payload) { executors.add(executor); }); break; - case "Executable": + case PayloadType.EXECUTABLE: Executable payloadExecutable = (Executable) Hibernate.unproxy(payload); Arrays.stream(payloadExecutable.getPlatforms()) .forEach( @@ -127,7 +127,7 @@ public Ability createAbility(Payload payload) { executors.add(executor); }); break; - case "FileDrop": + case PayloadType.FILE_DROP: FileDrop payloadFileDrop = (FileDrop) Hibernate.unproxy(payload); Arrays.stream(payloadFileDrop.getPlatforms()) .forEach( @@ -164,7 +164,7 @@ public Ability createAbility(Payload payload) { executors.add(executor); }); break; - case "DnsResolution": + case PayloadType.DNS_RESOLUTION: DnsResolution payloadDnsResolution = (DnsResolution) Hibernate.unproxy(payload); Arrays.stream(payloadDnsResolution.getPlatforms()) .forEach( diff --git a/openbas-api/src/main/java/io/openbas/migration/V3_49__Drop_injects_payloads_table.java b/openbas-api/src/main/java/io/openbas/migration/V3_49__Drop_injects_payloads_table.java new file mode 100644 index 0000000000..36554090f4 --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/migration/V3_49__Drop_injects_payloads_table.java @@ -0,0 +1,19 @@ +package io.openbas.migration; + +import java.sql.Connection; +import java.sql.Statement; +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.springframework.stereotype.Component; + +@Component +public class V3_49__Drop_injects_payloads_table extends BaseJavaMigration { + + @Override + public void migrate(Context context) throws Exception { + Connection connection = context.getConnection(); + Statement statement = connection.createStatement(); + + statement.executeUpdate("DROP TABLE IF EXISTS injects_payloads"); + } +} diff --git a/openbas-api/src/main/java/io/openbas/rest/document/DocumentApi.java b/openbas-api/src/main/java/io/openbas/rest/document/DocumentApi.java index 80456f011f..fe74a5e93d 100644 --- a/openbas-api/src/main/java/io/openbas/rest/document/DocumentApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/document/DocumentApi.java @@ -20,8 +20,8 @@ import io.openbas.rest.document.form.DocumentUpdateInput; import io.openbas.rest.exception.ElementNotFoundException; import io.openbas.rest.helper.RestBehavior; +import io.openbas.rest.inject.service.InjectService; import io.openbas.service.FileService; -import io.openbas.service.InjectService; import io.openbas.utils.pagination.SearchPaginationInput; import jakarta.servlet.http.HttpServletResponse; import jakarta.transaction.Transactional; diff --git a/openbas-api/src/main/java/io/openbas/rest/exercise/ExerciseApi.java b/openbas-api/src/main/java/io/openbas/rest/exercise/ExerciseApi.java index cf81424402..ef9b834de4 100644 --- a/openbas-api/src/main/java/io/openbas/rest/exercise/ExerciseApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/exercise/ExerciseApi.java @@ -30,6 +30,7 @@ import io.openbas.rest.helper.RestBehavior; import io.openbas.rest.helper.TeamHelper; import io.openbas.rest.inject.form.InjectExpectationResultsByAttackPattern; +import io.openbas.rest.inject.service.InjectService; import io.openbas.service.*; import io.openbas.telemetry.Tracing; import io.openbas.utils.AtomicTestingUtils.ExpectationResultsByType; diff --git a/openbas-api/src/main/java/io/openbas/rest/inject/ExerciseInjectApi.java b/openbas-api/src/main/java/io/openbas/rest/inject/ExerciseInjectApi.java index e115e4e251..601da469c8 100644 --- a/openbas-api/src/main/java/io/openbas/rest/inject/ExerciseInjectApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/inject/ExerciseInjectApi.java @@ -9,8 +9,8 @@ import io.openbas.database.model.InjectTestStatus; import io.openbas.rest.helper.RestBehavior; import io.openbas.rest.inject.output.InjectOutput; +import io.openbas.rest.inject.service.InjectService; import io.openbas.service.InjectSearchService; -import io.openbas.service.InjectService; import io.openbas.service.InjectTestStatusService; import io.openbas.utils.pagination.SearchPaginationInput; import io.swagger.v3.oas.annotations.Operation; diff --git a/openbas-api/src/main/java/io/openbas/rest/inject/InjectApi.java b/openbas-api/src/main/java/io/openbas/rest/inject/InjectApi.java index ab992647c8..9c8ff643ee 100644 --- a/openbas-api/src/main/java/io/openbas/rest/inject/InjectApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/inject/InjectApi.java @@ -28,9 +28,10 @@ import io.openbas.rest.exception.ElementNotFoundException; import io.openbas.rest.helper.RestBehavior; import io.openbas.rest.inject.form.*; +import io.openbas.rest.inject.service.ExecutableInjectService; import io.openbas.rest.inject.service.InjectDuplicateService; +import io.openbas.rest.inject.service.InjectService; import io.openbas.service.InjectSearchService; -import io.openbas.service.InjectService; import io.openbas.service.ScenarioService; import io.openbas.telemetry.Tracing; import io.openbas.utils.pagination.SearchPaginationInput; @@ -73,13 +74,14 @@ public class InjectApi extends RestBehavior { private final InjectRepository injectRepository; private final InjectDocumentRepository injectDocumentRepository; private final TeamRepository teamRepository; - private final AssetService assetService; - private final AssetGroupService assetGroupService; private final TagRepository tagRepository; private final DocumentRepository documentRepository; + private final AssetService assetService; + private final AssetGroupService assetGroupService; private final ExecutionContextService executionContextService; private final ScenarioService scenarioService; private final InjectService injectService; + private final ExecutableInjectService executableInjectService; private final InjectSearchService injectSearchService; private final InjectDuplicateService injectDuplicateService; @@ -163,6 +165,14 @@ public Inject injectExecutionCallback( return injectRepository.save(inject); } + @GetMapping(INJECT_URI + "/{injectId}/executable-payload") + @Tracing(name = "Get payload ready to be executed", layer = "api", operation = "GET") + public Payload getExecutablePayloadInject(@PathVariable @NotBlank final String injectId) { + return executableInjectService.getExecutablePayloadInject(injectId); + } + + // -- EXERCISES -- + @Transactional(rollbackFor = Exception.class) @PutMapping(INJECT_URI + "/{exerciseId}/{injectId}") @PreAuthorize("isExercisePlanner(#exerciseId)") diff --git a/openbas-api/src/main/java/io/openbas/rest/inject/ScenarioInjectApi.java b/openbas-api/src/main/java/io/openbas/rest/inject/ScenarioInjectApi.java index e6834cefc4..0ec56b48ca 100644 --- a/openbas-api/src/main/java/io/openbas/rest/inject/ScenarioInjectApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/inject/ScenarioInjectApi.java @@ -9,8 +9,8 @@ import io.openbas.database.model.InjectTestStatus; import io.openbas.rest.helper.RestBehavior; import io.openbas.rest.inject.output.InjectOutput; +import io.openbas.rest.inject.service.InjectService; import io.openbas.service.InjectSearchService; -import io.openbas.service.InjectService; import io.openbas.service.InjectTestStatusService; import io.openbas.telemetry.Tracing; import io.openbas.utils.pagination.SearchPaginationInput; diff --git a/openbas-api/src/main/java/io/openbas/rest/inject/service/ExecutableInjectService.java b/openbas-api/src/main/java/io/openbas/rest/inject/service/ExecutableInjectService.java new file mode 100644 index 0000000000..cd3da86dd0 --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/rest/inject/service/ExecutableInjectService.java @@ -0,0 +1,139 @@ +package io.openbas.rest.inject.service; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.openbas.database.model.*; +import io.openbas.database.repository.InjectRepository; +import io.openbas.rest.exception.ElementNotFoundException; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class ExecutableInjectService { + + private final InjectRepository injectRepository; + private static final Pattern argumentsRegex = Pattern.compile("#\\{([^#{}]+)}"); + + private List getArgumentsFromCommandLines(String command) { + Matcher matcher = argumentsRegex.matcher(command); + List commandParameters = new ArrayList<>(); + + while (matcher.find()) { + commandParameters.add(matcher.group(1)); + } + + return commandParameters; + } + + private String replaceArgumentsByValue( + String command, List defaultArguments, ObjectNode injectContent) { + + List arguments = getArgumentsFromCommandLines(command); + + for (String argument : arguments) { + String value = ""; + + // Try to get the value from injectContent + if (injectContent.has(argument) && !injectContent.get(argument).asText().isEmpty()) { + value = injectContent.get(argument).asText(); + } else { + // Fallback to defaultContent + value = + defaultArguments.stream() + .filter(a -> a.getKey().equals(argument)) + .map(PayloadArgument::getDefaultValue) + .findFirst() + .orElse(""); + } + + command = command.replace("#{" + argument + "}", value); + } + + return command; + } + + private String processAndEncodeCommand( + String command, + String executor, + List defaultArguments, + ObjectNode injectContent, + String obfuscator) { + String computedCommand = replaceArgumentsByValue(command, defaultArguments, injectContent); + + if (executor.equals("cmd")) { + computedCommand = computedCommand.trim().replace("\n", " & "); + } + + if (obfuscator.equals("base64")) { + return Base64.getEncoder().encodeToString(computedCommand.getBytes()); + } + + return computedCommand; + } + + public Payload getExecutablePayloadInject(String injectId) { + Inject inject = + this.injectRepository.findById(injectId).orElseThrow(ElementNotFoundException::new); + InjectorContract contract = + inject.getInjectorContract().orElseThrow(ElementNotFoundException::new); + + // prerequisite + contract + .getPayload() + .getPrerequisites() + .forEach( + prerequisite -> { + if (prerequisite.getCheckCommand() != null) { + prerequisite.setCheckCommand( + processAndEncodeCommand( + prerequisite.getCheckCommand(), + prerequisite.getExecutor(), + contract.getPayload().getArguments(), + inject.getContent(), + "base64")); + } + if (prerequisite.getGetCommand() != null) { + prerequisite.setGetCommand( + processAndEncodeCommand( + prerequisite.getGetCommand(), + prerequisite.getExecutor(), + contract.getPayload().getArguments(), + inject.getContent(), + "base64")); + } + }); + + // cleanup + if (contract.getPayload().getCleanupCommand() != null) { + contract + .getPayload() + .setCleanupCommand( + processAndEncodeCommand( + contract.getPayload().getCleanupCommand(), + contract.getPayload().getCleanupExecutor(), + contract.getPayload().getArguments(), + inject.getContent(), + "base64")); + } + + // Command + if (contract.getPayload().getTypeEnum().equals(PayloadType.COMMAND)) { + Command payloadCommand = (Command) contract.getPayload(); + payloadCommand.setContent( + processAndEncodeCommand( + payloadCommand.getContent(), + payloadCommand.getExecutor(), + contract.getPayload().getArguments(), + inject.getContent(), + "base64")); + return payloadCommand; + } + + return contract.getPayload(); + } +} diff --git a/openbas-api/src/main/java/io/openbas/service/InjectService.java b/openbas-api/src/main/java/io/openbas/rest/inject/service/InjectService.java similarity index 99% rename from openbas-api/src/main/java/io/openbas/service/InjectService.java rename to openbas-api/src/main/java/io/openbas/rest/inject/service/InjectService.java index dbf40beedc..d865049f43 100644 --- a/openbas-api/src/main/java/io/openbas/service/InjectService.java +++ b/openbas-api/src/main/java/io/openbas/rest/inject/service/InjectService.java @@ -1,4 +1,4 @@ -package io.openbas.service; +package io.openbas.rest.inject.service; import static io.openbas.utils.StringUtils.duplicateString; import static java.time.Instant.now; diff --git a/openbas-api/src/main/java/io/openbas/rest/payload/PayloadApi.java b/openbas-api/src/main/java/io/openbas/rest/payload/PayloadApi.java index 641a5955b7..fc5f93063e 100644 --- a/openbas-api/src/main/java/io/openbas/rest/payload/PayloadApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/payload/PayloadApi.java @@ -181,7 +181,7 @@ public Payload updatePayload( fromIterable(attackPatternRepository.findAllById(input.getAttackPatternsIds()))); payload.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds()))); payload.setUpdatedAt(Instant.now()); - switch (PayloadType.fromString(payload.getType())) { + switch (payload.getTypeEnum()) { case PayloadType.COMMAND: Command payloadCommand = (Command) Hibernate.unproxy(payload); payloadCommand.setUpdateAttributes(input); @@ -247,7 +247,7 @@ public Payload upsertPayload(@Valid @RequestBody PayloadUpsertInput input) { input.getAttackPatternsExternalIds()))); existingPayload.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds()))); existingPayload.setUpdatedAt(Instant.now()); - switch (PayloadType.fromString(existingPayload.getType())) { + switch (existingPayload.getTypeEnum()) { case PayloadType.COMMAND: Command payloadCommand = (Command) Hibernate.unproxy(existingPayload); payloadCommand.setUpdateAttributes(input); diff --git a/openbas-api/src/main/java/io/openbas/rest/report/ReportApi.java b/openbas-api/src/main/java/io/openbas/rest/report/ReportApi.java index f4629726a1..c53e48aa97 100644 --- a/openbas-api/src/main/java/io/openbas/rest/report/ReportApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/report/ReportApi.java @@ -5,11 +5,11 @@ import io.openbas.database.model.*; import io.openbas.rest.exercise.service.ExerciseService; import io.openbas.rest.helper.RestBehavior; +import io.openbas.rest.inject.service.InjectService; import io.openbas.rest.report.form.ReportInjectCommentInput; import io.openbas.rest.report.form.ReportInput; import io.openbas.rest.report.model.Report; import io.openbas.rest.report.service.ReportService; -import io.openbas.service.InjectService; import jakarta.transaction.Transactional; import jakarta.validation.Valid; import java.util.UUID; diff --git a/openbas-api/src/main/java/io/openbas/rest/scenario/ScenarioImportApi.java b/openbas-api/src/main/java/io/openbas/rest/scenario/ScenarioImportApi.java index 6642c3b45d..b1cafef774 100644 --- a/openbas-api/src/main/java/io/openbas/rest/scenario/ScenarioImportApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/scenario/ScenarioImportApi.java @@ -8,10 +8,10 @@ import io.openbas.database.repository.ImportMapperRepository; import io.openbas.rest.exception.ElementNotFoundException; import io.openbas.rest.helper.RestBehavior; +import io.openbas.rest.inject.service.InjectService; import io.openbas.rest.scenario.form.InjectsImportInput; import io.openbas.rest.scenario.response.ImportTestSummary; import io.openbas.service.InjectImportService; -import io.openbas.service.InjectService; import io.openbas.service.ScenarioService; import io.swagger.v3.oas.annotations.Operation; import jakarta.transaction.Transactional; diff --git a/openbas-api/src/main/java/io/openbas/service/AtomicTestingService.java b/openbas-api/src/main/java/io/openbas/service/AtomicTestingService.java index dbeb97ba23..186213cf9b 100644 --- a/openbas-api/src/main/java/io/openbas/service/AtomicTestingService.java +++ b/openbas-api/src/main/java/io/openbas/service/AtomicTestingService.java @@ -19,6 +19,7 @@ import io.openbas.rest.atomic_testing.form.InjectResultOutput; import io.openbas.rest.atomic_testing.form.InjectResultOverviewOutput; import io.openbas.rest.exception.ElementNotFoundException; +import io.openbas.rest.inject.service.InjectService; import io.openbas.utils.InjectMapper; import io.openbas.utils.pagination.SearchPaginationInput; import jakarta.annotation.Resource; @@ -45,9 +46,7 @@ public class AtomicTestingService { private final AssetGroupRepository assetGroupRepository; private final AssetRepository assetRepository; private final InjectRepository injectRepository; - private final InjectStatusRepository injectStatusRepository; private final InjectorContractRepository injectorContractRepository; - private final InjectDocumentRepository injectDocumentRepository; private final UserRepository userRepository; private final TeamRepository teamRepository; private final TagRepository tagRepository; diff --git a/openbas-api/src/test/java/io/openbas/rest/InjectApiTest.java b/openbas-api/src/test/java/io/openbas/rest/InjectApiTest.java index ab335e3cd7..478c87869e 100644 --- a/openbas-api/src/test/java/io/openbas/rest/InjectApiTest.java +++ b/openbas-api/src/test/java/io/openbas/rest/InjectApiTest.java @@ -30,6 +30,9 @@ import io.openbas.rest.inject.form.InjectInput; import io.openbas.service.ScenarioService; import io.openbas.utils.fixtures.InjectExpectationFixture; +import io.openbas.utils.fixtures.InjectFixture; +import io.openbas.utils.fixtures.InjectorContractFixture; +import io.openbas.utils.fixtures.PayloadFixture; import io.openbas.utils.mockUser.WithMockObserverUser; import io.openbas.utils.mockUser.WithMockPlannerUser; import jakarta.annotation.Resource; @@ -40,7 +43,10 @@ import java.io.FileInputStream; import java.io.InputStream; import java.time.Instant; +import java.util.Base64; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -67,10 +73,12 @@ class InjectApiTest extends IntegrationTest { static Document DOCUMENT2; static Team TEAM; static String SCENARIO_INJECT_ID; - + static InjectorContract PAYLOAD_INJECTOR_CONTRACT; + @Resource protected ObjectMapper mapper; @Autowired private MockMvc mvc; @Autowired private ScenarioService scenarioService; @Autowired private ExerciseService exerciseService; + @Autowired private ExerciseRepository exerciseRepository; @SpyBean private Executor executor; @Autowired private ScenarioRepository scenarioRepository; @@ -79,6 +87,8 @@ class InjectApiTest extends IntegrationTest { @Autowired private CommunicationRepository communicationRepository; @Autowired private InjectExpectationRepository injectExpectationRepository; @Autowired private TeamRepository teamRepository; + @Autowired private PayloadRepository payloadRepository; + @Autowired private InjectorRepository injectorRepository; @Autowired private InjectorContractRepository injectorContractRepository; @Autowired private UserRepository userRepository; @Resource private ObjectMapper objectMapper; @@ -120,6 +130,7 @@ void afterAll() { this.exerciseRepository.delete(EXERCISE); this.documentRepository.deleteAll(List.of(DOCUMENT1, DOCUMENT2)); this.teamRepository.delete(TEAM); + this.injectorContractRepository.delete(PAYLOAD_INJECTOR_CONTRACT); } // -- SCENARIOS -- @@ -613,4 +624,64 @@ void deleteInjectsForExerciseTest() throws Exception { .isEmpty(), "There should be no expectations related to the inject in the database"); } + + @Nested + @WithMockPlannerUser + @DisplayName("Retrieving executable payloads injects") + class RetrievingExecutablePayloadInject { + @DisplayName("Get encoded command payload with arguments") + @Test + void getExecutablePayloadInjectWithArguments() throws Exception { + // -- PREPARE -- + PayloadPrerequisite prerequisite = new PayloadPrerequisite(); + prerequisite.setGetCommand("cd ./src"); + prerequisite.setExecutor("bash"); + Command payloadCommand = + PayloadFixture.createCommand( + "bash", "echo command name #{arg_value}", List.of(prerequisite), "echo cleanup cmd"); + Payload payloadSaved = payloadRepository.save(payloadCommand); + + Injector injector = injectorRepository.findByType("openbas_implant").orElseThrow(); + InjectorContract injectorContract = + InjectorContractFixture.createPayloadInjectorContract(injector, payloadSaved); + PAYLOAD_INJECTOR_CONTRACT = injectorContractRepository.save(injectorContract); + + String argValue = "Hello world"; + Map payloadArguments = new HashMap<>(); + payloadArguments.put("arg_value", argValue); + Inject inject = + InjectFixture.createInjectCommandPayload(PAYLOAD_INJECTOR_CONTRACT, payloadArguments); + + Inject injectSaved = injectRepository.save(inject); + + // -- EXECUTE -- + String response = + mvc.perform( + get(INJECT_URI + "/" + injectSaved.getId() + "/executable-payload") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + assertNotNull(response); + // Verify prerequisite command + String expectedPrerequisiteCmdEncoded = + Base64.getEncoder().encodeToString(prerequisite.getGetCommand().getBytes()); + assertEquals( + expectedPrerequisiteCmdEncoded, + JsonPath.read(response, "$.payload_prerequisites[0].get_command")); + + // Verify cleanup command + String expectedCleanupCmdEncoded = + Base64.getEncoder().encodeToString(payloadCommand.getCleanupCommand().getBytes()); + assertEquals(expectedCleanupCmdEncoded, JsonPath.read(response, "$.payload_cleanup_command")); + + // Verify command + String cmdToExecute = payloadCommand.getContent().replace("#{arg_value}", "Hello world"); + String expectedCmdEncoded = Base64.getEncoder().encodeToString(cmdToExecute.getBytes()); + assertEquals(expectedCmdEncoded, JsonPath.read(response, "$.command_content")); + } + } } diff --git a/openbas-api/src/test/java/io/openbas/rest/MapperApiTest.java b/openbas-api/src/test/java/io/openbas/rest/MapperApiTest.java index 584f1493c2..a176809997 100644 --- a/openbas-api/src/test/java/io/openbas/rest/MapperApiTest.java +++ b/openbas-api/src/test/java/io/openbas/rest/MapperApiTest.java @@ -11,13 +11,13 @@ import com.jayway.jsonpath.JsonPath; import io.openbas.database.model.ImportMapper; import io.openbas.database.repository.ImportMapperRepository; +import io.openbas.rest.inject.service.InjectService; import io.openbas.rest.mapper.MapperApi; import io.openbas.rest.mapper.form.ImportMapperAddInput; import io.openbas.rest.mapper.form.ImportMapperUpdateInput; import io.openbas.rest.scenario.form.InjectsImportTestInput; import io.openbas.rest.scenario.response.ImportTestSummary; import io.openbas.service.InjectImportService; -import io.openbas.service.InjectService; import io.openbas.service.MapperService; import io.openbas.utils.fixtures.PaginationFixture; import io.openbas.utils.mockMapper.MockMapperUtils; diff --git a/openbas-api/src/test/java/io/openbas/rest/ReportApiTest.java b/openbas-api/src/test/java/io/openbas/rest/ReportApiTest.java index 7aaa6c1b4d..6a5b88bfcc 100644 --- a/openbas-api/src/test/java/io/openbas/rest/ReportApiTest.java +++ b/openbas-api/src/test/java/io/openbas/rest/ReportApiTest.java @@ -12,13 +12,13 @@ import com.jayway.jsonpath.JsonPath; import io.openbas.database.model.*; import io.openbas.rest.exercise.service.ExerciseService; +import io.openbas.rest.inject.service.InjectService; import io.openbas.rest.mapper.MapperApi; import io.openbas.rest.report.ReportApi; import io.openbas.rest.report.form.ReportInjectCommentInput; import io.openbas.rest.report.form.ReportInput; import io.openbas.rest.report.model.Report; import io.openbas.rest.report.service.ReportService; -import io.openbas.service.InjectService; import io.openbas.utils.fixtures.PaginationFixture; import io.openbas.utils.mockUser.WithMockPlannerUser; import java.lang.reflect.Field; diff --git a/openbas-api/src/test/java/io/openbas/rest/scenario/ScenarioImportApiTest.java b/openbas-api/src/test/java/io/openbas/rest/scenario/ScenarioImportApiTest.java index 1b4c49897b..632a3ef95e 100644 --- a/openbas-api/src/test/java/io/openbas/rest/scenario/ScenarioImportApiTest.java +++ b/openbas-api/src/test/java/io/openbas/rest/scenario/ScenarioImportApiTest.java @@ -8,10 +8,10 @@ import io.openbas.database.model.ImportMapper; import io.openbas.database.repository.ImportMapperRepository; +import io.openbas.rest.inject.service.InjectService; import io.openbas.rest.scenario.form.InjectsImportInput; import io.openbas.rest.scenario.response.ImportTestSummary; import io.openbas.service.InjectImportService; -import io.openbas.service.InjectService; import io.openbas.service.ScenarioService; import java.util.Optional; import java.util.UUID; diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectFixture.java index 4033718679..944696410c 100644 --- a/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectFixture.java +++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectFixture.java @@ -1,17 +1,19 @@ package io.openbas.utils.fixtures; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import io.openbas.database.model.Inject; -import io.openbas.database.model.InjectorContract; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.openbas.database.model.*; import io.openbas.injectors.challenge.model.ChallengeContent; import java.util.List; +import java.util.Map; public class InjectFixture { public static final String INJECT_EMAIL_NAME = "Test email inject"; public static final String INJECT_CHALLENGE_NAME = "Test challenge inject"; - public static Inject getInjectForEmailContract(InjectorContract injectorContract) { + private static Inject createInject(InjectorContract injectorContract, String title) { Inject inject = new Inject(); inject.setTitle(INJECT_EMAIL_NAME); inject.setInjectorContract(injectorContract); @@ -20,17 +22,30 @@ public static Inject getInjectForEmailContract(InjectorContract injectorContract return inject; } + public static Inject getInjectForEmailContract(InjectorContract injectorContract) { + return createInject(injectorContract, INJECT_EMAIL_NAME); + } + public static Inject createDefaultInjectChallenge( InjectorContract injectorContract, ObjectMapper objectMapper, List challengeIds) { - Inject inject = new Inject(); - inject.setTitle(INJECT_CHALLENGE_NAME); - inject.setInjectorContract(injectorContract); - inject.setEnabled(true); - inject.setDependsDuration(0L); + Inject inject = createInject(injectorContract, INJECT_CHALLENGE_NAME); ChallengeContent content = new ChallengeContent(); content.setChallenges(challengeIds); inject.setContent(objectMapper.valueToTree(content)); return inject; } + + public static Inject createInjectCommandPayload( + InjectorContract injectorContract, Map payloadArguments) { + + Inject inject = createInject(injectorContract, "Inject title"); + ObjectMapper objectMapper = new ObjectMapper(); + ObjectNode injectContent = objectMapper.createObjectNode(); + payloadArguments.forEach( + (key, value) -> injectContent.set(key, objectMapper.convertValue(value, JsonNode.class))); + inject.setContent(injectContent); + + return inject; + } } diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectorContractFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectorContractFixture.java new file mode 100644 index 0000000000..a27afc7a67 --- /dev/null +++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectorContractFixture.java @@ -0,0 +1,27 @@ +package io.openbas.utils.fixtures; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.openbas.database.model.Injector; +import io.openbas.database.model.InjectorContract; +import io.openbas.database.model.Payload; +import java.util.UUID; + +public class InjectorContractFixture { + public static InjectorContract createPayloadInjectorContract( + Injector injector, Payload payloadCommand) throws JsonProcessingException { + InjectorContract injectorContract = new InjectorContract(); + injectorContract.setInjector(injector); + injectorContract.setPayload(payloadCommand); + injectorContract.setId(UUID.randomUUID().toString()); + ObjectMapper objectMapper = new ObjectMapper(); + ObjectNode content = objectMapper.createObjectNode(); + content.set("fields", objectMapper.convertValue("none", JsonNode.class)); + injectorContract.setContent(objectMapper.writeValueAsString(content)); + injectorContract.setConvertedContent(content); + + return injectorContract; + } +} diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/PayloadFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/PayloadFixture.java index 4b8cda1395..f719370ba8 100644 --- a/openbas-api/src/test/java/io/openbas/utils/fixtures/PayloadFixture.java +++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/PayloadFixture.java @@ -6,40 +6,61 @@ import static io.openbas.database.model.Payload.PAYLOAD_STATUS.VERIFIED; import io.openbas.database.model.*; +import jakarta.annotation.Nullable; import java.util.Collections; +import java.util.List; public class PayloadFixture { + private static final Endpoint.PLATFORM_TYPE[] LINUX_PLATFORM = {Endpoint.PLATFORM_TYPE.Linux}; + private static final Endpoint.PLATFORM_TYPE[] MACOS_PLATFORM = {Endpoint.PLATFORM_TYPE.MacOS}; + private static final Endpoint.PLATFORM_TYPE[] WINDOWS_PLATFORM = {Endpoint.PLATFORM_TYPE.Windows}; + + private static void initializeDefaultPayload( + final Payload payload, final Endpoint.PLATFORM_TYPE[] platforms) { + payload.setPlatforms(platforms); + payload.setSource(MANUAL); + payload.setStatus(VERIFIED); + payload.setAttackPatterns(Collections.emptyList()); + } + public static Payload createDefaultCommand() { + return createCommand("PowerShell", "cd ..", null, null); + } + + public static Command createCommand( + String executor, + String commandLine, + @Nullable List prerequisites, + @Nullable String cleanupCmd) { Command command = new Command("command-id", COMMAND_TYPE, "command payload"); - command.setContent("cd .."); - command.setExecutor("PowerShell"); - command.setPlatforms(new Endpoint.PLATFORM_TYPE[] {Endpoint.PLATFORM_TYPE.Windows}); - command.setSource(MANUAL); - command.setStatus(VERIFIED); + command.setContent(commandLine); + command.setExecutor(executor); + if (prerequisites != null) { + command.setPrerequisites(prerequisites); + } + if (cleanupCmd != null) { + command.setCleanupCommand(cleanupCmd); + command.setCleanupExecutor(executor); + } + initializeDefaultPayload(command, WINDOWS_PLATFORM); command.setAttackPatterns(Collections.emptyList()); return command; } public static Payload createDefaultDnsResolution() { - DnsResolution dnsResolution = + final DnsResolution dnsResolution = new DnsResolution("dns-resolution-id", DNS_RESOLUTION_TYPE, "dns resolution payload"); dnsResolution.setHostname("localhost"); - dnsResolution.setPlatforms(new Endpoint.PLATFORM_TYPE[] {Endpoint.PLATFORM_TYPE.Linux}); - dnsResolution.setSource(MANUAL); - dnsResolution.setStatus(VERIFIED); - dnsResolution.setAttackPatterns(Collections.emptyList()); + initializeDefaultPayload(dnsResolution, LINUX_PLATFORM); return dnsResolution; } public static Payload createDefaultExecutable() { - Executable executable = + final Executable executable = new Executable("executable-id", Executable.EXECUTABLE_TYPE, "executable payload"); - executable.setPlatforms(new Endpoint.PLATFORM_TYPE[] {Endpoint.PLATFORM_TYPE.MacOS}); executable.setExecutableArch(Endpoint.PLATFORM_ARCH.arm64); - executable.setSource(MANUAL); - executable.setStatus(VERIFIED); - executable.setAttackPatterns(Collections.emptyList()); + initializeDefaultPayload(executable, MACOS_PLATFORM); return executable; } } diff --git a/openbas-framework/src/main/java/io/openbas/integrations/PayloadService.java b/openbas-framework/src/main/java/io/openbas/integrations/PayloadService.java index 118c936158..38af7be397 100644 --- a/openbas-framework/src/main/java/io/openbas/integrations/PayloadService.java +++ b/openbas-framework/src/main/java/io/openbas/integrations/PayloadService.java @@ -165,7 +165,7 @@ public Payload duplicate(@NotBlank final String payloadId) { } private Payload generateDuplicatedPayload(Payload originalPayload) { - return switch (PayloadType.fromString(originalPayload.getType())) { + return switch (originalPayload.getTypeEnum()) { case PayloadType.COMMAND -> { Command originCommand = (Command) Hibernate.unproxy(originalPayload); Command duplicateCommand = new Command(); diff --git a/openbas-front/src/utils/api-types.d.ts b/openbas-front/src/utils/api-types.d.ts index 8ee9a188f6..d75f1bdd58 100644 --- a/openbas-front/src/utils/api-types.d.ts +++ b/openbas-front/src/utils/api-types.d.ts @@ -1055,7 +1055,6 @@ export interface Inject { inject_id: string; inject_injector_contract?: InjectorContract; inject_kill_chain_phases?: KillChainPhase[]; - inject_payloads?: Asset[]; inject_ready?: boolean; inject_scenario?: Scenario; /** @format date-time */ diff --git a/openbas-model/src/main/java/io/openbas/database/model/Inject.java b/openbas-model/src/main/java/io/openbas/database/model/Inject.java index b3e3d1591f..92f88de1bf 100644 --- a/openbas-model/src/main/java/io/openbas/database/model/Inject.java +++ b/openbas-model/src/main/java/io/openbas/database/model/Inject.java @@ -199,16 +199,6 @@ public class Inject implements Base, Injection { @JsonProperty("inject_asset_groups") private List assetGroups = new ArrayList<>(); - @Getter - @ManyToMany(fetch = FetchType.EAGER) - @JoinTable( - name = "injects_payloads", - joinColumns = @JoinColumn(name = "inject_id"), - inverseJoinColumns = @JoinColumn(name = "payload_id")) - @JsonSerialize(using = MultiIdListDeserializer.class) - @JsonProperty("inject_payloads") - private List payloads = new ArrayList<>(); - // CascadeType.ALL is required here because of complex relationships @Getter @OneToMany( diff --git a/openbas-model/src/main/java/io/openbas/database/model/Payload.java b/openbas-model/src/main/java/io/openbas/database/model/Payload.java index 8493b1e627..1cebbf2ced 100644 --- a/openbas-model/src/main/java/io/openbas/database/model/Payload.java +++ b/openbas-model/src/main/java/io/openbas/database/model/Payload.java @@ -172,6 +172,11 @@ public String getCollectorType() { return this.collector != null ? this.collector.getType() : null; } + @Transient + public PayloadType getTypeEnum() { + return PayloadType.fromString(type); + } + @Override public int hashCode() { return Objects.hash(id);