About chouchen

An overview of the purpose of each module is as follows:

  • system_graph describe the scenario on a technical level (using attack graphs).

  • graphics offers graphical representation of system graphs.

  • constraints describes architectural constraints associated to procedure and how they are combined.

  • subconstraints helps with that.

  • file_generation generates files to populate the resulting machines.

  • secret_generation generates secrets to populate the resulting machines (passwords, keys…).

  • procedural_refinement combines all the above modules to generate a procedural-level scenario based on a technical-level description.

This API may be implemented as a PyPi library in the future.

chouchen.system_graph

System Graph definitions.

This module codifies what a scenario is on a technical level, by representing them as attack graphs (nodes and transitions).

This module contains the following classes:
  • SystemGraph, the scenario itself.

  • Node, the nodes of the attack graph. Represented as a tuple of (machine, user) representing an attack position.

  • Transition, the transitions of the attack graph. Labelled using MITRE ATT&CK techniques and/or procedures.

This module also provides basic simulation of the path of an attacker throughout the scenario:
  • AttackState, representing the state of an attacker (controlled nodes, acquired secrets) at a given time.

  • AttackPath, representing the journey of an attacker through the scenario (list of attack states).

This module can also import or export scenarios in a json format. Scenarios generated using this module may then be represented using the graphics module, or refined into procedural scenarios using the procedural_refinement module.

class chouchen.system_graph.AttackPath(attack_states, transitions)

A class used to represent the journey of an attacker in the scenario.

attack_states

The list of all attack states representing the attack path.

transitions

The list of all transitions that were taken to lead to these attack states.

check_if_valid(system_graph)

Checks whether the attack path is valid for a given scenario. This is done by checking that each attack state does result from taking each transition one by one.

Parameters:

system_graph (SystemGraph) – the scenario to check against

Return type:

bool

Returns:

True if the path is valid, False otherwise

check_if_winning(system_graph)

Checks if an attack path corresponds to a winning attack path for a given scenario.

Parameters:

system_graph (SystemGraph) – The scenario to check against.

Return type:

bool

Returns:

True if the attack path is winning, False otherwise.

static generate_random_attack_path(system_graph, number_of_actions)

Generates a random attack path for an attacker with a randomized starting position taking n transitions. Only transitions that lead to a different attack state will be taken. Therefore, if number_of_actions is too big this algorithm may end early. The attacker will take a valid transition at random at every step.

Parameters:
  • system_graph (SystemGraph) – The scenario to walk.

  • number_of_actions (int) – The number of transitions the attacker will take at random.

Return type:

AttackPath

Returns:

class chouchen.system_graph.AttackState(nodes, transitions, secrets)

A class used to represent the state of an attacker in the scenario.

nodes

The list of attack positions controlled by the attacker.

Type:

list[Node]

transitions

The list of transitions that were taken by the attacker.

Type:

list[Transition]

secrets

The list of secrets that were acquired by the attacker.

Type:

list[str]

static default_attack_state(system_graph)

Returns an attack state for the given system_graph where the attacker controls all starting nodes and has no secrets.

Parameters:

system_graph (SystemGraph) – The system graph object to generate an attack state for.

Return type:

AttackState

Returns:

The corresponding AttackState object

is_transition_valid(transition)

Checks if an attacker may effectively take a given transition. This is done by checking that he controls the starting node and knows the required secrets.

Parameters:

transition (Transition) – The transition to check.

Return type:

bool

Returns:

True if the transition is possible to take, False otherwise.

is_winning(system_graph)

Checks if the AttackState corresponds to a winning AttackState

Parameters:

system_graph (SystemGraph) – the scenario being played

Return type:

bool

Returns:

True if the scenario is won, False otherwise.

take_random_action(system_graph)

Makes the attacker take a random transition among all the ones he has available.

Parameters:

system_graph (SystemGraph) – The scenario actions are taken from.

Raises:

NoValidTransitionError – The attacker has no valid moves left.

Return type:

None

take_transition(transition)

Updates the AttackState by taking a transition, and acquiring new attack positions and secrets as a result.

Parameters:

transition (Transition) – The transition to take

Raises:

InvalidTransitionError – The attacker is not actually able to take this transition.

Return type:

None

exception chouchen.system_graph.InvalidTransitionError

Raised when an attacker attempts to take a transition he is not actually able to.

