Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add obfuscation chunk 2 #2090

Merged
merged 18 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
import io.openbas.injectors.channel.model.ChannelContent;
import io.openbas.rest.exercise.exports.VariableWithValueMixin;
import io.openbas.rest.inject.form.InjectDependencyInput;
import io.openbas.rest.payload.PayloadCreationService;
import io.openbas.rest.payload.form.PayloadCreateInput;
import io.openbas.rest.payload.service.PayloadCreationService;
import io.openbas.service.FileService;
import io.openbas.service.ImportEntry;
import io.openbas.service.ScenarioService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import io.openbas.injector_contract.fields.*;
import io.openbas.injectors.caldera.client.model.Ability;
import io.openbas.injectors.caldera.config.CalderaInjectorConfig;
import io.openbas.injectors.caldera.model.CalderaInjectContent;
import io.openbas.injectors.caldera.model.Obfuscator;
import io.openbas.injectors.caldera.service.CalderaInjectorService;
import jakarta.validation.constraints.NotNull;
Expand Down Expand Up @@ -74,7 +75,11 @@
List<Obfuscator> obfuscators = this.injectorCalderaService.obfuscators();
Map<String, String> obfuscatorChoices =
obfuscators.stream().collect(Collectors.toMap(Obfuscator::getName, Obfuscator::getName));
return selectFieldWithDefault("obfuscator", "Obfuscators", obfuscatorChoices, "base64");
return selectFieldWithDefault(

Check warning on line 78 in openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaContract.java

View check run for this annotation

Codecov / codecov/patch

openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaContract.java#L78

Added line #L78 was not covered by tests
"obfuscator",
"Obfuscators",
obfuscatorChoices,
CalderaInjectContent.getDefaultObfuscator());

Check warning on line 82 in openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaContract.java

View check run for this annotation

Codecov / codecov/patch

openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaContract.java#L82

Added line #L82 was not covered by tests
}

private ContractExpectations expectations() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@
@NotNull final Execution execution, @NotNull final ExecutableInject injection)
throws Exception {
CalderaInjectContent content = contentConvert(injection, CalderaInjectContent.class);
String obfuscator = content.getObfuscator() != null ? content.getObfuscator() : "base64";
String obfuscator =
content.getObfuscator() != null
? content.getObfuscator()
: CalderaInjectContent.getDefaultObfuscator();

Check warning on line 68 in openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaExecutor.java

View check run for this annotation

Codecov / codecov/patch

openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaExecutor.java#L67-L68

Added lines #L67 - L68 were not covered by tests
Inject inject =
this.injectRepository.findById(injection.getInjection().getInject().getId()).orElseThrow();

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -16,4 +17,9 @@

@JsonProperty("expectations")
private List<Expectation> expectations = new ArrayList<>();

@JsonIgnore
public static String getDefaultObfuscator() {
RomuDeuxfois marked this conversation as resolved.
Show resolved Hide resolved
return "plain-text";

Check warning on line 23 in openbas-api/src/main/java/io/openbas/injectors/caldera/model/CalderaInjectContent.java

View check run for this annotation

Codecov / codecov/patch

openbas-api/src/main/java/io/openbas/injectors/caldera/model/CalderaInjectContent.java#L23

Added line #L23 was not covered by tests
}
}
Original file line number Diff line number Diff line change
@@ -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<String, OpenBASObfuscation> obfuscationMap;

@Getter
public static class OpenBASObfuscation {
private final String information;
private final BiFunction<String, String, String> obfuscate;

public OpenBASObfuscation(String information, BiFunction<String, String, String> 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<String, String, String> function) {
if (key == null || function == null) {
throw new IllegalArgumentException("Key and function must not be null.");

Check warning on line 34 in openbas-api/src/main/java/io/openbas/injectors/openbas/util/OpenBASObfuscationMap.java

View check run for this annotation

Codecov / codecov/patch

openbas-api/src/main/java/io/openbas/injectors/openbas/util/OpenBASObfuscationMap.java#L34

Added line #L34 was not covered by tests
}
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);

Check warning on line 44 in openbas-api/src/main/java/io/openbas/injectors/openbas/util/OpenBASObfuscationMap.java

View check run for this annotation

Codecov / codecov/patch

openbas-api/src/main/java/io/openbas/injectors/openbas/util/OpenBASObfuscationMap.java#L44

Added line #L44 was not covered by tests
}

