def write_privesc_graphviz(graph: Graph, filepath: str, file_format: str) -> None: """The function to generate the privesc-only visualization with a Graphviz-generated file: this is only the nodes that are admins/privesc with blue/red highlights.""" pydg = pydot.Dot(graph_type='digraph', overlap='scale', layout='dot', splines='ortho', rankdir='LR', forcelabels='true') pydot_nodes = {} # Need to draw in order of "rank", one new subgraph per-rank, using the edge_list length from the privesc method ranked_nodes = {} for node in graph.nodes: if node.is_admin: if 0 not in ranked_nodes: ranked_nodes[0] = [] ranked_nodes[0].append(node) else: pe, edge_list = can_privesc(graph, node) if pe: if len(edge_list) not in ranked_nodes: ranked_nodes[len(edge_list)] = [] ranked_nodes[len(edge_list)].append(node) for rank in sorted(ranked_nodes.keys()): s = pydot.Subgraph(rank='same') for node in ranked_nodes[rank]: if node.is_admin: # just draw the node and nothing more pydot_node = pydot.Node(node.searchable_name(), style='filled', fillcolor='#BFEFFF', shape='box') pydot_nodes[node] = pydot_node s.add_node(pydot_node) else: # draw the node + add edge pe, edge_list = can_privesc(graph, node) pydot_node = pydot.Node(node.searchable_name(), style='filled', fillcolor='#FADBD8', shape='box') pydot_nodes[node] = pydot_node s.add_node(pydot_node) edge_to_add = pydot.Edge( node.searchable_name(), edge_list[0].destination.searchable_name(), xlabel=edge_list[0].short_reason) pydg.add_edge(edge_to_add) pydg.add_subgraph(s) # and draw pydg.write(filepath, format=file_format)
def generate_graphviz(graph: Graph, nodes: List[Node], edges: List[Edge], filepath: str, file_format: str) -> None: """Draws a graph using Graphviz (dot) with a specific set of nodes and edges.""" pydg = pydot.Dot(graph_type='digraph', overlap='scale', layout='neato', splines='true') pyd_nd = {} for node in nodes: if node.is_admin: color = '#BFEFFF' elif can_privesc(graph, node)[0]: color = '#FADBD8' else: color = 'white' pyd_nd[node] = pydot.Node(node.searchable_name(), style='filled', fillcolor=color, shape='box') pydg.add_node(pyd_nd[node]) for edge in edges: if not edge.source.is_admin: pydg.add_edge( pydot.Edge(pyd_nd[edge.source], pyd_nd[edge.destination], label=edge.short_reason)) # and draw pydg.write(filepath, format=file_format)
def handle_request(graph: Graph, path: str, file_format: str) -> None: """Meat of the graph_writer.py module, writes graph data in a given file-format to the given path.""" # Load graph data into pydot pydg = pydot.Dot(graph_type='digraph', graph_name='Principal Mapper Visualization: {}'.format( graph.metadata['account_id']), overlap='scale', layout='neato', concentrate='true', splines='true') pyd_nd = {} for node in graph.nodes: if node.is_admin: color = '#BFEFFF' elif can_privesc(graph, node)[0]: color = '#FADBD8' else: color = 'white' pyd_nd[node] = pydot.Node(node.searchable_name(), style='filled', fillcolor=color, shape='box') pydg.add_node(pyd_nd[node]) for edge in graph.edges: if not edge.source.is_admin: pydg.add_edge( pydot.Edge(pyd_nd[edge.source], pyd_nd[edge.destination])) # and draw pydg.write(path, format=file_format)
def write_standard_graphviz(graph: Graph, filepath: str, file_format: str, with_services: Optional[bool] = False) -> None: """The function to generate the standard visualization with a Graphviz-generated file: this is all the nodes with the admins/privesc highlights in blue/red respectively.""" # Load graph data into pydot pydg = pydot.Dot(graph_type='digraph', graph_name='Principal Mapper Visualization: {}'.format( graph.metadata['account_id']), overlap='scale', layout='neato', concentrate='true', splines='true') pyd_nd = {} # Draw standard nodes and edges: users/roles for node in graph.nodes: if node.is_admin: color = '#BFEFFF' elif can_privesc(graph, node)[0]: color = '#FADBD8' else: color = 'white' pyd_nd[node] = pydot.Node(node.searchable_name(), style='filled', fillcolor=color, shape='box') pydg.add_node(pyd_nd[node]) for edge in graph.edges: if not edge.source.is_admin: pydg.add_edge( pydot.Edge(pyd_nd[edge.source], pyd_nd[edge.destination])) # draw service nodes and edges if with_services: sam = compose_service_access_map(graph) for service in sam.keys(): pyd_nd[service] = pydot.Node(service, style='filled', fillcolor='#DDFFDD') pydg.add_node(pyd_nd[service]) for service, node_list in sam.items(): for node in node_list: pydg.add_edge(pydot.Edge(pyd_nd[service], pyd_nd[node])) # and draw pydg.write(filepath, format=file_format)
def write_privesc_graphml(graph: Graph, filepath: str) -> None: """The function to generate the privesc-only visualization with a GraphML file: this is only the nodes that are admins/privesc with blue/red highlights.""" node_color_mapping = {} for node in graph.nodes: if node.is_admin: node_color_mapping[node] = 'blue' elif can_privesc(graph, node)[0]: node_color_mapping[node] = 'red' result = ET.ElementTree(ET.Element(None)) generate_graphml(node_color_mapping, graph.edges, result) result.write(filepath, 'utf-8', True)
def write_standard_graphml(graph: Graph, filepath: str) -> None: """The function to generate the standard visualization with a GraphML file: this is all the nodes with the admins/privesc highlights in blue/red respectively.""" node_color_mapping = {} for node in graph.nodes: if node.is_admin: node_color_mapping[node] = 'blue' elif can_privesc(graph, node)[0]: node_color_mapping[node] = 'red' else: node_color_mapping[node] = 'white' result = ET.ElementTree(ET.Element(None)) generate_graphml(node_color_mapping, graph.edges, result) result.write(filepath, 'utf-8', True)
def gen_privesc_findings(graph: Graph) -> List[Finding]: """Generates findings related to privilege escalation risks.""" result = [] node_path_list = [] for node in graph.nodes: if node.is_admin: continue # skip current admins privesc_res, edge_list = can_privesc(graph, node) if privesc_res: node_path_list.append((node, edge_list)) if len(node_path_list) > 0: description_preamble = 'In AWS, IAM Principals such as IAM Users or IAM Roles have their permissions defined ' \ 'using IAM Policies. These policies describe different actions, resources, and ' \ 'conditions where the principal can make a given API call to a service.\n\n' \ 'Administrative principals can call any action with any resource, as in the ' \ 'AdministratorAccess AWS-managed policy. However, some permissions may allow another ' \ 'non-administrative principal to gain access to an administrative principal. ' \ 'This represents a privilege escalation risk.\n\n' \ 'The following principals could escalate privileges:\n\n' description_body = '' for node, edge_list in node_path_list: end_of_list = edge_list[-1].destination description_body += '* {} can escalate privileges by accessing the administrative principal {}:\n'.format( node.searchable_name(), end_of_list.searchable_name()) for edge in edge_list: description_body += ' * {}\n'.format(edge.describe_edge()) description_body += '\n' result.append( Finding( 'IAM {} Can Escalate Privileges'.format( 'Principals' if len(node_path_list) > 1 else 'Principal'), 'High', 'A lower-privilege IAM User or Role is able to gain administrative privileges. This could lead to the ' 'lower-privilege principal being used to compromise the account and its resources.', description_preamble + description_body, 'Review the IAM Policies that are applicable to the affected IAM User(s) or Role(s). Either reduce the ' 'permissions of the administrative principal(s), or reduce the permissions of the principal(s) that can ' 'access the administrative principals.')) return result