Book Image

Python for Security and Networking - Third Edition

By : José Ortega
4 (3)
Book Image

Python for Security and Networking - Third Edition

4 (3)
By: José Ortega

Overview of this book

Python’s latest updates add numerous libraries that can be used to perform critical security-related missions, including detecting vulnerabilities in web applications, taking care of attacks, and helping to build secure and robust networks that are resilient to them. This fully updated third edition will show you how to make the most of them and improve your security posture. The first part of this book will walk you through Python scripts and libraries that you’ll use throughout the book. Next, you’ll dive deep into the core networking tasks where you will learn how to check a network’s vulnerability using Python security scripting and understand how to check for vulnerabilities in your network – including tasks related to packet sniffing. You’ll also learn how to achieve endpoint protection by leveraging Python packages along with writing forensics scripts. The next part of the book will show you a variety of modern techniques, libraries, and frameworks from the Python ecosystem that will help you extract data from servers and analyze the security in web applications. You’ll take your first steps in extracting data from a domain using OSINT tools and using Python tools to perform forensics tasks. By the end of this book, you will be able to make the most of Python to test the security of your network and applications.
Table of Contents (23 chapters)
1
Section 1: Python Environment and System Programming Tools
4
Section 2: Network Scripting and Packet Sniffing with Python
8
Section 3: Server Scripting and Port Scanning with Python
12
Section 4: Server Vulnerabilities and Security in Web Applications
16
Section 5: Python Forensics
20
Assessments – Answers to the End-of-Chapter Questions
21
Other Books You May Enjoy
22
Index

Learn about data structures and collections in Python

In this section, we will review different types of data structures, including lists, tuples, and dictionaries. We will see methods and operations for managing these data structures and practical examples where we review the main use cases.

Python lists

Lists in Python are equivalent to structures such as dynamic vectors in programming languages such as C and C++. We can express literals by enclosing their elements between a pair of brackets and separating them with commas. The first element of a list has index 0.

Lists in Python are, used to store sets of related items of the same or different types. Also, a list is a mutable data structure which allows the list content can be modified after it has been created.

To create a list in Python, simply enclose a comma-separated sequence of elements in square brackets []. For example, creating a list with response codes would be done as follows:

>>> responses = [200,400,403,500]

Indexes are used to access an element of a list. An index is an integer that indicates the position of an element in a list. The first element of a list always starts at index 0.

>>> responses[0]
200
>>> responses[1]
400

If an attempt is made to access an index that is outside the range of the list, the interpreter will throw the IndexError exception. Similarly, if an index that is not an integer is used, the TypeError exception will be thrown:

>>> responses[4]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

Consider the following example: a programmer can create a list using the append() method by adding objects, printing the objects, and then sorting them before printing again. We describe a list of protocols in the following example, and use the key methods of a Python list, such as add, index, and remove:

>>> protocolList = []
>>> protocolList.append("FTP")
>>> protocolList.append("SSH")
>>> protocolList.append("SMTP")
>>> protocolList.append("HTTP")
>>> print(protocolList)
['FTP','SSH','SMTP','HTTP']
>>> protocolList.sort()
>>> print(protocolList)
['FTP','HTTP','SMTP','SSH']
>>> type(protocolList)
<type 'list'>
>>> len(protocolList)
4

To access specific positions, we can use the index() method, and to delete an element, we can use the remove() method:

>>> position = protocolList.index('SSH')
>>> print("SSH position"+str(position))
SSH position 3
>>> protocolList.remove("SSH")
>>> print(protocolList)
['FTP','HTTP','SMTP']
>>> count = len(protocolList)
>>> print("Protocol elements "+str(count))
Protocol elements 3

To print out the whole protocol list, use the following instructions. This will loop through all the elements and print them:

>>> for protocol in protocolList:
...     print(protocol)
...
FTP
HTTP
SMTP

Lists also provide methods that help manipulate the values within them and allow us to store more than one variable within them and provide a better way to sort object arrays in Python. These are the techniques commonly used to manage lists:

  • .append(value): Appends an element at the end of the list
  • .count('x'): Gets the number of 'x' elements in the list
  • .index('x'): Returns the index of 'x' in the list
  • .insert('y','x'): Inserts 'x' at location 'y'
  • .pop(): Returns the last element and removes it from the list
  • .remove('x'): Removes the first 'x' from the list
  • .reverse(): Reverses the elements in the list
  • .sort(): Sorts the list in ascending order

The indexing operator allows access to an element and is expressed syntactically by adding its index in brackets to the list, list [index]. You can change the value of a chosen element in the list using the index between brackets:

protocolList [4] = 'SSH'
print("New list content: ", protocols)

Also, you can copy the value of a specific position to another position in the list:

protocolList [1] = protocolList [4]
print("New list content:", protocols)

