Developers love Application Programming Interfaces (APIs) because they allow them to take advantage of services without having to build them. But do the developers who create the APIs feel the same way considering that the API exposes their service to potential mayhem from a security perspective?
I have worked with many vendors over the years and taken advantage of their APIs to integrate their services into my projects. Some are easy to integrate and others not so. In the end, the integration provides much value to my project. Furthermore, my skills and knowledge have increased through the trials of the integration.
In this case, I describe a lesson which was reiterated and reinforced throughout a specific integration effort. The OWASP Top Ten Risks Cheat Sheet documents the ten most critical application risks. Developers and projects should take this risk seriously. The realization of these risks might result in significant damage.
This case study explores one specific OWASP Top Ten risk.
OWASP A1 Injection
The injection risk is considered the highest by OWASP. Injection allows an attacker to compromise, destroy or control a system. Input validation and sanitization is one way to prevent it. OWASP recommends: "The preferred option is to use a safe API, which avoids the use of the interpreter entirely or provides a parameterized interface" [Top 10-2017 A1-Injection - OWASP].
Seems like common sense.
Why is this still the highest risk if it should be evident to developers to protect against it? Schedule and cost are likely the most prominent opponents of addressing this risk. Your software manager might tell the developer, "Just get it working because we need to release the product soon," or "We are running over budget. Make a note and secure it later." The developer might think, "It's secure enough. Plus we have firewalls and antivirus to protect against an intrusion." Whatever the reasons might be, this simple solution often gets overlooked.
How I was Using the Vendor API
I was working on a script to promote our vendor data from one stage to another stage in the pipeline. A pipeline is a set of environments configured to promote development code to production use. In our pipeline, we had a development environment, testing environment, and production environment. The scripts interfaced with the data on the development environment and created it in the testing environment. When all the tests passed, the scripts created the data in the production environment.
The API provided interfaces to log in, log out, retrieve data objects, create data objects, and delete data objects. The script workflow worked in the following way: log into one environment, retrieve the primary data objects, retrieve the child objects, log out, log into another environment, use the retrieved data to create the primary and child objects, and log out. Below is an example of how it worked.
#!/usr/bin/python
import vendor_api
import json
# log into the deveplopment environment
try:
vendor_api.login('username', 'password', 'development')
except:
exit(1)
# get primary object
# retrieves JSON structure
# {
# "uuid": "primary uuid value",
# "attribute1": "value1",
# "attribute2": "value2",
# "children": ["uuid1", "uuid2"]
# }
primary_object = json.loads(vendor_api.get(vendor_api.PRIMARY))
# get child objects one at a time
# retrieves JSON structure
# {
# "uuid": "child uuid value",
# "attribute1": "value1",
# "attribute2": "value2"
# }
child_objects = []
for x in range(0, len(primary_object['children']):
uuid = primary_object['children'][x]
child_object = json.loads(vendor_api.get(vendor_api.CHILD, uuid))
if "uuid" in child_object:
child_objects.append(child_object)
# log out
vendor_api.logout()
# log into the testing environment
try:
vendor_api.login('username', 'password', 'testing')
except:
exit(2)
# create primary object
# API documentation states the following required structure:
# {
# "attribute1": "value1",
# "attribute2": "value2"
# }
try:
vendor_api.post(vendor_api.PRIMARY, json.dumps(primary_object))
except:
exit(3)
# create child objects
# API documentation states the following required structure:
# {
# "attribute1": "value1",
# "attribute2": "value2"
# }
for x in range(0, len(child_objects)):
try:
vendor_api.post(vendor_api.CHILD, json.dumps(child_objects[x]))
except:
print("Failed to created child object {}".format(child_objects[x]['uuid']))
# log out
vendor_api.logout()
exit(0)
Concerns With the Use of the API
Do you notice something wrong here? (Ignore my lack of coding excellence.) Did you notice I did not delete the uuid
attribute from the primary_object
object and child_objects
objects, or I did not delete the children
attribute from the primary_object
? Why are these issues?
Let's assume the vendor had a strict API. When I passed uuid
and children
to vendor_api.post
for vendor_api.PRIMARY
object type, the vendor API should have returned an INVALID ATTRIBUTES
response. A looser API would have simply ignored them without an error response. An extremely loose API would have accepted them. An extremely loose API might accept an injection such as an rm -rf /
command.
How I Corrupted a Database with an Accidental Injection Attack
I was deep into testing, and my test scenarios kept failing. I could not understand why they were failing. I was confident I did my due diligent in the development environment; everything was working fine there. The same scenarios were failing in the testing environment. I eventually contacted the vendor to investigate what might be the issue. I needed the vendor's help since they provided limited debugging capability and needed to debug on their infrastructure.
Their first diagnosis: you are misusing the API. I explained my script logic over a phone call and emailed them my script. They admitted my script seemed fine.
Their second diagnosis: your test scenarios are incorrect. I explained my test scenarios over a phone call and emailed them my scenarios. They admitted my test scenarios were acceptable.
What Was the Source of the Issue?
They continued to investigate for a while and returned with their conclusion: I managed to create duplicate UUIDs across multiple environments in a system that required unique UUIDs globally. I inquired how this was possible. They admitted their API accepted all attributes and inputs for the child objects. They considered their primary objects the most critical information and took considerable efforts to secure that part of the API. Given cost and schedule, they felt it was less important to secure the child object portion of the API. Luckily, I was a partner in discovering this vulnerability before their API was widely adopted or a malicious agent attempted to exploit this finding. It took the vendor a few days to secure the API and fix all the environments.
A situation like this depicts why A1 Injection is still the highest OWASP security risk.
Before You Go
Join my mailing list to receive updates about my writing.
Visit miguelacallesmba.com/subscribe and sign up.
Stay secure,
Miguel
About the Author
Miguel is a Principal Security Engineer and is the author of the " Serverless Security " book. He has worked on multiple serverless projects as a developer and security engineer, contributed to open-source serverless projects, and worked on large military systems in various engineering roles.
Originally published on Secjuice.com
Image by Burnt Toast on Dribbble
Top comments (0)