Security Briefs

Threat Models Improve Your Security Process

Michael Howard

Contents

Threat Modeling
Code Review and Fuzz Testing Priority
Fuzz Testing
Attack Surface Reduction
Reducing Privileges
What Does It All Mean?

This column proposes a way to think about secure design from a more holistic perspective by using threat models to drive your security engineering process, primarily helping you prioritize code review, fuzz testing, and attack surface analysis tasks.

As a setup for this column, you might want to first read Jeremy Dallman's "'Crawling' Toward SDL" post, which can be found on the Security Development Lifecycle (SDL) blog. You will notice that the concepts in this column map very well onto Jeremy's ideas and will allow you to add more structure and precision to your security efforts as you learn to crawl, then walk, and finally run with the SDL.

What I am proposing is using the threat model to help drive other SDL security requirements, primarily code review priority, fuzz testing priority, and attack surface reduction. That's all there is to it. Of course, I need to explain myself, so let's look at each in a little detail. Let me start with threat modeling.

Threat Modeling

Many MSDN Magazine readers are familiar with the concept of threat modeling. Adam Shostack wrote an excellent article on the subject in the July 2008 issue ("Reinvigorate your Threat Modeling Process") and offers a glimpse of the future of the threat modeling process in more depth on the SDL blog.

In my opinion, when people think of security vulnerabilities, most think of implementation bugs. One could argue that the SDL is focused a little too much on implementation bugs, and historically it was because most of the vulnerabilities Microsoft has fixed resulted from implementation bugs. But over the last couple of years we have moved more resources to focus on secure design, in part because the implementation bugs are now more scarce thanks to the SDL.

Threat modeling is a cornerstone of the SDL because it allows a development team to think about secure design in a structured way. The threat modeling process can be effectively simplified into the following tasks:

  • Draw a picture of your software's data flows.
  • Use the "STRIDE per element" approach to find threats that apply to the design.
  • Address each threat.
  • Verify that you've modeled enough of the software, considered each threat, and addressed all the threats you discovered.

A core element of a threat model is the delineation of application entry points. The threat model captures entry points as trust boundaries during the "picture-drawing" phase. Examples include networking entry points, file and registry entry points, and so on.

