Strategies for CyberPatriot

A look inside how the high school cybersecurity competition CyberPatriot calculates score and the lessons learned from such analysis

CyberPatriot is a secondary school-level cybersecurity competition based around hardening instances of desktop and server machines. Sponsored by the Air Force Association and Northrop Grumman, the program is an initiative to inspire a new generation of engineers for the adapting government landscape. It presents a unique experience, especially to those without any prior introduction to the subject matter.

Unfortunately, much of the competition is hazed with an air of mystery; if not for the upperclassmen who had first introduced me I would have had no idea what CyberPatriot entailed before diving straight in competition day. Even after two years I realized I wasn’t much more aware of what was really going on beyond the Mario coin sound playing every time I got lucky with installing anti-virus software. This expository piece is my effort to alleviate this void in understanding, if not only for myself but for any other CyberPatriot stuck in the same rut.

Vulnerability Scoring

Behind every competition image is the CyberPatriot Scoring Engine, a local daemon responsible for tabulating all of your points and relaying that to the official server. Discussion of this protocol is neither here nor there but it can be insightful to know how exactly this scoring engine evaluates the state of your image.

Behind every security issue that points are awarded for, or in the case of penalties, confiscated for, are a series of tests and conditionals. Tests are rather straightforward – they specify a certain type of test and any parameters. The conditionals then evaluate the results of the tests and determine if the so-called check and its associated point value should be applied. Some checks involve several tests and conditionals while others are only responsible for evaluating one factor. Here’s an example of a simple check that will award 5 points when the user branw has been removed from the system:

checks:
  - name: "branw"
    description: "User branw has been removed"
    value: 5
    tests:
      - name: "test_a"
        test: "user"
        user_name: "branw"
    conditions:
      - if:
        - test: "test_a"
          value: "exists"
          equals: "false"

Every 5 minutes or so, the engine will iterate over the list of checks, evaluate every test, and finally award points if at least one set of conditionals is true. You can imagine penalties working very similarly, only with value existing on the other side of zero. To get an idea of what the engine is capable of, here are a few different kinds of tests it implements:

  • Account lockout policy:

    • Obtained from NetUserModalsGet with level = 3, returning

      typedef struct _USER_MODALS_INFO_3 {
        DWORD usrmod3_lockout_duration;
        DWORD usrmod3_lockout_observation_window;
        DWORD usrmod3_lockout_threshold;
      } USER_MODALS_INFO_3, *PUSER_MODALS_INFO_3, *LPUSER_MODALS_INFO_3;
      
    • Checks that a lockout threshold exists and is between a certain value

  • Registered antivirus:

    • Obtained by polling the WMI providers ROOT\\SecurityCenter and ROOT\\SecurityCenter2 with the query SELECT * FROM AntiVirusProduct
    • Checks that an anti-virus solution has been installed and registered with the security center
  • Audit policies:

    • Obtained from LsaQueryInformationPolicy with InformationClass = PolicyAuditEventsInformation, capable of auditing the following categories:

      typedef enum _POLICY_AUDIT_EVENT_TYPE { 
        AuditCategorySystem,
        AuditCategoryLogon,
        AuditCategoryObjectAccess,
        AuditCategoryPrivilegeUse,
        AuditCategoryDetailedTracking,
        AuditCategoryPolicyChange,
        AuditCategoryAccountManagement,
        AuditCategoryDirectoryServiceAccess,
        AuditCategoryAccountLogon
      } POLICY_AUDIT_EVENT_TYPE, *PPOLICY_AUDIT_EVENT_TYPE;
      
    • Checks a variety of details including when policies are changed

  • Files:

    • Checks existence of files as well as their contents against regular expressions
  • Firewall:

    • Obtained by polling the WMI providers ROOT\\SecurityCenter and ROOT\\SecurityCenter2 with the query SELECT * FROM FirewallProduct
    • Checks if a firewall is enabled and if so, which one
  • Resultant Set of Policy security settings (group policies):

    • Obtained by polling the WMI providers ROOT\\RSOP\\Computer with the query SELECT * FROM RSOP_SecuritySettingboolean
    • Checks any kind of group policy setting
  • Password policies:

    • Obtained from NetUserModalsGet with levels = 0, returning

      typedef struct _USER_MODALS_INFO_0 {
        DWORD usrmod0_min_passwd_len;
        DWORD usrmod0_max_passwd_age;
        DWORD usrmod0_min_passwd_age;
        DWORD usrmod0_force_logoff;
        DWORD usrmod0_password_hist_len;
      } USER_MODALS_INFO_0, *PUSER_MODALS_INFO_0, *LPUSER_MODALS_INFO_0;
      
    • Checks password requirements, including password length, history length, and age limits

  • Processes:

    • Obtained through EnumProcesses
    • Checks state of running processes
  • Registry:

    • Obtained through the Win32 registry API
    • Checks values for a variety of keys, mostly in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet and HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\
  • Services:

    • Obtained from QueryServiceStatus
    • Checks state of services
  • Users:

    • Obtained from NetUserGetInfo with level = 2, returning

      typedef struct _USER_INFO_2 {
        LPWSTR usri2_name;
        LPWSTR usri2_password;
        DWORD  usri2_password_age;
        DWORD  usri2_priv;
        LPWSTR usri2_home_dir;
        LPWSTR usri2_comment;
        DWORD  usri2_flags;
        LPWSTR usri2_script_path;
        DWORD  usri2_auth_flags;
        LPWSTR usri2_full_name;
        LPWSTR usri2_usr_comment;
        LPWSTR usri2_parms;
        LPWSTR usri2_workstations;
        DWORD  usri2_last_logon;
        DWORD  usri2_last_logoff;
        DWORD  usri2_acct_expires;
        DWORD  usri2_max_storage;
        DWORD  usri2_units_per_week;
        PBYTE  usri2_logon_hours;
        DWORD  usri2_bad_pw_count;
        DWORD  usri2_num_logons;
        LPWSTR usri2_logon_server;
        DWORD  usri2_country_code;
        DWORD  usri2_code_page;
      } USER_INFO_2, *PUSER_INFO_2, *LPUSER_INFO_2;
      
    • Checks if a user has been deleted or if they’re in a certain group, etc.

  • Volume:

    • Obtained from GetVolumeInformation
    • Checks volume metadata including serial number; the scoring engine also uses this to verify that the primary hard drive serial number matches, i.e. that it is not running outside of the competition
  • Windows version:

    • Obtained from GetNativeSystemInfo and GetProductInfo
    • Checks if an operating system is installed (just in case, you never know) including the build version and service packs

Keep in mind, the Ubuntu scoring engine is or at least has been a fairly crippled version of that seen on Windows images; generally only the file, user, and process tests are implemented. By the policy of “everything is a file,” this is not all too limiting

Conclusions

When taking a step back and seeing the competition by how it is scored, one is able to gain a bit of a deeper understanding of how to tackle the competition. While each round generally brings something new to the table, there are a few essential patterns behind every competition.