Programming Secure Web Applications in Python

01
of 10

What is Secure Programming?

Internet security
alengo/E+/Getty Images

Web programming innately involves programming for security. But programming for security does not require a siege mentality. As Bruce Schneier notes in Secrets and Lies(prices), attackers need find only one angle of attack; anyone who tries to secure systems, like programs, must secure everything. This is certainly true, but it means that the systems must be developed more intelligently.

When one knowingly programs unsecurely, one holds the mindset that the threat, if it exists, will effect someone else. The attack will not happen here. Popular notions of secure programming are the opposite: Imagine every attack happening here. After all, just because your paranoid does not mean they are not after you.

Programming securely lies somewhere in between. One must maximise control over one's sphere of influence while guarding against problems in one's sphere of concern. The ensuing discussion considers in turn the sphere of a Python programmers influence, his/her sphere of concern, and one common programming concern about which Python programmers need not worry.

Note: Each of these issues related specifically to web application development. Some of them may be transferrable to applications that run free of the Internet and a web server.

Several of the issues are inspired by
19 Deadly Sins of Software Security(prices), a highly-recommended read for anyone who writes programs of any sort.

02
of 10

Validate Input to Avoid Injections

Determine the precise parameters of input based upon string length, string content, and network characteristics (domain, hostname, port, as applicable).

Any program that interacts with an application outside itself can be exploited if the user-provided data is malformed. This can lead to two different kinds of injections. First, SQL-injections arise when malformed data is given to an application for insertion into a database; the malicious data contains SQL commands and thus runs with application-level privileges to corrupt, steal, or delete information. Second, any user-provided data that is not checked can be used to pass malicious parameters or commands to the operating sytem, the Python interpreter, or both.

In this regard, any Python system call is suspect because it interacts with the operating system. Python methods of which one should be particularly careful are:

  • exec()
  • eval()
  • os.system()
  • os.popen()
  • execfile()
  • input()
  • compile()
03
of 10

Broken Access Controls

Obviously, OS-based file permissions are beyond the control of the programmer. However, it is encumbent upon the developer to open each file with the permissions needed and to close it as soon after using it as practicable. While this security policy must obviously be weighed against runtime efficiency, the best way to do this is to cluster one's file access as much as possible -- using periodic caching of small files or snippets of larger files in RAM as necessary.

04
of 10

Broken Authentication and Session Management

One should pass as little data in the URL as possible. URLs which contain identifying information or other user data are sometimes called "Magic URLs". With the right incantation, anyone can access the same session as you (or the intended user) can. There are system-level tricks that one can use to thwart this (e.g., checking for concurrent logins from sufficiently disparate IP addresses). These, however, do little to prevent unauthorised logins at times when the intended user is not logged in.

If one does not pass information in the URL, one is usually left to pass information in the background, using a predefined format. The problem here is that anyone who figures out the protocol, format, and server address can then emulate a session and attack the server.

Ideally, one should encrypt sensitive data before transmitting it, ensuring that the encryption algorithm is strong enough for the task. Python's crypto module is helpful to affect this when combined with strong random numbers.

Note that Python's random and whrandom modules are not sufficiently strong for cryptographic use. To get cryptographically strong random numbers on a Unix or Linux machine, you are best to read from /dev/urandom:

 n = open('/dev/urandom') 
 data = n.read(128) 
05
of 10

Cross Site Scripting (XSS)

A cross-site scripting exploit works like this:

  1. An attacker identifies a web application that echoes user-provided data in its URLs (e.g., a search string).
  2. The attacker then forms a URL that includes HTML (not necessarily with a header) and some in-line script like Javascript or PHP.
  3. The attacker then, by hook or by crook, gets a victim to click on the link. This can be on a web page or an email -- anything that is HTML-based.
  4. When the victim clicks the link, the victim's browser send a GET request with the malformed HTML-Script combination to the vulnerable application.
  5. When the web application echoes the user data, the victim's browser reads the URL, along with the HTML, and executes the script contained therein. The scripting can be used for just about anything - user cookies, modifying web links, pilfering passwords, etc.

All of this is possible because the web application receives user data, does not check the input, and echoes it back in a URL. Naturally, this feeds back into the first issue, unvalidated input. If, however, the input field must be able to handle code, be sure to replace any instances of metacharacters with their escaped version and handle the string with Python's verbatim mode as much as reasonably possible.

06
of 10

Proper Error Handling

Errors live at the point where the computer is unsure of what to do. If the errors are left unchecked, the program either becomes unstable or terminates. If it is unstable, an attacker can exploit the lack of certainty and provide commands to be followed. If the application terminates prematurely, however, it may leave sensitive information open for exploitation.

