Unit 1: Python for defenders · Lesson 2 of 6 · 18 min

Strings, lists and dictionaries

Cut one inventory line into fields and file them under names you can query.

mission

You are
the engineer asked to review exposed services
Situation
The inventory is one text line per host. Before you can check anything at scale you need to turn a line into data Python understands.
Goal
Turn the legacy01 line into a dictionary and read its open ports.
Hand in
The number of open ports on legacy01.

A line through the sorting rack

01

Find the line

learner@py-lab
grep legacy01 data/inventory.txt
legacy01 file centos-7 21,22,23,111,445,2049,3306

you should see Four fields separated by spaces: name, role, operating system and ports.

02

Split the string into a list

split() cuts on whitespace and returns a list. Unpacking names each piece in one line.

>>
line = 'legacy01 file centos-7 21,22,23,111,445,2049,3306'
line.split()
['legacy01', 'file', 'centos-7', '21,22,23,111,445,2049,3306']
name, role, system, ports = line.split()
ports
'21,22,23,111,445,2049,3306'
ports.split(',')
['21', '22', '23', '111', '445', '2049', '3306']

you should see The ports start as one string, then become a list of strings.

03

File the fields in a dictionary

A dict gives each value a name, so code reads host['ports'] instead of remembering positions.

>>
line = 'legacy01 file centos-7 21,22,23,111,445,2049,3306'
name, role, system, ports = line.split()
host = {'name': name, 'role': role, 'os': system, 'ports': ports.split(',')}
host['os']
'centos-7'
len(host['ports'])
7
'23' in host['ports']
True
23 in host['ports']
False

you should see '23' in is True but 23 in is False: the ports are text, not numbers.

defender tip Convert with int() as soon as you parse, or every later comparison gets this wrong.

04

Convert ports to numbers

>>
ports = [int(p) for p in '21,22,23,111,445,2049,3306'.split(',')]
ports
[21, 22, 23, 111, 445, 2049, 3306]
max(ports)
3306
[p for p in ports if p < 1024]
[21, 22, 23, 111, 445]

you should see A list comprehension builds a new list in one line, here keeping only well known ports.

Positions versus names

Fragile
fields = line.split()
if '23' in fields[3]:
    print(fields[0])

Breaks the day someone adds a column, and '23' also matches '2375'.

Robust
name, role, system, ports = line.split()
ports = [int(p) for p in ports.split(',')]
if 23 in ports:
    print(name)

Named fields and real numbers. Unpacking fails loudly if the format changes.

What a defender takes away

  1. 1Strings split into lists, and lists of pairs become dicts.
  2. 2Parse values into the right type straight away: ports are numbers.
  3. 3Substring checks on text are a classic source of false matches.

Questions

0 of 3 solved

Answer each one from what you find on your lab machine.

  1. Show hint

    len(host['ports'])

  2. Show hint

    grep dc02 data/inventory.txt, then split it.

  3. Show hint

    Loop over every line, convert each port with int(), then use max().