Skip to content

Commit

Permalink
[backend/frontend] Enhance custom payloads (#743)
Browse files Browse the repository at this point in the history
  • Loading branch information
SamuelHassine committed Jun 3, 2024
1 parent 34936c6 commit 211bc8e
Show file tree
Hide file tree
Showing 19 changed files with 435 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ private List<Contract> abilityContracts(@NotNull final ContractConfig contractCo
ContractAssetGroup assetGroupField = assetGroupField("assetgroups", "Asset groups", Multiple);
ContractExpectations expectationsField = expectations();

List<Ability> abilities = this.injectorCalderaService.abilities();
List<Ability> abilities = this.injectorCalderaService.abilities().stream().filter(ability -> !ability.getTactic().equals("openbas")).toList();
// Build contracts
return abilities.stream().map((ability -> {
ContractDef builder = contractBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.openbas.database.repository.InjectRepository;
import io.openbas.execution.ExecutableInject;
import io.openbas.execution.Injector;
import io.openbas.injectors.caldera.client.model.Ability;
import io.openbas.injectors.caldera.client.model.Agent;
import io.openbas.injectors.caldera.client.model.ExploitResult;
import io.openbas.injectors.caldera.config.CalderaInjectorConfig;
Expand Down Expand Up @@ -80,10 +81,17 @@ public ExecutionProcess process(@NotNull final Execution execution, @NotNull fin
if (assets.isEmpty()) {
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();
String contract;
if( inject.getInjectorContract().getPayload() != null ) {
// This is a payload, need to create the ability on the fly

List<Ability> abilities = calderaService.abilities().stream().filter(ability -> ability.getName().equals(inject.getInjectorContract().getPayload().getId())).toList();
if( !abilities.isEmpty() ) {
calderaService.deleteAbility(abilities.getFirst());
}
Ability abilityToExecute = calderaService.createAbility(inject.getInjectorContract().getPayload());
contract = abilityToExecute.getAbility_id();
} else {
contract = inject.getInjectorContract().getId();
}
assets.forEach((asset, aBoolean) -> {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.openbas.database.model.Endpoint;
import io.openbas.database.model.Injector;
import io.openbas.injectors.caldera.client.model.Ability;
import io.openbas.injectors.caldera.client.model.Agent;
import io.openbas.injectors.caldera.client.model.Result;
Expand All @@ -26,6 +27,7 @@
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -53,6 +55,27 @@ public List<Ability> abilities() {
}
}

public Ability createAbility(Map<String, Object> body) {
try {
String jsonResponse = this.post(
this.config.getRestApiV2Url() + ABILITIES_URI,
body
);
return this.objectMapper.readValue(jsonResponse, new TypeReference<>() {
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public void deleteAbility(Ability ability) {
try {
this.delete(this.config.getRestApiV2Url() + ABILITIES_URI + "/" + ability.getAbility_id());
} catch (IOException e) {
throw new RuntimeException(e);
}
}

// -- AGENTS --

private final static String AGENT_URI = "/agents";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package io.openbas.injectors.caldera.service;

import com.fasterxml.jackson.core.type.TypeReference;
import io.openbas.database.model.*;
import io.openbas.injectors.caldera.client.CalderaInjectorClient;
import io.openbas.injectors.caldera.client.model.*;
import io.openbas.injectors.caldera.model.Obfuscator;
import io.openbas.injectors.caldera.model.ResultStatus;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.java.Log;
import org.hibernate.Hibernate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.*;
Expand Down Expand Up @@ -42,6 +46,47 @@ public List<Obfuscator> obfuscators() {
return this.client.obfuscators();
}

public void deleteAbility(Ability ability) {
this.client.deleteAbility(ability);
}

public Ability createAbility(Payload payload) {
List<Map<String, String>> executors = new ArrayList<>();
switch (payload.getType()) {
case "Command":
Command payloadCommand = (Command) Hibernate.unproxy(payload);
Arrays.stream(payloadCommand.getPlatforms()).forEach(platform -> {
Map<String, String> executor = new HashMap<>();
executor.put("platform", platform.equalsIgnoreCase("macos") ? "darwin" : platform.toLowerCase());
executor.put("name", payloadCommand.getExecutor().equals("bash") ? "sh" : payloadCommand.getExecutor());
executor.put("command", payloadCommand.getContent());
executors.add(executor);
});
break;
case "DnsResolution":
DnsResolution payloadDnsResolution = (DnsResolution) Hibernate.unproxy(payload);
Arrays.stream(payloadDnsResolution.getPlatforms()).forEach(platform -> {
Map<String, String> executor = new HashMap<>();
executor.put("platform", platform.equals(Endpoint.PLATFORM_TYPE.MacOS.name()) ? "darwin" : platform.toLowerCase());
executor.put("name", platform.equals(Endpoint.PLATFORM_TYPE.Windows.name()) ? "cmd" : "sh");
executor.put("command", "nslookup " + payloadDnsResolution.getHostname());
executors.add(executor);
});
break;
default:
throw new UnsupportedOperationException("Payload type " + payload.getType() + " is not supported");
}
Map<String, Object> body = new HashMap<>();
body.put("name", payload.getId());
body.put("tactic", "openbas");
body.put("technique_id", "openbas");
body.put("technique_name", "openbas");
body.put("executors", executors);
return this.client.createAbility(body);
}

// -- AGENTS --

public List<Agent> agents() {
try {
return this.client.agents().stream().toList();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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_17__Payloads extends BaseJavaMigration {

@Override
public void migrate(Context context) throws Exception {
Connection connection = context.getConnection();
Statement select = connection.createStatement();
select.execute("ALTER TABLE payloads RENAME COLUMN network_traffic_ip TO network_traffic_ip_src;");
select.execute("ALTER TABLE payloads ADD column network_traffic_ip_dst text;");
select.execute("ALTER TABLE payloads ADD column network_traffic_port_src int;");
select.execute("ALTER TABLE payloads ADD column network_traffic_port_dst int;");
select.execute("ALTER TABLE payloads ADD column network_traffic_protocol varchar(255);");
}
}
76 changes: 69 additions & 7 deletions openbas-api/src/main/java/io/openbas/rest/payload/PayloadApi.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
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.model.*;
import io.openbas.database.repository.AttackPatternRepository;
import io.openbas.database.repository.DocumentRepository;
import io.openbas.database.repository.PayloadRepository;
import io.openbas.database.repository.TagRepository;
import io.openbas.rest.exception.ElementNotFoundException;
Expand All @@ -15,6 +14,7 @@
import jakarta.transaction.Transactional;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import org.hibernate.Hibernate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Expand All @@ -39,6 +39,7 @@ public class PayloadApi extends RestBehavior {
private TagRepository tagRepository;
private PayloadService payloadService;
private AttackPatternRepository attackPatternRepository;
private DocumentRepository documentRepository;

@Autowired
public void setPayloadRepository(PayloadRepository payloadRepository) {
Expand All @@ -60,6 +61,11 @@ public void setAttackPatternRepository(AttackPatternRepository attackPatternRepo
this.attackPatternRepository = attackPatternRepository;
}

@Autowired
public void setDocumentRepository(DocumentRepository documentRepository) {
this.documentRepository = documentRepository;
}

@GetMapping("/api/payloads")
public Iterable<Payload> payloads() {
return payloadRepository.findAll();
Expand Down Expand Up @@ -98,9 +104,35 @@ public Payload createPayload(@Valid @RequestBody PayloadCreateInput input) {
executablePayload.setUpdateAttributes(input);
executablePayload.setAttackPatterns(fromIterable(attackPatternRepository.findAllById(input.getAttackPatternsIds())));
executablePayload.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds())));
executablePayload.setExecutableFile(documentRepository.findById(input.getExecutableFile()).orElseThrow());
executablePayload = payloadRepository.save(executablePayload);
this.payloadService.updateInjectorContractsForPayload(executablePayload);
return executablePayload;
case "FileDrop":
FileDrop fileDropPayload = new FileDrop();
fileDropPayload.setUpdateAttributes(input);
fileDropPayload.setAttackPatterns(fromIterable(attackPatternRepository.findAllById(input.getAttackPatternsIds())));
fileDropPayload.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds())));
fileDropPayload.setFileDropFile(documentRepository.findById(input.getExecutableFile()).orElseThrow());
fileDropPayload = payloadRepository.save(fileDropPayload);
this.payloadService.updateInjectorContractsForPayload(fileDropPayload);
return fileDropPayload;
case "DnsResolution":
DnsResolution dnsResolutionPayload = new DnsResolution();
dnsResolutionPayload.setUpdateAttributes(input);
dnsResolutionPayload.setAttackPatterns(fromIterable(attackPatternRepository.findAllById(input.getAttackPatternsIds())));
dnsResolutionPayload.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds())));
dnsResolutionPayload = payloadRepository.save(dnsResolutionPayload);
this.payloadService.updateInjectorContractsForPayload(dnsResolutionPayload);
return dnsResolutionPayload;
case "NetworkTraffic":
NetworkTraffic networkTrafficPayload = new NetworkTraffic();
networkTrafficPayload.setUpdateAttributes(input);
networkTrafficPayload.setAttackPatterns(fromIterable(attackPatternRepository.findAllById(input.getAttackPatternsIds())));
networkTrafficPayload.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds())));
networkTrafficPayload = payloadRepository.save(networkTrafficPayload);
this.payloadService.updateInjectorContractsForPayload(networkTrafficPayload);
return networkTrafficPayload;
default:
throw new UnsupportedOperationException("Payload type " + input.getType() + " is not supported");
}
Expand All @@ -113,13 +145,43 @@ public Payload updatePayload(
@NotBlank @PathVariable final String payloadId,
@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());
payload = payloadRepository.save(payload);
this.payloadService.updateInjectorContractsForPayload(payload);
return payload;
switch( payload.getType() ) {
case "Command":
Command payloadCommand = (Command) Hibernate.unproxy(payload);
payloadCommand.setUpdateAttributes(input);
payloadCommand = payloadRepository.save(payloadCommand);
this.payloadService.updateInjectorContractsForPayload(payloadCommand);
return payloadCommand;
case "Executable":
Executable payloadExecutable = (Executable) Hibernate.unproxy(payload);
payloadExecutable.setUpdateAttributes(input);
payloadExecutable = payloadRepository.save(payloadExecutable);
this.payloadService.updateInjectorContractsForPayload(payloadExecutable);
return payloadExecutable;
case "FileDrop":
FileDrop payloadFileDrop = (FileDrop) Hibernate.unproxy(payload);
payloadFileDrop.setUpdateAttributes(input);
payloadFileDrop = payloadRepository.save(payloadFileDrop);
this.payloadService.updateInjectorContractsForPayload(payloadFileDrop);
return payloadFileDrop;
case "DnsResolution":
DnsResolution payloadDnsResolution = (DnsResolution) Hibernate.unproxy(payload);
payloadDnsResolution.setUpdateAttributes(input);
payloadDnsResolution = payloadRepository.save(payloadDnsResolution);
this.payloadService.updateInjectorContractsForPayload(payloadDnsResolution);
return payloadDnsResolution;
case "NetworkTraffic":
NetworkTraffic payloadNetworkTraffic = (NetworkTraffic) Hibernate.unproxy(payload);
payloadNetworkTraffic.setUpdateAttributes(input);
payloadNetworkTraffic = payloadRepository.save(payloadNetworkTraffic);
this.payloadService.updateInjectorContractsForPayload(payloadNetworkTraffic);
return payloadNetworkTraffic;
default:
throw new UnsupportedOperationException("Payload type " + payload.getType() + " is not supported");
}
}

@Secured(ROLE_ADMIN)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ public class PayloadCreateInput {
@JsonProperty("command_content")
private String content;

@JsonProperty("executable_file")
private String executableFile;

@JsonProperty("file_drop_file")
private String fileDropFile;

@JsonProperty("dns_resolution_hostname")
private String hostname;

@JsonProperty("payload_arguments")
private List<PayloadArgument> arguments;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ public Ability createSubprocessorAbility(Injector injector) {
}
Map<String, Object> body = new HashMap<>();
body.put("name", "caldera-subprocessor-" + injector.getName());
body.put("tactic", "initial-access");
body.put("technique_id", "T1133");
body.put("technique_name", "External Remote Services");
body.put("tactic", "openbas");
body.put("technique_id", "openbas");
body.put("technique_name", "openbas");
body.put("executors", executors);
String jsonResponse = this.post(
this.config.getRestApiV2Url() + ABILITIES_URI,
Expand Down Expand Up @@ -146,9 +146,9 @@ public Ability createClearAbility(Injector injector) {
}
Map<String, Object> body = new HashMap<>();
body.put("name", "caldera-clear-" + injector.getName());
body.put("tactic", "initial-access");
body.put("technique_id", "T1133");
body.put("technique_name", "External Remote Services");
body.put("tactic", "openbas");
body.put("technique_id", "openbas");
body.put("technique_name", "openbas");
body.put("executors", executors);
String jsonResponse = this.post(
this.config.getRestApiV2Url() + ABILITIES_URI,
Expand Down
41 changes: 39 additions & 2 deletions openbas-front/src/admin/components/payloads/CreatePayload.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { connect } from 'react-redux';
import * as R from 'ramda';
import { Fab, List, ListItemButton, ListItemIcon, ListItemText, Step, StepLabel, Stepper } from '@mui/material';
import { withStyles } from '@mui/styles';
import { Add } from '@mui/icons-material';
import { Console } from 'mdi-material-ui';
import { Add, DnsOutlined } from '@mui/icons-material';
import { ApplicationCogOutline, Console, FileImportOutline, LanConnect } from 'mdi-material-ui';
import { addPayload } from '../../../actions/Payload';
import PayloadForm from './PayloadForm';
import inject18n from '../../../components/i18n';
Expand Down Expand Up @@ -43,6 +43,7 @@ class CreatePayload extends Component {
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)),
R.assoc('executable_file', data.executable_file?.id),
)(data);
return this.props
.addPayload(inputValues)
Expand All @@ -68,6 +69,42 @@ class CreatePayload extends Component {
</ListItemIcon>
<ListItemText primary={t('Command Line')} />
</ListItemButton>
<ListItemButton
divider={true}
onClick={this.handleSelectType.bind(this, 'Executable')}
>
<ListItemIcon color="primary">
<ApplicationCogOutline color="primary" />
</ListItemIcon>
<ListItemText primary={t('Executable')} />
</ListItemButton>
<ListItemButton
divider={true}
onClick={this.handleSelectType.bind(this, 'FileDrop')}
>
<ListItemIcon color="primary">
<FileImportOutline color="primary" />
</ListItemIcon>
<ListItemText primary={t('File Drop')} />
</ListItemButton>
<ListItemButton
divider={true}
onClick={this.handleSelectType.bind(this, 'DnsResolution')}
>
<ListItemIcon color="primary">
<DnsOutlined color="primary" />
</ListItemIcon>
<ListItemText primary={t('DNS Resolution')} />
</ListItemButton>
<ListItemButton
divider={true}
onClick={this.handleSelectType.bind(this, 'NetworkTraffic')}
>
<ListItemIcon color="primary">
<LanConnect color="primary" />
</ListItemIcon>
<ListItemText primary={t('Network Traffic')} />
</ListItemButton>
</List>
);
}
Expand Down
Loading

0 comments on commit 211bc8e

Please sign in to comment.