diff --git a/.isort.cfg b/.isort.cfg index f55fec7..d535851 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -9,7 +9,6 @@ known_third_party = simplejson six, stix2patterns, - stix2validator, taxii2client, known_first_party = stix2 force_sort_within_sections = 1 diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..c9ec75b --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include LICENSE +include CHANGELOG +recursive-exclude stix2\test * diff --git a/README.rst b/README.rst index c78923b..faacc53 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,7 @@ including data markings, versioning, and for resolving STIX IDs across multiple data sources. For more information, see `the -documentation `__ on +documentation `__ on ReadTheDocs. Installation @@ -62,6 +62,16 @@ To parse a STIX JSON string into a Python STIX object, use ``parse()``: For more in-depth documentation, please see `https://stix2.readthedocs.io/ `__. +STIX 2.X Technical Specification Support +---------------------------------------- + +This version of python-stix2 supports STIX 2.0 by default. Although, the +`stix2` Python library is built to support multiple versions of the STIX +Technical Specification. With every major release of stix2 the ``import stix2`` +statement will automatically load the SDO/SROs equivalent to the most recent +supported 2.X Technical Specification. Please see the library documentation +for more details. + Governance ---------- diff --git a/docs/guide/taxii.ipynb b/docs/guide/taxii.ipynb index 2890659..016f9d8 100644 --- a/docs/guide/taxii.ipynb +++ b/docs/guide/taxii.ipynb @@ -71,7 +71,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 18, "metadata": {}, "outputs": [ { @@ -79,21 +79,27 @@ "output_type": "stream", "text": [ "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--fb2c0e55-52a0-423c-b544-8b09622cafc1\",\n", - " \"created\": \"2017-10-02T19:26:30.000Z\",\n", - " \"modified\": \"2017-10-02T19:26:30.000Z\",\n", + " \"type\": \"malware\",\n", + " \"id\": \"malware--fdd60b30-b67c-11e3-b0b9-f01faf20d111\",\n", + " \"created\": \"2017-01-27T13:49:53.997Z\",\n", + " \"modified\": \"2017-01-27T13:49:53.997Z\",\n", + " \"name\": \"Poison Ivy\",\n", + " \"description\": \"Poison Ivy\",\n", " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '98.138.19.88' ]\",\n", - " \"valid_from\": \"2017-10-02T19:26:30Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", + " \"remote-access-trojan\"\n", + " ]\n", + "}\n", + "-------\n", + "{\n", + " \"type\": \"indicator\",\n", + " \"id\": \"indicator--a932fcc6-e032-176c-126f-cb970a5a1ade\",\n", + " \"created\": \"2014-05-08T09:00:00.000Z\",\n", + " \"modified\": \"2014-05-08T09:00:00.000Z\",\n", + " \"name\": \"File hash for Poison Ivy variant\",\n", + " \"pattern\": \"[file:hashes.'SHA-256' = 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c']\",\n", + " \"valid_from\": \"2014-05-08T09:00:00Z\",\n", + " \"labels\": [\n", + " \"file-hash-watchlist\"\n", " ]\n", "}\n" ] @@ -104,2293 +110,40 @@ "from taxii2client import Collection\n", "\n", "# establish TAXII2 Collection instance\n", - "collection = Collection(\"https://test.freetaxii.com:8000/api1/collections/9cfa669c-ee94-4ece-afd2-f8edac37d8fd/\")\n", + "collection = Collection(\"http://127.0.0.1:5000/trustgroup1/collections/91a7b528-80eb-42ed-a74d-c6fbd5a26116/\", user=\"admin\", password=\"Password0\")\n", "# supply the TAXII2 collection to TAXIICollection\n", "tc_source = TAXIICollectionSource(collection)\n", "\n", - "#retrieve STIX object by id\n", - "stix_obj = tc_source.get(\"indicator--0f63229c-07a2-46dd-939d-312c7bf6d114\")\n", + "#retrieve STIX objects by id\n", + "stix_obj = tc_source.get(\"malware--fdd60b30-b67c-11e3-b0b9-f01faf20d111\")\n", + "stix_obj_versions = tc_source.all_versions(\"indicator--a932fcc6-e032-176c-126f-cb970a5a1ade\")\n", "\n", "#for visual purposes\n", - "print(stix_obj)\n" + "print(stix_obj)\n", + "print(\"-------\")\n", + "for so in stix_obj_versions:\n", + " print(so)\n" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "indicators: 126\n", "{\n", " \"type\": \"indicator\",\n", - " \"id\": \"indicator--569b8969-bfce-4ab4-9a45-06ce78799a35\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", + " \"id\": \"indicator--a932fcc6-e032-176c-126f-cb970a5a1ade\",\n", + " \"created\": \"2014-05-08T09:00:00.000Z\",\n", + " \"modified\": \"2014-05-08T09:00:00.000Z\",\n", + " \"name\": \"File hash for Poison Ivy variant\",\n", + " \"pattern\": \"[file:hashes.'SHA-256' = 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c']\",\n", + " \"valid_from\": \"2014-05-08T09:00:00Z\",\n", " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '207.158.1.150' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--9c418633-9970-424e-8030-2c3dfa3105da\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '64.4.30.34' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--9d7cdfc1-94c3-49b5-b124-ebdce709fd99\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '216.152.67.22' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--37390a22-5d82-4ebc-9b90-7368a5efc8f7\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '69.16.172.34' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--30731d72-64b0-4851-bd97-c3d164d2fd2b\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '194.24.188.100' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--a4eb3524-992c-4b50-9729-99be3048625e\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '213.232.93.3' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--c00fb599-7e7b-4033-a6c2-d279212578a0\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '216.152.66.45' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--e7273b13-847c-4a69-8faf-08fc24af5ef0\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '89.16.176.16' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--b8d21867-c812-4ff9-866b-182a801b88ce\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '130.239.18.172' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--4c39b1a0-17f0-4cf1-9e48-250f0dd1f75c\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '140.211.166.4' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--8eeff049-f7da-45d9-89bb-713063baed2c\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '213.92.8.4' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--e3981158-1934-4236-8454-4dcfc27ac248\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '208.87.120.111' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--206c2a0c-149f-426f-a734-c0c534aa396b\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '216.93.243.34' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--58d7aa16-8baf-4026-b3d7-328267ed4bab\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '216.165.191.52' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--e6fd4a21-8290-40e5-9b1c-701f6f11e260\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '195.204.1.132' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--cca5ce5f-4c0e-4031-9997-063eb3badead\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '209.177.146.34' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--43a7784e-f11c-4739-91a8-dc87d05ddbb6\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '145.220.21.40' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--5716d954-e5b1-4bec-ba43-80b1053dee61\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '50.7.55.82' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--9135d4ab-a807-495b-8fff-b8433342501f\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '82.165.47.254' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--e070c86b-40e5-49ea-8d83-56bcae10b303\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '140.211.166.3' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--f4125383-930c-42ae-b57f-2c36f898d0b5\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '208.71.169.36' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--fa063c6a-1a9f-4a58-9470-ed80a23cc943\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '204.152.221.218' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--41b3ba86-dd1b-4f3d-a156-5dc27f31fb40\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '78.40.125.4' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--a9fcaba5-cd50-447d-8540-2dfe4e3c6c88\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '216.152.66.68' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--30b68eff-3c38-4c74-9783-1114a7759066\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '195.197.175.21' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--f10fa7c0-7a10-434e-908f-59a7e25e18c0\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '194.14.236.50' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--183f8cd7-2e6f-4073-bbe8-d5dc6b570fac\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '69.16.172.34' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--dd95ff3a-3ef1-409e-827b-087eb9cc3b2c\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '140.211.166.3' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--a97dc9cb-2b9f-4c1d-92cc-2fc15100e3ed\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '91.205.185.104' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--5552096e-b2b8-4057-bf5e-ccf300b8276e\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '193.163.220.3' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--0cc30ea9-eeaf-4f39-ab8d-3d2664c2b75e\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '64.202.189.170' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--7582ed02-c78d-451d-b0a5-065ae511f3ae\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '86.65.39.15' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--37fde688-ca75-4c1e-b5e1-1acb5bbfb23c\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '140.211.167.98' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--e967d3a0-0cfe-482c-b53a-390c0bb564f4\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '199.16.156.6' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--fda4f25d-8252-4593-bd8b-0a90764a561f\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '217.168.95.245' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--109b3de1-2353-42dc-8316-e2f7c0b5c67d\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '192.99.16.195' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--1efa50e4-ed2c-4fb5-ae9b-cb347bd4ad24\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '64.18.128.86' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--c7b60a1a-4c93-451f-b7c1-993c0dc14391\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '194.109.129.220' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--469381d9-c24e-4cf4-b25b-18a48975ef14\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '208.99.193.130' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--c5694bbd-3a11-4c16-ae73-eeed55acf9cc\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '70.84.101.150' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--a9b4301e-0327-4edc-b407-b7915bb0e7bc\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '64.4.30.62' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--f5ac23ca-8ab4-4597-837b-3d5e48d325cb\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '64.4.30.61' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--1a2a539b-d3f3-410b-a32c-4d1a5599364e\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '66.186.59.50' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--585e6f7b-7bad-45b0-a36b-9f3b3bff72c6\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '93.152.160.101' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--0a7dd603-d826-428f-b5f7-c82ff8bb60f3\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '216.152.66.46' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--cb2cebd2-c11f-43b1-a9a1-3c4b9893f38a\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '192.99.150.27' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--6a6c81df-7cb9-48b3-a4ea-db6924e47b5d\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '193.107.206.21' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--45177dce-6cfe-44b5-ac41-cbc1bee80527\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '69.16.172.40' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--6f58bdf5-1f26-4a17-8ba3-14c023e73a0f\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '72.51.18.254' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--d5731bef-623c-4793-994c-a6f3840bc2cf\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '193.190.67.98' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--4e8ac337-2e00-4d71-8526-bbfdb105e77f\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '140.211.166.4' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--b681b1fc-7cce-473e-81e9-f5f3657cf85b\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '130.237.188.200' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--08453fee-f3b8-449a-95a8-abc0d79710c3\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '216.155.130.130' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--79e2a4f6-ee8d-4466-8e82-ecb928e87c0d\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '208.71.169.36' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--2d3326c5-c112-4670-b6bd-6de667f4280b\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '216.152.66.47' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--4adc0666-89d1-4c67-a3c8-3b02fc351442\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '213.161.196.11' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--dc1e9fec-6d1e-46a1-902c-dc170424a23f\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '195.47.220.2' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--2d7480b1-ded5-4466-a1dd-470110eacdba\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '152.3.102.53' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--f06d6873-1538-4951-a069-d6af0dd0f8ed\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '84.208.29.17' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--4eaf258d-28d8-48d8-98f8-0d8442ba83fa\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '82.96.64.4' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--d7e4bba4-485d-4c1f-95c0-55e7d8a015f8\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '213.179.58.83' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--5c060dc8-a8cd-4067-985d-52d85ab3f256\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '128.237.157.136' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--d397fccb-3dbb-47c3-84ae-aa09f4223eca\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '193.110.95.1' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--d37d0928-c86b-474a-85ef-46e942fff510\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '98.138.19.88' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--5e6dd813-58bd-454e-9be7-246f3db01999\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '69.16.172.40' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--adb3c6bc-9694-471e-bf1f-0d0a02d70876\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '137.226.34.46' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--3ce88e57-edfb-45fa-81be-ed95d4564316\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '67.198.195.194' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--fbce496c-e9a6-4246-ad12-73b8f5a12a2a\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '149.9.1.16' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--efce84a3-0d17-4ae8-88be-86c86aa80bbd\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '193.109.122.77' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--20789570-8c07-42c4-8a45-b3ab170cf6ee\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '209.126.116.149' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--5c1b2889-6fec-4276-83e0-173938934ba9\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '64.250.116.136' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--bdad2fdb-71bd-49c3-8bf2-50d396fa55d5\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '163.172.17.231' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--07fd3e36-5500-4652-935f-23a2955b19f3\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '38.114.116.5' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--9e70a102-3440-4ad0-ab1d-653144632668\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '66.186.59.50' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--f566b659-ca36-42a9-8ebf-9476e6b651ab\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '195.204.1.130' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--40c0d87c-287a-4692-8227-b4976d14a5f0\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '212.27.60.27' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--da56b536-6ac7-44d5-a036-0db986926016\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '213.236.208.178' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--95f9c0f4-351b-43c9-81da-c5fdcfe4fa6d\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '94.125.182.252' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--5007db19-0906-4aec-b18b-e0819b3f13de\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '208.83.20.130' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--9e0667cd-9a83-4e19-b16f-78c3ed33bfc5\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '216.18.228.34' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--f170e9a9-abb8-4919-9902-7a5214e95cde\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '192.99.150.28' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--a30f883d-956d-4fdd-b926-db81d1893d81\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '178.79.132.147' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--8fc0e9c0-4d4d-4c4f-86a7-2f6c07cd69a4\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '193.109.122.67' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--586dc7e8-a08e-4ec2-8365-e2ee897d9ca3\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '195.47.220.2' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--16c9900c-ce48-4306-b8fa-a2de726be847\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '208.83.20.130' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--13f73e28-acf7-45b8-a5e9-6c37af914ef2\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '174.143.119.91' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--a09c4e42-8843-4c84-a75f-684bf90c5207\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '74.208.174.239' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--76646197-18a2-4513-8465-ccf72734a2e1\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '216.152.66.48' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--1169c1db-fd5b-4dcf-b4cb-9c0101ef0ea2\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '212.117.163.190' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--cdeb6ddb-5151-49ea-a488-23d806063eff\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '216.155.130.130' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--05d1ab76-d0a1-4a58-8137-98f5fdbc777c\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '90.147.160.69' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--876d7d09-248a-45ad-bcce-d92c73ad5aa3\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '89.16.176.16' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--ae1f860d-dc4f-4953-9e74-d4d7c389fdef\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '85.188.1.26' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--91bb4edc-f29f-41ba-87d9-d6a81ac8fdba\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '130.239.18.172' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--f006d048-f24f-46fa-837b-8f7fa41b43ca\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '8.7.233.233' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--436dcbec-48e2-4dc2-90f0-0876a876a38a\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '216.152.66.54' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--ff18364d-99f6-4d3d-b267-8401518af42c\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '194.68.45.50' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--8b26f167-b0ad-469b-b221-12896e2a0966\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '64.4.30.33' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--171268fb-f6a7-4085-adf5-2055a461cb93\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '64.161.254.20' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--b56c7a58-71cb-47c2-b615-f4e8a89a0732\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '141.213.238.252' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--bf09ce9a-3bb9-47c8-a686-ea1d8e1adbe8\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '213.92.8.4' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--42490e45-7350-4f48-884b-5d1610794a32\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '72.14.191.81' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--c28e91bf-a9a1-4bac-b3f3-cda89c7d28b8\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '69.16.172.2' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--ebe624b5-fb73-420a-a110-c1dc82baa6e4\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '69.61.21.115' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--ef65505f-4898-4968-82b4-f980e9705d21\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '64.18.128.86' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--b33c35ce-20f6-4fba-912c-dbf7756113f9\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '161.53.178.240' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--b3785934-f4f0-4ce7-b20c-e4384886ec45\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '204.11.244.21' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--10bbe70c-7bd3-443a-8f2c-1e56cd7a8a54\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '216.93.242.10' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--bcb54665-3461-43e2-8dbf-6b92c2413f67\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '216.152.67.23' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--a407b16b-cf5b-4f3a-a153-ba4dac5ce0e0\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '205.188.234.121' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--e7e50d3a-802d-41c8-b667-a27d29871098\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '82.96.64.4' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--017dfb8c-84b9-402f-8401-428477af7be4\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '80.88.108.18' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--84664128-cc14-480b-8d90-735727fd4b9f\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '154.35.200.44' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--f0aa750f-82cb-47f9-9c74-ace584fdadcb\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '195.68.221.222' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--9461c426-6404-4b7a-8552-c29dc60c9123\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '195.197.175.21' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--ba59cc70-03e4-47f4-871e-d40b727267f3\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '78.129.164.123' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--1b48b107-92e2-487f-9eae-3496eb64e125\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '140.211.167.99' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--e9aea5e2-9ef6-40b6-8f12-dff6ccd8eff4\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '85.25.43.27' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--cdbd95b1-17fb-4b2f-89b6-8c0f865b9e4d\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '193.219.128.49' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--afe4738d-bd3c-47de-9cc5-97e248291571\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '195.40.6.37' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--5eecb66e-f8fa-4ab9-85e4-599db7790edf\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '173.252.110.27' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--40b6b332-9a5a-42a7-8b25-6e3eb6d371d4\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '38.229.70.20' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--a16905d7-4452-4e9f-88a3-fc9338ea5116\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '38.99.64.210' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--5fcfa412-514f-43b5-b873-ed8c9b70bbb0\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '192.99.200.113' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--8259bca6-7c9c-4967-b048-a6f13f333f90\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '68.168.184.57' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", - " ]\n", - "}\n", - "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--96763c7c-4f52-436a-919a-8b09c841f6bd\",\n", - " \"created\": \"2017-10-02T20:40:44.000Z\",\n", - " \"modified\": \"2017-10-02T20:40:44.000Z\",\n", - " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '64.237.34.150' ]\",\n", - " \"valid_from\": \"2017-10-02T20:40:44Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", + " \"file-hash-watchlist\"\n", " ]\n", "}\n" ] @@ -2406,7 +159,6 @@ "indicators = tc_source.query([f1])\n", "\n", "#for visual purposes\n", - "print(\"indicators: {0}\").format(str(len(indicators)))\n", "for indicator in indicators:\n", " print(indicator)" ] @@ -2457,7 +209,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -2465,21 +217,14 @@ "output_type": "stream", "text": [ "{\n", - " \"type\": \"indicator\",\n", - " \"id\": \"indicator--d8e1cd37-4a6c-4088-aded-ed79c4ea2caa\",\n", - " \"created\": \"2017-10-02T20:24:03.000Z\",\n", - " \"modified\": \"2017-10-02T20:24:03.000Z\",\n", + " \"type\": \"malware\",\n", + " \"id\": \"malware--fdd60b30-b67c-11e3-b0b9-f01faf20d111\",\n", + " \"created\": \"2017-01-27T13:49:53.997Z\",\n", + " \"modified\": \"2017-01-27T13:49:53.997Z\",\n", + " \"name\": \"Poison Ivy\",\n", + " \"description\": \"Poison Ivy\",\n", " \"labels\": [\n", - " \"malicious-activity\"\n", - " ],\n", - " \"name\": \"Emerging Threats - Block Rules - Compromised IPs\",\n", - " \"pattern\": \"[ ipv4-addr:value = '98.138.19.88' ]\",\n", - " \"valid_from\": \"2017-10-02T20:24:03Z\",\n", - " \"kill_chain_phases\": [\n", - " {\n", - " \"kill_chain_name\": \"lockheed-martin-cyber-kill-chain\",\n", - " \"phase_name\": \"delivery\"\n", - " }\n", + " \"remote-access-trojan\"\n", " ]\n", "}\n" ] @@ -2494,7 +239,7 @@ "\n", "# retrieve STIX object by id from TAXII Collection through\n", "# TAXIICollectionStore\n", - "stix_obj2 = tc_source.get(\"indicator--6850d393-36b6-4a67-ad45-f9e4d512c799\")\n", + "stix_obj2 = tc_source.get(\"malware--fdd60b30-b67c-11e3-b0b9-f01faf20d111\")\n", "\n", "print(stix_obj2)" ] @@ -2520,21 +265,21 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "cti-python-stix2", "language": "python", - "name": "python3" + "name": "cti-python-stix2" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 3 + "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.5.2" + "pygments_lexer": "ipython2", + "version": "2.7.12" } }, "nbformat": 4, diff --git a/docs/guide/ts_support.ipynb b/docs/guide/ts_support.ipynb new file mode 100644 index 0000000..f98d7b5 --- /dev/null +++ b/docs/guide/ts_support.ipynb @@ -0,0 +1,237 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Technical Specification Support\n", + "\n", + "### How imports will work\n", + "\n", + "Imports can be used in different ways depending on the use case and support levels.\n", + "\n", + "People who want to (in general) support the latest version of STIX 2.X without making changes, implicitly using the latest version" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import stix2\n", + "\n", + "stix2.Indicator()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "or," + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from stix2 import Indicator\n", + "\n", + "Indicator()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "People who want to use an explicit version" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import stix2.v20\n", + "\n", + "stix2.v20.Indicator()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "or," + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from stix2.v20 import Indicator\n", + "\n", + "Indicator()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "or even," + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import stix2.v20 as stix2\n", + "\n", + "stix2.Indicator()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The last option makes it easy to update to a new version in one place per file, once you've made the deliberate action to do this.\n", + "\n", + "People who want to use multiple versions in a single file:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import stix2\n", + "\n", + "stix2.v20.Indicator()\n", + "\n", + "stix2.v21.Indicator()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "or," + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from stix2 import v20, v21\n", + "\n", + "v20.Indicator()\n", + "v21.Indicator()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "or (less preferred):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from stix2.v20 import Indicator as Indicator_v20\n", + "from stix2.v21 import Indicator as Indicator_v21\n", + "\n", + "Indicator_v20()\n", + "Indicator_v21()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### How parsing will work\n", + "If the ``version`` positional argument is not provided. The data will be parsed using the latest version of STIX 2.X supported by the `stix2` library.\n", + "\n", + "You can lock your `parse()` method to a specific STIX version by" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from stix2 import parse\n", + "\n", + "indicator = parse(\"\"\"{\n", + " \"type\": \"indicator\",\n", + " \"id\": \"indicator--dbcbd659-c927-4f9a-994f-0a2632274394\",\n", + " \"created\": \"2017-09-26T23:33:39.829Z\",\n", + " \"modified\": \"2017-09-26T23:33:39.829Z\",\n", + " \"labels\": [\n", + " \"malicious-activity\"\n", + " ],\n", + " \"name\": \"File hash for malware variant\",\n", + " \"pattern\": \"[file:hashes.md5 = 'd41d8cd98f00b204e9800998ecf8427e']\",\n", + " \"valid_from\": \"2017-09-26T23:33:39.829952Z\"\n", + "}\"\"\", version=\"2.0\")\n", + "print(indicator)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Keep in mind that if a 2.1 or higher object is parsed, the operation will fail." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### How will custom work\n", + "\n", + "CustomObject, CustomObservable, CustomMarking and CustomExtension must be registered explicitly by STIX version. This is a design decision since properties or requirements may change as the STIX Technical Specification advances.\n", + "\n", + "You can perform this by," + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import stix2\n", + "\n", + "# Make my custom observable available in STIX 2.0\n", + "@stix2.v20.CustomObservable('x-new-object-type',\n", + " ((\"prop\", stix2.properties.BooleanProperty())))\n", + "class NewObject2(object):\n", + " pass\n", + "\n", + "\n", + "# Make my custom observable available in STIX 2.1\n", + "@stix2.v21.CustomObservable('x-new-object-type',\n", + " ((\"prop\", stix2.properties.BooleanProperty())))\n", + "class NewObject2(object):\n", + " pass" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/setup.py b/setup.py index e359147..3ed7ba2 100644 --- a/setup.py +++ b/setup.py @@ -44,8 +44,8 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', ], - keywords="stix stix2 json cti cyber threat intelligence", - packages=find_packages(), + keywords='stix stix2 json cti cyber threat intelligence', + packages=find_packages(exclude=['*.test']), install_requires=[ 'python-dateutil', 'pytz', @@ -53,7 +53,6 @@ setup( 'simplejson', 'six', 'stix2-patterns', - 'stix2-validator', 'taxii2-client', ], ) diff --git a/stix2/__init__.py b/stix2/__init__.py index 661c247..6fe2a79 100644 --- a/stix2/__init__.py +++ b/stix2/__init__.py @@ -19,28 +19,10 @@ # flake8: noqa -from . import exceptions -from .common import (TLP_AMBER, TLP_GREEN, TLP_RED, TLP_WHITE, CustomMarking, - ExternalReference, GranularMarking, KillChainPhase, - LanguageContent, MarkingDefinition, StatementMarking, - TLPMarking) -from .core import Bundle, _register_type, parse +from .core import Bundle, _collect_stix2_obj_maps, _register_type, parse from .environment import Environment, ObjectFactory from .markings import (add_markings, clear_markings, get_markings, is_marked, remove_markings, set_markings) -from .observables import (URL, AlternateDataStream, ArchiveExt, Artifact, - AutonomousSystem, CustomExtension, CustomObservable, - Directory, DomainName, EmailAddress, EmailMessage, - EmailMIMEComponent, File, HTTPRequestExt, ICMPExt, - IPv4Address, IPv6Address, MACAddress, Mutex, - NetworkTraffic, NTFSExt, PDFExt, Process, - RasterImageExt, SocketExt, Software, TCPExt, - UNIXAccountExt, UserAccount, WindowsPEBinaryExt, - WindowsPEOptionalHeaderType, WindowsPESection, - WindowsProcessExt, WindowsRegistryKey, - WindowsRegistryValueType, WindowsServiceExt, - X509Certificate, X509V3ExtenstionsType, - parse_observable) from .patterns import (AndBooleanExpression, AndObservationExpression, BasicObjectPathComponent, EqualityComparisonExpression, FloatConstant, FollowedByObservationExpression, @@ -59,10 +41,6 @@ from .patterns import (AndBooleanExpression, AndObservationExpression, ReferenceObjectPathComponent, RepeatQualifier, StartStopQualifier, StringConstant, TimestampConstant, WithinQualifier) -from .sdo import (AttackPattern, Campaign, CourseOfAction, CustomObject, - Identity, Indicator, IntrusionSet, Location, Malware, Note, - ObservedData, Opinion, Report, ThreatActor, Tool, - Vulnerability) from .sources import CompositeDataSource from .sources.filesystem import (FileSystemSink, FileSystemSource, FileSystemStore) @@ -70,6 +48,10 @@ from .sources.filters import Filter from .sources.memory import MemorySink, MemorySource, MemoryStore from .sources.taxii import (TAXIICollectionSink, TAXIICollectionSource, TAXIICollectionStore) -from .sro import Relationship, Sighting from .utils import get_dict, new_version, revoke +from .v21 import * # This import will always be the latest STIX 2.X version from .version import __version__ + +_collect_stix2_obj_maps() + +DEFAULT_VERSION = "2.1" # Default version will always be the latest STIX 2.X version diff --git a/stix2/base.py b/stix2/base.py index 5307393..b0cf6ff 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -40,7 +40,14 @@ class _STIXBase(collections.Mapping): """Base class for STIX object types""" def object_properties(self): - return list(self._properties.keys()) + props = set(self._properties.keys()) + custom_props = list(set(self._inner.keys()) - props) + custom_props.sort() + + all_properties = list(self._properties.keys()) + all_properties.extend(custom_props) # Any custom properties to the bottom + + return all_properties def _check_property(self, prop_name, prop, kwargs): if prop_name not in kwargs: diff --git a/stix2/core.py b/stix2/core.py index cd0523e..3c98197 100644 --- a/stix2/core.py +++ b/stix2/core.py @@ -1,16 +1,15 @@ -"""STIX 2.0 Objects that are neither SDOs nor SROs.""" +"""STIX 2.X Objects that are neither SDOs nor SROs.""" from collections import OrderedDict +import importlib +import pkgutil + +import stix2 from . import exceptions from .base import _STIXBase -from .common import MarkingDefinition from .properties import IDProperty, ListProperty, Property, TypeProperty -from .sdo import (AttackPattern, Campaign, CourseOfAction, Identity, Indicator, - IntrusionSet, Location, Malware, Note, ObservedData, Opinion, - Report, ThreatActor, Tool, Vulnerability) -from .sro import Relationship, Sighting -from .utils import get_dict +from .utils import get_class_hierarchy_names, get_dict class STIXObjectProperty(Property): @@ -20,6 +19,11 @@ class STIXObjectProperty(Property): super(STIXObjectProperty, self).__init__() def clean(self, value): + # Any STIX Object (SDO, SRO, or Marking Definition) can be added to + # a bundle with no further checks. + if any(x in ('STIXDomainObject', 'STIXRelationshipObject', 'MarkingDefinition') + for x in get_class_hierarchy_names(value)): + return value try: dictified = get_dict(value) except ValueError: @@ -62,40 +66,30 @@ class Bundle(_STIXBase): super(Bundle, self).__init__(**kwargs) -OBJ_MAP = { - 'attack-pattern': AttackPattern, - 'bundle': Bundle, - 'campaign': Campaign, - 'course-of-action': CourseOfAction, - 'identity': Identity, - 'indicator': Indicator, - 'intrusion-set': IntrusionSet, - 'location': Location, - 'malware': Malware, - 'note': Note, - 'marking-definition': MarkingDefinition, - 'observed-data': ObservedData, - 'opinion': Opinion, - 'report': Report, - 'relationship': Relationship, - 'threat-actor': ThreatActor, - 'tool': Tool, - 'sighting': Sighting, - 'vulnerability': Vulnerability, -} +STIX2_OBJ_MAPS = {} -def parse(data, allow_custom=False): +def parse(data, allow_custom=False, version=None): """Deserialize a string or file-like object into a STIX object. Args: data (str, dict, file-like object): The STIX 2 content to be parsed. - allow_custom (bool): Whether to allow custom properties or not. Default: False. + allow_custom (bool): Whether to allow custom properties or not. + Default: False. + version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If + None, use latest version. Returns: An instantiated Python STIX object. """ + if not version: + # Use latest version + v = 'v' + stix2.DEFAULT_VERSION.replace('.', '') + else: + v = 'v' + version.replace('.', '') + + OBJ_MAP = STIX2_OBJ_MAPS[v] obj = get_dict(data) if 'type' not in obj: @@ -108,8 +102,34 @@ def parse(data, allow_custom=False): return obj_class(allow_custom=allow_custom, **obj) -def _register_type(new_type): +def _register_type(new_type, version=None): """Register a custom STIX Object type. + Args: + new_type (class): A class to register in the Object map. + version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If + None, use latest version. """ + if not version: + # Use latest version + v = 'v' + stix2.DEFAULT_VERSION.replace('.', '') + else: + v = 'v' + version.replace('.', '') + + OBJ_MAP = STIX2_OBJ_MAPS[v] OBJ_MAP[new_type._type] = new_type + + +def _collect_stix2_obj_maps(): + """Navigate the package once and retrieve all OBJ_MAP dicts for each v2X + package.""" + if not STIX2_OBJ_MAPS: + top_level_module = importlib.import_module('stix2') + path = top_level_module.__path__ + prefix = str(top_level_module.__name__) + '.' + + for module_loader, name, is_pkg in pkgutil.walk_packages(path=path, + prefix=prefix): + if name.startswith('stix2.v2') and is_pkg: + mod = importlib.import_module(name, str(top_level_module.__name__)) + STIX2_OBJ_MAPS[name.split('.')[-1]] = mod.OBJ_MAP diff --git a/stix2/environment.py b/stix2/environment.py index c4816ee..4919335 100644 --- a/stix2/environment.py +++ b/stix2/environment.py @@ -152,3 +152,22 @@ class Environment(object): def parse(self, *args, **kwargs): return _parse(*args, **kwargs) parse.__doc__ = _parse.__doc__ + + def creator_of(self, obj): + """Retrieve the Identity refered to by the object's `created_by_ref`. + + Args: + obj: The STIX object whose `created_by_ref` property will be looked + up. + + Returns: + The STIX object's creator, or + None, if the object contains no `created_by_ref` property or the + object's creator cannot be found. + + """ + creator_id = obj.get('created_by_ref', '') + if creator_id: + return self.get(creator_id) + else: + return None diff --git a/stix2/sources/__init__.py b/stix2/sources/__init__.py index 9d46ba9..1fe9391 100644 --- a/stix2/sources/__init__.py +++ b/stix2/sources/__init__.py @@ -44,36 +44,40 @@ class DataStore(object): self.source = source self.sink = sink - def get(self, stix_id): + def get(self, stix_id, allow_custom=False): """Retrieve the most recent version of a single STIX object by ID. Translate get() call to the appropriate DataSource call. Args: stix_id (str): the id of the STIX object to retrieve. + allow_custom (bool): whether to retrieve custom objects/properties + or not. Default: False. Returns: stix_obj: the single most recent version of the STIX object specified by the "id". """ - return self.source.get(stix_id) + return self.source.get(stix_id, allow_custom=allow_custom) - def all_versions(self, stix_id): + def all_versions(self, stix_id, allow_custom=False): """Retrieve all versions of a single STIX object by ID. Implement: Translate all_versions() call to the appropriate DataSource call Args: stix_id (str): the id of the STIX object to retrieve. + allow_custom (bool): whether to retrieve custom objects/properties + or not. Default: False. Returns: stix_objs (list): a list of STIX objects """ - return self.source.all_versions(stix_id) + return self.source.all_versions(stix_id, allow_custom=allow_custom) - def query(self, query): + def query(self, query=None, allow_custom=False): """Retrieve STIX objects matching a set of filters. Implement: Specific data source API calls, processing, @@ -82,6 +86,8 @@ class DataStore(object): Args: query (list): a list of filters (which collectively are the query) to conduct search on. + allow_custom (bool): whether to retrieve custom objects/properties + or not. Default: False. Returns: stix_objs (list): a list of STIX objects @@ -89,15 +95,17 @@ class DataStore(object): """ return self.source.query(query=query) - def add(self, stix_objs): + def add(self, stix_objs, allow_custom=False): """Store STIX objects. Translates add() to the appropriate DataSink call. Args: stix_objs (list): a list of STIX objects + allow_custom (bool): whether to allow custom objects/properties or + not. Default: False. """ - return self.sink.add(stix_objs) + return self.sink.add(stix_objs, allow_custom=allow_custom) class DataSink(object): @@ -111,7 +119,7 @@ class DataSink(object): def __init__(self): self.id = make_id() - def add(self, stix_objs): + def add(self, stix_objs, allow_custom=False): """Store STIX objects. Implement: Specific data sink API calls, processing, @@ -120,6 +128,8 @@ class DataSink(object): Args: stix_objs (list): a list of STIX objects (where each object is a STIX object) + allow_custom (bool): whether to allow custom objects/properties or + not. Default: False. """ raise NotImplementedError() @@ -139,7 +149,7 @@ class DataSource(object): self.id = make_id() self.filters = set() - def get(self, stix_id, _composite_filters=None): + def get(self, stix_id, _composite_filters=None, allow_custom=False): """ Implement: Specific data source API calls, processing, functionality required for retrieving data from the data source @@ -148,9 +158,10 @@ class DataSource(object): stix_id (str): the id of the STIX 2.0 object to retrieve. Should return a single object, the most recent version of the object specified by the "id". - _composite_filters (set): set of filters passed from the parent the CompositeDataSource, not user supplied + allow_custom (bool): whether to retrieve custom objects/properties + or not. Default: False. Returns: stix_obj: the STIX object @@ -158,7 +169,7 @@ class DataSource(object): """ raise NotImplementedError() - def all_versions(self, stix_id, _composite_filters=None): + def all_versions(self, stix_id, _composite_filters=None, allow_custom=False): """ Implement: Similar to get() except returns list of all object versions of the specified "id". In addition, implement the specific data @@ -169,9 +180,10 @@ class DataSource(object): stix_id (str): The id of the STIX 2.0 object to retrieve. Should return a list of objects, all the versions of the object specified by the "id". - _composite_filters (set): set of filters passed from the parent CompositeDataSource, not user supplied + allow_custom (bool): whether to retrieve custom objects/properties + or not. Default: False. Returns: stix_objs (list): a list of STIX objects @@ -179,7 +191,7 @@ class DataSource(object): """ raise NotImplementedError() - def query(self, query, _composite_filters=None): + def query(self, query=None, _composite_filters=None, allow_custom=False): """ Implement:Implement the specific data source API calls, processing, functionality required for retrieving query from the data source @@ -187,9 +199,10 @@ class DataSource(object): Args: query (list): a list of filters (which collectively are the query) to conduct search on - _composite_filters (set): a set of filters passed from the parent CompositeDataSource, not user supplied + allow_custom (bool): whether to retrieve custom objects/properties + or not. Default: False. Returns: stix_objs (list): a list of STIX objects @@ -224,7 +237,7 @@ class CompositeDataSource(DataSource): super(CompositeDataSource, self).__init__() self.data_sources = [] - def get(self, stix_id, _composite_filters=None): + def get(self, stix_id, _composite_filters=None, allow_custom=False): """Retrieve STIX object by STIX ID Federated retrieve method, iterates through all DataSources @@ -238,10 +251,11 @@ class CompositeDataSource(DataSource): Args: stix_id (str): the id of the STIX object to retrieve. - _composite_filters (list): a list of filters passed from a CompositeDataSource (i.e. if this CompositeDataSource is attached to another parent CompositeDataSource), not user supplied + allow_custom (bool): whether to retrieve custom objects/properties + or not. Default: False. Returns: stix_obj: the STIX object to be returned. @@ -259,20 +273,22 @@ class CompositeDataSource(DataSource): # for every configured Data Source, call its retrieve handler for ds in self.data_sources: - data = ds.get(stix_id=stix_id, _composite_filters=all_filters) + data = ds.get(stix_id=stix_id, _composite_filters=all_filters, allow_custom=allow_custom) if data: all_data.append(data) # remove duplicate versions if len(all_data) > 0: all_data = deduplicate(all_data) + else: + return None # reduce to most recent version stix_obj = sorted(all_data, key=lambda k: k['modified'], reverse=True)[0] return stix_obj - def all_versions(self, stix_id, _composite_filters=None): + def all_versions(self, stix_id, _composite_filters=None, allow_custom=False): """Retrieve STIX objects by STIX ID Federated all_versions retrieve method - iterates through all DataSources @@ -283,10 +299,11 @@ class CompositeDataSource(DataSource): Args: stix_id (str): id of the STIX objects to retrieve - _composite_filters (list): a list of filters passed from a CompositeDataSource (i.e. if this CompositeDataSource is attached to a parent CompositeDataSource), not user supplied + allow_custom (bool): whether to retrieve custom objects/properties + or not. Default: False. Returns: all_data (list): list of STIX objects that have the specified id @@ -305,7 +322,7 @@ class CompositeDataSource(DataSource): # retrieve STIX objects from all configured data sources for ds in self.data_sources: - data = ds.all_versions(stix_id=stix_id, _composite_filters=all_filters) + data = ds.all_versions(stix_id=stix_id, _composite_filters=all_filters, allow_custom=allow_custom) all_data.extend(data) # remove exact duplicates (where duplicates are STIX 2.0 objects @@ -315,7 +332,7 @@ class CompositeDataSource(DataSource): return all_data - def query(self, query=None, _composite_filters=None): + def query(self, query=None, _composite_filters=None, allow_custom=False): """Retrieve STIX objects that match query Federate the query to all DataSources attached to the @@ -323,10 +340,11 @@ class CompositeDataSource(DataSource): Args: query (list): list of filters to search on - _composite_filters (list): a list of filters passed from a CompositeDataSource (i.e. if this CompositeDataSource is attached to a parent CompositeDataSource), not user supplied + allow_custom (bool): whether to retrieve custom objects/properties + or not. Default: False. Returns: all_data (list): list of STIX objects to be returned @@ -351,7 +369,7 @@ class CompositeDataSource(DataSource): # federate query to all attached data sources, # pass composite filters to id for ds in self.data_sources: - data = ds.query(query=query, _composite_filters=all_filters) + data = ds.query(query=query, _composite_filters=all_filters, allow_custom=allow_custom) all_data.extend(data) # remove exact duplicates (where duplicates are STIX 2.0 diff --git a/stix2/sources/filesystem.py b/stix2/sources/filesystem.py index 103b882..eb83d8c 100644 --- a/stix2/sources/filesystem.py +++ b/stix2/sources/filesystem.py @@ -8,51 +8,51 @@ TODO: import json import os -from stix2.base import _STIXBase from stix2.core import Bundle, parse from stix2.sources import DataSink, DataSource, DataStore from stix2.sources.filters import Filter, apply_common_filters -from stix2.utils import deduplicate +from stix2.utils import deduplicate, get_class_hierarchy_names class FileSystemStore(DataStore): - """FileSystemStore + """Interface to a file directory of STIX objects. - Provides an interface to an file directory of STIX objects. FileSystemStore is a wrapper around a paired FileSystemSink and FileSystemSource. Args: stix_dir (str): path to directory of STIX objects + bundlify (bool): Whether to wrap objects in bundles when saving them. + Default: False. Attributes: source (FileSystemSource): FuleSystemSource - sink (FileSystemSink): FileSystemSink """ - def __init__(self, stix_dir): + def __init__(self, stix_dir, bundlify=False): super(FileSystemStore, self).__init__() self.source = FileSystemSource(stix_dir=stix_dir) - self.sink = FileSystemSink(stix_dir=stix_dir) + self.sink = FileSystemSink(stix_dir=stix_dir, bundlify=bundlify) class FileSystemSink(DataSink): - """FileSystemSink - - Provides an interface for adding/pushing STIX objects - to file directory of STIX objects. + """Interface for adding/pushing STIX objects to file directory of STIX + objects. Can be paired with a FileSystemSource, together as the two components of a FileSystemStore. Args: - stix_dir (str): path to directory of STIX objects + stix_dir (str): path to directory of STIX objects. + bundlify (bool): Whether to wrap objects in bundles when saving them. + Default: False. """ - def __init__(self, stix_dir): + def __init__(self, stix_dir, bundlify=False): super(FileSystemSink, self).__init__() self._stix_dir = os.path.abspath(stix_dir) + self.bundlify = bundlify if not os.path.exists(self._stix_dir): raise ValueError("directory path for STIX data does not exist") @@ -61,62 +61,72 @@ class FileSystemSink(DataSink): def stix_dir(self): return self._stix_dir - def add(self, stix_data=None): - """add STIX objects to file directory + def _check_path_and_write(self, stix_obj): + """Write the given STIX object to a file in the STIX file directory. + """ + path = os.path.join(self._stix_dir, stix_obj["type"], stix_obj["id"] + ".json") + + if not os.path.exists(os.path.dirname(path)): + os.makedirs(os.path.dirname(path)) + + if self.bundlify: + stix_obj = Bundle(stix_obj) + + with open(path, "w") as f: + f.write(str(stix_obj)) + + def add(self, stix_data=None, allow_custom=False, version=None): + """Add STIX objects to file directory. Args: stix_data (STIX object OR dict OR str OR list): valid STIX 2.0 content - in a STIX object(or list of), dict (or list of), or a STIX 2.0 - json encoded string + in a STIX object (or list of), dict (or list of), or a STIX 2.0 + json encoded string. + allow_custom (bool): whether to allow custom objects/properties or + not. Default: False. + version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If + None, use latest version. + + Note: + ``stix_data`` can be a Bundle object, but each object in it will be + saved separately; you will be able to retrieve any of the objects + the Bundle contained, but not the Bundle itself. - TODO: Bundlify STIX content or no? When dumping to disk. """ - def _check_path_and_write(stix_dir, stix_obj): - path = os.path.join(stix_dir, stix_obj["type"], stix_obj["id"] + ".json") - - if not os.path.exists(os.path.dirname(path)): - os.makedirs(os.path.dirname(path)) - - with open(path, "w") as f: - # Bundle() can take dict or STIX obj as argument - f.write(str(Bundle(stix_obj))) - - if isinstance(stix_data, _STIXBase): + if any(x in ('STIXDomainObject', 'STIXRelationshipObject', 'MarkingDefinition') + for x in get_class_hierarchy_names(stix_data)): # adding python STIX object - _check_path_and_write(self._stix_dir, stix_data) + self._check_path_and_write(stix_data) - elif isinstance(stix_data, dict): + elif isinstance(stix_data, (str, dict)): + stix_data = parse(stix_data, allow_custom, version) if stix_data["type"] == "bundle": - # adding json-formatted Bundle - extracting STIX objects - for stix_obj in stix_data["objects"]: + # extract STIX objects + for stix_obj in stix_data.get("objects", []): self.add(stix_obj) else: # adding json-formatted STIX - _check_path_and_write(self._stix_dir, stix_data) + self._check_path_and_write(stix_data) - elif isinstance(stix_data, str): - # adding json encoded string of STIX content - stix_data = parse(stix_data) - if stix_data["type"] == "bundle": - for stix_obj in stix_data["objects"]: - self.add(stix_obj) - else: - self.add(stix_data) + elif isinstance(stix_data, Bundle): + # recursively add individual STIX objects + for stix_obj in stix_data.get("objects", []): + self.add(stix_obj) elif isinstance(stix_data, list): - # if list, recurse call on individual STIX objects + # recursively add individual STIX objects for stix_obj in stix_data: self.add(stix_obj) else: - raise ValueError("stix_data must be a STIX object(or list of), json formatted STIX(or list of) or a json formatted STIX bundle") + raise TypeError("stix_data must be a STIX object (or list of), " + "JSON formatted STIX (or list of), " + "or a JSON formatted STIX bundle") class FileSystemSource(DataSource): - """FileSystemSource - - Provides an interface for searching/retrieving - STIX objects from a STIX object file directory. + """Interface for searching/retrieving STIX objects from a STIX object file + directory. Can be paired with a FileSystemSink, together as the two components of a FileSystemStore. @@ -136,14 +146,17 @@ class FileSystemSource(DataSource): def stix_dir(self): return self._stix_dir - def get(self, stix_id, _composite_filters=None): - """retrieve STIX object from file directory via STIX ID + def get(self, stix_id, _composite_filters=None, allow_custom=False, version=None): + """Retrieve STIX object from file directory via STIX ID. Args: stix_id (str): The STIX ID of the STIX object to be retrieved. - - composite_filters (set): set of filters passed from the parent + _composite_filters (set): set of filters passed from the parent CompositeDataSource, not user supplied + allow_custom (bool): whether to retrieve custom objects/properties + or not. Default: False. + version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If + None, use latest version. Returns: (STIX object): STIX object that has the supplied STIX ID. @@ -153,47 +166,55 @@ class FileSystemSource(DataSource): """ query = [Filter("id", "=", stix_id)] - all_data = self.query(query=query, _composite_filters=_composite_filters) + all_data = self.query(query=query, _composite_filters=_composite_filters, + allow_custom=allow_custom, version=version) if all_data: stix_obj = sorted(all_data, key=lambda k: k['modified'])[0] - stix_obj = parse(stix_obj) else: stix_obj = None return stix_obj - def all_versions(self, stix_id, _composite_filters=None): - """retrieve STIX object from file directory via STIX ID, all versions + def all_versions(self, stix_id, _composite_filters=None, allow_custom=False, version=None): + """Retrieve STIX object from file directory via STIX ID, all versions. Note: Since FileSystem sources/sinks don't handle multiple versions of a STIX object, this operation is unnecessary. Pass call to get(). Args: stix_id (str): The STIX ID of the STIX objects to be retrieved. - - composite_filters (set): set of filters passed from the parent + _composite_filters (set): set of filters passed from the parent CompositeDataSource, not user supplied + allow_custom (bool): whether to retrieve custom objects/properties + or not. Default: False. + version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If + None, use latest version. Returns: (list): of STIX objects that has the supplied STIX ID. The STIX objects are loaded from their json files, parsed into a python STIX objects and then returned - """ - return [self.get(stix_id=stix_id, _composite_filters=_composite_filters)] - def query(self, query=None, _composite_filters=None): - """search and retrieve STIX objects based on the complete query + """ + return [self.get(stix_id=stix_id, _composite_filters=_composite_filters, + allow_custom=allow_custom, version=version)] + + def query(self, query=None, _composite_filters=None, allow_custom=False, version=None): + """Search and retrieve STIX objects based on the complete query. A "complete query" includes the filters from the query, the filters - attached to MemorySource, and any filters passed from a - CompositeDataSource (i.e. _composite_filters) + attached to this FileSystemSource, and any filters passed from a + CompositeDataSource (i.e. _composite_filters). Args: query (list): list of filters to search on - - composite_filters (set): set of filters passed from the + _composite_filters (set): set of filters passed from the CompositeDataSource, not user supplied + allow_custom (bool): whether to retrieve custom objects/properties + or not. Default: False. + version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If + None, use latest version. Returns: (list): list of STIX objects that matches the supplied @@ -209,7 +230,7 @@ class FileSystemSource(DataSource): if not isinstance(query, list): # make sure dont make set from a Filter object, # need to make a set from a list of Filter objects (even if just one Filter) - query = list(query) + query = [query] query = set(query) # combine all query filters @@ -225,14 +246,14 @@ class FileSystemSource(DataSource): file_filters = self._parse_file_filters(query) # establish which subdirectories can be avoided in query - # by decluding as many as possible. A filter with "type" as the field + # by decluding as many as possible. A filter with "type" as the property # means that certain STIX object types can be ruled out, and thus # the corresponding subdirectories as well include_paths = [] declude_paths = [] - if "type" in [filter.field for filter in file_filters]: + if "type" in [filter.property for filter in file_filters]: for filter in file_filters: - if filter.field == "type": + if filter.property == "type": if filter.op == "=": include_paths.append(os.path.join(self._stix_dir, filter.value)) elif filter.op == "!=": @@ -254,14 +275,14 @@ class FileSystemSource(DataSource): # so query will look in all STIX directories that are not # the specified type. Compile correct dir paths for dir in os.listdir(self._stix_dir): - if os.path.abspath(dir) not in declude_paths: - include_paths.append(os.path.abspath(dir)) + if os.path.abspath(os.path.join(self._stix_dir, dir)) not in declude_paths: + include_paths.append(os.path.abspath(os.path.join(self._stix_dir, dir))) # grab stix object ID as well - if present in filters, as # may forgo the loading of STIX content into memory - if "id" in [filter.field for filter in file_filters]: + if "id" in [filter.property for filter in file_filters]: for filter in file_filters: - if filter.field == "id" and filter.op == "=": + if filter.property == "id" and filter.op == "=": id_ = filter.value break else: @@ -273,37 +294,35 @@ class FileSystemSource(DataSource): for path in include_paths: for root, dirs, files in os.walk(path): for file_ in files: - if id_: - if id_ == file_.split(".")[0]: - # since ID is specified in one of filters, can evaluate against filename first without loading - stix_obj = json.load(open(os.path.join(root, file_)))["objects"][0] - # check against other filters, add if match - all_data.extend(apply_common_filters([stix_obj], query)) - else: + if not id_ or id_ == file_.split(".")[0]: # have to load into memory regardless to evaluate other filters - stix_obj = json.load(open(os.path.join(root, file_)))["objects"][0] + stix_obj = json.load(open(os.path.join(root, file_))) + if stix_obj.get('type', '') == 'bundle': + stix_obj = stix_obj['objects'][0] + # check against other filters, add if match all_data.extend(apply_common_filters([stix_obj], query)) all_data = deduplicate(all_data) # parse python STIX objects from the STIX object dicts - stix_objs = [parse(stix_obj_dict) for stix_obj_dict in all_data] + stix_objs = [parse(stix_obj_dict, allow_custom, version) for stix_obj_dict in all_data] return stix_objs def _parse_file_filters(self, query): - """utility method to extract STIX common filters - that can used to possibly speed up querying STIX objects - from the file system + """Extract STIX common filters. - Extracts filters that are for the "id" and "type" field of + Possibly speeds up querying STIX objects from the file system. + + Extracts filters that are for the "id" and "type" property of a STIX object. As the file directory is organized by STIX object type with filenames that are equivalent to the STIX object ID, these filters can be used first to reduce the - search space of a FileSystemStore(or FileSystemSink) + search space of a FileSystemStore (or FileSystemSink). + """ file_filters = set() for filter_ in query: - if filter_.field == "id" or filter_.field == "type": + if filter_.property == "id" or filter_.property == "type": file_filters.add(filter_) return file_filters diff --git a/stix2/sources/filters.py b/stix2/sources/filters.py index 060d2c3..5772112 100644 --- a/stix2/sources/filters.py +++ b/stix2/sources/filters.py @@ -4,29 +4,6 @@ Filters for Python STIX 2.0 DataSources, DataSinks, DataStores """ import collections -import types - -# Currently, only STIX 2.0 common SDO fields (that are not complex objects) -# are supported for filtering on - -"""Supported STIX properties""" -STIX_COMMON_FIELDS = [ - "created", - "created_by_ref", - "external_references.source_name", - "external_references.description", - "external_references.url", - "external_references.hashes", - "external_references.external_id", - "granular_markings.marking_ref", - "granular_markings.selectors", - "id", - "labels", - "modified", - "object_marking_refs", - "revoked", - "type" -] """Supported filter operations""" FILTER_OPS = ['=', '!=', 'in', '>', '<', '>=', '<='] @@ -34,46 +11,40 @@ FILTER_OPS = ['=', '!=', 'in', '>', '<', '>=', '<='] """Supported filter value types""" FILTER_VALUE_TYPES = [bool, dict, float, int, list, str, tuple] -# filter lookup map - STIX 2 common fields -> filter method -STIX_COMMON_FILTERS_MAP = {} + +def _check_filter_components(prop, op, value): + """Check that filter meets minimum validity. + + Note: + Currently can create Filters that are not valid STIX2 object common + properties, as filter.prop value is not checked, only filter.op, + filter value are checked here. They are just ignored when applied + within the DataSource API. For example, a user can add a TAXII Filter, + that is extracted and sent to a TAXII endpoint within TAXIICollection + and not applied locally (within this API). + + """ + if op not in FILTER_OPS: + # check filter operator is supported + raise ValueError("Filter operator '%s' not supported for specified property: '%s'" % (op, prop)) + + if type(value) not in FILTER_VALUE_TYPES: + # check filter value type is supported + raise TypeError("Filter value type '%s' is not supported. The type must be a Python immutable type or dictionary" % type(value)) + + return True -def _check_filter_components(field, op, value): - """check filter meets minimum validity - - Note: Currently can create Filters that are not valid - STIX2 object common properties, as filter.field value - is not checked, only filter.op, filter.value are checked - here. They are just ignored when - applied within the DataSource API. For example, a user - can add a TAXII Filter, that is extracted and sent to - a TAXII endpoint within TAXIICollection and not applied - locally (within this API). - """ - - if op not in FILTER_OPS: - # check filter operator is supported - raise ValueError("Filter operator '%s' not supported for specified field: '%s'" % (op, field)) - - if type(value) not in FILTER_VALUE_TYPES: - # check filter value type is supported - raise TypeError("Filter value type '%s' is not supported. The type must be a python immutable type or dictionary" % type(value)) - - return True - - -class Filter(collections.namedtuple("Filter", ['field', 'op', 'value'])): +class Filter(collections.namedtuple("Filter", ['property', 'op', 'value'])): """STIX 2 filters that support the querying functionality of STIX 2 DataStores and DataSources. - Initialized like a python tuple + Initialized like a Python tuple. Args: - field (str): filter field name, corresponds to STIX 2 object property - + property (str): filter property name, corresponds to STIX 2 object property op (str): operator of the filter - - value (str): filter field value + value (str): filter property value Example: Filter("id", "=", "malware--0f862b01-99da-47cc-9bdb-db4a86a95bb1") @@ -81,235 +52,110 @@ class Filter(collections.namedtuple("Filter", ['field', 'op', 'value'])): """ __slots__ = () - def __new__(cls, field, op, value): + def __new__(cls, prop, op, value): # If value is a list, convert it to a tuple so it is hashable. if isinstance(value, list): value = tuple(value) - _check_filter_components(field, op, value) + _check_filter_components(prop, op, value) - self = super(Filter, cls).__new__(cls, field, op, value) + self = super(Filter, cls).__new__(cls, prop, op, value) return self - @property - def common(self): - """return whether Filter is valid STIX2 Object common property + def _check_property(self, stix_obj_property): + """Check a property of a STIX Object against this filter. - Note: The Filter operator and Filter value type are checked when - the filter is created, thus only leaving the Filter field to be - checked to make sure a valid STIX2 Object common property. + Args: + stix_obj_property: value to check this filter against - Note: Filters that are not valid STIX2 Object common property - Filters are still allowed to be created for extended usage of - Filter. (e.g. TAXII specific filters can be created, which are - then extracted and sent to TAXII endpoint.) + Returns: + True if property matches the filter, + False otherwise. """ - return self.field in STIX_COMMON_FIELDS + if self.op == "=": + return stix_obj_property == self.value + elif self.op == "!=": + return stix_obj_property != self.value + elif self.op == "in": + return stix_obj_property in self.value + elif self.op == ">": + return stix_obj_property > self.value + elif self.op == "<": + return stix_obj_property < self.value + elif self.op == ">=": + return stix_obj_property >= self.value + elif self.op == "<=": + return stix_obj_property <= self.value + else: + raise ValueError("Filter operator: {0} not supported for specified property: {1}".format(self.op, self.property)) def apply_common_filters(stix_objs, query): """Evaluate filters against a set of STIX 2.0 objects. - Supports only STIX 2.0 common property fields + Supports only STIX 2.0 common property properties. Args: stix_objs (list): list of STIX objects to apply the query to query (set): set of filters (combined form complete query) - Returns: - (generator): of STIX objects that successfully evaluate against - the query. + Yields: + STIX objects that successfully evaluate against the query. """ for stix_obj in stix_objs: clean = True for filter_ in query: - if not filter_.common: - # skip filter as it is not a STIX2 Object common property filter - continue - - if "." in filter_.field: - # For properties like granular_markings and external_references - # need to extract the first property from the string. - field = filter_.field.split(".")[0] - else: - field = filter_.field - - if field not in stix_obj.keys(): - # check filter "field" is in STIX object - if cant be - # applied to STIX object, STIX object is discarded - # (i.e. did not make it through the filter) - clean = False - break - - match = STIX_COMMON_FILTERS_MAP[filter_.field.split('.')[0]](filter_, stix_obj) + match = _check_filter(filter_, stix_obj) if not match: clean = False break - elif match == -1: - raise ValueError("Error, filter operator: {0} not supported for specified field: {1}".format(filter_.op, filter_.field)) # if object unmarked after all filters, add it if clean: yield stix_obj -"""Base type filters""" +def _check_filter(filter_, stix_obj): + """Evaluate a single filter against a single STIX 2.0 object. + Args: + filter_ (Filter): filter to match against + stix_obj: STIX object to apply the filter to -def _all_filter(filter_, stix_obj_field): - """all filter operations (for filters whose value type can be applied to any operation type)""" - if filter_.op == "=": - return stix_obj_field == filter_.value - elif filter_.op == "!=": - return stix_obj_field != filter_.value - elif filter_.op == "in": - return stix_obj_field in filter_.value - elif filter_.op == ">": - return stix_obj_field > filter_.value - elif filter_.op == "<": - return stix_obj_field < filter_.value - elif filter_.op == ">=": - return stix_obj_field >= filter_.value - elif filter_.op == "<=": - return stix_obj_field <= filter_.value + Returns: + True if the stix_obj matches the filter, + False if not. + + """ + # For properties like granular_markings and external_references + # need to extract the first property from the string. + prop = filter_.property.split(".")[0] + + if prop not in stix_obj.keys(): + # check filter "property" is in STIX object - if cant be + # applied to STIX object, STIX object is discarded + # (i.e. did not make it through the filter) + return False + + if "." in filter_.property: + # Check embedded properties, from e.g. granular_markings or external_references + sub_property = filter_.property.split(".", 1)[1] + sub_filter = filter_._replace(property=sub_property) + if isinstance(stix_obj[prop], list): + for elem in stix_obj[prop]: + if _check_filter(sub_filter, elem) is True: + return True + return False + else: + return _check_filter(sub_filter, stix_obj[prop]) + elif isinstance(stix_obj[prop], list): + # Check each item in list property to see if it matches + for elem in stix_obj[prop]: + if filter_._check_property(elem) is True: + return True + return False else: - return -1 - - -def _id_filter(filter_, stix_obj_id): - """base STIX id filter""" - if filter_.op == "=": - return stix_obj_id == filter_.value - elif filter_.op == "!=": - return stix_obj_id != filter_.value - else: - return -1 - - -def _boolean_filter(filter_, stix_obj_field): - """base boolean filter""" - if filter_.op == "=": - return stix_obj_field == filter_.value - elif filter_.op == "!=": - return stix_obj_field != filter_.value - else: - return -1 - - -def _string_filter(filter_, stix_obj_field): - """base string filter""" - return _all_filter(filter_, stix_obj_field) - - -def _timestamp_filter(filter_, stix_obj_timestamp): - """base STIX 2 timestamp filter""" - return _all_filter(filter_, stix_obj_timestamp) - - -"""STIX 2.0 Common Property Filters - -The naming of these functions is important as -they are used to index a mapping dictionary from -STIX common field names to these filter functions. - -REQUIRED naming scheme: - "check__filter" - -""" - - -def check_created_filter(filter_, stix_obj): - return _timestamp_filter(filter_, stix_obj["created"]) - - -def check_created_by_ref_filter(filter_, stix_obj): - return _id_filter(filter_, stix_obj["created_by_ref"]) - - -def check_external_references_filter(filter_, stix_obj): - """ - STIX object's can have a list of external references - - external_references properties supported: - external_references.source_name (string) - external_references.description (string) - external_references.url (string) - external_references.external_id (string) - - external_references properties not supported: - external_references.hashes - - """ - for er in stix_obj["external_references"]: - # grab er property name from filter field - filter_field = filter_.field.split(".")[1] - if filter_field in er: - r = _string_filter(filter_, er[filter_field]) - if r: - return r - return False - - -def check_granular_markings_filter(filter_, stix_obj): - """ - STIX object's can have a list of granular marking references - - granular_markings properties: - granular_markings.marking_ref (id) - granular_markings.selectors (string) - - """ - for gm in stix_obj["granular_markings"]: - # grab gm property name from filter field - filter_field = filter_.field.split(".")[1] - - if filter_field == "marking_ref": - return _id_filter(filter_, gm[filter_field]) - - elif filter_field == "selectors": - for selector in gm[filter_field]: - r = _string_filter(filter_, selector) - if r: - return r - return False - - -def check_id_filter(filter_, stix_obj): - return _id_filter(filter_, stix_obj["id"]) - - -def check_labels_filter(filter_, stix_obj): - for label in stix_obj["labels"]: - r = _string_filter(filter_, label) - if r: - return r - return False - - -def check_modified_filter(filter_, stix_obj): - return _timestamp_filter(filter_, stix_obj["modified"]) - - -def check_object_marking_refs_filter(filter_, stix_obj): - for marking_id in stix_obj["object_marking_refs"]: - r = _id_filter(filter_, marking_id) - if r: - return r - return False - - -def check_revoked_filter(filter_, stix_obj): - return _boolean_filter(filter_, stix_obj["revoked"]) - - -def check_type_filter(filter_, stix_obj): - return _string_filter(filter_, stix_obj["type"]) - - -# Create mapping of field names to filter functions -for name, obj in dict(globals()).items(): - if "check_" in name and isinstance(obj, types.FunctionType): - field_name = "_".join(name.split("_")[1:-1]) - STIX_COMMON_FILTERS_MAP[field_name] = obj + # Check if property matches + return filter_._check_property(stix_obj[prop]) diff --git a/stix2/sources/memory.py b/stix2/sources/memory.py index 0d5901e..2d1705d 100644 --- a/stix2/sources/memory.py +++ b/stix2/sources/memory.py @@ -24,16 +24,18 @@ from stix2.sources import DataSink, DataSource, DataStore from stix2.sources.filters import Filter, apply_common_filters -def _add(store, stix_data=None): - """Adds STIX objects to MemoryStore/Sink. +def _add(store, stix_data=None, allow_custom=False): + """Add STIX objects to MemoryStore/Sink. Adds STIX objects to an in-memory dictionary for fast lookup. Recursive function, breaks down STIX Bundles and lists. Args: stix_data (list OR dict OR STIX object): STIX objects to be added - """ + allow_custom (bool): whether to allow custom objects/properties or + not. Default: False. + """ if isinstance(stix_data, _STIXBase): # adding a python STIX object store._data[stix_data["id"]] = stix_data @@ -41,35 +43,35 @@ def _add(store, stix_data=None): elif isinstance(stix_data, dict): if stix_data["type"] == "bundle": # adding a json bundle - so just grab STIX objects - for stix_obj in stix_data["objects"]: - _add(store, stix_obj) + for stix_obj in stix_data.get("objects", []): + _add(store, stix_obj, allow_custom=allow_custom) else: # adding a json STIX object store._data[stix_data["id"]] = stix_data elif isinstance(stix_data, str): # adding json encoded string of STIX content - stix_data = parse(stix_data) + stix_data = parse(stix_data, allow_custom=allow_custom) if stix_data["type"] == "bundle": # recurse on each STIX object in bundle - for stix_obj in stix_data: - _add(store, stix_obj) + for stix_obj in stix_data.get("objects", []): + _add(store, stix_obj, allow_custom=allow_custom) else: _add(store, stix_data) elif isinstance(stix_data, list): # STIX objects are in a list- recurse on each object for stix_obj in stix_data: - _add(store, stix_obj) + _add(store, stix_obj, allow_custom=allow_custom) else: - raise TypeError("stix_data must be as STIX object(or list of),json formatted STIX (or list of), or a json formatted STIX bundle") + raise TypeError("stix_data must be a STIX object (or list of), JSON formatted STIX (or list of), or a JSON formatted STIX bundle") class MemoryStore(DataStore): - """Provides an interface to an in-memory dictionary - of STIX objects. MemoryStore is a wrapper around a paired - MemorySink and MemorySource + """Interface to an in-memory dictionary of STIX objects. + + MemoryStore is a wrapper around a paired MemorySink and MemorySource. Note: It doesn't make sense to create a MemoryStore by passing in existing MemorySource and MemorySink because there could @@ -77,36 +79,54 @@ class MemoryStore(DataStore): Args: stix_data (list OR dict OR STIX object): STIX content to be added + allow_custom (bool): whether to allow custom objects/properties or + not. Default: False. Attributes: _data (dict): the in-memory dict that holds STIX objects - source (MemorySource): MemorySource - sink (MemorySink): MemorySink """ - - def __init__(self, stix_data=None): + def __init__(self, stix_data=None, allow_custom=False): super(MemoryStore, self).__init__() self._data = {} if stix_data: - _add(self, stix_data) + _add(self, stix_data, allow_custom=allow_custom) - self.source = MemorySource(stix_data=self._data, _store=True) - self.sink = MemorySink(stix_data=self._data, _store=True) + self.source = MemorySource(stix_data=self._data, _store=True, allow_custom=allow_custom) + self.sink = MemorySink(stix_data=self._data, _store=True, allow_custom=allow_custom) - def save_to_file(self, file_path): - return self.sink.save_to_file(file_path=file_path) + def save_to_file(self, file_path, allow_custom=False): + """Write SITX objects from in-memory dictionary to JSON file, as a STIX + Bundle. - def load_from_file(self, file_path): - return self.source.load_from_file(file_path=file_path) + Args: + file_path (str): file path to write STIX data to + allow_custom (bool): whether to allow custom objects/properties or + not. Default: False. + + """ + return self.sink.save_to_file(file_path=file_path, allow_custom=allow_custom) + + def load_from_file(self, file_path, allow_custom=False): + """Load STIX data from JSON file. + + File format is expected to be a single JSON + STIX object or JSON STIX bundle. + + Args: + file_path (str): file path to load STIX data from + allow_custom (bool): whether to allow custom objects/properties or + not. Default: False. + + """ + return self.source.load_from_file(file_path=file_path, allow_custom=allow_custom) class MemorySink(DataSink): - """Provides an interface for adding/pushing STIX objects - to an in-memory dictionary. + """Interface for adding/pushing STIX objects to an in-memory dictionary. Designed to be paired with a MemorySource, together as the two components of a MemoryStore. @@ -114,51 +134,43 @@ class MemorySink(DataSink): Args: stix_data (dict OR list): valid STIX 2.0 content in bundle or a list. - _store (bool): if the MemorySink is a part of a DataStore, in which case "stix_data" is a direct reference to shared memory with DataSource. Not user supplied + allow_custom (bool): whether to allow custom objects/properties or + not. Default: False. Attributes: _data (dict): the in-memory dict that holds STIX objects. If apart of a MemoryStore, dict is shared between with a MemorySource - """ - def __init__(self, stix_data=None, _store=False): + """ + def __init__(self, stix_data=None, _store=False, allow_custom=False): super(MemorySink, self).__init__() self._data = {} if _store: self._data = stix_data elif stix_data: - _add(self, stix_data) + _add(self, stix_data, allow_custom=allow_custom) - def add(self, stix_data): - """add STIX objects to in-memory dictionary maintained by - the MemorySink (MemoryStore) + def add(self, stix_data, allow_custom=False): + _add(self, stix_data, allow_custom=allow_custom) + add.__doc__ = _add.__doc__ - see "_add()" for args documentation - """ - _add(self, stix_data) - - def save_to_file(self, file_path): - """write SITX objects in in-memory dictionary to json file, as a STIX Bundle - - Args: - file_path (str): file path to write STIX data to - - """ + def save_to_file(self, file_path, allow_custom=False): file_path = os.path.abspath(file_path) if not os.path.exists(os.path.dirname(file_path)): os.makedirs(os.path.dirname(file_path)) with open(file_path, "w") as f: - f.write(str(Bundle(self._data.values()))) + f.write(str(Bundle(self._data.values(), allow_custom=allow_custom))) + save_to_file.__doc__ = MemoryStore.save_to_file.__doc__ class MemorySource(DataSource): - """Provides an interface for searching/retrieving - STIX objects from an in-memory dictionary. + """Interface for searching/retrieving STIX objects from an in-memory + dictionary. Designed to be paired with a MemorySink, together as the two components of a MemoryStore. @@ -166,42 +178,44 @@ class MemorySource(DataSource): Args: stix_data (dict OR list OR STIX object): valid STIX 2.0 content in bundle or list. - _store (bool): if the MemorySource is a part of a DataStore, in which case "stix_data" is a direct reference to shared memory with DataSink. Not user supplied + allow_custom (bool): whether to allow custom objects/properties or + not. Default: False. Attributes: _data (dict): the in-memory dict that holds STIX objects. If apart of a MemoryStore, dict is shared between with a MemorySink - """ - def __init__(self, stix_data=None, _store=False): + """ + def __init__(self, stix_data=None, _store=False, allow_custom=False): super(MemorySource, self).__init__() self._data = {} if _store: self._data = stix_data elif stix_data: - _add(self, stix_data) + _add(self, stix_data, allow_custom=allow_custom) - def get(self, stix_id, _composite_filters=None): - """retrieve STIX object from in-memory dict via STIX ID + def get(self, stix_id, _composite_filters=None, allow_custom=False): + """Retrieve STIX object from in-memory dict via STIX ID. Args: stix_id (str): The STIX ID of the STIX object to be retrieved. - composite_filters (set): set of filters passed from the parent CompositeDataSource, not user supplied + allow_custom (bool): whether to retrieve custom objects/properties + or not. Default: False. Returns: (dict OR STIX object): STIX object that has the supplied ID. As the MemoryStore(i.e. MemorySink) adds STIX objects to memory as they are supplied (either as python dictionary or STIX object), it is returned in the same form as it as added - """ + """ if _composite_filters is None: # if get call is only based on 'id', no need to search, just retrieve from dict try: @@ -213,24 +227,28 @@ class MemorySource(DataSource): # if there are filters from the composite level, process full query query = [Filter("id", "=", stix_id)] - all_data = self.query(query=query, _composite_filters=_composite_filters) + all_data = self.query(query=query, _composite_filters=_composite_filters, allow_custom=allow_custom) - # reduce to most recent version - stix_obj = sorted(all_data, key=lambda k: k['modified'])[0] + if all_data: + # reduce to most recent version + stix_obj = sorted(all_data, key=lambda k: k['modified'])[0] - return stix_obj + return stix_obj + else: + return None - def all_versions(self, stix_id, _composite_filters=None): - """retrieve STIX objects from in-memory dict via STIX ID, all versions of it + def all_versions(self, stix_id, _composite_filters=None, allow_custom=False): + """Retrieve STIX objects from in-memory dict via STIX ID, all versions of it Note: Since Memory sources/sinks don't handle multiple versions of a STIX object, this operation is unnecessary. Translate call to get(). Args: stix_id (str): The STIX ID of the STIX 2 object to retrieve. - composite_filters (set): set of filters passed from the parent CompositeDataSource, not user supplied + allow_custom (bool): whether to retrieve custom objects/properties + or not. Default: False. Returns: (list): list of STIX objects that has the supplied ID. As the @@ -239,26 +257,27 @@ class MemorySource(DataSource): is returned in the same form as it as added """ - return [self.get(stix_id=stix_id, _composite_filters=_composite_filters)] + return [self.get(stix_id=stix_id, _composite_filters=_composite_filters, allow_custom=allow_custom)] - def query(self, query=None, _composite_filters=None): - """search and retrieve STIX objects based on the complete query + def query(self, query=None, _composite_filters=None, allow_custom=False): + """Search and retrieve STIX objects based on the complete query. A "complete query" includes the filters from the query, the filters - attached to MemorySource, and any filters passed from a - CompositeDataSource (i.e. _composite_filters) + attached to this MemorySource, and any filters passed from a + CompositeDataSource (i.e. _composite_filters). Args: query (list): list of filters to search on - composite_filters (set): set of filters passed from the CompositeDataSource, not user supplied + allow_custom (bool): whether to retrieve custom objects/properties + or not. Default: False. Returns: (list): list of STIX objects that matches the supplied query. As the MemoryStore(i.e. MemorySink) adds STIX objects to memory as they are supplied (either as python dictionary or STIX object), it - is returned in the same form as it as added + is returned in the same form as it as added. """ if query is None: @@ -267,7 +286,7 @@ class MemorySource(DataSource): if not isinstance(query, list): # make sure dont make set from a Filter object, # need to make a set from a list of Filter objects (even if just one Filter) - query = list(query) + query = [query] query = set(query) # combine all query filters @@ -281,15 +300,8 @@ class MemorySource(DataSource): return all_data - def load_from_file(self, file_path): - """load STIX data from json file - - File format is expected to be a single json - STIX object or json STIX bundle - - Args: - file_path (str): file path to load STIX data from - """ + def load_from_file(self, file_path, allow_custom=False): file_path = os.path.abspath(file_path) stix_data = json.load(open(file_path, "r")) - _add(self, stix_data) + _add(self, stix_data, allow_custom=allow_custom) + load_from_file.__doc__ = MemoryStore.load_from_file.__doc__ diff --git a/stix2/sources/taxii.py b/stix2/sources/taxii.py index 7ecedca..414e27f 100644 --- a/stix2/sources/taxii.py +++ b/stix2/sources/taxii.py @@ -1,9 +1,5 @@ """ -Python STIX 2.0 TAXII Source/Sink - -TODO: - Test everything - +Python STIX 2.x TaxiiCollectionStore """ from stix2.base import _STIXBase @@ -21,7 +17,7 @@ class TAXIICollectionStore(DataStore): around a paired TAXIICollectionSink and TAXIICollectionSource. Args: - collection (taxii2.Collection): TAXII Collection instance + collection (taxii2.Collection): TAXII Collection instance """ def __init__(self, collection): super(TAXIICollectionStore, self).__init__() @@ -41,39 +37,40 @@ class TAXIICollectionSink(DataSink): super(TAXIICollectionSink, self).__init__() self.collection = collection - def add(self, stix_data): - """add/push STIX content to TAXII Collection endpoint + def add(self, stix_data, allow_custom=False): + """Add/push STIX content to TAXII Collection endpoint Args: stix_data (STIX object OR dict OR str OR list): valid STIX 2.0 content in a STIX object (or Bundle), STIX onject dict (or Bundle dict), or a STIX 2.0 json encoded string, or list of any of the following + allow_custom (bool): whether to allow custom objects/properties or + not. Default: False. """ - if isinstance(stix_data, _STIXBase): # adding python STIX object - bundle = dict(Bundle(stix_data)) + bundle = dict(Bundle(stix_data, allow_custom=allow_custom)) elif isinstance(stix_data, dict): # adding python dict (of either Bundle or STIX obj) if stix_data["type"] == "bundle": bundle = stix_data else: - bundle = dict(Bundle(stix_data)) + bundle = dict(Bundle(stix_data, allow_custom=allow_custom)) elif isinstance(stix_data, list): # adding list of something - recurse on each for obj in stix_data: - self.add(obj) + self.add(obj, allow_custom=allow_custom) elif isinstance(stix_data, str): # adding json encoded string of STIX content - stix_data = parse(stix_data) + stix_data = parse(stix_data, allow_custom=allow_custom) if stix_data["type"] == "bundle": bundle = dict(stix_data) else: - bundle = dict(Bundle(stix_data)) + bundle = dict(Bundle(stix_data, allow_custom=allow_custom)) else: raise TypeError("stix_data must be as STIX object(or list of),json formatted STIX (or list of), or a json formatted STIX bundle") @@ -93,22 +90,22 @@ class TAXIICollectionSource(DataSource): super(TAXIICollectionSource, self).__init__() self.collection = collection - def get(self, stix_id, _composite_filters=None): - """retrieve STIX object from local/remote STIX Collection + def get(self, stix_id, _composite_filters=None, allow_custom=False): + """Retrieve STIX object from local/remote STIX Collection endpoint. Args: stix_id (str): The STIX ID of the STIX object to be retrieved. - composite_filters (set): set of filters passed from the parent CompositeDataSource, not user supplied + allow_custom (bool): whether to retrieve custom objects/properties + or not. Default: False. Returns: (STIX object): STIX object that has the supplied STIX ID. The STIX object is received from TAXII has dict, parsed into a python STIX object and then returned - """ # combine all query filters query = set() @@ -124,22 +121,25 @@ class TAXIICollectionSource(DataSource): stix_obj = list(apply_common_filters(stix_objs, query)) if len(stix_obj): - stix_obj = stix_obj[0] - stix_obj = parse(stix_obj) + stix_obj = parse(stix_obj[0], allow_custom=allow_custom) + if stix_obj.id != stix_id: + # check - was added to handle erroneous TAXII servers + stix_obj = None else: stix_obj = None return stix_obj - def all_versions(self, stix_id, _composite_filters=None): - """retrieve STIX object from local/remote TAXII Collection + def all_versions(self, stix_id, _composite_filters=None, allow_custom=False): + """Retrieve STIX object from local/remote TAXII Collection endpoint, all versions of it Args: stix_id (str): The STIX ID of the STIX objects to be retrieved. - composite_filters (set): set of filters passed from the parent CompositeDataSource, not user supplied + allow_custom (bool): whether to retrieve custom objects/properties + or not. Default: False. Returns: (see query() as all_versions() is just a wrapper) @@ -151,12 +151,18 @@ class TAXIICollectionSource(DataSource): Filter("match[version]", "=", "all") ] - all_data = self.query(query=query, _composite_filters=_composite_filters) + all_data = self.query(query=query, _composite_filters=_composite_filters, allow_custom=allow_custom) - return all_data + # parse STIX objects from TAXII returned json + all_data = [parse(stix_obj) for stix_obj in all_data] - def query(self, query=None, _composite_filters=None): - """search and retreive STIX objects based on the complete query + # check - was added to handle erroneous TAXII servers + all_data_clean = [stix_obj for stix_obj in all_data if stix_obj.id == stix_id] + + return all_data_clean + + def query(self, query=None, _composite_filters=None, allow_custom=False): + """Search and retreive STIX objects based on the complete query A "complete query" includes the filters from the query, the filters attached to MemorySource, and any filters passed from a @@ -164,9 +170,10 @@ class TAXIICollectionSource(DataSource): Args: query (list): list of filters to search on - composite_filters (set): set of filters passed from the CompositeDataSource, not user supplied + allow_custom (bool): whether to retrieve custom objects/properties + or not. Default: False. Returns: (list): list of STIX objects that matches the supplied @@ -174,14 +181,13 @@ class TAXIICollectionSource(DataSource): parsed into python STIX objects and then returned. """ - if query is None: query = set() else: if not isinstance(query, list): # make sure dont make set from a Filter object, # need to make a set from a list of Filter objects (even if just one Filter) - query = list(query) + query = [query] query = set(query) # combine all query filters @@ -194,7 +200,7 @@ class TAXIICollectionSource(DataSource): taxii_filters = self._parse_taxii_filters(query) # query TAXII collection - all_data = self.collection.get_objects(filters=taxii_filters)["objects"] + all_data = self.collection.get_objects(filters=taxii_filters, allow_custom=allow_custom)["objects"] # deduplicate data (before filtering as reduces wasted filtering) all_data = deduplicate(all_data) @@ -203,7 +209,7 @@ class TAXIICollectionSource(DataSource): all_data = list(apply_common_filters(all_data, query)) # parse python STIX objects from the STIX object dicts - stix_objs = [parse(stix_obj_dict) for stix_obj_dict in all_data] + stix_objs = [parse(stix_obj_dict, allow_custom=allow_custom) for stix_obj_dict in all_data] return stix_objs @@ -225,14 +231,13 @@ class TAXIICollectionSource(DataSource): for 'requests.get()'. """ - params = {} for filter_ in query: - if filter_.field in TAXII_FILTERS: - if filter_.field == "added_after": - params[filter_.field] = filter_.value + if filter_.property in TAXII_FILTERS: + if filter_.property == "added_after": + params[filter_.property] = filter_.value else: - taxii_field = "match[%s]" % filter_.field + taxii_field = "match[%s]" % filter_.property params[taxii_field] = filter_.value return params diff --git a/stix2/test/stix2_data/course-of-action/course-of-action--d9727aee-48b8-4fdb-89e2-4c49746ba4dd.json b/stix2/test/stix2_data/course-of-action/course-of-action--d9727aee-48b8-4fdb-89e2-4c49746ba4dd.json index 5bfb8bb..cb9cfe2 100755 --- a/stix2/test/stix2_data/course-of-action/course-of-action--d9727aee-48b8-4fdb-89e2-4c49746ba4dd.json +++ b/stix2/test/stix2_data/course-of-action/course-of-action--d9727aee-48b8-4fdb-89e2-4c49746ba4dd.json @@ -1,16 +1,9 @@ { - "id": "bundle--2ed6ab6a-ca68-414f-8493-e4db8b75dd51", - "objects": [ - { - "created": "2017-05-31T21:30:41.022744Z", - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "description": "Identify unnecessary system utilities or potentially malicious software that may be used to collect data from a network share, and audit and/or block them by using whitelisting[[CiteRef::Beechey 2010]] tools, like AppLocker,[[CiteRef::Windows Commands JPCERT]][[CiteRef::NSA MS AppLocker]] or Software Restriction Policies[[CiteRef::Corio 2008]] where appropriate.[[CiteRef::TechNet Applocker vs SRP]]", - "id": "course-of-action--d9727aee-48b8-4fdb-89e2-4c49746ba4dd", - "modified": "2017-05-31T21:30:41.022744Z", - "name": "Data from Network Shared Drive Mitigation", - "type": "course-of-action" - } - ], - "spec_version": "2.0", - "type": "bundle" + "created": "2017-05-31T21:30:41.022744Z", + "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", + "description": "Identify unnecessary system utilities or potentially malicious software that may be used to collect data from a network share, and audit and/or block them by using whitelisting[[CiteRef::Beechey 2010]] tools, like AppLocker,[[CiteRef::Windows Commands JPCERT]][[CiteRef::NSA MS AppLocker]] or Software Restriction Policies[[CiteRef::Corio 2008]] where appropriate.[[CiteRef::TechNet Applocker vs SRP]]", + "id": "course-of-action--d9727aee-48b8-4fdb-89e2-4c49746ba4dd", + "modified": "2017-05-31T21:30:41.022744Z", + "name": "Data from Network Shared Drive Mitigation", + "type": "course-of-action" } \ No newline at end of file diff --git a/stix2/test/test_bundle.py b/stix2/test/test_bundle.py index c7c95a8..b1cffd0 100644 --- a/stix2/test/test_bundle.py +++ b/stix2/test/test_bundle.py @@ -132,8 +132,9 @@ def test_create_bundle_invalid(indicator, malware, relationship): assert excinfo.value.reason == 'This property may not contain a Bundle object' -def test_parse_bundle(): - bundle = stix2.parse(EXPECTED_BUNDLE) +@pytest.mark.parametrize("version", ["2.0"]) +def test_parse_bundle(version): + bundle = stix2.parse(EXPECTED_BUNDLE, version=version) assert bundle.type == "bundle" assert bundle.id.startswith("bundle--") @@ -158,3 +159,10 @@ def test_parse_unknown_type(): with pytest.raises(stix2.exceptions.ParseError) as excinfo: stix2.parse(unknown) assert str(excinfo.value) == "Can't parse unknown object type 'other'! For custom types, use the CustomObject decorator." + + +def test_stix_object_property(): + prop = stix2.core.STIXObjectProperty() + + identity = stix2.Identity(name="test", identity_class="individual") + assert prop.clean(identity) is identity diff --git a/stix2/test/test_custom.py b/stix2/test/test_custom.py index 48529b9..92d5d4c 100644 --- a/stix2/test/test_custom.py +++ b/stix2/test/test_custom.py @@ -91,6 +91,7 @@ def test_custom_property_in_bundled_object(): bundle = stix2.Bundle(identity, allow_custom=True) assert bundle.objects[0].x_foo == "bar" + assert '"x_foo": "bar"' in str(bundle) @stix2.sdo.CustomObject('x-new-type', [ @@ -483,3 +484,13 @@ def test_parse_observable_with_unregistered_custom_extension(): with pytest.raises(ValueError) as excinfo: stix2.parse_observable(input_str) assert "Can't parse Unknown extension type" in str(excinfo.value) + + +def test_register_custom_object(): + # Not the way to register custom object. + class CustomObject2(object): + _type = 'awesome-object' + + stix2._register_type(CustomObject2) + # Note that we will always check against newest OBJ_MAP. + assert (CustomObject2._type, CustomObject2) in stix2.OBJ_MAP.items() diff --git a/stix2/test/test_data_sources.py b/stix2/test/test_data_sources.py index 689fe8c..3327ca9 100644 --- a/stix2/test/test_data_sources.py +++ b/stix2/test/test_data_sources.py @@ -1,17 +1,13 @@ -import os - import pytest from taxii2client import Collection -from stix2 import (Campaign, FileSystemSink, FileSystemSource, FileSystemStore, - Filter, MemorySource, MemoryStore) +from stix2 import Filter, MemorySource from stix2.sources import (CompositeDataSource, DataSink, DataSource, DataStore, make_id, taxii) from stix2.sources.filters import apply_common_filters from stix2.utils import deduplicate COLLECTION_URL = 'https://example.com/api1/collections/91a7b528-80eb-42ed-a74d-c6fbd5a26116/' -FS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "stix2_data") class MockTAXIIClient(object): @@ -148,28 +144,6 @@ def test_ds_abstract_class_smoke(): ds3.query([Filter("id", "=", "malware--fdd60b30-b67c-11e3-b0b9-f01faf20d111")]) -def test_memory_store_smoke(): - # Initialize MemoryStore with dict - ms = MemoryStore(STIX_OBJS1) - - # Add item to sink - ms.add(dict(id="bundle--%s" % make_id(), - objects=STIX_OBJS2, - spec_version="2.0", - type="bundle")) - - resp = ms.all_versions("indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f") - assert len(resp) == 1 - - resp = ms.get("indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f") - assert resp["id"] == "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f" - - query = [Filter('type', '=', 'malware')] - - resp = ms.query(query) - assert len(resp) == 0 - - def test_ds_taxii(collection): ds = taxii.TAXIICollectionSource(collection) assert ds.collection is not None @@ -205,7 +179,7 @@ def test_parse_taxii_filters(): def test_add_get_remove_filter(ds): - # First 3 filters are valid, remaining fields are erroneous in some way + # First 3 filters are valid, remaining properties are erroneous in some way valid_filters = [ Filter('type', '=', 'malware'), Filter('id', '!=', 'stix object id'), @@ -219,14 +193,14 @@ def test_add_get_remove_filter(ds): with pytest.raises(ValueError) as excinfo: # create Filter that has an operator that is not allowed Filter('modified', '*', 'not supported operator - just place holder') - assert str(excinfo.value) == "Filter operator '*' not supported for specified field: 'modified'" + assert str(excinfo.value) == "Filter operator '*' not supported for specified property: 'modified'" with pytest.raises(TypeError) as excinfo: # create Filter that has a value type that is not allowed Filter('created', '=', object()) # On Python 2, the type of object() is `` On Python 3, it's ``. assert str(excinfo.value).startswith("Filter value type") - assert str(excinfo.value).endswith("is not supported. The type must be a python immutable type or dictionary") + assert str(excinfo.value).endswith("is not supported. The type must be a Python immutable type or dictionary") assert len(ds.filters) == 0 @@ -433,7 +407,7 @@ def test_filters4(ds): with pytest.raises(ValueError) as excinfo: Filter("modified", "?", "2017-01-27T13:49:53.935Z") assert str(excinfo.value) == ("Filter operator '?' not supported " - "for specified field: 'modified'") + "for specified property: 'modified'") def test_filters5(ds): @@ -443,6 +417,52 @@ def test_filters5(ds): assert len(resp) == 1 +def test_filters6(ds): + # Test filtering on non-common property + resp = list(apply_common_filters(STIX_OBJS2, [Filter("name", "=", "Malicious site hosting downloader")])) + assert resp[0]['id'] == STIX_OBJS2[0]['id'] + assert len(resp) == 3 + + +def test_filters7(ds): + # Test filtering on embedded property + stix_objects = list(STIX_OBJS2) + [{ + "type": "observed-data", + "id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf", + "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", + "created": "2016-04-06T19:58:16.000Z", + "modified": "2016-04-06T19:58:16.000Z", + "first_observed": "2015-12-21T19:00:00Z", + "last_observed": "2015-12-21T19:00:00Z", + "number_observed": 50, + "objects": { + "0": { + "type": "file", + "hashes": { + "SHA-256": "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100f" + }, + "extensions": { + "pdf-ext": { + "version": "1.7", + "document_info_dict": { + "Title": "Sample document", + "Author": "Adobe Systems Incorporated", + "Creator": "Adobe FrameMaker 5.5.3 for Power Macintosh", + "Producer": "Acrobat Distiller 3.01 for Power Macintosh", + "CreationDate": "20070412090123-02" + }, + "pdfid0": "DFCE52BD827ECF765649852119D", + "pdfid1": "57A1E0F9ED2AE523E313C" + } + } + } + } + }] + resp = list(apply_common_filters(stix_objects, [Filter("objects.0.extensions.pdf-ext.version", ">", "1.2")])) + assert resp[0]['id'] == stix_objects[3]['id'] + assert len(resp) == 1 + + def test_deduplicate(ds): unique = deduplicate(STIX_OBJS1) @@ -512,207 +532,3 @@ def test_composite_datasource_operations(): # STIX_OBJS2 has indicator with later time, one with different id, one with # original time in STIX_OBJS1 assert len(results) == 3 - - -def test_filesytem_source(): - # creation - fs_source = FileSystemSource(FS_PATH) - assert fs_source.stix_dir == FS_PATH - - # get object - mal = fs_source.get("malware--6b616fc1-1505-48e3-8b2c-0d19337bff38") - assert mal.id == "malware--6b616fc1-1505-48e3-8b2c-0d19337bff38" - assert mal.name == "Rover" - - # all versions - (currently not a true all versions call as FileSystem cant have multiple versions) - id_ = fs_source.get("identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5") - assert id_.id == "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5" - assert id_.name == "The MITRE Corporation" - assert id_.type == "identity" - - # query - intrusion_sets = fs_source.query([Filter("type", '=', "intrusion-set")]) - assert len(intrusion_sets) == 2 - assert "intrusion-set--a653431d-6a5e-4600-8ad3-609b5af57064" in [is_.id for is_ in intrusion_sets] - assert "intrusion-set--f3bdec95-3d62-42d9-a840-29630f6cdc1a" in [is_.id for is_ in intrusion_sets] - - is_1 = [is_ for is_ in intrusion_sets if is_.id == "intrusion-set--f3bdec95-3d62-42d9-a840-29630f6cdc1a"][0] - assert "DragonOK" in is_1.aliases - assert len(is_1.external_references) == 4 - - # query2 - is_2 = fs_source.query([Filter("external_references.external_id", '=', "T1027")]) - assert len(is_2) == 1 - - is_2 = is_2[0] - assert is_2.id == "attack-pattern--b3d682b6-98f2-4fb0-aa3b-b4df007ca70a" - assert is_2.type == "attack-pattern" - - -def test_filesystem_sink(): - # creation - fs_sink = FileSystemSink(FS_PATH) - assert fs_sink.stix_dir == FS_PATH - - fs_source = FileSystemSource(FS_PATH) - - # Test all the ways stix objects can be added (via different supplied forms) - - # add python stix object - camp1 = Campaign(name="Hannibal", - objective="Targeting Italian and Spanish Diplomat internet accounts", - aliases=["War Elephant"]) - - fs_sink.add(camp1) - - assert os.path.exists(os.path.join(FS_PATH, "campaign", camp1.id + ".json")) - - camp1_r = fs_source.get(camp1.id) - assert camp1_r.id == camp1.id - assert camp1_r.name == "Hannibal" - assert "War Elephant" in camp1_r.aliases - - # add stix object dict - camp2 = { - "name": "Aurelius", - "type": "campaign", - "objective": "German and French Intelligence Services", - "aliases": ["Purple Robes"], - "id": "campaign--111111b6-1112-4fb0-111b-b111107ca70a", - "created": "2017-05-31T21:31:53.197755Z" - } - - fs_sink.add(camp2) - - assert os.path.exists(os.path.join(FS_PATH, "campaign", camp2["id"] + ".json")) - - camp2_r = fs_source.get(camp2["id"]) - assert camp2_r.id == camp2["id"] - assert camp2_r.name == camp2["name"] - assert "Purple Robes" in camp2_r.aliases - - # add stix bundle dict - bund = { - "type": "bundle", - "id": "bundle--112211b6-1112-4fb0-111b-b111107ca70a", - "spec_version": "2.0", - "objects": [ - { - "name": "Atilla", - "type": "campaign", - "objective": "Bulgarian, Albanian and Romanian Intelligence Services", - "aliases": ["Huns"], - "id": "campaign--133111b6-1112-4fb0-111b-b111107ca70a", - "created": "2017-05-31T21:31:53.197755Z" - } - ] - } - - fs_sink.add(bund) - - assert os.path.exists(os.path.join(FS_PATH, "campaign", bund["objects"][0]["id"] + ".json")) - - camp3_r = fs_source.get(bund["objects"][0]["id"]) - assert camp3_r.id == bund["objects"][0]["id"] - assert camp3_r.name == bund["objects"][0]["name"] - assert "Huns" in camp3_r.aliases - - # add json-encoded stix obj - camp4 = '{"type": "campaign", "id":"campaign--144111b6-1112-4fb0-111b-b111107ca70a",'\ - ' "created":"2017-05-31T21:31:53.197755Z", "name": "Ghengis Khan", "objective": "China and Russian infrastructure"}' - - fs_sink.add(camp4) - - assert os.path.exists(os.path.join(FS_PATH, "campaign", "campaign--144111b6-1112-4fb0-111b-b111107ca70a" + ".json")) - - camp4_r = fs_source.get("campaign--144111b6-1112-4fb0-111b-b111107ca70a") - assert camp4_r.id == "campaign--144111b6-1112-4fb0-111b-b111107ca70a" - assert camp4_r.name == "Ghengis Khan" - - # add json-encoded stix bundle - bund2 = '{"type": "bundle", "id": "bundle--332211b6-1132-4fb0-111b-b111107ca70a",' \ - ' "spec_version": "2.0", "objects": [{"type": "campaign", "id": "campaign--155155b6-1112-4fb0-111b-b111107ca70a",' \ - ' "created":"2017-05-31T21:31:53.197755Z", "name": "Spartacus", "objective": "Oppressive regimes of Africa and Middle East"}]}' - fs_sink.add(bund2) - - assert os.path.exists(os.path.join(FS_PATH, "campaign", "campaign--155155b6-1112-4fb0-111b-b111107ca70a" + ".json")) - - camp5_r = fs_source.get("campaign--155155b6-1112-4fb0-111b-b111107ca70a") - assert camp5_r.id == "campaign--155155b6-1112-4fb0-111b-b111107ca70a" - assert camp5_r.name == "Spartacus" - - # add list of objects - camp6 = Campaign(name="Comanche", - objective="US Midwest manufacturing firms, oil refineries, and businesses", - aliases=["Horse Warrior"]) - - camp7 = { - "name": "Napolean", - "type": "campaign", - "objective": "Central and Eastern Europe military commands and departments", - "aliases": ["The Frenchmen"], - "id": "campaign--122818b6-1112-4fb0-111b-b111107ca70a", - "created": "2017-05-31T21:31:53.197755Z" - } - - fs_sink.add([camp6, camp7]) - - assert os.path.exists(os.path.join(FS_PATH, "campaign", camp6.id + ".json")) - assert os.path.exists(os.path.join(FS_PATH, "campaign", "campaign--122818b6-1112-4fb0-111b-b111107ca70a" + ".json")) - - camp6_r = fs_source.get(camp6.id) - assert camp6_r.id == camp6.id - assert "Horse Warrior" in camp6_r.aliases - - camp7_r = fs_source.get(camp7["id"]) - assert camp7_r.id == camp7["id"] - assert "The Frenchmen" in camp7_r.aliases - - # remove all added objects - os.remove(os.path.join(FS_PATH, "campaign", camp1_r.id + ".json")) - os.remove(os.path.join(FS_PATH, "campaign", camp2_r.id + ".json")) - os.remove(os.path.join(FS_PATH, "campaign", camp3_r.id + ".json")) - os.remove(os.path.join(FS_PATH, "campaign", camp4_r.id + ".json")) - os.remove(os.path.join(FS_PATH, "campaign", camp5_r.id + ".json")) - os.remove(os.path.join(FS_PATH, "campaign", camp6_r.id + ".json")) - os.remove(os.path.join(FS_PATH, "campaign", camp7_r.id + ".json")) - - # remove campaign dir (that was added in course of testing) - os.rmdir(os.path.join(FS_PATH, "campaign")) - - -def test_filesystem_store(): - # creation - fs_store = FileSystemStore(FS_PATH) - - # get() - coa = fs_store.get("course-of-action--d9727aee-48b8-4fdb-89e2-4c49746ba4dd") - assert coa.id == "course-of-action--d9727aee-48b8-4fdb-89e2-4c49746ba4dd" - assert coa.type == "course-of-action" - - # all versions() - (note at this time, all_versions() is still not applicable to FileSystem, as only one version is ever stored) - rel = fs_store.all_versions("relationship--70dc6b5c-c524-429e-a6ab-0dd40f0482c1")[0] - assert rel.id == "relationship--70dc6b5c-c524-429e-a6ab-0dd40f0482c1" - assert rel.type == "relationship" - - # query() - tools = fs_store.query([Filter("labels", "in", "tool")]) - assert len(tools) == 2 - assert "tool--242f3da3-4425-4d11-8f5c-b842886da966" in [tool.id for tool in tools] - assert "tool--03342581-f790-4f03-ba41-e82e67392e23" in [tool.id for tool in tools] - - # add() - camp1 = Campaign(name="Great Heathen Army", - objective="Targeting the government of United Kingdom and insitutions affiliated with the Church Of England", - aliases=["Ragnar"]) - fs_store.add(camp1) - - camp1_r = fs_store.get(camp1.id) - assert camp1_r.id == camp1.id - assert camp1_r.name == camp1.name - - # remove - os.remove(os.path.join(FS_PATH, "campaign", camp1_r.id + ".json")) - - # remove campaign dir - os.rmdir(os.path.join(FS_PATH, "campaign")) diff --git a/stix2/test/test_environment.py b/stix2/test/test_environment.py index 81f2cda..c669a33 100644 --- a/stix2/test/test_environment.py +++ b/stix2/test/test_environment.py @@ -184,3 +184,35 @@ def test_parse_malware(): assert mal.modified == FAKE_TIME assert mal.labels == ['ransomware'] assert mal.name == "Cryptolocker" + + +def test_created_by(): + identity = stix2.Identity(**IDENTITY_KWARGS) + factory = stix2.ObjectFactory(created_by_ref=identity.id) + env = stix2.Environment(store=stix2.MemoryStore(), factory=factory) + env.add(identity) + + ind = env.create(stix2.Indicator, **INDICATOR_KWARGS) + creator = env.creator_of(ind) + assert creator is identity + + +def test_created_by_no_datasource(): + identity = stix2.Identity(**IDENTITY_KWARGS) + factory = stix2.ObjectFactory(created_by_ref=identity.id) + env = stix2.Environment(factory=factory) + + ind = env.create(stix2.Indicator, **INDICATOR_KWARGS) + with pytest.raises(AttributeError) as excinfo: + env.creator_of(ind) + assert 'Environment has no data source' in str(excinfo.value) + + +def test_created_by_not_found(): + identity = stix2.Identity(**IDENTITY_KWARGS) + factory = stix2.ObjectFactory(created_by_ref=identity.id) + env = stix2.Environment(store=stix2.MemoryStore(), factory=factory) + + ind = env.create(stix2.Indicator, **INDICATOR_KWARGS) + creator = env.creator_of(ind) + assert creator is None diff --git a/stix2/test/test_filesystem.py b/stix2/test/test_filesystem.py new file mode 100644 index 0000000..7aaa3f5 --- /dev/null +++ b/stix2/test/test_filesystem.py @@ -0,0 +1,377 @@ +import os +import shutil + +import pytest + +from stix2 import (Bundle, Campaign, CustomObject, FileSystemSink, + FileSystemSource, FileSystemStore, Filter, properties) + +FS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "stix2_data") + + +@pytest.fixture +def fs_store(): + # create + yield FileSystemStore(FS_PATH) + + # remove campaign dir + shutil.rmtree(os.path.join(FS_PATH, "campaign"), True) + + +@pytest.fixture +def fs_source(): + # create + fs = FileSystemSource(FS_PATH) + assert fs.stix_dir == FS_PATH + yield fs + + # remove campaign dir + shutil.rmtree(os.path.join(FS_PATH, "campaign"), True) + + +@pytest.fixture +def fs_sink(): + # create + fs = FileSystemSink(FS_PATH) + assert fs.stix_dir == FS_PATH + yield fs + + # remove campaign dir + shutil.rmtree(os.path.join(FS_PATH, "campaign"), True) + + +def test_filesystem_source_nonexistent_folder(): + with pytest.raises(ValueError) as excinfo: + FileSystemSource('nonexistent-folder') + assert "for STIX data does not exist" in str(excinfo) + + +def test_filesystem_sink_nonexistent_folder(): + with pytest.raises(ValueError) as excinfo: + FileSystemSink('nonexistent-folder') + assert "for STIX data does not exist" in str(excinfo) + + +def test_filesytem_source_get_object(fs_source): + # get object + mal = fs_source.get("malware--6b616fc1-1505-48e3-8b2c-0d19337bff38") + assert mal.id == "malware--6b616fc1-1505-48e3-8b2c-0d19337bff38" + assert mal.name == "Rover" + + +def test_filesytem_source_get_nonexistent_object(fs_source): + ind = fs_source.get("indicator--6b616fc1-1505-48e3-8b2c-0d19337bff38") + assert ind is None + + +def test_filesytem_source_all_versions(fs_source): + # all versions - (currently not a true all versions call as FileSystem cant have multiple versions) + id_ = fs_source.get("identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5") + assert id_.id == "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5" + assert id_.name == "The MITRE Corporation" + assert id_.type == "identity" + + +def test_filesytem_source_query_single(fs_source): + # query2 + is_2 = fs_source.query([Filter("external_references.external_id", '=', "T1027")]) + assert len(is_2) == 1 + + is_2 = is_2[0] + assert is_2.id == "attack-pattern--b3d682b6-98f2-4fb0-aa3b-b4df007ca70a" + assert is_2.type == "attack-pattern" + + +def test_filesytem_source_query_multiple(fs_source): + # query + intrusion_sets = fs_source.query([Filter("type", '=', "intrusion-set")]) + assert len(intrusion_sets) == 2 + assert "intrusion-set--a653431d-6a5e-4600-8ad3-609b5af57064" in [is_.id for is_ in intrusion_sets] + assert "intrusion-set--f3bdec95-3d62-42d9-a840-29630f6cdc1a" in [is_.id for is_ in intrusion_sets] + + is_1 = [is_ for is_ in intrusion_sets if is_.id == "intrusion-set--f3bdec95-3d62-42d9-a840-29630f6cdc1a"][0] + assert "DragonOK" in is_1.aliases + assert len(is_1.external_references) == 4 + + +def test_filesystem_sink_add_python_stix_object(fs_sink, fs_source): + # add python stix object + camp1 = Campaign(name="Hannibal", + objective="Targeting Italian and Spanish Diplomat internet accounts", + aliases=["War Elephant"]) + + fs_sink.add(camp1) + + assert os.path.exists(os.path.join(FS_PATH, "campaign", camp1.id + ".json")) + + camp1_r = fs_source.get(camp1.id) + assert camp1_r.id == camp1.id + assert camp1_r.name == "Hannibal" + assert "War Elephant" in camp1_r.aliases + + os.remove(os.path.join(FS_PATH, "campaign", camp1_r.id + ".json")) + + +def test_filesystem_sink_add_stix_object_dict(fs_sink, fs_source): + # add stix object dict + camp2 = { + "name": "Aurelius", + "type": "campaign", + "objective": "German and French Intelligence Services", + "aliases": ["Purple Robes"], + "id": "campaign--111111b6-1112-4fb0-111b-b111107ca70a", + "created": "2017-05-31T21:31:53.197755Z" + } + + fs_sink.add(camp2) + + assert os.path.exists(os.path.join(FS_PATH, "campaign", camp2["id"] + ".json")) + + camp2_r = fs_source.get(camp2["id"]) + assert camp2_r.id == camp2["id"] + assert camp2_r.name == camp2["name"] + assert "Purple Robes" in camp2_r.aliases + + os.remove(os.path.join(FS_PATH, "campaign", camp2_r.id + ".json")) + + +def test_filesystem_sink_add_stix_bundle_dict(fs_sink, fs_source): + # add stix bundle dict + bund = { + "type": "bundle", + "id": "bundle--112211b6-1112-4fb0-111b-b111107ca70a", + "spec_version": "2.0", + "objects": [ + { + "name": "Atilla", + "type": "campaign", + "objective": "Bulgarian, Albanian and Romanian Intelligence Services", + "aliases": ["Huns"], + "id": "campaign--133111b6-1112-4fb0-111b-b111107ca70a", + "created": "2017-05-31T21:31:53.197755Z" + } + ] + } + + fs_sink.add(bund) + + assert os.path.exists(os.path.join(FS_PATH, "campaign", bund["objects"][0]["id"] + ".json")) + + camp3_r = fs_source.get(bund["objects"][0]["id"]) + assert camp3_r.id == bund["objects"][0]["id"] + assert camp3_r.name == bund["objects"][0]["name"] + assert "Huns" in camp3_r.aliases + + os.remove(os.path.join(FS_PATH, "campaign", camp3_r.id + ".json")) + + +def test_filesystem_sink_add_json_stix_object(fs_sink, fs_source): + # add json-encoded stix obj + camp4 = '{"type": "campaign", "id":"campaign--144111b6-1112-4fb0-111b-b111107ca70a",'\ + ' "created":"2017-05-31T21:31:53.197755Z", "name": "Ghengis Khan", "objective": "China and Russian infrastructure"}' + + fs_sink.add(camp4) + + assert os.path.exists(os.path.join(FS_PATH, "campaign", "campaign--144111b6-1112-4fb0-111b-b111107ca70a" + ".json")) + + camp4_r = fs_source.get("campaign--144111b6-1112-4fb0-111b-b111107ca70a") + assert camp4_r.id == "campaign--144111b6-1112-4fb0-111b-b111107ca70a" + assert camp4_r.name == "Ghengis Khan" + + os.remove(os.path.join(FS_PATH, "campaign", camp4_r.id + ".json")) + + +def test_filesystem_sink_json_stix_bundle(fs_sink, fs_source): + # add json-encoded stix bundle + bund2 = '{"type": "bundle", "id": "bundle--332211b6-1132-4fb0-111b-b111107ca70a",' \ + ' "spec_version": "2.0", "objects": [{"type": "campaign", "id": "campaign--155155b6-1112-4fb0-111b-b111107ca70a",' \ + ' "created":"2017-05-31T21:31:53.197755Z", "name": "Spartacus", "objective": "Oppressive regimes of Africa and Middle East"}]}' + fs_sink.add(bund2) + + assert os.path.exists(os.path.join(FS_PATH, "campaign", "campaign--155155b6-1112-4fb0-111b-b111107ca70a" + ".json")) + + camp5_r = fs_source.get("campaign--155155b6-1112-4fb0-111b-b111107ca70a") + assert camp5_r.id == "campaign--155155b6-1112-4fb0-111b-b111107ca70a" + assert camp5_r.name == "Spartacus" + + os.remove(os.path.join(FS_PATH, "campaign", camp5_r.id + ".json")) + + +def test_filesystem_sink_add_objects_list(fs_sink, fs_source): + # add list of objects + camp6 = Campaign(name="Comanche", + objective="US Midwest manufacturing firms, oil refineries, and businesses", + aliases=["Horse Warrior"]) + + camp7 = { + "name": "Napolean", + "type": "campaign", + "objective": "Central and Eastern Europe military commands and departments", + "aliases": ["The Frenchmen"], + "id": "campaign--122818b6-1112-4fb0-111b-b111107ca70a", + "created": "2017-05-31T21:31:53.197755Z" + } + + fs_sink.add([camp6, camp7]) + + assert os.path.exists(os.path.join(FS_PATH, "campaign", camp6.id + ".json")) + assert os.path.exists(os.path.join(FS_PATH, "campaign", "campaign--122818b6-1112-4fb0-111b-b111107ca70a" + ".json")) + + camp6_r = fs_source.get(camp6.id) + assert camp6_r.id == camp6.id + assert "Horse Warrior" in camp6_r.aliases + + camp7_r = fs_source.get(camp7["id"]) + assert camp7_r.id == camp7["id"] + assert "The Frenchmen" in camp7_r.aliases + + # remove all added objects + os.remove(os.path.join(FS_PATH, "campaign", camp6_r.id + ".json")) + os.remove(os.path.join(FS_PATH, "campaign", camp7_r.id + ".json")) + + +def test_filesystem_store_get_stored_as_bundle(fs_store): + coa = fs_store.get("course-of-action--95ddb356-7ba0-4bd9-a889-247262b8946f") + assert coa.id == "course-of-action--95ddb356-7ba0-4bd9-a889-247262b8946f" + assert coa.type == "course-of-action" + + +def test_filesystem_store_get_stored_as_object(fs_store): + coa = fs_store.get("course-of-action--d9727aee-48b8-4fdb-89e2-4c49746ba4dd") + assert coa.id == "course-of-action--d9727aee-48b8-4fdb-89e2-4c49746ba4dd" + assert coa.type == "course-of-action" + + +def test_filesystem_store_all_versions(fs_store): + # all versions() - (note at this time, all_versions() is still not applicable to FileSystem, as only one version is ever stored) + rel = fs_store.all_versions("relationship--70dc6b5c-c524-429e-a6ab-0dd40f0482c1")[0] + assert rel.id == "relationship--70dc6b5c-c524-429e-a6ab-0dd40f0482c1" + assert rel.type == "relationship" + + +def test_filesystem_store_query(fs_store): + # query() + tools = fs_store.query([Filter("labels", "in", "tool")]) + assert len(tools) == 2 + assert "tool--242f3da3-4425-4d11-8f5c-b842886da966" in [tool.id for tool in tools] + assert "tool--03342581-f790-4f03-ba41-e82e67392e23" in [tool.id for tool in tools] + + +def test_filesystem_store_query_single_filter(fs_store): + query = Filter("labels", "in", "tool") + tools = fs_store.query(query) + assert len(tools) == 2 + assert "tool--242f3da3-4425-4d11-8f5c-b842886da966" in [tool.id for tool in tools] + assert "tool--03342581-f790-4f03-ba41-e82e67392e23" in [tool.id for tool in tools] + + +def test_filesystem_store_empty_query(fs_store): + results = fs_store.query() # returns all + assert len(results) == 26 + assert "tool--242f3da3-4425-4d11-8f5c-b842886da966" in [obj.id for obj in results] + assert "marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168" in [obj.id for obj in results] + + +def test_filesystem_store_query_multiple_filters(fs_store): + fs_store.source.filters.add(Filter("labels", "in", "tool")) + tools = fs_store.query(Filter("id", "=", "tool--242f3da3-4425-4d11-8f5c-b842886da966")) + assert len(tools) == 1 + assert tools[0].id == "tool--242f3da3-4425-4d11-8f5c-b842886da966" + + +def test_filesystem_store_query_dont_include_type_folder(fs_store): + results = fs_store.query(Filter("type", "!=", "tool")) + assert len(results) == 24 + + +def test_filesystem_store_add(fs_store): + # add() + camp1 = Campaign(name="Great Heathen Army", + objective="Targeting the government of United Kingdom and insitutions affiliated with the Church Of England", + aliases=["Ragnar"]) + fs_store.add(camp1) + + camp1_r = fs_store.get(camp1.id) + assert camp1_r.id == camp1.id + assert camp1_r.name == camp1.name + + # remove + os.remove(os.path.join(FS_PATH, "campaign", camp1_r.id + ".json")) + + +def test_filesystem_store_add_as_bundle(): + fs_store = FileSystemStore(FS_PATH, bundlify=True) + + camp1 = Campaign(name="Great Heathen Army", + objective="Targeting the government of United Kingdom and insitutions affiliated with the Church Of England", + aliases=["Ragnar"]) + fs_store.add(camp1) + + with open(os.path.join(FS_PATH, "campaign", camp1.id + ".json")) as bundle_file: + assert '"type": "bundle"' in bundle_file.read() + + camp1_r = fs_store.get(camp1.id) + assert camp1_r.id == camp1.id + assert camp1_r.name == camp1.name + + shutil.rmtree(os.path.join(FS_PATH, "campaign"), True) + + +def test_filesystem_add_bundle_object(fs_store): + bundle = Bundle() + fs_store.add(bundle) + + +def test_filesystem_store_add_invalid_object(fs_store): + ind = ('campaign', 'campaign--111111b6-1112-4fb0-111b-b111107ca70a') # tuple isn't valid + with pytest.raises(TypeError) as excinfo: + fs_store.add(ind) + assert 'stix_data must be' in str(excinfo.value) + assert 'a STIX object' in str(excinfo.value) + assert 'JSON formatted STIX' in str(excinfo.value) + assert 'JSON formatted STIX bundle' in str(excinfo.value) + + +def test_filesystem_object_with_custom_property(fs_store): + camp = Campaign(name="Scipio Africanus", + objective="Defeat the Carthaginians", + x_empire="Roman", + allow_custom=True) + + fs_store.add(camp, True) + + camp_r = fs_store.get(camp.id, True) + assert camp_r.id == camp.id + assert camp_r.x_empire == camp.x_empire + + +def test_filesystem_object_with_custom_property_in_bundle(fs_store): + camp = Campaign(name="Scipio Africanus", + objective="Defeat the Carthaginians", + x_empire="Roman", + allow_custom=True) + + bundle = Bundle(camp, allow_custom=True) + fs_store.add(bundle, True) + + camp_r = fs_store.get(camp.id, True) + assert camp_r.id == camp.id + assert camp_r.x_empire == camp.x_empire + + +def test_filesystem_custom_object(fs_store): + @CustomObject('x-new-obj', [ + ('property1', properties.StringProperty(required=True)), + ]) + class NewObj(): + pass + + newobj = NewObj(property1='something') + fs_store.add(newobj, True) + + newobj_r = fs_store.get(newobj.id, True) + assert newobj_r.id == newobj.id + assert newobj_r.property1 == 'something' + + # remove dir + shutil.rmtree(os.path.join(FS_PATH, "x-new-obj"), True) diff --git a/stix2/test/test_memory.py b/stix2/test/test_memory.py new file mode 100644 index 0000000..0603bf7 --- /dev/null +++ b/stix2/test/test_memory.py @@ -0,0 +1,270 @@ +import pytest + +from stix2 import (Bundle, Campaign, CustomObject, Filter, MemorySource, + MemoryStore, properties) +from stix2.sources import make_id + +IND1 = { + "created": "2017-01-27T13:49:53.935Z", + "id": "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f", + "labels": [ + "url-watchlist" + ], + "modified": "2017-01-27T13:49:53.935Z", + "name": "Malicious site hosting downloader", + "pattern": "[url:value = 'http://x4z9arb.cn/4712']", + "type": "indicator", + "valid_from": "2017-01-27T13:49:53.935382Z" +} +IND2 = { + "created": "2017-01-27T13:49:53.935Z", + "id": "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f", + "labels": [ + "url-watchlist" + ], + "modified": "2017-01-27T13:49:53.935Z", + "name": "Malicious site hosting downloader", + "pattern": "[url:value = 'http://x4z9arb.cn/4712']", + "type": "indicator", + "valid_from": "2017-01-27T13:49:53.935382Z" +} +IND3 = { + "created": "2017-01-27T13:49:53.935Z", + "id": "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f", + "labels": [ + "url-watchlist" + ], + "modified": "2017-01-27T13:49:53.936Z", + "name": "Malicious site hosting downloader", + "pattern": "[url:value = 'http://x4z9arb.cn/4712']", + "type": "indicator", + "valid_from": "2017-01-27T13:49:53.935382Z" +} +IND4 = { + "created": "2017-01-27T13:49:53.935Z", + "id": "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f", + "labels": [ + "url-watchlist" + ], + "modified": "2017-01-27T13:49:53.935Z", + "name": "Malicious site hosting downloader", + "pattern": "[url:value = 'http://x4z9arb.cn/4712']", + "type": "indicator", + "valid_from": "2017-01-27T13:49:53.935382Z" +} +IND5 = { + "created": "2017-01-27T13:49:53.935Z", + "id": "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f", + "labels": [ + "url-watchlist" + ], + "modified": "2017-01-27T13:49:53.935Z", + "name": "Malicious site hosting downloader", + "pattern": "[url:value = 'http://x4z9arb.cn/4712']", + "type": "indicator", + "valid_from": "2017-01-27T13:49:53.935382Z" +} +IND6 = { + "created": "2017-01-27T13:49:53.935Z", + "id": "indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f", + "labels": [ + "url-watchlist" + ], + "modified": "2017-01-31T13:49:53.935Z", + "name": "Malicious site hosting downloader", + "pattern": "[url:value = 'http://x4z9arb.cn/4712']", + "type": "indicator", + "valid_from": "2017-01-27T13:49:53.935382Z" +} +IND7 = { + "created": "2017-01-27T13:49:53.935Z", + "id": "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f", + "labels": [ + "url-watchlist" + ], + "modified": "2017-01-27T13:49:53.935Z", + "name": "Malicious site hosting downloader", + "pattern": "[url:value = 'http://x4z9arb.cn/4712']", + "type": "indicator", + "valid_from": "2017-01-27T13:49:53.935382Z" +} +IND8 = { + "created": "2017-01-27T13:49:53.935Z", + "id": "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f", + "labels": [ + "url-watchlist" + ], + "modified": "2017-01-27T13:49:53.935Z", + "name": "Malicious site hosting downloader", + "pattern": "[url:value = 'http://x4z9arb.cn/4712']", + "type": "indicator", + "valid_from": "2017-01-27T13:49:53.935382Z" +} + +STIX_OBJS2 = [IND6, IND7, IND8] +STIX_OBJS1 = [IND1, IND2, IND3, IND4, IND5] + + +@pytest.fixture +def mem_store(): + yield MemoryStore(STIX_OBJS1) + + +@pytest.fixture +def mem_source(): + yield MemorySource(STIX_OBJS1) + + +def test_memory_source_get(mem_source): + resp = mem_source.get("indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f") + assert resp["id"] == "indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f" + + +def test_memory_source_get_nonexistant_object(mem_source): + resp = mem_source.get("tool--d81f86b8-975b-bc0b-775e-810c5ad45a4f") + assert resp is None + + +def test_memory_store_all_versions(mem_store): + # Add bundle of items to sink + mem_store.add(dict(id="bundle--%s" % make_id(), + objects=STIX_OBJS2, + spec_version="2.0", + type="bundle")) + + resp = mem_store.all_versions("indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f") + assert len(resp) == 1 # MemoryStore can only store 1 version of each object + + +def test_memory_store_query(mem_store): + query = [Filter('type', '=', 'malware')] + resp = mem_store.query(query) + assert len(resp) == 0 + + +def test_memory_store_query_single_filter(mem_store): + query = Filter('id', '=', 'indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f') + resp = mem_store.query(query) + assert len(resp) == 1 + + +def test_memory_store_query_empty_query(mem_store): + resp = mem_store.query() + # sort since returned in random order + resp = sorted(resp, key=lambda k: k['id']) + assert len(resp) == 2 + assert resp[0]['id'] == 'indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f' + assert resp[0]['modified'] == '2017-01-27T13:49:53.935Z' + assert resp[1]['id'] == 'indicator--d81f86b9-975b-bc0b-775e-810c5ad45a4f' + assert resp[1]['modified'] == '2017-01-27T13:49:53.936Z' + + +def test_memory_store_query_multiple_filters(mem_store): + mem_store.source.filters.add(Filter('type', '=', 'indicator')) + query = Filter('id', '=', 'indicator--d81f86b8-975b-bc0b-775e-810c5ad45a4f') + resp = mem_store.query(query) + assert len(resp) == 1 + + +def test_memory_store_add_stix_object_str(mem_store): + # add stix object string + camp_id = "campaign--111111b6-1112-4fb0-111b-b111107ca70a" + camp_name = "Aurelius" + camp_alias = "Purple Robes" + camp = """{ + "name": "%s", + "type": "campaign", + "objective": "German and French Intelligence Services", + "aliases": ["%s"], + "id": "%s", + "created": "2017-05-31T21:31:53.197755Z" + }""" % (camp_name, camp_alias, camp_id) + + mem_store.add(camp) + + camp_r = mem_store.get(camp_id) + assert camp_r["id"] == camp_id + assert camp_r["name"] == camp_name + assert camp_alias in camp_r["aliases"] + + +def test_memory_store_add_stix_bundle_str(mem_store): + # add stix bundle string + camp_id = "campaign--133111b6-1112-4fb0-111b-b111107ca70a" + camp_name = "Atilla" + camp_alias = "Huns" + bund = """{ + "type": "bundle", + "id": "bundle--112211b6-1112-4fb0-111b-b111107ca70a", + "spec_version": "2.0", + "objects": [ + { + "name": "%s", + "type": "campaign", + "objective": "Bulgarian, Albanian and Romanian Intelligence Services", + "aliases": ["%s"], + "id": "%s", + "created": "2017-05-31T21:31:53.197755Z" + } + ] + }""" % (camp_name, camp_alias, camp_id) + + mem_store.add(bund) + + camp_r = mem_store.get(camp_id) + assert camp_r["id"] == camp_id + assert camp_r["name"] == camp_name + assert camp_alias in camp_r["aliases"] + + +def test_memory_store_add_invalid_object(mem_store): + ind = ('indicator', IND1) # tuple isn't valid + with pytest.raises(TypeError) as excinfo: + mem_store.add(ind) + assert 'stix_data must be' in str(excinfo.value) + assert 'a STIX object' in str(excinfo.value) + assert 'JSON formatted STIX' in str(excinfo.value) + assert 'JSON formatted STIX bundle' in str(excinfo.value) + + +def test_memory_store_object_with_custom_property(mem_store): + camp = Campaign(name="Scipio Africanus", + objective="Defeat the Carthaginians", + x_empire="Roman", + allow_custom=True) + + mem_store.add(camp, True) + + camp_r = mem_store.get(camp.id, True) + assert camp_r.id == camp.id + assert camp_r.x_empire == camp.x_empire + + +def test_memory_store_object_with_custom_property_in_bundle(mem_store): + camp = Campaign(name="Scipio Africanus", + objective="Defeat the Carthaginians", + x_empire="Roman", + allow_custom=True) + + bundle = Bundle(camp, allow_custom=True) + mem_store.add(bundle, True) + + bundle_r = mem_store.get(bundle.id, True) + camp_r = bundle_r['objects'][0] + assert camp_r.id == camp.id + assert camp_r.x_empire == camp.x_empire + + +def test_memory_store_custom_object(mem_store): + @CustomObject('x-new-obj', [ + ('property1', properties.StringProperty(required=True)), + ]) + class NewObj(): + pass + + newobj = NewObj(property1='something') + mem_store.add(newobj, True) + + newobj_r = mem_store.get(newobj.id, True) + assert newobj_r.id == newobj.id + assert newobj_r.property1 == 'something' diff --git a/stix2/test/test_properties.py b/stix2/test/test_properties.py index 7d03b9e..6bd1888 100644 --- a/stix2/test/test_properties.py +++ b/stix2/test/test_properties.py @@ -1,8 +1,7 @@ import pytest -from stix2 import TCPExt +from stix2 import EmailMIMEComponent, ExtensionsProperty, TCPExt from stix2.exceptions import AtLeastOnePropertyError, DictionaryKeyError -from stix2.observables import EmailMIMEComponent, ExtensionsProperty from stix2.properties import (BinaryProperty, BooleanProperty, DictionaryProperty, EmbeddedObjectProperty, EnumProperty, FloatProperty, HashesProperty, diff --git a/stix2/utils.py b/stix2/utils.py index 32aba72..cb02b15 100644 --- a/stix2/utils.py +++ b/stix2/utils.py @@ -34,7 +34,7 @@ class STIXdatetime(dt.datetime): def deduplicate(stix_obj_list): - """Deduplicate a list of STIX objects to a unique set + """Deduplicate a list of STIX objects to a unique set. Reduces a set of STIX objects to unique set by looking at 'id' and 'modified' fields - as a unique object version @@ -44,7 +44,6 @@ def deduplicate(stix_obj_list): of deduplicate(),that if the "stix_obj_list" argument has multiple STIX objects of the same version, the last object version found in the list will be the one that is returned. - () Args: stix_obj_list (list): list of STIX objects (dicts) @@ -56,7 +55,11 @@ def deduplicate(stix_obj_list): unique_objs = {} for obj in stix_obj_list: - unique_objs[(obj['id'], obj['modified'])] = obj + try: + unique_objs[(obj['id'], obj['modified'])] = obj + except KeyError: + # Handle objects with no `modified` property, e.g. marking-definition + unique_objs[(obj['id'], obj['created'])] = obj return list(unique_objs.values()) @@ -248,3 +251,11 @@ def revoke(data): if data.get("revoked"): raise RevokeError("revoke") return new_version(data, revoked=True) + + +def get_class_hierarchy_names(obj): + """Given an object, return the names of the class hierarchy.""" + names = [] + for cls in obj.__class__.__mro__: + names.append(cls.__name__) + return names diff --git a/stix2/v20/__init__.py b/stix2/v20/__init__.py new file mode 100644 index 0000000..888e1ca --- /dev/null +++ b/stix2/v20/__init__.py @@ -0,0 +1,43 @@ + +# flake8: noqa + +from ..core import Bundle +from .common import (TLP_AMBER, TLP_GREEN, TLP_RED, TLP_WHITE, CustomMarking, + ExternalReference, GranularMarking, KillChainPhase, + MarkingDefinition, StatementMarking, TLPMarking) +from .observables import (URL, AlternateDataStream, ArchiveExt, Artifact, + AutonomousSystem, CustomExtension, CustomObservable, + Directory, DomainName, EmailAddress, EmailMessage, + EmailMIMEComponent, ExtensionsProperty, File, + HTTPRequestExt, ICMPExt, IPv4Address, IPv6Address, + MACAddress, Mutex, NetworkTraffic, NTFSExt, PDFExt, + Process, RasterImageExt, SocketExt, Software, TCPExt, + UNIXAccountExt, UserAccount, WindowsPEBinaryExt, + WindowsPEOptionalHeaderType, WindowsPESection, + WindowsProcessExt, WindowsRegistryKey, + WindowsRegistryValueType, WindowsServiceExt, + X509Certificate, X509V3ExtenstionsType, + parse_observable) +from .sdo import (AttackPattern, Campaign, CourseOfAction, CustomObject, + Identity, Indicator, IntrusionSet, Malware, ObservedData, + Report, ThreatActor, Tool, Vulnerability) +from .sro import Relationship, Sighting + +OBJ_MAP = { + 'attack-pattern': AttackPattern, + 'bundle': Bundle, + 'campaign': Campaign, + 'course-of-action': CourseOfAction, + 'identity': Identity, + 'indicator': Indicator, + 'intrusion-set': IntrusionSet, + 'malware': Malware, + 'marking-definition': MarkingDefinition, + 'observed-data': ObservedData, + 'report': Report, + 'relationship': Relationship, + 'threat-actor': ThreatActor, + 'tool': Tool, + 'sighting': Sighting, + 'vulnerability': Vulnerability, +} diff --git a/stix2/v20/common.py b/stix2/v20/common.py new file mode 100644 index 0000000..51ded05 --- /dev/null +++ b/stix2/v20/common.py @@ -0,0 +1,189 @@ +"""STIX 2 Common Data Types and Properties.""" + +from collections import OrderedDict + +from ..base import _STIXBase +from ..markings import _MarkingsMixin +from ..properties import (HashesProperty, IDProperty, ListProperty, Property, + ReferenceProperty, SelectorProperty, StringProperty, + TimestampProperty, TypeProperty) +from ..utils import NOW, get_dict + + +class ExternalReference(_STIXBase): + + _properties = OrderedDict() + _properties.update([ + ('source_name', StringProperty(required=True)), + ('description', StringProperty()), + ('url', StringProperty()), + ('hashes', HashesProperty()), + ('external_id', StringProperty()), + ]) + + def _check_object_constraints(self): + super(ExternalReference, self)._check_object_constraints() + self._check_at_least_one_property(["description", "external_id", "url"]) + + +class KillChainPhase(_STIXBase): + + _properties = OrderedDict() + _properties.update([ + ('kill_chain_name', StringProperty(required=True)), + ('phase_name', StringProperty(required=True)), + ]) + + +class GranularMarking(_STIXBase): + + _properties = OrderedDict() + _properties.update([ + ('marking_ref', ReferenceProperty(required=True, type="marking-definition")), + ('selectors', ListProperty(SelectorProperty, required=True)), + ]) + + +class TLPMarking(_STIXBase): + + _type = 'tlp' + _properties = OrderedDict() + _properties.update([ + ('tlp', Property(required=True)) + ]) + + +class StatementMarking(_STIXBase): + + _type = 'statement' + _properties = OrderedDict() + _properties.update([ + ('statement', StringProperty(required=True)) + ]) + + def __init__(self, statement=None, **kwargs): + # Allow statement as positional args. + if statement and not kwargs.get('statement'): + kwargs['statement'] = statement + + super(StatementMarking, self).__init__(**kwargs) + + +class MarkingProperty(Property): + """Represent the marking objects in the ``definition`` property of + marking-definition objects. + """ + + def clean(self, value): + if type(value) in OBJ_MAP_MARKING.values(): + return value + else: + raise ValueError("must be a Statement, TLP Marking or a registered marking.") + + +class MarkingDefinition(_STIXBase, _MarkingsMixin): + + _type = 'marking-definition' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('id', IDProperty(_type)), + ('created_by_ref', ReferenceProperty(type="identity")), + ('created', TimestampProperty(default=lambda: NOW)), + ('external_references', ListProperty(ExternalReference)), + ('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))), + ('granular_markings', ListProperty(GranularMarking)), + ('definition_type', StringProperty(required=True)), + ('definition', MarkingProperty(required=True)), + ]) + + def __init__(self, **kwargs): + if set(('definition_type', 'definition')).issubset(kwargs.keys()): + # Create correct marking type object + try: + marking_type = OBJ_MAP_MARKING[kwargs['definition_type']] + except KeyError: + raise ValueError("definition_type must be a valid marking type") + + if not isinstance(kwargs['definition'], marking_type): + defn = get_dict(kwargs['definition']) + kwargs['definition'] = marking_type(**defn) + + super(MarkingDefinition, self).__init__(**kwargs) + + +OBJ_MAP_MARKING = { + 'tlp': TLPMarking, + 'statement': StatementMarking, +} + + +def _register_marking(cls): + """Register a custom STIX Marking Definition type. + """ + OBJ_MAP_MARKING[cls._type] = cls + return cls + + +def CustomMarking(type='x-custom-marking', properties=None): + """Custom STIX Marking decorator. + + Example: + >>> @CustomMarking('x-custom-marking', [ + ... ('property1', StringProperty(required=True)), + ... ('property2', IntegerProperty()), + ... ]) + ... class MyNewMarkingObjectType(): + ... pass + + """ + def custom_builder(cls): + + class _Custom(cls, _STIXBase): + _type = type + _properties = OrderedDict() + + if not properties or not isinstance(properties, list): + raise ValueError("Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]") + + _properties.update(properties) + + def __init__(self, **kwargs): + _STIXBase.__init__(self, **kwargs) + cls.__init__(self, **kwargs) + + _register_marking(_Custom) + return _Custom + + return custom_builder + + +# TODO: don't allow the creation of any other TLPMarkings than the ones below + +TLP_WHITE = MarkingDefinition( + id="marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9", + created="2017-01-20T00:00:00.000Z", + definition_type="tlp", + definition=TLPMarking(tlp="white") +) + +TLP_GREEN = MarkingDefinition( + id="marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da", + created="2017-01-20T00:00:00.000Z", + definition_type="tlp", + definition=TLPMarking(tlp="green") +) + +TLP_AMBER = MarkingDefinition( + id="marking-definition--f88d31f6-486f-44da-b317-01333bde0b82", + created="2017-01-20T00:00:00.000Z", + definition_type="tlp", + definition=TLPMarking(tlp="amber") +) + +TLP_RED = MarkingDefinition( + id="marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed", + created="2017-01-20T00:00:00.000Z", + definition_type="tlp", + definition=TLPMarking(tlp="red") +) diff --git a/stix2/v20/observables.py b/stix2/v20/observables.py new file mode 100644 index 0000000..a874df9 --- /dev/null +++ b/stix2/v20/observables.py @@ -0,0 +1,948 @@ +"""STIX 2.0 Cyber Observable Objects. + +Embedded observable object types, such as Email MIME Component, which is +embedded in Email Message objects, inherit from ``_STIXBase`` instead of +Observable and do not have a ``_type`` attribute. +""" + +from collections import OrderedDict + +from ..base import _Extension, _Observable, _STIXBase +from ..exceptions import (AtLeastOnePropertyError, DependentPropertiesError, + ParseError) +from ..properties import (BinaryProperty, BooleanProperty, DictionaryProperty, + EmbeddedObjectProperty, EnumProperty, FloatProperty, + HashesProperty, HexProperty, IntegerProperty, + ListProperty, ObjectReferenceProperty, Property, + StringProperty, TimestampProperty, TypeProperty) +from ..utils import get_dict + + +class ObservableProperty(Property): + """Property for holding Cyber Observable Objects. + """ + + def clean(self, value): + try: + dictified = get_dict(value) + except ValueError: + raise ValueError("The observable property must contain a dictionary") + if dictified == {}: + raise ValueError("The observable property must contain a non-empty dictionary") + + valid_refs = dict((k, v['type']) for (k, v) in dictified.items()) + + for key, obj in dictified.items(): + parsed_obj = parse_observable(obj, valid_refs) + dictified[key] = parsed_obj + + return dictified + + +class ExtensionsProperty(DictionaryProperty): + """Property for representing extensions on Observable objects. + """ + + def __init__(self, enclosing_type=None, required=False): + self.enclosing_type = enclosing_type + super(ExtensionsProperty, self).__init__(required) + + def clean(self, value): + try: + dictified = get_dict(value) + except ValueError: + raise ValueError("The extensions property must contain a dictionary") + if dictified == {}: + raise ValueError("The extensions property must contain a non-empty dictionary") + + if self.enclosing_type in EXT_MAP: + specific_type_map = EXT_MAP[self.enclosing_type] + for key, subvalue in dictified.items(): + if key in specific_type_map: + cls = specific_type_map[key] + if type(subvalue) is dict: + dictified[key] = cls(**subvalue) + elif type(subvalue) is cls: + dictified[key] = subvalue + else: + raise ValueError("Cannot determine extension type.") + else: + raise ValueError("The key used in the extensions dictionary is not an extension type name") + else: + raise ValueError("The enclosing type '%s' has no extensions defined" % self.enclosing_type) + return dictified + + +class Artifact(_Observable): + + _type = 'artifact' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('mime_type', StringProperty()), + ('payload_bin', BinaryProperty()), + ('url', StringProperty()), + ('hashes', HashesProperty()), + ('extensions', ExtensionsProperty(enclosing_type=_type)), + ]) + + def _check_object_constraints(self): + super(Artifact, self)._check_object_constraints() + self._check_mutually_exclusive_properties(["payload_bin", "url"]) + self._check_properties_dependency(["hashes"], ["url"]) + + +class AutonomousSystem(_Observable): + + _type = 'autonomous-system' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('number', IntegerProperty(required=True)), + ('name', StringProperty()), + ('rir', StringProperty()), + ('extensions', ExtensionsProperty(enclosing_type=_type)), + ]) + + +class Directory(_Observable): + + _type = 'directory' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('path', StringProperty(required=True)), + ('path_enc', StringProperty()), + # these are not the created/modified timestamps of the object itself + ('created', TimestampProperty()), + ('modified', TimestampProperty()), + ('accessed', TimestampProperty()), + ('contains_refs', ListProperty(ObjectReferenceProperty(valid_types=['file', 'directory']))), + ('extensions', ExtensionsProperty(enclosing_type=_type)), + ]) + + +class DomainName(_Observable): + + _type = 'domain-name' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('value', StringProperty(required=True)), + ('resolves_to_refs', ListProperty(ObjectReferenceProperty(valid_types=['ipv4-addr', 'ipv6-addr', 'domain-name']))), + ('extensions', ExtensionsProperty(enclosing_type=_type)), + ]) + + +class EmailAddress(_Observable): + + _type = 'email-addr' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('value', StringProperty(required=True)), + ('display_name', StringProperty()), + ('belongs_to_ref', ObjectReferenceProperty(valid_types='user-account')), + ('extensions', ExtensionsProperty(enclosing_type=_type)), + ]) + + +class EmailMIMEComponent(_STIXBase): + + _properties = OrderedDict() + _properties.update([ + ('body', StringProperty()), + ('body_raw_ref', ObjectReferenceProperty(valid_types=['artifact', 'file'])), + ('content_type', StringProperty()), + ('content_disposition', StringProperty()), + ]) + + def _check_object_constraints(self): + super(EmailMIMEComponent, self)._check_object_constraints() + self._check_at_least_one_property(["body", "body_raw_ref"]) + + +class EmailMessage(_Observable): + + _type = 'email-message' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('is_multipart', BooleanProperty(required=True)), + ('date', TimestampProperty()), + ('content_type', StringProperty()), + ('from_ref', ObjectReferenceProperty(valid_types='email-addr')), + ('sender_ref', ObjectReferenceProperty(valid_types='email-addr')), + ('to_refs', ListProperty(ObjectReferenceProperty(valid_types='email-addr'))), + ('cc_refs', ListProperty(ObjectReferenceProperty(valid_types='email-addr'))), + ('bcc_refs', ListProperty(ObjectReferenceProperty(valid_types='email-addr'))), + ('subject', StringProperty()), + ('received_lines', ListProperty(StringProperty)), + ('additional_header_fields', DictionaryProperty()), + ('body', StringProperty()), + ('body_multipart', ListProperty(EmbeddedObjectProperty(type=EmailMIMEComponent))), + ('raw_email_ref', ObjectReferenceProperty(valid_types='artifact')), + ('extensions', ExtensionsProperty(enclosing_type=_type)), + ]) + + def _check_object_constraints(self): + super(EmailMessage, self)._check_object_constraints() + self._check_properties_dependency(["is_multipart"], ["body_multipart"]) + if self.get("is_multipart") is True and self.get("body"): + # 'body' MAY only be used if is_multipart is false. + raise DependentPropertiesError(self.__class__, [("is_multipart", "body")]) + + +class ArchiveExt(_Extension): + + _type = 'archive-ext' + _properties = OrderedDict() + _properties.update([ + ('contains_refs', ListProperty(ObjectReferenceProperty(valid_types='file'), required=True)), + ('version', StringProperty()), + ('comment', StringProperty()), + ]) + + +class AlternateDataStream(_STIXBase): + + _properties = OrderedDict() + _properties.update([ + ('name', StringProperty(required=True)), + ('hashes', HashesProperty()), + ('size', IntegerProperty()), + ]) + + +class NTFSExt(_Extension): + + _type = 'ntfs-ext' + _properties = OrderedDict() + _properties.update([ + ('sid', StringProperty()), + ('alternate_data_streams', ListProperty(EmbeddedObjectProperty(type=AlternateDataStream))), + ]) + + +class PDFExt(_Extension): + + _type = 'pdf-ext' + _properties = OrderedDict() + _properties.update([ + ('version', StringProperty()), + ('is_optimized', BooleanProperty()), + ('document_info_dict', DictionaryProperty()), + ('pdfid0', StringProperty()), + ('pdfid1', StringProperty()), + ]) + + +class RasterImageExt(_Extension): + + _type = 'raster-image-ext' + _properties = OrderedDict() + _properties.update([ + ('image_height', IntegerProperty()), + ('image_weight', IntegerProperty()), + ('bits_per_pixel', IntegerProperty()), + ('image_compression_algorithm', StringProperty()), + ('exif_tags', DictionaryProperty()), + ]) + + +class WindowsPEOptionalHeaderType(_STIXBase): + + _properties = OrderedDict() + _properties.update([ + ('magic_hex', HexProperty()), + ('major_linker_version', IntegerProperty()), + ('minor_linker_version', IntegerProperty()), + ('size_of_code', IntegerProperty()), + ('size_of_initialized_data', IntegerProperty()), + ('size_of_uninitialized_data', IntegerProperty()), + ('address_of_entry_point', IntegerProperty()), + ('base_of_code', IntegerProperty()), + ('base_of_data', IntegerProperty()), + ('image_base', IntegerProperty()), + ('section_alignment', IntegerProperty()), + ('file_alignment', IntegerProperty()), + ('major_os_version', IntegerProperty()), + ('minor_os_version', IntegerProperty()), + ('major_image_version', IntegerProperty()), + ('minor_image_version', IntegerProperty()), + ('major_subsystem_version', IntegerProperty()), + ('minor_subsystem_version', IntegerProperty()), + ('win32_version_value_hex', HexProperty()), + ('size_of_image', IntegerProperty()), + ('size_of_headers', IntegerProperty()), + ('checksum_hex', HexProperty()), + ('subsystem_hex', HexProperty()), + ('dll_characteristics_hex', HexProperty()), + ('size_of_stack_reserve', IntegerProperty()), + ('size_of_stack_commit', IntegerProperty()), + ('size_of_heap_reserve', IntegerProperty()), + ('size_of_heap_commit', IntegerProperty()), + ('loader_flags_hex', HexProperty()), + ('number_of_rva_and_sizes', IntegerProperty()), + ('hashes', HashesProperty()), + ]) + + def _check_object_constraints(self): + super(WindowsPEOptionalHeaderType, self)._check_object_constraints() + self._check_at_least_one_property() + + +class WindowsPESection(_STIXBase): + + _properties = OrderedDict() + _properties.update([ + ('name', StringProperty(required=True)), + ('size', IntegerProperty()), + ('entropy', FloatProperty()), + ('hashes', HashesProperty()), + ]) + + +class WindowsPEBinaryExt(_Extension): + + _type = 'windows-pebinary-ext' + _properties = OrderedDict() + _properties.update([ + ('pe_type', StringProperty(required=True)), # open_vocab + ('imphash', StringProperty()), + ('machine_hex', HexProperty()), + ('number_of_sections', IntegerProperty()), + ('time_date_stamp', TimestampProperty(precision='second')), + ('pointer_to_symbol_table_hex', HexProperty()), + ('number_of_symbols', IntegerProperty()), + ('size_of_optional_header', IntegerProperty()), + ('characteristics_hex', HexProperty()), + ('file_header_hashes', HashesProperty()), + ('optional_header', EmbeddedObjectProperty(type=WindowsPEOptionalHeaderType)), + ('sections', ListProperty(EmbeddedObjectProperty(type=WindowsPESection))), + ]) + + +class File(_Observable): + + _type = 'file' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('hashes', HashesProperty()), + ('size', IntegerProperty()), + ('name', StringProperty()), + ('name_enc', StringProperty()), + ('magic_number_hex', HexProperty()), + ('mime_type', StringProperty()), + # these are not the created/modified timestamps of the object itself + ('created', TimestampProperty()), + ('modified', TimestampProperty()), + ('accessed', TimestampProperty()), + ('parent_directory_ref', ObjectReferenceProperty(valid_types='directory')), + ('is_encrypted', BooleanProperty()), + ('encryption_algorithm', StringProperty()), + ('decryption_key', StringProperty()), + ('contains_refs', ListProperty(ObjectReferenceProperty)), + ('content_ref', ObjectReferenceProperty(valid_types='artifact')), + ('extensions', ExtensionsProperty(enclosing_type=_type)), + ]) + + def _check_object_constraints(self): + super(File, self)._check_object_constraints() + self._check_properties_dependency(["is_encrypted"], ["encryption_algorithm", "decryption_key"]) + self._check_at_least_one_property(["hashes", "name"]) + + +class IPv4Address(_Observable): + + _type = 'ipv4-addr' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('value', StringProperty(required=True)), + ('resolves_to_refs', ListProperty(ObjectReferenceProperty(valid_types='mac-addr'))), + ('belongs_to_refs', ListProperty(ObjectReferenceProperty(valid_types='autonomous-system'))), + ('extensions', ExtensionsProperty(enclosing_type=_type)), + ]) + + +class IPv6Address(_Observable): + + _type = 'ipv6-addr' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('value', StringProperty(required=True)), + ('resolves_to_refs', ListProperty(ObjectReferenceProperty(valid_types='mac-addr'))), + ('belongs_to_refs', ListProperty(ObjectReferenceProperty(valid_types='autonomous-system'))), + ('extensions', ExtensionsProperty(enclosing_type=_type)), + ]) + + +class MACAddress(_Observable): + + _type = 'mac-addr' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('value', StringProperty(required=True)), + ('extensions', ExtensionsProperty(enclosing_type=_type)), + ]) + + +class Mutex(_Observable): + + _type = 'mutex' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('name', StringProperty(required=True)), + ('extensions', ExtensionsProperty(enclosing_type=_type)), + ]) + + +class HTTPRequestExt(_Extension): + + _type = 'http-request-ext' + _properties = OrderedDict() + _properties.update([ + ('request_method', StringProperty(required=True)), + ('request_value', StringProperty(required=True)), + ('request_version', StringProperty()), + ('request_header', DictionaryProperty()), + ('message_body_length', IntegerProperty()), + ('message_body_data_ref', ObjectReferenceProperty(valid_types='artifact')), + ]) + + +class ICMPExt(_Extension): + + _type = 'icmp-ext' + _properties = OrderedDict() + _properties.update([ + ('icmp_type_hex', HexProperty(required=True)), + ('icmp_code_hex', HexProperty(required=True)), + ]) + + +class SocketExt(_Extension): + + _type = 'socket-ext' + _properties = OrderedDict() + _properties.update([ + ('address_family', EnumProperty(allowed=[ + "AF_UNSPEC", + "AF_INET", + "AF_IPX", + "AF_APPLETALK", + "AF_NETBIOS", + "AF_INET6", + "AF_IRDA", + "AF_BTH", + ], required=True)), + ('is_blocking', BooleanProperty()), + ('is_listening', BooleanProperty()), + ('protocol_family', EnumProperty(allowed=[ + "PF_INET", + "PF_IPX", + "PF_APPLETALK", + "PF_INET6", + "PF_AX25", + "PF_NETROM" + ])), + ('options', DictionaryProperty()), + ('socket_type', EnumProperty(allowed=[ + "SOCK_STREAM", + "SOCK_DGRAM", + "SOCK_RAW", + "SOCK_RDM", + "SOCK_SEQPACKET", + ])), + ('socket_descriptor', IntegerProperty()), + ('socket_handle', IntegerProperty()), + ]) + + +class TCPExt(_Extension): + + _type = 'tcp-ext' + _properties = OrderedDict() + _properties.update([ + ('src_flags_hex', HexProperty()), + ('dst_flags_hex', HexProperty()), + ]) + + +class NetworkTraffic(_Observable): + + _type = 'network-traffic' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('start', TimestampProperty()), + ('end', TimestampProperty()), + ('is_active', BooleanProperty()), + ('src_ref', ObjectReferenceProperty(valid_types=['ipv4-addr', 'ipv6-addr', 'mac-addr', 'domain-name'])), + ('dst_ref', ObjectReferenceProperty(valid_types=['ipv4-addr', 'ipv6-addr', 'mac-addr', 'domain-name'])), + ('src_port', IntegerProperty()), + ('dst_port', IntegerProperty()), + ('protocols', ListProperty(StringProperty, required=True)), + ('src_byte_count', IntegerProperty()), + ('dst_byte_count', IntegerProperty()), + ('src_packets', IntegerProperty()), + ('dst_packets', IntegerProperty()), + ('ipfix', DictionaryProperty()), + ('src_payload_ref', ObjectReferenceProperty(valid_types='artifact')), + ('dst_payload_ref', ObjectReferenceProperty(valid_types='artifact')), + ('encapsulates_refs', ListProperty(ObjectReferenceProperty(valid_types='network-traffic'))), + ('encapsulates_by_ref', ObjectReferenceProperty(valid_types='network-traffic')), + ('extensions', ExtensionsProperty(enclosing_type=_type)), + ]) + + def _check_object_constraints(self): + super(NetworkTraffic, self)._check_object_constraints() + self._check_at_least_one_property(["src_ref", "dst_ref"]) + + +class WindowsProcessExt(_Extension): + + _type = 'windows-process-ext' + _properties = OrderedDict() + _properties.update([ + ('aslr_enabled', BooleanProperty()), + ('dep_enabled', BooleanProperty()), + ('priority', StringProperty()), + ('owner_sid', StringProperty()), + ('window_title', StringProperty()), + ('startup_info', DictionaryProperty()), + ]) + + +class WindowsServiceExt(_Extension): + + _type = 'windows-service-ext' + _properties = OrderedDict() + _properties.update([ + ('service_name', StringProperty(required=True)), + ('descriptions', ListProperty(StringProperty)), + ('display_name', StringProperty()), + ('group_name', StringProperty()), + ('start_type', EnumProperty(allowed=[ + "SERVICE_AUTO_START", + "SERVICE_BOOT_START", + "SERVICE_DEMAND_START", + "SERVICE_DISABLED", + "SERVICE_SYSTEM_ALERT", + ])), + ('service_dll_refs', ListProperty(ObjectReferenceProperty(valid_types='file'))), + ('service_type', EnumProperty(allowed=[ + "SERVICE_KERNEL_DRIVER", + "SERVICE_FILE_SYSTEM_DRIVER", + "SERVICE_WIN32_OWN_PROCESS", + "SERVICE_WIN32_SHARE_PROCESS", + ])), + ('service_status', EnumProperty(allowed=[ + "SERVICE_CONTINUE_PENDING", + "SERVICE_PAUSE_PENDING", + "SERVICE_PAUSED", + "SERVICE_RUNNING", + "SERVICE_START_PENDING", + "SERVICE_STOP_PENDING", + "SERVICE_STOPPED", + ])), + ]) + + +class Process(_Observable): + + _type = 'process' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('is_hidden', BooleanProperty()), + ('pid', IntegerProperty()), + ('name', StringProperty()), + # this is not the created timestamps of the object itself + ('created', TimestampProperty()), + ('cwd', StringProperty()), + ('arguments', ListProperty(StringProperty)), + ('command_line', StringProperty()), + ('environment_variables', DictionaryProperty()), + ('opened_connection_refs', ListProperty(ObjectReferenceProperty(valid_types='network-traffic'))), + ('creator_user_ref', ObjectReferenceProperty(valid_types='user-account')), + ('binary_ref', ObjectReferenceProperty(valid_types='file')), + ('parent_ref', ObjectReferenceProperty(valid_types='process')), + ('child_refs', ListProperty(ObjectReferenceProperty('process'))), + ('extensions', ExtensionsProperty(enclosing_type=_type)), + ]) + + def _check_object_constraints(self): + # no need to check windows-service-ext, since it has a required property + super(Process, self)._check_object_constraints() + try: + self._check_at_least_one_property() + if "windows-process-ext" in self.get('extensions', {}): + self.extensions["windows-process-ext"]._check_at_least_one_property() + except AtLeastOnePropertyError as enclosing_exc: + if 'extensions' not in self: + raise enclosing_exc + else: + if "windows-process-ext" in self.get('extensions', {}): + self.extensions["windows-process-ext"]._check_at_least_one_property() + + +class Software(_Observable): + + _type = 'software' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('name', StringProperty(required=True)), + ('cpe', StringProperty()), + ('languages', ListProperty(StringProperty)), + ('vendor', StringProperty()), + ('version', StringProperty()), + ('extensions', ExtensionsProperty(enclosing_type=_type)), + ]) + + +class URL(_Observable): + + _type = 'url' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('value', StringProperty(required=True)), + ('extensions', ExtensionsProperty(enclosing_type=_type)), + ]) + + +class UNIXAccountExt(_Extension): + + _type = 'unix-account-ext' + _properties = OrderedDict() + _properties.update([ + ('gid', IntegerProperty()), + ('groups', ListProperty(StringProperty)), + ('home_dir', StringProperty()), + ('shell', StringProperty()), + ]) + + +class UserAccount(_Observable): + + _type = 'user-account' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('user_id', StringProperty(required=True)), + ('account_login', StringProperty()), + ('account_type', StringProperty()), # open vocab + ('display_name', StringProperty()), + ('is_service_account', BooleanProperty()), + ('is_privileged', BooleanProperty()), + ('can_escalate_privs', BooleanProperty()), + ('is_disabled', BooleanProperty()), + ('account_created', TimestampProperty()), + ('account_expires', TimestampProperty()), + ('password_last_changed', TimestampProperty()), + ('account_first_login', TimestampProperty()), + ('account_last_login', TimestampProperty()), + ('extensions', ExtensionsProperty(enclosing_type=_type)), + ]) + + +class WindowsRegistryValueType(_STIXBase): + + _type = 'windows-registry-value-type' + _properties = OrderedDict() + _properties.update([ + ('name', StringProperty(required=True)), + ('data', StringProperty()), + ('data_type', EnumProperty(allowed=[ + 'REG_NONE', + 'REG_SZ', + 'REG_EXPAND_SZ', + 'REG_BINARY', + 'REG_DWORD', + 'REG_DWORD_BIG_ENDIAN', + 'REG_LINK', + 'REG_MULTI_SZ', + 'REG_RESOURCE_LIST', + 'REG_FULL_RESOURCE_DESCRIPTION', + 'REG_RESOURCE_REQUIREMENTS_LIST', + 'REG_QWORD', + 'REG_INVALID_TYPE', + ])), + ]) + + +class WindowsRegistryKey(_Observable): + + _type = 'windows-registry-key' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('key', StringProperty(required=True)), + ('values', ListProperty(EmbeddedObjectProperty(type=WindowsRegistryValueType))), + # this is not the modified timestamps of the object itself + ('modified', TimestampProperty()), + ('creator_user_ref', ObjectReferenceProperty(valid_types='user-account')), + ('number_of_subkeys', IntegerProperty()), + ('extensions', ExtensionsProperty(enclosing_type=_type)), + ]) + + @property + def values(self): + # Needed because 'values' is a property on collections.Mapping objects + return self._inner['values'] + + +class X509V3ExtenstionsType(_STIXBase): + + _type = 'x509-v3-extensions-type' + _properties = OrderedDict() + _properties.update([ + ('basic_constraints', StringProperty()), + ('name_constraints', StringProperty()), + ('policy_constraints', StringProperty()), + ('key_usage', StringProperty()), + ('extended_key_usage', StringProperty()), + ('subject_key_identifier', StringProperty()), + ('authority_key_identifier', StringProperty()), + ('subject_alternative_name', StringProperty()), + ('issuer_alternative_name', StringProperty()), + ('subject_directory_attributes', StringProperty()), + ('crl_distribution_points', StringProperty()), + ('inhibit_any_policy', StringProperty()), + ('private_key_usage_period_not_before', TimestampProperty()), + ('private_key_usage_period_not_after', TimestampProperty()), + ('certificate_policies', StringProperty()), + ('policy_mappings', StringProperty()), + ]) + + +class X509Certificate(_Observable): + + _type = 'x509-certificate' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('is_self_signed', BooleanProperty()), + ('hashes', HashesProperty()), + ('version', StringProperty()), + ('serial_number', StringProperty()), + ('signature_algorithm', StringProperty()), + ('issuer', StringProperty()), + ('validity_not_before', TimestampProperty()), + ('validity_not_after', TimestampProperty()), + ('subject', StringProperty()), + ('subject_public_key_algorithm', StringProperty()), + ('subject_public_key_modulus', StringProperty()), + ('subject_public_key_exponent', IntegerProperty()), + ('x509_v3_extensions', EmbeddedObjectProperty(type=X509V3ExtenstionsType)), + ('extensions', ExtensionsProperty(enclosing_type=_type)), + ]) + + +OBJ_MAP_OBSERVABLE = { + 'artifact': Artifact, + 'autonomous-system': AutonomousSystem, + 'directory': Directory, + 'domain-name': DomainName, + 'email-addr': EmailAddress, + 'email-message': EmailMessage, + 'file': File, + 'ipv4-addr': IPv4Address, + 'ipv6-addr': IPv6Address, + 'mac-addr': MACAddress, + 'mutex': Mutex, + 'network-traffic': NetworkTraffic, + 'process': Process, + 'software': Software, + 'url': URL, + 'user-account': UserAccount, + 'windows-registry-key': WindowsRegistryKey, + 'x509-certificate': X509Certificate, +} + + +EXT_MAP = { + 'file': { + 'archive-ext': ArchiveExt, + 'ntfs-ext': NTFSExt, + 'pdf-ext': PDFExt, + 'raster-image-ext': RasterImageExt, + 'windows-pebinary-ext': WindowsPEBinaryExt + }, + 'network-traffic': { + 'http-request-ext': HTTPRequestExt, + 'icmp-ext': ICMPExt, + 'socket-ext': SocketExt, + 'tcp-ext': TCPExt, + }, + 'process': { + 'windows-process-ext': WindowsProcessExt, + 'windows-service-ext': WindowsServiceExt, + }, + 'user-account': { + 'unix-account-ext': UNIXAccountExt, + }, +} + + +def parse_observable(data, _valid_refs=None, allow_custom=False): + """Deserialize a string or file-like object into a STIX Cyber Observable + object. + + Args: + data: The STIX 2 string to be parsed. + _valid_refs: A list of object references valid for the scope of the + object being parsed. Use empty list if no valid refs are present. + allow_custom: Whether to allow custom properties or not. + Default: False. + + Returns: + An instantiated Python STIX Cyber Observable object. + """ + + obj = get_dict(data) + obj['_valid_refs'] = _valid_refs or [] + + if 'type' not in obj: + raise ParseError("Can't parse observable with no 'type' property: %s" % str(obj)) + try: + obj_class = OBJ_MAP_OBSERVABLE[obj['type']] + except KeyError: + raise ParseError("Can't parse unknown observable type '%s'! For custom observables, " + "use the CustomObservable decorator." % obj['type']) + + if 'extensions' in obj and obj['type'] in EXT_MAP: + for name, ext in obj['extensions'].items(): + if name not in EXT_MAP[obj['type']]: + raise ParseError("Can't parse Unknown extension type '%s' for observable type '%s'!" % (name, obj['type'])) + ext_class = EXT_MAP[obj['type']][name] + obj['extensions'][name] = ext_class(allow_custom=allow_custom, **obj['extensions'][name]) + + return obj_class(allow_custom=allow_custom, **obj) + + +def _register_observable(new_observable): + """Register a custom STIX Cyber Observable type. + """ + + OBJ_MAP_OBSERVABLE[new_observable._type] = new_observable + + +def CustomObservable(type='x-custom-observable', properties=None): + """Custom STIX Cyber Observable Object type decorator. + + Example: + >>> @CustomObservable('x-custom-observable', [ + ... ('property1', StringProperty(required=True)), + ... ('property2', IntegerProperty()), + ... ]) + ... class MyNewObservableType(): + ... pass + """ + + def custom_builder(cls): + + class _Custom(cls, _Observable): + + _type = type + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ]) + + if not properties or not isinstance(properties, list): + raise ValueError("Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]") + + # Check properties ending in "_ref/s" are ObjectReferenceProperties + for prop_name, prop in properties: + if prop_name.endswith('_ref') and not isinstance(prop, ObjectReferenceProperty): + raise ValueError("'%s' is named like an object reference property but " + "is not an ObjectReferenceProperty." % prop_name) + elif (prop_name.endswith('_refs') and (not isinstance(prop, ListProperty) + or not isinstance(prop.contained, ObjectReferenceProperty))): + raise ValueError("'%s' is named like an object reference list property but " + "is not a ListProperty containing ObjectReferenceProperty." % prop_name) + + _properties.update(properties) + + def __init__(self, **kwargs): + _Observable.__init__(self, **kwargs) + try: + cls.__init__(self, **kwargs) + except (AttributeError, TypeError) as e: + # Don't accidentally catch errors raised in a custom __init__() + if ("has no attribute '__init__'" in str(e) or + str(e) == "object.__init__() takes no parameters"): + return + raise e + + _register_observable(_Custom) + return _Custom + + return custom_builder + + +def _register_extension(observable, new_extension): + """Register a custom extension to a STIX Cyber Observable type. + """ + + try: + observable_type = observable._type + except AttributeError: + raise ValueError("Unknown observable type. Custom observables must be " + "created with the @CustomObservable decorator.") + + try: + EXT_MAP[observable_type][new_extension._type] = new_extension + except KeyError: + if observable_type not in OBJ_MAP_OBSERVABLE: + raise ValueError("Unknown observable type '%s'. Custom observables " + "must be created with the @CustomObservable decorator." + % observable_type) + else: + EXT_MAP[observable_type] = {new_extension._type: new_extension} + + +def CustomExtension(observable=None, type='x-custom-observable', properties=None): + """Decorator for custom extensions to STIX Cyber Observables. + """ + + if not observable or not issubclass(observable, _Observable): + raise ValueError("'observable' must be a valid Observable class!") + + def custom_builder(cls): + + class _Custom(cls, _Extension): + + _type = type + _properties = { + 'extensions': ExtensionsProperty(enclosing_type=_type), + } + + if not isinstance(properties, dict) or not properties: + raise ValueError("'properties' must be a dict!") + + _properties.update(properties) + + def __init__(self, **kwargs): + _Extension.__init__(self, **kwargs) + try: + cls.__init__(self, **kwargs) + except (AttributeError, TypeError) as e: + # Don't accidentally catch errors raised in a custom __init__() + if ("has no attribute '__init__'" in str(e) or + str(e) == "object.__init__() takes no parameters"): + return + raise e + + _register_extension(observable, _Custom) + return _Custom + + return custom_builder diff --git a/stix2/v20/sdo.py b/stix2/v20/sdo.py new file mode 100644 index 0000000..1af0777 --- /dev/null +++ b/stix2/v20/sdo.py @@ -0,0 +1,364 @@ +"""STIX 2.0 Domain Objects""" + +from collections import OrderedDict + +import stix2 + +from ..base import _STIXBase +from ..markings import _MarkingsMixin +from ..properties import (BooleanProperty, IDProperty, IntegerProperty, + ListProperty, PatternProperty, ReferenceProperty, + StringProperty, TimestampProperty, TypeProperty) +from ..utils import NOW +from .common import ExternalReference, GranularMarking, KillChainPhase +from .observables import ObservableProperty + + +class STIXDomainObject(_STIXBase, _MarkingsMixin): + pass + + +class AttackPattern(STIXDomainObject): + + _type = 'attack-pattern' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('id', IDProperty(_type)), + ('created_by_ref', ReferenceProperty(type="identity")), + ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('name', StringProperty(required=True)), + ('description', StringProperty()), + ('kill_chain_phases', ListProperty(KillChainPhase)), + ('revoked', BooleanProperty()), + ('labels', ListProperty(StringProperty)), + ('external_references', ListProperty(ExternalReference)), + ('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))), + ('granular_markings', ListProperty(GranularMarking)), + ]) + + +class Campaign(STIXDomainObject): + + _type = 'campaign' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('id', IDProperty(_type)), + ('created_by_ref', ReferenceProperty(type="identity")), + ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('name', StringProperty(required=True)), + ('description', StringProperty()), + ('aliases', ListProperty(StringProperty)), + ('first_seen', TimestampProperty()), + ('last_seen', TimestampProperty()), + ('objective', StringProperty()), + ('revoked', BooleanProperty()), + ('labels', ListProperty(StringProperty)), + ('external_references', ListProperty(ExternalReference)), + ('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))), + ('granular_markings', ListProperty(GranularMarking)), + ]) + + +class CourseOfAction(STIXDomainObject): + + _type = 'course-of-action' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('id', IDProperty(_type)), + ('created_by_ref', ReferenceProperty(type="identity")), + ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('name', StringProperty(required=True)), + ('description', StringProperty()), + ('revoked', BooleanProperty()), + ('labels', ListProperty(StringProperty)), + ('external_references', ListProperty(ExternalReference)), + ('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))), + ('granular_markings', ListProperty(GranularMarking)), + ]) + + +class Identity(STIXDomainObject): + + _type = 'identity' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('id', IDProperty(_type)), + ('created_by_ref', ReferenceProperty(type="identity")), + ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('name', StringProperty(required=True)), + ('description', StringProperty()), + ('identity_class', StringProperty(required=True)), + ('sectors', ListProperty(StringProperty)), + ('contact_information', StringProperty()), + ('revoked', BooleanProperty()), + ('labels', ListProperty(StringProperty)), + ('external_references', ListProperty(ExternalReference)), + ('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))), + ('granular_markings', ListProperty(GranularMarking)), + ]) + + +class Indicator(STIXDomainObject): + + _type = 'indicator' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('id', IDProperty(_type)), + ('created_by_ref', ReferenceProperty(type="identity")), + ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('name', StringProperty()), + ('description', StringProperty()), + ('pattern', PatternProperty(required=True)), + ('valid_from', TimestampProperty(default=lambda: NOW)), + ('valid_until', TimestampProperty()), + ('kill_chain_phases', ListProperty(KillChainPhase)), + ('revoked', BooleanProperty()), + ('labels', ListProperty(StringProperty, required=True)), + ('external_references', ListProperty(ExternalReference)), + ('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))), + ('granular_markings', ListProperty(GranularMarking)), + ]) + + +class IntrusionSet(STIXDomainObject): + + _type = 'intrusion-set' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('id', IDProperty(_type)), + ('created_by_ref', ReferenceProperty(type="identity")), + ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('name', StringProperty(required=True)), + ('description', StringProperty()), + ('aliases', ListProperty(StringProperty)), + ('first_seen', TimestampProperty()), + ('last_seen ', TimestampProperty()), + ('goals', ListProperty(StringProperty)), + ('resource_level', StringProperty()), + ('primary_motivation', StringProperty()), + ('secondary_motivations', ListProperty(StringProperty)), + ('revoked', BooleanProperty()), + ('labels', ListProperty(StringProperty)), + ('external_references', ListProperty(ExternalReference)), + ('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))), + ('granular_markings', ListProperty(GranularMarking)), + ]) + + +class Malware(STIXDomainObject): + + _type = 'malware' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('id', IDProperty(_type)), + ('created_by_ref', ReferenceProperty(type="identity")), + ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('name', StringProperty(required=True)), + ('description', StringProperty()), + ('kill_chain_phases', ListProperty(KillChainPhase)), + ('revoked', BooleanProperty()), + ('labels', ListProperty(StringProperty, required=True)), + ('external_references', ListProperty(ExternalReference)), + ('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))), + ('granular_markings', ListProperty(GranularMarking)), + ]) + + +class ObservedData(STIXDomainObject): + + _type = 'observed-data' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('id', IDProperty(_type)), + ('created_by_ref', ReferenceProperty(type="identity")), + ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('first_observed', TimestampProperty(required=True)), + ('last_observed', TimestampProperty(required=True)), + ('number_observed', IntegerProperty(required=True)), + ('objects', ObservableProperty(required=True)), + ('revoked', BooleanProperty()), + ('labels', ListProperty(StringProperty)), + ('external_references', ListProperty(ExternalReference)), + ('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))), + ('granular_markings', ListProperty(GranularMarking)), + ]) + + +class Report(STIXDomainObject): + + _type = 'report' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('id', IDProperty(_type)), + ('created_by_ref', ReferenceProperty(type="identity")), + ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('name', StringProperty(required=True)), + ('description', StringProperty()), + ('published', TimestampProperty(required=True)), + ('object_refs', ListProperty(ReferenceProperty, required=True)), + ('revoked', BooleanProperty()), + ('labels', ListProperty(StringProperty, required=True)), + ('external_references', ListProperty(ExternalReference)), + ('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))), + ('granular_markings', ListProperty(GranularMarking)), + ]) + + +class ThreatActor(STIXDomainObject): + + _type = 'threat-actor' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('id', IDProperty(_type)), + ('created_by_ref', ReferenceProperty(type="identity")), + ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('name', StringProperty(required=True)), + ('description', StringProperty()), + ('aliases', ListProperty(StringProperty)), + ('roles', ListProperty(StringProperty)), + ('goals', ListProperty(StringProperty)), + ('sophistication', StringProperty()), + ('resource_level', StringProperty()), + ('primary_motivation', StringProperty()), + ('secondary_motivations', ListProperty(StringProperty)), + ('personal_motivations', ListProperty(StringProperty)), + ('revoked', BooleanProperty()), + ('labels', ListProperty(StringProperty, required=True)), + ('external_references', ListProperty(ExternalReference)), + ('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))), + ('granular_markings', ListProperty(GranularMarking)), + ]) + + +class Tool(STIXDomainObject): + + _type = 'tool' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('id', IDProperty(_type)), + ('created_by_ref', ReferenceProperty(type="identity")), + ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('name', StringProperty(required=True)), + ('description', StringProperty()), + ('kill_chain_phases', ListProperty(KillChainPhase)), + ('tool_version', StringProperty()), + ('revoked', BooleanProperty()), + ('labels', ListProperty(StringProperty, required=True)), + ('external_references', ListProperty(ExternalReference)), + ('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))), + ('granular_markings', ListProperty(GranularMarking)), + ]) + + +class Vulnerability(STIXDomainObject): + + _type = 'vulnerability' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('id', IDProperty(_type)), + ('created_by_ref', ReferenceProperty(type="identity")), + ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('name', StringProperty(required=True)), + ('description', StringProperty()), + ('revoked', BooleanProperty()), + ('labels', ListProperty(StringProperty)), + ('external_references', ListProperty(ExternalReference)), + ('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))), + ('granular_markings', ListProperty(GranularMarking)), + ]) + + +def CustomObject(type='x-custom-type', properties=None): + """Custom STIX Object type decorator. + + Example: + >>> @CustomObject('x-type-name', [ + ... ('property1', StringProperty(required=True)), + ... ('property2', IntegerProperty()), + ... ]) + ... class MyNewObjectType(): + ... pass + + Supply an ``__init__()`` function to add any special validations to the custom + type. Don't call ``super().__init__()`` though - doing so will cause an error. + + Example: + >>> @CustomObject('x-type-name', [ + ... ('property1', StringProperty(required=True)), + ... ('property2', IntegerProperty()), + ... ]) + ... class MyNewObjectType(): + ... def __init__(self, property2=None, **kwargs): + ... if property2 and property2 < 10: + ... raise ValueError("'property2' is too small.") + """ + + def custom_builder(cls): + + class _Custom(cls, STIXDomainObject): + _type = type + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('id', IDProperty(_type)), + ('created_by_ref', ReferenceProperty(type="identity")), + ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ]) + + if not properties or not isinstance(properties, list): + raise ValueError("Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]") + + _properties.update([x for x in properties if not x[0].startswith("x_")]) + + # This is to follow the general properties structure. + _properties.update([ + ('revoked', BooleanProperty()), + ('labels', ListProperty(StringProperty)), + ('external_references', ListProperty(ExternalReference)), + ('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))), + ('granular_markings', ListProperty(GranularMarking)), + ]) + + # Put all custom properties at the bottom, sorted alphabetically. + _properties.update(sorted([x for x in properties if x[0].startswith("x_")], key=lambda x: x[0])) + + def __init__(self, **kwargs): + _STIXBase.__init__(self, **kwargs) + try: + cls.__init__(self, **kwargs) + except (AttributeError, TypeError) as e: + # Don't accidentally catch errors raised in a custom __init__() + if ("has no attribute '__init__'" in str(e) or + str(e) == "object.__init__() takes no parameters"): + return + raise e + + stix2._register_type(_Custom, version="2.0") + return _Custom + + return custom_builder diff --git a/stix2/v20/sro.py b/stix2/v20/sro.py new file mode 100644 index 0000000..7f05f5e --- /dev/null +++ b/stix2/v20/sro.py @@ -0,0 +1,82 @@ +"""STIX 2.0 Relationship Objects.""" + +from collections import OrderedDict + +from ..base import _STIXBase +from ..markings import _MarkingsMixin +from ..properties import (BooleanProperty, IDProperty, IntegerProperty, + ListProperty, ReferenceProperty, StringProperty, + TimestampProperty, TypeProperty) +from ..utils import NOW +from .common import ExternalReference, GranularMarking + + +class STIXRelationshipObject(_STIXBase, _MarkingsMixin): + pass + + +class Relationship(STIXRelationshipObject): + + _type = 'relationship' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('id', IDProperty(_type)), + ('created_by_ref', ReferenceProperty(type="identity")), + ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('relationship_type', StringProperty(required=True)), + ('description', StringProperty()), + ('source_ref', ReferenceProperty(required=True)), + ('target_ref', ReferenceProperty(required=True)), + ('revoked', BooleanProperty()), + ('labels', ListProperty(StringProperty)), + ('external_references', ListProperty(ExternalReference)), + ('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))), + ('granular_markings', ListProperty(GranularMarking)), + ]) + + # Explicitly define the first three kwargs to make readable Relationship declarations. + def __init__(self, source_ref=None, relationship_type=None, + target_ref=None, **kwargs): + # Allow (source_ref, relationship_type, target_ref) as positional args. + if source_ref and not kwargs.get('source_ref'): + kwargs['source_ref'] = source_ref + if relationship_type and not kwargs.get('relationship_type'): + kwargs['relationship_type'] = relationship_type + if target_ref and not kwargs.get('target_ref'): + kwargs['target_ref'] = target_ref + + super(Relationship, self).__init__(**kwargs) + + +class Sighting(STIXRelationshipObject): + _type = 'sighting' + _properties = OrderedDict() + _properties.update([ + ('type', TypeProperty(_type)), + ('id', IDProperty(_type)), + ('created_by_ref', ReferenceProperty(type="identity")), + ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), + ('first_seen', TimestampProperty()), + ('last_seen', TimestampProperty()), + ('count', IntegerProperty()), + ('sighting_of_ref', ReferenceProperty(required=True)), + ('observed_data_refs', ListProperty(ReferenceProperty(type="observed-data"))), + ('where_sighted_refs', ListProperty(ReferenceProperty(type="identity"))), + ('summary', BooleanProperty()), + ('revoked', BooleanProperty()), + ('labels', ListProperty(StringProperty)), + ('external_references', ListProperty(ExternalReference)), + ('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))), + ('granular_markings', ListProperty(GranularMarking)), + ]) + + # Explicitly define the first kwargs to make readable Sighting declarations. + def __init__(self, sighting_of_ref=None, **kwargs): + # Allow sighting_of_ref as a positional arg. + if sighting_of_ref and not kwargs.get('sighting_of_ref'): + kwargs['sighting_of_ref'] = sighting_of_ref + + super(Sighting, self).__init__(**kwargs) diff --git a/stix2/v21/__init__.py b/stix2/v21/__init__.py index dad0785..f0d51d9 100644 --- a/stix2/v21/__init__.py +++ b/stix2/v21/__init__.py @@ -9,10 +9,10 @@ from .common import (TLP_AMBER, TLP_GREEN, TLP_RED, TLP_WHITE, CustomMarking, from .observables import (URL, AlternateDataStream, ArchiveExt, Artifact, AutonomousSystem, CustomExtension, CustomObservable, Directory, DomainName, EmailAddress, EmailMessage, - EmailMIMEComponent, File, HTTPRequestExt, ICMPExt, - IPv4Address, IPv6Address, MACAddress, Mutex, - NetworkTraffic, NTFSExt, PDFExt, Process, - RasterImageExt, SocketExt, Software, TCPExt, + EmailMIMEComponent, ExtensionsProperty, File, + HTTPRequestExt, ICMPExt, IPv4Address, IPv6Address, + MACAddress, Mutex, NetworkTraffic, NTFSExt, PDFExt, + Process, RasterImageExt, SocketExt, Software, TCPExt, UNIXAccountExt, UserAccount, WindowsPEBinaryExt, WindowsPEOptionalHeaderType, WindowsPESection, WindowsProcessExt, WindowsRegistryKey,