A good threat model should also capture the network accessibility and authentication/authorization requirements of the interfaces. This includes network accessibility through IP address (local and remote, local subnet, and local-only access). It also includes authentication and authorization levels, anonymous access, user access, and administrator-only access. In the case of Windows access control lists (ACLs), authorization levels are finer-grained, but there is no need to go too deep (for a deeper discussion of ACLs, check out John Michener's article, "Understanding Windows File And Registry Permissions", in this issue. Keep it simple.

Another critical piece of data captured by the threat model is process identity. An entry point is simply an interface to a piece of code running in a process, and high-privilege processes are very dangerous if compromised. In Windows, the highest privilege processes are those running as SYSTEM or administrator. In Linux or Mac OS X, processes running as root are the most privileged.

Each app entry point should record the following data:

  • Entry point name
  • Network accessibility
  • Authentication and authorization
  • Process name and process identity
  • Comments (if needed)

Figure 1 shows a few examples. Clearly, the first network port is the most exposed because it is exposed to anyone on the planet. The most exposed entry points are the entry points at greatest risk of attack because something that is remotely accessible can be accessed by anyone with a computer on the same network—in this case, probably the Internet. Something that is accessible by anonymous users is accessible by anyone with no authentication or authorization. So an entry point that is accessible anonymously and remotely can be accessed by absolutely anyone, and that includes an awful lot of bad guys.

Figure 1 Process Entry Points

Entry Point Network Accessibility Authentication and Authorization Process Comment
TCP/1234 Remote Anonymous App.exe (SYSTEM) Main request/response
\\.\pipe\admin Remote Admin App.exe (SYSTEM) Administrative named pipe
ncacn_ip_tcp 609b954b-4fb6-11d1-9971-00c04fbbb345 Remote Authenticated Users User.exe (Network Service) User's account information RPC interface
Config.xml Local Admin App.exe (SYSTEM) Application configuration file

The administrative named pipe is accessible only to remote (and local) administrators, which is correctly enforced using an ACL. It's important that you verify the ACLs on all Windows named objects, including named pipes.

You will also notice that many entry points are located in code running as SYSTEM. The combination of an unauthenticated, remotely accessible entry point and a process running as SYSTEM is the largest possible exposure you can have in Windows. If there is a security bug in this code, anyone can make it fail from anywhere on the planet. And exploited code would run as SYSTEM, meaning the exploit has total control of the computer. I'll cover more of this topic later in the column.

The degree of exposure helps drive code review and fuzz testing priority. That's discussed next.

Code Review and Fuzz Testing Priority

Think about this for a moment; you have a lot of code to review and test. But you cannot spend every waking moment for the next 10 years reviewing code because, at some point, you have to ship a product to customers. What this means is that you must prioritize your security efforts; therefore, you must give the riskiest components your first and deepest efforts (I recommend that you read the "Who Is the Bad Guy" sidebar for some tips on finding the riskiest components). This means reviewing and fuzzing the riskiest code first.

When reviewing code by hand, it is important to review the most exposed code first. For example, the TCP/1234 socket example might have code like that shown in Figure 2. Because the call to recv reads from an unauthenticated and remote network port, buf should be treated as toxic waste until it is verified. In security circles, this data is often considered tainted and should not be relied upon for any accuracy or structure.

Figure 2 A Potential Problem Buffer

SOCKET  remoteSocket = accept(listenSocket,NULL, NULL);
if (remoteSocket == INVALID_SOCKET) {
  PRINTERROR("accept()");
  closesocket(listenSocket);
  return;
}

// Receive data from the client
char szBuf[256];
memset(buf, 0, sizeof(buf));
nRet = recv(remoteSocket,  
      buf,  // Danger!
      sizeof(buf),          
      0);              

if (nRet == INVALID_SOCKET) {
  PRINTERROR("recv()");
  closesocket(listenSocket);
  closesocket(remoteSocket);
  return;
}

// process data in buf

To make things a little easier during code review, create a table that includes the source code location and API call for the entry point as well as the data (see Figure 3). Now follow the data from the entry point API to the code that verifies or sanitizes the incoming data. If you have no code that verifies the data integrity and your code performs potentially dangerous operations on the incoming data, then you might have a coding bug at best and a severe vulnerability at worst. Regardless of the code behavior, you should never assume the data is well formed.

Figure 3 Analyzing Entry Points

Entry Point Source File API and Data
TCP/1234 Readfromsocket.cpp recv(,buf,)
\\.\pipe\admin AdminConsole.cpp ReadFile(,bCommand)
ncacn_ip_tcp 609b954b-4fb6-11d1-9971-00c04fbbb345 UserInfo_i.c and UserInfo.idl Various
Config.xml Config.cpp ReadFile(,bFileContents)

Fuzz Testing

Look back at that last line: "never assume the data is well formed." The goal of fuzz testing is to determine whether you do make assumptions and, if so, whether someone can cause your application to fail by violating those assumptions. I am not going to explain fuzz testing here because it has been covered numerous times in MSDN Magazine (for instance, see the article "Fuzz Testing: Create a Custom Test Interface Provider for Team System" by Dan Griffin, but I will take a look at how you could be using fuzz testing.

Just like code review, you cannot fuzz test every possible code path, so you need to prioritize by exposure. Therefore, you should fuzz the most exposed entry points first. In my little example, I would fuzz test the TCP/1234 entry point brutally, early, and often. In fact, I would never stop fuzzing that entry point! CPU time is pretty cheap, after all.

I would go one step further and ask myself, why is this interface so highly exposed? Can I reduce the exposure by default? And that is the art of attack surface reduction.

Attack Surface Reduction

I have said this a million times before, and I'll say it again: attack surface reduction is as important as trying to get the code right because you'll never get the code right. I explained this mantra in more detail in my November 2004 article, "Attack Surface: Mitigate Security Risks by Minimizing the Code You Expose to Untrusted Users". I'll give you the short version here: you should reduce attack surface in order to reduce the potential risk to your customers. After all, an interface that is exposed to all attackers is a greater risk than an interface exposed only to attackers on your local subnet simply because there are fewer potential attackers in the second scenario.

Looking at my example, I would make the TCP/1234 entry point authenticated by default and allow only authenticated users access to it. This could be as simple as using Basic authentication (yes, I know it's a lousy authentication scheme) or something more complex, such as Kerberos. This would be my default, and if the system administrator wants to change it to anonymous access, then so be it. But that's a decision the administrator should make.

It's important to point out that if I do add authentication to the application, then the threat model needs to be updated because there is now an authentication process and an authentication credential database somewhere in the system.

Who Is the Bad Guy?

Here is the golden rule of security code review: always understand what the bad guy controls. A corollary to this is, Who is the bad guy? Who the bad guy is depends on the entry point's level of authentication and authorization.

In the case of the first TCP port example given in Figure 1, the bad guy is absolutely anyone on the planet. In the case of the second entry point, the named pipe, the bad guy isn't really a bad guy because he or she is a member of the local administrators group. If you can't trust your administrators, who can you trust?

Given that you have a finite amount of time, which interface should you code review earliest and deepest? Clearly, it's the first interface because anyone on the planet can access it. If you have a security bug in that code, anyone can compromise it. To really hammer the point home, think of a worm: worms rely on unauthenticated and remote networking interfaces.

Reducing Privileges

Another component of attack surface reduction is to reduce application privileges, and that's what I want to discuss next. Remember the processes that run as SYSTEM? Well, you need to think about using lower-privileged accounts to reduce the potential damage from a compromise. This is another important part of attack surface reduction.

Start with the most exposed entry points. In this case, if the decision is made to allow anonymous access to TCP/1234 by default, then you should ascertain why the process listening on that port needs to run as SYSTEM. Can it be reduced to a lower privileged account?

Also notice that the two network entry points and one file entry point all run in the same process. Is that really appropriate? Two entry points are for administrators, and one is for anonymous users. You should consider breaking this process up into two distinct processes so that you can take advantage of lower-privileged process identities.

If you look at the IIS 6.0 and IIS 7.0 architectures, you will notice that the processes handling anonymous and remote user requests (w3wp.exe) runs as Network Service, and the main administrative process (inetinfo.exe) runs as SYSTEM. The latter process is limited to administrators. In IIS 4.0 and IIS 5.0, the process handling and the administrative access were often performed by one high-privileged process.

Interestingly, a couple of aspects of attack surface reduction have been known for a long time. They are part of the classic Saltzer and Schroeder secure design principles and include least-privilege and least-common mechanism.

That second concept, least-common mechanism, needs a little explanation. It means you should reduce the amount of code shared by more than one user or user type. In my example, a single process performing administrative tasks and anonymous user requests can be dangerous. The remediation is to follow what IIS did and split the application into two processes that perform distinct tasks as separate identities.

What Does It All Mean?

To be absolutely honest, this column doesn't define anything new. It's all in the SDL! But the column explains the implicit relationship among the many security disciplines we define, describe and require in the SDL and how a good threat model can help streamline much of the rest of the security work required to ship more secure products to your customers.

Build a good threat model, determine your exposure, use a list of entry points (ranked by exposure) to help drive code review and fuzz testing priority, and finally drive your attack surface down. This will lead to more secure software.

Are you interested in finding out just how much you know about security? Take the challenging "Security IQ Test" found in this issue.

Send your questions and comments to briefs@microsoft.com.

Michael Howard is a Principal Security Program Manager at Microsoft focusing on secure process improvement and best practice. He is the coauthor of many security books including Writing Secure Code for Windows Vista, The Security Development Lifecycle, Writing Secure Code, and 19 Deadly Sins of Software Security.