exception chouchen.system_graph.NoValidTransitionsError

Raised when an attacker has no valid moves left.

class chouchen.system_graph.Node(machine, user, node_id=-1)

A class used to represent an attack position.

machine

The name of the machine corresponding to the attack position.

Type:

str

user

The name of the user corresponding to the attack position.

Type:

str

node_id

A unique node identifier, generated automatically for every node created.

Type:

int

class chouchen.system_graph.SystemGraph(nodes, starting_nodes, winning_nodes, transitions)

A class used to represent an attack scenario through a System Graph.

nodes

The list of all attack positions in the scenario.

Type:

list[Node]

starting_nodes

The list of all starting attack positions in the scenario.

Type:

list[Node]

winning_nodes

The list of all winning positions in the scenario.

Type:

list[Node]

transitions

The list of all transitions available between attack positions.

Type:

list[Transition]

add_node_to_scenario(machine, user, is_starting, is_winning)

Adds a node to the scenario. This node will initially have no transitions attached. Useful for GUI applications.

Parameters:
  • machine (str) – the machine name of the attack position.

  • user (str) – the user name of the attack position.

  • is_starting (bool) – whether this is a starting node.

  • is_winning (bool) – whether this is a winning node.

Return type:

None

add_transition_to_scenario(entry_node, exit_node, technique, required, rewarded)

Adds a transition to the scenario. Inputs is described as strings and converted into objects.

Parameters:
  • entry_node (str) – the entry node, written as “User, Machine”

  • exit_node (str) – the exit node, written as “User, Machine”

  • technique (str) – the associated technique, written as “TXXXX: Technique name”

  • required (str) – the required secrets, written as secret1, secret2, secret3… None for no secret.

  • rewarded (str) – the rewarded secrets, written as secret1, secret2, secret3… None for no secret.

Return type:

None

static empty_scenario()

Creates an empty SystemGraph object object.

Return type:

SystemGraph

find_transition_index(transition)

Returns the index of the transition equal to the given argument. Returns -1 if not found. 2 transitions are equal if they have the same transition_id.

Parameters:

transition (Transition) – a transition object

Return type:

int

Returns:

The index of the transition in self.transitions

find_transitions_requiring_secret(secret_name)

Gets all transitions where secret_name is a requirement. Useful for secret shenanigans later when we need to get all related users.

Parameters:

secret_name (str) – The name of the secret to find

Return type:

list[Transition]

Returns:

The list of all transitions where the secret_name is in the required secrets.

get_new_node_id()

Returns the lowest node_id that hasn’t been used already.

Return type:

int

is_winnable()

Checks whether the scenario is winnable or not. This is done by bruteforcing every transition available until either the scenario is won or there is no transition left and by only taking transitions that lead to a different attack state. This also calculates a winning attack path at random (list of transitions taken by the attacker leading to his winning position).

Return type:

Tuple[bool, list[Transition]]

Returns:

(True, list[Transitions]) if the scenario is winnable, (False, []) otherwise.

remove_node_from_scenario(node)

Removes a node from the scenario based on a string representation. Note that this will also remove any transition associated with that node.

Parameters:

node (Node) – a Node

remove_transition_from_scenario(transition)

Removes a transition from the scenario based on a string representation.

Parameters:

transition (Transition) – a transition

class chouchen.system_graph.Transition(entry_node, exit_node, technique, requires=[], rewards=[], procedure=None)

A class used to represent an transition on the technical level.

entry_node

The attack position the transition is initiated from.

Type:

Node

exit_node

The attack position the transition leads to (can be the same as entry).

Type:

Node

technique

An ATT&CK technique, represented by its technique number (such as T1021).

Type:

str

procedure

A procedure instantiating the technique.

Type:

str

requires

A list of secrets required for the transition, can be empty.

Type:

list[str]

rewards

A list of secrets rewarded by the transition, can be empty.

Type:

list[str]

transition_id

A unique transition identifier, generated automatically for every transition.

Type:

int

chouchen.system_graph.export_scenario_as_json(sys_graph, output_file_name)

Exports a SystemGraph object as a json file, which may be imported in the future using import_scenario_as_json.

Parameters:
  • sys_graph (SystemGraph) – The scenario to export.

  • output_file_name (str) – The name of the output json file. Will be exported in the ../json directory.

Return type:

None

