Custom Service Checks

New in version 1.6.

Attention

Custom Services are an advanced feature of IScorE. The vast majority of the time, you will not need them.

If you need the service scanner to perform a check that is not one of the built-in checks, IScorE has support for custom service checks. IScorE searches all the apps listed in INSTALLED_APPS for a services.py file, which it will then load. Service check types must be defined in a services.py to be recognized by IScorE. Service checks are defined by creating a sub-class of ServiceType and registering it with the ServiceRegistry.

In general, IScorE has three kinds of service checks: Standard Checks, Multi-Protocol Checks, and Mass Checks. Standard checks are your typical http/ssh/database checks. They check one server for one service and report the results. Multi-Protocol checks are somewhat of a “meta” check. They determine which service type should be used on a per-team basis based on a criteria defined in the service type. Mass checks check all teams at once, which is useful when integrating with an external scoring engine for specialty checks (i.e. SCADA).

Service Check Lifecycle

Registration

iscore.servicecheck.registry.ServiceRegistry.register(cls)
iscore.servicecheck.register(cls)

Service Types must be registered with the ServiceRegistry to be recognized by IScorE. Registration is done in services.py within a Django app that is listed in INSTALLED_APPS. The simplest way to register your type is to use the register() decorator.

Check Start

iscore.servicecheck.registry.ServiceType.start_check(service)

When a service scan runs, it spawns a thread per team (TeamWorker) which then goes on the spawn a thread per service check for that team (ServiceWorker). Before the team worker spawns the service worker for a check, it calls start_check(). This method is the last method called with the check that is allowed to directly access the database. Database access in a service worker is not allowed. In the base implementation, this method marks the check as in progress. If you override this method, you must call super(NameOfClass, self).start_check(check).

Pre-Check

iscore.servicecheck.registry.ServiceType.resolve_multi(service)
iscore.servicecheck.registry.ServiceType.pre_check(service)

During the pre-check phase, the service worker first attempts to resolve any multi-protocol service types. To do so, it calls resolve_multi() which it expects to return the short name of the check that should actually use, or the string "self", which is a special name meaning to use itself. In the base implementation, this method returns "self".

Warning

Returning the name of another multi-protocol check from resolve_multi is not supported. IScorE will simply attempt to use the multi-protocol check as if it were a standard check.

Once multi-protocol checks have been resolved, the worker calls pre_check(). Anything that needs to be done before the check (e.g. setting up storing state) should be done in this method.

Note

If the check is a mass check, resolve_multi gets the first service object and pre_check gets a list of all service objects.

Check

iscore.servicecheck.registry.ServiceType.do_check(service)
iscore.servicecheck.registry.ServiceType.do_mass_check(service)

do_check and do_mass_check perform the actually Standard and Mass checks respectively. Override whichever is appropriate for the type of check you are creating. Overriding the appropriate check method is required, the base implementation simply raises a NotImplemented exception.

To report the results of the scan back to the scoring engine, simply call report_score() with the service, score, and an error message if relevant. The error message should either be the error message returned when attempting the check, or a custom error message if it would be more helpful for the blue teams. Remember that this message is what is displayed on the service scanner page for failed scans. Some examples:

# Score of 5 with no errors
self.report_score(service, 5, None)

# Failed check with error
self.report_score(service, 0, "Connection timed-out")

The check is also expected to emit a log message detailing the status of the check. A helper method exists in order to standardize the messages to make them more parsable. Simply call log() to log a message in the standard format, to the scan logger. Some examples can be found below:

# <thread name>: FAIL TeamN : SSH login to <hostname>
self.log(service, "login", False)

# <thread name>: Success TeamN : HTTP connect to <hostname>
self.log(service, "connect", True)
iscore.servicecheck.registry.ServiceType.get_credentials(service)

If the check uses credentials (Team Specific info, Team Info Pools, explicit username and password) this method will return the username and password. By default, this method simply returns service.username and service.password. However, depending on the check it may be useful for this method to mutate the username or password to match what the service expects. For example, the LDAP service check mutates the username to follow the username@domain pattern.

Note

The Team Worker automatically sets service.username and service.password to the correct value if the service is configured to use Team Specific Info or Team Info Pools.

Post-Check

iscore.servicecheck.registry.ServiceType.post_check(service)

Immediately after the Check phase, the service worker calls post_check. Any cleanup that needs to be done after a check should be done in this method.

End Check

iscore.servicecheck.registry.ServiceType.end_check(service)

Run by the Team Worker to mark a check as finished. You should not need to override this, but if you do you must call super(NameOfClass, self).end_check(service).