Skip to content

Commit

Permalink
Merge pull request #77 from center-for-threat-informed-defense/af121-…
Browse files Browse the repository at this point in the history
…network-traffic

Fix validation errors for network_traffic
  • Loading branch information
mikecarenzo committed Jul 18, 2023
2 parents 809c86d + ca861c2 commit ab8eb90
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 16 deletions.
5 changes: 3 additions & 2 deletions src/attack_flow/mermaid.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def render(self):

lines.append("")

for (node_id, node_class, label) in self.nodes:
for node_id, node_class, label in self.nodes:
node_id = convert_id(node_id)
label = "<br>".join(textwrap.wrap(label.replace('"', ""), width=40))
if self.classes[node_class][0] == "circle":
Expand Down Expand Up @@ -87,7 +87,8 @@ def convert(bundle):
confidence = confidence_num_to_label(o.get("confidence", 95))
label_lines = [
"<b>Action</b>",
f"<b>{name}</b>: ", o.get("description", ""),
f"<b>{name}</b>: ",
o.get("description", ""),
f"<b>Confidence</b> {confidence}",
]
graph.add_node(o.id, "action", " - ".join(label_lines))
Expand Down
37 changes: 37 additions & 0 deletions src/attack_flow_builder/src/assets/builder.config.publisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,21 @@ class AttackFlowPublisher extends DiagramPublisher {
case "attack-operator":
sro = this.tryEmbedInOperator(parent, c.obj);
break;
case "ipv4-addr": // falls through
case "ipv6-addr": // falls through
case "mac-addr": // falls through
case "domain-name":
// Network traffic is a special case where it's _parent_ can be embedded if its one of the
// above types.
if (c.obj.type === "network-traffic") {
sro = this.tryEmbedInNetworkTraffic(parent, c.obj);
} else {
sro = this.tryEmbedInDefault(parent, c.obj);
}
break;
case "network-traffic":
sro = this.tryEmbedInNetworkTraffic(parent, c.obj);
break;
case "note":
this.tryEmbedInNote(parent, c.obj);
break;
Expand Down Expand Up @@ -476,6 +491,28 @@ class AttackFlowPublisher extends DiagramPublisher {
return sro;
}

/**
* Try to embed a reference in a network-traffic object, otherwise return a new SRO.
*
* Either parent or child must be a network-traffic, the other one should not be a network-traffic.
*
* @param parent
* A STIX node (network-traffic, ipv4-addr, ipv6-addr, mac-addr, or domain-name)
* @param child
* A STIX node (network-traffic, ipv4-addr, ipv6-addr, mac-addr, or domain-name)
* @returns
* An SRO, if one was created.
*/
private tryEmbedInNetworkTraffic(parent: Sdo, child: Sdo): Sro | undefined {
if (parent.type === "network-traffic" && !parent["dst_ref"]) {
parent["dst_ref"] = child.id;
} else if (child.type === "network-traffic" && !child["src_ref"]) {
child["src_ref"] = parent.id;
} else {
return this.createSro(parent, child);
}
}

/**
* Embed a reference to the child in the operator. If the child cannot be
* embedded, return a new SRO.
Expand Down
2 changes: 1 addition & 1 deletion src/attack_flow_builder/src/assets/builder.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ const config: AppConfiguration = {
is_active : BoolEnum,
src_port : { type: PropertyType.Int, min: 0, max: 65535 },
dst_port : { type: PropertyType.Int, min: 0, max: 65535 },
protocols : { type: PropertyType.List, form: { type: PropertyType.String, is_required: true }},
protocols : { type: PropertyType.List, min_items: 1, form: { type: PropertyType.String, is_required: true }},
src_byte_count : { type: PropertyType.Int, min: 0 },
dst_byte_count : { type: PropertyType.Int, min: 0 },
src_packets : { type: PropertyType.Int, min: 0 },
Expand Down
141 changes: 133 additions & 8 deletions src/attack_flow_builder/src/assets/builder.config.validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { DiagramValidator } from "./scripts/DiagramValidator/DiagramValidator";
import {
DiagramObjectModel,
DictionaryProperty,
GraphExport,
GraphObjectExport,
ListProperty,
Property,
Expand All @@ -10,44 +11,115 @@ import {
} from "./scripts/BlockDiagram";

class AttackFlowValidator extends DiagramValidator {

static WindowsRegistryregex = /^(Computer\\)?((HKEY_LOCAL_MACHINE)|(HKEY_CURRENT_CONFIG)|(HKEY_CLASSES_ROOT)|(HKEY_CURRENT_USER)|(HKEY_YSERS))\\(.*)[^\\]/
static IPv4regex = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(3[0-2]|[1-2][0-9]|[0-9]))?$/;
static IPv6regex = /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?(\/(12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))?$/i;
static MACregex = /^([0-9a-f]{2}[:]){5}([0-9a-f]{2})$/i;

protected graph?: GraphExport;

/**
* Validates a diagram.
* @param diagram
* The diagram to validate.
*/
protected override validate(diagram: DiagramObjectModel): void {
let graph = SemanticAnalyzer.toGraph(diagram);

this.graph = SemanticAnalyzer.toGraph(diagram);
// Validate nodes
let actionNodeCount = 0;
let flowId;
for (let [id, node] of graph.nodes) {
if (node.template.id == "flow") {
for (let [id, node] of this.graph.nodes) {
if (node.template.id === "flow") {
flowId = id;
}
if (node.template.id == "action") {
if (node.template.id === "action") {
actionNodeCount++;
}
this.validateNode(id, node);
}

// The flow requires at least one start_ref, which means it must have at least one action node.
if (flowId && actionNodeCount == 0) {
if (flowId && actionNodeCount === 0) {
this.addError(flowId, "The flow must have at least one action in it.");
}

// Validate edges
for (let [id, edge] of graph.edges) {
for (let [id, edge] of this.graph.edges) {
this.validateEdge(id, edge);
}
}

/**
* Get a node from the graph.
* @param id
* Node identifier.
* @returns a node
*/
protected getNode(id: string): GraphObjectExport {
if (this.graph) {
const node = this.graph.nodes.get(id);
if (node) {
return node;
} else {
throw new Error(`Node id=${id} not found in graph.`);
}
} else {
throw new Error("Cannot call getNode() before validate()");
}
}

/**
* Enumerate inbound nodes for a given node ID.
* @param id
* Node identifier.
* @returns a generator
*/
protected* getInboundNodes(id: string): Generator<[string, GraphObjectExport]> {
const node = this.getNode(id);
for (const edgeId of node.prev) {
const edge = this.getEdge(edgeId);
for (const prevId of edge.prev) {
yield [prevId, this.getNode(prevId)];
}
}
}

/**
* Enumerate outbound nodes for a given node ID.
* @param id
* Node identifier.
* @returns a generator
*/
protected* getOutboundNodes(id: string): Generator<[string, GraphObjectExport]> {
const node = this.getNode(id);
for (const edgeId of node.next) {
const edge = this.getEdge(edgeId);
for (const nextId of edge.next) {
yield [nextId, this.getNode(nextId)];
}
}
}

/**
* Get an edge from the graph.
* @param id
* Edge identifier.
* @returns an edge
*/
protected getEdge(id: string): GraphObjectExport {
if (this.graph) {
const edge = this.graph.edges.get(id);
if (edge) {
return edge;
} else {
throw new Error(`Edge id=${id} not found in graph.`);
}
} else {
throw new Error("Cannot call getEdge() before validate()");
}
}

/**
* Validates a node.
* @param id
Expand All @@ -71,6 +143,9 @@ class AttackFlowValidator extends DiagramValidator {
}
// Validate links
switch(node.template.id) {
case "network_traffic":
this.validateNetworkTrafficLinks(id, node);
break;
case "note":
if(node.next.length === 0) {
this.addError(id, "A Note must point to at least one object.");
Expand Down Expand Up @@ -114,6 +189,11 @@ class AttackFlowValidator extends DiagramValidator {
break;
case PropertyType.List:
if(property instanceof ListProperty) {
let descriptor = property.descriptor as any;
if (descriptor.min_items != null && property.value.size < descriptor.min_items) {
const suffix = descriptor.min_items === 1 ? "" : "s";
this.addError(id, `${name}: Requires at least ${descriptor.min_items} item${suffix}`);
}
for(let v of property.value.values()) {
switch(v.type) {
case PropertyType.Int:
Expand Down Expand Up @@ -151,6 +231,51 @@ class AttackFlowValidator extends DiagramValidator {
}
}

/**
* Validates the links to/from a network-traffic node.
*
* The requirements are a bit tricky: it must have a source node pointing to it, or it needs to point at
* at a destination node. The source/destination must be an IP address, MAC address, or domain name.
*
* Reference: https://docs.oasis-open.org/cti/stix/v2.1/os/stix-v2.1-os.html#_rgnc3w40xy
*
* @param id
* The node's id.
* @param node
* The node.
*/
protected validateNetworkTrafficLinks(id: string, node: GraphObjectExport) {
let networkSrc = null, networkDst = null;
const validSrcDst = /^((ipv[46]|mac)_addr|domain_name)$/;

// Check inbound nodes for a single valid networkSrc.
for (const [inboundId, inboundNode] of this.getInboundNodes(id)) {
if (validSrcDst.test(inboundNode.template.id)) {
if (networkSrc) {
this.addWarning(inboundId,
"Network Traffic should only have one incoming IP, MAC, or domain name.");
} else {
networkSrc = inboundNode;
}
}
}

// Check outbound nodes for a single valid networkDst.
for (const [outboundId, outboundNode] of this.getOutboundNodes(id)) {
if (validSrcDst.test(outboundNode.template.id)) {
if (networkDst) {
this.addWarning(outboundId,
"Network Traffic should only have one outgoing IP, MAC, or domain name.");
} else {
networkDst = outboundNode;
}
}
}

if (!(networkSrc || networkDst)) {
this.addError(id, "Network Traffic must be linked to an IP, MAC, or domain name.")
}
}
}

export default AttackFlowValidator;
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export type ListPropertyDescriptor = {
type: PropertyType.List,
form: PropertyDescriptor
value?: any,
min_items?: number,
is_primary?: boolean,
is_visible?: boolean,
is_editable? : boolean
Expand Down Expand Up @@ -152,6 +153,7 @@ type ValueListDescriptor = {
type: PropertyType.List,
form: ValueDescriptor,
value?: any,
min_items?: number,
is_primary?: boolean,
is_visible?: boolean,
is_editable? : boolean
Expand Down
2 changes: 1 addition & 1 deletion tests/test_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def test_graph_is_missing_node(caplog):
)

assert (
"is the graph missing node attack-action--7976c9b3-b593-45b6-a66e-3a49bfc1008f?"
"Node (attack-action--7976c9b3-b593-45b6-a66e-3a49bfc1008f) does not have a technique ID."
in caplog.text
)

Expand Down
8 changes: 4 additions & 4 deletions tests/test_mermaid.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ def test_convert_attack_flow_to_mermaid():
classDef condition fill:#99ff99
classDef builtin fill:#cccccc
attack_action__52f2c35a_fa2a_45a4_b84c_46ad9498071f["<b>Action</b> - <b>T1 Action 1</b>:<br>Description of action 1 -<br><b>Confidence</b> Very Probable"]
attack_action__52f2c35a_fa2a_45a4_b84c_46ad9498071f["<b>Action</b> - <b>T1 Action 1</b>: -<br>Description of action 1 -<br><b>Confidence</b> Very Probable"]
class attack_action__52f2c35a_fa2a_45a4_b84c_46ad9498071f action
attack_action__dd3820fa_bae3_4270_8000_5c4642fa780c["<b>Action</b> - <b>Action 2</b>:<br>Description of action 2 -<br><b>Confidence</b> Very Probable"]
attack_action__dd3820fa_bae3_4270_8000_5c4642fa780c["<b>Action</b> - <b>Action 2</b>: -<br>Description of action 2 -<br><b>Confidence</b> Very Probable"]
class attack_action__dd3820fa_bae3_4270_8000_5c4642fa780c action
attack_action__a0847849_a533_4b1f_a94a_720bbd25fc17["<b>Action</b> - <b>T3 Action 3</b>:<br>Description of action 3 -<br><b>Confidence</b> Very Probable"]
attack_action__a0847849_a533_4b1f_a94a_720bbd25fc17["<b>Action</b> - <b>T3 Action 3</b>: -<br>Description of action 3 -<br><b>Confidence</b> Very Probable"]
class attack_action__a0847849_a533_4b1f_a94a_720bbd25fc17 action
attack_action__7ddab166_c83e_4c79_a701_a0dc2a905dd3["<b>Action</b> - <b>T4 Action 4</b>:<br>Description of action 4 -<br><b>Confidence</b> Very Probable"]
attack_action__7ddab166_c83e_4c79_a701_a0dc2a905dd3["<b>Action</b> - <b>T4 Action 4</b>: -<br>Description of action 4 -<br><b>Confidence</b> Very Probable"]
class attack_action__7ddab166_c83e_4c79_a701_a0dc2a905dd3 action
attack_condition__64d5bf0b_6acc_4f43_b0f2_aa93a219897a["<b>Condition:</b> My condition"]
class attack_condition__64d5bf0b_6acc_4f43_b0f2_aa93a219897a condition
Expand Down

0 comments on commit ab8eb90

Please sign in to comment.