Skip to content

Commit 211bc8e

Browse files
committed
[backend/frontend] Enhance custom payloads (#743)
1 parent 34936c6 commit 211bc8e

File tree

19 files changed

+435
-30
lines changed

19 files changed

+435
-30
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ private List<Contract> abilityContracts(@NotNull final ContractConfig contractCo
103103
ContractAssetGroup assetGroupField = assetGroupField("assetgroups", "Asset groups", Multiple);
104104
ContractExpectations expectationsField = expectations();
105105

106-
List<Ability> abilities = this.injectorCalderaService.abilities();
106+
List<Ability> abilities = this.injectorCalderaService.abilities().stream().filter(ability -> !ability.getTactic().equals("openbas")).toList();
107107
// Build contracts
108108
return abilities.stream().map((ability -> {
109109
ContractDef builder = contractBuilder();

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.openbas.database.repository.InjectRepository;
1010
import io.openbas.execution.ExecutableInject;
1111
import io.openbas.execution.Injector;
12+
import io.openbas.injectors.caldera.client.model.Ability;
1213
import io.openbas.injectors.caldera.client.model.Agent;
1314
import io.openbas.injectors.caldera.client.model.ExploitResult;
1415
import io.openbas.injectors.caldera.config.CalderaInjectorConfig;
@@ -80,10 +81,17 @@ public ExecutionProcess process(@NotNull final Execution execution, @NotNull fin
8081
if (assets.isEmpty()) {
8182
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)"));
8283
}
83-
String contract = inject.getInjectorContract().getId();
84+
String contract;
8485
if( inject.getInjectorContract().getPayload() != null ) {
8586
// This is a payload, need to create the ability on the fly
86-
87+
List<Ability> abilities = calderaService.abilities().stream().filter(ability -> ability.getName().equals(inject.getInjectorContract().getPayload().getId())).toList();
88+
if( !abilities.isEmpty() ) {
89+
calderaService.deleteAbility(abilities.getFirst());
90+
}
91+
Ability abilityToExecute = calderaService.createAbility(inject.getInjectorContract().getPayload());
92+
contract = abilityToExecute.getAbility_id();
93+
} else {
94+
contract = inject.getInjectorContract().getId();
8795
}
8896
assets.forEach((asset, aBoolean) -> {
8997
try {

openbas-api/src/main/java/io/openbas/injectors/caldera/client/CalderaInjectorClient.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.fasterxml.jackson.core.type.TypeReference;
55
import com.fasterxml.jackson.databind.ObjectMapper;
66
import io.openbas.database.model.Endpoint;
7+
import io.openbas.database.model.Injector;
78
import io.openbas.injectors.caldera.client.model.Ability;
89
import io.openbas.injectors.caldera.client.model.Agent;
910
import io.openbas.injectors.caldera.client.model.Result;
@@ -26,6 +27,7 @@
2627
import org.springframework.util.StringUtils;
2728

2829
import java.io.IOException;
30+
import java.util.ArrayList;
2931
import java.util.HashMap;
3032
import java.util.List;
3133
import java.util.Map;
@@ -53,6 +55,27 @@ public List<Ability> abilities() {
5355
}
5456
}
5557

58+
public Ability createAbility(Map<String, Object> body) {
59+
try {
60+
String jsonResponse = this.post(
61+
this.config.getRestApiV2Url() + ABILITIES_URI,
62+
body
63+
);
64+
return this.objectMapper.readValue(jsonResponse, new TypeReference<>() {
65+
});
66+
} catch (IOException e) {
67+
throw new RuntimeException(e);
68+
}
69+
}
70+
71+
public void deleteAbility(Ability ability) {
72+
try {
73+
this.delete(this.config.getRestApiV2Url() + ABILITIES_URI + "/" + ability.getAbility_id());
74+
} catch (IOException e) {
75+
throw new RuntimeException(e);
76+
}
77+
}
78+
5679
// -- AGENTS --
5780

5881
private final static String AGENT_URI = "/agents";

openbas-api/src/main/java/io/openbas/injectors/caldera/service/CalderaInjectorService.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
package io.openbas.injectors.caldera.service;
22

3+
import com.fasterxml.jackson.core.type.TypeReference;
4+
import io.openbas.database.model.*;
35
import io.openbas.injectors.caldera.client.CalderaInjectorClient;
46
import io.openbas.injectors.caldera.client.model.*;
57
import io.openbas.injectors.caldera.model.Obfuscator;
68
import io.openbas.injectors.caldera.model.ResultStatus;
79
import jakarta.validation.constraints.NotBlank;
810
import lombok.RequiredArgsConstructor;
911
import lombok.extern.java.Log;
12+
import org.hibernate.Hibernate;
1013
import org.springframework.stereotype.Service;
1114

15+
import java.io.IOException;
1216
import java.nio.charset.StandardCharsets;
1317
import java.time.Instant;
1418
import java.util.*;
@@ -42,6 +46,47 @@ public List<Obfuscator> obfuscators() {
4246
return this.client.obfuscators();
4347
}
4448

49+
public void deleteAbility(Ability ability) {
50+
this.client.deleteAbility(ability);
51+
}
52+
53+
public Ability createAbility(Payload payload) {
54+
List<Map<String, String>> executors = new ArrayList<>();
55+
switch (payload.getType()) {
56+
case "Command":
57+
Command payloadCommand = (Command) Hibernate.unproxy(payload);
58+
Arrays.stream(payloadCommand.getPlatforms()).forEach(platform -> {
59+
Map<String, String> executor = new HashMap<>();
60+
executor.put("platform", platform.equalsIgnoreCase("macos") ? "darwin" : platform.toLowerCase());
61+
executor.put("name", payloadCommand.getExecutor().equals("bash") ? "sh" : payloadCommand.getExecutor());
62+
executor.put("command", payloadCommand.getContent());
63+
executors.add(executor);
64+
});
65+
break;
66+
case "DnsResolution":
67+
DnsResolution payloadDnsResolution = (DnsResolution) Hibernate.unproxy(payload);
68+
Arrays.stream(payloadDnsResolution.getPlatforms()).forEach(platform -> {
69+
Map<String, String> executor = new HashMap<>();
70+
executor.put("platform", platform.equals(Endpoint.PLATFORM_TYPE.MacOS.name()) ? "darwin" : platform.toLowerCase());
71+
executor.put("name", platform.equals(Endpoint.PLATFORM_TYPE.Windows.name()) ? "cmd" : "sh");
72+
executor.put("command", "nslookup " + payloadDnsResolution.getHostname());
73+
executors.add(executor);
74+
});
75+
break;
76+
default:
77+
throw new UnsupportedOperationException("Payload type " + payload.getType() + " is not supported");
78+
}
79+
Map<String, Object> body = new HashMap<>();
80+
body.put("name", payload.getId());
81+
body.put("tactic", "openbas");
82+
body.put("technique_id", "openbas");
83+
body.put("technique_name", "openbas");
84+
body.put("executors", executors);
85+
return this.client.createAbility(body);
86+
}
87+
88+
// -- AGENTS --
89+
4590
public List<Agent> agents() {
4691
try {
4792
return this.client.agents().stream().toList();
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.openbas.migration;
2+
3+
import org.flywaydb.core.api.migration.BaseJavaMigration;
4+
import org.flywaydb.core.api.migration.Context;
5+
import org.springframework.stereotype.Component;
6+
7+
import java.sql.Connection;
8+
import java.sql.Statement;
9+
10+
@Component
11+
public class V3_17__Payloads extends BaseJavaMigration {
12+
13+
@Override
14+
public void migrate(Context context) throws Exception {
15+
Connection connection = context.getConnection();
16+
Statement select = connection.createStatement();
17+
select.execute("ALTER TABLE payloads RENAME COLUMN network_traffic_ip TO network_traffic_ip_src;");
18+
select.execute("ALTER TABLE payloads ADD column network_traffic_ip_dst text;");
19+
select.execute("ALTER TABLE payloads ADD column network_traffic_port_src int;");
20+
select.execute("ALTER TABLE payloads ADD column network_traffic_port_dst int;");
21+
select.execute("ALTER TABLE payloads ADD column network_traffic_protocol varchar(255);");
22+
}
23+
}

openbas-api/src/main/java/io/openbas/rest/payload/PayloadApi.java

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package io.openbas.rest.payload;
22

3-
import io.openbas.database.model.Command;
4-
import io.openbas.database.model.Executable;
5-
import io.openbas.database.model.Payload;
3+
import io.openbas.database.model.*;
64
import io.openbas.database.repository.AttackPatternRepository;
5+
import io.openbas.database.repository.DocumentRepository;
76
import io.openbas.database.repository.PayloadRepository;
87
import io.openbas.database.repository.TagRepository;
98
import io.openbas.rest.exception.ElementNotFoundException;
@@ -15,6 +14,7 @@
1514
import jakarta.transaction.Transactional;
1615
import jakarta.validation.Valid;
1716
import jakarta.validation.constraints.NotBlank;
17+
import org.hibernate.Hibernate;
1818
import org.springframework.beans.factory.annotation.Autowired;
1919
import org.springframework.data.domain.Page;
2020
import org.springframework.data.domain.Pageable;
@@ -39,6 +39,7 @@ public class PayloadApi extends RestBehavior {
3939
private TagRepository tagRepository;
4040
private PayloadService payloadService;
4141
private AttackPatternRepository attackPatternRepository;
42+
private DocumentRepository documentRepository;
4243

4344
@Autowired
4445
public void setPayloadRepository(PayloadRepository payloadRepository) {
@@ -60,6 +61,11 @@ public void setAttackPatternRepository(AttackPatternRepository attackPatternRepo
6061
this.attackPatternRepository = attackPatternRepository;
6162
}
6263

64+
@Autowired
65+
public void setDocumentRepository(DocumentRepository documentRepository) {
66+
this.documentRepository = documentRepository;
67+
}
68+
6369
@GetMapping("/api/payloads")
6470
public Iterable<Payload> payloads() {
6571
return payloadRepository.findAll();
@@ -98,9 +104,35 @@ public Payload createPayload(@Valid @RequestBody PayloadCreateInput input) {
98104
executablePayload.setUpdateAttributes(input);
99105
executablePayload.setAttackPatterns(fromIterable(attackPatternRepository.findAllById(input.getAttackPatternsIds())));
100106
executablePayload.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds())));
107+
executablePayload.setExecutableFile(documentRepository.findById(input.getExecutableFile()).orElseThrow());
101108
executablePayload = payloadRepository.save(executablePayload);
102109
this.payloadService.updateInjectorContractsForPayload(executablePayload);
103110
return executablePayload;
111+
case "FileDrop":
112+
FileDrop fileDropPayload = new FileDrop();
113+
fileDropPayload.setUpdateAttributes(input);
114+
fileDropPayload.setAttackPatterns(fromIterable(attackPatternRepository.findAllById(input.getAttackPatternsIds())));
115+
fileDropPayload.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds())));
116+
fileDropPayload.setFileDropFile(documentRepository.findById(input.getExecutableFile()).orElseThrow());
117+
fileDropPayload = payloadRepository.save(fileDropPayload);
118+
this.payloadService.updateInjectorContractsForPayload(fileDropPayload);
119+
return fileDropPayload;
120+
case "DnsResolution":
121+
DnsResolution dnsResolutionPayload = new DnsResolution();
122+
dnsResolutionPayload.setUpdateAttributes(input);
123+
dnsResolutionPayload.setAttackPatterns(fromIterable(attackPatternRepository.findAllById(input.getAttackPatternsIds())));
124+
dnsResolutionPayload.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds())));
125+
dnsResolutionPayload = payloadRepository.save(dnsResolutionPayload);
126+
this.payloadService.updateInjectorContractsForPayload(dnsResolutionPayload);
127+
return dnsResolutionPayload;
128+
case "NetworkTraffic":
129+
NetworkTraffic networkTrafficPayload = new NetworkTraffic();
130+
networkTrafficPayload.setUpdateAttributes(input);
131+
networkTrafficPayload.setAttackPatterns(fromIterable(attackPatternRepository.findAllById(input.getAttackPatternsIds())));
132+
networkTrafficPayload.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds())));
133+
networkTrafficPayload = payloadRepository.save(networkTrafficPayload);
134+
this.payloadService.updateInjectorContractsForPayload(networkTrafficPayload);
135+
return networkTrafficPayload;
104136
default:
105137
throw new UnsupportedOperationException("Payload type " + input.getType() + " is not supported");
106138
}
@@ -113,13 +145,43 @@ public Payload updatePayload(
113145
@NotBlank @PathVariable final String payloadId,
114146
@Valid @RequestBody PayloadUpdateInput input) {
115147
Payload payload = this.payloadRepository.findById(payloadId).orElseThrow(ElementNotFoundException::new);
116-
payload.setUpdateAttributes(input);
117148
payload.setAttackPatterns(fromIterable(attackPatternRepository.findAllById(input.getAttackPatternsIds())));
118149
payload.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds())));
119150
payload.setUpdatedAt(Instant.now());
120-
payload = payloadRepository.save(payload);
121-
this.payloadService.updateInjectorContractsForPayload(payload);
122-
return payload;
151+
switch( payload.getType() ) {
152+
case "Command":
153+
Command payloadCommand = (Command) Hibernate.unproxy(payload);
154+
payloadCommand.setUpdateAttributes(input);
155+
payloadCommand = payloadRepository.save(payloadCommand);
156+
this.payloadService.updateInjectorContractsForPayload(payloadCommand);
157+
return payloadCommand;
158+
case "Executable":
159+
Executable payloadExecutable = (Executable) Hibernate.unproxy(payload);
160+
payloadExecutable.setUpdateAttributes(input);
161+
payloadExecutable = payloadRepository.save(payloadExecutable);
162+
this.payloadService.updateInjectorContractsForPayload(payloadExecutable);
163+
return payloadExecutable;
164+
case "FileDrop":
165+
FileDrop payloadFileDrop = (FileDrop) Hibernate.unproxy(payload);
166+
payloadFileDrop.setUpdateAttributes(input);
167+
payloadFileDrop = payloadRepository.save(payloadFileDrop);
168+
this.payloadService.updateInjectorContractsForPayload(payloadFileDrop);
169+
return payloadFileDrop;
170+
case "DnsResolution":
171+
DnsResolution payloadDnsResolution = (DnsResolution) Hibernate.unproxy(payload);
172+
payloadDnsResolution.setUpdateAttributes(input);
173+
payloadDnsResolution = payloadRepository.save(payloadDnsResolution);
174+
this.payloadService.updateInjectorContractsForPayload(payloadDnsResolution);
175+
return payloadDnsResolution;
176+
case "NetworkTraffic":
177+
NetworkTraffic payloadNetworkTraffic = (NetworkTraffic) Hibernate.unproxy(payload);
178+
payloadNetworkTraffic.setUpdateAttributes(input);
179+
payloadNetworkTraffic = payloadRepository.save(payloadNetworkTraffic);
180+
this.payloadService.updateInjectorContractsForPayload(payloadNetworkTraffic);
181+
return payloadNetworkTraffic;
182+
default:
183+
throw new UnsupportedOperationException("Payload type " + payload.getType() + " is not supported");
184+
}
123185
}
124186

