Software Security

A brief introduction to software security.

The software is an integral part of our lives, but time and time again, we hear on the news about data breaches. The frequency of such breaches seems to increase on a regular basis as well as the scale and impact of them. This may lead some people to think that software protection isn’t taken seriously. However, in my experience, there seem to be other reasons for insecure software. In this post, I will attempt to explain my experiences regarding software defense. While the reasons for insecure software are endless, a few categories seem to come to mind. Let’s walk through some of the more common ones and see if we can figure out the reasons for insecure software.

Iron Triangle

1920px-Project-triangle-en.svg

Every software project has three constraints that determine how much work can be done on the system. Those constraints are:

  • Scope
  • Resources (Cost)
  • Time

Scope refers to the work that is going to be done on the project. A project that has a large scope will require more work and conversely, a project that has a smaller scope requires less work. Resources are materials, money, people, and other inputs that are needed in order to develop a project. It is related to scope in the sense that more scope will require more resources, but keep in mind that inefficient project management can also lead to resources being wasted as well. Finally, there is time. Every project has deadlines and eventually the customer will want the deliverables.

All three of these resources are not finite. For example, you can ask for more time and resources, and likewise, the customer may wish to increase the scope of the project. However, this usually is a request for more features, not protection. Ensuring that system safety is something that everyone tends to pay lip service too, but until someone has actually experienced an incident, they are more likely to think of it as an afterthought rather than adopt a security first mentality.

Safety is a nonfunctional requirement and it requires time, resources, and scope to implement it properly. Hence, the iron triangle tends to get in the way of defense. It is often difficult to quantify the value of software assurance to stakeholders and thus, it can generally be seen as an uphill battle to encourage stakeholders to pay for it. Unlike features, protection isn’t something that users tend to see. A user simply expects safety to be present in the software. This leads to our next issue when it comes to creating safe IT systems.

Lack of Awareness

Security-And-Privacy

Management, users, and developers generally lack a proper understanding of secure IT systems, and this can lead to data breaches, denial of service, or other issues that impact the confidentiality, availability, and integrity of the system. While there are many reasons for this, a lack of security professionals in the workforce is certainly a problem. According to ISC2, there is a shortage of 3 million cybersecurity workers.

When we work with security aware people, we are more likely to become more aware of cybersecurity ourselves. However, a lack of cybersecurity people leads to a lack of voice at the table. For example, if management is planning out a system, they may not fully appreciate what is required in order to make a fully secured system unless there is somebody present to explain the cost, requirements, needs, and people resources that are needed to make a safe IT system.

Likewise, developers are under constant pressure to bring working code to the customer, but again, may not have the time, resources, training, or experience in order to make sure that they are producing a robust IT system. A lack of exposure to safety experts hinders a developer’s exposure to security and increases a lack of awareness. Project deadlines imposed by management may lead to developers skipping protection altogether in order to produce features for the customer. While many developers will acknowledge the importance of security, they rarely have a chance to learn about secure coding practices or even tend to overly rely on third-party libraries for safety.

Users are also a problem when it comes to cybersecurity. Many users simply do not follow safe IT practices. For example, users are constantly told not to use the same password for multiple websites yet many users do this on a regular basis. Web browsers will normally warn people not to browse to a site that has a certificate configuration issue, yet this is another thing that people are known to do. Finally, many people aren’t even aware that they should not connect to public WIFI hot spots without using a VPN. All of this leads to problems that can create information leakages.

There may not even be good engineering solutions to these problems. For example, when I write a website for a client, I will often download a list of known leaked passwords. Hackers love to publish such lists on the internet since they can be used in dictionary attacks. By using such a list myself, I can create code that prevents a user from using such a password and hopefully prevent brute force attacks. The problem is that they violate Psychological Acceptability because the user may be trying to use a password that conforms with the password requirements but still isn’t acceptable because it’s in the leaked password list. It can also create an illusion of defense since the password blacklist needs to be updated on a regular basis.

