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, status, 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 parsa ble. 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)
If you access to the get_credentials()
method you should handle
any exceptions that it will raise. This method will raise an error if no team specific information is not defined/setup
correctly.
- 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.
If you use this method be sure to catch exceptions from this method. This method will throw an exception if the pool is
empty.
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)
.