BLOG

Python Best Practices | Reduce Network Vulnerabilities with Avi

Sandeep Yadav
Posted on Sep 23, 2016 11:14:39 AM

At Avi Networks, scalability, security, automation, and self-service are part of our core objectives to develop a world-class product that stands up to the requirements of the most demanding production environments. As with any service exposed to the Internet, network attacks exploiting vulnerabilities can put proxied assets at an enormous risk. Such risks include but are not limited to the attacker taking full control of the victim network, accessing intellectual property, taking over resident hosts as zombies Distributed Denial of Service (DDoS) attacks, and more.

Avi Vantage is architected from the ground up with security in mind. As an example, we are constantly looking at ways to limit exploitation possibilities arising out of vulnerable Python library calls. The risks are created when invoking system commands by the shell using the Python module subprocess.

The use of shell to exploit systems is well-documented. Below we present our contribution through a Python module which helps invoke relatively complex multi-piped commands using subprocess' Popen() without passing the argument shell=True.

import shlex
import subprocess

class PyExecCmd(object):
    """
    Helper class to run a complex command through Python subprocess
    """
    def __init__(self):
        return

    def exec_cmd(self, cmdstr, *args, **kwargs):
        """ *Safely* execute the command passed as the string using 
Popen invocation without shell=True. The command may contain
multiple piped commands. Returns the <stdout> and <stderr> of
executing the command. Args: @param cmdstr: type string Returns: tuple """ allcmds = cmdstr.split('|') numcmds = len(allcmds) popen_objs = [] for i in range(numcmds): scmd = shlex.split(allcmds[i]) stdin = None if i == 0 else popen_objs[i-1].stdout stderr = subprocess.STDOUT if i < (numcmds - 1) else subprocess.PIPE thiscmd_p = subprocess.Popen(scmd, stdin=stdin, stdout=subprocess.PIPE, stderr=stderr, *args, **kwargs) if i != 0: popen_objs[i-1].stdout.close() popen_objs.append(thiscmd_p) # Collect output from the final command (cmdout, cmderr) = popen_objs[-1].communicate() # Set return codes for i in range(len(popen_objs) - 1): popen_objs[i].wait() # Now check if any command failed for i in range(numcmds): if popen_objs[i].returncode: raise subprocess.CalledProcessError(popen_objs[i].returncode, allcmds[i]) # All commands succeeded return (cmdout, cmderr)

From the code above, we see that the function exec_cmd() recursively reads the output from the current process, and writes it to the input file descriptor of the next process. Failure in any of the called processes results in the function generating an CalledProcessError exception, similar to as subprocess' check_call() and check_output() would generate.

Let's take an example which uses the class PyExecCmd. We assume having a file named file2.txt in the current working directory and we want to display the contents of the file.

On the Linux terminal:

$ cat file2.txt
This is the expected output.

The following is an excerpt from the Python terminal:

>>> p = PyExecCmd()

>>> cmd = "echo file1.txt | tr '1' '2' | xargs -I FILE cat FILE"

>>> out, err = p.exec_cmd(cmd)

('This is the expected output.\n', '')

The class PyExecCmd can now be used to invoke complex piped commands without ever invoking the shell.

Tweet me @sandeepvaday or comment here if you have similar suggestions or questions!

Topics: python, python best practices, Shell, Security

   
New Call-to-action

Subscribe to Email Updates

Recent Posts

Posts by Topic

see all