Wednesday, October 12, 2005

broad security considerations for web apps

Never reveal the engine's implementation.

Stack traces and internal errors echoed to the front-facing part of a web application can reveal vulnerabilities in underlying implementations. This is by no means advocacy of error suppression, as proper diagnostics are invaluable for debugging purposes. Ideally, error types should be mapped to a user-friendly dictionary that allows the user to describe his or her situation to support staff, while the stack trace slips quietly out the back into the error log where it belongs. Applications should be designed to cost more effort to bubble stack traces to their user-facing sides than a friendly error message.

Limit the dataset exposed to the templating layer.

With modern web MVC frameworks, it is typically very easy to provide fairly large subsets of the data structures and objects from within the application's space to the templating mechanism. In some systems, it is possible to provide live executable objects to the templating layer, thereby allowing layout designers to easily muddle logic and presentation, as well as inadvertently (or possibly deliberately) yield aspects of the application's internal state.

Decouple the authentication mechanism from the primary application.

When a web application is decoupled from its authentication mechanism, it enjoys two major benefits: First, developers do not have to invoke authentication (however it may be done) on a per-resource basis, and can develop as if their application is already authenticated. Second, both frameworks can be tested independently of each other, allowing for more efficient and effective testing.

Organize authenticated resources by URI path segments.

Stand-alone authentication modules are easiest to organize by URI path. Choosing an initial path segment like /auth or /protected, underneath which all resources are covered by the authentication module, will emphasize the logical division between regular and logged-in resources. Authorization modules can then be bound to subpaths under these segments.

Never trust input.

Input traditionally can come from three different sources: query string, form data and cookies. A common practice is to merge these into a single dictionary of key-value pairs and pass them into the application as parameters. Under no circumstances should it be possible to access this dictionary without it having been sanitized. Each entry in the dictionary should be checked against an expected set of keys. Entries present that are not expected should be at least removed, or better, produce an error. This should occur before validation of the values. Following this, the values can be checked against their respective validating functions, as well as each other, in the case of interdependencies. It is only now that the application developer should be able to retrieve these parameters.

Never trust input (Part II).

With the advent of web services, arbitrarily nested XML content (or any other content, for that matter) can come in via a POST or PUT request. XML content should be first inspected for encoding consistency. Ideally, the content's encoding should be converted into one's preferred Unicode format before it is parsed (suggest UTF-8). More ideally, this should happen in a separate process space, in case of an attempt to compromise. Any illegal character sequences should produce an error. Following this, the document should be parsed and checked for well-formedness. Lack of compliance should produce an error. Finally, the document should be checked against a DTD, XML Schema or RelaxNG content model. Anything not validating should produce an error. All of this should happen before the content is received by the application layer.

Never trust input (Part III).

It is extremely unwise to provide a user with data of any kind (cookie, hidden form field) with the expectation of retrieving it unmodified. Data passed to the user should only be done so when it is within the user's interest to keep it intact (e.g. session, or target resource). Ideally, the capacity for a web application developer to produce a dependency on a piece of user data should require greater effort than interacting with a central state mechanism.

Conclusion

Developers will inherently follow the path of least resistance to complete a project. If not of their own volition, they will be pressed to do so. Creating an environment that requires less effort to do the right (read: secure) thing will yield an overall more stable product with few to no embarrassing compromises.