Python CGI Project

Python Web Programming
Decided to use Python to serve webpages since I had not done this before. Here are some of the things I learned:

-Running subprocess and preventing bash injection
-Running a html form and preventing javascript injection
-Making password hashes
-Performing authentication

Subprocess and openssl
By using subprocess with as a list with arguments, it will prevent injection as bash commands.

# Run the openssl bash command, used to create hashes for passwords. Uses lists and arguments to prevent bash injection
def openssl(self, arg):
     output = subprocess.check_output(['openssl $0', arg], shell=True)
     return output

A htpasswd user can be created using this function like so:

username + ':' + self.openssl('passwd -apr1 ' + newpasswd)

You can perform password authentication by making a hash of the input password, using the salt of the user’s hash. Then simply compare the hashes.

# Creates a hash of a password or username, with a specific salt owned by a registered user
    def createhash(self, username, key, users):
        hashedkey = self.openssl("passwd -apr1 -salt " + users[username][0] + " " + key)
        hashedkey = hashedkey.split('$')[3]
        return hashedkey

Trac Password Script

The following is a script in progress, it is a user and administration script combined, which is used for changing passwords, creating users, deleting users for the Trac system. This is my first attempt at python web programming. All advice appreciated(especially about security).

#!/usr/bin/env python
# This script is a external trac administration website.
# It will allow a user to change their password, or an 
# admin to perform many administrative tasks, such as:
# adding users, modifying user settings, adding trac users
# to trac groups.
import cgi, subprocess, re, sys, os, time
import MySQLdb as mdb
from collections import defaultdict

