assemblyline-v4-service 4.6.1.dev231__py3-none-any.whl → 4.7.0.dev25__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- assemblyline_v4_service/VERSION +1 -1
- assemblyline_v4_service/common/api.py +0 -48
- assemblyline_v4_service/common/base.py +2 -6
- assemblyline_v4_service/common/ontology_helper.py +2 -0
- assemblyline_v4_service/common/request.py +4 -5
- assemblyline_v4_service/common/result.py +616 -3
- assemblyline_v4_service/common/task.py +6 -6
- assemblyline_v4_service/dev/updater.py +7 -2
- assemblyline_v4_service/healthz.py +18 -19
- {assemblyline_v4_service-4.6.1.dev231.dist-info → assemblyline_v4_service-4.7.0.dev25.dist-info}/METADATA +1 -1
- {assemblyline_v4_service-4.6.1.dev231.dist-info → assemblyline_v4_service-4.7.0.dev25.dist-info}/RECORD +15 -16
- test/test_common/test_api.py +0 -24
- assemblyline_v4_service/run_privileged_service.py +0 -337
- {assemblyline_v4_service-4.6.1.dev231.dist-info → assemblyline_v4_service-4.7.0.dev25.dist-info}/WHEEL +0 -0
- {assemblyline_v4_service-4.6.1.dev231.dist-info → assemblyline_v4_service-4.7.0.dev25.dist-info}/licenses/LICENCE.md +0 -0
- {assemblyline_v4_service-4.6.1.dev231.dist-info → assemblyline_v4_service-4.7.0.dev25.dist-info}/top_level.txt +0 -0
|
@@ -2,7 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
|
-
from
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, TextIO, Union
|
|
6
7
|
|
|
7
8
|
from assemblyline.common import log as al_log
|
|
8
9
|
from assemblyline.common.attack_map import attack_map, group_map, revoke_map, software_map
|
|
@@ -39,7 +40,8 @@ BODY_FORMAT = StringTable('BODY_FORMAT', [
|
|
|
39
40
|
('MULTI', 9),
|
|
40
41
|
('DIVIDER', 10), # This is not a real result section and can only be use inside a multi section
|
|
41
42
|
('ORDERED_KEY_VALUE', 11),
|
|
42
|
-
('TIMELINE', 12)
|
|
43
|
+
('TIMELINE', 12),
|
|
44
|
+
('SANDBOX', 13),
|
|
43
45
|
])
|
|
44
46
|
|
|
45
47
|
# This is a StringTable representation of the PROMOTE_TO set of keys in
|
|
@@ -410,6 +412,569 @@ class ProcessTreeSectionBody(SectionBody):
|
|
|
410
412
|
self._data.append(process.as_primitives())
|
|
411
413
|
|
|
412
414
|
|
|
415
|
+
class SandboxMachineMetadata:
|
|
416
|
+
"""Metadata about the machine where the sandbox analysis took place."""
|
|
417
|
+
|
|
418
|
+
def __init__(
|
|
419
|
+
self,
|
|
420
|
+
# The IP address of the analysis machine.
|
|
421
|
+
ip: Optional[str] = None,
|
|
422
|
+
|
|
423
|
+
# The hypervisor used by the analysis machine (e.g., VMware, KVM).
|
|
424
|
+
hypervisor: Optional[str] = None,
|
|
425
|
+
|
|
426
|
+
# The hostname of the machine used for analysis.
|
|
427
|
+
hostname: Optional[str] = None,
|
|
428
|
+
|
|
429
|
+
# The platform or operating system name (e.g., Windows, Linux, macOS).
|
|
430
|
+
platform: Optional[str] = None,
|
|
431
|
+
|
|
432
|
+
# The version of the operating system.
|
|
433
|
+
version: Optional[str] = None,
|
|
434
|
+
|
|
435
|
+
# The architecture of the operating system (e.g., x86, x64, ARM).
|
|
436
|
+
architecture: Optional[str] = None,
|
|
437
|
+
):
|
|
438
|
+
self.ip = ip
|
|
439
|
+
self.hypervisor = hypervisor
|
|
440
|
+
self.hostname = hostname
|
|
441
|
+
self.platform = platform
|
|
442
|
+
self.version = version
|
|
443
|
+
self.architecture = architecture
|
|
444
|
+
|
|
445
|
+
def as_primitives(self) -> Dict:
|
|
446
|
+
"""Return a JSON-serializable representation."""
|
|
447
|
+
return {
|
|
448
|
+
"ip": self.ip,
|
|
449
|
+
"hypervisor": self.hypervisor,
|
|
450
|
+
"hostname": self.hostname,
|
|
451
|
+
"platform": self.platform,
|
|
452
|
+
"version": self.version,
|
|
453
|
+
"architecture": self.architecture,
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
LookupType = Literal[
|
|
457
|
+
"A", "AAAA", "AFSDB", "APL", "CAA", "CDNSKEY", "CDS", "CERT", "CNAME", "CSYNC",
|
|
458
|
+
"DHCID", "DLV", "DNAME", "DNSKEY", "DS", "EUI48", "EUI64", "HINFO", "HIP",
|
|
459
|
+
"HTTPS", "IPSECKEY", "KEY", "KX", "LOC", "MX", "NAPTR", "NS", "NSEC", "NSEC3",
|
|
460
|
+
"NSEC3PARAM", "OPENPGPKEY", "PTR", "RRSIG", "RP", "SIG", "SMIMEA", "SOA",
|
|
461
|
+
"SRV", "SSHFP", "SVCB", "TA", "TKEY", "TLSA", "TSIG", "TXT", "URI", "ZONEMD"
|
|
462
|
+
]
|
|
463
|
+
|
|
464
|
+
RequestMethod = Literal[
|
|
465
|
+
"GET", "POST", "PUT", "DELETE", "HEAD", "CONNECT", "OPTIONS", "TRACE", "PATCH",
|
|
466
|
+
"BCOPY", "BDELETE", "BMOVE", "BPROPFIND", "BPROPPATCH", "COPY", "LOCK",
|
|
467
|
+
"MKCOL", "MOVE", "NOTIFY", "POLL", "PROPFIND", "PROPPATCH", "SEARCH",
|
|
468
|
+
"SUBSCRIBE", "UNLOCK", "UNSUBSCRIBE", "X-MS-ENUMATTS"
|
|
469
|
+
]
|
|
470
|
+
|
|
471
|
+
ConnectionType = Literal["http", "dns", "tls", "smtp"]
|
|
472
|
+
ConnectionDirection = Literal["outbound", "inbound", "unknown"]
|
|
473
|
+
SignatureType = Literal["CUCKOO", "YARA", "SIGMA", "SURICATA"]
|
|
474
|
+
|
|
475
|
+
class SandboxMachineMetadata:
|
|
476
|
+
"""Information about the sandbox machine used during analysis."""
|
|
477
|
+
|
|
478
|
+
def __init__(
|
|
479
|
+
self,
|
|
480
|
+
# The IP address of the machine used for analysis.
|
|
481
|
+
ip: Optional[str] = None,
|
|
482
|
+
|
|
483
|
+
# The hypervisor type of the machine used for analysis.
|
|
484
|
+
hypervisor: Optional[str] = None,
|
|
485
|
+
|
|
486
|
+
# The hostname of the machine used for analysis.
|
|
487
|
+
hostname: Optional[str] = None,
|
|
488
|
+
|
|
489
|
+
# The operating system platform of the machine (e.g., "Windows", "Linux").
|
|
490
|
+
platform: Optional[str] = None,
|
|
491
|
+
|
|
492
|
+
# The version of the operating system.
|
|
493
|
+
version: Optional[str] = None,
|
|
494
|
+
|
|
495
|
+
# The system architecture of the machine (e.g., "x64", "arm64").
|
|
496
|
+
architecture: Optional[str] = None,
|
|
497
|
+
):
|
|
498
|
+
self.ip = ip
|
|
499
|
+
self.hypervisor = hypervisor
|
|
500
|
+
self.hostname = hostname
|
|
501
|
+
self.platform = platform
|
|
502
|
+
self.version = version
|
|
503
|
+
self.architecture = architecture
|
|
504
|
+
|
|
505
|
+
def as_primitives(self) -> Dict[str, Any]:
|
|
506
|
+
return {
|
|
507
|
+
"ip": self.ip,
|
|
508
|
+
"hypervisor": self.hypervisor,
|
|
509
|
+
"hostname": self.hostname,
|
|
510
|
+
"platform": self.platform,
|
|
511
|
+
"version": self.version,
|
|
512
|
+
"architecture": self.architecture,
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
class SandboxAnalysisMetadata:
|
|
517
|
+
"""Metadata describing the context and configuration of a sandbox analysis."""
|
|
518
|
+
|
|
519
|
+
def __init__(
|
|
520
|
+
self,
|
|
521
|
+
# The unique identifier of the analysis task.
|
|
522
|
+
task_id: Optional[int] = None,
|
|
523
|
+
|
|
524
|
+
# The timestamp when the analysis started (ISO 8601 format).
|
|
525
|
+
start_time: str = "",
|
|
526
|
+
|
|
527
|
+
# The timestamp when the analysis ended (ISO 8601 format).
|
|
528
|
+
end_time: Optional[str] = None,
|
|
529
|
+
|
|
530
|
+
# The network routing used during analysis (e.g., "Spoofed", "Internet", "Tor", "VPN").
|
|
531
|
+
routing: Optional[str] = None,
|
|
532
|
+
|
|
533
|
+
# The screen resolution or window size used for the sandbox environment.
|
|
534
|
+
window_size: Optional[str] = None,
|
|
535
|
+
|
|
536
|
+
# Metadata describing the machine on which the analysis ran.
|
|
537
|
+
machine_metadata: Optional[SandboxMachineMetadata] = None,
|
|
538
|
+
):
|
|
539
|
+
self.task_id = task_id
|
|
540
|
+
self.start_time = start_time
|
|
541
|
+
self.end_time = end_time
|
|
542
|
+
self.routing = routing
|
|
543
|
+
self.window_size = window_size
|
|
544
|
+
self.machine_metadata = machine_metadata
|
|
545
|
+
|
|
546
|
+
def as_primitives(self) -> Dict[str, Any]:
|
|
547
|
+
return {
|
|
548
|
+
"task_id": self.task_id,
|
|
549
|
+
"start_time": self.start_time,
|
|
550
|
+
"end_time": self.end_time,
|
|
551
|
+
"routing": self.routing,
|
|
552
|
+
"window_size": self.window_size,
|
|
553
|
+
"machine_metadata": (
|
|
554
|
+
self.machine_metadata.as_primitives() if self.machine_metadata else None
|
|
555
|
+
),
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
class SandboxProcessItem:
|
|
560
|
+
"""Information about a process observed during sandbox execution."""
|
|
561
|
+
|
|
562
|
+
def __init__(
|
|
563
|
+
self,
|
|
564
|
+
# The executable image name of the process. Default: "<unknown_image>".
|
|
565
|
+
image: str,
|
|
566
|
+
|
|
567
|
+
# The timestamp when the process started (ISO 8601 format).
|
|
568
|
+
start_time: str,
|
|
569
|
+
|
|
570
|
+
# The parent process ID (PPID).
|
|
571
|
+
ppid: Optional[int] = None,
|
|
572
|
+
|
|
573
|
+
# The process ID (PID).
|
|
574
|
+
pid: Optional[int] = None,
|
|
575
|
+
|
|
576
|
+
# The full command line used to start the process.
|
|
577
|
+
command_line: Optional[str] = None,
|
|
578
|
+
|
|
579
|
+
# The timestamp when the process terminated (ISO 8601 format).
|
|
580
|
+
end_time: Optional[str] = None,
|
|
581
|
+
|
|
582
|
+
# The integrity level of the process (e.g., "High", "Medium", "Low").
|
|
583
|
+
integrity_level: Optional[str] = None,
|
|
584
|
+
|
|
585
|
+
# The hash of the executable file for the process (e.g., SHA256).
|
|
586
|
+
image_hash: Optional[str] = None,
|
|
587
|
+
|
|
588
|
+
# The original file name as embedded in the binary metadata.
|
|
589
|
+
original_file_name: Optional[str] = None,
|
|
590
|
+
|
|
591
|
+
# Indicates whether this process was safelisted (whitelisted).
|
|
592
|
+
safelisted: Optional[bool] = False,
|
|
593
|
+
|
|
594
|
+
# The number of file I/O events associated with this process.
|
|
595
|
+
file_count: Optional[int] = 0,
|
|
596
|
+
|
|
597
|
+
# The number of registry modification events associated with this process.
|
|
598
|
+
registry_count: Optional[int] = 0,
|
|
599
|
+
):
|
|
600
|
+
self.image = image or "<unknown_image>"
|
|
601
|
+
self.start_time = start_time
|
|
602
|
+
self.ppid = ppid
|
|
603
|
+
self.pid = pid
|
|
604
|
+
self.command_line = command_line
|
|
605
|
+
self.end_time = end_time
|
|
606
|
+
self.integrity_level = integrity_level
|
|
607
|
+
self.image_hash = image_hash
|
|
608
|
+
self.original_file_name = original_file_name
|
|
609
|
+
self.safelisted = safelisted
|
|
610
|
+
self.file_count = file_count
|
|
611
|
+
self.registry_count = registry_count
|
|
612
|
+
|
|
613
|
+
def as_primitives(self) -> Dict[str, Any]:
|
|
614
|
+
return {
|
|
615
|
+
"image": self.image,
|
|
616
|
+
"start_time": self.start_time,
|
|
617
|
+
"ppid": self.ppid,
|
|
618
|
+
"pid": self.pid,
|
|
619
|
+
"command_line": self.command_line,
|
|
620
|
+
"end_time": self.end_time,
|
|
621
|
+
"integrity_level": self.integrity_level,
|
|
622
|
+
"image_hash": self.image_hash,
|
|
623
|
+
"original_file_name": self.original_file_name,
|
|
624
|
+
"safelisted": self.safelisted,
|
|
625
|
+
"file_count": self.file_count,
|
|
626
|
+
"registry_count": self.registry_count,
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
class SandboxNetworkDNS:
|
|
633
|
+
"""Details of a DNS query observed during sandbox execution."""
|
|
634
|
+
|
|
635
|
+
def __init__(
|
|
636
|
+
self,
|
|
637
|
+
# The domain name requested (queried).
|
|
638
|
+
domain: str,
|
|
639
|
+
|
|
640
|
+
# The DNS lookup type (e.g., "A", "AAAA", "MX").
|
|
641
|
+
lookup_type: LookupType,
|
|
642
|
+
|
|
643
|
+
# A list of IP addresses returned in the DNS response.
|
|
644
|
+
resolved_ips: Optional[List[str]] = None,
|
|
645
|
+
|
|
646
|
+
# A list of domain names returned in the DNS response (for CNAMEs, etc.).
|
|
647
|
+
resolved_domains: Optional[List[str]] = None,
|
|
648
|
+
):
|
|
649
|
+
self.domain = domain
|
|
650
|
+
self.lookup_type = lookup_type
|
|
651
|
+
self.resolved_ips = resolved_ips or []
|
|
652
|
+
self.resolved_domains = resolved_domains or []
|
|
653
|
+
|
|
654
|
+
def as_primitives(self) -> Dict[str, Any]:
|
|
655
|
+
return {
|
|
656
|
+
"domain": self.domain,
|
|
657
|
+
"lookup_type": self.lookup_type,
|
|
658
|
+
"resolved_ips": self.resolved_ips,
|
|
659
|
+
"resolved_domains": self.resolved_domains,
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
class SandboxNetworkHTTP:
|
|
664
|
+
"""Details of an HTTP request/response observed during sandbox execution."""
|
|
665
|
+
|
|
666
|
+
def __init__(
|
|
667
|
+
self,
|
|
668
|
+
# The URI requested by the process.
|
|
669
|
+
request_uri: str,
|
|
670
|
+
|
|
671
|
+
# Headers included in the HTTP request.
|
|
672
|
+
request_headers: Optional[Dict[str, Any]] = None,
|
|
673
|
+
|
|
674
|
+
# The HTTP request method (e.g., "GET", "POST").
|
|
675
|
+
request_method: Optional[RequestMethod] = None,
|
|
676
|
+
|
|
677
|
+
# Headers included in the HTTP response.
|
|
678
|
+
response_headers: Optional[Dict[str, Any]] = None,
|
|
679
|
+
|
|
680
|
+
# The raw body content of the HTTP request.
|
|
681
|
+
request_body: Optional[str] = None,
|
|
682
|
+
|
|
683
|
+
# The HTTP status code of the response (e.g., 200, 404).
|
|
684
|
+
response_status_code: Optional[int] = None,
|
|
685
|
+
|
|
686
|
+
# The raw body content of the HTTP response.
|
|
687
|
+
response_body: Optional[str] = None,
|
|
688
|
+
|
|
689
|
+
# Metadata about any file contained in the HTTP response body.
|
|
690
|
+
response_content_fileinfo: Optional[Dict[str, Any]] = None,
|
|
691
|
+
|
|
692
|
+
# The MIME type of the HTTP response content.
|
|
693
|
+
response_content_mimetype: Optional[str] = None,
|
|
694
|
+
):
|
|
695
|
+
self.request_uri = request_uri
|
|
696
|
+
self.request_headers = request_headers or {}
|
|
697
|
+
self.request_method = request_method
|
|
698
|
+
self.response_headers = response_headers or {}
|
|
699
|
+
self.request_body = request_body
|
|
700
|
+
self.response_status_code = response_status_code
|
|
701
|
+
self.response_body = response_body
|
|
702
|
+
self.response_content_fileinfo = response_content_fileinfo
|
|
703
|
+
self.response_content_mimetype = response_content_mimetype
|
|
704
|
+
|
|
705
|
+
def as_primitives(self) -> Dict[str, Any]:
|
|
706
|
+
return {
|
|
707
|
+
"request_uri": self.request_uri,
|
|
708
|
+
"request_headers": self.request_headers,
|
|
709
|
+
"request_method": self.request_method,
|
|
710
|
+
"response_headers": self.response_headers,
|
|
711
|
+
"request_body": self.request_body,
|
|
712
|
+
"response_status_code": self.response_status_code,
|
|
713
|
+
"response_body": self.response_body,
|
|
714
|
+
"response_content_fileinfo": self.response_content_fileinfo,
|
|
715
|
+
"response_content_mimetype": self.response_content_mimetype,
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
class SandboxNetworkSMTP:
|
|
720
|
+
"""Details of an SMTP email transaction observed during sandbox execution."""
|
|
721
|
+
|
|
722
|
+
def __init__(
|
|
723
|
+
self,
|
|
724
|
+
# The sender email address in the SMTP transaction.
|
|
725
|
+
mail_from: str,
|
|
726
|
+
|
|
727
|
+
# A list of recipient email addresses in the SMTP transaction.
|
|
728
|
+
mail_to: List[str],
|
|
729
|
+
|
|
730
|
+
# Information about any attachments transmitted via SMTP.
|
|
731
|
+
attachments: Optional[List[Dict[str, Any]]] = None,
|
|
732
|
+
):
|
|
733
|
+
self.mail_from = mail_from
|
|
734
|
+
self.mail_to = mail_to
|
|
735
|
+
self.attachments = attachments or []
|
|
736
|
+
|
|
737
|
+
def as_primitives(self) -> Dict[str, Any]:
|
|
738
|
+
return {
|
|
739
|
+
"mail_from": self.mail_from,
|
|
740
|
+
"mail_to": self.mail_to,
|
|
741
|
+
"attachments": self.attachments,
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
class SandboxNetflowItem:
|
|
746
|
+
"""Details of a network flow (connection) observed during sandbox execution."""
|
|
747
|
+
|
|
748
|
+
def __init__(
|
|
749
|
+
self,
|
|
750
|
+
# The destination IP address of the network connection.
|
|
751
|
+
destination_ip: Optional[str] = None,
|
|
752
|
+
|
|
753
|
+
# The destination port number of the connection.
|
|
754
|
+
destination_port: Optional[int] = None,
|
|
755
|
+
|
|
756
|
+
# The transport layer protocol used (e.g., "tcp", "udp").
|
|
757
|
+
transport_layer_protocol: Optional[Literal["tcp", "udp"]] = None,
|
|
758
|
+
|
|
759
|
+
# The direction of the network connection (e.g., "inbound", "outbound").
|
|
760
|
+
direction: Optional["ConnectionDirection"] = None,
|
|
761
|
+
|
|
762
|
+
# The process ID that initiated or owned the network connection.
|
|
763
|
+
process: Optional[int] = None,
|
|
764
|
+
|
|
765
|
+
# The source IP address of the connection.
|
|
766
|
+
source_ip: Optional[str] = None,
|
|
767
|
+
|
|
768
|
+
# The source port number of the connection.
|
|
769
|
+
source_port: Optional[int] = None,
|
|
770
|
+
|
|
771
|
+
# The timestamp when the network event was observed (ISO 8601 format).
|
|
772
|
+
time_observed: Optional[str] = None,
|
|
773
|
+
|
|
774
|
+
# Detailed HTTP request/response data, if the flow is HTTP-related.
|
|
775
|
+
http_details: Optional["SandboxNetworkHTTP"] = None,
|
|
776
|
+
|
|
777
|
+
# Detailed DNS query/response data, if the flow is DNS-related.
|
|
778
|
+
dns_details: Optional["SandboxNetworkDNS"] = None,
|
|
779
|
+
|
|
780
|
+
# Detailed SMTP email data, if the flow is SMTP-related.
|
|
781
|
+
smtp_details: Optional[SandboxNetworkSMTP] = None,
|
|
782
|
+
|
|
783
|
+
# The type or category of the connection (e.g., "download", "upload").
|
|
784
|
+
connection_type: Optional["ConnectionType"] = None,
|
|
785
|
+
):
|
|
786
|
+
self.destination_ip = destination_ip
|
|
787
|
+
self.destination_port = destination_port
|
|
788
|
+
self.transport_layer_protocol = transport_layer_protocol
|
|
789
|
+
self.direction = direction
|
|
790
|
+
self.process = process
|
|
791
|
+
self.source_ip = source_ip
|
|
792
|
+
self.source_port = source_port
|
|
793
|
+
self.time_observed = time_observed
|
|
794
|
+
self.http_details = http_details
|
|
795
|
+
self.dns_details = dns_details
|
|
796
|
+
self.smtp_details = smtp_details
|
|
797
|
+
self.connection_type = connection_type
|
|
798
|
+
|
|
799
|
+
def as_primitives(self) -> Dict[str, Any]:
|
|
800
|
+
data: Dict[str, Any] = {
|
|
801
|
+
"destination_ip": self.destination_ip,
|
|
802
|
+
"destination_port": self.destination_port,
|
|
803
|
+
"transport_layer_protocol": self.transport_layer_protocol,
|
|
804
|
+
"direction": self.direction,
|
|
805
|
+
"process": self.process,
|
|
806
|
+
"source_ip": self.source_ip,
|
|
807
|
+
"source_port": self.source_port,
|
|
808
|
+
"time_observed": self.time_observed,
|
|
809
|
+
"connection_type": self.connection_type,
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if self.http_details is not None:
|
|
813
|
+
data["http_details"] = self.http_details.as_primitives()
|
|
814
|
+
if self.dns_details is not None:
|
|
815
|
+
data["dns_details"] = self.dns_details.as_primitives()
|
|
816
|
+
if self.smtp_details is not None:
|
|
817
|
+
data["smtp_details"] = self.smtp_details.as_primitives()
|
|
818
|
+
|
|
819
|
+
return data
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
class SandboxAttackItem:
|
|
823
|
+
"""Describes an ATT&CK technique or tactic detected during sandbox execution."""
|
|
824
|
+
|
|
825
|
+
def __init__(
|
|
826
|
+
self,
|
|
827
|
+
# The MITRE ATT&CK technique ID (e.g., "T1059.001").
|
|
828
|
+
attack_id: str,
|
|
829
|
+
|
|
830
|
+
# The name or pattern describing the attack behavior.
|
|
831
|
+
pattern: str,
|
|
832
|
+
|
|
833
|
+
# The list of categories or tactics associated with this attack.
|
|
834
|
+
categories: List[str] = [],
|
|
835
|
+
):
|
|
836
|
+
self.attack_id = attack_id
|
|
837
|
+
self.pattern = pattern
|
|
838
|
+
self.categories = categories or []
|
|
839
|
+
|
|
840
|
+
def as_primitives(self) -> Dict[str, Any]:
|
|
841
|
+
return {
|
|
842
|
+
"attack_id": self.attack_id,
|
|
843
|
+
"pattern": self.pattern,
|
|
844
|
+
"categories": self.categories,
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
|
|
848
|
+
class SandboxSignatureItem:
|
|
849
|
+
"""Represents a detection signature triggered during analysis."""
|
|
850
|
+
|
|
851
|
+
def __init__(
|
|
852
|
+
self,
|
|
853
|
+
# The name of the detection signature.
|
|
854
|
+
name: str,
|
|
855
|
+
|
|
856
|
+
# The source type of the signature (e.g., "CUCKOO", "YARA", "SIGMA", "SURICATA").
|
|
857
|
+
type: Literal["CUCKOO", "YARA", "SIGMA", "SURICATA"],
|
|
858
|
+
|
|
859
|
+
# The classification of the signature (e.g., "malicious", "benign").
|
|
860
|
+
classification: str,
|
|
861
|
+
|
|
862
|
+
# The list of ATT&CK patterns or related attack metadata linked to this signature.
|
|
863
|
+
attacks: Optional[List[SandboxAttackItem]] = None,
|
|
864
|
+
|
|
865
|
+
# The list of threat actors associated with this signature.
|
|
866
|
+
actors: Optional[List[str]] = None,
|
|
867
|
+
|
|
868
|
+
# The list of malware families linked to this signature.
|
|
869
|
+
malware_families: Optional[List[str]] = None,
|
|
870
|
+
|
|
871
|
+
# A human-readable description of what the signature represents.
|
|
872
|
+
description: Optional[str] = None,
|
|
873
|
+
|
|
874
|
+
# The list of process IDs (PIDs) that triggered the signature.
|
|
875
|
+
pid: Optional[List[int]] = None,
|
|
876
|
+
|
|
877
|
+
# The score or weight associated with the heuristic signature.
|
|
878
|
+
score: Optional[int] = None,
|
|
879
|
+
):
|
|
880
|
+
self.name = name
|
|
881
|
+
self.type = type
|
|
882
|
+
self.classification = classification
|
|
883
|
+
self.attacks = attacks or []
|
|
884
|
+
self.actors = actors or []
|
|
885
|
+
self.malware_families = malware_families or []
|
|
886
|
+
self.description = description
|
|
887
|
+
self.pid = pid or []
|
|
888
|
+
self.score = score
|
|
889
|
+
|
|
890
|
+
def as_primitives(self) -> Dict[str, Any]:
|
|
891
|
+
return {
|
|
892
|
+
"name": self.name,
|
|
893
|
+
"type": self.type,
|
|
894
|
+
"classification": self.classification,
|
|
895
|
+
"attacks": [a.as_primitives() for a in self.attacks] if self.attacks else [],
|
|
896
|
+
"actors": self.actors,
|
|
897
|
+
"malware_families": self.malware_families,
|
|
898
|
+
"description": self.description,
|
|
899
|
+
"pid": self.pid,
|
|
900
|
+
"score": self.score,
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
class SandboxSectionBody(SectionBody):
|
|
907
|
+
"""The main sandbox analysis body containing all observed data and metadata."""
|
|
908
|
+
|
|
909
|
+
def __init__(self) -> None:
|
|
910
|
+
super().__init__(BODY_FORMAT.SANDBOX, body={
|
|
911
|
+
"analysis_information": None,
|
|
912
|
+
"processes": [],
|
|
913
|
+
"network_connections": [],
|
|
914
|
+
"signatures": [],
|
|
915
|
+
})
|
|
916
|
+
|
|
917
|
+
def set_analysis_information(
|
|
918
|
+
self,
|
|
919
|
+
# The name of the sandbox used to perform the analysis.
|
|
920
|
+
sandbox_name: str,
|
|
921
|
+
|
|
922
|
+
# The version of the sandbox software.
|
|
923
|
+
sandbox_version: str,
|
|
924
|
+
|
|
925
|
+
# Metadata about when and how the analysis was executed.
|
|
926
|
+
analysis_metadata: SandboxAnalysisMetadata,
|
|
927
|
+
) -> None:
|
|
928
|
+
"""Set the general analysis information for the sandbox execution."""
|
|
929
|
+
self._data["analysis_information"] = {
|
|
930
|
+
"sandbox_name": sandbox_name,
|
|
931
|
+
"sandbox_version": sandbox_version,
|
|
932
|
+
"analysis_metadata": analysis_metadata.as_primitives(),
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
def add_process(self, process: SandboxProcessItem) -> None:
|
|
936
|
+
"""Add a single process to the sandbox result."""
|
|
937
|
+
if not isinstance(process, SandboxProcessItem):
|
|
938
|
+
raise TypeError("Expected SandboxProcessItem")
|
|
939
|
+
self._data["processes"].append(process.as_primitives())
|
|
940
|
+
|
|
941
|
+
def add_processes(self, processes: List[SandboxProcessItem]) -> None:
|
|
942
|
+
"""Add multiple processes to the sandbox result."""
|
|
943
|
+
for proc in processes:
|
|
944
|
+
self.add_process(proc)
|
|
945
|
+
|
|
946
|
+
def add_network_connection(self, connection: SandboxNetflowItem) -> None:
|
|
947
|
+
"""Add a single network connection to the sandbox result."""
|
|
948
|
+
if not isinstance(connection, SandboxNetflowItem):
|
|
949
|
+
raise TypeError("Expected SandboxNetflowItem")
|
|
950
|
+
self._data["network_connections"].append(connection.as_primitives())
|
|
951
|
+
|
|
952
|
+
def add_network_connections(self, connections: List[SandboxNetflowItem]) -> None:
|
|
953
|
+
"""Add multiple network connections to the sandbox result."""
|
|
954
|
+
for conn in connections:
|
|
955
|
+
self.add_network_connection(conn)
|
|
956
|
+
|
|
957
|
+
def add_signature(self, signature: SandboxSignatureItem) -> None:
|
|
958
|
+
"""Add a single detection signature to the sandbox result."""
|
|
959
|
+
if not isinstance(signature, SandboxSignatureItem):
|
|
960
|
+
raise TypeError("Expected SandboxSignatureItem")
|
|
961
|
+
self._data["signatures"].append(signature.as_primitives())
|
|
962
|
+
|
|
963
|
+
def add_signatures(self, signatures: List[SandboxSignatureItem]) -> None:
|
|
964
|
+
"""Add multiple detection signatures to the sandbox result."""
|
|
965
|
+
for sig in signatures:
|
|
966
|
+
self.add_signature(sig)
|
|
967
|
+
|
|
968
|
+
def as_primitives(self) -> Dict[str, Any]:
|
|
969
|
+
"""Return a fully JSON-serializable structure."""
|
|
970
|
+
return {
|
|
971
|
+
"analysis_information": self._data["analysis_information"],
|
|
972
|
+
"processes": self._data["processes"],
|
|
973
|
+
"network_connections": self._data["network_connections"],
|
|
974
|
+
"signatures": self._data["signatures"],
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
|
|
413
978
|
class TableRow(dict):
|
|
414
979
|
def __init__(self, *args, **kwargs) -> None:
|
|
415
980
|
data = {}
|
|
@@ -474,7 +1039,7 @@ class TimelineSectionBody(SectionBody):
|
|
|
474
1039
|
def add_node(self, title: str, content: str, opposite_content: str,
|
|
475
1040
|
icon: str = None, signatures: List[str] = [], score: int = 0) -> None:
|
|
476
1041
|
self._data.append(dict(title=title, content=content, opposite_content=opposite_content,
|
|
477
|
-
|
|
1042
|
+
icon=icon, signatures=signatures, score=score))
|
|
478
1043
|
|
|
479
1044
|
|
|
480
1045
|
class ResultSection:
|
|
@@ -795,6 +1360,54 @@ class ResultProcessTreeSection(TypeSpecificResultSection):
|
|
|
795
1360
|
self.section_body.add_process(process)
|
|
796
1361
|
|
|
797
1362
|
|
|
1363
|
+
class ResultSandboxSection(TypeSpecificResultSection):
|
|
1364
|
+
"""
|
|
1365
|
+
Represents a result section specifically designed for sandbox analysis data.
|
|
1366
|
+
Provides a typed interface to manipulate the underlying SandboxSectionBody.
|
|
1367
|
+
"""
|
|
1368
|
+
|
|
1369
|
+
def __init__(self, title_text: Union[str, List], **kwargs):
|
|
1370
|
+
self.section_body: SandboxSectionBody
|
|
1371
|
+
super().__init__(title_text, SandboxSectionBody(), **kwargs)
|
|
1372
|
+
|
|
1373
|
+
def set_analysis_information(
|
|
1374
|
+
self,
|
|
1375
|
+
sandbox_name: Optional[str],
|
|
1376
|
+
sandbox_version: Optional[str],
|
|
1377
|
+
analysis_metadata: Optional[SandboxAnalysisMetadata],
|
|
1378
|
+
) -> None:
|
|
1379
|
+
"""Set the general analysis information for the sandbox execution."""
|
|
1380
|
+
self.section_body.set_analysis_information(
|
|
1381
|
+
sandbox_name,
|
|
1382
|
+
sandbox_version,
|
|
1383
|
+
analysis_metadata,
|
|
1384
|
+
)
|
|
1385
|
+
|
|
1386
|
+
def add_process(self, process: SandboxProcessItem) -> None:
|
|
1387
|
+
"""Add a single process to the sandbox result."""
|
|
1388
|
+
self.section_body.add_process(process)
|
|
1389
|
+
|
|
1390
|
+
def add_processes(self, processes: List[SandboxProcessItem]) -> None:
|
|
1391
|
+
"""Add multiple processes to the sandbox result."""
|
|
1392
|
+
self.section_body.add_processes(processes)
|
|
1393
|
+
|
|
1394
|
+
def add_network_connection(self, connection: SandboxNetflowItem) -> None:
|
|
1395
|
+
"""Add a single network connection to the sandbox result."""
|
|
1396
|
+
self.section_body.add_network_connection(connection)
|
|
1397
|
+
|
|
1398
|
+
def add_network_connections(self, connections: List[SandboxNetflowItem]) -> None:
|
|
1399
|
+
"""Add multiple network connections to the sandbox result."""
|
|
1400
|
+
self.section_body.add_network_connections(connections)
|
|
1401
|
+
|
|
1402
|
+
def add_signature(self, signature: SandboxSignatureItem) -> None:
|
|
1403
|
+
"""Add a single detection signature to the sandbox result."""
|
|
1404
|
+
self.section_body.add_signature(signature)
|
|
1405
|
+
|
|
1406
|
+
def add_signatures(self, signatures: List[SandboxSignatureItem]) -> None:
|
|
1407
|
+
"""Add multiple detection signatures to the sandbox result."""
|
|
1408
|
+
self.section_body.add_signatures(signatures)
|
|
1409
|
+
|
|
1410
|
+
|
|
798
1411
|
class ResultTableSection(TypeSpecificResultSection):
|
|
799
1412
|
def __init__(self, title_text: Union[str, List], **kwargs):
|
|
800
1413
|
self.section_body: TableSectionBody
|
|
@@ -2,9 +2,9 @@ import json
|
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
4
|
import tempfile
|
|
5
|
-
from typing import Any, Dict, List, Optional
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
6
|
|
|
7
|
-
from assemblyline_v4_service.common.api import
|
|
7
|
+
from assemblyline_v4_service.common.api import ServiceAPI
|
|
8
8
|
from assemblyline_v4_service.common.helper import get_service_manifest
|
|
9
9
|
from assemblyline_v4_service.common.result import Result
|
|
10
10
|
|
|
@@ -76,7 +76,7 @@ class Task:
|
|
|
76
76
|
self.service_config: Dict[str, Any] = dict(task.service_config)
|
|
77
77
|
self.service_context: Optional[str] = None
|
|
78
78
|
self.service_debug_info: Optional[str] = None
|
|
79
|
-
self.service_default_result_classification = None
|
|
79
|
+
self.service_default_result_classification: Optional[str] = None
|
|
80
80
|
self.service_name: str = task.service_name
|
|
81
81
|
self.service_tool_version: Optional[str] = None
|
|
82
82
|
self.service_version: Optional[str] = None
|
|
@@ -88,7 +88,7 @@ class Task:
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
def _add_file(self, path: str, name: str, description: str,
|
|
91
|
-
classification: Optional[
|
|
91
|
+
classification: Optional[str] = None,
|
|
92
92
|
is_section_image: bool = False,
|
|
93
93
|
is_supplementary: bool = False,
|
|
94
94
|
allow_dynamic_recursion: bool = False,
|
|
@@ -131,7 +131,7 @@ class Task:
|
|
|
131
131
|
|
|
132
132
|
def add_extracted(self, path: str, name: str, description: str,
|
|
133
133
|
classification: Optional[Classification] = None,
|
|
134
|
-
safelist_interface: Optional[
|
|
134
|
+
safelist_interface: Optional[ServiceAPI] = None,
|
|
135
135
|
allow_dynamic_recursion: bool = False, parent_relation: str = PARENT_RELATION.EXTRACTED) -> bool:
|
|
136
136
|
|
|
137
137
|
# Service-based safelisting of files has to be configured at the global configuration
|
|
@@ -301,7 +301,7 @@ class Task:
|
|
|
301
301
|
def set_service_context(self, context: str) -> None:
|
|
302
302
|
self.service_context = context
|
|
303
303
|
|
|
304
|
-
def start(self, service_default_result_classification:
|
|
304
|
+
def start(self, service_default_result_classification: str,
|
|
305
305
|
service_version: str, service_tool_version: Optional[str] = None) -> None:
|
|
306
306
|
self.service_version = service_version
|
|
307
307
|
self.service_tool_version = service_tool_version
|