From 8b5b397ebc9d47b6065a014032b2b59f8d62c076 Mon Sep 17 00:00:00 2001 From: Marcus Bakker Date: Fri, 29 Mar 2019 15:26:25 +0100 Subject: [PATCH] initial commit --- LICENSE | 674 +++++++ README.md | 67 + blue_attack.py | 202 ++ data_source_mapping.py | 264 +++ generic.py | 369 ++++ group_mapping.py | 571 ++++++ interactive_menu.py | 382 ++++ requirements.txt | 6 + sample-data/data-sources-endpoints.yaml | 626 +++++++ sample-data/groups.yaml | 17 + .../techniques-administration-endpoints.yaml | 1660 +++++++++++++++++ scoring_table.xlsx | Bin 0 -> 29514 bytes technique_mapping.py | 273 +++ 13 files changed, 5111 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 blue_attack.py create mode 100644 data_source_mapping.py create mode 100644 generic.py create mode 100644 group_mapping.py create mode 100644 interactive_menu.py create mode 100644 requirements.txt create mode 100644 sample-data/data-sources-endpoints.yaml create mode 100644 sample-data/groups.yaml create mode 100644 sample-data/techniques-administration-endpoints.yaml create mode 100644 scoring_table.xlsx create mode 100644 technique_mapping.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9ac6d63 --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +Blue ATT&CK + +# Blue ATT&CK +#### Mapping your blue team to ATT&CK + +To get started with Blue ATT&CK, check out the +[Wiki](https://github.com/rabobank-cdc/Blue-ATTACK/wiki/Getting-started). + +Blue ATT&CK will help blue teams in scoring and comparing data source quality, visibility coverage, detection coverage and threat actor behaviours. The Blue ATT&CK framework consists of a Python tool, YAML administration files and [scoring tables](https://github.com/rabobank-cdc/Blue-ATTACK/raw/master/scoring_table.xlsx) for the different aspects. + +Blue ATT&CK will help you to: + +- Administrate and score the quality of your data sources. +- Get insight on the visibility you have on for example endpoints. +- Map your detection coverage. +- Map threat actor behaviours. +- Compare visibility, detections and threat actor behaviours in order to uncover possible improvements in detection and visibility. This can help you to prioritise your blue teaming efforts. + +## Authors and contribution +This project is developed and maintained by [Marcus Bakker](https://github.com/marcusbakker) (Twitter: [@bakker3m](https://twitter.com/bakk3rm)) and [Ruben Bouman](https://github.com/rubinatorz) (Twitter: [@rubenb_2](https://twitter.com/rubenb_2/)). Feel free to contact, DMs are open. + +We welcome contributions! Contributions can be both in code, as well as in ideas you might have for further development, usability improvements, etc. + +### Work of others +Some functionality within Blue ATT&CK was inspired by work of +others: +- Roberto Rodriguez's work on data quality and scoring of ATT&CK techniques ([How Hot Is Your Hunt Team?](https://cyberwardog.blogspot.com/2017/07/how-hot-is-your-hunt-team.html), [Ready to hunt? First, Show me your data!](https://cyberwardog.blogspot.com/2017/12/ready-to-hunt-first-show-me-your-data.html)). +- The MITRE ATT&CK Mapping project on GitHub: + https://github.com/siriussecurity/mitre-attack-mapping. + +## Example + +YAML files are used for administrating scores and relevant metadata. All +of which can be visualised by loading JSON layer files into the [ATT&CK Navigator](https://github.com/mitre-attack/attack-navigator) (some types of scores and metadata can also be written to Excel). + +See below an example of mapping your data sources to ATT&CK which gives you a rough overview of your visibility coverage: + +Blue ATT&CK
+ + +## Installation and requirements + +See our GitHub Wiki: [Installation and requirements](https://github.com/rabobank-cdc/Blue-ATTACK/wiki/Installation-and-requirements). + +## Future developments + +- Add more graphs: + - [ ] Detections: improvement based on newly added detections and improvements on the level/score of existing detections. Possibly with a changelog with support for comments. + - [ ] Visibility: improvement in the quality of an existing data source. +- Groups: + - [ ] Have groups YAML file type that contains a count on how popular a certain technique is. This can be very useful to map things such as Red Canary's [Threat Detection Report 2019](https://redcanary.com/resources/guides/threat-detection-report/). +- Excel output for: + - [ ] Techniques administration YAML file: visibility coverage + - [ ] Techniques administration YAML file: detection coverage +- Data quality Excel sheet: + - [ ] Add colors to the data quality scores in the Excel sheet. +- YAML files: + - [ ] Create an option within the tool to migrate an old administration YAML file version to a new version (such as adding specific key-value pairs). +- MITRE ATT&CK updates + - [ ] Have a smart way of knowing what to update in your data source and technique administration files once MITRE releases updates. + - [ ] Data sources: check for missing data sources in data sources administration files. +- Minimal visibility + - [ ] Integrate information into the framework on what a minimal set of visibility for a technique should be, before you can say to have useful visibility (e.g. technique X requires at least to have visibility on process monitoring, process command line monitoring and DLL monitoring.) + +## License: GPL-3.0 +[Blue ATT&CK's GNU General Public License v3.0](https://github.com/rabobank-cdc/Blue-ATTACK/blob/master/LICENSE) + diff --git a/blue_attack.py b/blue_attack.py new file mode 100644 index 0000000..a4e406e --- /dev/null +++ b/blue_attack.py @@ -0,0 +1,202 @@ +import argparse +from interactive_menu import * +import os +import signal + + +def init_menu(): + """ + Initialise the command line parameter menu. + :return: + """ + menu_parser = argparse.ArgumentParser(description='Create MITRE ATT&CK layers for visibility, detection and groups.', + epilog='Source: https://github.com/rabobank-cdc/Blue-ATTACK') + menu_parser.add_argument('--version', action='version', version='%(prog)s ' + VERSION) + menu_parser.add_argument('-i', '--interactive', help='launch the interactive menu, which has support for all modes', + action='store_true') + + # add subparsers + subparsers = menu_parser.add_subparsers(title='MODE', + description='Select the mode to use. Every mode has its own arguments and ' + 'help info displayed using: {visibility, detection, group, ' + 'generic} --help', metavar='', dest='subparser') + + # create the data source parser + parser_data_sources = subparsers.add_parser('datasource', help='data source mapping and quality', + aliases=['ds'], + description='Create a heat map based on data sources, output data ' + 'sources to Excel or generate a data source improvement ' + 'graph.') + parser_data_sources.add_argument('-f', '--file', help='path to the data source administration YAML file', + required=True) + parser_data_sources.add_argument('-l', '--layer', help='generate a data source layer for the ATT&CK navigator', + action='store_true') + parser_data_sources.add_argument('-e', '--excel', help='generate an Excel sheet with all data source', + action='store_true') + parser_data_sources.add_argument('-g', '--graph', help='generate a graph with data sources added through time', + action='store_true') + parser_data_sources.add_argument('-y', '--yaml', help='generate a technique administration YAML file with ' + 'visibility scores based on the number of available data ' + 'sources', + action='store_true') + + # create the visibility parser + parser_visibility = subparsers.add_parser('visibility', aliases=['v'], + help='visibility coverage mapping based on techniques and data sources', + description='Create a heat map based on visibility scores or overlay ' + 'visibility with detections.') + parser_visibility.add_argument('-ft', '--file-tech', help='path to the technique administration YAML file (used to ' + 'score the level of visibility)', required=True) + parser_visibility.add_argument('-fd', '--file-ds', help='path to the data source administration YAML file (used to ' + 'add metadata on the involved data sources)', + required=True) + parser_visibility.add_argument('-l', '--layer', help='generate a visibility layer for the ATT&CK navigator', + action='store_true') + parser_visibility.add_argument('-o', '--overlay', help='generate a visibility layer overlayed with detections for ' + 'the ATT&CK navigator.', action='store_true') + + # create the detection parser + parser_detection = subparsers.add_parser('detection', aliases=['d'], + help='detection coverage mapping based on techniques', + description='Create a heat map based on detection scores, overlay ' + 'detections with visibility or generate a detection ' + 'improvement graph.') + parser_detection.add_argument('-ft', '--file-tech', help='path to the technique administration YAML file (used to ' + 'score the level of visibility)', required=True) + parser_detection.add_argument('-fd', '--file-ds', help='path to the data source administration YAML file (used in ' + 'the overlay with visibility to add metadata on the ' + 'involved data sources)') + parser_detection.add_argument('-l', '--layer', help='generate detection layer for the ATT&CK navigator', + action='store_true') + parser_detection.add_argument('-o', '--overlay', help='generate a detection layer overlayed with visibility for ' + 'the ATT&CK navigator.', action='store_true') + parser_detection.add_argument('-g', '--graph', help='generate a graph with detections added through time', + action='store_true') + + # create the group parser + parser_group = subparsers.add_parser('group', aliases=['g'], + description='Create threat actor group heat maps, compare group(s) and ' + 'compare group(s) with visibility and detection coverage.', + help='threat actor group mapping') + parser_group.add_argument('-g', '--groups', help='specify the groups to include separated using commas. ' + 'Group can be their ID, name or alias (default is all groups). ' + 'Other option is to provide a YAML file with a custom group(s) ' + '(default = all)', + default='all') + parser_group.add_argument('-o', '--overlay', help='specify what to overlay on the group(s) (provided using the ' + 'arguments \'-g/--groups\'): group(s), visibility or detection. ' + 'When overlaying a GROUP: the group can be their ID, name or ' + 'alias separated using commas. Or provide a file path of a YAML ' + 'file with a custom group(s). When overlaying DETECTION or ' + 'VISIBILITY provide a YAML with the technique administration.') + parser_group.add_argument('-t', '--overlay-type', help='specify the type of overlay (default = group)', + choices=['group', 'visibility', 'detection'], default='group') + parser_group.add_argument('--software-group', help='add techniques to the heat map by checking which software is ' + 'used by group(s), and hence which techniques the software ' + 'supports (does not influence the scores). If overlay group(s) ' + 'are provided, only software related to those group(s) are ' + 'included', action='store_true', default=False) + parser_group.add_argument('-p', '--platform', help='specify the platform (default = Windows)', + choices=['all', 'Linux', 'macOS', 'Windows'], default='Windows') + parser_group.add_argument('-s', '--stage', help='specify the stage (default = attack)', + choices=['attack', 'pre-attack'], default='attack') + + # create the generic parser + parser_generic = subparsers.add_parser('generic', description='Generic functions which will output to stdout.', + help='includes: statistics on ATT&CK data source and updates on techniques' + ', groups and software', aliases=['ge']) + + parser_generic.add_argument('-s', '--statistics', help='get a sorted count on how much techniques are covered by a ' + 'particular data source', action='store_true') + + parser_generic.add_argument('-u', '--updates', help='get a sorted list for when updates were released for ' + 'techniques, groups or software', + choices=['techniques', 'groups', 'software']) + parser_generic.add_argument('--sort', help='sorting of the output from \'-u/--update\' on modified or creation ' + 'date (default = modified)', choices=['modified', 'created'], + default='modified') + + return menu_parser + + +def menu(menu_parser): + """ + Parser for the command line parameter menu and calls the appropriate functions. + :param menu_parser: the argparse menu as created with 'init_menu()' + :return: + """ + args = menu_parser.parse_args() + + if args.interactive: + interactive_menu() + + elif args.subparser in ['datasource', 'ds']: + if check_file_type(args.file, FILE_TYPE_DATA_SOURCE_ADMINISTRATION): + if args.layer: + generate_data_sources_layer(args.file) + if args.excel: + export_data_source_list_to_excel(args.file) + if args.graph: + plot_data_sources_graph(args.file) + if args.yaml: + generate_technique_administration_file(args.file) + + elif args.subparser in ['visibility', 'v']: + if check_file_type(args.file_tech, FILE_TYPE_TECHNIQUE_ADMINISTRATION) and \ + check_file_type(args.file_ds, FILE_TYPE_DATA_SOURCE_ADMINISTRATION): + if args.layer: + generate_visibility_layer(args.file_tech, args.file_ds, False) + if args.overlay: + generate_visibility_layer(args.file_tech, args.file_ds, True) + + elif args.subparser in ['group', 'g']: + generate_group_heat_map(args.groups, args.overlay, args.overlay_type, args.stage, args.platform, args.software_group) + + elif args.subparser in ['detection', 'd']: + if args.overlay: + if not args.file_ds: + print('[!] Doing an overlay requires adding the data source administration YAML file (\'--file-ds\')') + quit() + if not check_file_type(args.file_ds, FILE_TYPE_DATA_SOURCE_ADMINISTRATION): + quit() + + if check_file_type(args.file_tech, FILE_TYPE_TECHNIQUE_ADMINISTRATION): + if args.layer: + generate_detection_layer(args.file_tech, args.file_ds, False) + if args.overlay and check_file_type(args.file_ds, FILE_TYPE_DATA_SOURCE_ADMINISTRATION): + generate_detection_layer(args.file_tech, args.file_ds, True) + if args.graph: + plot_detection_graph(args.file_tech) + + elif args.subparser in ['generic', 'ge']: + if args.statistics: + get_statistics() + elif args.updates: + get_updates(args.updates, args.sort) + + +def prepare_folders(): + """ + Create the folders 'cache' and 'output' if they do not exist. + :return: + """ + if not os.path.exists('cache'): + os.mkdir('cache') + if not os.path.exists('output'): + os.mkdir('output') + + +def signal_handler(signum, frame): + """ + Function to handles exiting via Ctrl+C. + :param signum: + :param frame: + :return: + """ + sys.exit(0) + + +if __name__ == '__main__': + signal.signal(signal.SIGINT, signal_handler) + prepare_folders() + menu(init_menu()) diff --git a/data_source_mapping.py b/data_source_mapping.py new file mode 100644 index 0000000..17a1ea9 --- /dev/null +++ b/data_source_mapping.py @@ -0,0 +1,264 @@ +import simplejson +from generic import * +import xlsxwriter +import copy +# Imports for pandas and plotly are because of performance reasons in the function that uses these libraries. + + +def generate_data_sources_layer(filename): + """ + Generates a generic layer for data sources. + :param filename: the filename of the yaml file containing the data sources administration + :return: + """ + my_data_sources, name, platform, exceptions = _load_data_sources(filename) + + # Do the mapping between my data sources and MITRE data sources: + my_techniques = _map_and_colorize_techniques(my_data_sources, exceptions) + + layer = get_layer_template_data_sources("Data sources " + name, 'description', 'attack', platform) + layer['techniques'] = my_techniques + + json_string = simplejson.dumps(layer).replace('}, ', '},\n') + output_filename = 'output/data_sources_' + normalize_name_to_filename(name) + '.json' + with open(output_filename, 'w') as f: + f.write(json_string) + print("File written: " + output_filename) + + +def plot_data_sources_graph(filename): + """ + Generates a line graph which shows the improvements on numbers of data sources through time. + :param filename: the filename of the yaml file containing the data sources administration + :return: + """ + my_data_sources, name, platform, exceptions = _load_data_sources(filename) + + graph_values = [] + for t in my_data_sources.values(): + if t['date_connected']: + yyyymm = t['date_connected'].strftime('%Y-%m') + graph_values.append({'date': yyyymm, 'count': 1}) + + import pandas as pd + df = pd.DataFrame(graph_values).groupby('date', as_index=False)[['count']].sum() + df['cumcount'] = df.ix[::1, 'count'].cumsum()[::1] + + output_filename = 'output/graph_data_sources.html' + + import plotly + import plotly.graph_objs as go + plotly.offline.plot( + {'data': [go.Scatter(x=df['date'], y=df['cumcount'])], + 'layout': go.Layout(title="# of data sources for " + name)}, + filename=output_filename, auto_open=False + ) + print("File written: " + output_filename) + + +def export_data_source_list_to_excel(filename): + """ + Makes an overview of all MITRE ATT&CK data sources (via techniques) and lists which data sources are present + in the yaml administration including all properties and data quality score. + :param filename: the filename of the yaml file containing the data sources administration + :return: + """ + my_data_sources, name, platform, exceptions = _load_data_sources(filename, filter_empty_scores=False) + + excel_filename = 'output/data_sources.xlsx' + workbook = xlsxwriter.Workbook(excel_filename) + worksheet = workbook.add_worksheet('Data sources') + + # Formatting: + format_bold_left = workbook.add_format({'align': 'left', 'bold': True}) + format_title = workbook.add_format({'align': 'left', 'bold': True, 'font_size': '14'}) + format_center = workbook.add_format({'align': 'center'}) + + # Title + worksheet.write(0, 0, 'Data sources for ' + name, format_title) + + # Header columns + worksheet.write(2, 0, 'Data source name', format_bold_left) + worksheet.write(2, 1, 'Date registered', format_bold_left) + worksheet.write(2, 2, 'Date connected', format_bold_left) + worksheet.write(2, 3, 'Products', format_bold_left) + worksheet.write(2, 4, 'Comment', format_bold_left) + worksheet.write(2, 5, 'Available for data analytics', format_bold_left) + worksheet.write(2, 6, 'DQ: device completeness', format_bold_left) + worksheet.write(2, 7, 'DQ: data field completeness', format_bold_left) + worksheet.write(2, 8, 'DQ: timeliness', format_bold_left) + worksheet.write(2, 9, 'DQ: consistency', format_bold_left) + worksheet.write(2, 10, 'DQ: retention', format_bold_left) + worksheet.write(2, 11, 'DQ: score', format_bold_left) + + worksheet.set_column(0, 0, 35) + worksheet.set_column(1, 2, 15) + worksheet.set_column(3, 4, 35) + worksheet.set_column(5, 5, 24) + worksheet.set_column(6, 7, 25) + worksheet.set_column(8, 10, 15) + worksheet.set_column(11, 11, 10) + + # Putting the data sources data: + y = 3 + for d in get_all_mitre_data_sources(): + worksheet.write(y, 0, d) + if d in my_data_sources.keys(): + ds = my_data_sources[d] + worksheet.write(y, 1, str(ds['date_registered']).replace('None', '')) + worksheet.write(y, 2, str(ds['date_connected']).replace('None', '')) + worksheet.write(y, 3, ', '.join(ds['products']).replace('None', '')) + worksheet.write(y, 4, str(ds['comment']) if ds['comment'] else '') + worksheet.write(y, 5, str(ds['available_for_data_analytics'])) + worksheet.write(y, 6, ds['data_quality']['device_completeness'], format_center) + worksheet.write(y, 7, ds['data_quality']['data_field_completeness'], format_center) + worksheet.write(y, 8, ds['data_quality']['timeliness'], format_center) + worksheet.write(y, 9, ds['data_quality']['consistency'], format_center) + worksheet.write(y, 10, ds['data_quality']['retention'], format_center) + + score = 0 + score_count = 0 + for s in ds['data_quality'].values(): + if s != 0: + score_count += 1 + score += s + if score > 0: + score = score/score_count + + worksheet.write(y, 11, score, format_center) + y += 1 + + worksheet.autofilter(2, 0, 2, 11) + worksheet.freeze_panes(3, 0) + try: + workbook.close() + print("File written: " + excel_filename) + except Exception as e: + print('[!] Error while writing Excel file: %s' % str(e)) + + +def _load_data_sources(filename, filter_empty_scores=True): + """ + Loads the data sources (including all properties) from the given yaml file. + :param filename: the filename of the yaml file containing the data sources administration + :return: dictionaty with data sources, name, platform and exceptions list. + """ + my_data_sources = {} + with open(filename, 'r') as yaml_file: + yaml_content = yaml.load(yaml_file, Loader=yaml.FullLoader) + for d in yaml_content['data_sources']: + dq = d['data_quality'] + if not filter_empty_scores: + my_data_sources[d['data_source_name']] = d + elif dq['device_completeness'] > 0 and dq['data_field_completeness'] > 0 and dq['timeliness'] > 0 and dq['consistency'] > 0: + my_data_sources[d['data_source_name']] = d + name = yaml_content['name'] + platform = yaml_content['platform'] + exceptions = [t['technique_id'] for t in yaml_content['exceptions']] + return my_data_sources, name, platform, exceptions + + +def _map_and_colorize_techniques(my_ds, exceptions): + """ + Determine the color of the techniques based on how many data sources are available per technique. + :param my_ds: the configured data sources + :return: a dictionary with techniques that can be used in the layer's output file + """ + techniques = load_attack_data(DATATYPE_ALL_TECH) + technique_colors = {} + + # Color the techniques based on how many data sources are available. + for t in techniques: + if t['data_sources']: + total_ds_count = len(t['data_sources']) + ds_count = 0 + for ds in t['data_sources']: + if ds in my_ds.keys(): + ds_count += 1 + if total_ds_count > 0: + result = (float(ds_count) / float(total_ds_count)) * 100 + color = COLOR_DS_25p if result <= 25 else COLOR_DS_50p if result <= 50 else COLOR_DS_75p \ + if result <= 75 else COLOR_DS_99p if result <= 99 else COLOR_DS_100p + technique_colors[t['technique_id']] = color + + my_techniques = map_techniques_to_data_sources(techniques, my_ds) + + output_techniques = [] + for t, v in my_techniques.items(): + if t not in exceptions: + for tactic in v['tactics']: + d = {} + d['techniqueID'] = t + # d['score'] = 50 + d['color'] = technique_colors[t] + d['comment'] = '' + d['enabled'] = True + d['tactic'] = tactic.lower().replace(' ', '-') + d['metadata'] = [{'name': '-Available data sources', 'value': ', '.join(v['my_data_sources'])}, + {'name': '-ATT&CK data sources', 'value': ', '.join(v['data_sources'])}, + {'name': '-Products', 'value': ', '.join(v['products'])}] + + output_techniques.append(d) + + return output_techniques + + +def generate_technique_administration_file(filename): + """ + Generate a technique administration file based on the data source administration yaml file + :param filename: the filename of the yaml file containing the data sources administration + :return: + """ + my_data_sources, name, platform, exceptions = _load_data_sources(filename) + + techniques = load_attack_data(DATATYPE_ALL_TECH) + + # This is part of the techniques administration YAML file and is used as a template + dict_tech = {'technique_id': '', 'detection': {'date_registered': None, 'date_implemented': None, 'score': -1, + 'location': [''], 'comment': ''}, + 'visibility': {'score': 0, 'comment': ''}} + + yaml_file = {} + yaml_file['version'] = 1.0 + yaml_file['file_type'] = FILE_TYPE_TECHNIQUE_ADMINISTRATION + yaml_file['name'] = name + yaml_file['platform'] = platform + yaml_file['techniques'] = [] + + # Score visibility based on the number of available data sources and the exceptions + for t in techniques: + if t['matrix'] == 'mitre-attack': + platforms_lower = list(map(lambda x: x.lower(), t['platform'])) + if platform in platforms_lower: + if t['data_sources']: + total_ds_count = len(t['data_sources']) + ds_count = 0 + for ds in t['data_sources']: + if ds in my_data_sources.keys(): + ds_count += 1 + if total_ds_count > 0: + result = (float(ds_count) / float(total_ds_count)) * 100 + + score = 0 if result == 0 else 1 if result <= 49 else 2 if result <= 74 else 3 if result <= 99 else 4 + else: + score = 0 + + # Do not add technique if score == 0 or part of the exception list\ + techniques_upper = list(map(lambda x: x.upper(), exceptions)) + if score > 0 and t['technique_id'] not in techniques_upper: + tech = copy.deepcopy(dict_tech) + tech['technique_id'] = t['technique_id'] + tech['visibility']['score'] = score + yaml_file['techniques'].append(tech) + + yaml_string = '%YAML 1.2\n---\n' + yaml.dump(yaml_file, sort_keys=False).replace('null', '') + output_filename = 'output/techniques-administration-' + normalize_name_to_filename(name+'-'+platform) + '.yaml' + suffix = 1 + while os.path.exists(output_filename): + output_filename = 'output/techniques-administration-' + normalize_name_to_filename(name + '-' + platform) + \ + '_' + str(suffix) + '.yaml' + suffix += 1 + + with open(output_filename, 'w') as f: + f.write(yaml_string) + print("File written: " + output_filename) diff --git a/generic.py b/generic.py new file mode 100644 index 0000000..4ccc52d --- /dev/null +++ b/generic.py @@ -0,0 +1,369 @@ +import os +import pickle +from datetime import datetime as dt +import yaml +# Import for attackcti is because of performance reasons in the function that uses this library. + +APP_NAME = 'Blue ATT&CK' +APP_DESC = 'Mapping your blue team to ATT&CK' +VERSION = '1.0' + +EXPIRE_TIME = 60*60*24 + +DATATYPE_TECH_BY_GROUP = 'mitre_techniques_used_by_group' +DATATYPE_ALL_TECH = 'mitre_all_techniques' +DATATYPE_ALL_GROUPS = 'mitre_all_groups' +DATATYPE_ALL_SOFTWARE = 'mitre_all_software' +DATATYPE_TECH_BY_SOFTWARE = 'mitre_techniques_used_by_software' +DATATYPE_SOFTWARE_BY_GROUP = 'mitre_software_used_by_group' + +# Group colors +COLOR_GROUP_OVERLAY_MATCH = '#f9a825' # orange +COLOR_GROUP_OVERLAY_NO_MATCH = '#ffee58' # yellow +COLOR_SOFTWARE = '#0d47a1 ' # dark blue +COLOR_GROUP_AND_SOFTWARE = '#64b5f6 ' # light blue +COLOR_GRADIENT_MIN = '#ffcece' # light red +COLOR_GRADIENT_MAX = '#ff0000' # red +COLOR_TACTIC_ROW_BACKGRND = '#dddddd' # light grey +COLOR_GROUP_OVERLAY_ONLY_DETECTION = '#8BC34A' # green +COLOR_GROUP_OVERLAY_ONLY_VISIBILITY = '#1976D2' # blue + +# data source colors (purple range) +COLOR_DS_25p = '#E1BEE7' +COLOR_DS_50p = '#CE93D8' +COLOR_DS_75p = '#AB47BC' +COLOR_DS_99p = '#7B1FA2' +COLOR_DS_100p = '#4A148C' + +# data source colors HAPPY (green range) +COLOR_DS_25p_HAPPY = '#DCEDC8' +COLOR_DS_50p_HAPPY = '#AED581' +COLOR_DS_75p_HAPPY = '#8BC34A' +COLOR_DS_99p_HAPPY = '#689F38' +COLOR_DS_100p_HAPPY = '#33691E' + +# Detection colors (green range) +COLOR_D_0 = '#64B5F6' # Blue: Forensics/Context +COLOR_D_1 = '#DCEDC8' +COLOR_D_2 = '#AED581' +COLOR_D_3 = '#8BC34A' +COLOR_D_4 = '#689F38' +COLOR_D_5 = '#33691E' + +# Visibility colors (blue range) +COLOR_V_1 = '#BBDEFB' +COLOR_V_2 = '#64B5F6' +COLOR_V_3 = '#1976D2' +COLOR_V_4 = '#0D47A1' + +# Detection and visibility overlay color: +COLOR_OVERLAY_VISIBILITY = COLOR_V_3 +COLOR_OVERLAY_DETECTION = COLOR_D_3 +COLOR_OVERLAY_BOTH = COLOR_GROUP_OVERLAY_MATCH + +FILE_TYPE_DATA_SOURCE_ADMINISTRATION = 'data-source-administration' +FILE_TYPE_TECHNIQUE_ADMINISTRATION = 'technique-administration' +FILE_TYPE_GROUP_ADMINISTRATION = 'group-administration' + + +def save_attack_data(data, path): + """ + Save ATT&CK data to disk for the purpose of caching. + :param data: the MITRE ATT&CK data to save + :param path: file path to write to, including filename + :return: + """ + + if not os.path.exists('cache/'): + os.mkdir('cache/') + with open(path, 'wb') as f: + pickle.dump([data, dt.now()], f) + + +def load_attack_data(data_type): + """ + Load the cached ATT&CK data from disk, if not expired (data file on disk is older then EXPIRE_TIME seconds). + :param data_type: the desired data type, see DATATYPE_XX constants. + :return: MITRE ATT&CK data object + """ + if os.path.exists("cache/" + data_type): + with open("cache/" + data_type, 'rb') as f: + cached = pickle.load(f) + write_time = cached[1] + if not (dt.now() - write_time).total_seconds() >= EXPIRE_TIME: + return cached[0] + + from attackcti import attack_client + mitre = attack_client() + + json_data = None + if data_type == DATATYPE_TECH_BY_GROUP: + json_data = mitre.get_techniques_used_by_group() + elif data_type == DATATYPE_ALL_TECH: + json_data = mitre.get_all_techniques() + elif data_type == DATATYPE_ALL_GROUPS: + json_data = mitre.get_all_groups() + elif data_type == DATATYPE_ALL_SOFTWARE: + json_data = mitre.get_all_software() + elif data_type == DATATYPE_TECH_BY_SOFTWARE: + json_data = mitre.get_techniques_used_by_software() + elif data_type == DATATYPE_SOFTWARE_BY_GROUP: + json_data = mitre.get_software_used_by_group() + + save_attack_data(json_data, "cache/" + data_type) + + return json_data + + +def _get_base_template(name, description, stage, platform, sorting): + """ + Prepares a base template for the json layer file that can be loaded into the MITRE ATT&CK Navigator. + More information on the version 2.1 layer format: + https://github.com/mitre/attack-navigator/blob/master/layers/LAYERFORMATv2_1.md + :param name: name + :param description: description + :param stage: stage (act | prepare) + :param platform: platform + :param sorting: sorting + :return: layer template dictionary + """ + layer = {} + layer['name'] = name + layer['version'] = '2.1' + layer['domain'] = 'mitre-enterprise' + layer['description'] = description + + if platform == 'all': + platform = ['windows', 'linux', 'mac'] + else: + platform = [platform.lower()] + + if stage == 'attack': + layer['filters'] = {'stages': ['act'], 'platforms': platform} + else: + layer['filters'] = {'stages': ['prepare'], 'platforms': platform} + + layer['sorting'] = sorting + layer['viewMode'] = 0 + layer['hideDisable'] = False + layer['techniques'] = [] + + layer['showTacticRowBackground'] = False + layer['tacticRowBackground'] = COLOR_TACTIC_ROW_BACKGRND + layer['selectTechniquesAcrossTactics'] = True + return layer + + +def get_layer_template_groups(name, max_score, description, stage, platform): + """ + Prepares a base template for the json layer file that can be loaded into the MITRE ATT&CK Navigator. + More information on the version 2.1 layer format: + https://github.com/mitre/attack-navigator/blob/master/layers/LAYERFORMATv2_1.md + :param name: name + :param max_score: max_score + :param description: description + :param stage: stage (act | prepare) + :param platform: platform + :return: layer template dictionary + """ + layer = _get_base_template(name, description, stage, platform, 3) + layer['gradient'] = {'colors': [COLOR_GRADIENT_MIN, COLOR_GRADIENT_MAX], 'minValue': 0, 'maxValue': max_score} + layer['legendItems'] = \ + [ + {'label': 'Tech. ref. for ' + str(1) + ' group', 'color': COLOR_GRADIENT_MIN}, + {'label': 'Tech. ref. for ' + str(max_score) + ' groups', 'color': COLOR_GRADIENT_MAX}, + {'label': 'Groups overlay: tech. in group + overlay', 'color': COLOR_GROUP_OVERLAY_MATCH}, + {'label': 'Groups overlay: tech. in overlay', 'color': COLOR_GROUP_OVERLAY_NO_MATCH}, + {'label': 'Src. of tech. is only software', 'color': COLOR_SOFTWARE}, + {'label': 'Src. of tech. is group(s)/overlay + software', 'color': COLOR_GROUP_AND_SOFTWARE} + ] + return layer + + +def get_layer_template_detections(name, description, stage, platform): + """ + Prepares a base template for the json layer file that can be loaded into the MITRE ATT&CK Navigator. + More information on the version 2.1 layer format: + https://github.com/mitre/attack-navigator/blob/master/layers/LAYERFORMATv2_1.md + :param name: name + :param description: description + :param stage: stage (act | prepare) + :param platform: platform + :return: layer template dictionary + """ + layer = _get_base_template(name, description, stage, platform, 0) + layer['gradient'] = {'colors': ['#ff6666', '#ffe766', '#8ec843'], 'minValue': 0, 'maxValue': 100} + layer['legendItems'] = \ + [ + {'label': 'Detection score 0: Forensics/Context', 'color': COLOR_D_0}, + {'label': 'Detection score 1: Basic', 'color': COLOR_D_1}, + {'label': 'Detection score 2: Fair', 'color': COLOR_D_2}, + {'label': 'Detection score 3: Good', 'color': COLOR_D_3}, + {'label': 'Detection score 4: Very good', 'color': COLOR_D_4}, + {'label': 'Detection score 5: Excellent', 'color': COLOR_D_5} + ] + return layer + + +def get_layer_template_data_sources(name, description, stage, platform): + """ + Prepares a base template for the json layer file that can be loaded into the MITRE ATT&CK Navigator. + More information on the version 2.1 layer format: + https://github.com/mitre/attack-navigator/blob/master/layers/LAYERFORMATv2_1.md + :param name: name + :param description: description + :param stage: stage (act | prepare) + :param platform: platform + :return: layer template dictionary + """ + layer = _get_base_template(name, description, stage, platform, 0) + layer['gradient'] = {'colors': ['#ff6666', '#ffe766', '#8ec843'], 'minValue': 0, 'maxValue': 100} + layer['legendItems'] = \ + [ + {'label': '1-25% of data sources available', 'color': COLOR_DS_25p}, + {'label': '26-50% of data sources available', 'color': COLOR_DS_50p}, + {'label': '51-75% of data sources available', 'color': COLOR_DS_75p}, + {'label': '76-99% of data sources available', 'color': COLOR_DS_99p}, + {'label': '100% of data sources available', 'color': COLOR_DS_100p} + ] + return layer + + +def get_layer_template_visibility(name, description, stage, platform): + """ + Prepares a base template for the json layer file that can be loaded into the MITRE ATT&CK Navigator. + More information on the version 2.1 layer format: + https://github.com/mitre/attack-navigator/blob/master/layers/LAYERFORMATv2_1.md + :param name: name + :param description: description + :param stage: stage (act | prepare) + :param platform: platform + :return: layer template dictionary + """ + layer = _get_base_template(name, description, stage, platform, 0) + layer['gradient'] = {'colors': ['#ff6666', '#ffe766', '#8ec843'], 'minValue': 0, 'maxValue': 100} + layer['legendItems'] = \ + [ + {'label': 'Visibility score 1: Minimal', 'color': COLOR_V_1}, + {'label': 'Visibility score 2: Medium', 'color': COLOR_V_2}, + {'label': 'Visibility score 3: Good', 'color': COLOR_V_3}, + {'label': 'Visibility score 4: Excellent', 'color': COLOR_V_4} + ] + return layer + + +def get_layer_template_layered(name, description, stage, platform): + """ + Prepares a base template for the json layer file that can be loaded into the MITRE ATT&CK Navigator. + More information on the version 2.1 layer format: + https://github.com/mitre/attack-navigator/blob/master/layers/LAYERFORMATv2_1.md + :param name: name + :param description: description + :param stage: stage (act | prepare) + :param platform: platform + :return: layer template dictionary + """ + layer = _get_base_template(name, description, stage, platform, 0) + layer['gradient'] = {'colors': ['#ff6666', '#ffe766', '#8ec843'], 'minValue': 0, 'maxValue': 100} + layer['legendItems'] = \ + [ + {'label': 'Visibility', 'color': COLOR_OVERLAY_VISIBILITY}, + {'label': 'Detection', 'color': COLOR_OVERLAY_DETECTION}, + {'label': 'Visibility and detection', 'color': COLOR_OVERLAY_BOTH} + ] + return layer + + +def get_technique(techniques, technique_id): + """ + Generic function to lookup a specific technique_id in a list of dictionaries with techniques. + :param techniques: list with all techniques + :param technique_id: technique_id to look for + :return: the technique you're searching for. None if not found. + """ + for t in techniques: + if technique_id == t['technique_id']: + return t + return None + + +def normalize_name_to_filename(name): + """ + Normalize the input filename to a lowercase filename and replace spaces with dashes. + :param name: input filename + :return: normalized filename + """ + return name.lower().replace(' ', '-') + + +def map_techniques_to_data_sources(techniques, my_data_sources): + """ + This function maps the MITRE ATT&CK techniques to your data sources. + :param techniques: list with all MITRE ATT&CK techniques + :param my_data_sources: your configured data sources + :return: a dictionary containing techniques that can be used in the layer output file. + """ + my_techniques = {} + for i_ds in my_data_sources.keys(): + # Loop through all techniques, to find techniques using that data source: + for t in techniques: + # If your data source is in the list of data sources for this technique AND if the + # technique isn't added yet (by an other data source): + if t['data_sources'] and i_ds in t['data_sources'] and t['technique_id'] not in my_techniques.keys(): + my_techniques[t['technique_id']] = {} + my_techniques[t['technique_id']]['my_data_sources'] = [i_ds, ] + my_techniques[t['technique_id']]['data_sources'] = t['data_sources'] + my_techniques[t['technique_id']]['tactics'] = t['tactic'] + my_techniques[t['technique_id']]['products'] = my_data_sources[i_ds]['products'] + elif t['data_sources'] and i_ds in t['data_sources'] and t['technique_id'] in my_techniques.keys(): + my_techniques[t['technique_id']]['my_data_sources'].append(i_ds) + return my_techniques + + +def get_all_mitre_data_sources(): + """ + Gets all the data sources from the techniques and make a unique sorted list of it. + :return: a sorted list with all data sources + """ + techniques = load_attack_data(DATATYPE_ALL_TECH) + data_sources = set() + for t in techniques: + if t['data_sources']: + for ds in t['data_sources']: + if ds not in data_sources: + data_sources.add(ds) + return sorted(data_sources) + + +def check_file_type(filename, file_type=None): + """ + Check if the provided YAML file has the key 'file_type' and possible if that key matches a specific value. + :param filename: path to a YAML file + :param file_type: value to check against the 'file_type' key in the YAML file + :return: the file_type if present, else None is returned. + """ + if not os.path.exists(filename): + print('[!] File: \'' + filename + '\' does not exist') + return None + with open(filename, 'r') as yaml_file: + try: + yaml_content = yaml.load(yaml_file, Loader=yaml.FullLoader) + except: + print('[!] File: \'' + filename + '\' is not a valid YAML file.') + return None + + if not hasattr(yaml_content, 'keys'): + print('[!] File: \'' + filename + '\' is not a valid YAML file.') + return None + + if 'file_type' not in yaml_content.keys(): + print('[!] File: \'' + filename + '\' does not contain a file_type key.') + return None + elif file_type: + if file_type != yaml_content['file_type']: + print('[!] File: \'' + filename + '\' is not a file type of: \'' + file_type + '\'') + return None + else: + return yaml_content['file_type'] + else: + return yaml_content['file_type'] diff --git a/group_mapping.py b/group_mapping.py new file mode 100644 index 0000000..edca72d --- /dev/null +++ b/group_mapping.py @@ -0,0 +1,571 @@ +import simplejson +from generic import * +from technique_mapping import _load_detections + +CG_GROUPS = {} + + +def is_in_group(json_groups, argument_groups): + """ + Check if the two dicts (json_groups and argument_groups) have any groups in common based on their names/aliases. + :param json_groups: group aliases from ATT&CK + :param argument_groups: group names provided via the command line by the user + :return: true or false + """ + json_groups = list(map(lambda x: x.lower(), json_groups)) + + for group in argument_groups: + if group in json_groups: + return True + + return False + + +def is_group_found(groups_found, argument_groups): + """ + Check if a group that has been provided using '-g/--groups'/'-o/--overlay' is present within MITRE ATT&CK. + :param groups_found: groups that are found in the ATT&CK data + :param argument_groups: groups provided via the command line by the user + :return: returns boolean that incidates if the group is found + """ + groups_json = load_attack_data(DATATYPE_ALL_GROUPS) + + for group_arg in argument_groups: + if group_arg == 'all': # this one will be ignored as it does not make any sense for this function + return True + + group_id = None + + for group in groups_json: # is the group provided via the command line known in ATT&CK? + if group['group_aliases']: + group_aliases_lower = list(map(lambda x: x.lower(), group['group_aliases'])) + if group_arg in group_aliases_lower or group_arg == group['group_id'].lower(): + group_id = group['group_id'] + + if not group_id: # the group that has been provided through the command line cannot be found in ATT&CK + print('[!] Unknown group: ' + group_arg) + return False + elif group_id not in groups_found: # group not present in filtered data sate (i.e. platform and stage) + print('[!] Group not part of the data set: ' + group_arg) + return False + else: + return True + + +def get_software_techniques(groups, stage, platform): + """ + Get all techniques (in a dict) from the provided list of groups in relation to the software these groups use, + and hence techniques they support. + :param groups: ATT&CK groups + :param stage: attack or pre-attack + :param platform: the applicable platform + :return: dictionary with info on groups + """ + # { group_id: {group_name: NAME, techniques: set{id, ...} } } + groups_dict = {} + + tech_by_software_json = load_attack_data(DATATYPE_TECH_BY_SOFTWARE) + + # { software_id: [technique, ...] } + software_dict = {} + for tech in tech_by_software_json: + if tech['software_id'] not in software_dict: + software_dict[tech['software_id']] = set([tech['technique_id']]) + else: + software_dict[tech['software_id']].add(tech['technique_id']) + + # groups is a YAML file + if os.path.isfile(str(groups)): + with open(groups, 'r') as yaml_file: + config = yaml.load(yaml_file, Loader=yaml.FullLoader) + + for group in config['groups']: + if group['enabled']: + group_id = get_group_id(group['group_name'], group['campaign']) + groups_dict[group_id] = dict() + + groups_dict[group_id]['group_name'] = group['group_name'] + groups_dict[group_id]['techniques'] = set() + groups_dict[group_id]['campaign'] = group['campaign'] + groups_dict[group_id]['software'] = group['software_id'] + + if group['software_id']: + for soft_id in group['software_id']: + try: + groups_dict[group_id]['techniques'].update(software_dict[soft_id]) + except KeyError: + print('[!] unknown ATT&CK software ID: ' + soft_id) + + # groups are provided as arguments via the command line + else: + software_by_group_json = load_attack_data(DATATYPE_SOFTWARE_BY_GROUP) + + for s in software_by_group_json: + # group matches the: matrix/stage, platform and the group(s) we are interested in + if s['software_platform']: # their is some software that do not have a platform, skip those + if s['matrix'] == 'mitre-'+stage and (platform in s['software_platform'] or platform == 'all') and \ + (groups[0] == 'all' or s['group_id'].lower() in groups or is_in_group(s['group_aliases'], groups)): + if s['group_id'] not in groups_dict: + groups_dict[s['group_id']] = {'group_name': s['group']} + groups_dict[s['group_id']]['techniques'] = set() + groups_dict[s['group_id']]['techniques'].update(software_dict[s['software_id']]) + + return groups_dict + + +def get_group_id(group_name, campaign): + # CG_GROUPS = { group_name+campaign: id } } + """ + Generate a custom group id. + :param group_name: group name as used within the YAML file + :param campaign: campaign as used within the YAML file + :return: custom group identifier string (e.g. CG0001) + """ + global CG_GROUPS + + if not CG_GROUPS: + new_id = 1 + elif group_name + campaign not in CG_GROUPS: + new_id = len(CG_GROUPS) + 1 + + if group_name + campaign not in CG_GROUPS: + length = len(str(new_id)) + if length > 9: + cg_id = 'CG00' + str(new_id) + elif length > 99: + cg_id = 'CG0' + str(new_id) + elif length > 999: + cg_id = 'CG' + str(new_id) + else: + cg_id = 'CG000' + str(new_id) + + CG_GROUPS[group_name + campaign] = cg_id + + return CG_GROUPS[group_name + campaign] + + +def get_group_techniques(groups, stage, platform, file_type): + """ + Get all techniques (in a dict) from the provided list of groups + :param groups: group ID, group name/alias or a YAML file with group(s) data + :param stage: attack or pre-attack + :param platform: all, Linux, macOS, Windows + :param file_type: the file type of the YAML file as present in the key 'file_type' + :return: returns dictionary with all techniques from the provided list of groups or -1 when group is not found + """ + # { group_id: {group_name: NAME, techniques: set{id, ...} } } + groups_dict = {} + groups_found = set() + + # groups is a YAML file + if file_type == FILE_TYPE_GROUP_ADMINISTRATION: + with open(groups, 'r') as yaml_file: + config = yaml.load(yaml_file, Loader=yaml.FullLoader) + + for group in config['groups']: + if group['enabled']: + group_id = get_group_id(group['group_name'], group['campaign']) + groups_dict[group_id] = dict() + + groups_dict[group_id]['group_name'] = group['group_name'] + groups_dict[group_id]['techniques'] = set(group['technique_id']) + groups_dict[group_id]['campaign'] = group['campaign'] + groups_dict[group_id]['software'] = group['software_id'] + else: + # groups are provided as arguments via the command line + groups_json = load_attack_data(DATATYPE_TECH_BY_GROUP) + + for e in groups_json: + json_platform = e['platform'] + if not json_platform: + # we just set this to an random legit value, because for pre-attack 'platform' is not used + json_platform = 'Windows' + + # group matches the: matrix/stage, platform and the group(s) we are interested in + if e['matrix'] == 'mitre-'+stage and (platform in json_platform or platform == 'all') and \ + (groups[0] == 'all' or e['group_id'].lower() in groups or is_in_group(e['group_aliases'], groups)): + if e['group_id'] not in groups_dict: + groups_found.add(e['group_id']) + groups_dict[e['group_id']] = {'group_name': e['group']} + groups_dict[e['group_id']]['techniques'] = set() + + groups_dict[e['group_id']]['techniques'].add(e['technique_id']) + + # do not call 'is_group_found' when groups is a YAML file + # (this could contain groups that do not exists within ATT&CK) + if not os.path.isfile(str(groups)): + found = is_group_found(groups_found, groups) + if not found: + return -1 + + return groups_dict + + +def get_detection_techniques(filename): + """ + Get all techniques (in a dict) from the detection administration + :param filename: path to the YAML technique administration file + :return: dictionary + """ + # { group_id: {group_name: NAME, techniques: set{id, ...} } } + groups_dict = {} + + detection_techniques, name, platform = _load_detections(filename) + + group_id = 'DETECTION' + groups_dict[group_id] = {} + groups_dict[group_id]['group_name'] = 'Detection' + groups_dict[group_id]['techniques'] = set() + for t, v in detection_techniques.items(): + if 'detection' in v.keys() and v['detection']['score'] > 0: + groups_dict[group_id]['techniques'].add(t) + + return groups_dict + + +def get_visibility_techniques(filename): + """ + Get all techniques (in a dict) from the detections administration + :param filename: path to the YAML technique administration file + :return: dictionary + """ + # { group_id: {group_name: NAME, techniques: set{id, ...} } } + groups_dict = {} + + detection_techniques, name, platform = _load_detections(filename) + + group_id = 'VISIBILITY' + groups_dict[group_id] = {} + groups_dict[group_id]['group_name'] = 'Visibility' + groups_dict[group_id]['techniques'] = set() + for t, v in detection_techniques.items(): + if 'visibility' in v.keys() and v['visibility']['score'] > 0: + groups_dict[group_id]['techniques'].add(t) + + return groups_dict + + +def get_technique_count(groups, groups_overlay, groups_software): + """ + Create a dict with all involved techniques and their relevant count/score + :param groups: a dict with data on groups + :param groups_overlay: a dict with data on the groups to overlay + :param groups_software: a dict with with data on which techniques are used within related software + :return: dictionary + """ + # { technique_id: {count: ..., groups: set{} } + techniques_dict = {} + + for group, v in groups.items(): + for tech in v['techniques']: + if tech not in techniques_dict: + techniques_dict[tech] = dict() + techniques_dict[tech]['groups'] = set() + techniques_dict[tech]['count'] = 1 + else: + techniques_dict[tech]['count'] += 1 + techniques_dict[tech]['groups'].add(group) + + for group, v in groups_overlay.items(): + for tech in v['techniques']: + if tech not in techniques_dict: + techniques_dict[tech] = dict() + techniques_dict[tech]['groups'] = set() + techniques_dict[tech]['count'] = 1 + elif group in groups: + if tech not in groups[group]['techniques']: + techniques_dict[tech]['count'] += 1 + # Only to this when it was not already counted by being part of 'groups'. + # Meaning the group in 'groups_overlay' was also part of 'groups' (match on Group ID) and the + # technique was already counted for that group / it is not a new technique for that group coming + # from a YAML file + else: + techniques_dict[tech]['count'] += 1 + # increase count when the group in the YAML file is a custom group + techniques_dict[tech]['groups'].add(group) + + for group, v in groups_software.items(): + for tech in v['techniques']: + if tech not in techniques_dict: + techniques_dict[tech] = dict() + techniques_dict[tech]['count'] = 0 + # we will not adjust the scoring for groups_software. We will just set the the score to 0. + # This will later be used for the colouring of the heat map. + if 'groups' not in techniques_dict[tech]: + techniques_dict[tech]['groups'] = set() + techniques_dict[tech]['groups'].add(group) + + return techniques_dict + + +def get_technique_layer(techniques, groups, overlay, groups_software, overlay_file_type, overlay_type): + """ + Create the technique layer that will be part of the ATT&CK navigator json file + :param techniques: involved techniques with count (to be used within the scores) + :param groups: a dict with data on groups + :param overlay: a dict with data on the groups to overlay + :param groups_software: a dict with with data on which techniques are used within related software + :param overlay_file_type: the file type of the YAML file as present in the key 'file_type' + :param overlay_type: group, visibility or detection + :return: dictionary + """ + techniques_layer = [] + + # { technique_id: {count: ..., groups: set{} } + # add the technique count/scoring + for tech, v in techniques.items(): + t = dict() + t['techniqueID'] = tech + t['score'] = v['count'] + t['metadata'] = [] + metadata_dict = dict() + + for group, values in groups.items(): + if tech in values['techniques']: # we do not color this one because that's done using the scoring + if 'Groups' not in metadata_dict: + metadata_dict['Groups'] = set() + metadata_dict['Groups'].add(values['group_name']) + + # this will only be effective when loading a YAML files that has a value for the key 'campaign' + if 'Campaign' in values and values['campaign'] is not None: + if 'CAMPAIGN' not in metadata_dict: + metadata_dict['Campaign'] = set() + metadata_dict['Campaign'].add(values['campaign']) + + # change the color and add metadata to make the groups overlay visible + for group, values in overlay.items(): + if tech in values['techniques']: + if len(v['groups'].intersection(set(groups.keys()))) > 0: + # if the technique is both present in the group (-g/--groups) and the groups overlay (-o/--overlay) + t['color'] = COLOR_GROUP_OVERLAY_MATCH + else: + # the technique is only present in the overlay and not in the provided groups (-g/--groups) + if overlay_file_type == FILE_TYPE_TECHNIQUE_ADMINISTRATION: + if overlay_type == 'visibility': + t['color'] = COLOR_GROUP_OVERLAY_ONLY_VISIBILITY + elif overlay_type == 'detection': + t['color'] = COLOR_GROUP_OVERLAY_ONLY_DETECTION + else: + t['color'] = COLOR_GROUP_OVERLAY_NO_MATCH + + if 'Overlay' not in metadata_dict: + metadata_dict['Overlay'] = set() + metadata_dict['Overlay'].add(values['group_name']) + + # this will only be effective when loading a YAML files that has a value for the key 'campaign' + if 'campaign' in values and values['campaign'] is not None: + if 'Campaign' not in metadata_dict: + metadata_dict['Campaign'] = set() + metadata_dict['Campaign'].add(values['campaign']) + + # change the color and add metadata to make the groups software overlay visible + for group, values in groups_software.items(): # TODO add support for campaign info in layer metadata + if tech in values['techniques']: + if t['score'] > 0: + t['color'] = COLOR_GROUP_AND_SOFTWARE + else: + t['color'] = COLOR_SOFTWARE + + if 'Software groups' not in metadata_dict: + metadata_dict['Software groups'] = set() + metadata_dict['Software groups'].add(values['group_name']) + + # create the metadata based on the dict 'metadata_dict' + for metadata, values in metadata_dict.items(): + tmp_dict = {'name': '-' + metadata, 'value': ', '.join(values)} + t['metadata'].append(tmp_dict) + + techniques_layer.append(t) + + return techniques_layer + + +def get_group_list(groups, file_type): + """ + Make a list of group names for the involved groups. + :param groups: a dict with data on groups + :param file_type: the file type of the YAML file as present in the key 'file_type' + :return: list + """ + if file_type == FILE_TYPE_GROUP_ADMINISTRATION: + groups_list = [] + for group, values in groups.items(): + # if YAML file contains campaign key with a legit value + if 'campaign' in values and values['campaign'] is not None: + groups_list.append(values['group_name'] + ' (' + values['campaign'] + ')') + else: + groups_list.append(values['group_name']) + + return groups_list + else: + return groups + + +def generate_group_heat_map(groups, overlay, overlay_type, stage, platform, software_groups): + """ + Calls all functions that are necessary for the generation of the heat map and write a json layer to disk. + :param groups: threat actor groups + :param overlay: group(s), visibility or detections to overlay (group ID, group name/alias, YAML file with + group(s), detections or visibility) + :param overlay_type: group, visibility or detection + :param stage: attack or pre-attack + :param platform: all, Linux, macOS, Windows + :param software_groups: specify if techniques from related software should be included. + :return: returns nothing when something's wrong + """ + overlay_dict = {} + groups_software_dict = {} + + groups_file_type = None + if os.path.isfile(groups): + groups_file_type = check_file_type(groups, file_type=FILE_TYPE_GROUP_ADMINISTRATION) + if not groups_file_type: + return + else: + # remove whitespaces (leading and trailing), convert to lower case and put in a list + groups = groups.split(',') + groups = list(map(lambda x: x.strip().lower(), groups)) + + overlay_file_type = None + if overlay: + if os.path.isfile(overlay): + expected_file_type = FILE_TYPE_GROUP_ADMINISTRATION if overlay_type == 'group' else FILE_TYPE_TECHNIQUE_ADMINISTRATION if overlay_type in ['visibility', 'detection'] else None + overlay_file_type = check_file_type(overlay, expected_file_type) + if not overlay_file_type: + return + else: + overlay = overlay.split(',') + overlay = list(map(lambda x: x.strip().lower(), overlay)) + else: + overlay = [] + + if overlay_file_type == FILE_TYPE_TECHNIQUE_ADMINISTRATION: + if overlay_type == 'visibility': + overlay_dict = get_visibility_techniques(overlay) + elif overlay_type == 'detection': + overlay_dict = get_detection_techniques(overlay) + elif len(overlay) > 0: + overlay_dict = get_group_techniques(overlay, stage, platform, overlay_file_type) + if not overlay_dict: + return + + groups_dict = get_group_techniques(groups, stage, platform, groups_file_type) + if groups_dict == -1: + return + if len(groups_dict) == 0: + print('[!] Empty layer.') # the provided groups dit not result in any techniques + return + + # check if we are doing an software group overlay + if software_groups and overlay: # TODO add support for campaign info in layer metadata + # if a group overlay is provided, get the software techniques for the overlay + groups_software_dict = get_software_techniques(overlay, stage, platform) + elif software_groups: + groups_software_dict = get_software_techniques(groups, stage, platform) + + technique_count = get_technique_count(groups_dict, overlay_dict, groups_software_dict) + technique_layer = get_technique_layer(technique_count, groups_dict, overlay_dict, groups_software_dict, + overlay_file_type, overlay_type) + max_technique_count = max(technique_count.values(), key=lambda v: v['count'])['count'] + + # make a list group names for the involved groups. + if groups == ['all']: + groups_list = ['all'] + else: + groups_list = get_group_list(groups_dict, groups_file_type) + overlay_list = get_group_list(overlay_dict, overlay_file_type) + + desc = 'stage: ' + stage + ' | platform: ' + platform + ' | group(s): ' + ', '.join(groups_list) + \ + ' | overlay group(s): ' + ', '.join(overlay_list) + + layer = get_layer_template_groups(stage[0].upper() + stage[1:] + ' ' + platform, max_technique_count, desc, stage, platform) + layer['techniques'] = technique_layer + + json_string = simplejson.dumps(layer).replace('}, ', '},\n') + + if overlay: + filename = "output/" + stage + '_' + platform.lower() + '_' + '_'.join(groups_list) + '-overlay_' + '_'.join(overlay_list) + '.json' + else: + filename = "output/" + stage + '_' + platform.lower() + '_' + '_'.join(groups_list) + '.json' + with open(filename, 'w') as f: # write layer file to disk + f.write(json_string) + print('Written layer: ' + filename) + + +def get_updates(update_type, sort='modified'): + """ + Print a list of updates for a techniques, groups or software. Sort by modified or creation date. + :param update_type: the type of update: techniques, groups or software + :param sort: sort the list by modified or creation date + :return: + """ + if update_type[:-1] == 'technique': + techniques = load_attack_data(DATATYPE_ALL_TECH) + sorted_techniques = sorted(techniques, key=lambda k: k[sort]) + + for t in sorted_techniques: + print(t['technique_id'] + ' ' + t['technique']) + print(' ' * 6 + 'created: ' + t['created'].split(' ')[0]) + print(' ' * 6 + 'modified: ' + t['modified'][:10]) + print(' ' * 6 + 'matrix: ' + t['matrix'][6:]) + if t['tactic']: + print(' ' * 6 + 'tactic: ' + ' '.join(t['tactic'])) + else: + print(' ' * 6 + 'tactic: None') + print('') + + elif update_type[:-1] == 'group': + groups = load_attack_data(DATATYPE_ALL_GROUPS) + sorted_groups = sorted(groups, key=lambda k: k[sort]) + + for t in sorted_groups: + print(t['group_id'] + ' ' + t['group']) + print(' ' * 6 + 'created: ' + t['created'].split(' ')[0]) + print(' ' * 6 + 'modified: ' + t['modified'][:10]) + print('') + + elif update_type == 'software': + software = load_attack_data(DATATYPE_ALL_SOFTWARE) + sorted_software = sorted(software, key=lambda k: k[sort]) + + for t in sorted_software: + print(t['software_id'] + ' ' + t['software']) + print(' ' * 6 + 'created: ' + t['created'].split(' ')[0]) + print(' ' * 6 + 'modified: ' + t['modified'][:10]) + print(' ' * 6 + 'matrix: ' + t['matrix'][6:]) + print(' ' * 6 + 'type: ' + t['type']) + if t['software_platform']: + print(' ' * 6 + 'platform: ' + ', '.join(t['software_platform'])) + else: + print(' ' * 6 + 'platform: None') + print('') + + +def get_statistics(): + """ + Print out statistics related to data sources and how many techniques they cover. + :return: + """ + techniques = load_attack_data(DATATYPE_ALL_TECH) + + # {data_source: {techniques: [T0001, ...}, count: ...} + data_sources_dict = {} + for tech in techniques: + tech_id = tech['technique_id'] + data_sources = tech['data_sources'] + + if data_sources: + for ds in data_sources: + if ds not in data_sources_dict: + data_sources_dict[ds] = {'techniques': [tech_id], 'count': 1} + else: + data_sources_dict[ds]['techniques'].append(tech_id) + data_sources_dict[ds]['count'] += 1 + + # sort the dict on the value of 'count' + data_sources_dict_sorted = dict(sorted(data_sources_dict.items(), key=lambda kv: kv[1]['count'], reverse=True)) + str_format = '{:<6s} {:s}' + print(str_format.format('Count', 'Data Source')) + print('-'*50) + for k, v in data_sources_dict_sorted.items(): + print(str_format.format(str(v['count']), k)) diff --git a/interactive_menu.py b/interactive_menu.py new file mode 100644 index 0000000..25f9a99 --- /dev/null +++ b/interactive_menu.py @@ -0,0 +1,382 @@ +import sys +import glob +from data_source_mapping import * +from technique_mapping import * +from group_mapping import * + + +groups = 'all' +software_group = False +platform = 'Windows' +stage = 'attack' +groups_overlay = '' +overlay_type = '' + +MENU_NAME_DATA_SOURCE_MAPPING = 'Data source mapping' +MENU_NAME_VISIBILITY_MAPPING = 'Visibility coverage mapping' +MENU_NAME_DETECTION_COVERAGE_MAPPING = 'Detection coverage mapping' +MENU_NAME_THREAT_ACTOR_GROUP_MAPPING = 'Threat actor group mapping' + + +def clear(): + """ + Clears the terminal screen and prints the title and version of the application. + :return: + """ + if sys.platform.startswith('linux') or sys.platform == 'darwin': + os.system('clear') + elif sys.platform == 'win32': + os.system('cls') + name = '-= %s =-' % APP_NAME + desc = '-- %s --' % APP_DESC + version = 'version %s' % VERSION + print(' ' * int((len(desc)-len(name))/2) + name) + print(desc) + print(' ' * int((len(desc)-len(version))/2) + version) + print('') + + +def ask_input(): + """ + Waits for input from the terminal. + :return: + """ + return input(' >> ') + + +def wait(): + """ + Prints wait statement and wait for pressing ENTER key. + :return: + """ + print('') + print('Press a key to return to the last menu') + input('') + + +def interactive_menu(): + """ + Main menu for interactive mode. + :return: + """ + clear() + print('Select a mode:') + print('1. %s' % MENU_NAME_DATA_SOURCE_MAPPING) + print('2. %s' % MENU_NAME_VISIBILITY_MAPPING) + print('3. %s' % MENU_NAME_DETECTION_COVERAGE_MAPPING) + print('4. %s' % MENU_NAME_THREAT_ACTOR_GROUP_MAPPING) + print('5. Updates') + print('6. Statistics') + print('9. Quit') + choice = ask_input() + if choice == '1': + menu_data_source(select_file(MENU_NAME_DATA_SOURCE_MAPPING, 'data sources', FILE_TYPE_DATA_SOURCE_ADMINISTRATION)) + elif choice == '2': + menu_visibility(select_file(MENU_NAME_VISIBILITY_MAPPING, 'techniques (used to score the level of visibility)', FILE_TYPE_TECHNIQUE_ADMINISTRATION), + select_file(MENU_NAME_VISIBILITY_MAPPING, 'data sources (used to add metadata on the involved data sources to the heat map)', FILE_TYPE_DATA_SOURCE_ADMINISTRATION, False)) + elif choice == '3': + menu_detection(select_file(MENU_NAME_DETECTION_COVERAGE_MAPPING, 'techniques', FILE_TYPE_TECHNIQUE_ADMINISTRATION)) + elif choice == '4': + menu_groups() + elif choice == '5': + menu_updates() + elif choice == '6': + menu_statistics() + elif choice in ['9', 'q']: + quit() + else: + interactive_menu() + + +def select_file(title, what, expected_file_type, b_clear=True, path='sample-data/'): + """ + Prints and handles the file selection in the terminal. It shows just .yaml files. + :param title: title to print on top of this menu + :param what: print for what purpose the file is selected + :param expected_file_type: the expected file type of the YAML file + :param b_clear: clear the terminal before showing this memu + :param path: the path to look in + :return: filename of the selected file + """ + if b_clear: + clear() + print('Menu: %s' % title) + print('') + print('Select the YAML file with %s:' % what) + print('') + print('Path: %s' % path) + n = 1 + files = [] + for f in glob.glob(path + '*.yaml'): + files.append(f) + print('%d. %s' % (n, f)) + n += 1 + + change_path_nr = 8 if n < 8 else n + (5 - n % 5) - 1 + print('%d. Change path' % change_path_nr) + + back_nr = 9 if n < 9 else n + (5 - n % 5) + print('%d. Back to main menu.' % back_nr) + + choice = ask_input() + if choice == str(change_path_nr): + print("Supply full or relative path:") + choice = ask_input() + choice = choice if choice.endswith('/') else choice + '/' + if os.path.exists(choice): + return select_file(title, what, expected_file_type, b_clear, choice) + else: + print("[!] Path doesn't exist") + wait() + return select_file(title, what, expected_file_type, b_clear, path) + elif choice == str(back_nr): + interactive_menu() + elif choice == 'q': + quit() + else: + if choice.isdigit() and int(choice) < n: + filename = files[int(choice) - 1] + file_type = check_file_type(filename, file_type=expected_file_type) + if file_type: + return filename + else: + print("[!] Invalid choice") + + wait() + return select_file(title, what, expected_file_type, b_clear, path) + + +def menu_updates(): + """ + Prints and handles the menu for the Updates functionality. + :return: + """ + clear() + + print('Menu: Updates') + print('') + print('Select for what you want to see updates:') + print('1. Techniques (sorted by modified date)') + print('1s. Techniques (sorted by creation date)') + print('2. Groups (sorted by modified date)') + print('2s. Groups (sorted by creation date)') + print('3. Software (sorted by modified date)') + print('3s. Software (sorted by creation date)') + print('9. Back to main menu.') + choice = ask_input() + if choice == '1': + get_updates('techniques') + wait() + if choice == '1s': + get_updates('techniques', 'created') + wait() + elif choice == '2': + get_updates('groups') + wait() + elif choice == '2s': + get_updates('groups', 'created') + wait() + elif choice == '3': + get_updates('software') + wait() + elif choice == '3s': + get_updates('software', 'created') + wait() + elif choice == '9': + interactive_menu() + elif choice == 'q': + quit() + menu_updates() + + +def menu_statistics(): + """ + Handles the Statistics functionality. + :return: + """ + clear() + print('Menu: Statistics') + print('') + get_statistics() + wait() + interactive_menu() + + +def menu_data_source(filename): + """ + Prints and handles the Data source mapping functionality. + :param filename: + :return: + """ + clear() + print('Menu: %s' % MENU_NAME_DATA_SOURCE_MAPPING) + print('') + print('Selected data source YAML file: %s' % filename) + print('') + print('Select what you want to do:') + print('1. Generate a data source layer for the ATT&CK Navigator.') + print('2. Generate a graph with data sources added through time.') + print('3. Generate an Excel sheet with all data sources.') + print('4. Generate a technique administration YAML file with visibility scores, based on the number of available ' + 'data sources') + print('9. Back to main menu.') + choice = ask_input() + if choice == '1': + print('Writing data sources layer...') + generate_data_sources_layer(filename) + wait() + elif choice == '2': + print('Drawing the graph...') + plot_data_sources_graph(filename) + wait() + elif choice == '3': + print('Generating Excel file...') + export_data_source_list_to_excel(filename) + wait() + elif choice == '4': + print('Generating YAML file...') + generate_technique_administration_file(filename) + wait() + elif choice == '9': + interactive_menu() + elif choice == 'q': + quit() + menu_data_source(filename) + + +def menu_detection(filename_t): + """ + Prints and handles the Detection coverage mapping functionality. + :param filename_t: + :return: + """ + clear() + print('Menu: %s' % MENU_NAME_DETECTION_COVERAGE_MAPPING) + print('') + print('Selected techniques YAML file: %s' % filename_t) + print('') + print('Select what you want to do:') + print('1. Generate a layer for detection coverage for the ATT&CK Navigator.') + print('2. Generate a layer for detection coverage overlayed with visibility for the ATT&CK Navigator.') + print('3. Generate a graph with detections added through time.') + print('9. Back to main menu.') + choice = ask_input() + if choice == '1': + print('Writing detection coverage layer...') + generate_detection_layer(filename_t, None, False) + wait() + elif choice == '2': + filename_ds = select_file(MENU_NAME_DETECTION_COVERAGE_MAPPING, 'data sources (used to add metadata on the involved data sources to the heat map)', FILE_TYPE_DATA_SOURCE_ADMINISTRATION, False) + print('Writing detection coverage layer with visibility as overlay...') + generate_detection_layer(filename_t, filename_ds, True) + wait() + elif choice == '3': + print('Drawing the graph...') + plot_detection_graph(filename_t) + wait() + elif choice == '9': + interactive_menu() + elif choice == 'q': + quit() + menu_detection(filename_t) + + +def menu_visibility(filename_t, filename_ds): + """ + Prints and handles the Visibility coverage mappin functionality. + :param filename_t: + :param filename_ds: + :return: + """ + clear() + print('Menu: %s' % MENU_NAME_VISIBILITY_MAPPING) + print('') + print('Selected techniques YAML file: %s' % filename_t) + print('Selected data source YAML file: %s' % filename_ds) + print('') + print('Select what you want to do:') + print('1. Generate a layer for visibility for the ATT&CK Navigator.') + print('2. Generate a layers for visibility overlayed with detection coverage for the ATT&CK Navigator.') + print('9. Back to main menu.') + choice = ask_input() + if choice == '1': + print('Writing visibility coverage layer...') + generate_visibility_layer(filename_t, filename_ds, False) + wait() + elif choice == '2': + print('Writing visibility coverage layers overlayed with detections...') + generate_visibility_layer(filename_t, filename_ds, True) + wait() + elif choice == '9': + interactive_menu() + elif choice == 'q': + quit() + menu_visibility(filename_t, filename_ds) + + +def menu_groups(): + """ + Prints and handles the Threat actor group mapping functionality. + :return: + """ + global groups, software_group, platform, stage, groups_overlay, overlay_type + clear() + print('Menu: %s' % MENU_NAME_THREAT_ACTOR_GROUP_MAPPING) + print('') + print('Options:') + print('1. Software group: %s' % str(software_group)) + print('2. Platform: %s' % platform) + print('3. Stage: %s' % stage) + print('4. Groups: %s' % groups) + print('5. Overlay: ') + print(' - %s: %s' % ('File' if os.path.exists(groups_overlay) else 'Groups', groups_overlay)) + print(' - Type: %s' % overlay_type) + print('') + print('6. Generate a heat map layer.') + print('9. Back to main menu.') + choice = ask_input() + if choice == '1': + print('Specify True or False for software group:') + software_group = True if ask_input().lower() == 'true' else False + elif choice == '2': + print('Specify platform (all, Linux, macOS, Windows):') + p = ask_input().lower() + platform = 'Windows' if p == 'windows' else 'Linux' if p == 'linux' else 'macOS' if p == 'macos' else 'all' + elif choice == '3': + print('Specify stage (pre-attack, attack):') + s = ask_input().lower() + stage = 'pre-attack' if s == 'pre-attack' else 'attack' + elif choice == '4': + print('Specify the groups to include separated using commas. Group can be their ID, name or alias ' + '(default is all groups). Other option is to provide a YAML file with a custom group(s)') + groups = ask_input() + elif choice == '5': + print('') + print('1. Overlay with groups.') + print('2. Overlay with detections.') + print('3. Overlay with visibility.') + print('4. No overlay.') + choice = ask_input() + if choice == '1': + print('Specify the group(s) to overlay (in a different color) on the one specified in the Groups option. ' + 'A group can be their ID, name or alias separated using commas. Other option is to provide a YAML ' + 'file with a custom group(s).') + overlay_type = 'group' + groups_overlay = ask_input() + elif choice == '2': + overlay_type = 'detection' + groups_overlay = select_file(MENU_NAME_THREAT_ACTOR_GROUP_MAPPING, 'techniques', FILE_TYPE_TECHNIQUE_ADMINISTRATION, False) + elif choice == '3': + overlay_type = 'visibility' + groups_overlay = select_file(MENU_NAME_THREAT_ACTOR_GROUP_MAPPING, 'techniques', FILE_TYPE_TECHNIQUE_ADMINISTRATION, False) + elif choice == '4': + overlay_type = '' + groups_overlay = '' + elif choice == '6': + generate_group_heat_map(groups, groups_overlay, overlay_type, stage, platform, software_group) + wait() + elif choice == '9': + interactive_menu() + elif choice == 'q': + quit() + menu_groups() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3c69d5a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +attackcti==0.2.1 +simplejson==3.16.0 +PyYAML==5.1 +plotly==3.7.1 +pandas==0.24.2 +xlsxwriter==1.1.5 \ No newline at end of file diff --git a/sample-data/data-sources-endpoints.yaml b/sample-data/data-sources-endpoints.yaml new file mode 100644 index 0000000..80bccc6 --- /dev/null +++ b/sample-data/data-sources-endpoints.yaml @@ -0,0 +1,626 @@ +%YAML 1.2 +--- +version: 1.0 +file_type: data-source-administration +name: endpoints-example +platform: windows +data_sources: + # A data source is treated as not available when all dimensions of the data quality have a score of 0. + # If desired you are free to add any key-value pairs. + - data_source_name: Process monitoring + date_registered: 2019-03-01 + date_connected: 2017-01-01 + products: [Windows event log] + available_for_data_analytics: True + comment: '' + data_quality: + device_completeness: 5 + data_field_completeness: 5 + timeliness: 5 + consistency: 5 + retention: 5 + - data_source_name: File monitoring + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Process command-line parameters + date_registered: 2019-03-01 + date_connected: 2017-01-01 + products: [Windows event log] + available_for_data_analytics: True + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: API monitoring + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Process use of network + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Windows Registry + date_registered: 2019-03-01 + date_connected: 2017-02-01 + products: [Windows event log] + available_for_data_analytics: True + comment: '' + data_quality: + device_completeness: 5 + data_field_completeness: 5 + timeliness: 5 + consistency: 5 + retention: 5 + - data_source_name: Packet capture + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Authentication logs + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Netflow/Enclave netflow + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Windows event logs + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Binary file metadata + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Network protocol analysis + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: DLL monitoring + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Loaded DLLs + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: System calls + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Malware reverse engineering + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: SSL/TLS inspection + date_registered: 2019-01-10 + date_connected: 2000-01-01 + products: [Proxy Product] + available_for_data_analytics: True + comment: '' + data_quality: + device_completeness: 5 + data_field_completeness: 5 + timeliness: 5 + consistency: 5 + retention: 5 + - data_source_name: Anti-virus + date_registered: 2019-01-10 + date_connected: 2000-01-01 + products: [AV Product] + available_for_data_analytics: True + comment: '' + data_quality: + device_completeness: 4 + data_field_completeness: 2 + timeliness: 3 + consistency: 5 + retention: 5 + - data_source_name: Network intrusion detection system + date_registered: 2019-01-10 + date_connected: 2016-01-01 + products: [NIDS] + available_for_data_analytics: True + comment: '' + data_quality: + device_completeness: 4 + data_field_completeness: 3 + timeliness: 3 + consistency: 4 + retention: 4 + - data_source_name: Data loss prevention + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Application logs + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Email gateway + date_registered: 2019-01-10 + date_connected: 2000-01-01 + products: [Email Gateway Product] + available_for_data_analytics: True + comment: '' + data_quality: + device_completeness: 4 + data_field_completeness: 3 + timeliness: 3 + consistency: 5 + retention: 3 + - data_source_name: Network device logs + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Web proxy + date_registered: 2019-01-10 + date_connected: 2000-01-01 + products: [Proxy Product] + available_for_data_analytics: True + comment: '' + data_quality: + device_completeness: 5 + data_field_completeness: 5 + timeliness: 5 + consistency: 5 + retention: 5 + - data_source_name: Windows Error Reporting + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Kernel drivers + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: User interface + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Host network interface + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Third-party application logs + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Services + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Web logs + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Detonation chamber + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Mail server + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Environment variable + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: MBR + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: BIOS + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Web application firewall logs + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Asset management + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: DHCP + date_registered: + date_connected: + products: [None] + available_for_data_analytics: True + comment: 'At the time of writing: unknown data source within ATT&CK' + data_quality: + device_completeness: 5 + data_field_completeness: 5 + timeliness: 5 + consistency: 5 + retention: 5 + - data_source_name: DNS records + date_registered: 2019-03-01 + date_connected: 2017-04-01 + products: [Windows DNS server] + available_for_data_analytics: True + comment: '' + data_quality: + device_completeness: 5 + data_field_completeness: 5 + timeliness: 5 + consistency: 5 + retention: 5 + - data_source_name: Browser extensions + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Access tokens + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Digital certificate logs + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Disk forensics + date_registered: 2019-01-10 + date_connected: 2019-01-01 + products: [Manual, Commercial tool] + available_for_data_analytics: True + comment: '' + data_quality: + device_completeness: 5 + data_field_completeness: 5 + timeliness: 1 + consistency: 5 + retention: 0 + - data_source_name: Component firmware + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: WMI Objects + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: VBR + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Named Pipes + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: Sensor health and status + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: EFI + date_registered: + date_connected: + products: [None] + available_for_data_analytics: False + comment: '' + data_quality: + device_completeness: 0 + data_field_completeness: 0 + timeliness: 0 + consistency: 0 + retention: 0 + - data_source_name: PowerShell logs + date_registered: 2019-03-01 + date_connected: 2018-05-01 + products: [Windows event log] + available_for_data_analytics: True + comment: 'Script block logging is enabled' + data_quality: + device_completeness: 5 + data_field_completeness: 5 + timeliness: 5 + consistency: 5 + retention: 4 +exceptions: + # Adding a technique ID below will result in removing that technique in the heat map (meaning not enough data source are available for proper detection). + # Please note that the below is just an example, many more can exists. + - technique_id: T1130 + name: Install Root Certificate \ No newline at end of file diff --git a/sample-data/groups.yaml b/sample-data/groups.yaml new file mode 100644 index 0000000..cbcc557 --- /dev/null +++ b/sample-data/groups.yaml @@ -0,0 +1,17 @@ +%YAML 1.2 +--- +version: 1.0 +file_type: group-administration +groups: + - + group_name: Red team + campaign: Scenario 1 + technique_id: [T1086, T1053, T1193, T1204, T1003, T1055, T1027, T1085, T1099, T1082, T1016, T1033, T1087, T1075, T1057, T1039, T1041, T1071, T1043, T1001, T1114, T1002] + software_id: [S0002] + enabled: True + - + group_name: APT3 (MITRE ATT&CK evaluation) + campaign: First Scenario + technique_id: [T1204, T1064, T1085, T1060, T1043, T1071, T1132, T1016, T1059, T1033, T1057, T1106, T1007, T1082, T1069, T1087, T1012, T1088, T1134, T1055, T1018, T1049, T1003, T1026, T1076, T1136, T1061, T1105, T1053, T1083, T1056, T1010, T1113, T1039, T1041, T1078] + software_id: [S0154] + enabled: False diff --git a/sample-data/techniques-administration-endpoints.yaml b/sample-data/techniques-administration-endpoints.yaml new file mode 100644 index 0000000..c93096f --- /dev/null +++ b/sample-data/techniques-administration-endpoints.yaml @@ -0,0 +1,1660 @@ +%YAML 1.2 +--- +version: 1.0 +file_type: technique-administration +name: endpoints-example +platform: windows +techniques: + # - Note that detection and visibility are independent from each other. + # Meaning that detection could be left blank and only have visibility filled in. + # - Also note that the below serves purely as an example and is therefore not accurate on all areas. + # + # - If desired you are free to add any key-value pairs. This will not impact the functionality of the tool. +- technique_id: T1222 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1223 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1221 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1220 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1217 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1196 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-12-01 + score: 4 + location: [EDR] + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1214 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-12-01 + score: 3 + location: [EDR] + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1189 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-11-01 + score: 1 + location: [SIEM UC 123, Tool Model Y] + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1203 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1210 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1211 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1202 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1212 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1201 + detection: + date_registered: 2019-01-10 + date_implemented: 2017-01-01 + score: 4 + location: + - 'Third party product A' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1191 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1219 + detection: + date_registered: 2019-01-10 + date_implemented: 2017-01-01 + score: 4 + location: + - 'Third party product A' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1198 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1218 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1193 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1216 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1192 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1209 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1195 + detection: + date_registered: 2019-01-10 + date_implemented: 2017-01-01 + score: 2 + location: + - 'Third party product A' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1194 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 4 + comment: '' +- technique_id: T1204 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-12-01 + score: 0 + location: [EDR] + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1182 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-12-01 + score: 3 + location: [EDR] + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1176 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1175 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1185 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1174 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1170 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1171 + detection: + date_registered: 2019-01-10 + date_implemented: 2017-01-01 + score: 2 + location: + - 'Third party product A' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1173 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1181 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-12-01 + score: 4 + location: [EDR] + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1179 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1186 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1172 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-08-01 + score: 5 + location: + - 'Model A' + comment: '' + visibility: + score: 4 + comment: '' +- technique_id: T1183 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-11-01 + score: 2 + location: [Tool] + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1177 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1180 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1134 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-12-01 + score: 4 + location: [EDR] + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1138 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-12-01 + score: 1 + location: [SIEM] + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1140 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1136 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1137 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1158 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1135 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1132 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1131 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1129 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1128 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1127 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1126 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1125 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1124 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1123 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1122 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1121 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1118 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1117 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-12-01 + score: 3 + location: [EDR] + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1114 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1113 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1112 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1111 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1109 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1108 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1106 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1105 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1103 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1102 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1101 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-11-01 + score: 4 + location: [SIEM UC 789] + comment: '' + visibility: + score: 3 + comment: '' +- technique_id: T1100 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1099 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-11-01 + score: 2 + location: [Tool Model X] + comment: '' + visibility: + score: 4 + comment: '' +- technique_id: T1095 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 3 + comment: '' +- technique_id: T1094 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 3 + comment: '' +- technique_id: T1093 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1090 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1089 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1088 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1087 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1086 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-12-01 + score: 3 + location: [EDR] + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1085 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-12-01 + score: 3 + location: [EDR] + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1083 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1082 + detection: + date_registered: 2019-01-10 + date_implemented: 2017-01-01 + score: 3 + location: + - 'Third party product A' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1080 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1079 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1078 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1077 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-10-01 + score: 0 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1076 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1074 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1073 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1072 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1071 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-11-01 + score: -1 + location: [SIEM UC 123] + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1070 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1069 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1068 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1066 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1065 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-10-01 + score: 5 + location: + - 'Model B' + comment: '' + visibility: + score: 3 + comment: '' +- technique_id: T1064 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-12-01 + score: 3 + location: [EDR, AV Product] + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1063 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1061 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1060 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1059 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1058 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1057 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1056 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-12-01 + score: 4 + location: [EDR] + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1055 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-12-01 + score: 4 + location: [EDR] + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1054 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1053 + detection: + date_registered: + date_implemented: + score: -1 + location: '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1051 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1050 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: 'Model G' + visibility: + score: 2 + comment: '' +- technique_id: T1049 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1048 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1047 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1043 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-10-01 + score: 0 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1042 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1041 + detection: + date_registered: 2019-01-10 + date_implemented: 2017-01-01 + score: 2 + location: + - 'Third party product A' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1040 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1039 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1038 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1037 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-05-07 + score: 3 + location: + - 'Model F' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1036 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-02-01 + score: 4 + location: [Model C] + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1035 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-12-01 + score: 4 + location: [EDR] + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1034 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1033 + detection: + date_registered: 2019-01-10 + date_implemented: 2017-01-01 + score: 3 + location: + - 'Third party product A' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1032 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 3 + comment: '' +- technique_id: T1031 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1030 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1029 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1028 + detection: + date_registered: + date_implemented: + score: -1 + location: '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1027 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1026 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1025 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1024 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-12-01 + score: 0 + location: [EDR] + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1023 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1022 + detection: + date_registered: 2019-01-10 + date_implemented: 2017-10-10 + score: 2 + location: + - 'Model D' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1020 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1018 + detection: + date_registered: 2019-01-10 + date_implemented: 2017-01-01 + score: 3 + location: + - 'Third party product A' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1017 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1016 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1015 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1013 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1012 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1011 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1010 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1008 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1007 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1005 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1004 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1003 + detection: + date_registered: 2019-01-10 + date_implemented: 2018-12-01 + score: 3 + location: [EDR] + comment: '' + visibility: + score: 2 + comment: '' +- technique_id: T1002 + detection: + date_registered: 2019-01-10 + date_implemented: 2017-10-10 + score: 2 + location: + - 'Model E' + comment: '' + visibility: + score: 1 + comment: '' +- technique_id: T1001 + detection: + date_registered: + date_implemented: + score: -1 + location: + - '' + comment: '' + visibility: + score: 2 + comment: '' diff --git a/scoring_table.xlsx b/scoring_table.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..3aebf9ea2228b9f52a0693f394071c10348c8a52 GIT binary patch literal 29514 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