chouchen.system_graph.get_scenario_difficulty(scenario)

Gets the difficulty of a given scenario by summing the difficulty score of all its transitions that already have transitions attached to them.

Parameters:

scenario (SystemGraph) – the scenario to check.

Return type:

int

Returns:

The difficulty score.

chouchen.system_graph.import_scenario_from_json(json_file_path)

Imports a json file into a SystemGraph object. TODO: check if this still works. :type json_file_path: str :param json_file_path: The path of the json file to import. :type json_file_path: str

Return type:

SystemGraph

Returns:

A SystemGraph object, corresponding to the information stored in json_file_path.

Raises:

FileNotFoundError – The inputted file path is not a json file

chouchen.graphics

Graphics utility module.

This is used to convert a scenario described using the SystemGraph class and model into a proper graph representation.

Options include:
  • Showing technique names alongside their number on the transitions.

  • Show attacker state.

  • Show procedure names instead of technique names.

  • Save the graph at a given location.

In particular, refining a scenario will call this module in order to provide a graphical aid on which procedures were taken.

chouchen.graphics.draw_graph(sys_graph, attack_state=Nodes : [], Transitions : [], Secrets : [], technique_names=False, proc_mode=False, save_mode=None, hide_requires_rewards=False)

Provides a graphical representation of the scenario, showing off all attack positions and transitions alongside their attack techniques. Starting and winning nodes are also indicated.

If an attack state is provided, nodes controlled and transitions taken by the attacker will be indicated, alongside the list of secrets acquired by the attacker.

Parameters:
  • sys_graph (<module ‘chouchen.system_graph’ from ‘/home/docs/checkouts/readthedocs.org/user_builds/ursid/checkouts/latest/chouchen/system_graph.py’>) – The scenario to graphically represent.

  • attack_state (AttackState) – An optional attack state.

  • technique_names (bool) – Show full technique names if available (default: False)

  • proc_mode (bool) – Show procedure names instead of technique names (default: False)

  • save_mode (str) – Saves the graph at this location instead of showing if this parameter exists (default: None)

  • hide_requires_rewards (bool) – Does not show secret information on the graph (requires, rewards, attacker information)

Return type:

None

Returns:

chouchen.constraints

Architectural constraint definitions.

This module codifies what an architectural constraint is using the ArchitecturalConstraint class. An ArchitecturalConstraint object is composed of 4 constraints:

  • OSConstraint, a class responsible for operating system conditions applying to procedures, including its version and type.

  • AccountConstraint, the same for accounts, including its name, group, privilege, credentials and services.

  • SoftwareConstraint, the same for software, including its version and type.

  • FileConstraint, the same for files, including its path, permissions and content.

This module is also responsible for SecretPrecondition objects, which make sure that procedures are picked accordingly to the technical scenario.

Once procedures are loaded from their json files, their corresponding ArchitecturalConstraint objects are generated. They are then combined according to our refinement backtracking algorithm.

class chouchen.constraints.AccountConstraints(name, group='*', privilege='*', credentials={'Credential Type': '*', 'Requires Secret': False}, services=None)

A class to represent account constraints and their compatibilities. Account constraints consist of a name (“Alice”, “root” …), group (“Bob”, “root” …), permissions (“USER” or “SUPERUSER”) and credentials (“WEAK_RANDOM_PASSWORD”, …). TODO: list of acceptable credential types. .. attribute:: name

the name constraint.

type:

subconstraints.AccountName

group

the group constraint.

Type:

subconstraints.AccountGroup

privilege

the permission constraint.

Type:

str

credentials

the credentials constraint.

Type:

dict[str]

services

the service constraint

Type:

list[str]

assigned_secrets

the list of assigned secrets

Type:

list[str]

Examples:
>>> credential_dic = {"Credential Type": "RANDOM_WEAK_PASSWORD", "Requires Secret": False}
>>> alice_weak_password = AccountConstraints("alice", group = "alice", credentials=credential_dic)
static fuse_duplicates(first, other)

Fuses 2 entries with the same user name. This is done by getting the smallest constraint for each of Group, Privilege and Credentials (usually the one that is not a wildcard). :type first: AccountConstraints :param first: the first contraint to be fused :type first: AccountConstraints :type other: AccountConstraints :param other: the constraint to fuse with. :type other: AccountConstraints

Returns: The fused entry

Return type:

AccountConstraints