Errors indicate the overall health of the executing process. It is imperatival to handle exceptions intelligently. All errors are not critical. Some can be allowed to pass silently (but explicitly!). It is therefore important to understand the health indicators of the executing process and to handle as critical those errors that are.

One need not declare every critical error to the user. Rather, it is often best to keep a logfile. If the error is sufficiently critical, one can simply scuttle the program, giving a polite, but not necessarily informative, error message to the user. Remember that any information you give a user in an error message can be used by an attacker to break into your program. Best to save critical information for a logfile.

07
of 10

Insecure Storage

Up to this point, we have looked at what you as a programmer can do to ensure your program does not compromise user data -- whether by accident or by malevolence. These are the things you can control. Now, we begin to look at aspects of your program's runtime environment that you cannot control but about which you should be concerned.

With the exception of cookies, there is very little that a Python-based web application can do on the client's harddrive. Therefore, storage largely becomes a matter of securing the harddrive of the server -- a job for your network manager.

That being said, you can use a built-in cryptographic function to ensure the security of the stored data. Take care in using this, however. If you do not compile your program, the Python source code will be plainly readable.

08
of 10

Denial of Service

DoS attacks are infrastructure-based and can only be mitigated adequately by firewalls, quotas, and other network-administration implementations. Packet inspection and such is the job of your network administrator(s). However, you can check whether and how many instances of a Python program are running, aborting the current instance if the number is too high.

To do this on Windows, borrow from Bill Bell's class at the Python Cookbook:

 from win32event import CreateMutex 
 from win32api import CloseHandle, GetLastError 
 from winerror import ERROR_ALREADY_EXISTS 
 
 class singleinstance: 
 """ Limits application to single instance """ 
 
 def __init__(self): 
 self.mutexname = "testmutex_{D0E858DF-985E-4907-B7FB-8D732C3FC3B9}" 
 self.mutex = CreateMutex(None, False, self.mutexname) 
 self.lasterror = GetLastError() 
 
 def aleradyrunning(self): 
 return (self.lasterror == ERROR_ALREADY_EXISTS) 
 
 def __del__(self): 
 if self.mutex: 
 CloseHandle(self.mutex) 
 
 
 

If you are on a Unix-based system, you are best off using the os module to access tools like ps and to process that information to determine the pids of the various processes. As mentioned above, you can then abort the current instance if too many processes are already active.

09
of 10

Configuration Management

As with anything else related to the execution environment on the server, configuration issues are largely determined by the network policies. If, however, you use session IDs and saved configuration files, you will want to guard against their corruption or malicious redaction by securing your web application within parameters discussed earlier in this series.

If this sounds like Magic URLs redivivus, it's not. It is a matter of ensuring your session IDs timeout soon enough to avoid malicious copying but not so soon that it negatively impacts usability.

If one uses session IDs alone, getting this down is a fine art that can easily end up in frustrating the user and causing them to try to circumvent your security implementation. However, if one logs information about one's visitors separately from the web log, one can allow longer session times based upon identifiers such as IP address, browser type, and the like. These are certainly not fool proof, but they go a long way to securing your application.

10
of 10

Buffer Overflows

Buffer overflows arise when the memory alotted to an array or to a process is exceeded, allowing the source of the overflow to reset parameters of execution. This issue is of much greater concern for languages that do not allow dynamic memory management.

Because Python's memory is dynamically allocated, overflows are exceedingly unlikely -- one would need to exploit a wider vulnerability in Python itself. Such vulnerabilities have been found. Nevertheless, they are not something over which the Python programmer has control -- unless one is also the network or system manager. One can thus speak of overflows as being of little or no concern.

Outside of exploiting vulnerabilities, should an attacker attempt an overflow, the worst that is likely to happen is for the application to run out of RAM and a memory-related error to be thrown by the interpreter. Therefore, while memory handling is not an issue, proper error-handling is an imperative.

Format
mla apa chicago
Your Citation
Lukaszewski, Al. "Programming Secure Web Applications in Python." ThoughtCo, Feb. 18, 2016, thoughtco.com/programming-secure-web-applications-2813531. Lukaszewski, Al. (2016, February 18). Programming Secure Web Applications in Python. Retrieved from https://www.thoughtco.com/programming-secure-web-applications-2813531 Lukaszewski, Al. "Programming Secure Web Applications in Python." ThoughtCo. https://www.thoughtco.com/programming-secure-web-applications-2813531 (accessed November 24, 2017).