diff --git a/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/utils/ExpectationUtils.java b/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/utils/ExpectationUtils.java index 1a3648f2e4..bab850eb1b 100644 --- a/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/utils/ExpectationUtils.java +++ b/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/utils/ExpectationUtils.java @@ -21,7 +21,7 @@ public static String computeFailedMessage(@NotNull final EXPECTATION_TYPE expect ? "Not detected" : PREVENTION.equals(expectationType) ? "Not prevented" - : "Failed"; + : "FAILED"; } } diff --git a/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaContract.java b/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaContract.java index 3525c41e60..975eff866e 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaContract.java +++ b/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaContract.java @@ -73,8 +73,7 @@ public List contracts() { private ContractSelect obfuscatorField() { List obfuscators = this.injectorCalderaService.obfuscators(); - Map obfuscatorChoices = obfuscators.stream() - .collect(Collectors.toMap(Obfuscator::getName, Obfuscator::getName)); + Map obfuscatorChoices = obfuscators.stream().collect(Collectors.toMap(Obfuscator::getName, Obfuscator::getName)); return selectFieldWithDefault( "obfuscator", "Obfuscators", @@ -94,19 +93,14 @@ private ContractExpectations expectations() { detectionExpectation.setType(DETECTION); detectionExpectation.setName("Expect inject to be detected"); detectionExpectation.setScore(0); - - return expectationsField( - "expectations", "Expectations", List.of(preventionExpectation, detectionExpectation) - ); + return expectationsField("expectations", "Expectations", List.of(preventionExpectation, detectionExpectation)); } private List abilityContracts(@NotNull final ContractConfig contractConfig) { // Fields ContractSelect obfuscatorField = obfuscatorField(); ContractAsset assetField = assetField("assets", "Assets", Multiple); - ContractAssetGroup assetGroupField = assetGroupField("assetgroups", "Asset groups", - Multiple); - // Expectations + ContractAssetGroup assetGroupField = assetGroupField("assetgroups", "Asset groups", Multiple); ContractExpectations expectationsField = expectations(); List abilities = this.injectorCalderaService.abilities(); 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 d2a12b5a4d..d33cd96bc5 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 @@ -81,6 +81,10 @@ public ExecutionProcess process(@NotNull final Execution execution, @NotNull fin execution.addTrace(traceError("Found 0 asset to execute the ability on (likely this inject does not have any target or the targeted asset is inactive and has been purged)")); } String contract = inject.getInjectorContract().getId(); + if( inject.getInjectorContract().getPayload() != null ) { + // This is a payload, need to create the ability on the fly + + } assets.forEach((asset, aBoolean) -> { try { Endpoint executionEndpoint = this.findAndRegisterAssetForExecution(injection.getInjection().getInject(), asset); diff --git a/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaInjector.java b/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaInjector.java index f1bf751825..0de7fbe0a8 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaInjector.java +++ b/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaInjector.java @@ -37,7 +37,8 @@ public CalderaInjector(InjectorService injectorService, CalderaContract contract false, "simulation-agent", executorCommands, - executorClearCommands + executorClearCommands, + true ); } catch (Exception e) { log.log(Level.SEVERE, "Error creating Caldera injector (" + e.getMessage() + ")" + "\n" + Arrays.toString(e.getStackTrace())); diff --git a/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaResultCollector.java b/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaResultCollector.java index 8f6bd925f6..4282c81509 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaResultCollector.java +++ b/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaResultCollector.java @@ -26,16 +26,13 @@ public class CalderaResultCollector { private final ThreadPoolTaskScheduler taskScheduler; private final InjectRepository injectRepository; private final InjectStatusRepository injectStatusRepository; - private final InjectExpectationService injectExpectationService; private final CalderaInjectorService calderaService; - private final CalderaInjectorConfig calderaInjectorConfig; - private final EndpointService endpointService; @PostConstruct public void init() { // If enabled, scheduled every X seconds if (this.config.isEnable()) { - CalderaResultCollectorService service = new CalderaResultCollectorService(this.injectRepository, this.injectStatusRepository, this.injectExpectationService, this.calderaService, this.calderaInjectorConfig, this.endpointService); + CalderaResultCollectorService service = new CalderaResultCollectorService(this.injectRepository, this.injectStatusRepository, this.calderaService); this.taskScheduler.scheduleAtFixedRate(service, Duration.ofSeconds(60)); } } diff --git a/openbas-api/src/main/java/io/openbas/injectors/caldera/service/CalderaResultCollectorService.java b/openbas-api/src/main/java/io/openbas/injectors/caldera/service/CalderaResultCollectorService.java index 743d952458..e29c38fe47 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/caldera/service/CalderaResultCollectorService.java +++ b/openbas-api/src/main/java/io/openbas/injectors/caldera/service/CalderaResultCollectorService.java @@ -29,26 +29,17 @@ public class CalderaResultCollectorService implements Runnable { private final InjectRepository injectRepository; private final InjectStatusRepository injectStatusRepository; - private final InjectExpectationService injectExpectationService; private final CalderaInjectorService calderaService; - private final CalderaInjectorConfig calderaInjectorConfig; - private final EndpointService endpointService; @Autowired public CalderaResultCollectorService( InjectRepository injectRepository, InjectStatusRepository injectStatusRepository, - InjectExpectationService injectExpectationService, - CalderaInjectorService calderaService, - CalderaInjectorConfig calderaInjectorConfig, - EndpointService endpointService + CalderaInjectorService calderaService ) { this.injectRepository = injectRepository; this.injectStatusRepository = injectStatusRepository; - this.injectExpectationService = injectExpectationService; this.calderaService = calderaService; - this.calderaInjectorConfig = calderaInjectorConfig; - this.endpointService = endpointService; } @Override @@ -74,7 +65,7 @@ public void run() { log.log(Level.INFO, "Trying to get result for " + linkId); resultStatus = this.calderaService.results(linkId); } catch (Exception e) { - injectStatus.getTraces().add(traceError("Cannot get result for linkID " + linkId + ", injection has failed")); + injectStatus.getTraces().add(traceMaybePrevented("Cannot get result for linkID " + linkId + ", injection has failed")); log.log(Level.INFO, "Cannot get result for linkID " + linkId + ", injection has failed"); resultStatus.setFail(true); completedActions.add(resultStatus); @@ -82,7 +73,7 @@ public void run() { } if (resultStatus.getPaw() == null) { if (injectStatus.getTrackingSentDate().isBefore(Instant.now().minus(EXPIRATION_TIME / 60, ChronoUnit.MINUTES))) { - injectStatus.getTraces().add(traceError("Cannot get result for linkID " + linkId + ", injection has failed")); + injectStatus.getTraces().add(traceMaybePrevented("Cannot get result for linkID " + linkId + ", injection has failed")); log.log(Level.INFO, "Cannot get result for linkID " + linkId + ", injection has failed"); resultStatus.setFail(true); completedActions.add(resultStatus); @@ -93,11 +84,7 @@ public void run() { completedActions.add(resultStatus); if (resultStatus.isFail()) { injectStatus.setTrackingTotalError(injectStatus.getTrackingTotalError() + 1); - if (resultStatus.getContent().contains("denied")) { - injectStatus.getTraces().add(traceMaybePrevented("Failed result for linkID " + linkId + " (" + resultStatus.getContent() + ")")); - } else { - injectStatus.getTraces().add(traceError("Failed result for linkID " + linkId + " (" + resultStatus.getContent() + ")")); - } + injectStatus.getTraces().add(traceMaybePrevented("Failed result for linkID " + linkId + " (" + resultStatus.getContent() + ")")); } else { injectStatus.setTrackingTotalSuccess(injectStatus.getTrackingTotalSuccess() + 1); injectStatus.getTraces().add(traceSuccess("Success result for linkID " + linkId + " (" + resultStatus.getContent() + ")")); @@ -107,7 +94,7 @@ public void run() { finalExecutionTime = resultStatus.getFinish(); } } else if (injectStatus.getTrackingSentDate().isBefore(Instant.now().minus(5L, ChronoUnit.MINUTES))) { - injectStatus.getTraces().add(traceError("Timeout on linkID " + linkId + ", injection has failed")); + injectStatus.getTraces().add(traceMaybePrevented("Timeout on linkID " + linkId + ", injection has failed")); log.log(Level.INFO, "Timeout on linkID " + linkId + ", injection has failed"); resultStatus.setFail(true); completedActions.add(resultStatus); diff --git a/openbas-api/src/main/java/io/openbas/injectors/challenge/ChallengeInjector.java b/openbas-api/src/main/java/io/openbas/injectors/challenge/ChallengeInjector.java index 33d15ad891..4e96893b6e 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/challenge/ChallengeInjector.java +++ b/openbas-api/src/main/java/io/openbas/injectors/challenge/ChallengeInjector.java @@ -20,7 +20,8 @@ public ChallengeInjector(InjectorService injectorService, ChallengeContract cont false, "capture-the-flag", null, - null + null, + false ); } catch (Exception e) { throw new RuntimeException(e); diff --git a/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelContract.java b/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelContract.java index a9b385502f..c17da0b546 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelContract.java +++ b/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelContract.java @@ -63,7 +63,7 @@ public List contracts() { // Choices are contextual to a specific exercise. String messageBody = """ Dear player,

- New channel pressure entries have been published.

+ New media pressure entries have been published.

<#list articles as article> - ${article.name}
@@ -88,7 +88,7 @@ public List contracts() { .optional(expectationsField) // Emailing zone .optional(emailingField) - .mandatory(textField("subject", "Subject", "New channel pressure entries published for ${user.email}", + .mandatory(textField("subject", "Subject", "New media pressure entries published for ${user.email}", List.of(emailingField))) .mandatory(richTextareaField("body", "Body", messageBody, List.of(emailingField))) @@ -96,7 +96,7 @@ public List contracts() { List.of(emailingField))) .build(); Contract publishArticle = executableContract(contractConfig, - CHANNEL_PUBLISH, Map.of(en, "Publish channel pressure", fr, "Publier de la pression médiatique"), publishInstance, List.of(Endpoint.PLATFORM_TYPE.Internal.name()), false); + CHANNEL_PUBLISH, Map.of(en, "Publish a media pressure", fr, "Publier de la pression médiatique"), publishInstance, List.of(Endpoint.PLATFORM_TYPE.Internal.name()), false); // Adding generated variables publishArticle.addVariable(variable(VARIABLE_ARTICLES, "List of articles published by the injection", VariableType.Object, Multiple, List.of( variable(VARIABLE_ARTICLE + ".id", "Id of the article in the platform", VariableType.String, One), diff --git a/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelInjector.java b/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelInjector.java index d540f7f85a..9f7f4f2bea 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelInjector.java +++ b/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelInjector.java @@ -20,7 +20,8 @@ public ChannelInjector(InjectorService injectorService, ChannelContract contract false, "media-pressure", null, - null + null, + false ); } catch (Exception e) { throw new RuntimeException(e); diff --git a/openbas-api/src/main/java/io/openbas/injectors/email/EmailInjector.java b/openbas-api/src/main/java/io/openbas/injectors/email/EmailInjector.java index bd5f9c7011..7ea21d2c32 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/email/EmailInjector.java +++ b/openbas-api/src/main/java/io/openbas/injectors/email/EmailInjector.java @@ -20,7 +20,8 @@ public EmailInjector(InjectorService injectorService, EmailContract contract) { false, "communication", null, - null + null, + false ); } catch (Exception e) { throw new RuntimeException(e); diff --git a/openbas-api/src/main/java/io/openbas/injectors/lade/LadeInjector.java b/openbas-api/src/main/java/io/openbas/injectors/lade/LadeInjector.java index 47fb926d9f..2825df0fa0 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/lade/LadeInjector.java +++ b/openbas-api/src/main/java/io/openbas/injectors/lade/LadeInjector.java @@ -20,7 +20,8 @@ public LadeInjector(InjectorService injectorService, LadeContract contract) { false, "cyber-range", null, - null + null, + false ); } catch (Exception e) { throw new RuntimeException(e); diff --git a/openbas-api/src/main/java/io/openbas/injectors/manual/ManualInjector.java b/openbas-api/src/main/java/io/openbas/injectors/manual/ManualInjector.java index e26d96acaa..299cabe510 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/manual/ManualInjector.java +++ b/openbas-api/src/main/java/io/openbas/injectors/manual/ManualInjector.java @@ -20,7 +20,8 @@ public ManualInjector(InjectorService injectorService, ManualContract contract) true, "generic", null, - null + null, + false ); } catch (Exception e) { throw new RuntimeException(e); diff --git a/openbas-api/src/main/java/io/openbas/injectors/mastodon/MastodonInjector.java b/openbas-api/src/main/java/io/openbas/injectors/mastodon/MastodonInjector.java index 11ea4d69d1..eda98b1433 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/mastodon/MastodonInjector.java +++ b/openbas-api/src/main/java/io/openbas/injectors/mastodon/MastodonInjector.java @@ -20,7 +20,8 @@ public MastodonInjector(InjectorService injectorService, MastodonContract contra false, "social-media", null, - null + null, + false ); } catch (Exception e) { throw new RuntimeException(e); diff --git a/openbas-api/src/main/java/io/openbas/injectors/opencti/OpenCTIInjector.java b/openbas-api/src/main/java/io/openbas/injectors/opencti/OpenCTIInjector.java index 20d81cba0e..2e71c47297 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/opencti/OpenCTIInjector.java +++ b/openbas-api/src/main/java/io/openbas/injectors/opencti/OpenCTIInjector.java @@ -20,7 +20,8 @@ public OpenCTIInjector(InjectorService injectorService, OpenCTIContract contract true, "incident-response", null, - null + null, + false ); } catch (Exception e) { throw new RuntimeException(e); diff --git a/openbas-api/src/main/java/io/openbas/injectors/ovh/OvhSmsInjector.java b/openbas-api/src/main/java/io/openbas/injectors/ovh/OvhSmsInjector.java index 4be998f639..a60decf558 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/ovh/OvhSmsInjector.java +++ b/openbas-api/src/main/java/io/openbas/injectors/ovh/OvhSmsInjector.java @@ -20,7 +20,8 @@ public OvhSmsInjector(InjectorService injectorService, OvhSmsContract contract) true, "communication", null, - null + null, + false ); } catch (Exception e) { throw new RuntimeException(e); diff --git a/openbas-api/src/main/java/io/openbas/migration/V3_11__Assets.java b/openbas-api/src/main/java/io/openbas/migration/V3_11__Assets.java index 75bb042e83..429c39e08f 100644 --- a/openbas-api/src/main/java/io/openbas/migration/V3_11__Assets.java +++ b/openbas-api/src/main/java/io/openbas/migration/V3_11__Assets.java @@ -14,6 +14,5 @@ public class V3_11__Assets extends BaseJavaMigration { public void migrate(Context context) throws Exception { Connection connection = context.getConnection(); Statement select = connection.createStatement(); - select.execute("ALTER TABLE assets ADD column asset_process_name varchar(255);"); - } + select.execute("ALTER TABLE assets ADD column asset_process_name varchar(255);"); } } \ No newline at end of file diff --git a/openbas-api/src/main/java/io/openbas/migration/V3_12__Payloads.java b/openbas-api/src/main/java/io/openbas/migration/V3_12__Payloads.java new file mode 100644 index 0000000000..4b9eb38263 --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/migration/V3_12__Payloads.java @@ -0,0 +1,32 @@ +package io.openbas.migration; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.springframework.stereotype.Component; + +import java.sql.Connection; +import java.sql.Statement; + +@Component +public class V3_12__Payloads extends BaseJavaMigration { + + @Override + public void migrate(Context context) throws Exception { + Connection connection = context.getConnection(); + Statement select = connection.createStatement(); + // Purge + select.execute("TRUNCATE payloads CASCADE;"); + select.execute("ALTER TABLE payloads DROP column payload_content;"); + select.execute("ALTER TABLE payloads ADD column payload_platforms text[];"); + select.execute("ALTER TABLE payloads ADD column command_executor varchar(255);"); + select.execute("ALTER TABLE payloads ADD column command_content text;"); + select.execute("ALTER TABLE payloads ADD column executable_file varchar(255) constraint executable_file_fk references documents on delete cascade;"); + select.execute("ALTER TABLE payloads ADD column file_drop_file varchar(255) constraint file_drop_file_fk references documents on delete cascade;"); + select.execute("ALTER TABLE payloads ADD column dns_resolution_hostname text;"); + select.execute("ALTER TABLE payloads ADD column network_traffic_ip text;"); + select.execute("ALTER TABLE payloads ADD column payload_cleanup_executor varchar(255);"); + select.execute("ALTER TABLE payloads ADD column payload_cleanup_command text;"); + select.execute("ALTER TABLE payloads ADD column payload_arguments json;"); + select.execute("ALTER TABLE payloads ADD column payload_prerequisites json;"); + } +} diff --git a/openbas-api/src/main/java/io/openbas/migration/V3_13__Payloads_Attack_Patterns.java b/openbas-api/src/main/java/io/openbas/migration/V3_13__Payloads_Attack_Patterns.java new file mode 100644 index 0000000000..4deccdb104 --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/migration/V3_13__Payloads_Attack_Patterns.java @@ -0,0 +1,34 @@ +package io.openbas.migration; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.springframework.stereotype.Component; + +import java.sql.Connection; +import java.sql.Statement; + +@Component +public class V3_13__Payloads_Attack_Patterns extends BaseJavaMigration { + + @Override + public void migrate(Context context) throws Exception { + Connection connection = context.getConnection(); + Statement select = connection.createStatement(); + // Create relations between contracts and attack_patterns + select.execute(""" + CREATE TABLE payloads_attack_patterns ( + attack_pattern_id varchar(255) not null + constraint attack_pattern_id_fk + references attack_patterns + on delete cascade, + payload_id varchar(255) not null + constraint payloads_id_fk + references payloads + on delete cascade, + primary key (attack_pattern_id, payload_id) + ); + CREATE INDEX idx_payloads_attack_patterns_pattern on payloads_attack_patterns (attack_pattern_id); + CREATE INDEX idx_payloads_attack_patterns_contract on payloads_attack_patterns (payload_id); + """); + } +} diff --git a/openbas-api/src/main/java/io/openbas/migration/V3_14__Injectors_payload.java b/openbas-api/src/main/java/io/openbas/migration/V3_14__Injectors_payload.java new file mode 100644 index 0000000000..09e32b9188 --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/migration/V3_14__Injectors_payload.java @@ -0,0 +1,17 @@ +package io.openbas.migration; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.springframework.stereotype.Component; + +import java.sql.Statement; + +@Component +public class V3_14__Injectors_payload extends BaseJavaMigration { + + @Override + public void migrate(Context context) throws Exception { + Statement select = context.getConnection().createStatement(); + select.execute("ALTER TABLE injectors ADD injector_payloads bool default false;"); + } +} diff --git a/openbas-api/src/main/java/io/openbas/migration/V3_15__Injector_contracts_Payloads.java b/openbas-api/src/main/java/io/openbas/migration/V3_15__Injector_contracts_Payloads.java new file mode 100644 index 0000000000..fc577f2e02 --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/migration/V3_15__Injector_contracts_Payloads.java @@ -0,0 +1,21 @@ +package io.openbas.migration; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.springframework.stereotype.Component; + +import java.sql.Connection; +import java.sql.Statement; + +@Component +public class V3_15__Injector_contracts_Payloads extends BaseJavaMigration { + + @Override + public void migrate(Context context) throws Exception { + Connection connection = context.getConnection(); + Statement select = connection.createStatement(); + // Purge + select.execute("ALTER TABLE injectors_contracts ADD column injector_contract_payload varchar(255) constraint injector_contract_payload_fk references payloads on delete cascade;"); + select.execute("CREATE UNIQUE INDEX injector_contract_payload_unique on injectors_contracts (injector_contract_payload, injector_id);"); + } +} diff --git a/openbas-api/src/main/java/io/openbas/rest/injector/InjectorApi.java b/openbas-api/src/main/java/io/openbas/rest/injector/InjectorApi.java index 7b6a19d500..c19a981878 100644 --- a/openbas-api/src/main/java/io/openbas/rest/injector/InjectorApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/injector/InjectorApi.java @@ -123,7 +123,8 @@ private Injector updateInjector( Boolean customContracts, String category, Map executorCommands, - Map executorClearCommands) { + Map executorClearCommands, + Boolean payloads) { injector.setUpdatedAt(Instant.now()); injector.setType(type); injector.setName(name); @@ -132,6 +133,7 @@ private Injector updateInjector( injector.setCategory(category); injector.setExecutorCommands(executorCommands); injector.setExecutorClearCommands(executorClearCommands); + injector.setPayloads(payloads); List existing = new ArrayList<>(); List toDeletes = new ArrayList<>(); injector.getContracts().forEach(contract -> { @@ -174,7 +176,8 @@ public Injector updateInjector(@PathVariable String injectorId, @Valid @RequestB input.getCustomContracts(), input.getCategory(), input.getExecutorCommands(), - input.getExecutorClearCommands() + input.getExecutorClearCommands(), + input.getPayloads() ); } @@ -231,7 +234,8 @@ public InjectorRegistration registerInjector(@Valid @RequestPart("input") Inject input.getCustomContracts(), input.getCategory(), input.getExecutorCommands(), - input.getExecutorClearCommands() + input.getExecutorClearCommands(), + input.getPayloads() ); } else { // save the injector @@ -244,6 +248,7 @@ public InjectorRegistration registerInjector(@Valid @RequestPart("input") Inject newInjector.setCustomContracts(input.getCustomContracts()); newInjector.setExecutorCommands(input.getExecutorCommands()); newInjector.setExecutorClearCommands(input.getExecutorClearCommands()); + newInjector.setPayloads(input.getPayloads()); Injector savedInjector = injectorRepository.save(newInjector); // Save the contracts List injectorContracts = input.getContracts().stream() diff --git a/openbas-api/src/main/java/io/openbas/rest/injector/form/InjectorCreateInput.java b/openbas-api/src/main/java/io/openbas/rest/injector/form/InjectorCreateInput.java index 3dd66478a0..185fbab49c 100644 --- a/openbas-api/src/main/java/io/openbas/rest/injector/form/InjectorCreateInput.java +++ b/openbas-api/src/main/java/io/openbas/rest/injector/form/InjectorCreateInput.java @@ -38,6 +38,9 @@ public class InjectorCreateInput { @JsonProperty("injector_executor_clear_commands") private Map executorClearCommands; + @JsonProperty("injector_payloads") + private Boolean payloads = false; + public String getId() { return id; } @@ -101,4 +104,12 @@ public Map getExecutorClearCommands() { public void setExecutorClearCommands(Map executorClearCommands) { this.executorClearCommands = executorClearCommands; } + + public Boolean getPayloads() { + return payloads; + } + + public void setPayloads(Boolean payloads) { + this.payloads = payloads; + } } diff --git a/openbas-api/src/main/java/io/openbas/rest/injector/form/InjectorUpdateInput.java b/openbas-api/src/main/java/io/openbas/rest/injector/form/InjectorUpdateInput.java index 95d748f67e..27f1d4e144 100644 --- a/openbas-api/src/main/java/io/openbas/rest/injector/form/InjectorUpdateInput.java +++ b/openbas-api/src/main/java/io/openbas/rest/injector/form/InjectorUpdateInput.java @@ -33,4 +33,7 @@ public class InjectorUpdateInput { @JsonProperty("injector_executor_clear_commands") private Map executorClearCommands; + + @JsonProperty("injector_payloads") + private Boolean payloads = false; } 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 6120fce141..bbe1d58119 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 @@ -1,12 +1,16 @@ package io.openbas.rest.payload; +import io.openbas.database.model.Command; +import io.openbas.database.model.Executable; import io.openbas.database.model.Payload; +import io.openbas.database.repository.AttackPatternRepository; import io.openbas.database.repository.PayloadRepository; import io.openbas.database.repository.TagRepository; import io.openbas.rest.exception.ElementNotFoundException; import io.openbas.rest.helper.RestBehavior; import io.openbas.rest.payload.form.PayloadCreateInput; import io.openbas.rest.payload.form.PayloadUpdateInput; +import io.openbas.integrations.PayloadService; import io.openbas.utils.pagination.SearchPaginationInput; import jakarta.transaction.Transactional; import jakarta.validation.Valid; @@ -23,6 +27,7 @@ import static io.openbas.database.model.User.ROLE_ADMIN; import static io.openbas.database.model.User.ROLE_USER; +import static io.openbas.helper.StreamHelper.fromIterable; import static io.openbas.helper.StreamHelper.iterableToSet; import static io.openbas.utils.pagination.PaginationUtils.buildPaginationJPA; @@ -32,6 +37,8 @@ public class PayloadApi extends RestBehavior { private PayloadRepository payloadRepository; private TagRepository tagRepository; + private PayloadService payloadService; + private AttackPatternRepository attackPatternRepository; @Autowired public void setPayloadRepository(PayloadRepository payloadRepository) { @@ -43,6 +50,16 @@ public void setTagRepository(TagRepository tagRepository) { this.tagRepository = tagRepository; } + @Autowired + public void setPayloadService(PayloadService payloadService) { + this.payloadService = payloadService; + } + + @Autowired + public void setAttackPatternRepository(AttackPatternRepository attackPatternRepository) { + this.attackPatternRepository = attackPatternRepository; + } + @GetMapping("/api/payloads") public Iterable payloads() { return payloadRepository.findAll(); @@ -67,10 +84,26 @@ public Payload payload(@PathVariable String payloadId) { @PreAuthorize("isPlanner()") @Transactional(rollbackOn = Exception.class) public Payload createPayload(@Valid @RequestBody PayloadCreateInput input) { - Payload payload = new Payload(); - payload.setUpdateAttributes(input); - payload.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds()))); - return payloadRepository.save(payload); + switch (input.getType()) { + case "Command": + Command commandPayload = new Command(); + commandPayload.setUpdateAttributes(input); + commandPayload.setAttackPatterns(fromIterable(attackPatternRepository.findAllById(input.getAttackPatternsIds()))); + commandPayload.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds()))); + commandPayload = payloadRepository.save(commandPayload); + this.payloadService.updateInjectorContractsForPayload(commandPayload); + return commandPayload; + case "Executable": + Executable executablePayload = new Executable(); + executablePayload.setUpdateAttributes(input); + executablePayload.setAttackPatterns(fromIterable(attackPatternRepository.findAllById(input.getAttackPatternsIds()))); + executablePayload.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds()))); + executablePayload = payloadRepository.save(executablePayload); + this.payloadService.updateInjectorContractsForPayload(executablePayload); + return executablePayload; + default: + throw new UnsupportedOperationException("Payload type " + input.getType() + " is not supported"); + } } @PutMapping("/api/payloads/{payloadId}") @@ -81,9 +114,12 @@ public Payload updatePayload( @Valid @RequestBody PayloadUpdateInput input) { Payload payload = this.payloadRepository.findById(payloadId).orElseThrow(ElementNotFoundException::new); payload.setUpdateAttributes(input); + payload.setAttackPatterns(fromIterable(attackPatternRepository.findAllById(input.getAttackPatternsIds()))); payload.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds()))); payload.setUpdatedAt(Instant.now()); - return payloadRepository.save(payload); + payload = payloadRepository.save(payload); + this.payloadService.updateInjectorContractsForPayload(payload); + return payload; } @Secured(ROLE_ADMIN) diff --git a/openbas-api/src/main/java/io/openbas/rest/payload/form/PayloadCreateInput.java b/openbas-api/src/main/java/io/openbas/rest/payload/form/PayloadCreateInput.java index a81c1bf097..5de0fd7e1d 100644 --- a/openbas-api/src/main/java/io/openbas/rest/payload/form/PayloadCreateInput.java +++ b/openbas-api/src/main/java/io/openbas/rest/payload/form/PayloadCreateInput.java @@ -22,15 +22,29 @@ public class PayloadCreateInput { @JsonProperty("payload_name") private String name; + @JsonProperty("payload_platforms") + private String[] platforms; + @JsonProperty("payload_description") private String description; - @NotBlank(message = MANDATORY_MESSAGE) - @JsonProperty("payload_content") + @JsonProperty("command_executor") + private String executor; + + @JsonProperty("command_content") private String content; + @JsonProperty("payload_cleanup_executor") + private String cleanupExecutor; + + @JsonProperty("payload_cleanup_command") + private String cleanupCommand; + @JsonProperty("payload_tags") private List tagIds = new ArrayList<>(); + + @JsonProperty("payload_attack_patterns") + private List attackPatternsIds = new ArrayList<>(); } diff --git a/openbas-api/src/main/java/io/openbas/rest/payload/form/PayloadUpdateInput.java b/openbas-api/src/main/java/io/openbas/rest/payload/form/PayloadUpdateInput.java index 4e829b3eb1..36cb83fb51 100644 --- a/openbas-api/src/main/java/io/openbas/rest/payload/form/PayloadUpdateInput.java +++ b/openbas-api/src/main/java/io/openbas/rest/payload/form/PayloadUpdateInput.java @@ -13,24 +13,33 @@ @Getter @Setter public class PayloadUpdateInput { - @NotBlank(message = MANDATORY_MESSAGE) - @JsonProperty("payload_type") - private String type; - @NotBlank(message = MANDATORY_MESSAGE) @JsonProperty("payload_name") private String name; + @JsonProperty("payload_platforms") + private String[] platforms; + @JsonProperty("payload_description") private String description; - @NotBlank(message = MANDATORY_MESSAGE) - @JsonProperty("payload_content") + @JsonProperty("command_executor") + private String executor; + + @JsonProperty("command_content") private String content; + @JsonProperty("payload_cleanup_executor") + private String cleanupExecutor; + + @JsonProperty("payload_cleanup_command") + private String cleanupCommand; + @JsonProperty("payload_tags") private List tagIds = new ArrayList<>(); + @JsonProperty("payload_attack_patterns") + private List attackPatternsIds = new ArrayList<>(); } diff --git a/openbas-framework/src/main/java/io/openbas/injector_contract/ContractConfig.java b/openbas-framework/src/main/java/io/openbas/injector_contract/ContractConfig.java index f965c66280..8322c223b3 100644 --- a/openbas-framework/src/main/java/io/openbas/injector_contract/ContractConfig.java +++ b/openbas-framework/src/main/java/io/openbas/injector_contract/ContractConfig.java @@ -16,8 +16,7 @@ public class ContractConfig { private final String color_dark; private final String color_light; - public ContractConfig(String type, Map label, String color_dark, String color_light, - String icon, boolean expose) { + public ContractConfig(String type, Map label, String color_dark, String color_light, String icon, boolean expose) { this.type = type; this.expose = expose; this.color_dark = color_dark; diff --git a/openbas-framework/src/main/java/io/openbas/integrations/InjectorService.java b/openbas-framework/src/main/java/io/openbas/integrations/InjectorService.java index 1c13e4da34..126b0d0b34 100644 --- a/openbas-framework/src/main/java/io/openbas/integrations/InjectorService.java +++ b/openbas-framework/src/main/java/io/openbas/integrations/InjectorService.java @@ -41,6 +41,8 @@ public class InjectorService { private AttackPatternRepository attackPatternRepository; + private PayloadService payloadService; + @Resource public void setFileService(FileService fileService) { this.fileService = fileService; @@ -61,8 +63,13 @@ public void setInjectorContractRepository(InjectorContractRepository injectorCon this.injectorContractRepository = injectorContractRepository; } + @Autowired + public void setPayloadService(PayloadService payloadService) { + this.payloadService = payloadService; + } + @Transactional - public void register(String id, String name, Contractor contractor, Boolean isCustomizable, String category, Map executorCommands, Map executorClearCommands) throws Exception { + public void register(String id, String name, Contractor contractor, Boolean isCustomizable, String category, Map executorCommands, Map executorClearCommands, Boolean isPayloads) throws Exception { if(!contractor.isExpose()) { return; } @@ -88,6 +95,7 @@ public void register(String id, String name, Contractor contractor, Boolean isCu injector.setCategory(category); injector.setExecutorCommands(executorCommands); injector.setExecutorClearCommands(executorClearCommands); + injector.setPayloads(isPayloads); injector.setUpdatedAt(Instant.now()); List existing = new ArrayList<>(); List toUpdates = new ArrayList<>(); @@ -150,6 +158,9 @@ public void register(String id, String name, Contractor contractor, Boolean isCu injectorContractRepository.saveAll(toCreates); injectorContractRepository.saveAll(toUpdates); injectorRepository.save(injector); + if( injector.isPayloads() ) { + this.payloadService.updateInjectorContractsForInjector(injector); + } } else { // save the injector Injector newInjector = new Injector(); @@ -157,9 +168,14 @@ public void register(String id, String name, Contractor contractor, Boolean isCu newInjector.setName(name); newInjector.setType(contractor.getType()); newInjector.setCategory(category); + newInjector.setCustomContracts(isCustomizable); newInjector.setExecutorCommands(executorCommands); newInjector.setExecutorClearCommands(executorClearCommands); + newInjector.setPayloads(isPayloads); Injector savedInjector = injectorRepository.save(newInjector); + if( savedInjector.isPayloads() ) { + this.payloadService.updateInjectorContractsForInjector(savedInjector); + } // Save the contracts List injectorContracts = contracts.stream().map(in -> { InjectorContract injectorContract = new InjectorContract(); diff --git a/openbas-framework/src/main/java/io/openbas/integrations/PayloadService.java b/openbas-framework/src/main/java/io/openbas/integrations/PayloadService.java new file mode 100644 index 0000000000..8117cbfdda --- /dev/null +++ b/openbas-framework/src/main/java/io/openbas/integrations/PayloadService.java @@ -0,0 +1,145 @@ +package io.openbas.integrations; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.openbas.database.model.AttackPattern; +import io.openbas.database.model.Injector; +import io.openbas.database.model.InjectorContract; +import io.openbas.database.model.Payload; +import io.openbas.database.repository.AttackPatternRepository; +import io.openbas.database.repository.InjectorContractRepository; +import io.openbas.database.repository.InjectorRepository; +import io.openbas.database.repository.PayloadRepository; +import io.openbas.helper.SupportedLanguage; +import io.openbas.injector_contract.Contract; +import io.openbas.injector_contract.ContractConfig; +import io.openbas.injector_contract.ContractDef; +import io.openbas.injector_contract.fields.ContractAsset; +import io.openbas.injector_contract.fields.ContractAssetGroup; +import io.openbas.injector_contract.fields.ContractExpectations; +import io.openbas.model.inject.form.Expectation; +import jakarta.annotation.Resource; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static io.openbas.database.model.InjectExpectation.EXPECTATION_TYPE.DETECTION; +import static io.openbas.database.model.InjectExpectation.EXPECTATION_TYPE.PREVENTION; +import static io.openbas.helper.StreamHelper.fromIterable; +import static io.openbas.helper.SupportedLanguage.en; +import static io.openbas.helper.SupportedLanguage.fr; +import static io.openbas.injector_contract.Contract.executableContract; +import static io.openbas.injector_contract.ContractCardinality.Multiple; +import static io.openbas.injector_contract.ContractDef.contractBuilder; +import static io.openbas.injector_contract.fields.ContractAsset.assetField; +import static io.openbas.injector_contract.fields.ContractAssetGroup.assetGroupField; +import static io.openbas.injector_contract.fields.ContractExpectations.expectationsField; + +@RequiredArgsConstructor +@Service +public class PayloadService { + @Resource + protected ObjectMapper mapper; + + private final PayloadRepository payloadRepository; + private final InjectorRepository injectorRepository; + private final InjectorContractRepository injectorContractRepository; + private final AttackPatternRepository attackPatternRepository; + + @Transactional + public void updateInjectorContractsForInjector(Injector injector) { + if( !injector.isPayloads() ) { + throw new UnsupportedOperationException("This injector does not support payloads"); + } + Iterable payloads = payloadRepository.findAll(); + payloads.forEach(payload -> { + updateInjectorContract(injector, payload); + }); + } + + @Transactional + public void updateInjectorContractsForPayload(Payload payload) { + List injectors = injectorRepository.findAllByPayloads(true); + injectors.forEach(injector -> { + updateInjectorContract(injector, payload); + }); + } + + private void updateInjectorContract(Injector injector, Payload payload) { + Optional injectorContract = injectorContractRepository.findByInjectorAndPayload(injector, payload); + Contract contract = buildContract(injector, payload); + if (injectorContract.isPresent()) { + InjectorContract existingInjectorContract = injectorContract.get(); + Map labels = Map.of("en", payload.getName(), "fr", payload.getName()); + existingInjectorContract.setLabels(labels); + existingInjectorContract.setManual(false); + existingInjectorContract.setInjector(injector); + existingInjectorContract.setPayload(payload); + existingInjectorContract.setPlatforms(payload.getPlatforms()); + existingInjectorContract.setAttackPatterns(fromIterable(attackPatternRepository.findAllById(payload.getAttackPatterns().stream().map(AttackPattern::getId).toList()))); + existingInjectorContract.setAtomicTesting(true); + try { + existingInjectorContract.setContent(mapper.writeValueAsString(contract)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + injectorContractRepository.save(existingInjectorContract); + } else { + Map labels = Map.of("en", payload.getName(), "fr", payload.getName()); + InjectorContract newInjectorContract = new InjectorContract(); + newInjectorContract.setId(payload.getId() + "-" + injector.getId()); + newInjectorContract.setLabels(labels); + newInjectorContract.setManual(false); + newInjectorContract.setInjector(injector); + newInjectorContract.setPayload(payload); + newInjectorContract.setPlatforms(payload.getPlatforms()); + newInjectorContract.setAttackPatterns(fromIterable(attackPatternRepository.findAllById(payload.getAttackPatterns().stream().map(AttackPattern::getId).toList()))); + newInjectorContract.setAtomicTesting(true); + try { + newInjectorContract.setContent(mapper.writeValueAsString(contract)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + injectorContractRepository.save(newInjectorContract); + } + } + + private Contract buildContract(@NotNull final Injector injector, @NotNull final Payload payload) { + Map labels = Map.of(en, injector.getName(), fr, injector.getName()); + ContractConfig contractConfig = new ContractConfig(injector.getType(), labels, "#000000", "#000000", "/img/icon-" + injector.getType() + ".png", true); + ContractAsset assetField = assetField("assets", "Assets", Multiple); + ContractAssetGroup assetGroupField = assetGroupField("assetgroups", "Asset groups", Multiple); + ContractExpectations expectationsField = expectations(); + ContractDef builder = contractBuilder(); + builder.mandatoryGroup(assetField, assetGroupField); + builder.optional(expectationsField); + return executableContract( + contractConfig, + payload.getId(), + Map.of(en, payload.getName(), fr, payload.getName()), + builder.build(), + Arrays.asList(payload.getPlatforms()), + true + ); + } + + private ContractExpectations expectations() { + // Prevention + Expectation preventionExpectation = new Expectation(); + preventionExpectation.setType(PREVENTION); + preventionExpectation.setName("Expect inject to be prevented"); + preventionExpectation.setScore(0); + // Detection + Expectation detectionExpectation = new Expectation(); + detectionExpectation.setType(DETECTION); + detectionExpectation.setName("Expect inject to be detected"); + detectionExpectation.setScore(0); + return expectationsField("expectations", "Expectations", List.of(preventionExpectation, detectionExpectation)); + } +} diff --git a/openbas-front/src/admin/Index.tsx b/openbas-front/src/admin/Index.tsx index a70897f1a1..734c51c803 100644 --- a/openbas-front/src/admin/Index.tsx +++ b/openbas-front/src/admin/Index.tsx @@ -30,6 +30,7 @@ const IndexIntegrations = lazy(() => import('./components/integrations/Index')); const IndexAgents = lazy(() => import('./components/agents/Agents')); const LessonsTemplates = lazy(() => import('./components/lessons/LessonsTemplates')); const IndexLessonsTemplate = lazy(() => import('./components/lessons/Index')); +const Payloads = lazy(() => import('./components/payloads/Payloads')); const Mitigations = lazy(() => import('./components/mitigations/Mitigations')); const IndexSettings = lazy(() => import('./components/settings/Index')); @@ -85,6 +86,7 @@ const Index = () => { + diff --git a/openbas-front/src/admin/components/common/injects/InjectAddArticles.tsx b/openbas-front/src/admin/components/common/injects/InjectAddArticles.tsx index 63525bec6f..3c97609b93 100644 --- a/openbas-front/src/admin/components/common/injects/InjectAddArticles.tsx +++ b/openbas-front/src/admin/components/common/injects/InjectAddArticles.tsx @@ -127,7 +127,7 @@ const InjectAddArticles: FunctionComponent = ({ diff --git a/openbas-front/src/admin/components/common/injects/InjectDefinition.js b/openbas-front/src/admin/components/common/injects/InjectDefinition.js index 0d05cf1fde..30bbde7440 100644 --- a/openbas-front/src/admin/components/common/injects/InjectDefinition.js +++ b/openbas-front/src/admin/components/common/injects/InjectDefinition.js @@ -1198,7 +1198,7 @@ class InjectDefinition extends Component { {hasArticles && ( <> - {t('Channel pressure to publish')} + {t('Media pressure to publish')} {sortedArticles.map((article) => ( diff --git a/openbas-front/src/admin/components/common/injects/Injects.js b/openbas-front/src/admin/components/common/injects/Injects.js index 391c459e32..d5b817ded7 100644 --- a/openbas-front/src/admin/components/common/injects/Injects.js +++ b/openbas-front/src/admin/components/common/injects/Injects.js @@ -489,6 +489,7 @@ const Injects = (props) => { status={inject.inject_ready ? inject.inject_enabled : false} label={injectStatus} variant="inList" + tooltip={injectStatus} />
import('./channels/Index')); const Channels = lazy(() => import('./channels/Channels')); const Documents = lazy(() => import('./documents/Documents')); -const Payloads = lazy(() => import('./payloads/Payloads')); const Challenges = lazy(() => import('./challenges/Challenges')); const useStyles = makeStyles(() => ({ @@ -25,7 +24,6 @@ const Index = () => { } /> - diff --git a/openbas-front/src/admin/components/components/payloads/CreatePayload.js b/openbas-front/src/admin/components/components/payloads/CreatePayload.js deleted file mode 100644 index 435f4746cc..0000000000 --- a/openbas-front/src/admin/components/components/payloads/CreatePayload.js +++ /dev/null @@ -1,90 +0,0 @@ -import React, { Component } from 'react'; -import * as PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import * as R from 'ramda'; -import { Fab } from '@mui/material'; -import { withStyles } from '@mui/styles'; -import { Add } from '@mui/icons-material'; -import { addPayload } from '../../../../actions/Payload'; -import PayloadForm from './PayloadForm'; -import inject18n from '../../../../components/i18n'; -import Drawer from '../../../../components/common/Drawer'; - -const styles = () => ({ - createButton: { - position: 'fixed', - bottom: 30, - right: 30, - }, -}); - -class CreatePayload extends Component { - constructor(props) { - super(props); - this.state = { open: false }; - } - - handleOpen() { - this.setState({ open: true }); - } - - handleClose() { - this.setState({ open: false }); - } - - onSubmit(data) { - const inputValues = R.pipe( - R.assoc('payload_tags', R.pluck('id', data.payload_tags)), - )(data); - return this.props - .addPayload(inputValues) - .then((result) => { - if (this.props.onCreate) { - const payloadCreated = result.entities.payloads[result.result]; - this.props.onCreate(payloadCreated); - } - return (result.result ? this.handleClose() : result); - }); - } - - render() { - const { classes, t } = this.props; - return ( - <> - - - - - - - - ); - } -} - -CreatePayload.propTypes = { - t: PropTypes.func, - organizations: PropTypes.array, - addPayload: PropTypes.func, - onCreate: PropTypes.func, -}; - -export default R.compose( - connect(null, { addPayload }), - inject18n, - withStyles(styles), -)(CreatePayload); diff --git a/openbas-front/src/admin/components/components/payloads/PayloadForm.js b/openbas-front/src/admin/components/components/payloads/PayloadForm.js deleted file mode 100644 index 5aa6436e12..0000000000 --- a/openbas-front/src/admin/components/components/payloads/PayloadForm.js +++ /dev/null @@ -1,110 +0,0 @@ -import React from 'react'; -import * as PropTypes from 'prop-types'; -import { Form } from 'react-final-form'; -import { Button, MenuItem } from '@mui/material'; -import OldTextField from '../../../../components/fields/OldTextField'; -import { useFormatter } from '../../../../components/i18n'; -import OldSelectField from '../../../../components/fields/OldSelectField'; -import TagField from '../../../../components/TagField'; - -const PayloadForm = (props) => { - const { onSubmit, initialValues, editing, handleClose } = props; - const { t } = useFormatter(); - const validate = (values) => { - const errors = {}; - const requiredFields = ['payload_type', 'payload_name', 'payload_content']; - requiredFields.forEach((field) => { - if (!values[field]) { - errors[field] = t('This field is required.'); - } - }); - return errors; - }; - return ( -
{ - changeValue(state, field, () => value); - }, - }} - > - {({ handleSubmit, form, values, submitting, pristine }) => ( - - - - {t('Windows Command Line')} - - - {t('Windows Powershell')} - - - - - - -
- - -
- - )} - - ); -}; - -PayloadForm.propTypes = { - onSubmit: PropTypes.func.isRequired, - handleClose: PropTypes.func, - editing: PropTypes.bool, -}; - -export default PayloadForm; diff --git a/openbas-front/src/admin/components/integrations/Injectors.tsx b/openbas-front/src/admin/components/integrations/Injectors.tsx index aec3643f69..5129433b28 100644 --- a/openbas-front/src/admin/components/integrations/Injectors.tsx +++ b/openbas-front/src/admin/components/integrations/Injectors.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { makeStyles } from '@mui/styles'; import { Link } from 'react-router-dom'; import { Card, CardActionArea, CardContent, Chip, Grid, Tooltip, Typography } from '@mui/material'; -import { AutoModeOutlined } from '@mui/icons-material'; +import { AutoModeOutlined, SubscriptionsOutlined } from '@mui/icons-material'; import { useFormatter } from '../../../components/i18n'; import { useHelper } from '../../../store'; import useDataLoader from '../../../utils/hooks/useDataLoader'; @@ -58,6 +58,10 @@ const useStyles = makeStyles((theme: Theme) => ({ top: 10, right: 10, }, + payload: { + position: 'absolute', + top: 10, + }, })); const Injectors = () => { @@ -152,7 +156,12 @@ const Injectors = () => {
{injector.injector_custom_contracts &&
- + + +
} + {injector.injector_payloads &&
+ +
} diff --git a/openbas-front/src/admin/components/integrations/injectors/injector_contracts/CreateInjectorContract.js b/openbas-front/src/admin/components/integrations/injectors/injector_contracts/CreateInjectorContract.js index b5d75c44a5..e8b18fdaa6 100644 --- a/openbas-front/src/admin/components/integrations/injectors/injector_contracts/CreateInjectorContract.js +++ b/openbas-front/src/admin/components/integrations/injectors/injector_contracts/CreateInjectorContract.js @@ -116,7 +116,7 @@ class CreateInjectorContract extends Component { <> - {t('SelectField the template')} + {t('Select the template')} {t('Create the injector contract')} diff --git a/openbas-front/src/admin/components/integrations/injectors/injector_contracts/InjectorContractPopover.js b/openbas-front/src/admin/components/integrations/injectors/injector_contracts/InjectorContractPopover.js index 864dc82d56..c2011c1b66 100644 --- a/openbas-front/src/admin/components/integrations/injectors/injector_contracts/InjectorContractPopover.js +++ b/openbas-front/src/admin/components/integrations/injectors/injector_contracts/InjectorContractPopover.js @@ -5,7 +5,7 @@ import { Button, Dialog, DialogActions, DialogContent, DialogContentText, IconBu import { MoreVert } from '@mui/icons-material'; import InjectorContractForm from './InjectorContractForm'; import { useFormatter } from '../../../../../components/i18n'; -import { attackPatternsOptions } from '../../../../../utils/Option'; +import { attackPatternOptions } from '../../../../../utils/Option'; import Transition from '../../../../../components/common/Transition'; import { deleteInjectorContract, updateInjectorContract, updateInjectorContractMapping } from '../../../../../actions/InjectorContracts'; import InjectorContractCustomForm from './InjectorContractCustomForm'; @@ -83,7 +83,7 @@ const InjectorContractPopover = ({ injectorContract, killChainPhasesMap, attackP dispatch(deleteInjectorContract(injectorContract.injector_contract_id)); handleCloseDelete(); }; - const injectorContractAttackPatterns = attackPatternsOptions(injectorContract.injector_contract_attack_patterns, attackPatternsMap, killChainPhasesMap); + const injectorContractAttackPatterns = attackPatternOptions(injectorContract.injector_contract_attack_patterns, attackPatternsMap, killChainPhasesMap); let initialValues = null; if (injectorContract.injector_contract_custom) { initialValues = R.pipe( diff --git a/openbas-front/src/admin/components/mitigations/MitigationPopover.js b/openbas-front/src/admin/components/mitigations/MitigationPopover.js index 9c84a86143..7a57a34aef 100644 --- a/openbas-front/src/admin/components/mitigations/MitigationPopover.js +++ b/openbas-front/src/admin/components/mitigations/MitigationPopover.js @@ -8,7 +8,7 @@ import MitigationForm from './MitigationForm'; import { useFormatter } from '../../../components/i18n'; import Transition from '../../../components/common/Transition'; import Drawer from '../../../components/common/Drawer'; -import { attackPatternsOptions } from '../../../utils/Option'; +import { attackPatternOptions } from '../../../utils/Option'; const MitigationPopover = ({ mitigation, attackPatternsMap, killChainPhasesMap, onUpdate, onDelete }) => { const [openDelete, setOpenDelete] = useState(false); @@ -54,7 +54,7 @@ const MitigationPopover = ({ mitigation, attackPatternsMap, killChainPhasesMap, ); handleCloseDelete(); }; - const mitigationAttackPatterns = attackPatternsOptions(mitigation.mitigation_attack_patterns, attackPatternsMap, killChainPhasesMap); + const mitigationAttackPatterns = attackPatternOptions(mitigation.mitigation_attack_patterns, attackPatternsMap, killChainPhasesMap); const initialValues = R.pipe( R.pick([ 'mitigation_external_id', diff --git a/openbas-front/src/admin/components/nav/LeftBar.tsx b/openbas-front/src/admin/components/nav/LeftBar.tsx index f309ca2321..b0dfa82504 100644 --- a/openbas-front/src/admin/components/nav/LeftBar.tsx +++ b/openbas-front/src/admin/components/nav/LeftBar.tsx @@ -489,7 +489,6 @@ const LeftBar = () => { [ { type: 'Document', link: '/admin/components/documents', label: 'Documents', icon: }, { type: 'Variable', link: '/admin/components/variables', label: 'Custom variables', icon: , disabled: true }, - { type: 'Payload', link: '/admin/components/payloads', label: 'Payloads', icon: }, { type: 'Persona', link: '/admin/components/personas', label: 'Personas', icon: , disabled: true }, { type: 'Channel', link: '/admin/components/channels', label: 'Channels', icon: }, { type: 'Challenge', link: '/admin/components/challenges', label: 'Challenges', icon: }, @@ -542,6 +541,25 @@ const LeftBar = () => { + + + + + + {navOpen && ( + + )} + + ({ + createButton: { + position: 'fixed', + bottom: 30, + right: 30, + }, +}); + +class CreatePayload extends Component { + constructor(props) { + super(props); + this.state = { open: false, activeStep: 0, selectedType: null }; + } + + handleOpen() { + this.setState({ open: true }); + } + + handleClose() { + this.setState({ open: false, activeStep: 0, selectedType: null }); + } + + handleSelectType(type) { + this.setState({ selectedType: type, activeStep: 1 }); + } + + onSubmit(data) { + const inputValues = R.pipe( + R.assoc('payload_type', this.state.selectedType), + R.assoc('payload_platforms', R.pluck('id', data.payload_platforms)), + R.assoc('payload_tags', R.pluck('id', data.payload_tags)), + R.assoc('payload_attack_patterns', R.pluck('id', data.payload_attack_patterns)), + )(data); + return this.props + .addPayload(inputValues) + .then((result) => { + if (this.props.onCreate) { + const payloadCreated = result.entities.payloads[result.result]; + this.props.onCreate(payloadCreated); + } + return (result.result ? this.handleClose() : result); + }); + } + + renderTypes() { + const { t } = this.props; + return ( + + + + + + + + + ); + } + + render() { + const { classes, t } = this.props; + const { open, activeStep, selectedType } = this.state; + return ( + <> + + + + + <> + + + {t('Select the type')} + + + {t('Create the payload')} + + + {activeStep === 0 && this.renderTypes()} + {activeStep === 1 && ( + + )} + + + + ); + } +} + +CreatePayload.propTypes = { + t: PropTypes.func, + organizations: PropTypes.array, + addPayload: PropTypes.func, + onCreate: PropTypes.func, +}; + +export default R.compose( + connect(null, { addPayload }), + inject18n, + withStyles(styles), +)(CreatePayload); diff --git a/openbas-front/src/admin/components/payloads/PayloadForm.js b/openbas-front/src/admin/components/payloads/PayloadForm.js new file mode 100644 index 0000000000..3f7b7b69c9 --- /dev/null +++ b/openbas-front/src/admin/components/payloads/PayloadForm.js @@ -0,0 +1,172 @@ +import React from 'react'; +import * as PropTypes from 'prop-types'; +import { Form } from 'react-final-form'; +import { Button, MenuItem } from '@mui/material'; +import OldTextField from '../../../components/fields/OldTextField'; +import { useFormatter } from '../../../components/i18n'; +import TagField from '../../../components/TagField'; +import PlatformField from '../../../components/PlatformField'; +import OldSelectField from '../../../components/fields/OldSelectField'; +import AttackPatternField from '../../../components/AttackPatternField'; + +const PayloadForm = (props) => { + const { onSubmit, initialValues, editing, handleClose, type } = props; + const { t } = useFormatter(); + const validate = (values) => { + const errors = {}; + const requiredFields = ['payload_name', 'payload_platforms']; + switch (type) { + case 'Command': + requiredFields.push(...['command_executor', 'command_content']); + break; + default: + // do nothing + } + requiredFields.forEach((field) => { + if (!values[field]) { + errors[field] = t('This field is required.'); + } + }); + return errors; + }; + return ( +
{ + changeValue(state, field, () => value); + }, + }} + > + {({ handleSubmit, form, values, submitting, errors }) => ( + + + + + {type === 'Command' && ( + <> + + + {t('PowerShell')} + + + {t('Command Prompt')} + + + {t('Bash')} + + + {t('Sh')} + + + + + )} + + + {t('PowerShell')} + + + {t('Command Prompt')} + + + {t('Bash')} + + + {t('Sh')} + + + + + +
+ + +
+ + )} + + ); +}; + +PayloadForm.propTypes = { + onSubmit: PropTypes.func.isRequired, + handleClose: PropTypes.func, + editing: PropTypes.bool, +}; + +export default PayloadForm; diff --git a/openbas-front/src/admin/components/components/payloads/PayloadPopover.js b/openbas-front/src/admin/components/payloads/PayloadPopover.js similarity index 74% rename from openbas-front/src/admin/components/components/payloads/PayloadPopover.js rename to openbas-front/src/admin/components/payloads/PayloadPopover.js index 9bbdc614e0..97788b4f4b 100644 --- a/openbas-front/src/admin/components/components/payloads/PayloadPopover.js +++ b/openbas-front/src/admin/components/payloads/PayloadPopover.js @@ -3,14 +3,14 @@ import { useDispatch } from 'react-redux'; import * as R from 'ramda'; import { Button, Dialog, DialogActions, DialogContent, DialogContentText, IconButton, Menu, MenuItem } from '@mui/material'; import { MoreVert } from '@mui/icons-material'; -import { deletePayload, updatePayload } from '../../../../actions/Payload'; +import { deletePayload, updatePayload } from '../../../actions/Payload'; import PayloadForm from './PayloadForm'; -import { useFormatter } from '../../../../components/i18n'; -import { tagOptions } from '../../../../utils/Option'; -import Transition from '../../../../components/common/Transition'; -import Drawer from '../../../../components/common/Drawer'; +import { useFormatter } from '../../../components/i18n'; +import { attackPatternOptions, platformOptions, tagOptions } from '../../../utils/Option'; +import Transition from '../../../components/common/Transition'; +import Drawer from '../../../components/common/Drawer'; -const PayloadPopover = ({ payload, tagsMap, onUpdate, onDelete }) => { +const PayloadPopover = ({ payload, tagsMap, attackPatternsMap, killChainPhasesMap, onUpdate, onDelete }) => { const [openDelete, setOpenDelete] = useState(false); const [openEdit, setOpenEdit] = useState(false); const [anchorEl, setAnchorEl] = useState(null); @@ -28,7 +28,9 @@ const PayloadPopover = ({ payload, tagsMap, onUpdate, onDelete }) => { const handleCloseEdit = () => setOpenEdit(false); const onSubmitEdit = (data) => { const inputValues = R.pipe( + R.assoc('payload_platforms', R.pluck('id', data.payload_platforms)), R.assoc('payload_tags', R.pluck('id', data.payload_tags)), + R.assoc('payload_attack_patterns', R.pluck('id', data.payload_attack_patterns)), )(data); return dispatch(updatePayload(payload.payload_id, inputValues)).then((result) => { if (onUpdate) { @@ -54,14 +56,20 @@ const PayloadPopover = ({ payload, tagsMap, onUpdate, onDelete }) => { ); handleCloseDelete(); }; + const payloadAttackPatterns = attackPatternOptions(payload.payload_attack_patterns, attackPatternsMap, killChainPhasesMap); const payloadTags = tagOptions(payload.payload_tags, tagsMap); + const payloadPlatforms = platformOptions(payload.payload_platforms); const initialValues = R.pipe( R.pick([ - 'payload_type', 'payload_name', 'payload_description', - 'payload_content', + 'payload_cleanup_executor', + 'payload_cleanup_command', + 'command_executor', + 'command_content', ]), + R.assoc('payload_platforms', payloadPlatforms), + R.assoc('payload_attack_patterns', payloadAttackPatterns), R.assoc('payload_tags', payloadTags), )(payload); return ( @@ -105,6 +113,7 @@ const PayloadPopover = ({ payload, tagsMap, onUpdate, onDelete }) => { editing={true} onSubmit={onSubmitEdit} handleClose={handleCloseEdit} + type={payload.payload_type} /> diff --git a/openbas-front/src/admin/components/components/payloads/Payloads.js b/openbas-front/src/admin/components/payloads/Payloads.js similarity index 76% rename from openbas-front/src/admin/components/components/payloads/Payloads.js rename to openbas-front/src/admin/components/payloads/Payloads.js index 35d479b206..799571e7a3 100644 --- a/openbas-front/src/admin/components/components/payloads/Payloads.js +++ b/openbas-front/src/admin/components/payloads/Payloads.js @@ -3,19 +3,21 @@ import { useDispatch } from 'react-redux'; import { Chip, List, ListItem, ListItemIcon, ListItemSecondaryAction, ListItemText } from '@mui/material'; import { makeStyles } from '@mui/styles'; import { SubscriptionsOutlined } from '@mui/icons-material'; -import { searchPayloads } from '../../../../actions/Payload'; +import { searchPayloads } from '../../../actions/Payload'; import CreatePayload from './CreatePayload'; -import useDataLoader from '../../../../utils/hooks/useDataLoader'; -import { useHelper } from '../../../../store'; +import useDataLoader from '../../../utils/hooks/useDataLoader'; +import { useHelper } from '../../../store'; import PayloadPopover from './PayloadPopover'; -import { fetchKillChainPhases } from '../../../../actions/KillChainPhase'; -import PaginationComponent from '../../../../components/common/pagination/PaginationComponent'; -import SortHeadersComponent from '../../../../components/common/pagination/SortHeadersComponent'; -import { initSorting } from '../../../../components/common/pagination/Page'; -import { useFormatter } from '../../../../components/i18n'; -import Breadcrumbs from '../../../../components/Breadcrumbs'; -import { fetchTags } from '../../../../actions/Tag'; -import ItemTags from '../../../../components/ItemTags'; +import { fetchKillChainPhases } from '../../../actions/KillChainPhase'; +import PaginationComponent from '../../../components/common/pagination/PaginationComponent'; +import SortHeadersComponent from '../../../components/common/pagination/SortHeadersComponent'; +import { initSorting } from '../../../components/common/pagination/Page'; +import { useFormatter } from '../../../components/i18n'; +import Breadcrumbs from '../../../components/Breadcrumbs'; +import { fetchTags } from '../../../actions/Tag'; +import ItemTags from '../../../components/ItemTags'; +import { fetchAttackPatterns } from '../../../actions/AttackPattern'; +import PlatformIcon from '../../../components/PlatformIcon'; const useStyles = makeStyles(() => ({ itemHead: { @@ -27,6 +29,10 @@ const useStyles = makeStyles(() => ({ paddingLeft: 10, height: 50, }, + bodyItems: { + display: 'flex', + alignItems: 'center', + }, bodyItem: { fontSize: 13, float: 'left', @@ -41,7 +47,7 @@ const useStyles = makeStyles(() => ({ float: 'left', textTransform: 'uppercase', borderRadius: 4, - marginRight: 10, + width: 150, }, })); @@ -53,6 +59,10 @@ const inlineStyles = { payload_name: { width: '20%', }, + payload_platforms: { + width: '10%', + cursor: 'default', + }, payload_description: { width: '20%', }, @@ -60,10 +70,10 @@ const inlineStyles = { width: '20%', }, payload_created_at: { - width: '12%', + width: '10%', }, payload_updated_at: { - width: '12%', + width: '10%', }, }; @@ -72,11 +82,14 @@ const Payloads = () => { const classes = useStyles(); const dispatch = useDispatch(); const { t, nsdt } = useFormatter(); - const { tagsMap } = useHelper((helper) => ({ + const { tagsMap, attackPatternsMap, killChainPhasesMap } = useHelper((helper) => ({ + attackPatternsMap: helper.getAttackPatternsMap(), + killChainPhasesMap: helper.getKillChainPhasesMap(), tagsMap: helper.getTagsMap(), })); useDataLoader(() => { dispatch(fetchTags()); + dispatch(fetchAttackPatterns()); dispatch(fetchKillChainPhases()); }); @@ -84,6 +97,7 @@ const Payloads = () => { const headers = [ { field: 'payload_type', label: 'Type', isSortable: false }, { field: 'payload_name', label: 'Name', isSortable: true }, + { field: 'payload_platforms', label: 'Platforms', isSortable: true }, { field: 'payload_description', label: 'Description', isSortable: true }, { field: 'payload_tags', label: 'Tags', isSortable: true }, { field: 'payload_created_at', label: 'Created', isSortable: true }, @@ -158,7 +172,7 @@ const Payloads = () => { +
{ @@ -177,6 +190,14 @@ const Payloads = () => { > {payload.payload_name}
+
+ {payload.payload_platforms?.map( + (platform) => , + )} +
{ > {nsdt(payload.payload_updated_at)}
- +
} /> setPayloads(payloads.map((a) => (a.payload_id !== result.payload_id ? a : result)))} onDelete={(result) => setPayloads(payloads.filter((a) => (a.payload_id !== result)))} diff --git a/openbas-front/src/admin/components/settings/attack_patterns/AttackPatternPopover.js b/openbas-front/src/admin/components/settings/attack_patterns/AttackPatternPopover.js index dd58bb50b8..cfa701c9bc 100644 --- a/openbas-front/src/admin/components/settings/attack_patterns/AttackPatternPopover.js +++ b/openbas-front/src/admin/components/settings/attack_patterns/AttackPatternPopover.js @@ -6,7 +6,7 @@ import { MoreVert } from '@mui/icons-material'; import { deleteAttackPattern, updateAttackPattern } from '../../../../actions/AttackPattern'; import AttackPatternForm from './AttackPatternForm'; import { useFormatter } from '../../../../components/i18n'; -import { killChainPhasesOptions } from '../../../../utils/Option'; +import { killChainPhaseOptions } from '../../../../utils/Option'; import Transition from '../../../../components/common/Transition'; import Drawer from '../../../../components/common/Drawer'; @@ -54,7 +54,7 @@ const AttackPatternPopover = ({ attackPattern, killChainPhasesMap, onUpdate, onD ); handleCloseDelete(); }; - const attackPatternKillChainPhases = killChainPhasesOptions(attackPattern.attack_pattern_kill_chain_phases, killChainPhasesMap); + const attackPatternKillChainPhases = killChainPhaseOptions(attackPattern.attack_pattern_kill_chain_phases, killChainPhasesMap); const initialValues = R.pipe( R.pick([ 'attack_pattern_external_id', diff --git a/openbas-front/src/components/ItemBoolean.js b/openbas-front/src/components/ItemBoolean.js index e75f776219..1faeb47453 100644 --- a/openbas-front/src/components/ItemBoolean.js +++ b/openbas-front/src/components/ItemBoolean.js @@ -31,7 +31,7 @@ const styles = () => ({ float: 'left', textTransform: 'uppercase', borderRadius: 4, - width: 100, + width: 140, }, }); diff --git a/openbas-front/src/components/ItemResult.tsx b/openbas-front/src/components/ItemResult.tsx index 77b463e1ec..eb35ad48c3 100644 --- a/openbas-front/src/components/ItemResult.tsx +++ b/openbas-front/src/components/ItemResult.tsx @@ -65,12 +65,14 @@ interface ItemStatusProps { const computeStatusStyle = (status: string | undefined | null) => { switch (status) { case 'FAILED': + case 'Failed': case 'Not Prevented': case 'Not Detected': return inlineStyles.red; + case 'SUCCESS': + case 'Success': case 'Prevented': case 'Detected': - case 'SUCCESS': return inlineStyles.green; default: return inlineStyles.blueGrey; diff --git a/openbas-front/src/components/ItemTags.js b/openbas-front/src/components/ItemTags.js index 883d0bbd81..5ebb3ce295 100644 --- a/openbas-front/src/components/ItemTags.js +++ b/openbas-front/src/components/ItemTags.js @@ -2,7 +2,7 @@ import React from 'react'; import * as PropTypes from 'prop-types'; import * as R from 'ramda'; import { makeStyles, useTheme } from '@mui/styles'; -import { Chip, Slide } from '@mui/material'; +import { Chip, Slide, Tooltip } from '@mui/material'; import { hexToRGB } from '../utils/Colors'; import { useFormatter } from './i18n'; import { useHelper } from '../store'; @@ -67,17 +67,18 @@ const ItemTags = (props) => { {orderedTags.length > 0 ? ( R.map( (tag) => ( - + + + ), R.take(limit, orderedTags), ) diff --git a/openbas-front/src/components/PlatformField.js b/openbas-front/src/components/PlatformField.js new file mode 100644 index 0000000000..45b73c18a0 --- /dev/null +++ b/openbas-front/src/components/PlatformField.js @@ -0,0 +1,71 @@ +import React, { Component } from 'react'; +import * as R from 'ramda'; +import { Box } from '@mui/material'; +import { withStyles } from '@mui/styles'; +import Autocomplete from './Autocomplete'; +import inject18n from './i18n'; +import PlatformIcon from './PlatformIcon'; + +const styles = () => ({ + icon: { + paddingTop: 4, + display: 'inline-block', + }, + text: { + display: 'inline-block', + flexGrow: 1, + marginLeft: 10, + }, + autoCompleteIndicator: { + display: 'none', + }, +}); + +class PlatformField extends Component { + render() { + const { + name, + multiple, + classes, + onKeyDown, + style, + label, + placeholder, + disabled, + } = this.props; + const platformsOptions = [ + { id: 'Windows', label: 'Windows' }, + { id: 'Linux', label: 'Linux' }, + { id: 'MacOS', label: 'MacOS' }, + ]; + return ( + ( + +
+ +
+
{option.label}
+
+ )} + classes={{ clearIndicator: classes.autoCompleteIndicator }} + /> + ); + } +} + +export default R.compose( + inject18n, + withStyles(styles), +)(PlatformField); diff --git a/openbas-front/src/public/components/channels/ChannelMicroblogging.js b/openbas-front/src/public/components/channels/ChannelMicroblogging.js index 64e48bb9c9..88950e61cf 100644 --- a/openbas-front/src/public/components/channels/ChannelMicroblogging.js +++ b/openbas-front/src/public/components/channels/ChannelMicroblogging.js @@ -84,7 +84,7 @@ const ChannelMicroblogging = ({ channelReader }) => { {articles.length === 0 && (
- +
)} {articles.map((article) => { diff --git a/openbas-front/src/public/components/channels/ChannelNewspaper.js b/openbas-front/src/public/components/channels/ChannelNewspaper.js index c5cabe3577..c056a1b1e5 100644 --- a/openbas-front/src/public/components/channels/ChannelNewspaper.js +++ b/openbas-front/src/public/components/channels/ChannelNewspaper.js @@ -105,7 +105,7 @@ const ChannelNewspaper = ({ channelReader }) => { {!firstArticle && (
- +
)} diff --git a/openbas-front/src/public/components/channels/ChannelTvChannel.js b/openbas-front/src/public/components/channels/ChannelTvChannel.js index 6a47e3adb4..37b521c106 100644 --- a/openbas-front/src/public/components/channels/ChannelTvChannel.js +++ b/openbas-front/src/public/components/channels/ChannelTvChannel.js @@ -99,7 +99,7 @@ const ChannelTvChannel = ({ channelReader }) => { {!firstArticle && (
- +
)} diff --git a/openbas-front/src/utils/Localization.js b/openbas-front/src/utils/Localization.js index 337c02e3bd..04fd6ebe76 100644 --- a/openbas-front/src/utils/Localization.js +++ b/openbas-front/src/utils/Localization.js @@ -327,14 +327,14 @@ const i18n = { 'Remove Filigran logos': 'Retirer les logos Filigran', Contextual: 'Contextuelle', 'Send email': 'Envoyer le mail', - 'Add documents in this channel pressure': + 'Add documents in this media pressure': 'Ajouter des documents à cette pression médiatique', 'Expect teams to read the article(s)': 'Les équipes doivent lire le(s) article(s)', - 'Add channel pressure': 'Ajouter de la pression médiatique', + 'Add media pressure': 'Ajouter de la pression médiatique', 'Remove from the media pressure': 'Supprimer de la pression médiatique', 'Raw request data': 'Données brutes de la requête', - 'Channel pressure to publish': 'Pression médiatique à publier', + 'Media pressure to publish': 'Pression médiatique à publier', 'Form request data': 'Données de formulaire de la requête', Key: 'Clé', Headers: 'En-têtes', @@ -392,7 +392,6 @@ const i18n = { Controls: 'Contrôles', 'Lessons learned': 'Expérience', Planning: 'Planification', - 'Channel pressure': 'Pression médiatique', Comchecks: 'Vérifications', Dryruns: 'Simulations', 'Messages header': 'En-tête des messages', @@ -442,7 +441,7 @@ const i18n = { Failure: 'Echec', Expired: 'Expiré', Anonymized: 'Anonymisé', - 'No channel pressure entry in this channel yet.': + 'No media pressure entry in this channel yet.': 'Encore aucune entrée de pression médiatique dans ce média.', 'Your communication check is': 'Votre test de communication est', successful: 'un succès', @@ -525,8 +524,8 @@ const i18n = { 'Change logo': 'Changer le logo', 'Scheduled / in use': 'Planifié / utilisé', 'Not used in the context': 'Pas utilisé dans ce contexte', - 'Update the channel pressure': 'Modifier la pression médiatique', - 'Do you want to delete this channel pressure?': + 'Update the media pressure': 'Modifier la pression médiatique', + 'Do you want to delete this media pressure?': 'Souhaitez-vous supprimer cette pression médiatique ?', 'Do you want to delete this challenge?': 'Souhaitez-vous supprimer ce challenge ?', @@ -534,7 +533,7 @@ const i18n = { "Souhaitez-vous supprimer cette catégorie de retour d'expérience ?", 'Update the lessons learned category': "Modifier une catégorie de retour d'expérience", - 'Create a new channel pressure': 'Créer une nouvelle pression médiatique', + 'Create a new media pressure': 'Créer une nouvelle pression médiatique', 'Create a new lessons learned question': "Créer une nouvelle question de retour d'expérience", 'Collaborative lessons learned': "Retour d'expérience collaboratif", @@ -595,7 +594,7 @@ const i18n = { 'Challenges to publish': 'Challenges à publier', 'Add challenge in this inject': 'Ajouter des challenges à ce stimuli', 'Add challenges': 'Ajouter des challenges', - 'Do you want to remove this channel pressure from the inject?': + 'Do you want to remove this media pressure from the inject?': 'Souhaitez-vous retirer cette pression médiatique de ce stimuli ?', 'Do you want to remove this challenge from the inject?': 'Souhaitez-vous retirer ce challenge de ce stimuli ?', diff --git a/openbas-front/src/utils/Option.ts b/openbas-front/src/utils/Option.ts index d6df3f3208..3572b70d1c 100644 --- a/openbas-front/src/utils/Option.ts +++ b/openbas-front/src/utils/Option.ts @@ -34,7 +34,19 @@ export const tagOptions = ( }) as Option, ); -export const attackPatternsOptions = ( +export const platformOptions = ( + platform_ids: string[] | undefined, +) => (platform_ids ?? []) + .map( + (platformId) => { + return { + id: platformId, + label: platformId, + }; + }, + ); + +export const attackPatternOptions = ( attack_pattern_ids: string[] | undefined, attackPatternsMap: Record, killChainPhasesMap: Record, @@ -52,7 +64,7 @@ export const attackPatternsOptions = ( }, ); -export const killChainPhasesOptions = ( +export const killChainPhaseOptions = ( kill_chain_phase_ids: string[] | undefined, killChainPhasesMap: Record, ) => (kill_chain_phase_ids ?? []) diff --git a/openbas-front/src/utils/api-types.d.ts b/openbas-front/src/utils/api-types.d.ts index feabd49ce2..51c6ae407d 100644 --- a/openbas-front/src/utils/api-types.d.ts +++ b/openbas-front/src/utils/api-types.d.ts @@ -1130,6 +1130,7 @@ export interface Injector { injector_external?: boolean; injector_id: string; injector_name: string; + injector_payloads?: boolean; injector_type: string; /** @format date-time */ injector_updated_at?: string; @@ -1159,6 +1160,7 @@ export interface InjectorContract { injector_contract_labels?: Record; injector_contract_manual?: boolean; injector_contract_needs_executor?: boolean; + injector_contract_payload?: Payload; injector_contract_platforms?: string[]; /** @format date-time */ injector_contract_updated_at?: string; @@ -1211,6 +1213,7 @@ export interface InjectorCreateInput { injector_executor_commands?: Record; injector_id: string; injector_name: string; + injector_payloads?: boolean; injector_type: string; } @@ -1226,6 +1229,7 @@ export interface InjectorUpdateInput { injector_executor_clear_commands?: Record; injector_executor_commands?: Record; injector_name: string; + injector_payloads?: boolean; } export type JsonNode = object; @@ -1913,12 +1917,17 @@ export interface Pause { } export interface Payload { - payload_content?: string; + payload_arguments?: PayloadArgument[]; + payload_attack_patterns?: AttackPattern[]; + payload_cleanup_command?: string; + payload_cleanup_executor?: string; /** @format date-time */ payload_created_at?: string; payload_description?: string; payload_id: string; payload_name: string; + payload_platforms?: string[]; + payload_prerequisites?: PayloadPrerequisite[]; /** @uniqueItems true */ payload_tags?: Tag[]; payload_type?: string; @@ -1927,14 +1936,27 @@ export interface Payload { updateAttributes?: object; } +export interface PayloadArgument { + description?: string; + key: string; + type: string; + value: string; +} + export interface PayloadCreateInput { - payload_content: string; payload_description?: string; payload_name: string; payload_tags?: string[]; payload_type: string; } +export interface PayloadPrerequisite { + checkCommand?: string; + description?: string; + executor: string; + getCommand: string; +} + export interface PayloadUpdateInput { payload_content: string; payload_description?: string; diff --git a/openbas-model/src/main/java/io/openbas/database/model/Command.java b/openbas-model/src/main/java/io/openbas/database/model/Command.java new file mode 100644 index 0000000000..56969fff5d --- /dev/null +++ b/openbas-model/src/main/java/io/openbas/database/model/Command.java @@ -0,0 +1,42 @@ +package io.openbas.database.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.openbas.annotation.Queryable; +import io.openbas.database.audit.ModelBaseListener; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +@Entity +@DiscriminatorValue(Command.COMMAND_TYPE) +@EntityListeners(ModelBaseListener.class) +public class Command extends Payload { + + public static final String COMMAND_TYPE = "Command"; + + @Queryable(filterable = true, sortable = true) + @Column(name = "command_executor") + @JsonProperty("command_executor") + @NotNull + private String executor; + + @Queryable(filterable = true, sortable = true) + @Column(name = "command_content") + @JsonProperty("command_content") + @NotNull + private String content; + + public Command() { + + } + + public Command(String id, String type, String name) { + super(id, type, name); + } +} diff --git a/openbas-model/src/main/java/io/openbas/database/model/DnsResolution.java b/openbas-model/src/main/java/io/openbas/database/model/DnsResolution.java new file mode 100644 index 0000000000..5790a3cdb6 --- /dev/null +++ b/openbas-model/src/main/java/io/openbas/database/model/DnsResolution.java @@ -0,0 +1,36 @@ +package io.openbas.database.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.openbas.annotation.Queryable; +import io.openbas.database.audit.ModelBaseListener; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +@Entity +@DiscriminatorValue(DnsResolution.DNS_RESOLUTION_TYPE) +@EntityListeners(ModelBaseListener.class) +public class DnsResolution extends Payload { + + public static final String DNS_RESOLUTION_TYPE = "DnsResolution"; + + @Queryable(filterable = true, sortable = true) + @Column(name = "dns_resolution_hostname") + @JsonProperty("dns_resolution_hostname") + @NotNull + private String hostname; + + public DnsResolution() { + + } + + public DnsResolution(String id, String type, String name) { + super(id, type, name); + } +} diff --git a/openbas-model/src/main/java/io/openbas/database/model/Executable.java b/openbas-model/src/main/java/io/openbas/database/model/Executable.java new file mode 100644 index 0000000000..ef2a79a5d2 --- /dev/null +++ b/openbas-model/src/main/java/io/openbas/database/model/Executable.java @@ -0,0 +1,33 @@ +package io.openbas.database.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.openbas.database.audit.ModelBaseListener; +import io.openbas.helper.MonoIdDeserializer; +import jakarta.persistence.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +@Entity +@DiscriminatorValue(Executable.EXECUTABLE_TYPE) +@EntityListeners(ModelBaseListener.class) +public class Executable extends Payload { + + public static final String EXECUTABLE_TYPE = "Executable"; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "executable_file") + @JsonSerialize(using = MonoIdDeserializer.class) + @JsonProperty("executable_file") + private Document file; + + public Executable() { + + } + + public Executable(String id, String type, String name) { + super(id, type, name); + } +} diff --git a/openbas-model/src/main/java/io/openbas/database/model/FileDrop.java b/openbas-model/src/main/java/io/openbas/database/model/FileDrop.java new file mode 100644 index 0000000000..4e26383978 --- /dev/null +++ b/openbas-model/src/main/java/io/openbas/database/model/FileDrop.java @@ -0,0 +1,33 @@ +package io.openbas.database.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.openbas.database.audit.ModelBaseListener; +import io.openbas.helper.MonoIdDeserializer; +import jakarta.persistence.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +@Entity +@DiscriminatorValue(FileDrop.FILE_DROP_TYPE) +@EntityListeners(ModelBaseListener.class) +public class FileDrop extends Payload { + + public static final String FILE_DROP_TYPE = "FileDrop"; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "file_drop_file") + @JsonSerialize(using = MonoIdDeserializer.class) + @JsonProperty("file_drop_file") + private Document file; + + public FileDrop() { + + } + + public FileDrop(String id, String type, String name) { + super(id, type, name); + } +} diff --git a/openbas-model/src/main/java/io/openbas/database/model/Injector.java b/openbas-model/src/main/java/io/openbas/database/model/Injector.java index d36c467ee0..6a4bbc90a9 100644 --- a/openbas-model/src/main/java/io/openbas/database/model/Injector.java +++ b/openbas-model/src/main/java/io/openbas/database/model/Injector.java @@ -69,6 +69,11 @@ public class Injector implements Base { @Type(PostgreSQLHStoreType.class) private Map executorClearCommands = new HashMap<>(); + @Getter + @Column(name = "injector_payloads") + @JsonProperty("injector_payloads") + private boolean payloads = false; + @Getter @Column(name = "injector_created_at") @JsonProperty("injector_created_at") diff --git a/openbas-model/src/main/java/io/openbas/database/model/InjectorContract.java b/openbas-model/src/main/java/io/openbas/database/model/InjectorContract.java index 58f2f08f25..a6af549d91 100644 --- a/openbas-model/src/main/java/io/openbas/database/model/InjectorContract.java +++ b/openbas-model/src/main/java/io/openbas/database/model/InjectorContract.java @@ -11,6 +11,7 @@ import io.openbas.database.converter.ContentConverter; import io.openbas.helper.MonoIdDeserializer; import io.openbas.helper.MultiIdListDeserializer; +import io.openbas.helper.MultiModelDeserializer; import jakarta.persistence.*; import jakarta.validation.constraints.NotBlank; import lombok.Getter; @@ -67,6 +68,11 @@ public class InjectorContract implements Base { @JsonProperty("injector_contract_platforms") private String[] platforms = new String[0]; + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "injector_contract_payload") + @JsonProperty("injector_contract_payload") + private Payload payload; + @Column(name = "injector_contract_created_at") @JsonProperty("injector_contract_created_at") private Instant createdAt = now(); diff --git a/openbas-model/src/main/java/io/openbas/database/model/NetworkTraffic.java b/openbas-model/src/main/java/io/openbas/database/model/NetworkTraffic.java new file mode 100644 index 0000000000..33579d9635 --- /dev/null +++ b/openbas-model/src/main/java/io/openbas/database/model/NetworkTraffic.java @@ -0,0 +1,36 @@ +package io.openbas.database.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.openbas.annotation.Queryable; +import io.openbas.database.audit.ModelBaseListener; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +@Entity +@DiscriminatorValue(NetworkTraffic.NETWORK_TRAFFIC_TYPE) +@EntityListeners(ModelBaseListener.class) +public class NetworkTraffic extends Payload { + + public static final String NETWORK_TRAFFIC_TYPE = "NetworkTraffic"; + + @Queryable(filterable = true, sortable = true) + @Column(name = "network_traffic_ip") + @JsonProperty("network_traffic_ip") + @NotNull + private String ip; + + public NetworkTraffic() { + + } + + public NetworkTraffic(String id, String type, String name) { + super(id, type, name); + } +} 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 508920759c..c0283bb613 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 @@ -2,23 +2,31 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.hypersistence.utils.hibernate.type.array.StringArrayType; +import io.hypersistence.utils.hibernate.type.json.JsonType; import io.openbas.annotation.Queryable; import io.openbas.database.audit.ModelBaseListener; +import io.openbas.helper.MultiIdListDeserializer; import io.openbas.helper.MultiIdSetDeserializer; import jakarta.persistence.*; import jakarta.validation.constraints.NotBlank; import lombok.Data; +import lombok.Setter; +import org.hibernate.annotations.Type; import org.hibernate.annotations.UuidGenerator; import java.time.Instant; -import java.util.HashSet; -import java.util.Set; +import java.util.*; +import static jakarta.persistence.DiscriminatorType.STRING; import static java.time.Instant.now; +import static lombok.AccessLevel.NONE; @Data @Entity @Table(name = "payloads") +@Inheritance(strategy = InheritanceType.SINGLE_TABLE) +@DiscriminatorColumn(name = "payload_type", discriminatorType = STRING) @EntityListeners(ModelBaseListener.class) public class Payload implements Base { @@ -30,9 +38,9 @@ public class Payload implements Base { @NotBlank private String id; - @Queryable(searchable = true, filterable = true, sortable = true) - @Column(name = "payload_type") + @Column(name = "payload_type", insertable = false, updatable = false) @JsonProperty("payload_type") + @Setter(NONE) private String type; @Queryable(searchable = true, sortable = true) @@ -45,9 +53,42 @@ public class Payload implements Base { @JsonProperty("payload_description") private String description; - @Column(name = "payload_content") - @JsonProperty("payload_content") - private String content; + @Type(StringArrayType.class) + @Column(name = "payload_platforms", columnDefinition = "text[]") + @JsonProperty("payload_platforms") + private String[] platforms = new String[0]; + + @Setter + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "payloads_attack_patterns", + joinColumns = @JoinColumn(name = "payload_id"), + inverseJoinColumns = @JoinColumn(name = "attack_pattern_id")) + @JsonSerialize(using = MultiIdListDeserializer.class) + @JsonProperty("payload_attack_patterns") + @Queryable(searchable = true, filterable = true, property = "externalId") + private List attackPatterns = new ArrayList<>(); + + @Setter + @Column(name = "payload_cleanup_executor") + @JsonProperty("payload_cleanup_executor") + private String cleanupExecutor; + + @Setter + @Column(name = "payload_cleanup_command") + @JsonProperty("payload_cleanup_command") + private String cleanupCommand; + + @Setter + @Type(JsonType.class) + @Column(name = "payload_arguments") + @JsonProperty("payload_arguments") + private List arguments = new ArrayList<>(); + + @Setter + @Type(JsonType.class) + @Column(name = "payload_prerequisites") + @JsonProperty("payload_prerequisites") + private List prerequisites = new ArrayList<>(); // -- TAG -- @@ -69,4 +110,19 @@ public class Payload implements Base { @Column(name = "payload_updated_at") @JsonProperty("payload_updated_at") private Instant updatedAt = now(); + + @Override + public int hashCode() { + return Objects.hash(id); + } + + public Payload() { + + } + + public Payload(String id, String type, String name) { + this.name = name; + this.id = id; + this.type = type; + } } diff --git a/openbas-model/src/main/java/io/openbas/database/model/PayloadArgument.java b/openbas-model/src/main/java/io/openbas/database/model/PayloadArgument.java new file mode 100644 index 0000000000..f8f0d2db4c --- /dev/null +++ b/openbas-model/src/main/java/io/openbas/database/model/PayloadArgument.java @@ -0,0 +1,22 @@ +package io.openbas.database.model; + +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class PayloadArgument { + + @NotBlank + private String key; + + private String description; + + @NotBlank + private String value; + + @NotBlank + private String type; + +} diff --git a/openbas-model/src/main/java/io/openbas/database/model/PayloadPrerequisite.java b/openbas-model/src/main/java/io/openbas/database/model/PayloadPrerequisite.java new file mode 100644 index 0000000000..07ec53e2b6 --- /dev/null +++ b/openbas-model/src/main/java/io/openbas/database/model/PayloadPrerequisite.java @@ -0,0 +1,21 @@ +package io.openbas.database.model; + +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class PayloadPrerequisite { + + @NotBlank + private String executor; + + private String description; + + private String checkCommand; + + @NotBlank + private String getCommand; + +} diff --git a/openbas-model/src/main/java/io/openbas/database/repository/InjectorContractRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/InjectorContractRepository.java index a956546318..f1b3412ab8 100644 --- a/openbas-model/src/main/java/io/openbas/database/repository/InjectorContractRepository.java +++ b/openbas-model/src/main/java/io/openbas/database/repository/InjectorContractRepository.java @@ -2,6 +2,7 @@ import io.openbas.database.model.Injector; import io.openbas.database.model.InjectorContract; +import io.openbas.database.model.Payload; import jakarta.validation.constraints.NotNull; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.repository.CrudRepository; @@ -20,4 +21,7 @@ public interface InjectorContractRepository extends @NotNull List findInjectorContractsByInjector(@NotNull Injector injector); + + @NotNull + Optional findByInjectorAndPayload(@NotNull Injector injector, @NotNull Payload payload); } diff --git a/openbas-model/src/main/java/io/openbas/database/repository/InjectorRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/InjectorRepository.java index 9a80ce1e21..f8713be5d7 100644 --- a/openbas-model/src/main/java/io/openbas/database/repository/InjectorRepository.java +++ b/openbas-model/src/main/java/io/openbas/database/repository/InjectorRepository.java @@ -5,6 +5,7 @@ import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; @Repository @@ -15,4 +16,6 @@ public interface InjectorRepository extends CrudRepository { @NotNull Optional findByType(@NotNull String type); + + List findAllByPayloads(@NotNull Boolean payloads); }