The value inside the brackets that selects one element of the list is called an index, while the operation of selecting an element from the list is known as indexing.

Adding elements to a list

Lists are mutable sequences that can be modified, which means items can be added, updated, or removed. To add one or more elements, we can use the extend() method. Also, we can use the insert() method to insert an element in a specific index location. We can add elements to a list by means of the following methods:

  • list.append(value): This method allows an element to be inserted at the end of the list. It takes its argument’s value and puts it at the end of the list that owns the method. The list’s length then increases by one.
  • list.extend(values): This method allows inserting many elements at the end of the list.
  • list.insert(location, value): The insert() method is a bit smarter since it can add a new element at any place in the list, not just at the end. It takes as arguments first the required location of the element to be inserted and then the element to be inserted.

In the following example we are using these methods to add elements to the response code list.

>>> responses.append(503)
>>> responses
[200, 400, 403, 500, 503]
>>> responses.extend([504,505])
>>> responses
[200, 400, 403, 500, 503, 504, 505]
>>> responses.insert(6,300)
>>> responses
[201, 400, 403, 500, 503, 504, 300, 505]

Reversing a list

Another interesting operation that we perform in lists is the one that offers the possibility of getting elements in a reverse way in the list through the reverse() method:

>>> protocolList.reverse()
>>> print(protocolList)
['SMTP','HTTP','FTP']

Another way to do the same operation is to use the -1 index. This quick and easy technique shows how you can access all the elements of a list in reverse order:

>>> protocolList[::-1]
>>> print(protocolList)
['SMTP','HTTP','FTP']

Searching elements in a list

In this example, we can see the code for finding the location of a given element inside a list. We use the range function to get elements inside protocolList and we compare each element with the element to find. When both elements are equal, we break the loop and return the element. To find out if an element is contained in a list, we can use the membership operator in.

>>> 'HTTPS' in protocolList
False
>>> 'HTTP' in protocolList
True

You can find the following code in the search_element_list.py file:

protocolList = ["FTP", "HTTP", "SNMP", "SSH"]
element_to_find = "SSH"
for i in range(len(protocolList)):
    if element_to_find in protocolList[i]:
        print("Element found at index", i)
        break

Now that you know how to add, reverse, and search for elements in a list, let’s move on to learning about tuples in Python.

Python tuples

Like lists, the tuple class in Python is a data structure that can store elements of different types.

Along with the list and range classes, it is one of the sequence types in Python, with the particularity that they are immutable. This means its content cannot be modified after it has been created.

In general, to create a tuple in Python, you simply define a sequence of elements separated by commas. Indices are used to access an element of a tuple. An index is an integer indicating the position of an element in a tuple. The first element of a tuple always starts at index 0.

>>> tuple=("FTP","SSH","HTTP","SNMP")
>>> tuple[0]
'FTP'

If an attempt is made to access an index that is outside the range of the tuple, the interpreter will throw the IndexError exception. Similarly, if an index that is not an integer is used, the TypeError exception will be thrown:

>>> tuple[5]
Traceback (most recent call last):
  File "<input>", line 1, in <module>
IndexError: tuple index out of range

As with lists and all sequential types, it is permissible to use negative indices to access the elements of a tuple. In this case, the index -1 refers to the last element of the sequence, -2 to the penultimate, and so on:

>>> tuple[-1]
'SNMP'
>>> tuple[-2]
'HTTP'

When trying to modify a tuple, we see how we get an error since tuples are immutable objects:

>>> tuple[0]="FTP"
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

Now that you know the basic data structures for working with Python, let’s move on to learning about Python dictionaries in order to organize information in the key-value format.

Python dictionaries

The Python dictionary data structure is probably the most important in the entire language and allows us to associate values with keys. Python’s dict class is a map type that maps keys to values. Unlike sequential types (list, tuple, range, or str), which are indexed by a numeric index, dictionaries are indexed by keys. Among the main features of the dictionaries, we can highlight:

  • It is a mutable type, that is, its content can be modified after it has been created.
  • It is a type that reserves the order in which key-value pairs are inserted.

In Python there are several ways to create a dictionary. The simplest is to enclose a sequence of comma-separated key:value pairs in curly braces {}. In this example we will define the service name as the key and the port number as the value.

>>> services = {"FTP":21, "SSH":22, "SMTP":25, "HTTP":80}

Another way to create a dictionary is using the dict class:

>>> dict(services)
{'FTP': 21, 'SSH': 22, 'SMTP': 25, 'HTTP': 80}
>>> type(services)
<class 'dict'>

Accessing an element of a dictionary is one of the main operations for which this type of data exists. Access to a value is done by indexing the key. To do this, simply enclose the key in square brackets. If the key does not exist, the KeyError exception will be thrown.

