We have always done architecture work. In the past clients replaced their legacy systems with ‘new-fangled’ JavaEE. As they explored platform features, an ecosystem of web frameworks, and related commercial products (Netegrity’s SiteMinder). Realizing they needed help, they looked to us for:
Historically organizations wanted “one expert” and their ultimate goal usually aimed at something we might still call a “reference architecture”. They were building their own security API or securing a framework and would say, “You know JEE Security really well, ‘write us the book on how to do XXX’.”
Organizations no longer want one expert to “write the book”. They want five-to-ten smart architects to shepherd applications through upgrading design/implementation so assessments cease finding the same vulnerability over and over again. But, hiring this many sharp architects proves a real challenge. One technique has consistently made preparing individuals for this job easier for me. Step-wise:
This scheme decomposes a system in a dramatically different way than STRIDE (one of Microsoft’s 2+ threat modeling approaches). Whereas STRIDE proposes a set of conceptual attacks (such as spoofing or tampering) the above approach takes a software-centric stance. Just like STRIDE, this approach applied alone would not be thorough; one still must consider a system’s assets and risks. But, using this scheme you can make understanding security architecture of even large, unknown, or scary designs seem more simple. Let’s try it:
One of the most common patterns in play in a n-tier web system has historically been the MVC (or model view controller). So herein I decompose that pattern. As you’ll see, many security responsibilities fall on this pattern’s controller element.
Unlike some patterns the MVC’s elements are self-evident:
The figure above displays, in an n-tier architecture, what elements of the MVC pattern are and, to some extent, where they live. In the case of MVC, its elements are structural in nature. Behavioral patterns such as the chain of responsibility (which Spring’s interceptors implement) will decompose differently.
For this post’s remainder, I focus on the controller element. Though, when tearing an architecture apart, one must consider each pattern element.
What is MVC’s controller meant to do? The answer varies depending on the architect you speak to and this variance is reflected in each MVC framework’s implementation. Generically speaking, responsibilities break down as follows:
We could be more exacting in the functional responsibilities but we’ll find that the above suffices. Remember too, it’s not necessary to get things thoroughly correct the first pass through. Iterate and improve.
Taking as input the functional element responsibilities ask yourself, “What security impact might each of these have if abused?” If you can, think to yourself, “What does the user expect–from security–when this component does its job?”. Doing so, we come up with the following security responsibilities, by MVC element, as viewed from the general JavaEE perspective:
Summarizing, our controller must:
Again, at this point one should dig into the model and view in turn. The diagram above was built in ’05. Experience has taught us a lot since then and it’s probably painful to see view without more clear responsibility around output encoding and escaping. Likewise, model not being charged with canonical form may bother some.
Above we describe the general JavaEE MVC framework. Interestingly, the first responsibility (making sure a user can conduct an action or ‘authorization’), pre-supposes the system know with whom it’s dealing. Since the JavaEE platform didn’t tackle that problem prior to pushing developers into the MVC paradigm, it should shock none of us that a ecosystem of commercial URL-based authentication packages sprouted up: the market simply addressed an evidently necessary but missing responsibility.
Remember that other MVC frameworks represent other architects’ similar but slightly different take on the same pattern. So, in their cases, security responsibilities may slide from component X to component Y. Luckily, most framework documentation actively advertises its philosophy, design, and features. Often, “tutorial” and “hello world” example sites/documents quickly provide the curious with pointers to specific components that implement a pattern element.
Often, however, the hardest part of this process is TIMTOWDI. APIs, frameworks, and platforms evolve over time and typically accrete several ways (rarely deprecating) for applications to instantiate their structural elements and integrate against their security features. For instance, in Spring, elements of an application’s controller can be defined through text file, XML, class inheritance, method and/or class annotation, and convention. Tracking these down thoroughly (in a security assessment) can be challenging.
Knowing what the responsibilities need to be is a good start but it doesn’t help conduct a targeted SCR or developer interview. Key questions:
In essence, one must glue together #3 Security Responsibilities with #4 implementations of key design pattern elements. See the figure below:
In essence, a checklist exists in the diagram above at its right. The controller’s responsibility to understand a request’s authorization translates to AuthN and AuthZ. Note that each outlined controller responsibility is represented in the side bar.
Notice that the side bar includes common security concerns not within our stated controller responsibilities. You may want to check a pattern element’s responsibilities, as enumerated in section #3, against a more comprehensive list of security concerns to assure that you didn’t forget one. In the example above, the diagram colors particular concerns either 1) outside our controller’s responsibilities or that 2) I didn’t want to address more thoroughly in my first analysis pass. Again, iterate and improve rather than attempting to nail everything flawless the first time around.
So we’re done here?
No, not really. Even within MVC we could stand to give View and Model more thought.
Notice that we considered decidedly pedestrian functional elements of our application in order to see particular security concerns fully manifest. For instance, an organization-specific DispatcherServlet and DirectoryAdaptor may have profound effect on our first responsibility (AuthN/AuthZ). And, no organization with which I’ve dealt considers their home-rolled DispatcherServlet a security control. Likewise, when considering an organizations extensions to a framework’s FormController we’ll learn a lot about mandatory application of input processing and filtration.
“Technological progress is like an axe in the hands of a pathological criminal” – Albert Einstein
And, of course, MVC sounded a lot better on paper than it felt in code. Development has since shifted. As more dynamic languages have given rise to more dynamic application/UI frameworks they typically ‘push down’ application-layer controller logic. More recent web frameworks favor use of key conventions in application-level logic that direct a framework-layer controller otherwise ‘invisible’ to the developer. Frameworks such as Python’s Django, and Cocoa’s KVO/KVC pull this trick and JavaEE APIs like Spring‘s ModelAndView have followed suit. If the controller has been pushed down into the framework, it invalidates the decomposition I’ve written above and we have to reconsider our step-wise process coming up with a new assignment of responsibilities and checklist.
Pay particular attention to this “vertical” (up/down) dimension of the architecture. The layer in which a security concern manifests itself in an application implies things about how much control we have over vulnerability and mitigations. Below you’ll see another view on the same system:
From an application developer’s perspective, the further “down” in the diagram a security concern appears the more “invisible” or effort-free it is. Features the developer must add themselves must be 1) carefully considered for their implementation’s correctness, 2) correct configuration and use, and 3) thorough and consistent use. At first glance, developers prefer APIs, frameworks, and shared libraries because they only consider concerns #2 and #3. Their application may be automatic because of inheritance, the template pattern, or similar scheme. In these cases, the developer can skip concern #3. However, these situations assume the called component’s developer tackled #1: implementation correctness. If property #1 fails the developer they’re often hard-pressed to fix the situation as compared to that which they implemented in their code. Trade-offs.
Remember these properties as you write your checklist. Those responsibilities borne by developers must be checked for 1) implementation correctness, 2) correct configuration and use, and 3) consistent and thorough usage.
Responsibilities in APIs, frameworks, and shared services should be 1) audited once for correctness and, when used, 2) audited for appropriateness, 3) correct configuration and use, and if applied on a discretionary basis, 4) audited for consistent and thorough use. Platform properties and features require the same checks.
Hopefully, with a little practice, you’ll be able to go out and look at common architectures, identify their usage of patterns, attribute to them responsibilities, build a checklist for their correct implementation, and then decide how and where to harden them.