This month, a day-zero Flash exploit was disclosed, exposing potentially millions of users’ data. After an emergency patch was rushed out, two more exploits were quickly discovered, leading to a vocal demand for an end-of-life date for Flash.
This isn’t just a problem that affects media outlets and entertainment sites — vulnerabilities from services such as Flash and other dependencies commonly used in the production, use, and implementation of APIs are often subject to the same day-zero exploits and disclosures as the beleaguered Adobe Flash platform. Today, we’re going to discuss the nature of security in the modern dependency-centric design methodology, the importance of versioning, and some basic steps developers can take to secure their product throughout the API Lifecycle.
Short History of Dependency-Centric Design Architecture
Unauthorized access to resources on a network is as old as networking itself — in 1976 and 1978, for example, John Draper (aka Captain Crunch) was indicted for using a cereal-box toy whistle to generate tones for free calling around the world. Vulnerabilities in phone systems, cable television systems, and early internet-based subscription systems have long been the focus of skilled, intelligent, and persistent hackers.
In the old days, hacking into systems was a difficult prospect — once systems began to move away from tone generation (as in the phone system) and basic unencrypted passwords and usernames, gaining access to these systems became more difficult. Larger connected systems soon began to implement proprietary security solutions, managing Authentication and Authorization using internal services behind a secure connection to the outside world.
As the concept of user-oriented services such as payment processing, online shopping, and internet media providers began to take hold, however, a certain amount of standardization was required. With the burgeoning World Wide Web taking the world by storm on the standardized TCP/IP protocol stack, developers soon began to create systems that they could tie into their own code for further functionality.
The code created with these programs depended on the standardized programs to function — hence the term dependencies. Shockwave, Flash, and other such systems enabled developers to stop “re-inventing the wheel” each time they created a new product, and assured compatibility between disparate systems.
This shift in architecture design has created many of the problems API developers face today — the change from internal design to dependency-centric design has created a situation where dependency vulnerabilities are causing increased security concerns.
The Hotfix — Versioning
Seeing the issue with dependencies and the constant need to update and revise, developers soon implemented a solution known as versioning. With versioning, an API or service developer can elect to use the most up-to-date version, beta versions, and even old versions of a dependency for their code.
While this was largely meant for compatibility, allowing services to utilize older dependencies on machines without access or permissions to utilize newer dependencies, this also created a security situation where developers of the dependencies could ensure security by pushing out patches to fix vulnerabilities identified through testing and real-world use cases. While this made development simpler for the developers, it came with its own set of caveats.
Within the context of modern API development, versioning has two basic approaches, each with strengths and weaknesses.
The First Approach — Update and then Review
The first and most common approach is to Update and Review as dependency versions are released. By taking this approach, an API developer will immediately install updated dependencies or systems, and then review the update through changelogs and real-life use. This type of versioning often ignores beta or test releases, and focuses on stable releases.
While this seems like a good approach, there are some huge weaknesses. By not first looking at the code of the newest patch for the dependency, an API developer is placing blind faith in the dependency developer. This shifts responsibility from personal to external, creating a situation where the security of your ecosystem is placed squarely on the shoulders of someone without a vested interest in keeping your system safe.
Furthermore, this approach could have an inverse impact — by installing an untested or unreviewed version, an API developer could potentially open themselves up to more exploits than the previous unpatched dependency. For many, this is simply a caveat too big to ignore — it’s easier to fight a battle with an enemy you know than to wage a war with untold enemies you don’t.
The Second Approach — Review and then Update
The second and less common approach is to Review and Update. Unlike the first approach, this necessitates the developer first reviews the code of the dependency update, comparing it to previously known code. The developer installs only when entirely convinced of the update’s veracity and completeness.
This might seem counter-intuitive — “increase your security by not installing new updates”. The truth is that you dramatically increase your security by using this approach simply due to the fact that you will know what you are fighting.
Let’s say you use a dependency such as Flash for the web portal for your API. You know a set of exploits exist that can force a buffer overflow. Knowing this, what is the best solution — update this dependency to a new version with unproven, unreviewed code, or handle the buffer overflow with a prevention system and self-patch while you review the new update?
Simply put, an API developer should review each and every piece of code before implementation — or at the very least, compare it to the already existent code to ensure compatibility, security, and safety amongst a wide variety of their use cases.
Dependency Implementation Steps: EIT
So what then is the proper process an API developer should take to ensure that something like the recent day-zero Flash exploits don’t occur? This process can be summed up in three simple letters: EIT.
- E — Examine. Examine the code of the dependency patch you are intending to install. Check it against previous patches — has the code changed dramatically? If so, do you understand how it has changed? Use changelogs and bug trackers to examine issues with new patches. Use these resources to dig deep into how the dependency functions and why the patch had to be created.
- I — Implement. Once you have thoroughly examined the code of the dependency update, the dependency should be implemented. Ensure that compatibility between security systems and the updated dependency is maintained.
- T — Test. Test your API. Use penetration testing tools, try and trigger buffer overflows, and send un-validated calls and requests to see how your API responds. If you trigger any failures in the new patch, add your result and methods to duplicate to the bug tracker for your dependency after repairing your ecosystem to ensure it doesn’t continue to be an issue.
By following this set of guidelines, an API developer can ensure that their API dependencies and complete ecosystem functions as intended, ensuring long-term security and functionality throughout patching.
Dependencies are a wonderful thing — to be able to quickly create, implement, and patch, API developers must use a certain amount of dependencies. When these dependencies inevitably are exposed, a trend towards blaming the dependency developer quickly blooms.
This is a poor response — the fact of the matter is that the vulnerability in the API is not the fault of the dependency developer, but the fault of the API developer who adopted that dependency. Security is solely the domain of the developer — shifting responsibility to the user or to another developer is simply bad practice, and will lead to a less secure ecosystem.
Though the recent exploits are focused on Flash, this is not just an issue exclusive to platforms, either — vulnerabilities in methodologies and workarounds in code such as Scala, Go, and other such languages can lead to buffer overflows, memory leaks, and poor encryption. Issues within the cloud computing stack can expose huge security flaws. Even lack of effective encryption could result in the complete exposure of network resources which may betray your security ecosystem.
The takeaway? Check your dependencies, and check your code — security for your API is solely your responsibility.