>>> services['FTP']
21

The dict class also offers the get (key[, default value]) method. This method returns the value corresponding to the key used as the first parameter. If the key does not exist, it does not throw any errors, but returns the second argument by default. If this argument is not supplied, the value None is returned.

>>> services.get('SSH')
22

If the key does not exist, it does not throw any errors, but returns the second argument by default.

>>> services.get('gopher', "service not found")
'service not found'

If this argument is not supplied, the value None is returned.

>>> type(services.get('gopher'))
<class 'NoneType'>

Using the update method, we can combine two distinct dictionaries into one. In addition, the update method will merge existing elements if they conflict:

>>> services = {"FTP":21, "SSH":22, "SMTP":25, "HTTP":80}
>>> services2 = {"FTP":21, "SSH":22, "SMTP":25, "LDAP":389}
>>> services.update(services2)
>>> services
{'FTP': 21, 'SSH': 22, 'SMTP': 25, 'HTTP': 80, 'LDAP': 389}

The first value is the key, and the second the key value. We can use any unchangeable value as a key. We can use numbers, sequences, Booleans, or tuples, but not lists or dictionaries, since they are mutable.

The main difference between dictionaries and lists or tuples is that values contained in a dictionary are accessed by their name and not by their index. You may also use this operator to reassign values, as in the lists and tuples:

>>> services["HTTP"] = 8080
>>> services
{'FTP': 21, 'SSH': 22, 'SMTP': 25, 'HTTP': 8080, 'LDAP': 389}

This means that a dictionary is a set of key-value pairs with the following conditions:

  • Each key must be unique: That means it is not possible to have more than one key of the same value.
  • A key may be a number or a string.
  • A dictionary is not a list: A list contains a set of numbered values, while a dictionary holds pairs of values.
  • The len() function: This works for dictionaries and returns the number of key-value elements in the dictionary.

IMPORTANT NOTE

In Python 3.10, dictionaries have become ordered collections by default.

The dict class implements three methods, since they return an iterable data type, known as view objects. These objects provide a view of the keys and values of type dict_values contained in the dictionary, and if the dictionary changes, these objects are instantly updated. The methods are as follows:

  • items(): Returns a view of (key, value) pairs from the dictionary.
  • keys(): Returns a view of the keys in the dictionary.
  • values(): Returns a view of the values in the dictionary.
>>> services.items()
dict_items([('FTP', 21), ('SSH', 22), ('SMTP', 25), ('HTTP', 8080), ('LDAP', 389)])
>>> services.keys()
dict_keys(['FTP', 'SSH', 'SMTP', 'HTTP', 'LDAP'])
>>> services.values()
dict_values([21, 22, 25, 8080, 389])

You might want to iterate over a dictionary and extract and display all the key-value pairs with a for loop:

>>> for key,value in services.items():
...     print(key,value)
... 
FTP 21
SSH 22
SMTP 25
HTTP 8080
LDAP 389

The dict class is mutable, so elements can be added, modified, and/or removed after an object of this type has been created. To add a new item to an existing dictionary, use the assignment operator =. To the left of the operator appears the dictionary object with the new key in square brackets [] and to the right the value associated with said key.

>>> services['HTTPS'] = 443
>>> services
{'FTP': 21, 'SSH': 22, 'SMTP': 25, 'HTTP': 8080, 'LDAP': 389, 'HTTPS': 443}

Now that you know the main data structures for working with Python, let’s move on to learning how to structure our Python code with functions and classes.

Remove an item from a dictionary in Python

In Python there are several ways to remove an element from a dictionary. They are the following:

  • pop(key [, default value]): If the key is in the dictionary, it removes the element and return its value; if not, it returns the default value. If the default value is not provided and the key is not in the dictionary, the KeyError exception is raised.
  • popitem(): Removes the last key:value pair from the dictionary and returns it. If the dictionary is empty, the KeyError exception is raised.
  • del d[key]: Deletes the key:value pair. If the key does not exist, the KeyError exception is thrown.
  • clear(): Clears all key:value pairs from the dictionary.

In the following instructions we are removing the elements of the services dictionary using the previous methods:

>>> services = {'FTP': 21, 'SSH': 22, 'SMTP': 25, 'HTTP': 8080, 'LDAP': 389, 'HTTPS': 443} 
>>> services.pop('HTTPS')
443
>>> services
{'FTP': 21, 'SSH': 22, 'SMTP': 25, 'HTTP': 8080, 'LDAP': 389}
>>> services.popitem()
('LDAP', 389)
>>> services
{'FTP': 21, 'SSH': 22, 'SMTP': 25, 'HTTP': 8080}
>>> del services['HTTP']
>>> services
{'FTP': 21, 'SSH': 22, 'SMTP': 25}
>>> services.clear()
>>> services
{}