We Need To Talk About API Security
Tags: codequality, security, rest
Published: 2020-10-14 03:06:12

The first thing we need to establish is that I am **_NOT_** any kind of "security expert".  If you've been programming for long enough, you can't help but be exposed to security issues.  Solid programmers should always be thinking about potential security loopholes.  And eventually, you touch enough security-related bugs that you end up thinking that you _know_ something about the field.

But the simple fact is that security is its own unique field in IT.  For good reason.  There's sooooo much to learn with regard to security that it's truly its own discipline.  You can't be, say, the best React dev _and_ the best security expert.  At some point... you have to choose.

So, with those caveats out of the way, I still think we need to have a little "security chat".  Because I've seen some things, in just the last year-or-two of dev, that have made me really nervous.  And those scary things have been centered on APIs.
<br/><br/>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/wg2n1geed7avwagge32v.jpg)
## Not Always A Net-Good
If you've ever been tasked with integrating with some third-party system, the first thing you probably asked was, "Where's the API?"  In the worst-case scenario, there _was no_ API.  But far too often, there was an API... sorta.  There were a few exposed endpoints / methods / whatever.  There were a few ways to do basic tasks.  But when you reached the point of trying to build _true_ integration, all too often, the "API" was sorely lacking.

For this reason, APIs usually fall into three distinct categories:
<br/>

1. There is **_NO_** API.  And we curse the programmers who originally created this monstrosity.
1. There is... _some_ sorta API.  It allows you to do _some_ things.  But it also provides no interface for most of the critical features we need to implement.  And we curse the programmers who originally created this monstrosity.
1. There is a true, "full" API.  Most-or-all of the features we must implement are already accounted for in the API.  And we rejoice in the streets.

<br/>But I'm here to tell you:  Option #3 isn't always the panacea that it appears to be on the surface.  We get so frustrated by systems that lack APIs, or by systems with insufficient APIs, that we assume anything resembling a "full" API must be, unquestionably, an undeniable _good_.
<br/><br/>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/67ok28xbxgn40bbsxrrm.jpg)
## Common Pitfalls
I've run into some pitfalls lately that make me very nervous about the systems that our institutions are putting into place.  I'm gonna reiterate that I'm **_NOT_** a security expert.  And I know that security experts could probably extend this article by 10,000 words (or more).  But even from my quaint little perch as someone who's "just" a programmer, here are some of the headaches that I've been battling recently:
<br/>

**Manual Updating of Derived Values**
Let's imagine that you're writing a football ("soccer", for us Americans) management system.  And in that system, you can track a goalkeeper's _save rate_.  The "save rate" _should_ be a simple formula that's _derived_ from `saves / shotsOnGoal`.  

But then imagine that you've created the following endpoint in your API:

`PUT /v1/player/{playerId}`

And finally, let's assume that this endpoint accepts a `player` object _that includes the 'save rate' value_.  Do you see the problem with that logic??  `save rate` shouldn't be _manually_ set.  It should be _calculated_, based on that player's total number of saves, divided by that player's total shots on goal.  But I've seen too many APIs where I'm given an endpoint that allows me to update "save rate" _directly_.

Maybe that's a bit too esoteric (or farcical) for you.  So allow me to give you a more real-life example:

Last year, I did security consulting for a bank - a bank with _physical_ locations - all throughout Texas.  I was specifically helping them with their _physical access_ system.  In other words, I was working on the software that would determine when a particular door was open, or who was allowed to go through that door.  I think you'll agree with me that such things are quite important _in a bank_, right??

I was working with a vendor's software that allowed you to create an infinite number of rules that would, ultimately, determine whether a door was open at a given time, or whether a given person was allowed through at that time.  But the problem I ran into was that, no matter how much time I spent crafting those rules, there was still a REST endpoint that would manually override **_ALL_** of those rules with a simple POST/PUT.  Even if every rule in the system said, "LOCK THIS DOOR!!!", you could still do a `PUT` to the proper endpoint that would _unlock_ that door, disregarding every other rule in the system.

For some, the knee-jerk reaction is to say:
<br/>

> Well... If you had the required API _key_ to update that door's status, then you _should_ be able to unlock that door, whenever you want.  

<br/>But this (simplistic) mindset overlooks the fact that the vendor's base system was intricately designed to _force_ you to manage all of the appropriate rules.  Except... you could bypass the whole system if you just managed to get ahold of the right key.

Many programmers would scoff at such a "weakness".  They'd claim that it's not _their_ problem if the key falls into the wrong hands.

But I assure you that the client (the bank in Texas) was not satisfied with such an answer.  In fact, I _illustrated_ for them how I could snag their API key - _in a mere matter of hours_ - and proceed to open every door in every one of their locations.  Suffice it to say that they were not thrilled.

The point here is not that you can't expose critical functions in your API.  But if some value should only ever be _calculated_ through a separate series of rules, then don't expose a method that will allow any-old-programmer to set that value manually, irrespective of those rules.
<br/>

**API Users With God Rights**
I wish I was joking - but I've seen _this_ scenario too many times:  The application has an intricate set of roles / rights / permissions defined on the front end.  But if you manage to get an API key, that key _always_ comes with **_god rights_**.  

Think about that.  Your app creates finely-delineated rights _on the front end_.  But once someone gets an API key to interact with it _on the back end_, they can do whatever the hell they want.

I'm not trying to claim that you should never allow an API user with "god rights".  But that should never be _the default_.  And if the app provides fine-grained user controls on the front end, you should almost always be accounting for the same thing on the back end.

I saw this recently with a hardware compliance management system.  The client had a very pressing need to illustrate that _all_ of their users were compliant with government regulations.  But their API afforded only **one** access method - access via "god rights".  This meant that anyone tasked with _building_ the system also had all of the rights they needed to hide their own non-compliance (if they so chose).  
<br/>

**Leaky Interfaces**
I also recently found a scenario where the API seemed to be fairly "robust" - except...

By using the API, I was able to infer critical details _about the underlying database_ that stored the API's values.  In other words, accessing the API gave me the critical info that I needed to hack _directly into the database_.  

Obviously, if you've given me the data I need to crack your underlying data store, then any "security" you slap onto the API layer is rather pointless.  With this data in-hand, I actually illustrated for a client how I could change all of their base data without ever officially _hacking_ the API itself. 
<br/><br/>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ukk18vzxqyz65udcda33.jpg)
##Endless Holes
I know that this is just the tip of the security iceberg.  And I know that real "security types" could add sooooo much more to such an article.  But I wanted to point out a few of these horrific (and silly) flaws because it seems that I've been running into them with ever-greater frequency in the last year-or-two.

IMHO, there's a common theme that API developers tend to view the API as just an extension of the application's _internal_ logic.  

They wouldn't create a form field in the app's UI that allowed the user to manually set the value for `saveRate`, irrespective of the values for `saves` and `shotsOnGoal`.  But they'll create a REST endpoint that allows you to `PUT` a manual value for `saveRate` - even if it doesn't mesh with the other values.  

They wouldn't expose their database schema on the frontend.  But they'll create a REST endpoint that basically represents a full schema diagram - complete with credentials that match those of the API user.

Too often, we design APIs to be inherently _trusting_ of the API user - because the API user is a "technical" user.  In other words, the API user is more like "us" - the programmers - than like the end users.  But this scenario shouldn't make us more _trusting_.  It should make us more _wary_.