is_compatible(other)

Checks if 2 account constraints are compatible with each other, i.e that they may coexist on the same architecture.

2 account constraints are comaptible if: - They have different names. - They have compatible group, permissions and credentials subconstraints.

Parameters:

other (AccountConstraints) – the constraint to check against.

Return type:

bool

Returns:

True if the 2 constraints are compatible, False otherwise.

set_assigned_secrets(lsecrets)

Sets the list of secrets assignated to the constraint to the given value.

Parameters:

lsecrets (list[str]) – The list of secrets to set.

to_dic(secret_dictionary)

Convert the AccountConstraints object into a format compatible with our final configuration file. This requires the secret dictionary in argument in order to generate the credentials.

Return type:

dict

Returns:

a dictionnary representing the constraint

class chouchen.constraints.ArchitecturalConstraints(proc_dic, transition, machine_name=None, user_name=None)
A class to represent procedure constraints and their compatibilities. Procedure constraints consist of

OS constraints, Account constraints, Software constraints and File constraints. A instance of this class may be created by providing it a procedure stored in a dictionary format.

os_constraints

the list of OS constraints.

Type:

OSConstraints

account_constraints

the list of Account constraints.

Type:

AccountConstraints

software_constraints

the list of Software constraints.

Type:

SoftwareConstraints

file_constraints

the list of File constraints.

Type:

FileConstraints

assignated_secrets

the list of all secrets related to the transition associated to this procedure.

Type:

list[str]

assign_secret(secret_list)

Adds secrets to this architectural constraint. See README for more details on why this is done.

Parameters:

secret_list (list) – the list of secrets to add.

assign_secrets_to_subconstraints()

Assigns secrets to each account (and later file) constraints. This is called when the architectural constraint just got created and only contains information about a procedure’s constraints. There should not be account AND file constraints requiring secrets in a single procedure. First this gives secrets to each account constraint that require specific numbers of them. Then it gives the leftover secrets to the constraints with a wildcard amount of them (at least 1 for each). TODO: This function is currently very janky and unpractical. Needs a rewrite. This should be rewritten from scratch eventually, the concept of giving secrets to subconstraints seems sound though. IMPORTANT: IN PROCEDURES, FILES REQUIRING SECRETS SHOULD BE WRITTEN BEFORE FILES REQUIRING REWARDS THE ORDER MATTERS Returns:

Return type:

None

combine_constraints(other)

Combines 2 constraint objects by combining all of their subconstraints. This first checks whether the constraints are compatible, and will raise an error if not.

Parameters:

other (ArchitecturalConstraints) – the constraint to fuse with.

Return type:

ArchitecturalConstraints

Returns:

An architectural constraint object combining both constraints.

static default_unix_template_with_account(machine_name, user_name, machine_additional_config)

Creates a default ArchitecturalConstraints objects with a few software preinstalled.

The software are as follows: - rsync for file copy purposes - acl for script execution purposes

machine_additional_config is a dictionary. The relevant entry is [“options”], which contains strings.

Return type:

ArchitecturalConstraints

Returns:

An ArchitecturalConstraints object with every attribute empty except software.

edit_special_cases(scenario)

Edits a few static values to their corresponding real value. For now this is only used for the /etc/shadow procedure, which requires setting the account constraint to a specific value which can only be found somewhere else in the scenario

Parameters:

scenario (sg.SystemGraph) – the scenario

static empty_constraint()

Creates an ArchitecturalConstraints object with no constraints inside.

Return type:

ArchitecturalConstraints

Returns:

An ArchitecturalConstraints object with every attribute empty.

static empty_constraint_with_account(account_name)

Creates an ArchitecturalConstraints object with no constraints inside.

Return type:

ArchitecturalConstraints

Returns:

An ArchitecturalConstraints object with every attribute empty.

fuse_duplicates()

Fuses duplicate entries in the architectural constraints. This is done by checking for every user constraints if an other user constraint has the same name, and combining them into one if so. All these constraints should be combinable - if not an incompatibility would have been raised This is called at the very end of the refinement (could be done at every step though if you want). This also fuses every entry in the OS constraint using the smae process - comparing every constraint and returning the smallest one.

Return type:

None

generate_credentials_and_files(secret_dictionary)

