In this blog post we will talk about writing secure code. Am I pretending to always write secure code?… Heck no! I am lazy just like the rest of us :-). That being said, there are a few things that one should be aware of when writing code (even when it is sample code). I believe that you can still write your simple samples. However, if you know at least what vulnerabilities your code might have then you can write that down in your README. Even the famous Chinese philosopher Confucius knew this back around 530 BC:
“To know what you know and what you do not know,
that is true knowledge.” – Confucius
Should I be worried about my hello_world.py script?
No. Again, I am not preaching that you should always go the full mile for some code you have written to test out an API. However, I have listed 5 common Python mistakes that can cause serious vulnerabilities in production applications. Please be mindful of these and try to avoid them as much as you can! Also, these coding mistakes can obviously also happen in other programming languages as well, so this does not just apply to Python.
Python Security Trap 1: Arbitrary Code Execution
What is it?
Arbitrary Code Execution is an attacker’s ability to run any commands or code on a target machine or in a target process. This is most common in Python and occurs in many types such as command injection, SQL injection, and more. It arises from user inputs that are being directly passed in a standard Python function. The lack of input sanitization is usually the reason.
# -*- coding: utf-8 -*- # example code snippet py_vuln00: Arbitrary Code Execution: compute_user_input = input('\nType something here to compute: ') if not compute_user_input: print ("No input") else: print ("Result: ", eval(compute_user_input)) # 2*2 # __import__("os").system("ls") # __import__('os').system('rm –rf /')
How can you solve it?
Always sanitize and validate user inputs first before passing them to the system commands. By using the `ast` Python module you can do so. The Python module `shlex` can also help to automatically escape user input.
You can use shlex to correctly parse a command string into an array and to correctly sanitize input as a command-line parameter.
The ast module helps Python applications to process trees of the Python abstract syntax grammar. You can use this to parse and then validate the user input. Here is an example if how to solve it using `ast`.
Python Security Trap 2: Directory Traversal Attack
What is it?
A Directory Traversal Attack is also caused by improper user input validation. This can lead to sensitive files to be exposed and even to remote code execution. It arises if the path of file access by Python script is not properly checked. An attacker can manipulate the file path for example to something like /etc/passwd…
# -*- coding: utf-8 -*- import os file_location = input('\nType location: ') # /Users/christophervandermade/../area51.txt # open and read file #file = open(file_location, "r") #print(file.read())
How can you solve it?
This can be solved by Sanitizing the user input using for example “shlex”. Also, you can maintain an allowed list of files that the user can access or set up a static safe directory. Try to avoid the usage of direct paths. You can also use the `os.path.realpath` function to clean up a path and return its canonical form (absolute path):
# -*- coding: utf-8 -*- import os file_location = input('\nType location: ') # /Users/christophervandermade/../area51.txt print(f"File path before: {file_location}\n") real_path = os.path.realpath(file_location) print(f"File path before: {real_path}") # open and read file #file = open(file_location, "r") #print(file.read())
Python Security Trap 3: Outdated Libraries
What is it?
Simply put, a Python library is code written by others, which can be easily imported into your script. Code is written by humans, humans make mistakes and mistakes get patched (hopefully). Unfortunately, we often forget to update (and test!) our code with those patches, making it vulnerable for attacks.
requests==2.19.0
As example Python library, the Requests package (who doesn’t use this one?) before 2.20.0 for Python sends an HTTP Authorization header to an http URI upon receiving a same-hostname https-to-http redirect, which makes it easier for remote attackers to discover credentials by sniffing the network.
How can you solve it?
This vulnerability can be fixed by updating (and testing!) all the packages for which updates are available. (DUH!)
You can also use tools to help with this after the fact:
- Static application security testing (SAST) – static test that happens without executing the code.
- Dynamic application security testing (DAST) – dynamic tests that happen by executing code paths.
- Interactive application security testing (IAST) – Agents and sensors are run to continually analyze the application workings during automated testing.
- Runtime application self-protection (RASP) – The big advantage of RASP is that it might also be able to catch (and block) exploits of vulnerabilities that have arisen after the application was deployed. An example RASP is Cisco AppDynamics with Secure Application, please see the last section of this blog post for more details).
Python Security Trap 4: Incomplete Assertions
What is it?
This vulnerability happens when Python assertions are used to evaluate a condition, such as Boolean expressions. If the condition is true, the execution moves to the following line. Otherwise, it will show an error. The `assert` keyword should normally be used when debugging code.
# -*- coding: utf-8 -*- # set variable to assert: var_to_assert = "hello" # if condition returns True, then nothing happens: assert var_to_assert == "hello" # if condition returns False, AssertionError is raised (comment out to test the next one): #assert var_to_assert == "goodbye" # if condition returns False, custom AssertionError is raised: assert var_to_assert == "goodbye", f"var_to_assert should be '{var_to_assert}'" # run like this to disable assert statements: python3 -O py_vuln03.py print("When you run code with -O, assert statements are skipped...")
How can you solve it?
Do NOT use Python assertions for logic, use if-else logic for Boolean conditions. In production, assertions might be disabled, so only use assertions in testing environments. Python assertions are not an error-handling tool, they are a debugging tool, please use them as such.
Python Security Trap 5: Broken Access Control
What is it?
Broken access control describes the exploitation of access control management by attackers and bad actors. This vulnerability was actually moved to OWASP10 spot #1 from #5. A stunning 94% of apps were tested for some form of broken access control.
Some examples:
- Manual app state modification: These modifications could be URL modification, browser cookies and sessions, or the use of custom API attack tools.
- Key identifier change: This allows the alteration of key identifiers, like the user’s primary key, in such a way that gives unwanted access to another user to perform actions otherwise unauthorized.
- Privilege escalation: This is a known method of attack where an attacker logs into a business database as an administrator. This attack can take the form of acting as an authenticated user without authentication.
Example snippet
If we look at the following authentication URL we can see the parameters that are being passed:
https://example.com/accounts/details?id=123&access_key=abcdefg&access_secret=opensecret
An attacker may change the URL parameters such as the ID, ACCESS_KEY, and ACCESS_SECRET to anything malicious, giving them access to account information. Via this attack sensitive information might be leaked or altered.
How can you solve it?
Validation and verification of requests should always be in place. Role-based permissions and object-level permissions should also be implemented, so that authorization can be verified between the authorized user and the requested object resource. Below is a simple example of such validation and verification where a check is done to see if the User ID of the request matches the User ID of the account:
def update_details(request, acc_id): user = Account.objects.get(acc=acc_id) if request.user.id == user.id: # ALLOW ACTION # VALIDATE REQUEST DATA form = AccountForm(instance=user,request=request) ... else: # DENY ACTION
Developers vs. Security: Friends or foes?
Sometimes developers and the security team do not really vibe. This might result from the fact that they have somewhat of conflicting interests. Developers might be focused on creating useful features (a.s.a.p.) and only collaborates with security teams during investigations, remediations, and changes to vulnerable code. Security teams (e.g. SecOps and/or AppSec) might be focused more on ensuring developers write secure software and use secure dependencies. They might also create security guardrails through training, testing, tooling, and pipeline integration. They will also investigate events that could be security incidents or breaches.
To sum this up a developer wants to create new lines of code to create features as fast as possible, where the security teams want them to be diligent and secure. How can we make these teams collaborate better?
Cisco AppDynamics with Secure Application
Cisco might be able to help out with this conflict of interest. Unfortunately, it cannot solve it completely, however it can help to relieve some of the friction.
This tool can detect application code dependency and configuration-level security vulnerabilities in production with automatic runtime protection. It will continuously monitor vulnerabilities to find and even block exploits automatically, maximizing speed and uptime while minimizing risk. As earlier mentioned, Cisco Secure Application is a Runtime Application Self-Protection (RASP) solution for modern applications by defending against attacks to prevent breaches. Most importantly, it simplifies the life cycle of vulnerability fixes by giving both developers and security teams a common interface to work with. A small note: at the time of writing this blog post, Cisco Secure Application only works for Java AppDynamics agent, however the support is being built out to the rest of the agents as we speak.
You made it to the end of this blog post! Thanks! As a reward, I have some more info for you to check out:
- OWASP10
- Secure Code Warrior
- Cisco Secure Application
- Cisco App-First Security landing page
- Sample python scripts
- Cisco Security Dev Center
We’d love to hear what you think. Ask a question or leave a comment below.
And stay connected with Cisco DevNet on social!
LinkedIn | Twitter @CiscoDevNet | Facebook | Developer Video Channel
Great write-up. Thanks.