Of course, there are endless examples of a lack of safety awareness. The point is that such a lack of awareness impacts the quality of an IT system since there is a lack of knowledge as to how to secure a system. When project managers, developers, and users lack the expertise to secure a system, it will inevitably result in an IT system that is weak. Training and practice are the antidotes to such problems. The more that we train and expose people to secure IT practices, the stronger our systems will become.

Lack of Security Culture

2009-12-12-08.21

Lack of culture can certainly be related to a lack of awareness, but it can also come from attitudes and values in the organization. An organization will promote a safe IT culture when protection is brought up in meetings and acted upon. Unfortunately, many organizations lack the leadership that is necessary to build strong and safe systems and this results in weak systems.

An organization can look at software protection as a forethought or as an afterthought. In other words, they can be proactive or reactive. While common sense may dictate that we should be proactive, the reality is that many organizations tend to react to an incident. There are several (and this is non-exhaustive) reasons for this.

Attackers Strike Anytime

An attacker of a system has the luxury of being to strike at will at any time. The defender of a system has to be on guard twenty-four hours a day, seven days a weak. Most of an attacker’s time is spent in reconnaissance, which means that they are exploring the system and looking for weaknesses. Attackers have a variety of tools that they can use such as dumpster diving, social engineering, or using scripts.

Ultimately, it is the attacker that gets to decide when to conduct an attack and often times, the attack isn’t discovered until after it is complete and the damage is done. A good attacker will even cover their tracks by manipulating logs or masquerading as legitimate users so that they can keep coming back. While organizations can take preventative action to limit such an attack, the reality is that complete protection is utopian and eventually an attack will succeed. This will lead to a reactive approach to defense.

Cost

Securing a software system has a cost associated with it and the cost is generally seen as overhead. Preventative costs such as penetration testing, red team / blue team exercises, and phishing simulations may be seen as too expensive or unnecessary. Many managers are conditioned to believe that shareholder value is the only stakeholder that matters in an organization and may disregard anything that doesn’t maximize shareholder value. Furthermore, a lack of penalties and enforcement from the government may mean that managers disregard IT protection since a data breach may only impact users and not the manager.

In other words, managers may not see the benefits of safety as outweighing the risks. The cost of prevention is generally known upfront since you can easily request a quote from a penetration testing organization. However, the cost of a breach is generally known until after it occurs. This can cause management to become reluctant to pay for prevention and may lead to them taking a risk instead.

Lack of Expertise

A lack of expertise goes hand in hand with a lack of awareness that was discussed above. However, if we don’t have people in the organization that is trained in cybersecurity, then chances are high that we won’t have a safety culture either. Without training expertise, an organization will not know how to promote a safety culture in the first place, which leads to a reactive stance when it comes to addressing incidents.

What to do about it?

Of course, the above methods are not exhaustive by any means. There are real hurdles that need to be overcome in order to have an organization adopt a security-first mindset. However, there are a few things that can certainly help to produce software that is more secure. The first one is a commitment to protection.

When it comes to making a commitment to defense, it means that the organization has to be committed to producing truly secure software. This starts at the highest levels of leadership by setting an example. Senior management must take the time to educate themselves about IT security and understand what it means to be a secure organization. They must also include safety awareness and training as part of the interview process or training process in order to ensure that staff is trained in security practices. This may mean a change in recruiting and hiring practices.

It also means that a security policy is continually evaluated to ensure that it is up to date, works for the organization, and is acted upon. The U.S. government, Microsoft, and other large organizations often have publicly available models to follow, so it’s not as if an organization needs to start from the beginning. For example, OWASP has the SAMM project that is available for anyone who needs information on how to get started. You can also consider hiring consultants or investing in training for employees also.

Practice is also important. While having an incident response plan is important, it also just as important to go through the plan. A plan is simply a piece of paper until it is acted upon and in the event of an incident, people may not have time to read and understand what is expected of them. This is why proper preparation and planning is important.

