Applying Basic Trust Concepts to Software Module Design

In the January/February 2009 issue of IEEE S&P, Schneider and Birman talk about “trust attacks” in a good article discussing IT Monoculture risk. They write:

Networked systems admit the possibility of trust attacks. In them, one computer satisfies a request from another because it trusts the source of the request, but in fact the source has already been compromised by an attacker [1].

As a consultant who reviews code a lot, this statement immediately got me thinking about trust at the code-level. Trust issues are fun to think about — not only because there are bookoos of trust-related issues in today’s apps (particularly mashups) but also because they are difficult to mitigate through good defensive design. As a software design enthusiast, I believe we can apply the concept of trust attacks as described above to low-level module design (think the Class keyword in Java or class keyword is C++) by changing one word in the second sentence:

[...] one module satisfies a request from another because it trusts the source of the request, but in fact the source has already been compromised by an attacker.

Consider a J2EE webapp built on top of Struts — a framework made up of view objects (JSP pages), a controller that processes requests by executing appropriate actions, and model elements in the form of database access objects (DAOs) that often call stored procedures. Putting all of these class-level entities into a diagram and attempting to model data flow (even the slightest hint of data flow) will likely illuminate a list of potential trust-related concerns from client-to-server or server-to-server interaction.

Although such an activity is a struggle to undertake the first time (everything is always more difficult the first go round), thinking about trust at the module level is an important exercise to undertake and one that I believe is often neglected due to pressure to “get features working”. That’s a shame.

Why is thinking about module design important? As a designer (or developer if you sit in both seats), why should I burn cycles thinking so deeply about trust at the class-level? Because mid-level design is arguably at the lowest level of abstraction where the idea of trust in an application makes sense.

An authorization check example

Continuing with the J2EE example, a team of designers are debating where to implement a new authorization check for access to critical data, such as a file that is stored as a BLOB in the DB. It’s a simple check to make sure the authenticated user has permission to download or update a requested file; if the check passes, the file record is accessed for use. Questions arise as there are a number of different design alternatives for positioning such an authorization check:

  • Should the check be placed in a custom Struts RequestProcessor?
  • Should the check be placed in a custom J2EE filter?
  • Should the check be placed in a base Action class that all other “important” Actions extend?
  • Should the check be placed in specific DAOs?
  • Should the check be placed in stored procedures?

It’s all about trade-offs here, and there’s really no one size fits all answer. You could perform the check in one place, in two places, or (most defensively but inefficient) in all places. The point of this post is to get you thinking about how this design decision affects how modules ultimately (and often times implicitly) trust one another and how that trust can be violated. Let’s assume the team decided to issue the check in a custom base Struts action that’s associated with “/downloadfile.do” action mapping, whose call graph looks something like:

downloadAction.execute
	-> super.performAuthChecks
		-> authorizeDownload(user, file)
	-> daoGetFile(file)
		-> storedProcGetFile(file)
	-> forwardRequest

In this implementation, downloadAction considers the “source” of the request to be a client (or user). Its responsibility is to issue an authorization check and then invoke a DAO to retrieve the file. The daoGetFile(file) module considers the “source” of the request to be downloadAction — the entity that supplied data to it by invoking one of its methods. Don’t confuse this way of thinking with the fact that the user submitted the original request; our goal is to focus on how entities within our software architecture interact and hand data off to one another. Here, daoGetFile trusts downloadAction in the sense that it blindly assumes downloadAction correctly authorized the initial request. Likewise, storedProcGetFile trusts daoGetFile and assumes all up-stream authorization has previously been performed. In such a perfect and simplistic world, there isn’t an obvious problem with this design decision. But…

Potential trust attacks

(1) So what about the legacy Java servlet (that’s still available) that directly calls daoGetFile(file) to download files prior to the move to Struts? Does it perform the same authorization check that’s in the base Struts action? In this case, daoGetFile(file) incorrectly assumes the servlet performs all authorization checks and provides direct access to file data — uh oh. Perhaps it makes more sense to place the authorize check in the DAO itself or some other middle-ware tier that interacts with DAOs (like a security monitor)?

(2) If SQL injection vulnerabilities exist in the application, can attackers inject SQL into queries to manipulate the DB tables containing the files; by say, changing the table that maps user permissions to file records? This is very interesting attack vector to consider because if data integrity is compromised, the protection mechanisms put in place will work exactly as advertised, except that it’s performing checks on compromised data.

(3) Are there any other DAOs that directly call daoGetFile(file)? If so, can attackers force this method to be executed by exploiting race conditions?

The need for guidance surrounding module trust concerns

The three attacks listed above are just a few examples. Yet, I think it demonstrates the importance of carefully thinking about module dependencies, connections, and information flow. I thought about this a great deal in grad school when I attempted to create a taxonomy of design principles and map secure coding heuristics to them. To get a feel for the taxonomy, here’s the mapping for a heuristic for executing DB queries using parameterized statements:

  • Continuous Protection of Information (CPoI)

    • –> Defense in Depth (DiD)

      • –> Trust Boundaries (TB)

        • –> Reluctance to Trust (RtT)

          • RtT.1 Assume input data is maliciously crafted.

            • RtT.1.3 Keep input data and control information separate.

              • RtT.1.3.a Make use of data value placeholders in parameterized statements when executing database queries using java.sql.PreparedStatement.

For more details about the taxonomy and design-driven approach, I invite you to read about it here. I think we’ll be in a much better position when all developers apply a security-aware mindset when writing every line of code, perhaps by keeping these kinds of principles in mind.

[1] IEEE Security and Privacy, “The Monoculture Risk Put into Context,” F. B. Schneider and K. P. Birman, January/February 2009, pg. 15.

Post a Comment

Your email is never published nor shared. Required fields are marked *