A Windows 2000 or Windows NT service is a process that runs independently, regardless of whether a user is logged on. As a result, service security is important to overall network security. The choices you make as a programmer can have a strong impact on the security of your users’ networks. One of the most important decisions to make is the user context that a service will use. There are three options: LocalSystem, a local user account, and a domain user account. Each option has associated compromises, and you need to be careful when deciding which choice is right for your service.
Attackers can use services to compromise your network security in two ways. First, the service might have programming flaws that let attackers execute their own code. This type of attack might occur through buffer overruns or as a result of some capability of the service to execute local command-line applications. Numerous examples of both exist in the CERT archives or any good security mailing list. The second type of attack is more sophisticated and involves taking advantage of the distributed nature of authentication on some networks. This attack involves finding a service that is running as a high-level user on several systems and then locating one that the attacker can compromise. The attacker can then typically escalate privilege from that point. This type of attack is not limited to Windows networks and is not a flaw in OS design. Any network using a distributed authentication system (NIS would be a common example for a UNIX network) can encounter problems with this method of attack. Rather, these attacks occur when the network administrator is not aware of this issue and is not properly securing the network to prevent it. Windows networks give you several options in terms of service user context and how those options affect our ability to counter typical attacks on services.
Countering Service Attacks
A service running as LocalSystem runs under the context of a machine account. Under NT 4.0 and earlier, the machine account has little real scope at the network level. Win2K changes that, and the MACHINENAME\LocalSystem now has domain scope as an ordinary domain user that an administrator can add to an ACL. Running a service as LocalSystem is perhaps the most dangerous option from the standpoint of executing arbitrary code. A process running as LocalSystem has full access to the internal OS structures and enables some privileges that are not typically enabled, even for administrators. Services running as LocalSystem are considered to be part of the trusted code base (TCB), which means that they have the same level of implied trust as the OS itself. Any error on your part that might allow execution of arbitrary code can result in complete compromise of the system, especially for those users logging on as a service or locally. This type of attack can also compromise network user security to some extent. For example, an attacker might leverage an attack against one system to find passwords commonly used on several systems. If you believe your service ought to run as LocalSystem, a security-focused code review is essential.
However, because the OS does not store a password for a service running as LocalSystem (even under Win2K, the local machine account has very few rights on the typical network), running a service as LocalSystem is one of the safest from the standpoint of an attacker escalating privilege by compromising one system. If the attacker has already gained administrative control over a system, the attacker can now install his or her own services and drivers, and compromising a service running as LocalSystem gains the attacker little to nothing.
One problem that might prevent you from running a service as LocalSystem is that under all versions of NT prior to Win2K, the LocalSystem account has no network-level access. As a result, it is common to find services that run under domain accounts. If the domain account requires a high level of local access, it might be a member of the local administrator’s group. If the service has administrator rights to the local system, the effects of executing arbitrary code are the same locally as a service running as LocalSystem, and can now take action across the network. If an attacker compromises just one system running a service that you’ve configured this way, then the attacker has compromised any other system running the same service because the attacker can alter the OS to obtain that user’s credentials. Or, the attacker could simply replace the service with one that executes code the attacker wants to execute. If you must run a service under a domain account, try to run it under as low level an account as possible, both locally and across the network. Some services seem to need to run under Domain Admin accounts, and you need to manage the security of these hosts very carefully. I suggest avoiding this choice, but if you cannot, make sure your users understand the security implications. I became aware of one piece of software while working for a former employer that did not do this and installed a service running at Domain Admin level on all the systems on the network. Because the network administrators were unable to secure every system on the network against all the local users, the software compromised the entire domain just one day after its installation.
If the service does not require network access, you can run some services under a local account. Selecting a local account with limited rights is the safest approach. That way, if an attacker executes arbitrary code, the attack will be limited to only those resources that the local user controls. Also, if an attacker gains control of the local system, a service running under a local account gives the attacker no additional privileges.
Something else to consider is that when you install your service, you need to properly set permissions on the executable. A service running as LocalSystem that lets any local user change the binaries will result in an escalation-of-privilege attack. Also, if your users can configure your service to execute another binary or batch file, be sure that you've properly secured those files. This risk is a common user error with the UPS service. Services themselves also have an ACL, and if you use a custom set of permissions, be careful with users who can configure the service because they can replace it with one of their own.
As with many aspects of writing secure code, understanding the implications of the design choices you make can have a large impact on the security of your user’s networks. If you make your users aware of the trade-offs you have made, then they can understand how to properly deploy your applications.