class tracpassword:
    def __init__(self):
        # FieldStorage gets POST and GET and escape() is used to prevent javascript injection in fields
        form = cgi.FieldStorage()
        user = cgi.escape(form.getfirst('user', ''))
        newpasswd = cgi.escape(form.getfirst('newpasswd', ''))
        cnewpasswd = cgi.escape(form.getfirst('cnewpasswd', ''))
        button = cgi.escape(form.getfirst('button', ''))
        adminlist = cgi.escape(form.getfirst('adminlist', ''))
        passwdfile = "/etc/httpd/htpasswordfile" # Password file
        users = self.makedict(passwdfile)
        debug = ""
        username = os.environ['REMOTE_USER'] # Take user from htpasswd, basic auth login
        admins = self.getadmin('hostname', 'username', 'password', 'database') # Get list of admins

        # Check if user is admin
        if username in admins:
            if button == "Change Password":
                debug = self.changepasswd(user, newpasswd, cnewpasswd, passwdfile, button, False)
            elif button == "Add User":
                if self.checkuser(user, passwdfile):
                    debug = self.changepasswd(user, newpasswd, cnewpasswd, passwdfile, button, False)
            elif button == "Delete User":
                if not self.checkuser(user, passwdfile):
                    debug = self.changepasswd(user, newpasswd, cnewpasswd, passwdfile, button, True)
                else:
                    debug = "User does not exist"
            self.adminform(debug, username, adminlist)
        else:
            if button == "Change Password":
                debug = self.changepasswd(username, newpasswd, cnewpasswd, passwdfile, button, False)
            self.passwdform(debug, username)

    # Check if user exists 
    def checkuser(self, user, passwdfile):
        f = open(passwdfile)
        lines = f.readlines()
        f.close()
        for line in lines:
            if line.split(":")[0].strip() == user.strip():
                return False
        return True

    # Change the password inside the password file
    def changepasswd(self, username, newpasswd, cnewpasswd, passwdfile, button, delete):
        if delete:
            pass
        elif ' ' in newpasswd or ' ' in cnewpasswd or ' ' in username:
            return "Must not contain spaces"
        elif ':' in username:
            return "Username must not contain colons"
        elif newpasswd != cnewpasswd:
            return "Passwords do not match"
        elif len(newpasswd) < 8:
            return "Passwords must contain 8 or more characters"
        try:
            f = open(passwdfile)
            lines = f.readlines()
            f.close()
        except:
            exit("trac-password - cannot read file: " + passwdfile)
        # Write back all lines except the line that has changed
        try:
            f = open(passwdfile, 'w')
            for line in lines:
                if re.search('^' + username + ':.*$', line) or re.search('^$', line):
                    pass
                else:
                    f.write(line)
            # Recreate the user with new password and new salt
            if not delete:
                f.write(username + ':' + self.openssl('passwd -apr1 ' + newpasswd))
            f.close
            return "Action: " + button + " was successful"
        except:
            exit("trac-password - cannot read or write to file: " + passwdfile)

    # Run the openssl bash command, used to create hashes for passwords. Uses lists and arguments to prevent bash injection
    def openssl(self, arg):
         output = subprocess.check_output(['openssl $0', arg], shell=True)
         return output

    # Check which users are admin by searching the trac mysql database for users in "TRAC_ADMIN" group. 
    def getadmin(self, host, user, password, db):
        admins = []
        # Connect to database
        connection = mdb.connect(host, user, password, db)
        # Use the connection and send it queries
        with connection:
            q = connection.cursor()
            q.execute("SELECT * FROM permission")
            # Get all rows and find out which users are in the "TRAC_ADMIN" group
            rows = q.fetchall()
            for row in rows:
                if row[1] == "TRAC_ADMIN":
                   admins.append(row[0])
        return admins

    # Open htpassword file, generate and return a dictionary
    def makedict(self, passwdfile):
        try:
            passfile = open(passwdfile)
            users = passfile.read().split()
            for item in range(0,len(users)):
                users[item] = users[item].split(':')
                users[item][1] = users[item][1].split('$')
                users[item][1].pop(0)
                users[item][1].pop(0)
            users = dict(users)
            passfile.close
            return users
        except:
            exit("trac-password - cannot read file: " + passwdfile)

    # Creates a hash of a password or username, with a specific salt owned by a registered user
    def createhash(self, username, key, users):
        hashedkey = self.openssl("passwd -apr1 -salt " + users[username][0] + " " + key)
        hashedkey = hashedkey.split('$')[3]
        return hashedkey

    # Admin page
    def adminform(self, debug, username, selected):
        print "Content-Type: text/html"
        print ""
        print """\
<html>
  <head>
    <script type="text/javascript">
      function hide(){
        document.getElementById('Add User').style.display='none'
        document.getElementById('Change Password').style.display='none'
        document.getElementById('Delete User').style.display='none'
      }
      function put(){
        
        txt=document.forms[0].adminlist.options[document.forms[0].adminlist.selectedIndex].text
        if(txt == "Change Password"){
          hide()
          document.getElementById('Change Password').style.display='block'
        }
        else if(txt == "Add User"){
          hide()
          document.getElementById('Add User').style.display='block'
        }
        else if(txt == "Delete User"){
          hide()
          document.getElementById('Delete User').style.display='block'
        }
      }
    </script>
    <title>Trac Settings</title>
  </head>
  <body onload="hide()">
    <h2>Admin Settings</h2>
    <form method="post" action="">
      Select an Action:
      <select name="adminlist" id="adminlist" onchange="put()">
        <option></option>
        <option>Add User</option>
        <option>Delete User</option>
        <option>Change Password</option>
        <option>Add user to Group</option>
        <option>Remove user from Group</option>
        <option>Add Group</option>
        <option>Remove Group</option>
      </select>
      <br><br>
      <div id="Add User">
        <table>
          <tr><td><h2>Add User</h2></td>
          <tr><td><p>Username:</p></td><td><input type="text" name="user"></td></tr>
          <tr><td><p>Password(8 or more characters):</p></td><td><input type="password" name="newpasswd"></td></tr>
          <tr><td><p>Confirm Password:</p></td><td><input type="password" name="cnewpasswd"></td></tr>
          <tr><td><input type="submit" name="button" value="Add User" style="height: 25px; width: 175px"></td><td></td></tr>
        </table>
      </div>
      <div id="Change Password">
        <table>
          <tr><td><h2>Change Password</h2></td>
          <tr><td><p>Username:</p></td><td><input type="text" name="user"></td></tr>
          <tr><td><p>Password(8 or more characters):</p></td><td><input type="password" name="newpasswd"></td></tr>
          <tr><td><p>Confirm Password:</p></td><td><input type="password" name="cnewpasswd"></td></tr>
          <tr><td><p><input type="submit" name="button" value="Change Password" style="height: 25px; width: 175px"></td></tr>
        </table>
      </div>
      <div id="Delete User">
        <table>
          <tr><td><h2>Delete User</h2></td>
          <tr><td><p>Username:</p></td><td><input type="text" name="user"></td></tr>
          <tr><td><p><input type="submit" name="button" value="Delete User" style="height: 25px; width: 175px"></td></tr>
        </table>
      </div>
    </form>
    <p>%s</p>
  </body>
</html>"""% (debug)


    # Print the html code
    def passwdform(self, debug, username):
        print "Content-Type: text/html"
        print ""
        print """\
<html><body>
    <head><title>Trac Settings</title></head>
    <h2>Change Password</h2>
    <p>Username: %s</p>
    <form method="post" action="">
        <table border=0>
            <tr><td>New Password(min 6 digits):</td><td> <input type="password" name="newpasswd"></tr></td>
            <tr><td>Confirm New Password:</td><td> <input type="password" name="cnewpasswd"></tr></td>
            <tr><td><input type="submit" name="button" value="Change Password" style="height: 25px; width: 175px"></td>
        </table>
    </form>
    <p>%s</p>
</body></html>
        """ % (username, debug)

if __name__ == "__main__":
   tracpassword()
Advertisements

About oatleywillisa

Computer Networking Student
This entry was posted in SBR600 and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s