Goes through all the file and account constraints, in particular their FileContent et AccountCredentials subconstraints, and generates the appropriate secrets based on the secret names contained in self.assignated_secrets. For now this only works if the constraint has either an account or a file constraint requiring secrets (but not both). Reparting secrets when that is not the case is surprisingly annoying, but this should cover most cases. This function thus will give all secrets it has been assignated to the subconstraint it has. Then based on the value of secret_value, secret_type, credentials/content and the secret dictionary, this will call the secret and file generating module.

Parameters:

secret_dictionary (dict) – the secret dictionary. Will be updated by this function.

Return type:

list[dict]

Returns:

A list of all configuration dictionaries related to the generated secrets and files.

static get_constraint_from_procedure_name(proc_name, transition, entry_or_exit)

Browses all procedure jsons (defined in ../json/procedures) looking for a procedure with the corresponding name, and returns the 2 ArchitecturalConstraint object corresponding to the entry and exit machine constraints. If several procedures share the same name (should NOT happen), only the first one will be returned.

Parameters:
  • entry_or_exit (str) – whether to return the entry or exit constraints of this procedure

  • transition (Transition) – The related transition

  • proc_name (str) – The name of the procedure.

Returns:

The 2 corresponding ArchitecturalConstraint objects.

Return type:

corresponding_entry_proc, corresponding_exit_proc

static get_dictionary_from_procedure_name(transition)

Gets the dictionary corresponding to a procedure constraint from its name. Raises an error if the name can’t be found in the ./json/fused_procedures.json file.

Parameters:

transition (Transition) – the related transition (contains a technique name).

Return type:

dict

Returns:

a dictionary containing the related procedure constraints.

static get_list_from_procedure_pattern(transition)

Gets the list of all dictionaries corresponding to a procedure pattern from its name. Raises an error if noi pattern matching can’t be found in the ./json/fused_procedures.json file.

Parameters:

transition (Transition) – the related transition (contains a technique name).

Return type:

list[dict]

Returns:

a list of dictionaries containing the related procedures constraints.

is_compatible(other)

2 architectural constraints are compatible if all their subconstraints are compatible with each other. :type other: ArchitecturalConstraints :param other: the architectural constraint to check against. :type other: ArchitecturalConstraints

Return type:

bool

Returns:

True if the two constraints are compatible, False otherwise.

is_empty()

Checks whether an architectural constraint is empty by checking the length of all its constraints.

Return type:

bool

Returns:

True if the constraint is empty, false otherwise.

is_user_in_constraint(user_name)

Checks whether a given username has an entry in the architectural constraints already. This is useful for initializing the refinement process.

Parameters:

user_name (str) – The name to look for

Return type:

bool

Returns: True if the name already exists, False otherwise.

to_dic(name, secret_dictionary)

Converts all the architectural constraints into a dictionary which will be added to the final json output :type name: str :param name: the name of the machine corresponding to these constraints :type name: str :type secret_dictionary: dict :param secret_dictionary: the secret dictionary :type secret_dictionary: dict

Return type:

dict

Returns:

A dictionary containing all information necessary to deploy this specific machine.

exception chouchen.constraints.CombiningIncompatibleConstraintsError(constraint_1, constraint_2, message='Incompatible constraints')

Exception raised for errors when trying to combine ArchitecturalConstraints objects

constraint_1

The first constraint.

Type:

ArchitecturalConstraints

constraint_2

The second constraint.

Type:

ArchitecturalConstraints

message

explanation of the error.

Type:

str

class chouchen.constraints.FileConstraints(path, perm, user, group, content)

A class to represent file constraints and their compatibilities. File constraints consist of a path constraint (where the file will be stored on the machine), permission constraints (written in the UNIX numbered format) and content constraints (hardcoded in subconstraints.FileContent). During the refinement process this will also remember which secret name in the scenario (ie the associated transition) is needed for the file construction (if there is any).

path

the path of the file.

Type:

str

perm

the permissions required for the file.

Type:

subconstraints.FilePermission

content

a dictionary containing information related to the content of the file.

Type:

dict

assigned_secrets

the list of secrets associated to the file. Can be empty.

Type:

list[str]

machine_name

the name of the associated machine.

Type:

str

user_name

the name of the associated user.

Type:

str

is_compatible(other)

Checks if two file constraints objects are compatible.

2 file constraints are compatible if: - They link to different file paths. - They link to the same file path but have compatible permissions and content constraints. :type other: FileConstraints :param other: the file constraint to check against. :type other: FileConstraints