Upfront security planning will also help to improve the security of software. For example, it’s important for an organization to conduct threat modeling, attack surface analysis, and security planning. This will help developers understand that is needed for them in order to create a safe and robust system and it will also improve security awareness and culture in the organization.

Follow through is critical as well. An organization must always be checking their work for security flaws. This can be achieved using techniques such as internal and external security audits, red hat / black hat exercises, and penetration testing. An organization can also conduct simulated social engineering attacks as well. Adding any such steps to the software engineering processes is bound to improve the security of the system and make the IT world a better and safer place.

Sources

“The iron triangle of planning”, Tareq Aljaber

“Cybersecurity Skills Shortage Soars, Nearing 3 Million”, ISC2 Management

“Dictionary Attack”, Wikipedia

Psychological Acceptability, Michael Gegick and Sean Barnum

Advertisements

Python Color Chooser

The tkinter library in Python comes with an askcolor function that will pull up the system’s color picker dialog. It’s really easy to use and it returns a tuple with a RGB value and a hexadecimal value. This makes it really easy for anyone who is working with colors to ask the user for a color.

from tkinter import *
from tkinter.colorchooser import askcolor


class Window(Frame):
    def __init__(self, master=None, cnf={}, **kw):
        super().__init__(master, cnf, **kw)
        self.open = Button(self, text='Pick a color', command=self.pick_a_color)
        self.exit = Button(self, text='Exit', command=self.quit)

        for b in (self.open, self.exit):
            b.pack(side=LEFT, expand=YES, fill=BOTH)
        self.pack()

    def pick_a_color(self):
        print(askcolor(parent=self, title='Pick a color'))


if __name__ == '__main__':
    win = Window(Tk())
    win.mainloop()

askcolor

The askcolor function simply shows the system’s ask color dialog window. Therefore, it will adjust to the user’s platform and look natural. You can add the parent and title arguments if you want but otherwise the defaults work just as well.

Calling the function only requires one line of code.

askcolor(parent=self, title='Pick a color')

When the user picks a color, a tuple is returned with the rgb values and the hexadecimal values.

((255.99609375, 170.6640625, 104.40625), '#ffaa68')

You will get this result if they click on cancel.

(None, None)

Notice that it is a tuple of None.

Python File Dialog

Many Python applications need to ask the user if they want to a open a file or folder or save a file. The tkinter library has built in dialog functions for this exact purpose. Here is an example of how to use the askopenfilename, asksaveasfile, and askdirectory functions with some common configurations.

from tkinter import *
from pathlib import Path
from tkinter.filedialog import askopenfilename, asksaveasfile, askdirectory


class Window(Frame):
    def __init__(self, master=None, cnf={}, **kw):
        super().__init__(master, cnf, **kw)
        self.open = Button(self, text='Open', command=self.open_file)
        self.save = Button(self, text='Save', command=self.save_file)
        self.ask_dir = Button(self, text='Folder', command=self.ask_folder)
        self.exit = Button(self, text='Exit', command=self.quit)

        for b in (self.open, self.save, self.ask_dir, self.exit):
            b.pack(side=LEFT, fill=BOTH)

        self.pack()


    def open_file(self):
        file = askopenfilename(filetypes=(("Python files", "*.py"),
                                           ("All files", "*.*")),
                               title='Open File',
                               initialdir=str(Path.home()))
        if file:
            print(file)
        else:
            print('Cancelled')

    def save_file(self):
        file = asksaveasfile(filetypes=(("Python files", "*.py"),
                                           ("All files", "*.*")),
                               title='Save File',
                               initialdir=str(Path.home()))
        if file:
            print(file)
        else:
            print('Cancelled')

    def ask_folder(self):
        folder = askdirectory(title='Pick a folder', initialdir=str(Path.home()))

        if folder:
            print(folder)
        else:
            print('Cancelled')