public Map<String, String> getAllObfuscationInfo() {
Map<String, String> keyInfoMap = new HashMap<>();
for (Map.Entry<String, OpenBASObfuscation> 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(

Check warning on line 69 in openbas-api/src/main/java/io/openbas/injectors/openbas/util/OpenBASObfuscationMap.java

View check run for this annotation

Codecov / codecov/patch

openbas-api/src/main/java/io/openbas/injectors/openbas/util/OpenBASObfuscationMap.java#L68-L69

Added lines #L68 - L69 were not covered by tests
"eval \"$(echo %s | base64 --decode)\"",
Base64.getEncoder().encodeToString(command.getBytes()));

Check warning on line 71 in openbas-api/src/main/java/io/openbas/injectors/openbas/util/OpenBASObfuscationMap.java

View check run for this annotation

Codecov / codecov/patch

openbas-api/src/main/java/io/openbas/injectors/openbas/util/OpenBASObfuscationMap.java#L71

Added line #L71 was not covered by tests
}
return obfuscatedCommand;
}

public String getDefaultObfuscator() {
return "plain-text";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package io.openbas.migration;

import java.sql.Connection;
import java.sql.Statement;
import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
import org.springframework.stereotype.Component;

@Component
public class V3_55__Add_obfuscator_inject_contract extends BaseJavaMigration {

@Override
public void migrate(Context context) throws Exception {
Connection connection = context.getConnection();
Statement statement = connection.createStatement();

String addObfuscationQuery =
"UPDATE injectors_contracts "
+ "SET injector_contract_content = JSONB_SET("
+ " injector_contract_content::jsonb,"
+ " '{fields}',"
+ " CASE WHEN NOT EXISTS ("
+ " SELECT 1 FROM jsonb_array_elements(injector_contract_content::jsonb->'fields') AS fields "
+ " WHERE fields->>'key' = 'obfuscator'"
+ " ) THEN "
+ " injector_contract_content::jsonb->'fields' || "
+ " jsonb_build_object("
+ " 'key', 'obfuscator',"
+ " 'cardinality', '1',"
+ " 'defaultValue', jsonb_build_array('plain-text'),"
+ " 'mandatory', false,"
+ " 'mandatoryGroups', null,"
+ " 'label', 'Obfuscator',"
+ " 'readOnly', false,"
+ " 'linkedFields', jsonb_build_array(),"
+ " 'linkedValues', jsonb_build_array(),"
+ " 'type', 'choice',"
+ " 'choices', jsonb_build_array("
+ " jsonb_build_object('value', 'base64', 'label', 'base64', 'information', 'CMD does not support base64 obfuscation'),"
+ " jsonb_build_object('value', 'plain-text', 'label', 'plain-text', 'information', '')"
+ " )"
+ " )"
+ " ELSE "
+ " injector_contract_content::jsonb->'fields'"
+ " END"
+ ") "
+ "WHERE injector_id IN ("
+ " SELECT injector_id FROM injectors WHERE injector_type = 'openbas_implant'"
+ ") "
+ "AND injector_contract_payload IN ("
+ " SELECT payload_id FROM payloads WHERE payload_type = 'Command'"
+ ")";
statement.executeUpdate(addObfuscationQuery);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public class StatusPayloadOutput {
@JsonProperty("payload_arguments")
private List<PayloadArgument> arguments = new ArrayList<>();

@JsonProperty("payload_obfuscator")
private String obfuscator;

@JsonProperty("payload_prerequisites")
private List<PayloadPrerequisite> prerequisites = new ArrayList<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ public Inject injectExecutionCallback(

@GetMapping(INJECT_URI + "/{injectId}/executable-payload")
@Tracing(name = "Get payload ready to be executed", layer = "api", operation = "GET")
public Payload getExecutablePayloadInject(@PathVariable @NotBlank final String injectId) {
public Payload getExecutablePayloadInject(@PathVariable @NotBlank final String injectId)
throws Exception {
return executableInjectService.getExecutablePayloadInject(injectId);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.openbas.database.model.*;
import io.openbas.database.repository.InjectRepository;
import io.openbas.injectors.openbas.model.OpenBASImplantInjectContent;
import io.openbas.injectors.openbas.util.OpenBASObfuscationMap;
import io.openbas.rest.exception.ElementNotFoundException;
import java.util.ArrayList;
import java.util.Base64;
Expand All @@ -19,6 +21,7 @@
public class ExecutableInjectService {

private final InjectRepository injectRepository;
private final InjectService injectService;
private static final Pattern argumentsRegex = Pattern.compile("#\\{([^#{}]+)}");
private static final Pattern cmdVariablesRegex = Pattern.compile("%(\\w+)%");

Expand Down Expand Up @@ -79,25 +82,27 @@ private String processAndEncodeCommand(
List<PayloadArgument> defaultArguments,
ObjectNode injectContent,
String obfuscator) {
OpenBASObfuscationMap obfuscationMap = new OpenBASObfuscationMap();
String computedCommand = replaceArgumentsByValue(command, defaultArguments, injectContent);

if (executor.equals("cmd")) {
computedCommand = replaceCmdVariables(computedCommand);
computedCommand = computedCommand.trim().replace("\n", " & ");
}

if (obfuscator.equals("base64")) {
return Base64.getEncoder().encodeToString(computedCommand.getBytes());
}
computedCommand = obfuscationMap.executeObfuscation(obfuscator, computedCommand, executor);

return computedCommand;
return Base64.getEncoder().encodeToString(computedCommand.getBytes());
}

public Payload getExecutablePayloadInject(String injectId) {
public Payload getExecutablePayloadInject(String injectId) throws Exception {
Inject inject =
this.injectRepository.findById(injectId).orElseThrow(ElementNotFoundException::new);
InjectorContract contract =
inject.getInjectorContract().orElseThrow(ElementNotFoundException::new);
OpenBASImplantInjectContent content =
injectService.convertInjectContent(inject, OpenBASImplantInjectContent.class);
String obfuscator = content.getObfuscator() != null ? content.getObfuscator() : "plain-text";

// prerequisite
contract
Expand All @@ -112,7 +117,7 @@ public Payload getExecutablePayloadInject(String injectId) {
prerequisite.getExecutor(),
contract.getPayload().getArguments(),
inject.getContent(),
"base64"));
obfuscator));
}
if (hasText(prerequisite.getGetCommand())) {
prerequisite.setGetCommand(
Expand All @@ -121,7 +126,7 @@ public Payload getExecutablePayloadInject(String injectId) {
prerequisite.getExecutor(),
contract.getPayload().getArguments(),
inject.getContent(),
"base64"));
obfuscator));
}
});

Expand All @@ -135,7 +140,7 @@ public Payload getExecutablePayloadInject(String injectId) {
contract.getPayload().getCleanupExecutor(),
contract.getPayload().getArguments(),
inject.getContent(),
"base64"));
obfuscator));
}