Return type:

bool

Returns:

True if the two constraints are compatible, False otherwise.

set_assigned_secrets(lsecrets)

Sets the list of associated secrets to this procedure.

Parameters:

lsecrets (list[str]) – the list of assignated secrets.

set_machine_name(machine_name)

Sets the machine_name attribute to a new value. TODO: remember what this is used for. :type machine_name: str :param machine_name: the machine name to assign. :type machine_name: str

set_user_name(user_name)

Sets the user_name attribute to a new value. TODO: remember what this is used for. :type user_name: str :param user_name: the username to assign. :type user_name: str

to_dic(secret_dictionary, already_generated=False)

Convert the FileConstraints object into a format compatible with our final configuration file NOTE: This will also generate the related file (which may generate secrets itself!). This function will actually be called twice. We do not want to regenerate files we already generated before, hence the already_generated parameter. :rtype: dict :returns: a dictionnary representing the constraint

Parameters:
  • secret_dictionary (dict) – the secret dictionary. Necessary to generate credentials inside files.

  • already_generated (bool) – indicates whether files will need to be regenerated.

Returns:

A dictionary representing the constraint.

class chouchen.constraints.OSConstraints(os_type, os_version)

A class to represent OSConstraints and check their mutual compatibilities. OS constraints contain a type (Windows, Ubuntu…) and a version (<=20.04, ==16.04…).

Current limitations: - A procedure may only have a single OS constraint. If you want to have a procedure that can work on Ubuntu AND Debian, you will have to create 2 different procedures. - If you want to specify specific versions (such as 16.04 OR 20.04), you will have to write it as an interval instead (>=16.04, <=20.04).

os_type

The type of the OS.

Type:

str

os_version str

The acceptable OS versions.

Examples

>>> ubuntu_1604 = OSConstraints("canonical:ubuntu_linux", "==16.04")
>>> debian_10 = OSConstraints("debian:debian_linux", "==10.0")
static default_os_constraint()

Creates a wildcard OS constraint, representing a lack of constraints. Used in fuse_duplicates.

Return type:

OSConstraints

Returns:

a wildcard OS constraint, compatible with everything.

static fuse_duplicates(first, other)

Fuses 2 OS entries that are compatible with each other This is done by getting the smallest constraint for each of Type and Version :type first: OSConstraints :param first: the first contraint to be fused :type first: OSConstraints :type other: OSConstraints :param other: the constraint to fuse with. :type other: OSConstraints

Returns: The fused entry

Return type:

OSConstraints

is_compatible(other)

Checks if two OS constraints are compatible with each other, i.e that they may coexist on the same architecture.

2 os constraints are compatible if: - They represent the same type of OS and have compatible versions. For instance Ubuntu <= 16.04 and Ubuntu == 16.04. - Their OS type are compatible and they have compatible versions. For instance Linux * and Ubuntu == 16.04.

Parameters:

other (OSConstraints) – The constraint to compare against.

Return type:

bool

Returns:

True if the 2 constraints are compatible, False otherwise.

Examples

>>> ubuntu_1604 = OSConstraints("canonical:ubuntu_linux", "==16.04")
>>> ubuntu_2004 = OSConstraints("canonical:ubuntu_linux", "==20.04")
>>> ubuntu_1604.is_compatible(ubuntu_2004)
False
to_dic()

Convert the OSConstraints object into a format compatible with our final configuration file. :rtype: dict :returns: a dictionnary representing the constraint

class chouchen.constraints.SecretPreconditions(requires_secrets, rewards_secrets)

A class handling secret preconditions, which limits which kind of procedures are eligible for a given transition.

For instance: - If a transition requires 1 secret and the procedure doesn’t require any, that procedure is inelegible. - If a transition requires 1 secret and the procedure may give * amount of them, that procedure is eligible. - If a transition requires 1 secret and the procedure may give 1 or more amount of them, that proc is eligible. - If a transition requires 1 secret that has already been generated as an SSH_KEY, and the procedure may give 1 PLAINTEXT_PASSWORD, that proc is ineligible. - If a transition requires 1 secret that has already been generated as an SSH_KEY, and the procedure may give 1 SSH_KEY, that proc is eligible.

self.requires_secrets

a list of SecretPrecondition.

