diff --git a/openbas-api/src/main/java/io/openbas/injectors/caldera/model/CalderaInjectContent.java b/openbas-api/src/main/java/io/openbas/injectors/caldera/model/CalderaInjectContent.java index 196db38722..c0eed1d449 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/caldera/model/CalderaInjectContent.java +++ b/openbas-api/src/main/java/io/openbas/injectors/caldera/model/CalderaInjectContent.java @@ -1,5 +1,6 @@ package io.openbas.injectors.caldera.model; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import io.openbas.model.inject.form.Expectation; import java.util.ArrayList; @@ -17,6 +18,7 @@ public class CalderaInjectContent { @JsonProperty("expectations") private List expectations = new ArrayList<>(); + @JsonIgnore public static String getDefaultObfuscator() { return "plain-text"; } diff --git a/openbas-api/src/main/java/io/openbas/injectors/openbas/model/OpenBASImplantInjectContent.java b/openbas-api/src/main/java/io/openbas/injectors/openbas/model/OpenBASImplantInjectContent.java index 0d241434d1..04f620c87d 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/openbas/model/OpenBASImplantInjectContent.java +++ b/openbas-api/src/main/java/io/openbas/injectors/openbas/model/OpenBASImplantInjectContent.java @@ -3,9 +3,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.openbas.model.inject.form.Expectation; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import lombok.Getter; import lombok.Setter; @@ -18,15 +16,4 @@ public class OpenBASImplantInjectContent { @JsonProperty("expectations") private List expectations = new ArrayList<>(); - - public static String getDefaultObfuscator() { - return "plain-text"; - } - - public static Map getObfuscatorState() { - Map obfuscatorMap = new HashMap<>(); - obfuscatorMap.put("base64", "CMD does not support base64 obfuscation"); - obfuscatorMap.put("plain-text", ""); - return obfuscatorMap; - } } diff --git a/openbas-api/src/main/java/io/openbas/injectors/openbas/util/OpenBASObfuscationMap.java b/openbas-api/src/main/java/io/openbas/injectors/openbas/util/OpenBASObfuscationMap.java new file mode 100644 index 0000000000..b0b2aadbae --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/injectors/openbas/util/OpenBASObfuscationMap.java @@ -0,0 +1,79 @@ +package io.openbas.injectors.openbas.util; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiFunction; +import lombok.Getter; + +public class OpenBASObfuscationMap { + private final Map obfuscationMap; + + @Getter + public static class OpenBASObfuscation { + private final String information; + private final BiFunction obfuscate; + + public OpenBASObfuscation(String information, BiFunction obfuscate) { + this.information = information; + this.obfuscate = obfuscate; + } + } + + public OpenBASObfuscationMap() { + this.obfuscationMap = new HashMap<>(); + this.registerObfuscation("plain-text", "", this::obfuscatePlainText); + this.registerObfuscation( + "base64", "CMD does not support base64 obfuscation", this::obfuscateBase64); + } + + public void registerObfuscation( + String key, String information, BiFunction function) { + if (key == null || function == null) { + throw new IllegalArgumentException("Key and function must not be null."); + } + obfuscationMap.put(key, new OpenBASObfuscation(information, function)); + } + + public String executeObfuscation(String key, String command, String executor) { + OpenBASObfuscation obfuscation = obfuscationMap.get(key); + if (obfuscation != null) { + return obfuscation.getObfuscate().apply(command, executor); + } + throw new IllegalArgumentException("No obfuscation found for key: " + key); + } + + public Map getAllObfuscationInfo() { + Map keyInfoMap = new HashMap<>(); + for (Map.Entry entry : obfuscationMap.entrySet()) { + keyInfoMap.put(entry.getKey(), entry.getValue().getInformation()); + } + return keyInfoMap; + } + + private String obfuscatePlainText(String command, String executor) { + return command; + } + + private String obfuscateBase64(String command, String executor) { + String obfuscatedCommand = command; + + if (executor.equals("psh") || executor.equals("cmd")) { + byte[] utf16Bytes = command.getBytes(StandardCharsets.UTF_16LE); + String base64 = Base64.getEncoder().encodeToString(utf16Bytes); + obfuscatedCommand = String.format("powershell -Enc %s", base64); + + } else if (executor.equals("bash") || executor.equals("sh")) { + obfuscatedCommand = + String.format( + "eval \"$(echo %s | base64 --decode)\"", + Base64.getEncoder().encodeToString(command.getBytes())); + } + return obfuscatedCommand; + } + + public String getDefaultObfuscator() { + return "plain-text"; + } +} diff --git a/openbas-api/src/main/java/io/openbas/rest/inject/service/ExecutableInjectService.java b/openbas-api/src/main/java/io/openbas/rest/inject/service/ExecutableInjectService.java index 5d697b857c..ac4cbb5e9f 100644 --- a/openbas-api/src/main/java/io/openbas/rest/inject/service/ExecutableInjectService.java +++ b/openbas-api/src/main/java/io/openbas/rest/inject/service/ExecutableInjectService.java @@ -5,9 +5,9 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import io.openbas.database.model.*; import io.openbas.database.repository.InjectRepository; +import io.openbas.injectors.openbas.util.OpenBASObfuscationMap; import io.openbas.injectors.openbas.model.OpenBASImplantInjectContent; import io.openbas.rest.exception.ElementNotFoundException; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; import java.util.List; @@ -76,29 +76,13 @@ public static String replaceCmdVariables(String cmd) { return result.toString(); } - private String obfuscateCommandBase64(String command, String executor) { - String obfuscatedCommand = command; - - if (executor.equals("psh") || executor.equals("cmd")) { - byte[] utf16Bytes = command.getBytes(StandardCharsets.UTF_16LE); - String base64 = Base64.getEncoder().encodeToString(utf16Bytes); - obfuscatedCommand = String.format("powershell -Enc %s", base64); - - } else if (executor.equals("bash") || executor.equals("sh")) { - obfuscatedCommand = - String.format( - "eval \"$(echo %s | base64 --decode)\"", - Base64.getEncoder().encodeToString(command.getBytes())); - } - return obfuscatedCommand; - } - private String processAndEncodeCommand( String command, String executor, List defaultArguments, ObjectNode injectContent, String obfuscator) { + OpenBASObfuscationMap obfuscationMap = new OpenBASObfuscationMap(); String computedCommand = replaceArgumentsByValue(command, defaultArguments, injectContent); if (executor.equals("cmd")) { @@ -106,9 +90,7 @@ private String processAndEncodeCommand( computedCommand = computedCommand.trim().replace("\n", " & "); } - if (obfuscator.equals("base64")) { - computedCommand = obfuscateCommandBase64(computedCommand, executor); - } + computedCommand = obfuscationMap.executeObfuscation(obfuscator, computedCommand, executor); return Base64.getEncoder().encodeToString(computedCommand.getBytes()); } diff --git a/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadService.java b/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadService.java index 5876d05d92..f797807531 100644 --- a/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadService.java +++ b/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadService.java @@ -23,11 +23,8 @@ 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.injector_contract.fields.ContractSelect; -import io.openbas.injectors.openbas.model.OpenBASImplantInjectContent; +import io.openbas.injector_contract.fields.*; +import io.openbas.injectors.openbas.util.OpenBASObfuscationMap; import io.openbas.utils.StringUtils; import jakarta.annotation.Resource; import jakarta.validation.constraints.NotBlank; @@ -110,14 +107,11 @@ private void updateInjectorContract(Injector injector, Payload payload) { } } - private ContractSelect obfuscatorField() { - Map obfuscatorState = OpenBASImplantInjectContent.getObfuscatorState(); - - return ContractSelect.selectFieldWithChoiceInformations( - "obfuscator", - "Obfuscators", - obfuscatorState, - OpenBASImplantInjectContent.getDefaultObfuscator()); + private ContractChoiceInformation obfuscatorField() { + OpenBASObfuscationMap obfuscationMap = new OpenBASObfuscationMap(); + Map obfuscationInfo = obfuscationMap.getAllObfuscationInfo(); + return ContractChoiceInformation.choiceInformationField( + "obfuscator", "Obfuscators", obfuscationInfo, obfuscationMap.getDefaultObfuscator()); } private Contract buildContract( @@ -139,8 +133,8 @@ private Contract buildContract( ContractDef builder = contractBuilder(); builder.mandatoryGroup(assetField, assetGroupField); - if (injector.getType().equals("openbas_implant") && payload.getType().equals("Command")) { - ContractSelect obfuscatorField = obfuscatorField(); + if (payload.getType().equals("Command")) { + ContractChoiceInformation obfuscatorField = obfuscatorField(); builder.optional(obfuscatorField); } diff --git a/openbas-framework/src/main/java/io/openbas/injector_contract/ContractType.java b/openbas-framework/src/main/java/io/openbas/injector_contract/ContractType.java index 64491f5b67..c928af5d18 100644 --- a/openbas-framework/src/main/java/io/openbas/injector_contract/ContractType.java +++ b/openbas-framework/src/main/java/io/openbas/injector_contract/ContractType.java @@ -15,6 +15,8 @@ public enum ContractType { Textarea, @JsonProperty("select") Select, + @JsonProperty("choice") + Choice, @JsonProperty("article") Article, @JsonProperty("challenge") diff --git a/openbas-framework/src/main/java/io/openbas/injector_contract/fields/ContractChoiceInformation.java b/openbas-framework/src/main/java/io/openbas/injector_contract/fields/ContractChoiceInformation.java new file mode 100644 index 0000000000..e59eab2967 --- /dev/null +++ b/openbas-framework/src/main/java/io/openbas/injector_contract/fields/ContractChoiceInformation.java @@ -0,0 +1,52 @@ +package io.openbas.injector_contract.fields; + +import io.openbas.injector_contract.ContractCardinality; +import io.openbas.injector_contract.ContractType; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class ContractChoiceInformation extends ContractCardinalityElement { + private List choices = List.of(); + + public ContractChoiceInformation(String key, String label, ContractCardinality cardinality) { + super(key, label, cardinality); + } + + @Getter + public static class ChoiceItem { + private final String label; + private final String value; + private final String information; + + public ChoiceItem(String label, String value, String information) { + this.information = information; + this.label = label; + this.value = value; + } + } + + public static ContractChoiceInformation choiceInformationField( + String key, String label, Map choiceInformations, String def) { + ContractChoiceInformation contractChoice = + new ContractChoiceInformation(key, label, ContractCardinality.One); + + ArrayList choiceItems = new ArrayList<>(); + for (Map.Entry entry : choiceInformations.entrySet()) { + choiceItems.add(new ChoiceItem(entry.getKey(), entry.getKey(), entry.getValue())); + } + + contractChoice.setChoices(choiceItems); + contractChoice.setDefaultValue(List.of(def)); + return contractChoice; + } + + @Override + public ContractType getType() { + return ContractType.Choice; + } +} diff --git a/openbas-framework/src/main/java/io/openbas/injector_contract/fields/ContractSelect.java b/openbas-framework/src/main/java/io/openbas/injector_contract/fields/ContractSelect.java index 872a5a69e3..6e97292a6f 100644 --- a/openbas-framework/src/main/java/io/openbas/injector_contract/fields/ContractSelect.java +++ b/openbas-framework/src/main/java/io/openbas/injector_contract/fields/ContractSelect.java @@ -5,7 +5,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import lombok.Getter; import lombok.Setter; @@ -28,19 +27,6 @@ public static ContractSelect selectFieldWithDefault( return contractSelect; } - public static ContractSelect selectFieldWithChoiceInformations( - String key, String label, Map choiceInformations, String def) { - ContractSelect contractSelect = new ContractSelect(key, label, ContractCardinality.One); - - Map choices = - choiceInformations.keySet().stream().collect(Collectors.toMap(k -> k, k -> k)); - - contractSelect.setChoices(choices); - contractSelect.setDefaultValue(List.of(def)); - contractSelect.setChoiceInformations(choiceInformations); - return contractSelect; - } - @Override public ContractType getType() { return ContractType.Select; diff --git a/openbas-front/src/admin/components/common/injects/form/InjectContentFieldComponent.tsx b/openbas-front/src/admin/components/common/injects/form/InjectContentFieldComponent.tsx index a84cef6f89..50f52e5ab4 100644 --- a/openbas-front/src/admin/components/common/injects/form/InjectContentFieldComponent.tsx +++ b/openbas-front/src/admin/components/common/injects/form/InjectContentFieldComponent.tsx @@ -11,17 +11,21 @@ import TextField from '../../../../../components/fields/TextField'; import { useFormatter } from '../../../../../components/i18n'; import { Document } from '../../../../../utils/api-types'; +type choiceItem = { + label: string; + value: string; + information: string; +}; type InjectField = { key: string; richText: boolean; cardinality?: string; mandatory: boolean; - choiceInformations: Record; defaultValue: string | string[]; - choices: Record> | Record ; + choices: Record | choiceItem[]; label: string; contractAttachment: { key: string; label: string }[]; - type: 'textarea' | 'text' | 'select' | 'number' | 'tuple' | 'checkbox' | 'dependency-select'; + type: 'textarea' | 'text' | 'select' | 'number' | 'tuple' | 'checkbox' | 'dependency-select' | 'choice'; dependencyField: string; }; @@ -53,11 +57,12 @@ const InjectContentFieldComponent = ({ } useEffect(() => { - if (!selectedValue && field.choiceInformations) { - setInformationToDisplay(field.choiceInformations[values[field.key]] ?? field.choiceInformations[field.defaultValue[0]]); + const findInformation = (value: string) => (field.choices as choiceItem[]).find(c => c.value === value)?.information || ''; + if (!selectedValue && field.type === 'choice') { + setInformationToDisplay(findInformation(values[field.key]) ?? findInformation(field.defaultValue[0])); } - if (selectedValue && field.choiceInformations) { - setInformationToDisplay(field.choiceInformations[selectedValue]); + if (selectedValue && field.type === 'choice') { + setInformationToDisplay(findInformation(selectedValue)); } if (fieldType === 'checkbox' || fieldType === 'select') { onSelectOrCheckboxFieldChange(); @@ -234,43 +239,69 @@ const InjectContentFieldComponent = ({ ); } case 'select': { - const renderMultipleValuesFct = (v: string[]) => v.map(a => field.choices[a]).join(', '); - const renderSingleValueFct = (v: string) => (t(field.choices[v] || 'Unknown')); + const choices = field.choices as Record; + const renderMultipleValuesFct = (v: string[]) => v.map(a => (choices[a])).join(', '); + const renderSingleValueFct = (v: string) => (t(choices[v] || 'Unknown')); const renderValueFct = (v: string | string[]) => Array.isArray(v) ? renderMultipleValuesFct(v) : renderSingleValueFct(v); return ( - <> - renderValueFct(v)} - name={field.key} - fullWidth={true} - style={{ marginTop: 20 }} - control={control} - disabled={readOnly} - defaultValue={field.defaultValue} - > - {Object.entries(field.choices) - .sort((a, b) => a[1].localeCompare(b[1])) - .map(([k, v]) => ( - - - {t(v || 'Unknown')} - - - ))} - - + renderValueFct(v)} + name={field.key} + fullWidth={true} + style={{ marginTop: 20 }} + control={control} + disabled={readOnly} + defaultValue={field.defaultValue} + > + {Object.entries(choices) + .sort((a, b) => a[1].localeCompare(b[1])) + .map(([k, v]) => ( + + + {t(v || 'Unknown')} + + + ))} + + ); + } + case 'choice': { + const choices = field.choices as choiceItem[]; + const renderMultipleValuesFct = (v: string[]) => choices.filter(c => v.includes(c.value)).map(c => c.label).join(', '); + const renderSingleValueFct = (v: string) => (t(choices.find(c => c.value === v)?.label || 'Unknown')); + const renderValueFct = (v: string | string[]) => + Array.isArray(v) ? renderMultipleValuesFct(v) : renderSingleValueFct(v); + return ( + renderValueFct(v)} + name={field.key} + fullWidth={true} + style={{ marginTop: 20 }} + control={control} + disabled={readOnly} + defaultValue={field.defaultValue} + > + {choices.map(choice => ( + {choice.label} + ))} + + ); } case 'dependency-select': { const depValue = values[field.dependencyField]; - const choices = field.choices[depValue] as Record ?? {}; + const choices = ((field.choices as Record)[depValue]) as unknown as Record ?? {}; const renderMultipleValuesFct = (v: string[]) => v.map(a => choices[a]).join(', '); const renderSingleValueFct = (v: string) => (t(choices[v] || 'Unknown')); const renderValueFct = (v: string | string[]) => diff --git a/openbas-front/src/utils/Localization.js b/openbas-front/src/utils/Localization.js index ff238214f6..2e4c1f3898 100644 --- a/openbas-front/src/utils/Localization.js +++ b/openbas-front/src/utils/Localization.js @@ -1699,6 +1699,9 @@ const i18n = { '你想要删除这个文档么?', 'Update the document': '更新文档', 'Inject data': '注入数据', + 'Obfuscator': '混淆器', + 'Obfuscators': '混淆器', + 'CMD does not support base64 obfuscation': 'CMD不支持base64混淆', 'Update teams': '编辑团队', 'Add target teams in this inject': '在这个注入里添加目标团队',