125187
@Secured(ROLE_ADMIN)

openbas-api/src/main/java/io/openbas/rest/payload/form/PayloadCreateInput.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ public class PayloadCreateInput {
3535
@JsonProperty("command_content")
3636
private String content;
3737

38+
@JsonProperty("executable_file")
39+
private String executableFile;
40+
41+
@JsonProperty("file_drop_file")
42+
private String fileDropFile;
43+
44+
@JsonProperty("dns_resolution_hostname")
45+
private String hostname;
46+
3847
@JsonProperty("payload_arguments")
3948
private List<PayloadArgument> arguments;
4049

openbas-framework/src/main/java/io/openbas/executors/caldera/client/CalderaExecutorClient.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,9 @@ public Ability createSubprocessorAbility(Injector injector) {
104104
}
105105
Map<String, Object> body = new HashMap<>();
106106
body.put("name", "caldera-subprocessor-" + injector.getName());
107-
body.put("tactic", "initial-access");
108-
body.put("technique_id", "T1133");
109-
body.put("technique_name", "External Remote Services");
107+
body.put("tactic", "openbas");
108+
body.put("technique_id", "openbas");
109+
body.put("technique_name", "openbas");
110110
body.put("executors", executors);
111111
String jsonResponse = this.post(
112112
this.config.getRestApiV2Url() + ABILITIES_URI,
@@ -146,9 +146,9 @@ public Ability createClearAbility(Injector injector) {
146146
}
147147
Map<String, Object> body = new HashMap<>();
148148
body.put("name", "caldera-clear-" + injector.getName());
149-
body.put("tactic", "initial-access");
150-
body.put("technique_id", "T1133");
151-
body.put("technique_name", "External Remote Services");
149+
body.put("tactic", "openbas");
150+
body.put("technique_id", "openbas");
151+
body.put("technique_name", "openbas");
152152
body.put("executors", executors);
153153
String jsonResponse = this.post(
154154
this.config.getRestApiV2Url() + ABILITIES_URI,

openbas-front/src/admin/components/payloads/CreatePayload.js

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { connect } from 'react-redux';
44
import * as R from 'ramda';
55
import { Fab, List, ListItemButton, ListItemIcon, ListItemText, Step, StepLabel, Stepper } from '@mui/material';
66
import { withStyles } from '@mui/styles';
7-
import { Add } from '@mui/icons-material';
8-
import { Console } from 'mdi-material-ui';
7+
import { Add, DnsOutlined } from '@mui/icons-material';
8+
import { ApplicationCogOutline, Console, FileImportOutline, LanConnect } from 'mdi-material-ui';
99
import { addPayload } from '../../../actions/Payload';
1010
import PayloadForm from './PayloadForm';
1111
import inject18n from '../../../components/i18n';
@@ -43,6 +43,7 @@ class CreatePayload extends Component {
4343
R.assoc('payload_platforms', R.pluck('id', data.payload_platforms)),
4444
R.assoc('payload_tags', R.pluck('id', data.payload_tags)),
4545
R.assoc('payload_attack_patterns', R.pluck('id', data.payload_attack_patterns)),
46+
R.assoc('executable_file', data.executable_file?.id),
4647
)(data);
4748
return this.props
4849
.addPayload(inputValues)
@@ -68,6 +69,42 @@ class CreatePayload extends Component {
6869
</ListItemIcon>
6970
<ListItemText primary={t('Command Line')} />
7071
</ListItemButton>
72+
<ListItemButton
73+
divider={true}
74+
onClick={this.handleSelectType.bind(this, 'Executable')}
75+
>
76+
<ListItemIcon color="primary">
77+
<ApplicationCogOutline color="primary" />
78+
</ListItemIcon>
79+
<ListItemText primary={t('Executable')} />
80+
</ListItemButton>
81+
<ListItemButton
82+
divider={true}
83+
onClick={this.handleSelectType.bind(this, 'FileDrop')}
84+
>
85+
<ListItemIcon color="primary">
86+
<FileImportOutline color="primary" />
87+
</ListItemIcon>
88+
<ListItemText primary={t('File Drop')} />
89+
</ListItemButton>
90+
<ListItemButton
91+
divider={true}
92+
onClick={this.handleSelectType.bind(this, 'DnsResolution')}
93+
>
94+
<ListItemIcon color="primary">
95+
<DnsOutlined color="primary" />
96+
</ListItemIcon>
97+
<ListItemText primary={t('DNS Resolution')} />
98+
</ListItemButton>
99+
<ListItemButton
100+
divider={true}
101+
onClick={this.handleSelectType.bind(this, 'NetworkTraffic')}
102+
>
103+
<ListItemIcon color="primary">
104+
<LanConnect color="primary" />
105+
</ListItemIcon>
106+
<ListItemText primary={t('Network Traffic')} />
107+
</ListItemButton>
71108
</List>
72109
);
73110
}

0 commit comments

Comments
 (0)