A simple and fast Python script to scan configurations for US Government Security Technical Implementation Guidance (STIG) compliance. The tool works in an offline mode using an extensible framework of YAML rulesets for each vulnerability of interest.
Contact information:
Email: njrusmc@gmail.com
Twitter: @nickrusso42518
Any platform that has a text-based configuration suited for matching by regex can be used. The examples in this repository are all based on Cisco network devices. The support structures are in place for other operating systems as well.
At the time of this writing, Cisco IOS and NXOS configurations are supported. Cisco IOS-XR and ASA will be supported in the near future.
usage: stig.py [-h] [-v {0,1,2}] [-f] config_file
A config_file
is a relative path to the configuration file to scan,
for example configs/l3pr.cfg
. These files do not have to be in git
but could be if they are being used as golden templates. This argument
is required.
The -v
or --verbosity
argument determines the output style:
0
: One line per rule showing the vuln ID, description, and result1
: Verbose output showing all rule info, including pass/fail objects2
: CSV format, one rule per line, including pass/fail objects
This argument is optional and when unspecified, 0
is assumed. See the
samples/
folder for example outputs of each style.
The -f
or --failonly
argument enables the user to only print failed
(out of compliance) rules. This reduces output and is good for on-demand
testing or automated testing where the pass/NA results are not important.
This a boolean option and does not take additional parameters. This
argument is optional and when unspecified, false
is assumed. All
test results are printed by default (pass, fail, and NA).
Each individual rule or sub-rule goes in its own YAML file. Having many
small files enables simpler searching, editing, adding, and deleting for
the management of the rule set. Note that some rules as written in the STIG
specifications may check multiple things. For example V18633
lists many
banned tunneling protocols, but it is simpler to break these into separate
sub-rule files as shown below. This way, if there are only a few missing
protocols, the entire rule does not fail, and provides a more targeted
notification for remediation.
# V18633a.yml
---
severity: 2
desc: Deny outdated tunneling protocol IPP 42
check:
text: deny\s+42\s+any\s+any\s+log
text_cnt: 1
parent: ^ip\s+access-list\s+extended\s+ACL_EXTERNAL
when: true
part_of_stig:
- l3ps
- l3pr
# V18633b.yml
---
severity: 2
desc: Deny outdated tunneling protocol IPP 93
check:
text: deny\s+93\s+any\s+any\s+log
text_cnt: 1
parent: ^ip\s+access-list\s+extended\s+ACL_EXTERNAL
when: true
part_of_stig:
- l3ps
- l3pr
Different operating systems will have different CLI syntax for the same
features, so separate rulesets are needed per OS. In the rules/
directory,
there is a subdirectory for each OS, such as ios
, xr
, asa
, and nxos
.
Each configuration file must contain a !@#type:type_name
directive at
the top of the file to indicate what the OS is.
The components of a rule file are described below:
severity
: The category number of 1, 2, or 3. Documentation only.desc
: Summarized explanation of the rule; be succinct.check
: Nested dictionary containing the critical parts of the ruletext
: The regex to search for. Do not quote the string.text_cnt
: The number of times to search fortext
. Often times this is set to 1, but could be greater if the regex is generic and looking for many things (e.g. multiple NTP or AAA servers). To test for a configuration item being totally absent, use 0 (e.g. ensure thatip directed-broadcast
appears zero times under each interface).parent
: The regex of the parent under which thetext
regex should be searched. For example, searching for ACL entries under an ACL. Do not quote the string.when
: The sibling totext
that tests for a regex to be present before looking fortext
. For example, only check forno ip proxy-arp
under an interface if it has an IP address. Set this totrue
to always look fortext
. Ifwhen
isfalse
or the regex fails to match, the item is marked "N/A" versus "PASS" or "FAIL". Do not quote the string.part_of_stig
: List of strings that indicate when this rule should be evaluated. This string must match the directive at the top of each configuration file to be included. For example, if a rule is part ofl3ps
andl3pr
, a configuration with either one of these directives will include this rule. The directive string is!@#stig:stig_name
. Seeconfigs/
for examples.
A GNU Makefile is used for testing this codebase. There are currently two steps:
lint
: Runs YAML and Python linters, as well as a Python static code analyzer to check fo security flaws.run
: Runs the STIG tool itself with a variety of input files at all available verbosities to test proper operation. The default input files should have no failures. If any failures do exist, this step fails. Failures can be STIG rule failures or catastrophic unhandled exceptions.
Q: Does this tool have the logic to traverse complex dependencies?
A: No. It applies the text
regex for each rule based on its position
in the configuration, either globally or under a parent
regex. For example,
embedding blacklist items in an object-group and calling the object-group
from an access-list will be counted by this tool unless the user defines
the rules appropriately.
Q: Can I add my own rules or change the existing rules?
A: Yes. There is nothing specific about DISA STIGs for this tool, other
than some naming conventions (e.g., vuln ID) and design intent. I have
included several extra
rules in the rules/
directory to illustrate
this point. Users are encouraged to update the rules to fit their
specific environment; this is not a static, click-button dogmatic tool.
Q: Can configurations be part of more than one STIG?
A: Yes. Use the !@#stig:stig_name
directive at the top of the file
as many times as necessary. Ensure the corresponding rules have this
string in their part_of_stig
YAML list.