$0Ryt%CLDAgVTKJsaWBM`5*AkC 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 0 and dq['data_field_completeness'] > 0 and dq['timeliness'] > 0 and dq['consistency'] > 0: + my_data_sources[d['data_source_name']] = d + return my_data_sources + + +def _write_layer(layer, mapped_techniques, filename_prefix, name): + """ + Writes the json layer file to disk. + :param layer: the prepped layer dictionary + :param mapped_techniques: the techniques section that will be included in the layer + :param filename_prefix: the prefix for the output filename + :param name: the name that will be used in the filename together with the prefix + :return: + """ + + layer['techniques'] = mapped_techniques + json_string = simplejson.dumps(layer).replace('}, ', '},\n') + output_filename = 'output/%s_%s.json' % (filename_prefix, normalize_name_to_filename(name)) + with open(output_filename, 'w') as f: + f.write(json_string) + print("File written: " + output_filename) + + +def _map_and_colorize_techniques_for_detections(my_techniques): + """ + Determine the color of the techniques based on the detection score in the given yaml file. + :param my_techniques: the configured techniques + :return: a dictionary with techniques that can be used in the layer's output file + """ + techniques = load_attack_data(DATATYPE_ALL_TECH) + + # Color the techniques based on how the coverage defined in the detections definition and generate a list with + # techniques to be used in the layer output file. + mapped_techniques = [] + try: + for d, c in my_techniques.items(): + s = -1 if 'detection' not in c.keys() else c['detection']['score'] + color = COLOR_D_0 if s == 0 else COLOR_D_1 if s == 1 else COLOR_D_2 if s == 2 else COLOR_D_3 \ + if s == 3 else COLOR_D_4 if s == 4 else COLOR_D_5 if s == 5 else '' + technique = get_technique(techniques, d) + for tactic in technique['tactic']: + location = ', '.join(c['detection']['location']) if 'detection' in c.keys() else '-' + location = location if location != '' else '-' + x = {} + x['techniqueID'] = d + x['color'] = color + x['comment'] = '' + x['enabled'] = True + x['tactic'] = tactic.lower().replace(' ', '-') + x['metadata'] = [{'name': '-Detection score', 'value': str(s)}, + {'name': '-Detection location', 'value': location}] + + mapped_techniques.append(x) + except Exception: + print('[!] Possible error in YAML file at: ' + d) + quit() + + return mapped_techniques + + +def _map_and_colorize_techniques_for_visibility(my_techniques, my_data_sources): + """ + Determine the color of the techniques based on the visibility score in the given yaml file. + :param my_techniques: the configured techniques + :param my_data_sources: the configured data sources + :return: a dictionary with techniques that can be used in the layer's output file + """ + techniques = load_attack_data(DATATYPE_ALL_TECH) + + technique_ds_mapping = map_techniques_to_data_sources(techniques, my_data_sources) + + # Color the techniques based on how the coverage defined in the detections definition and generate a list with + # techniques to be used in the layer output file. + mapped_techniques = [] + for d, c in my_techniques.items(): + s = 0 if 'visibility' not in c.keys() else c['visibility']['score'] + if 'visibility' in c.keys(): + comment = str(c['visibility']['comment']) if str(c['visibility']['comment']) != '' else '-' + else: + comment = '-' + my_ds = ', '.join(technique_ds_mapping[d]['my_data_sources']) if d in technique_ds_mapping.keys() and technique_ds_mapping[d]['my_data_sources'] else '-' + color = COLOR_V_1 if s == 1 else COLOR_V_2 if s == 2 else COLOR_V_3 if s == 3 else COLOR_V_4 if s == 4 else '' + technique = get_technique(techniques, d) + for tactic in technique['tactic']: + x = {} + x['techniqueID'] = d + x['color'] = color + x['comment'] = '' + x['enabled'] = True + x['tactic'] = tactic.lower().replace(' ', '-') + x['metadata'] = [{'name': '-Visibility score', 'value': str(s)}, + {'name': '-Comment', 'value': comment}, + {'name': '-Available data sources', 'value': my_ds}, + {'name': '-ATT&CK data sources', 'value': ', '.join(technique['data_sources'])}] + + mapped_techniques.append(x) + + for t in techniques: + if t['technique_id'] not in my_techniques.keys(): + if t['tactic']: + for tactic in t['tactic']: + x = {} + x['techniqueID'] = t['technique_id'] + x['comment'] = '' + x['enabled'] = True + x['tactic'] = tactic.lower().replace(' ', '-') + ds = ', '.join(t['data_sources']) if t['data_sources'] else '-' + x['metadata'] = [{'name': '-ATT&CK data sources', 'value': ds}] + + mapped_techniques.append(x) + + return mapped_techniques + + +def _map_and_colorize_techniques_for_overlayed(my_techniques, my_data_sources): + """ + Determine the color of the techniques based on both detection and visibility. + :param my_techniques: the configured techniques + :param my_data_sources: the configured data sources + :return: a dictionary with techniques that can be used in the layer's output file + """ + techniques = load_attack_data(DATATYPE_ALL_TECH) + + technique_ds_mapping = map_techniques_to_data_sources(techniques, my_data_sources) + + # Color the techniques based on how the coverage defined in the detections definition and generate a list with + # techniques to be used in the layer output file. + mapped_techniques = [] + for d, c in my_techniques.items(): + detection_score = 0 if 'detection' not in c.keys() else c['detection']['score'] + visibility_score = 0 if 'visibility' not in c.keys() else c['visibility']['score'] + + detection = True if detection_score > 0 else False + visibility = True if visibility_score > 0 else False + + if detection and visibility: + color = COLOR_OVERLAY_BOTH + elif detection and not visibility: + color = COLOR_OVERLAY_DETECTION + elif not detection and visibility: + color = COLOR_OVERLAY_VISIBILITY + + location = ', '.join(c['detection']['location']) if 'detection' in c.keys() else '-' + location = location if location != '' else '-' + + if 'visibility' in c.keys(): + comment = str(c['visibility']['comment']) if str(c['visibility']['comment']) != '' else '-' + else: + comment = '-' + + my_ds = ', '.join(technique_ds_mapping[d]['my_data_sources']) if d in technique_ds_mapping.keys() and technique_ds_mapping[d]['my_data_sources'] else '-' + + technique = get_technique(techniques, d) + for tactic in technique['tactic']: + x = {} + x['techniqueID'] = d + x['color'] = color + x['comment'] = '' + x['enabled'] = True + x['tactic'] = tactic.lower().replace(' ', '-') + x['metadata'] = [{'name': '-Visibility score', 'value': str(visibility_score)}, + {'name': '-Comment', 'value': comment}, + {'name': '-Available data sources', 'value': my_ds}, + {'name': '-ATT&CK data sources', 'value': ', '.join(technique['data_sources'])}, + {'name': '-Detection score', 'value': str(detection_score)}, + {'name': '-Detection location', 'value': location}] + + mapped_techniques.append(x) + + return mapped_techniques