self.rewards_secrets

a list of SecretPrecondition.

is_eligible(transition, secret_dictionary)

Checks whether a procedure is eligible for a given transition.

This is done by checking: - If the number of secrets generated or required by the procedure matches the number of secrets generated or required by the transition. - If the type of secrets generated or required by the transition that have already been generated is a subset of the ones applicable to the procedure.

Parameters:
  • transition (sg.Transition) – the transition to check against.

  • secret_dictionary (dict) – a dictionary containing all secrets that have already been generated.

Return type:

bool

Returns:

update_secret_dictionary_for_transition(secret_dictionary, transition, scenario)

Looks for all the secrets handled by the transition and checks that types are coherent for the secrets that already exist. Then checks that number matches. If all is correct, updates secret_dictionary with all the secrets that were not already in it. IMPORTANT: For now this assumes that procedures will at best require or reward a single type of secret. While it may be possible to handle more than one type and imagine procedures that could reward more than one type (for instance a text leak containing passwords AND ssh keys), it seems just less tedious to add a second transition to the graph instead. :type secret_dictionary: dict :param secret_dictionary: the secret dictionary :type secret_dictionary: dict(secg.secret) :type transition: Transition :param transition: the transition being refined :type transition: sg.Transition :type scenario: SystemGraph :param scenario: the overall scenario. Useful to get user names related to the secret

class chouchen.constraints.SoftwareConstraints(software_type, version, port=-1)

A class to represent software constraints and their compatibilities. Software constraints contain a type (ssh, windows_rdp …), a version (==1.8.2 …) and optionally a port (22 …).

software_type

the software type.

Type:

str

version

the software versions.

Type:

list[(int, str)])

port

the port used by the software. Set to -1 if not initialized.

Type:

int

Examples

>>> sudo_182 = SoftwareConstraints("sudo_project:sudo", "==1.8.27", -1)
is_compatible(other)

Checks if two software constraints are compatible with each other, i.e that they may coexist on the same architecture.

2 software constraints are compatible if: - They represent the same type of software, have compatible versions and the same port. For instance sudo <= 1.9.5 and sudo >= 1.9.0, both with no ports. - They represent 2 different types of software and don’t use the same port. For instance ssh (port 22) and sudo (no port)

Parameters:

other (SoftwareConstraints) – The constraint to check against.

Return type:

bool

Returns:

True if the 2 constraints are compatible, False otherwise.

Examples

>>>
to_dic()

Convert the SoftwareConstraints object into a format compatible with our final configuration file. This is done by converting the type, version and port to their respective configuration format, and outputting a dictionary containing everything.

Return type:

dict

Returns:

a dictionnary representing the constraint

chouchen.file_generation

File generation module.

This module is responsible for generating files needed for the scenario, such as password leaks, copying entire directories, or writing flawed configuration files. Note that generating files may imply generating secrets that go along with them (for instance files containing credentials).

Examples of file generation:
  • Files are copied directly from the static_files directory.

  • Files are entirely written here.

  • Files are naturally present on the machine, and only the line that needs to be changed will be generated here.

The possible type of file edits are as follows:
  • WRITE: the file will be entirely copied onto the machine.

  • APPEND: the contents of the file will be added at the end of a file with the same path on the machine.

  • EDIT “text”: the contents of this file will replace the line containing “text” of a file with the same path.

Files are generated in the GENERATED_FILE_PATH folder, which by default is ./output/scenario_0/generated_files. If more than one scenario are generated (ie if that folder already exists), new files will be generated in ./output/scenario_i/generated_files, with scenario_i being the first available directory number.

chouchen.file_generation.generate_file(file_constraint, secret_dictionary, machine_name, user_name, already_generated=False)

Generates a new file according to file_type and returns its local path and the type of file edit it will be. These have to be written manually for now. This may also reuse values within or update the secret dictionary - for instance if a file has to leak a password. If already_generated is True, the file will not be regenerated and only the location + edit values will be output.

Parameters:
  • machine_name (str) – The name of the related machine (useful to create directory)

  • user_name (str) – The name of the related user (useful for some files)

  • secret_dictionary (dict) – the dictionary of all secrets.

  • file_constraint (FileConstraints) – The type of file that needs to be generated.

  • already_generated (bool) – Indicates whether the file was generated already or not.

Returns:

The location of the generated file and the edit to make to that file.