if __name__ == '__main__':
    win = Window(Tk())
    win.mainloop()

askopenfilename

You use askopenfilename when you want to open a file. It will return the absolute path of the file as a string if the user picks a file, or it will return None if the user cancels. You can restict the file types by passing an Iterable of tuples to the filetypes argument. If you do not specify an initialdir argument, it will default to the root of the user’s disk. I usually prefer to set it to the user’s home directory using Path.home() but you can adjust this to your application’s needs. The dialog will look specific to the user’s platform.

def open_file(self):
    file = askopenfilename(filetypes=(("Python files", "*.py"),
                                       ("All files", "*.*")),
                           title='Open File',
                           initialdir=str(Path.home())
    if file:
        print(file)
    else:
        print('Cancelled')

asksaveasfile

You can use this dialog when you want to perform a save operation. It takes arguments that are similar to askopenfilename, but it will return a file handler object opened in write mode (if you use the default arguments) rather than a string. You can then proceed with your save code. This function also returns None if the user cancels.

def save_file(self):
    file = asksaveasfile(filetypes=(("Python files", "*.py"),
                                       ("All files", "*.*")),
                         title='Save File',
                         initialdir=str(Path.home()))
    if file:
        print(file)
    else:
        print('Cancelled')

askdirectory

This function is used to let the user pick a folder. It will return a string if the user picked a folder or None if they chose to cancel.

def ask_folder(self):
    folder = askdirectory(title='Pick a folder', initialdir=str(Path.home()))

    if folder:
        print(folder)
    else:
        print('Cancelled')

Python Simple Dialogs

Applications typically have to request input from the user from time to time. Python and tkinter have built in dialogs that help you ask the user basic questions. These dialogs also provide validation to help make sure that the user enters valid input. This is an example program that shows off askquestion, askfloat, askinteger, and askstring.

from tkinter import *
from tkinter.messagebox import askquestion, showinfo
from tkinter.simpledialog import askfloat, askinteger, askstring


class AskDialogDemo(Frame):
    def __init__(self, master=None, cnf={}, **kw):
        super().__init__(master, cnf, **kw)
        self.pack()
        Button(self, text='Ask a Question', command=self.askquestion_demo).pack(side=LEFT, fill=BOTH, expand=YES)
        Button(self, text='Ask for a Float', command=self.askfloat_demo).pack(side=LEFT, fill=BOTH, expand=YES)
        Button(self, text='Ask for an Integer', command=self.askinteger_demo).pack(side=LEFT, fill=BOTH, expand=YES)
        Button(self, text='Ask for a String', command=self.askstring_demo).pack(side=LEFT, fill=BOTH, expand=YES)

    def askquestion_demo(self):
        answer = askquestion('Question', 'Do you like corn?')
        if answer:
            showinfo('Answer', answer)
        else:
            self.canceled()

    def askfloat_demo(self):
        num = askfloat('Float', 'Enter a decimal number')
        if num:
            showinfo('Float', 'You entered {}'.format(num))
        else:
            self.canceled()

    def askinteger_demo(self):
        num = askinteger('Integer', 'Enter a whole number')
        if num:
            showinfo('Integer', 'You entered {}'.format(num))
        else:
            self.canceled()

    def askstring_demo(self):
        str = askstring('String', 'Enter a string')
        if str:
            showinfo('String', 'You entered {}'.format(str))
        else:
            self.canceled()

    def canceled(self):
        showinfo('Canceled', 'You canceled')


if __name__ == '__main__':
    AskDialogDemo().mainloop()

Explanation

askquestion

You use askquestion to ask the user a basic yes or no question. It takes two arguments: one for the title, and the other is for the question. The returned value will be a string containing yes or no.

answer = askquestion('Question', 'Do you like corn?')

askfloat

The askfloat function returns decimal numbers. It will also perform validation that makes sure the user enters a valid float. The function will either return the float that the user entered or it will return None if they hit cancel.

num = askfloat('Float', 'Enter a decimal number')

askinteger

This function works like askfloat but it returns integers.

num = askinteger('Integer', 'Enter a whole number')

askstring

You can use askstring to get a string value from the user. It will return None if the user enters a blank string and hits Ok or Cancel.

str = askstring('String', 'Enter a string')

Python Advanced Quit Button

Object orientated programming fits extremely well with GUI programming. Using OOP, we can easily make reusable GUI components. This post shows off a quit button that confirms if the user really wants to exit the application. I got the idea from Programming Python: Powerful Object-Oriented Programming. Here is my implementation of the idea followed by the explanation.

from tkinter import *
from tkinter.messagebox import *


class TkQuitButton(Frame):
    def __init__(self, master=None,
                 auto_pack=True,  # Pack the widget automatically?
                 dialog_title='Confirm',  # Title text for the askyesno dialog
                 dialog_message='Are you sure you want to quit?',  # Message for the askyesno dialog
                 button_text='Quit',  # The quit button's text
                 quit_command=Frame.quit,  # Callback command for when the user wants to quit
                 cnf={}, **kw):

        super().__init__(master, cnf, **kw)
        # Store our fields for later user
        self.quit_command = quit_command
        self.dialog_message = dialog_message
        self.dialog_title = dialog_title
        self.quit_button = Button(self, text=button_text, command=self.quit)

        # Notice that self.quit_button is exposed. This can be useful for when
        # the client code needs to configure this frame on its own
        if auto_pack:
            self.pack_widget()
    
    # This let's us override the packing        
    def pack_widget(self):
        self.pack()
        self.quit_button.pack(side=LEFT, expand=YES, fill=BOTH)

    def quit(self):
        # Call the askyesno dialog
        result = askyesno(self.dialog_title, self.dialog_message)
        if result:
            # if they quit, then execute the stored callback command
            self.quit_command(self)


if __name__ == '__main__':
    TkQuitButton().mainloop()

This class extends the Frame class and packs a button into the frame. There are a few configuration properties that can be passed into the constructor. For example, we can auto_pack the widget so that it uses a default packing scheme. We can specifiy a custom title for the askyesno dialog as well as a custom message. The code even lets use customize the text of the button. We can also use a custom quit handler function should we choose to do so.

We can customize how the widget is packed in two different ways. The first way to access the quit_button property and call pack on it directly. This allows client code to change how this widget is packed into their GUIs. Alternatively, we can subclass this class and just override the pack_widget method.

The default quit implementation uses Tk’s askyesno dialog function to display a confirmation dialog to the user. It’s title and message are set to self.dialog_title and self.dialog_message properties. This allows use to customize what the user sees when the dialog is displayed. If the user presses yes, then we call the self.quit_command function which defaults to Frame.quit. Note that since self.quit is a method, we can customize this behavior by overriding it. Since we use a callback handler to exit the applicaiton, we can also customize how the application exits as well.

Tk — Toplevel

Toplevel widgets are non-root windows. In other words, Toplevel widgets are windows that appear outside of the application’s root window. We can use Toplevel widgets for items such as dialogs, color picker windows, or even dragging tabs into windows. Basically, anytime you need a window that isn’t part of the main application, you can use a Toplevel to create it.

Here is an example of a script that creates Toplevel windows everytime the Spawn button is clicked.

from tkinter import *

count = 0


def spawn_top_level(text):
    global count

    # Create a new window with a label
    win = Toplevel()
    Label(win, text=text, font=('Arial', 32, 'italic')).pack(expand=YES, fill=BOTH)

    count += 1


# This is the main application window
root = Tk()
Button(root,
       text='Spawn',
       command=(lambda: spawn_top_level('Top Level: {}'.format(count)))).pack()
Button(root, text='Quit', command=root.quit).pack()
root.mainloop()

The code creates Toplevel windows inside of the spawn_top_level() function. The window itself is created on line 10, and then we attach a Label to it on line 11. Notice how we pass win as the Label’s parent on line 11. This is how Tkinter knows where to attach the label. When the script is run, we get something like the screenshot below.

toplevel

Tk – GUI Composition with Classes

Since Python’s Tk widgets are classes, it is really easy to compose GUIs by using Pythons OOP capabilities. OOP works really well because we can break complex GUIs down into smaller components and then compose a larger GUI out of these components. Let’s begin with a text area control.

from tkinter import *


class TextArea(Text):
    def __init__(self, parent=None):
        Text.__init__(self, parent, width=40, height=10, border=2)
        self.pack(expand=YES, fill=BOTH, side=TOP)

if __name__ == '__main__':
    TextArea(Toplevel())
    mainloop()

This code subclasses the Text control and initializes it to what would be a reasonably sized text area control that grows and shrinks with the window. We can verify if our control is working properly by using the self-test code found in the script. Here is a screenshot of what it looks like.

textarea

It’s not much of a window, but we aren’t done composing our GUI yet either. Now let’s make some buttons that will let us load, save, and quit the application. The buttons will be arranged horizontally from left to right. This time, we are going to subclass the Frame class.

class ControlPanel(Frame):
    def __init__(self, parent=None, save=None, load=None, quit_command=exit):
        Frame.__init__(self, parent)
        Button(self, text='Save', command=save).pack(side=LEFT)
        Button(self, text='Load', command=load).pack(side=LEFT)
        Button(self, text='Exit', command=quit_command).pack(side=LEFT)
        self.pack(expand=YES, fill=BOTH, side=TOP)

if __name__ == '__main__':
    ControlPanel(Toplevel())
    mainloop()

In this example, we are using the self variable as a parent object to our 3 objects. The ControlPanel’s constructor accepts three references to functions that act as event handlers for the buttons. Inside of the constructor, we create three buttons and set their text and command attributes. Then we pack them to the left side of the layout. Finally, the frame itself is packed. Running the self-test code gives us the following window.

control_panel

The final task is to combine our controls into a single window. Once again, we are going to subclass Frame.

class TextPanel(Frame):
    def __init__(self, parent=None):
        Frame.__init__(self, parent)
        TextArea(self)
        ControlPanel(self)
        self.pack(expand=YES, fill=BOTH)

if __name__ == '__main__':
    TextPanel(Toplevel())
    mainloop()

Notice how the TextPanel class simply uses TextArea and ControlPanel. Once again, we are using composition to build up a complex GUI. The beauty of this pattern is that we can use both TextArea and ControlPanel in other GUIs. Futhermore, the TextPanel class can also get embedded into other GUIs as well.

Since all three classes have test code, we can easily see how our code is working as we develop. This is part of the reason why it’s so easy to build up GUI applications in Python using Tk or another widget toolkit. We can easily contruct GUIs using OOP and then test then instantly and independently of the application.

Here is the finished GUI followed by a complete script.

complete

from tkinter import *


class TextArea(Text):
    def __init__(self, parent=None):
        Text.__init__(self, parent, width=40, height=10, border=2)
        self.pack(expand=YES, fill=BOTH, side=TOP)


class ControlPanel(Frame):
    def __init__(self, parent=None, save=None, load=None, quit_command=exit):
        Frame.__init__(self, parent)
        Button(self, text='Save', command=save).pack(side=LEFT)
        Button(self, text='Load', command=load).pack(side=LEFT)
        Button(self, text='Exit', command=quit_command).pack(side=LEFT)
        self.pack(expand=YES, fill=BOTH, side=TOP)


class TextPanel(Frame):
    def __init__(self, parent=None):
        Frame.__init__(self, parent)
        TextArea(self)
        ControlPanel(self)
        self.pack(expand=YES, fill=BOTH)


if __name__ == '__main__':
    TextArea(Toplevel())
    ControlPanel(Toplevel())
    TextPanel(Toplevel())
    mainloop()