diff --git a/src/attack_flow_builder/src/assets/builder.config.publisher.ts b/src/attack_flow_builder/src/assets/builder.config.publisher.ts index f63227f0..7591a69b 100644 --- a/src/attack_flow_builder/src/assets/builder.config.publisher.ts +++ b/src/attack_flow_builder/src/assets/builder.config.publisher.ts @@ -242,6 +242,10 @@ class AttackFlowPublisher extends DiagramPublisher { break; case PropertyType.List: if (prop.isDefined()) { + if(key === "hashes") { + this.mergeComplexListProperty(node, key, prop as ListProperty); + break; + } this.mergeBasicListProperty(node, key, prop as ListProperty); } break; @@ -296,6 +300,35 @@ class AttackFlowPublisher extends DiagramPublisher { } } + /** + * Merges a more complicated list into a STIX node. + * @param node + * The STIX node. + * @param key + * The list property's key. + * @param property + * The list property. + */ + private mergeComplexListProperty(node: Sdo, key: string, property: ListProperty) { + switch(key) { + case "hashes": + let hashList = []; + for(let prop of property.value.values()) { + switch(prop.type) { + case PropertyType.Dictionary: + if(prop.isDefined()) { + let entries = prop.toRawValue() as RawEntries; + hashList.push(Object.fromEntries(entries)); + } + break; + } + } + if (hashList.length > 0) { + let hashes = hashList.map(hash => [hash.hash_type, hash.hash_value]); // Drop the property labels + node[key] = Object.fromEntries(hashes); + } + } + } /////////////////////////////////////////////////////////////////////////// // 2. Relationships Embeddings ////////////////////////////////////////// diff --git a/src/attack_flow_builder/src/assets/builder.config.ts b/src/attack_flow_builder/src/assets/builder.config.ts index d6360d29..1c2e06a8 100644 --- a/src/attack_flow_builder/src/assets/builder.config.ts +++ b/src/attack_flow_builder/src/assets/builder.config.ts @@ -555,7 +555,32 @@ const config: AppConfiguration = { mime_type : { type: PropertyType.String }, payload_bin : { type: PropertyType.String }, url : { type: PropertyType.String }, - hashes : { type: PropertyType.String }, + hashes : { + type: PropertyType.List, + form: { + type: PropertyType.Dictionary, + form: { + hash_type: { + type: PropertyType.Enum, + options: { + type: PropertyType.List, + form: { type: PropertyType.String }, + value: [ + ["custom", "Custom Hash Key"], + ["md5", "MD5"], + ["sha-1", "SHA-1"], + ["sha-256", "SHA-256"], + ["sha-512", "SHA-512"], + ["sha3-256", "SHA3-256"], + ["ssdeep", "SSDEEP"], + ["tlsh", "TLSH"] + ] + } + }, + hash_value: { type: PropertyType.String, is_primary: true } + } + } + }, encryption_algorithm : { type: PropertyType.String }, decryption_key : { type: PropertyType.String }, }, @@ -638,7 +663,32 @@ const config: AppConfiguration = { type: TemplateType.DictionaryBlock, role: SemanticRole.Node, properties: { - hashes : { type: PropertyType.String }, + hashes : { + type: PropertyType.List, + form: { + type: PropertyType.Dictionary, + form: { + hash_type: { + type: PropertyType.Enum, + options: { + type: PropertyType.List, + form: { type: PropertyType.String }, + value: [ + ["custom", "Custom Hash Key"], + ["md5", "MD5"], + ["sha-1", "SHA-1"], + ["sha-256", "SHA-256"], + ["sha-512", "SHA-512"], + ["sha3-256", "SHA3-256"], + ["ssdeep", "SSDEEP"], + ["tlsh", "TLSH"] + ] + } + }, + hash_value: { type: PropertyType.String, is_primary: true } + } + } + }, size : { type: PropertyType.String }, name : { type: PropertyType.String, is_primary: true }, name_enc : { type: PropertyType.String }, @@ -804,7 +854,32 @@ const config: AppConfiguration = { properties: { subject : { type: PropertyType.String, is_primary: true, is_required: true }, is_self_signed : BoolEnum, - hashes : { type: PropertyType.String }, + hashes : { + type: PropertyType.List, + form: { + type: PropertyType.Dictionary, + form: { + hash_type: { + type: PropertyType.Enum, + options: { + type: PropertyType.List, + form: { type: PropertyType.String }, + value: [ + ["custom", "Custom Hash Key"], + ["md5", "MD5"], + ["sha-1", "SHA-1"], + ["sha-256", "SHA-256"], + ["sha-512", "SHA-512"], + ["sha3-256", "SHA3-256"], + ["ssdeep", "SSDEEP"], + ["tlsh", "TLSH"] + ] + } + }, + hash_value: { type: PropertyType.String, is_primary: true } + } + } + }, version : { type: PropertyType.String }, serial_number : { type: PropertyType.String }, signature_algorithm : { type: PropertyType.String }, diff --git a/src/attack_flow_builder/src/assets/builder.config.validator.ts b/src/attack_flow_builder/src/assets/builder.config.validator.ts index 625525a5..a41ce3ba 100644 --- a/src/attack_flow_builder/src/assets/builder.config.validator.ts +++ b/src/attack_flow_builder/src/assets/builder.config.validator.ts @@ -7,6 +7,7 @@ import { ListProperty, Property, PropertyType, + RawEntries, SemanticAnalyzer } from "./scripts/BlockDiagram"; @@ -153,6 +154,13 @@ class AttackFlowValidator extends DiagramValidator { } // Validate links switch(node.template.id) { + case "artifact": { + const hashes = node.props.value.get("hashes"); + if(hashes?.isDefined()) { + this.validateHash(id, hashes as ListProperty); + } + break; + } case "email_address": // Additional validation for email addresses if (!AttackFlowValidator.Emailregex.test(String(node.props.value.get("value")))) { this.addError(id, "Invalid email address.") @@ -164,6 +172,9 @@ class AttackFlowValidator extends DiagramValidator { if(!hash?.isDefined() && !name?.isDefined()) { this.addError(id, "File requires one of the following properties: Hashes, Name"); } + if(hashes?.isDefined()) { + this.validateHash(id, hashes as ListProperty); + } break; case "grouping": if(node.next.length === 0) { @@ -229,6 +240,13 @@ class AttackFlowValidator extends DiagramValidator { this.addError(id, "Invalid Windows registry key."); } break; + case "x509_certificate": { + const hashes = node.props.value.get("hashes"); + if(hashes?.isDefined()) { + this.validateHash(id, hashes as ListProperty); + } + break; + } } } @@ -304,6 +322,43 @@ class AttackFlowValidator extends DiagramValidator { } } + /** + * Validate a hash from any hash-containing node. + * + * Reference: https://docs.oasis-open.org/cti/stix/v2.1/os/stix-v2.1-os.html#_odoabbtwuxyd + * + * @param id + * The node's id. + * @param hashes + * The list of hashes to validate. + */ + protected validateHash(id: string, hashes: ListProperty) { + const validKey = /^[a-zA-Z0-9_-]{3,250}$/; + const hashDictionaryProps = hashes.value; + + if(!hashDictionaryProps) { + // This is an older AFB file that does not have the Hash property updated to current version. + this.addWarning(id, "This AFB is outdated, please remake it in a new file."); + return; + } + + for(let hashDictionary of hashDictionaryProps.values()) { + if(!hashDictionary.isDefined()) { + this.addError(id, "Hash Value cannot be empty."); + } + // Make sure hash_type is not empty. + let entries = hashDictionary.toRawValue()! as RawEntries; + if(!Object.fromEntries(entries).hash_type) { + this.addError(id, "Hash Type cannot be left empty."); + } + + let key = hashDictionary.toString(); + if (!validKey.test(key)) { + this.addError(id, "Invalid hash key."); + } + } + } + /** * Validates the links to/from a network-traffic node. *