// Command
Expand All @@ -147,7 +152,7 @@ public Payload getExecutablePayloadInject(String injectId) {
payloadCommand.getExecutor(),
contract.getPayload().getArguments(),
inject.getContent(),
"base64"));
obfuscator));
return payloadCommand;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static java.time.Instant.now;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.openbas.database.model.ExecutionStatus;
import io.openbas.database.model.Inject;
import io.openbas.database.model.InjectDocument;
Expand All @@ -19,6 +20,7 @@
import jakarta.annotation.Resource;
import jakarta.transaction.Transactional;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.Instant;
import java.util.List;
import java.util.stream.Stream;
Expand Down Expand Up @@ -84,6 +86,12 @@ public void cleanInjectsDocExercise(String exerciseId, String documentId) {
injectDocumentRepository.deleteAll(updatedInjects);
}

public <T> T convertInjectContent(@NotNull final Inject inject, @NotNull final Class<T> converter)
throws Exception {
ObjectNode content = inject.getContent();
return this.mapper.treeToValue(content, converter);
}

public void cleanInjectsDocScenario(String scenarioId, String documentId) {
// Delete document from all scenario injects
List<Inject> scenarioInjects =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@

import io.openbas.database.model.*;
import io.openbas.database.repository.*;
import io.openbas.integrations.PayloadService;
import io.openbas.rest.exception.BadRequestException;
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.rest.payload.form.PayloadUpsertInput;
import io.openbas.rest.payload.form.PayloadsDeprecateInput;
import io.openbas.rest.payload.service.PayloadCreationService;
import io.openbas.rest.payload.service.PayloadService;
import io.openbas.utils.pagination.SearchPaginationInput;
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.openbas.rest.payload;
package io.openbas.rest.payload.service;

import static io.openbas.helper.StreamHelper.fromIterable;
import static io.openbas.helper.StreamHelper.iterableToSet;
Expand All @@ -9,7 +9,6 @@
import io.openbas.database.repository.DocumentRepository;
import io.openbas.database.repository.PayloadRepository;
import io.openbas.database.repository.TagRepository;
import io.openbas.integrations.PayloadService;
import io.openbas.rest.payload.form.PayloadCreateInput;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
Expand Down
Loading
Loading