├── requirements.txt ├── Results ├── source.csv └── Results.csv ├── Figures ├── framework_gui.png ├── network_model.png ├── framework_architecture.png └── functionality_overview.png ├── CONTRIBUTORS ├── CHANGES ├── Scripts ├── Topology.py ├── plotResult.py ├── Node.py ├── Harm.py ├── Vulnerability.py ├── Network.py ├── glmMap.py ├── AttackGraph.py ├── plotMetrics.py ├── SecurityEvaluator.py ├── AttackTree.py └── NetGen.py ├── LICENSE ├── README.md ├── developer_guide.md ├── GLM └── IEEE_4_Node.glm ├── database.json └── GridAttackAnalyzer.py /requirements.txt: -------------------------------------------------------------------------------- 1 | graphviz 2 | numpy==1.14.6 3 | pyvis 4 | pandas 5 | matplotlib 6 | -------------------------------------------------------------------------------- /Results/source.csv: -------------------------------------------------------------------------------- 1 | Device,CVE,CVSS Base Score 2.0,Impact Subcore,Exploitability Subscore 2 | -------------------------------------------------------------------------------- /Figures/framework_gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crond-jaist/GridAttackAnalyzer/HEAD/Figures/framework_gui.png -------------------------------------------------------------------------------- /Figures/network_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crond-jaist/GridAttackAnalyzer/HEAD/Figures/network_model.png -------------------------------------------------------------------------------- /Figures/framework_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crond-jaist/GridAttackAnalyzer/HEAD/Figures/framework_architecture.png -------------------------------------------------------------------------------- /Figures/functionality_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crond-jaist/GridAttackAnalyzer/HEAD/Figures/functionality_overview.png -------------------------------------------------------------------------------- /Results/Results.csv: -------------------------------------------------------------------------------- 1 | Time,Case Study Name,Streets and Houses Vector,Attack Success Probability,Attack Cost,Attack Impact,Attack Risk,Number of Devices,Average Attack Cost,Average Attack Impact,Number of Paths,Rare Paths,Unlilkely Paths ,Possible Paths,Likely Paths ,Almost Certain Paths 2 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | This file includes the main contributors to the GridAttackAnalyzer project. 2 | 3 | Initial implementation: 4 | Tan Duy Le (main developer) 5 | Mengmeng Ge (revised the AG/AT/HARM implementation; 6 | initial network/node/vulnerability classes & metric calculation) 7 | Jin B. Hong (initial AG/AT/HARM implementation) 8 | 9 | Current maintainers: 10 | Razvan Beuran 11 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | 2 | GridAttackAnalyzer v1.0 3 | ----------------------- 4 | * First public release of GridAttackAnalyzer, the smart grid attack 5 | analysis framework that makes it possible to determine attack paths 6 | and calculate various security metrics for a given smart grid model 7 | and cyber-attack scenario. The framework is extensible by end users, 8 | and the current release includes three smart grid models, more than 9 | 20 types of smart grid devices, and 40 types of vulnerabilities. 10 | -------------------------------------------------------------------------------- /Scripts/Topology.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains topology object. 3 | """ 4 | 5 | class topology(object): 6 | """ 7 | Create topology object. 8 | """ 9 | def __init__(self, topoType, nodeNo): 10 | topoType = topoType.lower() 11 | 12 | if topoType not in ['tree', 'partialmesh', 'fullmesh']: 13 | return None 14 | else : 15 | #Assign topology type 16 | self.topoType = topoType 17 | #Assign node number 18 | self.nodeNo = nodeNo 19 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Japan Advanced Institute of Science and Technology 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Scripts/plotResult.py: -------------------------------------------------------------------------------- 1 | from pyvis.network import Network 2 | import pandas as pd 3 | def plotResult(file_in, file_out): 4 | got_net = Network(height="100%", width="100%", bgcolor="white", font_color="black") 5 | 6 | got_net.barnes_hut() 7 | got_data = pd.read_csv(file_in) 8 | 9 | sources = got_data['From'] 10 | targets = got_data['To'] 11 | weights = got_data['Weight'] 12 | x_array = got_data['x'] 13 | y_array = got_data['y'] 14 | highlight = got_data['Highlight'] 15 | 16 | edge_data = zip(sources, targets, weights, x_array, y_array, highlight) 17 | 18 | for e in edge_data: 19 | src = e[0] 20 | dst = e[1] 21 | w = e[2] 22 | x_array = e[3] 23 | y_array = e[4] 24 | h = e[5] 25 | v_color_dst = "#ADFF2F" #Green color 26 | image_url = 'cat.png' 27 | v_shape = 'dot' 28 | 29 | 30 | 31 | 32 | if(dst in ['central_concentrator'] or h==1): 33 | image_url = 'cat.png' 34 | v_shape = 'circle' 35 | 36 | 37 | got_net.add_node(src, shape = v_shape, title=src, x=x_array, y=y_array, color = "#ADFF2F") #Green color 38 | got_net.add_node(dst, shape = v_shape, title=dst, x = x_array, y = y_array, color = v_color_dst) 39 | got_net.add_edge(src, dst, value=h) 40 | 41 | neighbor_map = got_net.get_adj_list() 42 | 43 | # add neighbor data to node hover data 44 | for node in got_net.nodes: 45 | node["title"] += " Neighbors:
" + "
".join(neighbor_map[node["id"]]) 46 | node["value"] = len(neighbor_map[node["id"]]) 47 | 48 | 49 | 50 | 51 | got_net.set_options('var options = { "edges": { "arrows": { "middle": { "enabled": true } }, "color": { "inherit": "false"}, "smooth": false},"physics": {"enabled": true, "forceAtlas2Based":{ "gravitationalConstant": -500, "springLength": 100, "avoidOverlap": 1}, "minVelocity": 0.75, "solver": "forceAtlas2Based"}}') 52 | 53 | 54 | got_net.show(file_out) 55 | -------------------------------------------------------------------------------- /Scripts/Node.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains node objects 3 | """ 4 | 5 | from random import * 6 | from math import * 7 | 8 | class node(object): 9 | """ 10 | Create basic node object. 11 | """ 12 | def __init__(self, name): 13 | self.name = name 14 | #Set connections 15 | self.con = [] 16 | #Store lower layer info 17 | self.child = None 18 | #Store a list of parent nodes 19 | self.parent = None 20 | #Set default value of start/end 21 | self.isStart = False 22 | self.isEnd = False 23 | self.subnet = [] 24 | self.vul = None 25 | 26 | #Set the node as normal/start/end 27 | def setStart(self): 28 | self.isStart = True 29 | def setNormal(self): 30 | self.isStart = False 31 | self.isEnd = False 32 | def setEnd(self): 33 | self.isEnd = True 34 | #Check whether the node is leaf or not 35 | def isLeaf(self): 36 | return (len(self.con) is 1) 37 | 38 | class iot(node): 39 | """ 40 | Create IoT device object. 41 | """ 42 | def __init__(self, name): 43 | super(iot, self).__init__(name) 44 | self.type = None 45 | 46 | def checkNodeInCons(self, node1, node2): 47 | """ 48 | Check whether the node1 is in the connections of node2. 49 | """ 50 | for temp in node2.con: 51 | if node1.name == temp.name: 52 | return 1 53 | 54 | return 0 55 | 56 | def checkNodeInList(self, list): 57 | """ 58 | Check whether the node is in the list or not. 59 | """ 60 | for temp in list: 61 | if self.name == temp.name: 62 | return True 63 | 64 | return False 65 | 66 | 67 | class computer(node): 68 | """ 69 | Create computer object. 70 | Could be used for the attacker node. 71 | """ 72 | def __init__(self, name): 73 | super(computer, self).__init__(name) 74 | self.type = None 75 | 76 | -------------------------------------------------------------------------------- /Scripts/Harm.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module constructs HARMs using AG, AT in both upper and lower layers. 3 | """ 4 | 5 | 6 | from AttackGraph import * 7 | from AttackTree import * 8 | 9 | class harm(object): 10 | """ 11 | Create harm object. 12 | """ 13 | def __init__(self): 14 | self.model = None 15 | 16 | def constructHarm(self, net, up, valueUp, lo, valueLow, pri): 17 | self.model = self.makeHARM(net, up, valueUp, lo, valueLow, pri) 18 | 19 | 20 | def addToTreeRecursive(self, gate, childType, val, pri): 21 | for u in gate.con: 22 | #print(u.name) 23 | if u.t is "node": 24 | if (u.n is not None) and (u.n.vul is not None): 25 | childType = childType.lower() 26 | if childType.find("attacktree") >= 0: 27 | u.child = at(u.n.vul, val, pri) 28 | elif childType.find("attackgraph") >= 0: 29 | u.child = ag(u.n.vul, val, pri) 30 | else: 31 | print("Error") 32 | else: 33 | self.addToTreeRecursive(u, childType, val, pri) 34 | 35 | def addToTree(self, aT, childType, val, pri): 36 | self.addToTreeRecursive(aT.topGate, childType, val, pri) 37 | 38 | def addToGraph(self, aG, childType, val, pri): 39 | for u in aG.nodes: 40 | if (u.n is not None) and (u.n.vul is not None): 41 | childType = childType.lower() 42 | if childType.find("attacktree") >= 0: 43 | u.child = at(u.n.vul, val, pri) 44 | elif childType.find("attackgraph") >= 0: 45 | u.child = ag(u.n.vul, val, pri) 46 | else: 47 | print("Error") 48 | 49 | def makeHARM(self, net, up, vu, lo, vl, pri): 50 | """ 51 | Construct HARM. 52 | 53 | :param net: network 54 | :param up: upper layer type 55 | :param vu: assign a default value to val parameter for node, no real meaning when initializing, changed and used in security analysis 56 | :param lo: lower layer type 57 | :param vl: assign a default value to val parameter for vulnerability, no real meaning when initializing, changed and used in security analysis 58 | :param pri: assign a privilege value in construction of lower layer vulnerability connections 59 | :returns: HARM: contains two layers, when using AGAT, \ 60 | the upper layer is attack graph listing nodes and attack paths \ 61 | each node has a lower layer which stored in child parameter, containing vulnerability tree 62 | """ 63 | 64 | up = up.lower() 65 | 66 | #Construct upper layer 67 | if up.find("attacktree") >= 0: 68 | print("Upper layer constructed") 69 | harm = at(net, vu) 70 | elif up.find("attackgraph") >= 0: 71 | harm = ag(net, vu) 72 | else: 73 | harm = None 74 | print("HARM construction error") 75 | 76 | #Add lower layer to upper layer 77 | if harm is not None: 78 | print("Lower layer constructed") 79 | if type(harm) is ag: 80 | self.addToGraph(harm, lo, vl, pri) 81 | harm.calcPath() #Compute attack path 82 | else: 83 | self.addToTree(harm, lo, vl, pri) 84 | 85 | return harm 86 | 87 | 88 | -------------------------------------------------------------------------------- /Scripts/Vulnerability.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains vulnerability class and relevant functions. 3 | """ 4 | 5 | from Network import * 6 | 7 | class vulNode(object): 8 | """ 9 | Create vulnerability object. 10 | """ 11 | def __init__(self, name): 12 | #Initialize privilege value 13 | self.privilege = None 14 | #Initialize value for input of the HARM 15 | self.val = 0 16 | #Initialize name 17 | self.name = name 18 | self.con = [] 19 | #Initialize metric values 20 | self.pro = 0 21 | self.impact = 0 22 | self.cost = 0 23 | self.risk = 0 24 | self.return_on_attack = 0 25 | 26 | 27 | def __str__(self): 28 | return self.name 29 | 30 | def createVuls(self, node, metricValue, pri): 31 | """ 32 | Create vulnerability network for the node. 33 | :param node: node in the network which has vulnerabilities 34 | :param metricValue: assign a metric value to vulnerability (e.g. attack probability) 35 | :param pri: assign privilege value to vulnerability (1: user; 2: admin; 3: root) 36 | :returns: none 37 | """ 38 | 39 | #Instantiate vulnerability parameters 40 | self.val = metricValue 41 | self.privilege = pri 42 | 43 | #Create a vulnerability network for the node 44 | if node.vul is None: 45 | node.vul = network() 46 | 47 | #Add vulnerability into the vulnerability network of node 48 | node.vul.nodes.append(self) 49 | 50 | return None 51 | 52 | def thresholdPri(self, node, t): 53 | """ 54 | Set start point for the vulnerability network and link vulnerabilities to the start point. 55 | 56 | :param node: node in the network which has vulnerabilities 57 | :param t: privilege value for the start point 58 | :returns None 59 | """ 60 | 61 | node.vul.s = vulNode('s') 62 | s = node.vul.s 63 | del s.con[:] 64 | for v in node.vul.nodes: 65 | if v.privilege <= t: 66 | s.con.append(v) 67 | return None 68 | 69 | def terminalPri(self, node, t): 70 | """ 71 | Set end point for the vulnerability network and link vulnerabilities to the end point. 72 | 73 | :param node: node in the network which has vulnerabilities 74 | :param t: privilege value for the end point 75 | :returns None 76 | """ 77 | 78 | node.vul.e = vulNode('e') 79 | e = node.vul.e 80 | del e.con[:] 81 | for v in node.vul.nodes: 82 | if v.privilege >= t: 83 | v.con.append(e) 84 | return None 85 | 86 | def assignRisk(self, pro, ai): 87 | ''' 88 | Risk = Probability * Impact 89 | ''' 90 | self.pro = pro 91 | self.impact = ai 92 | self.risk = self.pro * self.impact 93 | self.val = self.risk 94 | return None 95 | 96 | def assignReturnOnAttack(self, pro, ai, cost): 97 | ''' 98 | Return on Attack Path = Probability * Impact / Cost 99 | ''' 100 | self.pro = pro 101 | self.impact = ai 102 | self.cost = cost 103 | self.return_on_attack = float(self.pro * self.impact / self.cost) 104 | self.val = self.return_on_attack 105 | return None 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /Scripts/Network.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains network object and relevant functions. 3 | """ 4 | 5 | from Node import * 6 | from Topology import * 7 | import copy 8 | 9 | class network(object): 10 | """ 11 | Create network object. 12 | """ 13 | def __init__(self): 14 | #Initialize node list 15 | self.nodes = [] 16 | #Initialize start and end points 17 | self.s = None 18 | self.e = None 19 | #Initialize subnets 20 | self.subnets = [] 21 | #Initialize vulnerability list which contains all node vulnerabilities 22 | self.vuls = [] 23 | 24 | def copyNet(self): 25 | """ 26 | Copy the network to a network. 27 | """ 28 | 29 | temp = network() 30 | temp = copy.deepcopy(self) 31 | 32 | return temp 33 | 34 | def constructSE(self): 35 | """ 36 | Set the start and end in the network. 37 | """ 38 | self.s = node('S-') 39 | self.e = node('E-') 40 | 41 | for n in self.nodes: 42 | if n.isStart: 43 | self.s.con.append(n) 44 | if n.isEnd: 45 | n.con.append(self.e) 46 | 47 | 48 | def connectOneWay(self, node1, node2): 49 | """ 50 | Connect node1 to node2 in the network. 51 | """ 52 | #no self connection 53 | if node1 is node2: 54 | return None 55 | #connect node1 to node2 56 | if (node2 not in node1.con): 57 | node1.con.append(node2) 58 | 59 | 60 | def connectTwoWays(self, node1, node2): 61 | """ 62 | Connect node1 with node2 in the network. 63 | """ 64 | #no self connection 65 | if node1 is node2: 66 | return None 67 | #create connections 68 | if (node2 not in node1.con): 69 | node1.con.append(node2) 70 | if (node1 not in node2.con): 71 | node2.con.append(node1) 72 | 73 | def disconnectTwoWays(self, node1, node2): 74 | """ 75 | Disconnect node1 and node2 in the network. 76 | """ 77 | if node2 in node1.con: 78 | node1.con.remove(node2) 79 | if node1 in node2.con: 80 | node2.con.remove(node1) 81 | 82 | def printNet(self): 83 | """ 84 | Print network. 85 | """ 86 | for node in self.nodes: 87 | print(node.name+":", node.type) 88 | print("connect:",) 89 | for conNode in node.con: 90 | print(conNode.name) 91 | print("-----------------------------") 92 | return None 93 | 94 | def printNetWithVul(self): 95 | """ 96 | Print network with vulnerabilities. 97 | """ 98 | for node in self.nodes: 99 | #print(node.name+":", node.type, ",", node.sec) 100 | print(node.name +" : ") 101 | print("connect:",) 102 | for conNode in node.con: 103 | if conNode.name == 'S-' or conNode.name == 'E-': 104 | print(conNode.name) 105 | else: 106 | print(conNode.name) 107 | 108 | print("vulnerability:",) 109 | if node.vul is not None: 110 | for vul in node.vul.nodes: 111 | #print(vul.name+":", vul.type, ",", vul.val) 112 | print(vul.name+":", vul.val) 113 | print("------------------------------") 114 | 115 | return None 116 | -------------------------------------------------------------------------------- /Scripts/glmMap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | #Pre-requisites software required to run this script: 4 | #Python 2.7: Modules:: sys, re, os 5 | #Graphviz 6 | ##If you just installed Graphviz, "restart" your computer for Graphviz commandline execution to work 7 | 8 | #Purpose of this script and How can a modeler use it: 9 | #Convert a GridLAB-D file (.glm) to a Graphviz file (.dot) 10 | #Use that .dot to generate .svg 11 | #Use any web browser to open the .svg file - SIMPLE 12 | 13 | #Usage: 14 | #python glmMap.py inputfile.glm outputfile.dot 15 | 16 | #Licensing: Copyright 2016; licensing falls under GridLAB-D 17 | #Email Contact: sri at pnnl dot gov 18 | #Devloped at: Pacific Northwest National Laboratory 19 | #Acknowledgements: This tool is developed using a base script provided by Philip Douglass, Technical University of Denmark in 2013 20 | 21 | #Version and Patch Notes: 22 | ##05/20/2016: Added :: line spacing; object names; object color coding 23 | ##06/02/2016: Bug Fix :: If the node names has ':' in it, grapgviz errors out. replaced ':' with '_' as well 24 | 25 | import sys; 26 | import re; 27 | import os 28 | 29 | infile = open(sys.argv[1], 'r') 30 | outfile = open(sys.argv[2], 'w') 31 | 32 | lines = infile.readlines() 33 | 34 | # .dot files begin with the 'graph' keyword. 35 | outfile.write("digraph {\n") 36 | outfile.write("node [shape=box]\n") 37 | # These are state variables. There is no error checking, so we rely on 38 | # well formatted *.GLM files. 39 | s =0 40 | state = 'start' 41 | edge_color = 'black' 42 | edge_style = 'solid' 43 | lengthVal = 'None' 44 | lineLengthIncrement = 0 45 | 46 | # Loop through each line in the input file... 47 | while s < len(lines): 48 | # Discard Comments 49 | if re.match("//", lines[s]) == None: 50 | if re.search("from", lines[s])!=None: 51 | ts = lines[s].split() 52 | #Graphvis format can't handle '-' characters, so they are converted to '_' 53 | ns = ts[1].rstrip(';').replace('-','_').replace(':','_') 54 | outfile.write(ns) 55 | state = 'after_from' 56 | elif state == 'after_from' and re.search("to ", lines[s])!=None: 57 | ts = lines[s].split() 58 | ns = ts[1].rstrip(';').replace('-','_').replace(':','_') 59 | if edge_color == 'red': 60 | outfile.write(' -> ' + ns + '[style=' + edge_style + ' color='+ edge_color + ' label="'+lengthVal+'"]\n') 61 | outfile.write("node [shape=box]\n") 62 | else: 63 | outfile.write(' -> ' + ns + '[style=' + edge_style + ' color='+ edge_color + ' label="'+lengthVal+'"]\n') 64 | lengthVal = 'None' 65 | # After an edge is added to the graph, reset the states back to default 66 | state = 'start' 67 | edge_color = 'black' 68 | edge_style = 'solid' 69 | elif (re.search("object underground_line", lines[s]) != None) or (re.search("object overhead_line", lines[s]) != None): 70 | while '}' not in lines[s+lineLengthIncrement]: 71 | if re.search("length ", lines[s+lineLengthIncrement]) != None: 72 | les = lines[s+lineLengthIncrement].split() 73 | if len(les) > 2: 74 | tsVal = les[1]+' '+les[2].strip(';') 75 | lengthVal = tsVal 76 | elif len(les)<=2: 77 | tsVal = les[1].strip(';') 78 | lengthVal = tsVal 79 | break 80 | else: 81 | lineLengthIncrement = lineLengthIncrement+1 82 | if '}' in lines[s+lineLengthIncrement]: 83 | lineLengthIncrement = 0 84 | lengthVal = 'None' 85 | break 86 | if re.search("object underground_line", lines[s]) != None: 87 | lengthVal = "UG_line\\n"+lengthVal 88 | elif re.search("object overhead_line", lines[s]) != None: 89 | lengthVal = "OH_line\\n"+lengthVal 90 | elif re.search("object transformer", lines[s]) != None: 91 | edge_color='red' 92 | lengthVal = "transformer\\n"+lengthVal 93 | outfile.write("node [shape=oval]\n") 94 | elif re.search("object triplex_line", lines[s]) != None: 95 | edge_color='green' 96 | lengthVal = "triplex_line\\n"+lengthVal 97 | elif re.search("object fuse", lines[s]) != None: 98 | edge_color='blue' 99 | lengthVal = "Fuse\\n"+lengthVal 100 | elif re.search("phases ", lines[s]) != None: 101 | ts = lines[s].split() 102 | if len(ts[1].rstrip(';')) > 3: 103 | edge_style = 'bold' 104 | elif len(ts[1].rstrip(';')) > 2: 105 | edge_style = 'dashed' 106 | 107 | s+=1 108 | 109 | 110 | outfile.write("}\n") 111 | infile.close() 112 | outfile.close() 113 | os.system("dot -Tsvg -O "+sys.argv[2]) 114 | 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # GridAttackAnalyzer: Smart Grid Attack Analysis Framework 3 | 4 | GridAttackAnalyzer is a smart grid attack analysis framework that 5 | makes it possible to determine attack paths and calculate various 6 | security metrics for a given smart grid model and cyber-attack 7 | scenario. The framework is extensible by end users, and the current 8 | release includes three smart grid models, more than 20 types of smart 9 | grid devices, and 40 types of vulnerabilities. GridAttackAnalyzer is 10 | being developed by the Cyber Range Organization and Design 11 | ([CROND](https://www.jaist.ac.jp/misc/crond/index-en.html)) 12 | NEC-endowed chair at the Japan Advanced Institute of Science and 13 | Technology ([JAIST](https://www.jaist.ac.jp/english/)) in Ishikawa, 14 | Japan. 15 | 16 | The functionality that GridAttackAnalyzer provides to assist with the 17 | smart grid attack analysis includes attack tree and attack graph 18 | generation, as well as the computation of attack success probability, 19 | attack impact, attack cost, and attack risk. These characteristics can 20 | be visualized as shown below. 21 | 22 | ![GridAttackAnalyzer Overview](Figures/functionality_overview.png "Overview of the GridAttackAnalyzer functionality") 23 | 24 | Since GridAttackAnalyzer helps determine all the possible attack paths 25 | in a smart grid, evaluate the consequences of various cyber-attacks, 26 | and estimate the potential damage, several uses of the framework are 27 | possible. Thus, researchers can study how IoT device vulnerabilities 28 | can affect smart grid security, which are the most vulnerable devices 29 | that need to be protected first in order to prevent worst 30 | consequences, and also evaluate objectively the efficiency of various 31 | mitigation strategies. 32 | 33 | The architecture of GridAttackAnalyzer, pictured in the figure below, 34 | has the following components: 35 | * **Smart Grid Model**: Definitions of network and power grid models 36 | * **Database**: Information about device types and network 37 | vulnerabilities 38 | * **Attack Scenarios**: Definition of the attack scenario (entry 39 | points, targets, vulnerabilities) 40 | * **Database Manager**: Interface that interacts with the end users to 41 | provide the necessary information from the database to the attack 42 | analysis manager 43 | * **Attack Analysis Manager**: Core module that manages the entire 44 | attack analysis process 45 | * **Attack Model Generator**: Creates a model for the attack given the 46 | input parameters provided by the Analysis Manager 47 | * **Attack Model Evaluator**: Determines the **Attack Graph** and 48 | calculates the **Security Metrics** corresponding to a given attack 49 | scenario 50 | 51 | ![GridAttackAnalyzer Architecture](Figures/framework_architecture.png "Architecture of the GridAttackAnalyzer framework") 52 | 53 | End users can extend the features provided by GridAttackAnalyzer by 54 | adding new smart grid topologies, new devices, and new network 55 | vulnerabilities. Please consult the [Developer 56 | Guide](/developer_guide.md) for more information on how to perform 57 | such extensions. 58 | 59 | 60 | ## Installation 61 | 62 | GridAttackAnalyzer was developed and tested using Python 3 on the 63 | Ubuntu 18.04 LTS operating system; either a physical host or a virtual 64 | machine installation can be used. Other OSes may work too if they 65 | support running Python 3. 66 | 67 | To install GridAttackAnalyzer, use the 68 | [releases](https://github.com/crond-jaist/GridAttackAnalyzer/releases) 69 | page to download the latest version, and extract the source code 70 | archive into a directory of your choice. Then follow the steps below 71 | to set up all the additional components: 72 | 73 | 1. Install Tkinter, a Python binding to the Tk GUI toolkit, which was 74 | used to develop the GridAttackAnalyzer GUI: 75 | ``` 76 | sudo apt-get install python3-tk 77 | ``` 78 | 2. Install the `pip3` packet manager: 79 | ``` 80 | sudo apt install python3-pip 81 | ``` 82 | 3. Install other packages required by GridAttackAnalyzer as specified 83 | in the file `requirements.txt` included with the distribution by 84 | running the following command from the `GridAttackAnalyzer/` 85 | directory: 86 | ``` 87 | sudo -H pip3 install -r requirements.txt 88 | ``` 89 | 90 | 91 | ## Quick Start 92 | 93 | 1. Use a terminal window to navigate to the GridAttackAnalyzer 94 | directory, then start the GUI by running the command below: 95 | ``` 96 | python3 GridAttackAnalyzer.py 97 | ``` 98 | 99 | ![GridAttackAnalyzer GUI](Figures/framework_gui.png "GUI of the GridAttackAnalyzer framework") 100 | 101 | 2. Once the GUI is displayed, as shown above, follow the next steps to 102 | configure and run the analysis: 103 | 104 | 1. Select the input parameters: Smart Grid Model, Smart Meter 105 | Connection, CVEs, Devices and Vulnerabilities. The built-in 106 | network model is shown below; please consider it when selecting 107 | the devices to make sure that end-to-end routes exist. 108 | 109 | ![Network Model](Figures/network_model.png "Built-in smart grid network model") 110 | 111 | 2. Click on the "Generate File" button to generate the initial 112 | attack analysis file; this file can be fine tuned if desired by: 113 | * _Option 1:_ Click on "1 - Manual Edit" to open the file, then 114 | edit it directly. 115 | * _Option 2:_ Use "Edit a Device & CVE" to select a specific 116 | device and assign a new CVE value to it, then click on 117 | "Update" to bring the attack analysis file up to date. 118 | 119 | 3. Click on the "Run" button to start the attack analysis. 120 | 121 | 4. When the attack analysis process finishes, you can use any of 122 | the following functions by clicking on the corresponding 123 | buttons: 124 | * Attack Graph: Show the computed attack graph 125 | * Graph Source File": Show the CSV file containing the attack 126 | graph data 127 | * Security Metrics: Show the CSV file containing the 128 | computed security metrics 129 | * Plot Metrics: Visualize the computed security metrics 130 | 131 | 132 | ## References 133 | 134 | For a research background regarding GridAttackAnalyzer, please refer to the 135 | following papers: 136 | 137 | * T. D. Le, M. Ge, P. T. Duy, H. D. Hoang, A. Anwar, S. W. Loke, 138 | R. Beuran, Y. Tan, "CVSS Based Attack Analysis using a Graphical 139 | Security Model: Review and Smart Grid Case Study", 4th EAI 140 | International Conference on Smart Grid and Internet of Things (SGIoT 141 | 2020), TaiChung, Taiwan, December 5-6, 2020, pp. 116-134. 142 | 143 | For a list of contributors to this project, check the file 144 | CONTRIBUTORS included with the source code. 145 | -------------------------------------------------------------------------------- /developer_guide.md: -------------------------------------------------------------------------------- 1 | 2 | # Developer Guide 3 | 4 | In this document we provide information to help those researchers 5 | interested in extending the functionality of GridAttackAnalyzer, for 6 | example by adding new smart grid models, new devices or new network 7 | vulnerabilities. 8 | 9 | 10 | ## File Overview 11 | 12 | The GridAttackAnalyzer release contains a large number of files, and 13 | we provide an overview below to facilitate further development and 14 | extensions of the software. 15 | 16 | ``` 17 | ├── GLM/ # Folder with power grid models (GLM files) 18 | ├── Results/ # Folder for attack analysis files 19 | │ ├── source.csv # Input file for analysis 20 | │ └── Results.csv # Output file for analysis 21 | ├── Scripts/ # Folder for scripts used internally 22 | │ ├── AttackGraph.py # Construct the attack graph (AG) 23 | │ ├── AttackTree.py # Construct the attack tree (AT) 24 | │ ├── Harm.py # Construct HARMs using AG and AT 25 | │ ├── NetGen.py # Generate IoT network based on attack scenario 26 | │ ├── Network.py # Network object and relevant functions 27 | │ ├── Node.py # Node object 28 | │ ├── SecurityEvaluator.py # Conduct security analysis 29 | │ ├── Topology.py # Topology object 30 | │ ├── Vulnerability.py # Vulnerability object and relevant functions 31 | │ ├── glmMap.py # Convert power grid model GLM file to graph 32 | │ ├── plotMetrics.py # Plot security metrics 33 | │ └── plotResult.py # Plot the attack graph 34 | ├── GridAttackAnalyzer.py # Main application GUI file 35 | └── database.json # Attack library in JSON format 36 | ``` 37 | 38 | 39 | ## How to Add New Objects 40 | 41 | GridAttackAnalyzer makes it possible to analyze various types of 42 | cyber-attacks on smart grids. These attacks are modeled via 43 | information stored in a database that uses the JSON format to 44 | represent three types of objects: 45 | * Power grid model: Abstraction of a smart grid topology with houses 46 | grouped in "streets" 47 | * Network device: Abstraction of a smart grid device with a list of 48 | vulnerabilities associated to it 49 | * Network vulnerability: An IoT device vulnerability represented using 50 | the CVE format 51 | 52 | The database includes three lists of objects, one for each type of 53 | object mentioned above, and is structured as follows: 54 | 55 | ``` 56 | ├── "object" # JSON object containing the three lists 57 | │ ├── object_id # Power grid model list ID 58 | │ │ ├── name # Name of the object 59 | │ │ ├── description # Power grid model list description 60 | │ │ └── model_list # Power grid model list 61 | │ │ ├── list_name # Model name 62 | │ │ └── streets_and_houses # Areas and number of houses for each area 63 | │ ├── object_id # Devices list ID 64 | │ │ ├── name # Name of the object 65 | │ │ ├── description # Device list description 66 | │ │ └── devices_list # Device list 67 | │ │ ├── device_name # Device name 68 | │ │ ├── CVE_list # List of vulnerabilities for that device 69 | │ │ └── group # Device group 70 | │ └── object_id # CVE list ID 71 | │ ├── name # Name of the object 72 | │ ├── description # CVE list description 73 | │ └── CVE list # CVE list 74 | │ ├── CVE # CVE ID 75 | │ ├── description # CVE description 76 | │ ├── CVSS_Base_Score_2.0 # CVSS Base Score 2.0 77 | │ ├── Impact_Subscore # Impact Subscore 78 | └── └── Exploitability_Subscore # Exploitability Subscore 79 | ``` 80 | 81 | We present below an example database with three objects: a power grid 82 | model named "IEEE 4 Node", a device named "Smart TV" and a 83 | vulnerability with the ID "CVE-2017-9944". 84 | 85 | ``` 86 | { 87 | "type": "attack_tree_library", 88 | "version": "1.0", 89 | "created": "2021-01-01", 90 | "object": [ 91 | { 92 | "object_id": 0, 93 | "name": "Power Grid Model", 94 | "description": "Power Grid Model", 95 | "model_list":[ 96 | { 97 | "list_name": "IEEE 4 Node", 98 | "streets_and_houses": [ 99 | { 100 | "A": 5, 101 | "B": 6, 102 | "C": 8, 103 | "D": 12 104 | } 105 | ] 106 | }, 107 | }, 108 | { 109 | "object_id": 1, 110 | "name": "Devices", 111 | "description": "Devices List", 112 | "devices_list":[ 113 | { 114 | "device_name": "Smart TV", 115 | "CVE_list": ["CVE-2019-9871","CVE-2019-11336", "CVE-2019-12477", "CVE-2018-13989", "CVE-2020-9264"], 116 | "group": 1 117 | } 118 | ] 119 | }, 120 | { 121 | "object_id": 2, 122 | "name": "CVE", 123 | "description": "CVE List", 124 | "CVE_list":[ 125 | { 126 | "CVE": "CVE-2017-9944", 127 | "description": "Allow an unauthenticated remote attacker to perform administrative operations over the network", 128 | "CVSS_Base_Score_2.0": 10, 129 | "Impact_Subscore": 10, 130 | "Exploitability_Subscore": 10 131 | } 132 | ] 133 | } 134 | ] 135 | } 136 | ``` 137 | 138 | In order to add a new object to the database, for example a new device 139 | or a new vulnerability, one simply has to edit the database file, 140 | named `database.json`, and add the new object into the appropriate 141 | list. Once GridAttackAnalyzer is restarted, it will load the new 142 | database and the new objects become usable in analysis scenarios. 143 | 144 | ### Notes 145 | 146 | * For power grid models, a corresponding GLM file needs to be placed 147 | in the `GLM/` directory in order to make topology visualization 148 | possible. 149 | * For network devices, after adding a new device into the database the 150 | file `Scripts/NetGen.py` needs to be edited to make sure that the 151 | new device is integrated with the built-in network model. 152 | * The built-in network model cannot be changed without modifying the 153 | source code; those interested in updating it should edit the file 154 | `Scripts/NetGen.py` mentioned above. 155 | -------------------------------------------------------------------------------- /GLM/IEEE_4_Node.glm: -------------------------------------------------------------------------------- 1 | 2 | clock { 3 | timezone EST+5EDT; 4 | timestamp '2000-01-01 0:00:00'; 5 | stoptime '2000-01-03 0:00:00'; 6 | } 7 | #include "light_schedule.glm"; 8 | module powerflow { 9 | solver_method FBS; 10 | NR_iteration_limit 50; 11 | }; 12 | module market; 13 | module tape; 14 | module residential { 15 | implicit_enduses NONE; 16 | }; 17 | module climate; 18 | module connection; 19 | 20 | class auction { 21 | double current_price_mean_24h; 22 | double current_price_stdev_24h; 23 | } 24 | 25 | object auction { 26 | name Market_1; 27 | special_mode NONE; 28 | unit kW; 29 | price_cap 3.78; 30 | period 300; 31 | init_price 0.10; 32 | init_stdev 0.05; 33 | capacity_reference_object transformer23; 34 | capacity_reference_property power_out_real; 35 | object player { 36 | property capacity_reference_bid_price; 37 | file ExamplePrices2.player; 38 | loop 150; 39 | }; 40 | max_capacity_reference_bid_quantity 150; 41 | warmup 0; 42 | object recorder { 43 | property capacity_reference_bid_price, current_market.clearing_price, current_market.clearing_quantity; 44 | limit 100000000; 45 | interval 60; 46 | file baseprice_clearedprice_clearedquantity.csv; 47 | }; 48 | } 49 | 50 | 51 | 52 | 53 | object climate { 54 | name "WA-Yakima"; 55 | tmyfile "WA-Yakima.tmy3"; 56 | interpolate QUADRATIC; 57 | }; 58 | 59 | object fncs_msg { 60 | name fncs_msg; 61 | parent Market_1; 62 | route "function:controller/submit_bid_state -> auction/submit_bid_state"; 63 | option "transport:hostname localhost, port 5570"; 64 | configure fncs_msg.txt; 65 | } 66 | 67 | 68 | 69 | object overhead_line_conductor { 70 | name overhead_line_conductor100; 71 | geometric_mean_radius 0.0244; 72 | resistance 0.306; 73 | } 74 | 75 | object overhead_line_conductor { 76 | name overhead_line_conductor101; 77 | geometric_mean_radius 0.00814; 78 | resistance 0.592; 79 | } 80 | 81 | object line_spacing { 82 | name line_spacing200; 83 | distance_AB 2.5; 84 | distance_BC 4.5; 85 | distance_AC 7.0; 86 | distance_AN 5.656854; 87 | distance_BN 4.272002; 88 | distance_CN 5.0; 89 | } 90 | 91 | object line_configuration { 92 | name line_configuration300; 93 | conductor_A overhead_line_conductor100; 94 | conductor_B overhead_line_conductor100; 95 | conductor_C overhead_line_conductor100; 96 | conductor_N overhead_line_conductor101; 97 | spacing line_spacing200; 98 | } 99 | 100 | object transformer_configuration { 101 | name transformer_configuration400; 102 | connect_type 1; 103 | power_rating 6000; 104 | powerA_rating 2000; 105 | powerB_rating 2000; 106 | powerC_rating 2000; 107 | primary_voltage 12470; 108 | secondary_voltage 4160; 109 | resistance 0.01; 110 | reactance 0.06; 111 | } 112 | 113 | object node { 114 | name node1; 115 | bustype SWING; 116 | phases "ABCN"; 117 | voltage_A +7199.558+0.000j; 118 | voltage_B -3599.779-6235.000j; 119 | voltage_C -3599.779+6235.000j; 120 | nominal_voltage 7200; 121 | } 122 | 123 | object overhead_line { 124 | phases "ABCN"; 125 | from node1; 126 | to node2; 127 | length 2000; 128 | configuration line_configuration300; 129 | } 130 | 131 | object node { 132 | name node2; 133 | phases "ABCN"; 134 | voltage_A +7199.558+0.000j; 135 | voltage_B -3599.779-6235.000j; 136 | voltage_C -3599.779+6235.000j; 137 | nominal_voltage 7200; 138 | } 139 | 140 | object transformer { 141 | name transformer23; 142 | phases "ABCN"; 143 | from node2; 144 | to node3; 145 | configuration transformer_configuration400; 146 | object recorder { 147 | //file IEEE_13_house_market_rtp_bill_transformer_power.csv; 148 | interval 60; 149 | limit 0; 150 | property power_out_real; 151 | file totalload.csv; 152 | //property power_in_A.real,power_in_A.imag,power_in_B.real,power_in_B.imag,power_in_C.real,power_in_C.imag,power_out_A.real,power_out_A.imag,power_out_B.real,power_out_B.imag,power_out_C.real,power_out_C.imag,power_losses_A.real,power_losses_A.imag; 153 | }; 154 | } 155 | 156 | object node { 157 | name node3; 158 | phases "ABCN"; 159 | voltage_A +2401.777+0.000j; 160 | voltage_B -1200.889-2080.000j; 161 | voltage_C -1200.889+2080.000j; 162 | nominal_voltage 2400; 163 | } 164 | 165 | object overhead_line:34 { 166 | phases "ABCN"; 167 | from node3; 168 | to node4; 169 | length 2500; 170 | configuration line_configuration300; 171 | } 172 | 173 | object node { 174 | name node4; 175 | phases ABCN; 176 | nominal_voltage 2400; 177 | } 178 | 179 | object transformer { 180 | name CTTF_A1; 181 | phases AS; 182 | from node4; 183 | to tn_A1; 184 | configuration object transformer_configuration { 185 | connect_type SINGLE_PHASE_CENTER_TAPPED; 186 | install_type POLETOP; 187 | shunt_impedance 10000+10000j; 188 | primary_voltage 2401.777; 189 | secondary_voltage 120; 190 | powerA_rating 25 kVA; 191 | impedance 0.00033+0.0022j; 192 | }; 193 | groupid Distribution_Trans; 194 | } 195 | 196 | object triplex_meter { 197 | name tn_A1; 198 | phases AS; 199 | nominal_voltage 120; 200 | } 201 | 202 | object triplex_line_configuration { 203 | name trip_line_config; 204 | conductor_1 object triplex_line_conductor { 205 | resistance 0.97; 206 | geometric_mean_radius 0.01111; 207 | }; 208 | conductor_2 object triplex_line_conductor { 209 | resistance 0.97; 210 | geometric_mean_radius 0.01111; 211 | }; 212 | conductor_N object triplex_line_conductor { 213 | resistance 0.97; 214 | geometric_mean_radius 0.01111; 215 | }; 216 | insulation_thickness 0.08; 217 | diameter 0.368; 218 | } 219 | 220 | object triplex_line { 221 | name tl_A1; 222 | phases AS; 223 | from tn_A1; 224 | to tm_A1; 225 | length 19.10; 226 | configuration trip_line_config; 227 | groupid Triplex_Line; 228 | object recorder { 229 | file house_demand_5_1_1.csv; 230 | limit 0; 231 | interval 60; 232 | property power_out.real; 233 | }; 234 | } 235 | 236 | object triplex_meter { 237 | name tm_A1; 238 | phases AS; 239 | nominal_voltage 120; 240 | } 241 | 242 | 243 | object house { 244 | parent tm_A1; 245 | name house_A1; 246 | floor_area 1936; 247 | schedule_skew 2754; 248 | heating_system_type HEAT_PUMP; 249 | heating_COP 2.7; 250 | cooling_system_type ELECTRIC; 251 | object controller { 252 | name HOUSE_1; 253 | schedule_skew 2754; 254 | market Market_1; 255 | bid_mode ON; 256 | 257 | 258 | proxy_average 0.042676; 259 | proxy_standard_deviation 0.020000; 260 | proxy_market_id 1; 261 | proxy_clear_price 0.042676; 262 | proxy_price_cap 3.78; 263 | 264 | 265 | control_mode DOUBLE_RAMP; 266 | resolve_mode DEADBAND; 267 | slider_setting_heat 0.500; 268 | slider_setting_cool 0.500; 269 | heating_base_setpoint 68; 270 | cooling_base_setpoint 74; 271 | period 300; 272 | average_target current_price_mean_24h; 273 | standard_deviation_target current_price_stdev_24h; 274 | target air_temperature; 275 | heating_setpoint heating_setpoint; 276 | heating_demand last_heating_load; 277 | cooling_setpoint cooling_setpoint; 278 | cooling_demand last_cooling_load; 279 | deadband thermostat_deadband; 280 | total hvac_load; 281 | load hvac_load; 282 | state power_state; 283 | }; 284 | thermal_integrity_level 5; 285 | air_temperature 70; 286 | mass_temperature 70; 287 | cooling_COP 2.7; 288 | object ZIPload { 289 | base_power LIGHTS*1.33; 290 | schedule_skew -1582; 291 | heatgain_fraction 0.9; 292 | power_pf 1.000; 293 | current_pf 1.000; 294 | impedance_pf 1.000; 295 | impedance_fraction 0.300000; 296 | current_fraction 0.300000; 297 | power_fraction 0.400000; 298 | }; 299 | } 300 | -------------------------------------------------------------------------------- /Scripts/AttackGraph.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module constructs attack graph. 3 | """ 4 | 5 | 6 | from Node import * 7 | from Network import * 8 | from Vulnerability import * 9 | from math import * 10 | 11 | class gnode(node): 12 | """ 13 | Create attack graph node object. 14 | """ 15 | def __init__(self, name): 16 | super(gnode, self).__init__(name) 17 | #Store the network node 18 | self.n = None 19 | #Store the Simulation value used in security analysis 20 | self.val = 0 21 | self.vuls = [] 22 | self.type = None 23 | #Used to check whether the node is included in the attack path or not 24 | self.inPath = 0 25 | self.subnet = [] 26 | 27 | def __str__(self): 28 | return self.name 29 | 30 | 31 | class gVulNode(vulNode): 32 | """ 33 | Create attack graph vulnerability object. 34 | """ 35 | def __init__(self, name): 36 | super(gVulNode, self).__init__(name) 37 | #Store the vulnerability node 38 | self.n = None 39 | #Store the Simulation value used in security analysis 40 | self.val = 0 41 | self.inPath = 0 42 | 43 | def __str__(self): 44 | return self.name 45 | 46 | 47 | class ag(network): 48 | """ 49 | Create attack graph. 50 | """ 51 | 52 | #Construct the attack graph 53 | def __init__(self, network, val, *arg): 54 | super(ag, self).__init__() 55 | self.path = [] 56 | #Store all possible paths from start to end 57 | self.allpath = [] 58 | self.isAG = 1 59 | self.subnets = network.subnets #All subnets in the network 60 | self.vuls = network.vuls #All vuls in the network 61 | 62 | #Instantiate nodes in attack graph using network info 63 | for u in [network.s, network.e] + network.nodes: 64 | if u is not None: 65 | #For vulnerability 66 | if type(u) is vulNode: 67 | gn = gVulNode('ag_' + str(u.name)) 68 | gn.privilege = u.privilege 69 | gn.val = u.val 70 | #For node 71 | else: 72 | gn = gnode('ag_' + str(u.name)) 73 | 74 | #Assign default value to attacker node 75 | if u.isStart == True: 76 | gn.val = -1 77 | else: 78 | gn.val = val 79 | 80 | if u is not network.s and u is not network.e: 81 | gn.type = u.type 82 | 83 | #for sub in u.subnet: 84 | #gn.subnet.append(sub) 85 | 86 | gn.n = u 87 | 88 | #Assign default value to start and end in network 89 | if u in [network.s, network.e]: 90 | gn.val = -1 91 | 92 | self.nodes.append(gn) 93 | #print(gn.name) 94 | 95 | 96 | #Initialize connections for attack graph node 97 | for u in self.nodes: 98 | #print(u) 99 | for v in u.n.con: 100 | #For upper layer 101 | if len(arg) is 0: 102 | for t in self.nodes: 103 | if t.n.name == v.name: 104 | #print("connections:", t.name) 105 | u.con.append(t) 106 | #For lower layer 107 | else: 108 | if arg[0] >= v.privilege: 109 | for t in self.nodes: 110 | if t.n is v: 111 | u.con.append(t) 112 | 113 | #Initialize start and end in attack graph 114 | for u in self.nodes: 115 | if u.n is network.s: 116 | self.s = u 117 | if u.n is network.e: 118 | self.e = u 119 | 120 | #Remove start and end from nodes in attack graph 121 | if self.s is not None: 122 | self.nodes.remove(self.s) 123 | if self.e is not None: 124 | self.nodes.remove(self.e) 125 | 126 | 127 | #Traverse graph 128 | def travelAgRecursive(self, u, e, path): 129 | val = 0 130 | for v in u.con: 131 | 132 | #Only include nodes with vulnerabilities in the path 133 | if v.inPath == 0 and (v.child != None or v.name == 'ag_attacker' or v is e): 134 | self.path.append(v) 135 | v.inPath = 1 136 | #print(self.path) 137 | #Recursively traverse the path until to the end point 138 | if v is not e: 139 | val += self.travelAgRecursive(v, e, self.path) 140 | else: 141 | self.allpath.append(path[:]) 142 | 143 | self.path.pop() 144 | v.inPath = 0 145 | 146 | return val 147 | 148 | #Traverse graph to get attack paths 149 | def travelAg(self): 150 | self.allpath = [] 151 | #Start to traverse from start point 152 | self.path = [self.s] 153 | #print(self.s.name) 154 | val = self.travelAgRecursive(self.s, self.e, self.path) #The value records recursion times 155 | 156 | return val 157 | 158 | #Print graph 159 | def printAG(self): 160 | i = 0 161 | 162 | for node in self.nodes: 163 | print("===============================================================") 164 | print(i ,': ', node.name, ', ', "number of connections: ", len(node.con)) 165 | for cons in node.con: 166 | #the target connects to end point, do not print end point 167 | if cons != self.e: 168 | print(cons.name,) 169 | print 170 | i += 1 171 | 172 | if node.child != None and node is not self.s and node is not self.e and node.name != 'ag_attacker': 173 | print("attack tree for " + node.name, " :") 174 | node.child.treePrint() 175 | 176 | return None 177 | 178 | #Print attack paths 179 | def printPath(self): 180 | 181 | for path in self.allpath: 182 | print("--------------------------------------------------") 183 | #print(path) 184 | for node in path: 185 | print(node.name) 186 | print("--------------------------------------------------") 187 | return None 188 | 189 | 190 | #Calculate attack paths 191 | def calcPath(self): 192 | return self.travelAg() 193 | 194 | #----------------------------------------------------------------------------------------------------------------------- 195 | #Security analysis part: including attack impact, attack cost, return-on-attack, risk and attack success probability 196 | 197 | #In case that the node is in the upper layer and has child (not none), assign child value to node value 198 | def getImpactValue(self): 199 | for u in self.nodes: 200 | if u.child is not None: 201 | u.val = u.child.calcImpact() 202 | #print(u.name, u.val) 203 | 204 | def calcImpact(self): 205 | self.getImpactValue() 206 | 207 | 208 | #In case that the node is in the upper layer and has child (not none), assign child value to node value 209 | def getCostValue(self): 210 | for u in self.nodes: 211 | if u.child is not None: 212 | u.val = u.child.calcCost() 213 | #print(u.name, u.val) 214 | 215 | def calcCost(self): 216 | self.getCostValue() 217 | 218 | 219 | #In case that the node is in the upper layer and has child (not none), assign child value to node value 220 | def getProValue(self): 221 | for u in self.nodes: 222 | if u.child is not None: 223 | u.val = u.child.calcPro() 224 | #print(u.name, u.val) 225 | 226 | def calcPro(self): 227 | self.getProValue() 228 | 229 | #In case that the node is in the upper layer and has child (not none), assign child value to node value 230 | def getRiskValue(self): 231 | for u in self.nodes: 232 | if u.child is not None: 233 | u.val = u.child.calcRisk() 234 | #print(u.name, u.val) 235 | 236 | def calcRisk(self): 237 | self.getRiskValue() 238 | 239 | #In case that the node is in the upper layer and has child (not none), assign child value to node value 240 | def getReturnOnAttackValue(self): 241 | for u in self.nodes: 242 | if u.child is not None: 243 | u.val = u.child.calcReturnOnAttack() 244 | #print(u.name, u.val) 245 | 246 | def calcReturnOnAttack(self): 247 | self.getRiskValue() 248 | 249 | #When the node in the upper layer has one vulnerability, calculate the value for parent node 250 | def getNodeValue(self): 251 | for u in self.nodes: 252 | if u.child is not None: 253 | u.val = u.child.getNodeValue(u.child.topGate) 254 | 255 | -------------------------------------------------------------------------------- /Scripts/plotMetrics.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter.ttk import * 3 | from tkinter import ttk 4 | from pandas import DataFrame 5 | import matplotlib.pyplot as plt 6 | from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg 7 | import os 8 | import sys 9 | import matplotlib 10 | import numpy as np 11 | import pandas as pd 12 | from matplotlib import interactive 13 | import matplotlib.ticker as plticker 14 | from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg 15 | from matplotlib import cm 16 | from matplotlib.colors import ListedColormap 17 | import random 18 | root= tk.Tk() 19 | root.title("Security Metrics I") 20 | lbl = Label(root, text="Security Metrics I", font=("Times", 18), foreground="#000280") 21 | lbl.pack() 22 | 23 | windows_paths = tk.Tk() 24 | windows_paths.title("Security Metrics II") 25 | lbl = Label(windows_paths, text="Security Metrics II", font=("Times", 18), foreground="#000280") 26 | lbl.pack() 27 | 28 | windows_average = tk.Tk() 29 | windows_average.title("Security Metrics III") 30 | lbl = Label(windows_average, text="Security Metrics III", font=("Times", 18), foreground="#000280") 31 | lbl.pack() 32 | 33 | 34 | 35 | def add_value_labels(ax, spacing=0): 36 | """Add labels to the end of each bar in a bar chart. 37 | 38 | Arguments: 39 | ax (matplotlib.axes.Axes): The matplotlib object containing the axes 40 | of the plot to annotate. 41 | spacing (int): The distance between the labels and the bars. 42 | """ 43 | 44 | # For each bar: Place a label 45 | for rect in ax.patches: 46 | # Get X and Y placement of label from rect. 47 | y_value = rect.get_height() 48 | x_value = rect.get_x() + rect.get_width() / 2 49 | 50 | # Number of points between bar and label. Change to your liking. 51 | space = spacing 52 | # Vertical alignment for positive values 53 | va = 'bottom' 54 | 55 | # If value of bar is negative: Place label below bar 56 | if y_value < 0: 57 | # Invert space to place label below 58 | space *= -1 59 | # Vertically align label at top 60 | va = 'center' 61 | 62 | # Use Y value as label and format number with one decimal place 63 | label = "{:.2f}".format(y_value) 64 | 65 | 66 | # Create annotation 67 | ax.annotate( 68 | label, # Use `label` as label 69 | (x_value, y_value), # Place label at end of the bar 70 | xytext=(3, space), # Vertically shift label by `space` 71 | textcoords="offset points", # Interpret `xytext` as offset in points 72 | ha='center', # Horizontally center label 73 | va=va) # Vertically align label differently for 74 | # positive and negative values. 75 | 76 | 77 | def plotMetrics(file): 78 | my_colormap = ['tab:blue', 'tab:orange', 'tab:green', 'tab:red', 'tab:purple', 'tab:brown', 'tab:pink', 'tab:gray', 'tab:olive', 'tab:cyan', 'b', 'g', 'r', 'c', 'm', 'y', 'k'] # red, green, blue, black, etc. 79 | 80 | # Plot Attack Cost, Attack Impact, Attack Risk 81 | figure = plt.Figure(figsize=(5, 6), dpi=100) 82 | ax1 = figure.add_subplot(221) 83 | bar1 = FigureCanvasTkAgg(figure, root) 84 | bar1.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True) 85 | figure.subplots_adjust(hspace=0.4, wspace=0.4) 86 | ax2 = figure.add_subplot(222) 87 | ax3 = figure.add_subplot(223) 88 | ax4 = figure.add_subplot(224) 89 | 90 | # Plot Paths 91 | figure_paths = plt.Figure(figsize=(5, 6), dpi=100) 92 | ax5 = figure_paths.add_subplot(231) 93 | bar_paths = FigureCanvasTkAgg(figure_paths, windows_paths) 94 | bar_paths.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True) 95 | figure_paths.subplots_adjust(hspace=0.4, wspace=0.4) 96 | ax6 = figure_paths.add_subplot(232) 97 | ax7 = figure_paths.add_subplot(233) 98 | ax8 = figure_paths.add_subplot(234) 99 | ax9 = figure_paths.add_subplot(235) 100 | ax10 = figure_paths.add_subplot(236) 101 | 102 | # Plot average 103 | 104 | figure_average = plt.Figure(figsize=(5, 6), dpi=100) 105 | bar_average = FigureCanvasTkAgg(figure_average, windows_average) 106 | bar_average.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True) 107 | figure_average.subplots_adjust(hspace=0.4, wspace=0.4) 108 | ax11 = figure_average.add_subplot(121) 109 | ax12 = figure_average.add_subplot(122) 110 | 111 | 112 | 113 | 114 | 115 | for i in range(1, len(file) - 1): 116 | data_loading = pd.read_csv(file, delimiter=',',skiprows=1, 117 | names=["time", 118 | "case_study_name", 119 | "streets_and_houses_vector", 120 | "attack_success_probability", 121 | "attack_cost", 122 | "attack_impact", 123 | "attack_risk", 124 | "number_of_devices", 125 | "average_attack_cost", 126 | "average_attack_impact", 127 | "number_of_paths", 128 | "rare_paths", 129 | "unlilkely_paths", 130 | "possible_paths", 131 | "likely_paths", 132 | "almost_certain_paths" 133 | ]) 134 | data_loading.head(5) 135 | # print(data_loading) 136 | ax1.bar("case_study_name", 'attack_success_probability', data=data_loading, color=my_colormap) 137 | ax1.set_title('Attack Success Probability') 138 | add_value_labels(ax1) 139 | plt.setp(ax1.get_xticklabels(), rotation=15, horizontalalignment='right') 140 | ax1.set_xlabel('Case Study') 141 | ax1.set_ylabel('Attack Success Probability') 142 | #Enable to save figure 143 | #figure.savefig('Attack_Success_Probability.png') 144 | 145 | 146 | ax2.bar("case_study_name", 'attack_cost', data=data_loading, color=my_colormap) 147 | ax2.set_title('Attack Cost') 148 | add_value_labels(ax2) 149 | plt.setp(ax2.get_xticklabels(), rotation=15, horizontalalignment='right') 150 | ax2.set_xlabel('Case Study') 151 | ax2.set_ylabel('Attack Cost') 152 | #Enable to save figure 153 | #figure.savefig('Attack_Cost.png') 154 | 155 | ax3.bar("case_study_name", 'attack_impact', data=data_loading, color=my_colormap) 156 | ax3.set_title('Attack Impact') 157 | plt.setp(ax3.get_xticklabels(), rotation=15, horizontalalignment='right') 158 | ax3.set_xlabel('Case Study') 159 | ax3.set_ylabel('Attack Impact') 160 | add_value_labels(ax3) 161 | #Enable to save figure 162 | #figure.savefig('Attack_Impact.png') 163 | 164 | ax4.bar("case_study_name", 'attack_risk', data=data_loading, color=my_colormap) 165 | ax4.set_title('Attack Risk') 166 | plt.setp(ax4.get_xticklabels(), rotation=15, horizontalalignment='right') 167 | ax4.set_xlabel('Case Study') 168 | ax4.set_ylabel('Attack_Risk') 169 | add_value_labels(ax4) 170 | #Enable to save figure 171 | #figure.savefig('Attack_Risk.png') 172 | 173 | 174 | # Number of Paths 175 | ax5.bar("case_study_name", 'number_of_paths', data=data_loading, color=my_colormap) 176 | ax5.set_title('Number of Paths') 177 | plt.setp(ax5.get_xticklabels(), rotation=15, horizontalalignment='right') 178 | ax5.set_xlabel('Case Study') 179 | ax5.set_ylabel('Number of Paths') 180 | add_value_labels(ax5) 181 | #Enable to save figure 182 | #figure_paths.savefig('Number_of_Paths.png') 183 | # Rare Paths 184 | ax6.bar("case_study_name", 'rare_paths', data=data_loading, color=my_colormap) 185 | ax6.set_title('Rare Paths') 186 | plt.setp(ax6.get_xticklabels(), rotation=15, horizontalalignment='right') 187 | ax6.set_xlabel('Case Study') 188 | ax6.set_ylabel('Rare Paths') 189 | add_value_labels(ax6) 190 | #Enable to save figure 191 | #figure_paths.savefig('Rare_Paths.png') 192 | 193 | # unlilkely_paths 194 | ax7.bar("case_study_name", 'unlilkely_paths', data=data_loading, color=my_colormap) 195 | ax7.set_title('Unlilkely Paths') 196 | plt.setp(ax7.get_xticklabels(), rotation=15, horizontalalignment='right') 197 | ax7.set_xlabel('Case Study') 198 | ax7.set_ylabel('Unlilkely Paths') 199 | add_value_labels(ax7) 200 | #Enable to save figure 201 | #figure_paths.savefig('Unlilkely_Paths.png') 202 | 203 | # possible_paths 204 | ax8.bar("case_study_name", 'possible_paths', data=data_loading, color=my_colormap) 205 | ax8.set_title('Possible Paths') 206 | plt.setp(ax8.get_xticklabels(), rotation=15, horizontalalignment='right') 207 | ax8.set_xlabel('Case Study') 208 | ax8.set_ylabel('Possible Paths') 209 | add_value_labels(ax8) 210 | #Enable to save figure 211 | #figure_paths.savefig('Possible_Paths.png') 212 | 213 | # likely_paths 214 | ax9.bar("case_study_name", 'likely_paths', data=data_loading, color=my_colormap) 215 | ax9.set_title('Likely Paths') 216 | plt.setp(ax9.get_xticklabels(), rotation=15, horizontalalignment='right') 217 | ax9.set_xlabel('Case Study') 218 | ax9.set_ylabel('Likely Paths') 219 | add_value_labels(ax9) 220 | #Enable to save figure 221 | #figure_paths.savefig('Likely_Paths.png') 222 | 223 | # likely_paths 224 | ax10.bar("case_study_name", 'almost_certain_paths', data=data_loading, color=my_colormap) 225 | ax10.set_title('Almost Certain Paths') 226 | plt.setp(ax10.get_xticklabels(), rotation=15, horizontalalignment='right') 227 | ax10.set_xlabel('Case Study') 228 | ax10.set_ylabel('Almost Certain Paths') 229 | add_value_labels(ax10) 230 | #Enable to save figure 231 | #figure_paths.savefig('Almost_Certain_Paths.png') 232 | 233 | 234 | # Average Attack Cost 235 | 236 | ax11.bar("case_study_name", 'average_attack_cost', data=data_loading, color=my_colormap) 237 | ax11.set_title('Average Attack Cost') 238 | plt.setp(ax11.get_xticklabels(), rotation=15, horizontalalignment='right') 239 | ax11.set_xlabel('Case Study') 240 | ax11.set_ylabel('Average Attack Cost') 241 | add_value_labels(ax11) 242 | #Enable to save figure 243 | #figure_average.savefig('Average_Attack_Cost.png') 244 | 245 | # average_attack_impact 246 | 247 | ax12.bar("case_study_name", 'average_attack_impact', data=data_loading, color=my_colormap) 248 | ax12.set_title('Average Attack Impact') 249 | plt.setp(ax12.get_xticklabels(), rotation=15, horizontalalignment='right') 250 | ax12.set_xlabel('Case Study') 251 | ax12.set_ylabel('Average Attack Impact') 252 | add_value_labels(ax12) 253 | #Enable to save figure 254 | #figure_average.savefig('Average_Attack_Impact.png') 255 | 256 | 257 | 258 | if __name__ == '__main__': 259 | plotMetrics("Results/Results.csv") 260 | root.mainloop() 261 | windows_paths.mainloop() 262 | windows_average.mainloop() 263 | -------------------------------------------------------------------------------- /Scripts/SecurityEvaluator.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module conducts security analysis and generates SHARPE code from HARM as text file. 3 | """ 4 | 5 | from AttackGraph import * 6 | from AttackTree import * 7 | from Harm import * 8 | import os 9 | import math 10 | import csv 11 | from random import shuffle, uniform, expovariate 12 | import numpy as np 13 | from tempfile import NamedTemporaryFile 14 | import shutil 15 | from plotResult import * 16 | import time 17 | from time import sleep 18 | #------------------------------------------------------- 19 | #Compute maximum risk 20 | #------------------------------------------------------- 21 | def computeRisk(harm): 22 | """ 23 | Compute risk for HARM using attack graph as upper layer and attack tree as lower layer. 24 | """ 25 | risk = [] 26 | 27 | harm.model.calcRisk() 28 | #No vf() function 29 | 30 | for path in harm.model.allpath: 31 | pathRisk = 0 32 | for node in path: 33 | if node is not harm.model.s and node is not harm.model.e: 34 | if node.val > 0: 35 | print(node.name, node.type, node.val) 36 | pathRisk += node.val 37 | 38 | 39 | risk.append(pathRisk) 40 | 41 | value = max(risk) 42 | return value 43 | 44 | def riskAnalysis(net, pri): 45 | 46 | h = harm() 47 | h.constructHarm(net, "attackgraph", 1, "attacktree", 1, pri) 48 | if len(h.model.allpath) != 0: 49 | r = computeRisk(h) 50 | else: 51 | return 0 52 | 53 | return r 54 | 55 | 56 | #------------------------------------------------------- 57 | #Compute maximum return on attack 58 | #------------------------------------------------------- 59 | def computeReturnOnAttack(harm): 60 | """ 61 | Compute risk for HARM using attack graph as upper layer and attack tree as lower layer. 62 | """ 63 | attackReturn = [] 64 | 65 | harm.model.calcReturnOnAttack() 66 | 67 | for path in harm.model.allpath: 68 | pathReturn = 0 69 | for node in path: 70 | if node is not harm.model.s and node is not harm.model.e: 71 | if node.val > 0: 72 | #print(node.name, node.type, node.val) 73 | pathReturn += node.val 74 | #print(pathReturn) 75 | attackReturn.append(pathReturn) 76 | 77 | value = max(attackReturn) 78 | 79 | return value 80 | 81 | 82 | def returnOnAttackAnalysis(net, pri): 83 | 84 | h = harm() 85 | h.constructHarm(net, "attackgraph", 1, "attacktree", 1, pri) 86 | if len(h.model.allpath) != 0: 87 | r = computeReturnOnAttack(h) 88 | else: 89 | return 0 90 | 91 | return r 92 | 93 | 94 | #------------------------------------------------------- 95 | #Compute maximum attack impact 96 | #------------------------------------------------------- 97 | def update_attackImpactAnalysis_file(filename, max_impact): 98 | tempfile = NamedTemporaryFile(mode='w', delete=False) 99 | 100 | fields = ['From', 'To', 'Weight', 'x', 'y', 'Highlight'] 101 | with open(filename, 'r') as csvfile: 102 | reader = csv.DictReader(csvfile, fieldnames=fields) 103 | term_max_impact = 0 104 | index = 0 105 | for row in reader: 106 | if(index): 107 | if (index > 2 and row['From'] == "attacker"): 108 | break 109 | print("Weight") 110 | print(float(row['Weight'])) 111 | print(row['From']) 112 | term_max_impact = term_max_impact + float(row['Weight']) 113 | index = index + 1 114 | 115 | print("Max Impact") 116 | print(max_impact) 117 | print("term_max_impact") 118 | print(term_max_impact) 119 | with open(filename, 'r') as csvfile, tempfile: 120 | reader = csv.DictReader(csvfile, fieldnames=fields) 121 | writer = csv.DictWriter(tempfile, fieldnames=fields) 122 | index_term = 0 123 | if(term_max_impact < max_impact): 124 | for row in reader: 125 | if (index_term and index_term < index): 126 | row['Highlight'] = 0 127 | index_term = index_term+1 128 | row = {'From': row['From'], 'To': row['To'], 'Weight': row['Weight'], 129 | 'x': row['x'], 130 | 'y': row['y'], 131 | 'Highlight': row['Highlight'], 132 | } 133 | writer.writerow(row) 134 | else: 135 | for row in reader: 136 | row = {'From': row['From'], 'To': row['To'], 'Weight': row['Weight'], 137 | 'x': row['x'], 138 | 'y': row['y'], 139 | 'Highlight': row['Highlight'], 140 | } 141 | writer.writerow(row) 142 | shutil.move(tempfile.name, filename) 143 | 144 | 145 | def computeAttackImpact(harm, file_prefix): 146 | """ 147 | Compute attack impact for HARM using attack graph as upper layer and attack tree as lower layer. 148 | """ 149 | impact = [] 150 | create_Graph = [] 151 | nodeval_array = [] 152 | sum_nodeval_array = 0 153 | highlight = [] 154 | flag_path_0 = True 155 | flag_update = True 156 | index = 0 157 | number_paths = 0 158 | file_name = file_prefix+'attackImpactAnalysis.csv' 159 | 160 | with open(file_name, 'w', newline='') as file: 161 | writer = csv.writer(file) 162 | writer.writerow(["From", "To", "Weight", "x", "y", "Highlight"]) 163 | 164 | harm.model.calcImpact() 165 | print("=================================================") 166 | print("Print attack paths: \n") 167 | for path in harm.model.allpath: 168 | pathImpact = 0 169 | create_Graph.clear() 170 | nodeval_array.clear() 171 | print(path) 172 | number_paths = number_paths + 1 173 | for node in path: 174 | print(node.name, end =' ') 175 | create_Graph.append(str(node.name)[3:]) 176 | if node is not harm.model.s and node is not harm.model.e: 177 | if node.val > 0: 178 | print('(', node.val, ')', end = ' ') 179 | pathImpact += node.val 180 | nodeval_array.append(node.val) 181 | else: 182 | nodeval_array.append(0) 183 | if flag_path_0: 184 | index = index + 1 185 | flag_path_0 = False 186 | 187 | 188 | if sum_nodeval_array < sum(nodeval_array): 189 | sum_nodeval_array = sum(nodeval_array) 190 | highlight[:] = [1] * (len(nodeval_array)) 191 | if flag_update: 192 | r = csv.reader(open(file_name)) 193 | lines = list(r) 194 | for i in range(index-2): 195 | print(i) 196 | #lines[i][5] = '0' 197 | writer = csv.writer(open(file_name, 'w')) 198 | writer.writerows(lines) 199 | flag_update = False 200 | else: 201 | highlight[:] = [0] * (len(nodeval_array)) 202 | 203 | with open(file_name, 'a', newline='') as file: 204 | writer = csv.writer(file) 205 | for i in range(2, len(create_Graph)-1): 206 | if i == 2: 207 | x = 0 208 | y = 0 209 | 210 | elif i == len(create_Graph)-2: 211 | x = 6 212 | y = 6 213 | 214 | else: 215 | x = 3 216 | y = 3 217 | 218 | writer.writerow([create_Graph[i-1], create_Graph[i], nodeval_array[i-1], x, y, highlight[i-1]]) 219 | 220 | print("\n") 221 | impact.append(pathImpact) 222 | 223 | value = max(impact) 224 | 225 | print("Maximum attack impact is: ", value) 226 | #update_attackImpactAnalysis_file(file_name, value) 227 | #sleep(1) # Time in seconds 228 | plotResult(file_name, file_name.replace(".csv",".html")) 229 | 230 | return value, number_paths 231 | 232 | 233 | def attackImpactAnalysis(net, pri, file_prefix): 234 | 235 | h = harm() 236 | h.constructHarm(net, "attackgraph", 1, "attacktree", 1, pri) 237 | h.model.printPath() 238 | h.model.printAG() 239 | 240 | if len(h.model.allpath) != 0: 241 | ai = computeAttackImpact(h, file_prefix) 242 | else: 243 | return 0 244 | 245 | return ai 246 | 247 | #--------------------------------------------------------- 248 | #Compute minimum attack cost 249 | #--------------------------------------------------------- 250 | 251 | def computeAttackCost(harm): 252 | """ 253 | Compute attack cost for HARM using attack graph as upper layer and attack tree as lower layer. 254 | """ 255 | cost = [] 256 | 257 | harm.model.calcCost() 258 | 259 | for path in harm.model.allpath: 260 | pathCost = 0 261 | for node in path: 262 | if node is not harm.model.s and node is not harm.model.e: 263 | if node.val > 0: 264 | pathCost += node.val 265 | 266 | cost.append(pathCost) 267 | 268 | 269 | value = min(cost) 270 | print("Attack Cost:") 271 | print(value) 272 | 273 | return value 274 | 275 | def attackCostAnalysis(net, pri): 276 | 277 | h = harm() 278 | h.constructHarm(net, "attackgraph", 1, "attacktree", 1, pri) 279 | if len(h.model.allpath) != 0: 280 | ac = computeAttackCost(h) 281 | else: 282 | return 0 283 | 284 | return ac 285 | 286 | #--------------------------------------------------------------- 287 | #Compute maximum attack success probability 288 | #--------------------------------------------------------------- 289 | 290 | def computeAttackPro(harm): 291 | """ 292 | Compute attack success probability for HARM using attack graph as upper layer and attack tree as lower layer. 293 | """ 294 | pro = [] 295 | rare_paths = 0 # number of rare paths 296 | unlikely_paths = 0 #number of unlikely paths 297 | possible_paths = 0 #number of possible paths 298 | likely_paths = 0 #number of likely paths 299 | certain_paths = 0 #number of certain_paths 300 | harm.model.calcPro() 301 | print("=================================================") 302 | print("Print attack paths: \n") 303 | for path in harm.model.allpath: 304 | pathPro = 1 305 | for node in path: 306 | if node is not harm.model.s and node is not harm.model.e: 307 | print(node.name, end =' ') 308 | #Exclude the attacker 309 | if node.val > 0: 310 | print('(', node.val, ')', end = ' ') 311 | pathPro *= node.val 312 | if pathPro > 0 and pathPro <= 0.19: 313 | rare_paths = rare_paths + 1 314 | elif pathPro >= 0.2 and pathPro <= 0.39: 315 | unlikely_paths = unlikely_paths + 1 316 | elif pathPro >= 0.4 and pathPro <= 0.59: 317 | possible_paths = possible_paths + 1 318 | elif pathPro >= 0.6 and pathPro <= 0.79: 319 | likely_paths = likely_paths + 1 320 | else: 321 | certain_paths = certain_paths + 1 322 | print('\n') 323 | pro.append(pathPro) 324 | 325 | value = max(pro) 326 | print("Maximum attack success probability is: ", value) 327 | return value, rare_paths, unlikely_paths, possible_paths, likely_paths, certain_paths 328 | 329 | def attackProAnalysis(net, pri): 330 | 331 | h = harm() 332 | h.constructHarm(net, "attackgraph", 1, "attacktree", 1, pri) 333 | h.model.printPath() 334 | h.model.printAG() 335 | 336 | if len(h.model.allpath) != 0: 337 | pro = computeAttackPro(h) 338 | else: 339 | return 0 340 | 341 | return pro 342 | 343 | 344 | #------------------------------------ 345 | #Compute the number of paths 346 | #------------------------------------ 347 | def NP_metric(harm): 348 | 349 | value = len(harm.model.allpath) 350 | return value 351 | 352 | #------------------------------------------ 353 | #Compute the mean of path lengths 354 | #------------------------------------------ 355 | def MPL_metric(harm): 356 | 357 | sum_path_length = 0 358 | for path in harm.model.allpath: 359 | sum_path_length += int(len(path)-3) 360 | 361 | value = float(sum_path_length/len(harm.model.allpath)) 362 | 363 | return value 364 | 365 | #---------------------------------------- 366 | #Compute the mode of path lengths 367 | #---------------------------------------- 368 | def MoPL_metric(harm): 369 | 370 | NP = [] 371 | for path in harm.model.allpath: 372 | NP.append(int(len(path)-3)) 373 | 374 | value = max(NP, key=NP.count) 375 | return value 376 | 377 | #---------------------------------------------------------- 378 | #Compute the standard deviation of path lengths 379 | #---------------------------------------------------------- 380 | def SDPL_metric(harm): 381 | 382 | sumation_DPL = 0 383 | MPL = MPL_metric(harm) 384 | for path in harm.model.allpath: 385 | sumation_DPL += float(len(path) - 3 - MPL)**2 386 | 387 | value = math.sqrt(float(sumation_DPL / len(harm.model.allpath))) 388 | 389 | return value 390 | 391 | #-------------------------------------- 392 | #Compute the shortest attack path 393 | #-------------------------------------- 394 | def SP_metric(harm): 395 | 396 | SP=[] 397 | for path in harm.model.allpath: 398 | SP.append(int(len(path)-3)) 399 | value = min(SP) 400 | return value 401 | 402 | -------------------------------------------------------------------------------- /Scripts/AttackTree.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module constructs attack tree. 3 | """ 4 | 5 | from Node import * 6 | from Network import * 7 | from Vulnerability import * 8 | 9 | 10 | class tNode(node): 11 | """ 12 | Create attack tree node object. 13 | """ 14 | def __init__(self, name): 15 | super(tNode, self).__init__(name) 16 | self.n = None 17 | self.t = "node" 18 | self.val = 0 19 | 20 | def __str__(self): 21 | return self.name 22 | 23 | class tVulNode(vulNode): 24 | """ 25 | Create attack tree vulnerability object. 26 | """ 27 | def __init__(self, name): 28 | super(tVulNode, self).__init__(name) 29 | self.n = None 30 | self.t = "node" 31 | self.val = 0 32 | self.command = 0 33 | 34 | def __str__(self): 35 | return self.name 36 | 37 | class andGate(node): 38 | def __init__(self): 39 | super(andGate, self).__init__("andGate") 40 | self.t = "andGate" 41 | 42 | class orGate(node): 43 | def __init__(self): 44 | super(orGate, self).__init__("orGate") 45 | self.t = "orGate" 46 | 47 | 48 | class at(object): 49 | """ 50 | Create attack tree. 51 | """ 52 | def __init__(self, network, val, *arg): 53 | self.nodes = [] 54 | self.topGate = None 55 | self.construct(network, val, *arg) 56 | self.isAG = 0 57 | 58 | #Preprocess for the construction 59 | def preprocess(self, network, nodes, val, *arg): 60 | for u in [network.s, network.e] + network.nodes: 61 | if u is not None: 62 | #For vulnerability 63 | if type(u) is vulNode: 64 | tn = tVulNode('at_'+str(u.name)) 65 | tn.privilege = u.privilege 66 | tn.val = u.val 67 | tn.vulname = u.name 68 | #For node 69 | else: 70 | tn = tNode('at_'+str(u.name)) 71 | 72 | #Assign default value to attacker node 73 | if u.isStart == True: 74 | tn.val = -1 75 | else: 76 | tn.val = val 77 | 78 | tn.n = u 79 | 80 | #Assign default value to start and end in vulnerability network 81 | if u in [network.s, network.e]: 82 | tn.val = 0 83 | tn.command = 1 84 | 85 | nodes.append(tn) 86 | 87 | #Initialize connections for attack tree node 88 | for u in nodes: 89 | for v in u.n.con: 90 | #For upper layer 91 | if len(arg) is 0: 92 | for t in nodes: 93 | if t.n is v: 94 | u.con.append(t) 95 | #For lower layer 96 | else: 97 | # Privilege value is used here to decide what vulnerabilities an attacker can use for attack paths 98 | if v.privilege is not None and arg[0] >= v.privilege: 99 | for t in nodes: 100 | if t.n is v: 101 | u.con.append(t) 102 | return None 103 | 104 | #Construct the attack tree 105 | def construct(self, network, val, *arg): 106 | nodes = [] 107 | history = [] 108 | self.topGate = orGate() 109 | self.preprocess(network, nodes, val, *arg) 110 | 111 | #For one vulnerability 112 | if len(nodes) < 4: 113 | a_gate = andGate() 114 | for node in nodes: 115 | a_gate.con.append(node) 116 | 117 | self.topGate.con.append(a_gate) 118 | #For more than one vulnerability 119 | else: 120 | for u in nodes: 121 | if u.n is network.e: 122 | e = u 123 | if u.n is network.s: 124 | self.topGate.con.append(u) 125 | 126 | self.simplify(self.topGate, history, e) 127 | self.targetOut(self.topGate, e) 128 | self.foldgate(self.topGate) 129 | 130 | #Simplify the method 131 | def simplify(self, gate, history, target): 132 | tGate = [] 133 | tGate.extend(gate.con) 134 | value = 1 135 | if len(tGate) == 0: 136 | value = 0 137 | 138 | for item in tGate: 139 | if (item is not target) and (item.t is "node"): 140 | a_gate = andGate() 141 | gate.con.append(a_gate) 142 | gate.con.remove(item) 143 | 144 | a_gate.con.append(item) 145 | o_gate = orGate() 146 | a_gate.con.append(o_gate) 147 | 148 | for u in item.con: 149 | if u not in history: 150 | o_gate.con.append(u) 151 | 152 | history.append(item) 153 | value = self.simplify(o_gate, history, target) 154 | history.pop() 155 | if len(o_gate.con) < 1: 156 | a_gate.con.remove(o_gate) 157 | if len(a_gate.con) == 1 and value == 0: 158 | gate.con.append(item) 159 | gate.con.remove(a_gate) 160 | 161 | value = value * item.val 162 | 163 | return value 164 | 165 | def targetOut(self, rootGate, target): 166 | self.targetOutRecursive(rootGate, target) 167 | for gate in rootGate.con: 168 | gate.con.append(target) 169 | self.deleteEmptyGates(rootGate) 170 | 171 | def deleteEmptyGates(self, gate): 172 | removedGates = [] 173 | for node in gate.con: 174 | if node.t in ['andGate', 'orGate']: 175 | if (len(node.con) is 1) and (node.con[0] is "removed"): 176 | removedGates.append(node) 177 | else: 178 | self.deleteEmptyGates(node) 179 | 180 | for node in removedGates: 181 | gate.con.remove(node) 182 | 183 | def targetOutRecursive(self, gate, target): 184 | toChange = [] 185 | for node in gate.con: 186 | if node is target: 187 | if len(gate.con) is 1: 188 | del gate.con[:] 189 | gate.con.append("removed") 190 | break 191 | else: 192 | toChange.append(node) 193 | 194 | elif node.t in ['andGate', 'orGate']: 195 | self.targetOutRecursive(node, target) 196 | for node in toChange: 197 | gate.con.remove(node) 198 | nothing = tNode('at-.') 199 | nothing.val = 1 200 | gate.con.append(nothing) 201 | 202 | #Fold gate with one child 203 | def foldgate(self, gate): 204 | removedGates = [] 205 | for node in gate.con: 206 | if node.t in ['andGate', 'orGate']: 207 | self.foldgate(node) 208 | if len(node.con) == 1: 209 | gate.con.extend(node.con) 210 | removedGates.append(node) 211 | for node in removedGates: 212 | gate.con.remove(node) 213 | 214 | def tPrintRecursive(self, gate): 215 | print(gate.name, '->',) 216 | for u in gate.con: 217 | print(u.name,) 218 | print 219 | for u in gate.con: 220 | if u.t in ['andGate', 'orGate']: 221 | self.tPrintRecursive(u) 222 | 223 | #Print tree 224 | def treePrint(self): 225 | self.tPrintRecursive(self.topGate) 226 | 227 | 228 | #--------------------------------------------------------------------------------------------------------------------------- 229 | #Security analysis part: including attack impact, attack cost, return-on-attack, risk and attack success probability 230 | 231 | 232 | #--------------------------------------------------------------------------------------------------------------------------- 233 | #AT is upper layer 234 | #Assign child value to node value recursively 235 | def getImpactValueRecursive(self, gate): 236 | for u in gate.con: 237 | if u.t is "node": 238 | if u.child is not None: 239 | u.val = u.child.calcImpact() 240 | else: 241 | self.getImpactValueRecursive(u) 242 | 243 | def getImpactValue(self): 244 | self.getImpactValueRecursive(self.topGate) 245 | 246 | 247 | def getCostValueRecursive(self, gate): 248 | for u in gate.con: 249 | if u.t is "node": 250 | if u.child is not None: 251 | u.val = u.child.calcCost() 252 | else: 253 | self.getCostValueRecursive(u) 254 | 255 | def getCostValue(self): 256 | self.getCostValueRecursive(self.topGate) 257 | 258 | def getProValueRecursive(self, gate): 259 | for u in gate.con: 260 | if u.t is "node": 261 | if u.child is not None: 262 | u.val = u.child.calcPro() 263 | else: 264 | self.getProValueRecursive(u) 265 | 266 | def getProValue(self): 267 | self.getProValueRecursive(self.topGate) 268 | 269 | 270 | #---------------------------------------------------------------------------------------------- 271 | #AT is lower layer 272 | 273 | #Calculate the impact value for each node in the attack tree 274 | def calcImpactRecursive(self, s): 275 | if s.t is "andGate": 276 | val = 0 277 | for u in s.con: 278 | val += self.calcImpactRecursive(u) 279 | print ('and: ', val) 280 | elif s.t is "orGate": 281 | val = 0 282 | for u in s.con: 283 | tval = self.calcImpactRecursive(u) 284 | if tval >= val: 285 | val = tval 286 | print('or:', val) 287 | elif s.t is "node": 288 | val = s.val 289 | print('node value: ', val, s.name) 290 | else: 291 | val = 0 292 | return val 293 | 294 | #Get the impact value of each node in the attack tree 295 | def calcImpact(self): 296 | return self.calcImpactRecursive(self.topGate) 297 | 298 | 299 | #Calculate the attack cost value for each node in the attack tree 300 | def calcCostRecursive(self, s): 301 | if s.t is "andGate": 302 | val = 0 303 | for u in s.con: 304 | val += self.calcCostRecursive(u) 305 | elif s.t is "orGate": 306 | val = 0 307 | tval = [] 308 | for u in s.con: 309 | tval.append(self.calcCostRecursive(u)) 310 | #choose the minimum cost value 311 | val = min(tval) 312 | elif s.t is "node": 313 | val = s.val 314 | else: 315 | val = 0 316 | return val 317 | 318 | #Get the attack cost value of each node in the attack tree 319 | def calcCost(self): 320 | return self.calcCostRecursive(self.topGate) 321 | 322 | 323 | #Calculate the probability value for each node in the attack tree 324 | def calcProRecursive(self, s): 325 | if s.t is "andGate": 326 | val = 1.0 327 | for u in s.con: 328 | val *= self.calcProRecursive(u) #probability 329 | #print('and: ', val) 330 | elif s.t is "orGate": 331 | val = 1.0 332 | for u in s.con: 333 | tval = self.calcProRecursive(u) 334 | #print('tval: ', tval) 335 | if tval > 0: 336 | val *= (1.0-self.calcProRecursive(u)) #probability 337 | val = 1.0-val 338 | #print('or: ', val) 339 | elif s.t is "node" and s.val > 0: 340 | val = s.val 341 | #print('node: ', val, s.name) 342 | else: 343 | val = 1.0 344 | return val 345 | 346 | #Get the probability value of each node in the attack tree 347 | def calcPro(self): 348 | print('==============================================') 349 | return self.calcProRecursive(self.topGate) 350 | 351 | 352 | #Calculate the risk value for each node in the attack tree 353 | def calcRiskRecursive(self, s): 354 | if s.t is "andGate": 355 | val = 0 356 | for u in s.con: 357 | val += self.calcRiskRecursive(u) 358 | #print ('and:', val) 359 | elif s.t is "orGate": 360 | val = 0 361 | for u in s.con: 362 | tval = self.calcRiskRecursive(u) 363 | if tval >= val: 364 | val = tval 365 | elif s.t is "node": 366 | val = s.val 367 | else: 368 | val = 0 369 | return val 370 | 371 | #Get the risk value of each node in the attack tree 372 | def calcRisk(self): 373 | return self.calcRiskRecursive(self.topGate) 374 | 375 | 376 | #Calculate the return on attack path value for each node in the attack tree 377 | def calcReturnOnAttackRecursive(self, s): 378 | if s.t is "andGate": 379 | val = 0 380 | for u in s.con: 381 | val += self.calcReturnOnAttackRecursive(u) 382 | #print ('and:', val) 383 | elif s.t is "orGate": 384 | val = 0 385 | for u in s.con: 386 | tval = self.calcReturnOnAttackRecursive(u) 387 | if tval >= val: 388 | val = tval 389 | elif s.t is "node": 390 | val = s.val 391 | else: 392 | val = 0 393 | return val 394 | 395 | #Get the return on attack path value of each node in the attack tree 396 | def calcReturnOnAttack(self): 397 | return self.calcReturnOnAttackRecursive(self.topGate) 398 | 399 | #When only one node is in the attack tree, calculate the value for the node 400 | def getNodeValue(self, s): 401 | if s.t is "andGate": 402 | val = 0 403 | for u in s.con: 404 | val = self.getNodeValue(u) 405 | elif s.t is "orGate": 406 | val = 0 407 | for u in s.con: 408 | val = self.getNodeValue(u) 409 | elif s.t is "node": 410 | val = s.val 411 | else: 412 | val = 0 413 | return val 414 | 415 | #For MTTC 416 | #Get value recursively 417 | def getValueRecursive(self, gate, elements): 418 | for u in gate.con: 419 | if u.t is "node": 420 | if u.val > 0: 421 | elements.append((u.name, u.val)) 422 | #print (elements) 423 | else: 424 | self.getValueRecursive(u, elements) 425 | return None 426 | 427 | #Get gate type recursively 428 | def getGateRecursive(self, gate, orn, andn): 429 | for u in gate.con: 430 | if u.t is 'orGate': 431 | orn = orn + 1 432 | #print (u.name, orn) 433 | orn, andn = self.getGateRecursive(u, orn, andn) 434 | elif u.t is 'andGate': 435 | andn = andn + 1 436 | #print (u.name, andn) 437 | orn, andn = self.getGateRecursive(u, orn, andn) 438 | return (orn, andn) 439 | -------------------------------------------------------------------------------- /database.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "attack_tree_library", 3 | "version": "1.0", 4 | "created": "2021-01-01", 5 | "object": [ 6 | { 7 | "object_id": 0, 8 | "name": "Power Grid Model", 9 | "description": "Power Grid Model", 10 | "model_list":[ 11 | { 12 | "list_name": "IEEE 4 Node", 13 | "streets_and_houses": [ 14 | { 15 | "A": 5, 16 | "B": 6, 17 | "C": 8, 18 | "D": 12 19 | } 20 | ] 21 | }, 22 | { 23 | "list_name": "IEEE 13 Node", 24 | "streets_and_houses": [ 25 | { 26 | "A": 5, 27 | "B": 6, 28 | "C": 8, 29 | "D": 12, 30 | "E": 15 31 | } 32 | ] 33 | }, 34 | { 35 | "list_name": "R4-12.47-2", 36 | "streets_and_houses": [ 37 | { 38 | "A": 2, 39 | "B": 2, 40 | "C": 1, 41 | "D": 1, 42 | "E": 1 43 | } 44 | ] 45 | } 46 | ] 47 | }, 48 | 49 | { 50 | "object_id": 1, 51 | "name": "Devices", 52 | "description": "Devices List", 53 | "devices_list":[ 54 | { 55 | "device_name": "Smart TV", 56 | "CVE_list": ["CVE-2019-9871","CVE-2019-11336", "CVE-2019-12477", "CVE-2018-13989", "CVE-2020-9264"], 57 | "group": 1 58 | }, 59 | { 60 | "device_name": "Smart Thermostat", 61 | "CVE_list": ["CVE-2018-11315","CVE-2013-4860"], 62 | "group": 1 63 | }, 64 | { 65 | "device_name": "Cleaner", 66 | "CVE_list": ["CVE-2018-17177","CVE-2018-10987","CVE-2018-20785","CVE-2019-12821", "CVE-2019-12820"], 67 | "group": 1 68 | }, 69 | { 70 | "device_name": "Light", 71 | "CVE_list": ["CVE-2020-6007","CVE-2019-18980","CVE-2017-14797"], 72 | "group": 1 73 | }, 74 | { 75 | "device_name": "IP Camera", 76 | "CVE_list": ["CVE-2020-3110","CVE-2020-11949", "CVE-2020-11623"], 77 | "group": 1 78 | }, 79 | { 80 | "device_name": "Smart Meter", 81 | "CVE_list": ["CVE-2017-9944"], 82 | "group": 2 83 | }, 84 | { 85 | "device_name": "Gateway", 86 | "CVE_list": ["CVE-2018-3879","CVE-2018-3911","CVE-2018-3907", "CVE-2018-3909", "CVE-2018-3902", "CVE-2018-3880", "CVE-2018-3906", "CVE-2018-3912", "CVE-2018-3917", "CVE-2018-3919", "CVE-2018-3926", "CVE-2018-3927"], 87 | "group": 2 88 | }, 89 | { 90 | "device_name": "Concentrator", 91 | "CVE_list": ["CVE-2020-1638"], 92 | "group": 3 93 | }, 94 | { 95 | "device_name": "Local Terminal", 96 | "CVE_list": ["CVE-2020-9391", "CVE-2019-6454"], 97 | "group": 3 98 | }, 99 | 100 | { 101 | "device_name": "PMU", 102 | "CVE_list": ["CVE-2020-9391", "CVE-2019-6454"], 103 | "group": 3 104 | }, 105 | 106 | { 107 | "device_name": "Substation RTU", 108 | "CVE_list": ["CVE-2020-9391", "CVE-2019-6454"], 109 | "group": 3 110 | }, 111 | 112 | { 113 | "device_name": "PLC", 114 | "CVE_list": ["CVE-2020-9391", "CVE-2019-6454"], 115 | "group": 3 116 | }, 117 | { 118 | "device_name": "FEP", 119 | "CVE_list": ["CVE-2020-9391", "CVE-2019-6454"], 120 | "group": 3 121 | }, 122 | { 123 | "device_name": "Application Server", 124 | "CVE_list": ["CVE-2020-9391", "CVE-2019-6454"], 125 | "group": 3 126 | }, 127 | { 128 | "device_name": "Active Directory Server", 129 | "CVE_list": ["CVE-2020-9391", "CVE-2019-6454"], 130 | "group": 3 131 | }, 132 | { 133 | "device_name": "Historian Server", 134 | "CVE_list": ["CVE-2020-9391", "CVE-2019-6454"], 135 | "group": 3 136 | }, 137 | { 138 | "device_name": "Communication Server", 139 | "CVE_list": ["CVE-2020-9391", "CVE-2019-6454"], 140 | "group": 3 141 | }, 142 | { 143 | "device_name": "HMI", 144 | "CVE_list": ["CVE-2020-9391", "CVE-2019-6454"], 145 | "group": 3 146 | }, 147 | { 148 | "device_name": "Work Station", 149 | "CVE_list": ["CVE-2020-9391", "CVE-2019-6454"], 150 | "group": 3 151 | }, 152 | { 153 | "device_name": "ICCP Server", 154 | "CVE_list": ["CVE-2020-9391", "CVE-2019-6454"], 155 | "group": 3 156 | }, 157 | { 158 | "device_name": "EMS DRP Server", 159 | "CVE_list": ["CVE-2020-9391", "CVE-2019-6454"], 160 | "group": 3 161 | } 162 | 163 | 164 | ] 165 | }, 166 | 167 | { 168 | "object_id": 2, 169 | "name": "CVE", 170 | "description": "CVE List", 171 | "CVE_list":[ 172 | { 173 | "CVE": "CVE-2017-9944", 174 | "description": "Allow an unauthenticated remote attacker to perform administrative operations over the network", 175 | "CVSS_Base_Score_2.0": 10, 176 | "Impact_Subscore": 10, 177 | "Exploitability_Subscore": 10 178 | 179 | }, 180 | { 181 | "CVE": "CVE-2020-9391", 182 | "description": "An issue was discovered in the Linux kernel 5.4 and 5.5 through 5.5.6 on the AArch64 architecture. It ignores the top byte in the address passed to the brk system call, potentially moving the memory break downwards when the application expects it to move upwards, aka CID-dcde237319e6. This has been observed to cause heap corruption with the GNU C Library malloc implementation.", 183 | "CVSS_Base_Score_2.0": 2.1, 184 | "Impact_Subscore": 2.9, 185 | "Exploitability_Subscore": 3.9 186 | 187 | }, 188 | { 189 | "CVE": "CVE-2019-6454", 190 | "description": "An issue was discovered in sd-bus in systemd 239. bus_process_object() in libsystemd/sd-bus/bus-objects.c allocates a variable-length stack buffer for temporarily storing the object path of incoming D-Bus messages. An unprivileged local user can exploit this by sending a specially crafted message to PID1, causing the stack pointer to jump over the stack guard pages into an unmapped memory region and trigger a denial of service (systemd PID1 crash and kernel panic).\n\n", 191 | "CVSS_Base_Score_2.0": 4.9, 192 | "Impact_Subscore": 6.9, 193 | "Exploitability_Subscore": 3.9 194 | 195 | }, 196 | { 197 | "CVE": "CVE-2019-12477", 198 | "description": "Supra Smart Cloud TV allows remote file inclusion in the openLiveURL function, which allows a local attacker to broadcast fake video without any authentication via a /remote/media_control?action=setUri&uri= URI.", 199 | "CVSS_Base_Score_2.0": 2.1, 200 | "Impact_Subscore": 2.9, 201 | "Exploitability_Subscore": 3.9 202 | 203 | }, 204 | { 205 | "CVE": "CVE-2019-12820", 206 | "description": "A vulnerability was found in the app 2.0 of the Shenzhen Jisiwei i3 robot vacuum cleaner. Actions performed on the app such as changing a password, and personal information it communicates with the server, use unencrypted HTTP. As an example, while logging in through the app to a Jisiwei account, the login request is being sent in cleartext. The vulnerability exists in both the Android and iOS version of the app. An attacker could exploit this by using an MiTM attack on the local network to obtain someone's login credentials, which gives them full access to the robot vacuum cleaner.", 207 | "CVSS_Base_Score_2.0": 4.3, 208 | "Impact_Subscore": 2.9, 209 | "Exploitability_Subscore": 8.6 210 | 211 | }, 212 | { 213 | "CVE": "CVE-2019-18980", 214 | "description": "Lack of Transport Encryption in the public API in Philips Hue Bridge BSB002 SW 1707040932 allows remote attackers to read API keys (and consequently bypass the pushlink protection mechanism, and obtain complete control of the connected accessories) by leveraging the ability to sniff HTTP traffic on the local intranet network.", 215 | "CVSS_Base_Score_2.0": 7.9, 216 | "Impact_Subscore": 10, 217 | "Exploitability_Subscore": 5.5 218 | 219 | }, 220 | { 221 | "CVE": "CVE-2018-13989", 222 | "description": "Grundig Smart Inter@ctive TV 3.0 devices allow CSRF attacks via a POST request to TCP port 8085 containing a predictable ID value, as demonstrated by a /sendrcpackage?keyid=-2544&keysymbol=-4081 request to shut off the device.", 223 | "CVSS_Base_Score_2.0": 8.3, 224 | "Impact_Subscore": 8.5, 225 | "Exploitability_Subscore": 8.6 226 | }, 227 | { 228 | "CVE": "CVE-2020-3110", 229 | "description": "A vulnerability in the Cisco Discovery Protocol implementation for the Cisco Video Surveillance 8000 Series IP Cameras could allow an unauthenticated, adjacent attacker to execute code remotely or cause a reload of an affected IP Camera. The vulnerability is due to missing checks when processing Cisco Discovery Protocol messages. An attacker could exploit this vulnerability by sending a malicious Cisco Discovery Protocol packet to the targeted IP Camera. A successful exploit could allow the attacker to expose the affected IP Camera for remote code execution or cause it to reload unexpectedly, resulting in a denial of service (DoS) condition. Cisco Discovery Protocol is a Layer 2 protocol. To exploit this vulnerability, an attacker must be in the same broadcast domain as the affected device (Layer 2 adjacent). This vulnerability is fixed in Video Surveillance 8000 Series IP Camera Firmware Release 1.0.7 and later.", 230 | "CVSS_Base_Score_2.0": 8.3, 231 | "Impact_Subscore": 10, 232 | "Exploitability_Subscore": 6.5 233 | }, 234 | { 235 | "CVE": "CVE-2020-9264", 236 | "description": "ESET Archive Support Module before 1296 allows virus-detection bypass via a crafted Compression Information Field in a ZIP archive. This affects versions before 1294 of Smart Security Premium, Internet Security, NOD32 Antivirus, Cyber Security Pro (macOS), Cyber Security (macOS), Mobile Security for Android, Smart TV Security, and NOD32 Antivirus 4 for Linux Desktop.", 237 | "CVSS_Base_Score_2.0": 4.3, 238 | "Impact_Subscore": 2.9, 239 | "Exploitability_Subscore": 8.6 240 | }, 241 | { 242 | "CVE": "CVE-2020-11949", 243 | "description": "testserver.cgi of the web service on VIVOTEK Network Cameras before XXXXX-VVTK-2.2002.xx.01x (and before XXXXX-VVTK-0XXXX_Beta2) allows an authenticated user to obtain arbitrary files from a camera's local filesystem. For example, this affects IT9388-HT devices.", 244 | "CVSS_Base_Score_2.0": 4, 245 | "Impact_Subscore": 2.9, 246 | "Exploitability_Subscore": 8 247 | }, 248 | { 249 | "CVE": "CVE-2020-11623", 250 | "description": "An issue was discovered in AvertX Auto focus Night Vision HD Indoor/Outdoor IP Dome Camera HD838 and Night Vision HD Indoor/Outdoor Mini IP Bullet Camera HD438. An attacker with physical access to the UART interface could access additional diagnostic and configuration functionalities as well as the camera's bootloader. Successful exploitation could compromise confidentiality, integrity, and availability of the affected system. It could even render the device inoperable.", 251 | "CVSS_Base_Score_2.0": 7.2, 252 | "Impact_Subscore": 10, 253 | "Exploitability_Subscore": 3.9 254 | }, 255 | { 256 | "CVE": "CVE-2020-1638", 257 | "description": "The FPC (Flexible PIC Concentrator) of Juniper Networks Junos OS and Junos OS Evolved may restart after processing a specific IPv4 packet", 258 | "CVSS_Base_Score_2.0": 5, 259 | "Impact_Subscore": 2.9, 260 | "Exploitability_Subscore": 10 261 | 262 | }, 263 | { 264 | "CVE": "CVE-2019-12821", 265 | "description": "\"A vulnerability was found in the app 2.0 of the Shenzhen Jisiwei i3 robot vacuum cleaner, while adding a device to the account using a QR-code. The QR-code follows an easily predictable pattern that depends only on the specific device ID of the robot vacuum cleaner. By generating a QR-code containing information about the device ID, it is possible to connect an arbitrary device and gain full access to it. The device ID has an initial \"\"JSW\"\" substring followed by a six digit number that depends on the specific device.\n\"", 266 | "CVSS_Base_Score_2.0": 5.8, 267 | "Impact_Subscore": 4.9, 268 | "Exploitability_Subscore": 8.6 269 | 270 | }, 271 | { 272 | "CVE": "CVE-2019-11890", 273 | "description": "Sony Bravia Smart TV devices allow remote attackers to cause a denial of service (device hang or reboot) via a SYN flood attack over a wired or Wi-Fi LAN.", 274 | "CVSS_Base_Score_2.0": 7.8, 275 | "Impact_Subscore": 6.9, 276 | "Exploitability_Subscore": 10 277 | 278 | }, 279 | 280 | 281 | { 282 | "CVE": "CVE-2019-11336", 283 | "description": "Sony Bravia Smart TV devices allow remote attackers to retrieve the static Wi-Fi password (used when the TV is acting as an access point) by using the Photo Sharing Plus application to execute a backdoor API command, a different vulnerability than CVE-2019-10886.", 284 | "CVSS_Base_Score_2.0": 4.3, 285 | "Impact_Subscore": 2.9, 286 | "Exploitability_Subscore": 8.6 287 | 288 | }, 289 | 290 | 291 | { 292 | "CVE": "CVE-2018-6294", 293 | "description": "Unsecured way of firmware update in Hanwha Techwin Smartcams", 294 | "CVSS_Base_Score_2.0": 7.5, 295 | "Impact_Subscore": 6.4, 296 | "Exploitability_Subscore": 10 297 | 298 | }, 299 | 300 | 301 | { 302 | "CVE": "CVE-2020-6007", 303 | "description": "Philips Hue Bridge model 2.X prior to and including version 1935144020 contains a Heap-based Buffer Overflow when handling a long ZCL string during the commissioning phase, resulting in a remote code execution.", 304 | "CVSS_Base_Score_2.0": 4.3, 305 | "Impact_Subscore": 6.4, 306 | "Exploitability_Subscore": 3.2 307 | 308 | }, 309 | 310 | 311 | 312 | 313 | { 314 | "CVE": "CVE-2019-18980", 315 | "description": "On Signify Philips Taolight Smart Wi-Fi Wiz Connected LED Bulb 9290022656 devices, an unprotected API lets remote users control the bulb's operation. Anyone can turn the bulb on or off, or change its color or brightness remotely. There is no authentication or encryption to use the control API. The only requirement is that the attacker have network access to the bulb.", 316 | "CVSS_Base_Score_2.0": 5, 317 | "Impact_Subscore": 2.9, 318 | "Exploitability_Subscore": 10 319 | 320 | }, 321 | 322 | 323 | { 324 | "CVE": "CVE-2018-11315", 325 | "description": "The Local HTTP API in Radio Thermostat CT50 and CT80 1.04.84 and below products allows unauthorized access via a DNS rebinding attack. This can result in remote device temperature control, as demonstrated by a tstat t_heat request that accesses a device purchased in the Spring of 2018, and sets a home's target temperature to 95 degrees Fahrenheit. This vulnerability might be described as an addendum to CVE-2013-4860.", 326 | "CVSS_Base_Score_2.0": 3.3, 327 | "Impact_Subscore": 2.9, 328 | "Exploitability_Subscore": 6.5 329 | 330 | }, 331 | 332 | { 333 | "CVE": "CVE-2018-17177", 334 | "description": "An issue was discovered on Neato Botvac Connected 2.2.0 and Botvac 85 1.2.1 devices. Static encryption is used for the copying of so-called \"black box\" logs (event logs and core dumps) to a USB stick. These logs are RC4-encrypted with a 9-character password of *^JEd4W!I that is obfuscated by hiding it within a custom /bin/rc4_crypt binary.", 335 | "CVSS_Base_Score_2.0": 2.1, 336 | "Impact_Subscore": 2.9, 337 | "Exploitability_Subscore": 3.9 338 | 339 | }, 340 | 341 | { 342 | "CVE": "CVE-2018-20785", 343 | "description": "Secure boot bypass and memory extraction can be achieved on Neato Botvac Connected 2.2.0 devices. During startup, the AM335x secure boot feature decrypts and executes firmware. Secure boot can be bypassed by starting with certain commands to the USB serial port. Although a power cycle occurs, this does not completely reset the chip: memory contents are still in place. Also, it restarts into a boot menu that enables XMODEM upload and execution of an unsigned QNX IFS system image, thereby completing the bypass of secure boot. Moreover, the attacker can craft custom IFS data and write it to unused memory to extract all memory contents that had previously been present. This includes the original firmware and sensitive information such as Wi-Fi credentials.", 344 | "CVSS_Base_Score_2.0": 4.4, 345 | "Impact_Subscore": 6.4, 346 | "Exploitability_Subscore": 3.4 347 | 348 | }, 349 | 350 | 351 | { 352 | "CVE": "CVE-2018-10987", 353 | "description": "An issue was discovered on Dongguan Diqee Diqee360 devices. The affected vacuum cleaner suffers from an authenticated remote code execution vulnerability. An authenticated attacker can send a specially crafted UDP packet, and execute commands on the vacuum cleaner as root. The bug is in the function REQUEST_SET_WIFIPASSWD (UDP command 153). A crafted UDP packet runs \"/mnt/skyeye/mode_switch.sh %s\" with an attacker controlling the %s variable. In some cases, authentication can be achieved with the default password of 888888 for the admin account.", 354 | "CVSS_Base_Score_2.0": 8.5, 355 | "Impact_Subscore": 10, 356 | "Exploitability_Subscore": 6.8 357 | 358 | }, 359 | 360 | { 361 | "CVE": "CVE-2019-9871", 362 | "description": "Allow remote code execution", 363 | "CVSS_Base_Score_2.0": 10, 364 | "Impact_Subscore": 10, 365 | "Exploitability_Subscore": 10 366 | 367 | }, 368 | 369 | 370 | 371 | { 372 | "CVE": "CVE-2013-4860", 373 | "description": "Allow remote attackers to change settings", 374 | "CVSS_Base_Score_2.0": 8.3, 375 | "Impact_Subscore": 10, 376 | "Exploitability_Subscore": 6.5 377 | 378 | }, 379 | 380 | { 381 | "CVE": "CVE-2018-19442", 382 | "description": "Allow a remote attacker to execute arbitrary code with root privileges", 383 | "CVSS_Base_Score_2.0": 10, 384 | "Impact_Subscore": 10, 385 | "Exploitability_Subscore": 10 386 | 387 | }, 388 | 389 | { 390 | "CVE": "CVE-2017-14797", 391 | "description": "Allows remote attackers to read API keys", 392 | "CVSS_Base_Score_2.0": 7.9, 393 | "Impact_Subscore": 10, 394 | "Exploitability_Subscore": 5.5 395 | 396 | }, 397 | { 398 | "CVE": "CVE-2019-18226", 399 | "description": "A weak authentication method", 400 | "CVSS_Base_Score_2.0": 7.5, 401 | "Impact_Subscore": 6.4, 402 | "Exploitability_Subscore": 10 403 | 404 | }, 405 | 406 | { 407 | "CVE": "CVE-2018-3911", 408 | "description": "An exploitable vulnerability exists in the REST parser of video-core's HTTP server of the Samsung SmartThings Hub STH-ETH-250 - Firmware version 0.20.17", 409 | "CVSS_Base_Score_2.0": 5, 410 | "Impact_Subscore": 2.9, 411 | "Exploitability_Subscore": 10 412 | 413 | }, 414 | 415 | 416 | 417 | { 418 | "CVE": "CVE-2018-3907", 419 | "description": "An exploitable vulnerability exists in the REST parser of video-core's HTTP server of the Samsung SmartThings Hub STH-ETH-250 - Firmware version 0.20.17", 420 | "CVSS_Base_Score_2.0": 6.4, 421 | "Impact_Subscore": 4.9, 422 | "Exploitability_Subscore": 10 423 | 424 | }, 425 | 426 | 427 | 428 | { 429 | "CVE": "CVE-2018-3909", 430 | "description": "An exploitable vulnerability exists in the REST parser of video-core's HTTP server of the Samsung SmartThings Hub STH-ETH-250 - Firmware version 0.20.17", 431 | "CVSS_Base_Score_2.0": 6.4, 432 | "Impact_Subscore": 4.9, 433 | "Exploitability_Subscore": 10 434 | 435 | }, 436 | 437 | 438 | 439 | { 440 | "CVE": "CVE-2018-3902", 441 | "description": "An exploitable buffer overflow vulnerability exists in the camera \"replace\" feature of video-core's HTTP server of Samsung SmartThings Hub STH-ETH-250 devices with firmware version 0.20.17", 442 | "CVSS_Base_Score_2.0": 9, 443 | "Impact_Subscore": 10, 444 | "Exploitability_Subscore": 8 445 | 446 | }, 447 | 448 | { 449 | "CVE": "CVE-2018-3879", 450 | "description": "An exploitable JSON injection vulnerability exists in the credentials handler of video-core's HTTP server of Samsung SmartThings Hub STH-ETH-250 devices with firmware version 0.20.17", 451 | "CVSS_Base_Score_2.0": 6.5, 452 | "Impact_Subscore": 6.4, 453 | "Exploitability_Subscore": 8 454 | 455 | }, 456 | 457 | 458 | 459 | { 460 | "CVE": "CVE-2018-3880", 461 | "description": "An exploitable stack-based buffer overflow vulnerability exists in the database 'find-by-cameraId' functionality of video-core's HTTP server of Samsung SmartThings Hub STH-ETH-250 - Firmware version 0.20.17. The video-core process incorrectly handles existing records inside its SQLite database, leading to a buffer overflow on the stack. An attacker can send an HTTP request to trigger this vulnerability.", 462 | "CVSS_Base_Score_2.0": 9, 463 | "Impact_Subscore": 10, 464 | "Exploitability_Subscore": 8 465 | 466 | }, 467 | 468 | 469 | { 470 | "CVE": "CVE-2018-3906", 471 | "description": "An exploitable stack-based buffer overflow vulnerability exists in the retrieval of a database field in video-core's HTTP server of Samsung SmartThings Hub. The video-core process insecurely extracts the shard.videoHostURL field from its SQLite database, leading to a buffer overflow on the stack. An attacker can send an HTTP request to trigger this vulnerability.", 472 | "CVSS_Base_Score_2.0": 7.2, 473 | "Impact_Subscore": 10, 474 | "Exploitability_Subscore": 3.9 475 | 476 | }, 477 | 478 | 479 | { 480 | "CVE": "CVE-2018-3912", 481 | "description": "On Samsung SmartThings Hub STH-ETH-250 devices with firmware version 0.20.17, the video-core process insecurely extracts the fields from the \"shard\" table of its SQLite database, leading to a buffer overflow on the stack. The strcpy call overflows the destination buffer, which has a size of 128 bytes. An attacker can send an arbitrarily long \"secretKey\" value in order to exploit this vulnerability.", 482 | "CVSS_Base_Score_2.0": 6.9, 483 | "Impact_Subscore": 10, 484 | "Exploitability_Subscore": 3.4 485 | 486 | }, 487 | 488 | 489 | 490 | { 491 | "CVE": "CVE-2018-3917", 492 | "description": "On Samsung SmartThings Hub STH-ETH-250 devices with firmware version 0.20.17, the video-core process insecurely extracts the fields from the 'shard' table of its SQLite database, leading to a buffer overflow on the stack. An attacker can send an HTTP request to trigger this vulnerability. The strcpy call overflows the destination buffer, which has a size of 16 bytes. An attacker can send an arbitrarily long 'region' value in order to exploit this vulnerability.", 493 | "CVSS_Base_Score_2.0": 9, 494 | "Impact_Subscore": 10, 495 | "Exploitability_Subscore": 8 496 | 497 | }, 498 | 499 | 500 | 501 | 502 | { 503 | "CVE": "CVE-2018-3919", 504 | "description": "An exploitable stack-based buffer overflow vulnerability exists in the retrieval of database fields in video-core's HTTP server of Samsung SmartThings Hub STH-ETH-250 devices with firmware version 0.20.17. The video-core process insecurely extracts the fields from the \"clips\" table of its SQLite database, leading to a buffer overflow on the stack. An attacker can send a series of HTTP requests to trigger this vulnerability.", 505 | "CVSS_Base_Score_2.0": 9, 506 | "Impact_Subscore": 10, 507 | "Exploitability_Subscore": 8 508 | 509 | }, 510 | 511 | 512 | 513 | { 514 | "CVE": "CVE-2018-3926", 515 | "description": "An exploitable integer underflow vulnerability exists in the ZigBee firmware update routine of the hubCore binary of the Samsung SmartThings Hub STH-ETH-250 - Firmware version 0.20.17. The hubCore process incorrectly handles malformed files existing in its data directory, leading to an infinite loop, which eventually causes the process to crash. An attacker can send an HTTP request to trigger this vulnerability.", 516 | "CVSS_Base_Score_2.0": 4.9, 517 | "Impact_Subscore": 6.9, 518 | "Exploitability_Subscore": 3.9 519 | 520 | }, 521 | 522 | 523 | 524 | { 525 | "CVE": "CVE-2018-3927", 526 | "description": "An exploitable information disclosure vulnerability exists in the crash handler of the hubCore binary of the Samsung SmartThings Hub STH-ETH-250 - Firmware version 0.20.17. When hubCore crashes, Google Breakpad is used to record minidumps, which are sent over an insecure HTTPS connection to the backtrace.io service, leading to the exposure of sensitive data. An attacker can impersonate the remote backtrace.io server in order to trigger this vulnerability.", 527 | "CVSS_Base_Score_2.0": 4.3, 528 | "Impact_Subscore": 2.9, 529 | "Exploitability_Subscore": 8.6 530 | 531 | }, 532 | 533 | 534 | { 535 | "CVE": "CVE-2019-11063", 536 | "description": "A broken access control vulnerability in SmartHome app (Android versions up to 3.0.42_190515, ios versions up to 2.0.22) allows an attacker in the same local area network to list user accounts and control IoT devices that connect with its gateway (HG100) via http://[target]/smarthome/devicecontrol without any authentication. CVSS 3.0 base score 10 (Confidentiality, Integrity and Availability impacts). CVSS vector: (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H).", 537 | "CVSS_Base_Score_2.0": 8.3, 538 | "Impact_Subscore": 10, 539 | "Exploitability_Subscore": 6.5 540 | 541 | } 542 | 543 | 544 | ] 545 | } 546 | 547 | 548 | 549 | 550 | ] 551 | } 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | -------------------------------------------------------------------------------- /Scripts/NetGen.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module generates example IoT networks based on topology type and vulnerabilities. 3 | Modified by LE Duy Tan 1st March 2021 4 | """ 5 | 6 | from Network import * 7 | from Harm import * 8 | from SecurityEvaluator import * 9 | import math 10 | import random 11 | import os 12 | import sys 13 | import json 14 | from collections import OrderedDict 15 | import csv 16 | from shutil import copyfile 17 | from datetime import datetime 18 | import shutil 19 | import os.path 20 | from os import path 21 | """ 22 | ------------------------------------------------------------------------- 23 | Create network with vulnerabilities for the example IoT network. 24 | ------------------------------------------------------------------------- 25 | """ 26 | 27 | tv_name = [] 28 | cam_name = [] 29 | thermostat_name = [] 30 | cleaner_name = [] 31 | light_name = [] 32 | meter_name = [] 33 | gateway_name = [] 34 | concentrator_name = [] 35 | local_terminal_name = [] 36 | substation_rtu_name = [] 37 | pmu_name = [] 38 | plc_name = [] 39 | fep_name = [] 40 | application_server_name = [] 41 | active_directory_server_name = [] 42 | historian_server_name = [] 43 | communication_server_name = [] 44 | hmi_name = [] 45 | work_station_name = [] 46 | iccp_server_name = [] 47 | ems_drp_server_name = [] 48 | 49 | def find_cve_values(cve): 50 | find =['','',0,0,0] 51 | flag = False 52 | with open(source_file_name) as f: 53 | for l, i in enumerate(f): 54 | data = i.split(",") 55 | if data[0] == cve: 56 | flag = True 57 | find.clear() 58 | find = data 59 | break 60 | return find, flag 61 | 62 | 63 | 64 | def createWiFi(street_list, house_list, entry, target): 65 | 66 | # Create a Wi-Fi network 67 | net = network() 68 | #print(find_cve_values('CVE-2019-9871')) 69 | 70 | #create central concentrator 71 | central_concentrator_name = 'central_concentrator' 72 | central_concentrator = iot(central_concentrator_name) 73 | central_concentrator.subnet.append('zb') #another type 74 | v_central_concentrator = vulNode(find_cve_values("central_concentrator")[0][1]) 75 | # createVuls(self, node, metricValue, pri) 76 | """ 77 | Create vulnerability network for the node. 78 | :param node: node in the network which has vulnerabilities 79 | :param metricValue: assign a metric value to vulnerability 80 | (e.g. attack probability) 81 | => metricValue = Exploitability Subscore/10 82 | :param pri: assign privilege value to vulnerability 83 | (1: user; 2: admin; 3: root) 84 | root higher privilege vulnerability 85 | 86 | :returns: none 87 | """ 88 | v_central_concentrator.createVuls(central_concentrator, float(find_cve_values("central_concentrator")[0][4]) / 10, 89 | 1) # Change 1 => 3, pri after exploiting vuls 90 | v_central_concentrator.thresholdPri(central_concentrator, 1) # change 1 => 0, pri before exploitation of attackers 91 | v_central_concentrator.terminalPri(central_concentrator, 92 | 1) # change 1 => 3, pri after expoitation #line 49 == line 51 93 | 94 | # create FEP 95 | fep_name = 'fep' 96 | fep = iot(fep_name) 97 | fep.subnet.append('zb') # another type 98 | v_fep = vulNode(find_cve_values("fep")[0][1]) 99 | v_fep.createVuls(fep, float(find_cve_values("fep")[0][4]) / 10, 1) # Change 1 => 3, pri after exploiting vuls 100 | v_fep.thresholdPri(fep, 1) # change 1 => 0, pri before exploitation of attackers 101 | v_fep.terminalPri(fep, 1) # change 1 => 3, pri after expoitation #line 49 == line 51 102 | 103 | # create application_server 104 | application_server_name = 'application_server' 105 | application_server = iot(application_server_name) 106 | application_server.subnet.append('zb') # another type 107 | v_application_server = vulNode(find_cve_values("application_server")[0][1]) 108 | v_application_server.createVuls(application_server, float(find_cve_values("application_server")[0][4]) / 10, 1) # Change 1 => 3, pri after exploiting vuls 109 | v_application_server.thresholdPri(application_server, 1) # change 1 => 0, pri before exploitation of attackers 110 | v_application_server.terminalPri(application_server, 1) # change 1 => 3, pri after expoitation #line 49 == line 51 111 | 112 | # create active_directory_server 113 | active_directory_server_name = 'active_directory_server' 114 | active_directory_server = iot(active_directory_server_name) 115 | active_directory_server.subnet.append('zb') # another type 116 | v_active_directory_server = vulNode(find_cve_values("active_directory_server")[0][1]) 117 | v_active_directory_server.createVuls(active_directory_server, float(find_cve_values("active_directory_server")[0][4]) / 10, 1) # Change 1 => 3, pri after exploiting vuls 118 | v_active_directory_server.thresholdPri(active_directory_server, 1) # change 1 => 0, pri before exploitation of attackers 119 | v_active_directory_server.terminalPri(active_directory_server, 1) # change 1 => 3, pri after expoitation #line 49 == line 51 120 | 121 | # create historian_server 122 | historian_server_name = 'historian_server' 123 | historian_server = iot(historian_server_name) 124 | historian_server.subnet.append('zb') # another type 125 | v_historian_server = vulNode(find_cve_values("historian_server")[0][1]) 126 | v_historian_server.createVuls(historian_server, 127 | float(find_cve_values("historian_server")[0][4]) / 10, 128 | 1) # Change 1 => 3, pri after exploiting vuls 129 | v_historian_server.thresholdPri(historian_server, 130 | 1) # change 1 => 0, pri before exploitation of attackers 131 | v_historian_server.terminalPri(historian_server, 132 | 1) # change 1 => 3, pri after expoitation #line 49 == line 51 133 | 134 | # create communication_server 135 | communication_server_name = 'communication_server' 136 | communication_server = iot(communication_server_name) 137 | communication_server.subnet.append('zb') # another type 138 | v_communication_server = vulNode(find_cve_values("communication_server")[0][1]) 139 | v_communication_server.createVuls(communication_server, 140 | float(find_cve_values("communication_server")[0][4]) / 10, 141 | 1) # Change 1 => 3, pri after exploiting vuls 142 | v_communication_server.thresholdPri(communication_server, 143 | 1) # change 1 => 0, pri before exploitation of attackers 144 | v_communication_server.terminalPri(communication_server, 145 | 1) # change 1 => 3, pri after expoitation #line 49 == line 51 146 | 147 | # create hmi 148 | hmi_name = 'hmi' 149 | hmi = iot(hmi_name) 150 | hmi.subnet.append('zb') # another type 151 | v_hmi = vulNode(find_cve_values("hmi")[0][1]) 152 | v_hmi.createVuls(hmi, float(find_cve_values("hmi")[0][4]) / 10, 1) # Change 1 => 3, pri after exploiting vuls 153 | v_hmi.thresholdPri(hmi, 1) # change 1 => 0, pri before exploitation of attackers 154 | v_hmi.terminalPri(hmi, 1) # change 1 => 3, pri after expoitation #line 49 == line 51 155 | 156 | # create work_station 157 | work_station_name = 'work_station' 158 | work_station = iot(work_station_name) 159 | work_station.subnet.append('zb') # another type 160 | v_work_station = vulNode(find_cve_values("work_station")[0][1]) 161 | v_work_station.createVuls(work_station, float(find_cve_values("work_station")[0][4]) / 10, 1) # Change 1 => 3, pri after exploiting vuls 162 | v_work_station.thresholdPri(work_station, 1) # change 1 => 0, pri before exploitation of attackers 163 | v_work_station.terminalPri(work_station, 1) # change 1 => 3, pri after expoitation #line 49 == line 51 164 | 165 | # create iccp_server 166 | iccp_server_name = 'iccp_server' 167 | iccp_server = iot(iccp_server_name) 168 | iccp_server.subnet.append('zb') # another type 169 | v_iccp_server= vulNode(find_cve_values("iccp_server")[0][1]) 170 | v_iccp_server.createVuls(iccp_server, float(find_cve_values("iccp_server")[0][4]) / 10, 1) # Change 1 => 3, pri after exploiting vuls 171 | v_iccp_server.thresholdPri(iccp_server, 1) # change 1 => 0, pri before exploitation of attackers 172 | v_iccp_server.terminalPri(iccp_server, 1) # change 1 => 3, pri after expoitation #line 49 == line 51 173 | 174 | # create ems_drp_server 175 | ems_drp_server_name = 'ems_drp_server' 176 | ems_drp_server = iot(ems_drp_server_name) 177 | ems_drp_server.subnet.append('zb') # another type 178 | v_ems_drp_server = vulNode(find_cve_values("ems_drp_server")[0][1]) 179 | v_ems_drp_server.createVuls(ems_drp_server, float(find_cve_values("ems_drp_server")[0][4]) / 10, 180 | 1) # Change 1 => 3, pri after exploiting vuls 181 | v_ems_drp_server.thresholdPri(ems_drp_server, 1) # change 1 => 0, pri before exploitation of attackers 182 | v_ems_drp_server.terminalPri(ems_drp_server, 1) # change 1 => 3, pri after expoitation #line 49 == line 51 183 | 184 | 185 | 186 | 187 | 188 | 189 | for i in range(len(street_list)): 190 | for j in range(len(house_list)): 191 | 192 | #Concentrator 193 | if j == 0: 194 | 195 | # concentrator 196 | concentrator_name.append('concentrator_' + street_list[i][0]) 197 | concentrator = iot(concentrator_name[i]) 198 | concentrator.subnet.append('zb') 199 | v_concentrator = vulNode(find_cve_values(concentrator_name[i])[0][1]) 200 | v_concentrator.createVuls(concentrator, float(find_cve_values(concentrator_name[i])[0][4])/10, 1) 201 | v_concentrator.thresholdPri(concentrator, 1) 202 | v_concentrator.terminalPri(concentrator, 1) 203 | 204 | #local_terminal 205 | local_terminal_name.append('local_terminal_' + street_list[i][0]) 206 | local_terminal = iot(local_terminal_name[i]) 207 | local_terminal.subnet.append('zb') 208 | v_local_terminal = vulNode(find_cve_values(local_terminal_name[i])[0][1]) 209 | v_local_terminal.createVuls(local_terminal, float(find_cve_values(local_terminal_name[i])[0][4]) / 10, 1) 210 | v_local_terminal.thresholdPri(local_terminal, 1) 211 | v_local_terminal.terminalPri(local_terminal, 1) 212 | 213 | # pmu 214 | pmu_name.append('pmu_' + street_list[i][0]) 215 | pmu = iot(pmu_name[i]) 216 | pmu.subnet.append('zb') 217 | v_pmu = vulNode(find_cve_values(pmu_name[i])[0][1]) 218 | v_pmu.createVuls(pmu, float(find_cve_values(pmu_name[i])[0][4]) / 10, 219 | 1) 220 | v_pmu.thresholdPri(pmu, 1) 221 | v_pmu.terminalPri(pmu, 1) 222 | 223 | # substation_rtu 224 | substation_rtu_name.append('substation_rtu_' + street_list[i][0]) 225 | substation_rtu = iot(substation_rtu_name[i]) 226 | substation_rtu.subnet.append('zb') 227 | v_substation_rtu = vulNode(find_cve_values(substation_rtu_name[i])[0][1]) 228 | v_substation_rtu.createVuls(substation_rtu, float(find_cve_values(substation_rtu_name[i])[0][4]) / 10, 229 | 1) 230 | v_substation_rtu.thresholdPri(substation_rtu, 1) 231 | v_substation_rtu.terminalPri(substation_rtu, 1) 232 | 233 | # plc 234 | plc_name.append('plc_' + street_list[i][0]) 235 | plc = iot(plc_name[i]) 236 | plc.subnet.append('zb') 237 | v_plc = vulNode(find_cve_values(plc_name[i])[0][1]) 238 | v_plc.createVuls(plc, float(find_cve_values(plc_name[i])[0][4]) / 10, 239 | 1) 240 | v_plc.thresholdPri(plc, 1) 241 | v_plc.terminalPri(plc, 1) 242 | 243 | #TV 244 | #tv_A_0 245 | tv_name.append('smart_tv_' + street_list[i][0]+'_'+ str(j)) 246 | tv = iot(tv_name[-1]) # tv_name[-1] is the last value of tv_name array 247 | print(tv_name[-1]) 248 | # tv = iot('tv_A_0') 249 | tv.subnet.append('wifi') # bluetooth 250 | v_tv = vulNode(find_cve_values(tv_name[-1])[0][1]) # vulNode(object) 251 | v_tv.createVuls(tv, float(find_cve_values(tv_name[-1])[0][4])/10, 1) # CVSS base score: 10.0 # 252 | v_tv.thresholdPri(tv, 1) 253 | v_tv.terminalPri(tv, 1) 254 | 255 | 256 | #Camera 257 | cam_name.append('ip_camera_' + street_list[i][0] + '_' + str(j)) 258 | cam = iot(cam_name[-1]) 259 | print(cam_name[-1]) 260 | cam.subnet.append('wifi') 261 | v_cam = vulNode(find_cve_values(cam_name[-1])[0][1]) 262 | v_cam.createVuls(cam, float(find_cve_values(cam_name[-1])[0][4])/10, 1) # CVSS base score: 10.0 263 | v_cam.thresholdPri(cam, 1) 264 | v_cam.terminalPri(cam, 1) 265 | 266 | 267 | #Thermostat 268 | thermostat_name.append('smart_thermostat_' + street_list[i][0] + '_' + str(j)) 269 | thermostat = iot(thermostat_name[-1]) 270 | print(thermostat_name[-1]) 271 | thermostat.subnet.append('wifi') 272 | v_thermostat = vulNode(find_cve_values(thermostat_name[-1])[0][1]) 273 | v_thermostat.createVuls(thermostat, float(find_cve_values(thermostat_name[-1])[0][4])/10, 1) # CVSS base score: 10.0 274 | v_thermostat.thresholdPri(thermostat, 1) 275 | v_thermostat.terminalPri(thermostat, 1) 276 | 277 | #Cleaner 278 | cleaner_name.append('cleaner_' + street_list[i][0] + '_' + str(j)) 279 | cleaner = iot(cleaner_name[-1]) 280 | print(cleaner_name[-1]) 281 | cleaner.subnet.append('wifi') 282 | v_cleaner = vulNode(find_cve_values(cleaner_name[-1])[0][1]) 283 | v_cleaner.createVuls(cleaner, float(find_cve_values(cleaner_name[-1])[0][4])/10, 1) # CVSS base score: 10.0 284 | v_cleaner.thresholdPri(cleaner, 1) 285 | v_cleaner.terminalPri(cleaner, 1) 286 | 287 | #Light 288 | light_name.append('light_' + street_list[i][0] + '_' + str(j)) 289 | light = iot(light_name[-1]) 290 | print(light_name[-1]) 291 | light.subnet.append('wifi') 292 | v_light = vulNode(find_cve_values(light_name[-1])[0][1]) 293 | v_light.createVuls(light, float(find_cve_values(light_name[-1])[0][4])/10, 1) # CVSS base score: 10.0 294 | v_light.thresholdPri(light, 1) 295 | v_light.terminalPri(light, 1) 296 | 297 | #Gateway 298 | gateway_name.append('gateway_' + street_list[i][0] + '_' + str(j)) 299 | gateway = iot(gateway_name[-1]) 300 | print(gateway_name[-1]) 301 | gateway.subnet.append('wifi') 302 | v_gateway = vulNode(find_cve_values(gateway_name[-1])[0][1]) 303 | v_gateway.createVuls(gateway, float(find_cve_values(gateway_name[-1])[0][4])/10, 1) 304 | v_gateway.thresholdPri(gateway, 1) # Start Point #Must Remove if we have 2 vuls 305 | v_gateway.terminalPri(gateway, 1) 306 | 307 | #Meter 308 | meter_name.append('smart_meter_' + street_list[i][0] + '_' + str(j)) 309 | meter = iot(meter_name[-1]) 310 | print(meter_name[-1]) 311 | meter.subnet.append('wifi') 312 | v_meter = vulNode(find_cve_values(meter_name[-1])[0][1]) 313 | v_meter.createVuls(meter, float(find_cve_values(meter_name[-1])[0][4])/10, 1) # 1.0 initial value with no meaning 314 | v_meter.thresholdPri(meter, 1) 315 | v_meter.terminalPri(meter, 1) 316 | 317 | 318 | net.connectOneWay(meter, concentrator) 319 | net.connectOneWay(local_terminal, substation_rtu) 320 | net.connectOneWay(pmu, substation_rtu) 321 | net.connectOneWay(substation_rtu, fep) 322 | net.connectOneWay(concentrator, fep) 323 | net.connectOneWay(plc, fep) 324 | #net.connectOneWay(concentrator, central_concentrator) 325 | net.connectOneWay(fep, application_server) 326 | net.connectOneWay(fep, active_directory_server) 327 | net.connectOneWay(fep, historian_server) 328 | net.connectOneWay(fep, communication_server) 329 | 330 | net.connectOneWay(work_station, application_server) 331 | net.connectOneWay(work_station, active_directory_server) 332 | net.connectOneWay(work_station, historian_server) 333 | net.connectOneWay(work_station, communication_server) 334 | 335 | net.connectOneWay(hmi, application_server) 336 | net.connectOneWay(hmi, active_directory_server) 337 | net.connectOneWay(hmi, historian_server) 338 | net.connectOneWay(hmi, communication_server) 339 | 340 | net.connectOneWay(application_server, iccp_server) 341 | net.connectOneWay(active_directory_server, iccp_server) 342 | net.connectOneWay(historian_server, iccp_server) 343 | net.connectOneWay(communication_server, iccp_server) 344 | 345 | net.connectOneWay(iccp_server, ems_drp_server) 346 | 347 | 348 | 349 | 350 | 351 | 352 | if find_cve_values(tv_name[-1])[1]: 353 | net.nodes.append(tv) 354 | if find_cve_values(cam_name[-1])[1]: 355 | net.nodes.append(cam) 356 | if find_cve_values(thermostat_name[-1])[1]: 357 | net.nodes.append(thermostat) 358 | if find_cve_values(cleaner_name[-1])[1]: 359 | net.nodes.append(cleaner) 360 | if find_cve_values(light_name[-1])[1]: 361 | net.nodes.append(light) 362 | if find_cve_values(gateway_name[-1])[1]: 363 | net.connectOneWay(tv, gateway) 364 | net.connectOneWay(cam, gateway) 365 | net.connectOneWay(thermostat, gateway) 366 | net.connectOneWay(cleaner, gateway) 367 | net.connectOneWay(light, gateway) 368 | net.connectOneWay(gateway, meter) 369 | 370 | net.nodes.append(gateway) 371 | else: 372 | net.connectOneWay(tv, meter) 373 | net.connectOneWay(cam, meter) 374 | net.connectOneWay(thermostat, meter) 375 | net.connectOneWay(cleaner, meter) 376 | net.connectOneWay(light, meter) 377 | 378 | if find_cve_values(meter_name[-1])[1]: 379 | net.nodes.append(meter) 380 | if j == 0: 381 | if find_cve_values(concentrator_name[i])[1]: 382 | net.nodes.append(concentrator) 383 | if find_cve_values(local_terminal_name[i])[1]: 384 | net.nodes.append(local_terminal) 385 | if find_cve_values(substation_rtu_name[i])[1]: 386 | net.nodes.append(substation_rtu) 387 | if find_cve_values(pmu_name[i])[1]: 388 | net.nodes.append(pmu) 389 | if find_cve_values(plc_name[i])[1]: 390 | net.nodes.append(plc) 391 | 392 | 393 | 394 | if find_cve_values("central_concentrator")[1]: 395 | net.nodes.append(central_concentrator) 396 | if find_cve_values("fep")[1]: 397 | net.nodes.append(fep) 398 | if find_cve_values("application_server")[1]: 399 | net.nodes.append(application_server) 400 | if find_cve_values("active_directory_server")[1]: 401 | net.nodes.append(active_directory_server) 402 | if find_cve_values("historian_server")[1]: 403 | net.nodes.append(historian_server) 404 | if find_cve_values("hmi")[1]: 405 | net.nodes.append(hmi) 406 | if find_cve_values("work_station")[1]: 407 | net.nodes.append(work_station) 408 | if find_cve_values("iccp_server")[1]: 409 | net.nodes.append(iccp_server) 410 | if find_cve_values("ems_drp_server")[1]: 411 | net.nodes.append(ems_drp_server) 412 | 413 | 414 | # Set the attacker as the start 415 | A = computer('attacker') 416 | A.setStart() # ag_S- Staring point 417 | # Link the attacker with TV and camera 418 | entry_list = [] 419 | name_list = {'smart_tv':'tv_name', 420 | 'cleaner': 'cleaner_name', 421 | 'light': 'light_name', 422 | 'ip_camera': 'cam_name', 423 | 'smart_thermostat': 'thermostat_name', 424 | 'smart_meter': 'meter_name', 425 | 'concentrator': 'concentrator_name', 426 | 'gateway': 'gateway_name', 427 | 'central_concentrator': 'central_concentrator_name', 428 | "fep": "fep_name", 429 | "application_server": "application_server_name", 430 | "active_directory_server": "active_directory_server_name", 431 | "historian_server": "historian_server_name", 432 | "communication_server": "communication_server_name", 433 | "hmi": "hmi_name", 434 | "work_station": "work_station_name", 435 | "iccp_server": "iccp_server_name", 436 | "ems_drp_server": "ems_drp_server_name", 437 | "local_terminal": "local_terminal_name", 438 | "substation_rtu": "substation_rtu_name", 439 | "pmu": "pmu_name", 440 | "plc": "plc_name" 441 | } 442 | for i in range(len(entry)-1): 443 | if(entry[i][-1].isnumeric()): 444 | entry_list.append(entry[i]) 445 | 446 | else: 447 | for key, value in name_list.items(): 448 | if key == entry[i]: 449 | entry_list.extend(globals()[value]) 450 | 451 | 452 | 453 | 454 | 455 | target_list = [] 456 | if "smart_tv" == target[0]: 457 | target_list.extend(tv_name) 458 | elif "cleaner" == target[0]: 459 | target_list.extend(cleaner_name) 460 | elif "light" == target[0]: 461 | target_list.extend(light_name) 462 | elif "ip_camera" == target[0]: 463 | target_list.extend(cam_name) 464 | elif "smart_meter" == target[0]: 465 | target_list.extend(meter_name) 466 | elif "gateway" == target[0]: 467 | target_list.extend(gateway_name) 468 | elif "concentrator" == target[0]: 469 | target_list.extend(concentrator_name) 470 | elif "central_concentrator" == target[0]: 471 | target_list.extend(['central_concentrator']) 472 | elif "local_terminal" == target[0]: 473 | target_list.extend(['local_terminal']) 474 | elif "substation_rtu" == target[0]: 475 | target_list.extend(['substation_rtu']) 476 | elif "pmu" == target[0]: 477 | target_list.extend(['pmu']) 478 | elif "plc" == target[0]: 479 | target_list.extend(['plc']) 480 | elif "fep" == target[0]: 481 | target_list.extend(['fep']) 482 | elif "application_server" == target[0]: 483 | target_list.extend(['application_server']) 484 | elif "active_directory_server" == target[0]: 485 | target_list.extend(['active_directory_server']) 486 | elif "historian_server" == target[0]: 487 | target_list.extend(['historian_server']) 488 | elif "communication_server" == target[0]: 489 | target_list.extend(['communication_server']) 490 | elif "hmi" == target[0]: 491 | target_list.extend(['hmi']) 492 | elif "work_station" == target[0]: 493 | target_list.extend(['work_station']) 494 | elif "iccp_server" == target[0]: 495 | target_list.extend(['iccp_server']) 496 | elif "ems_drp_server" == target[0]: 497 | target_list.extend(['ems_drp_server']) 498 | elif "smart_thermostat" == target[0]: 499 | target_list.extend(thermostat_name) 500 | else: 501 | target_list.append(target[0]) 502 | #if entry_list in 503 | for node in net.nodes: 504 | if node.name in entry_list: 505 | A.con.append(node) 506 | elif node.name in target_list: 507 | node.setEnd() # central_concentrator ag_E- End point 508 | 509 | net.nodes.append(A) 510 | net.constructSE() 511 | net.printNetWithVul() # Network Model 512 | 513 | return net 514 | 515 | 516 | 517 | 518 | 519 | def assignNewMetricValue(net, metric): 520 | for node in net.nodes: 521 | if node.name in tv_name + cam_name + thermostat_name \ 522 | + cleaner_name + light_name \ 523 | + concentrator_name + ['central_concentrator']\ 524 | + meter_name + gateway_name + local_terminal_name + \ 525 | substation_rtu_name + pmu_name + plc_name + fep_name + \ 526 | application_server_name + active_directory_server_name + \ 527 | historian_server_name + communication_server_name + hmi_name + \ 528 | work_station_name + iccp_server_name + ems_drp_server_name: #tv_A_0 tv_A_1 #tv_B_0 529 | for vul in node.vul.nodes: 530 | if metric == "risk": 531 | vul.assignRisk(float(find_cve_values(node.name)[0][4])/10, float(find_cve_values(node.name)[0][3])) 532 | # asp, attack impact 533 | elif metric == "asp": 534 | vul.val = float(find_cve_values(node.name)[0][4])/10 535 | # attack success probability = exploitability/10 536 | elif metric == "impact": 537 | vul.val = float(find_cve_values(node.name)[0][3]) 538 | # impact from the CVE version 2 539 | 540 | 541 | 542 | 543 | net.printNetWithVul() 544 | return None 545 | 546 | 547 | 548 | 549 | if __name__ == '__main__': 550 | # 551 | 552 | street_list = sys.argv[1].split(':')[:-1] # Street List 553 | house_list = sys.argv[2].split(':')[:-1] # Number of houses in each street 554 | 555 | entry_list = sys.argv[3].split(':') # Entry 556 | target_list = sys.argv[4].split(':') # Target 557 | strategy_name = sys.argv[5].replace(":","_") #name 558 | current_time = sys.argv[6] #current_time 559 | num_devices = float(sys.argv[7]) #Total Number of devices 560 | global source_file_name 561 | 562 | source_file_name = "Results/"+strategy_name+"_source.csv" 563 | 564 | copyfile("Results/source.csv", source_file_name) 565 | file_prefix = "Results/"+strategy_name+"_" 566 | 567 | 568 | net = createWiFi(street_list, house_list, entry_list, target_list) 569 | 570 | # Create HARM and compute attack paths 571 | 572 | #h = harm() 573 | #h.constructHarm(net, "attackgraph", 1, "attacktree", 1, 3) 574 | #constructHarm(self, net, up, valueUp, lo, valueLow, pri) 575 | """ 576 | Construct HARM. 577 | 578 | :param net: network 579 | :param up: upper layer type 580 | :param vu: assign a default value to val parameter for node, no real meaning when initializing, changed and used in security analysis 581 | :param lo: lower layer type 582 | :param vl: assign a default value to val parameter for vulnerability, no real meaning when initializing, changed and used in security analysis 583 | :param pri: assign a privilege value in construction of lower layer vulnerability connections 584 | :returns: HARM: contains two layers, when using AGAT, \ 585 | the upper layer is attack graph listing nodes and attack paths \ 586 | each node has a lower layer which stored in child parameter, containing vulnerability tree 587 | """ 588 | 589 | # h.model.printPath() 590 | # h.model.printAG() 591 | # h.model.printNetWithVul() 592 | # h.model.treePrint() 593 | 594 | assignNewMetricValue(net, "asp") 595 | attackProAnalysis_array = attackProAnalysis(net, 1) # 1 because we just have 1 vul for everything 596 | # change 1 => 3 which is the pri after the exploitation 597 | attack_success_probability = attackProAnalysis_array[0] 598 | 599 | #Compute Attack Risk 600 | assignNewMetricValue(net, "risk") 601 | attack_risk = riskAnalysis(net, 1) # 1 because we just have 1 vul for everything 602 | 603 | 604 | assignNewMetricValue(net, "impact") #risk #asp #impact 605 | analysis_array = attackImpactAnalysis(net, 1, file_prefix) # 1 because we just have 1 vul for everything 606 | 607 | attack_cost = attackCostAnalysis(net, 1) 608 | # change 1 => 3 which is the pri after the exploitation 609 | 610 | # read header automatically 611 | with open('Results/Results.csv', "r") as f: 612 | reader = csv.reader(f) 613 | for header in reader: 614 | break 615 | 616 | with open(r'Results/Results.csv', 'a+', newline='') as file: 617 | writer = csv.writer(file) 618 | print(current_time) 619 | print(str(strategy_name).replace("_"," ")) 620 | print(str(street_list)) 621 | print(str(house_list)) 622 | print(attack_success_probability) 623 | print(attack_cost) 624 | print(analysis_array[0]) 625 | print(analysis_array[1]) 626 | writer.writerow([current_time, 627 | str(strategy_name).replace("_"," "), 628 | str(street_list)+"\n"+str(house_list), 629 | attack_success_probability, 630 | attack_cost, 631 | analysis_array[0], 632 | float(attack_success_probability*analysis_array[0]), 633 | num_devices, 634 | float(attack_cost/num_devices), 635 | float(analysis_array[0] / num_devices), 636 | analysis_array[1], 637 | attackProAnalysis_array[1], 638 | attackProAnalysis_array[2], 639 | attackProAnalysis_array[3], 640 | attackProAnalysis_array[4], 641 | attackProAnalysis_array[5], 642 | ] 643 | 644 | ) 645 | file.close() 646 | 647 | 648 | 649 | -------------------------------------------------------------------------------- /GridAttackAnalyzer.py: -------------------------------------------------------------------------------- 1 | from tkinter import * 2 | from tkinter.ttk import * 3 | from tkinter import ttk 4 | import tkinter as tk 5 | 6 | import json 7 | 8 | from tkinter import messagebox 9 | 10 | import datetime 11 | 12 | from tkinter.filedialog import askopenfile 13 | from graphviz import Source #pip install graphviz # to install graphviz 14 | from collections import OrderedDict 15 | from tkinter import StringVar 16 | import csv 17 | import os 18 | import os.path 19 | from os import path 20 | from tkinter import simpledialog 21 | 22 | 23 | from datetime import datetime 24 | import random 25 | from tempfile import NamedTemporaryFile 26 | import shutil 27 | 28 | 29 | filter = "JSON file (*.json)|*.json|All Files (*.*)|*.*||" 30 | with open('database.json', 'r') as f: 31 | distros_dict = json.load(f, object_pairs_hook=OrderedDict) 32 | 33 | path = "Scripts/" 34 | 35 | #Sets up a frame 36 | 37 | class MyApplication(Frame): 38 | 39 | #When a class is initialized, this is called as per any class 40 | def __init__(self, root): 41 | 42 | #Similar to saying MyFrame = Frame(master) 43 | Frame.__init__(self, root) 44 | 45 | self.text_file = tk.StringVar() 46 | self.text_file.set("") 47 | #Puts the frame on a grid. If you had two frames on one window, you would do the row, column keywords (or not...) 48 | 49 | self.grid() 50 | 51 | 52 | 53 | 54 | #Function to put the widgets on the frame. Can have any name! 55 | self.create_widgets(root) 56 | 57 | def updtcblist(self, list1, list2): 58 | 59 | combo_target.config(values=list1) 60 | combo_target.current(1) 61 | combo_to_edit_device.config(values=list2) 62 | combo_to_edit_device.current(1) 63 | 64 | for i in list1: 65 | lstbox_target.insert(END,str(i)) 66 | 67 | def generate_tree(self): 68 | n_street_list = '' 69 | n_house_list = '' 70 | n_devices_list = '' 71 | n_cve_list = '' 72 | n_entry_list = '' 73 | current_time = datetime.now().strftime("%H:%M:%S") 74 | n_strategy_name = (str(combo_smartgrid_model.get()) + " " + current_time) 75 | 76 | strategy_name = simpledialog.askstring("Just one more step", "Do you want to set a case study name?\n" + 77 | "Otherwise '" + 78 | n_strategy_name + 79 | "' will be used", 80 | parent=window) 81 | if str(strategy_name).strip(): 82 | n_strategy_name = strategy_name 83 | 84 | 85 | 86 | 87 | entry = [lstbox_target.get(idx) for idx in lstbox_target.curselection()] 88 | for i in range(len(street_list)): 89 | n_street_list = n_street_list + str(street_list[i]) + ":" 90 | for i in range(len(house_list)): 91 | n_house_list = n_house_list + str(house_list[i]) + ":" 92 | for i in range(len(devices_list)): 93 | n_devices_list = n_devices_list + str(devices_list[i]) + ":" 94 | for i in range(len(cve_list)): 95 | n_cve_list = n_cve_list + str(cve_list[i]) + ":" 96 | for i in range(len(entry)): 97 | n_entry_list = n_entry_list + str(entry[i]) + ":" 98 | 99 | 100 | 101 | #Count Number of Devices 102 | num_devices = 1 103 | file = open("Results/source.csv") 104 | reader = csv.reader(file) 105 | num_devices = len(list(reader)) 106 | 107 | self.text_file.set("File was generated at" + str(os.getcwd()) + "/" + "Results/"+(str(n_strategy_name).replace(" ","_")).replace(":","_")+"_source.csv") 108 | 109 | os.system('python3 '+ path +'NetGen.py '+ 110 | n_street_list + 111 | ' ' + 112 | n_house_list + 113 | ' ' + 114 | n_entry_list + 115 | ' ' + 116 | combo_target.get() + 117 | ' ' + 118 | str(n_strategy_name).replace(" ","_") + 119 | ' ' + 120 | current_time + 121 | ' ' + 122 | str(num_devices) 123 | ) 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | def open_csv_file(self): 134 | #ubuntu only 135 | 136 | if(self.text_file.get()): 137 | os.system('libreoffice -o Results/'+str(self.text_file.get().split("/")[-1])) 138 | else: 139 | os.system('libreoffice -o Results/source.csv') 140 | 141 | def open_security_csv_file(self): 142 | #ubuntu only 143 | os.system('libreoffice -o Results/Results.csv') 144 | def open_graph_source_file(self): 145 | os.system('libreoffice -o Results/' +str(self.text_file.get().split("/")[-1]).replace('source.csv','attackImpactAnalysis.csv')) 146 | def open_attack_graph(self): 147 | os.system('firefox Results/' + str(self.text_file.get().split("/")[-1]).replace('source.csv','attackImpactAnalysis.html')) 148 | 149 | 150 | def find_cve_values(self, cve): 151 | base_score = 0 152 | impact_score = 0 153 | exploitability_score = 0 154 | description = '' 155 | for key in range(len(distros_dict['object'][2]['CVE_list'])): 156 | if distros_dict['object'][2]['CVE_list'][key]["CVE"] in cve: 157 | base_score = distros_dict['object'][2]['CVE_list'][key]["CVSS_Base_Score_2.0"] 158 | impact_score = distros_dict['object'][2]['CVE_list'][key]["Impact_Subscore"] 159 | exploitability_score = distros_dict['object'][2]['CVE_list'][key]["Exploitability_Subscore"] 160 | description = distros_dict['object'][2]['CVE_list'][key]["description"] 161 | return base_score, impact_score, exploitability_score, description 162 | 163 | def OnButtonClick(self, key): 164 | cve = globals()["CVE_list" + str(key)].get() 165 | 166 | messagebox.showinfo("CVE Information", 167 | cve+"\n- Description: " + str(self.find_cve_values(cve)[3]) + 168 | "\n\n- CVSS Base Score v2.0: " + str(self.find_cve_values(cve)[0]) + 169 | "\n\n- Impact Subscore: " + str(self.find_cve_values(cve)[1]) + 170 | "\n\n- Exploitability Subscore: " + str(self.find_cve_values(cve)[2]) 171 | ) 172 | 173 | def getRandomCVEVals(self, device_name): 174 | print(devices_and_cve_list.get(device_name)) 175 | return str(random.choice(devices_and_cve_list.get(device_name))) 176 | 177 | 178 | def generate_file(self): 179 | global street_list 180 | global house_list 181 | global devices_list 182 | global cve_list 183 | street_list = [] 184 | house_list = [] 185 | devices_list =[] 186 | cve_list =[] 187 | street_list.clear() 188 | house_list.clear() 189 | devices_list.clear() 190 | cve_list.clear() 191 | entry_target = [] 192 | entry_target.clear() 193 | global n_strategy_name 194 | 195 | 196 | for key_1 in range(len(distros_dict['object'][0]['model_list'])): 197 | if (distros_dict['object'][0]['model_list'][key_1]['list_name'] in combo_smartgrid_model.get() ): 198 | dict = distros_dict['object'][0]['model_list'][key_1]["streets_and_houses"] 199 | street_list = [k for k,v in dict[0].items()] 200 | house_list = [v for k,v in dict[0].items()] 201 | devices_list = [] 202 | cve_list = [] 203 | for key in range(number_device_list + 1): 204 | try: 205 | if globals()["devices_list" + str(key)].state()[0] in ['selected']: 206 | # print(globals()["devices_list" + str(key)].cget("text")) 207 | devices_list.append(globals()["devices_list" + str(key)].cget("text").lower().replace(" ", "_")) 208 | cve_list.append(globals()["CVE_list" + str(key)].get()) 209 | except Exception as e: 210 | print("Something was wrong!") 211 | entry_target.extend(devices_list) 212 | 213 | global source_file_name 214 | source_file_name = 'Results/source.csv' 215 | 216 | if (combo_cve_vals_selection.get() == "Manually"): 217 | with open(source_file_name, 'w', newline='') as file: 218 | writer = csv.writer(file) 219 | writer.writerow(["Device", "CVE", "CVSS Base Score 2.0", "Impact Subcore", "Exploitability Subscore"]) 220 | 221 | #SCADA Devices 222 | try: 223 | index_central_concentrator = devices_list.index("central_concentrator") 224 | if index_central_concentrator >= 0: 225 | writer.writerow(["central_concentrator", cve_list[index_central_concentrator], 226 | self.find_cve_values(cve_list[index_central_concentrator])[0], 227 | self.find_cve_values(cve_list[index_central_concentrator])[1], 228 | self.find_cve_values(cve_list[index_central_concentrator])[2]]) 229 | except ValueError: 230 | print("List does not contain value") 231 | try: 232 | index_fep = devices_list.index("fep") 233 | if index_fep >= 0: 234 | writer.writerow(["fep", cve_list[index_fep], 235 | self.find_cve_values(cve_list[index_fep])[0], 236 | self.find_cve_values(cve_list[index_fep])[1], 237 | self.find_cve_values(cve_list[index_fep])[2]]) 238 | except ValueError: 239 | print("No fep") 240 | try: 241 | index_application_server = devices_list.index("application_server") 242 | if index_application_server >= 0: 243 | writer.writerow(["application_server", cve_list[index_application_server], 244 | self.find_cve_values(cve_list[index_application_server])[0], 245 | self.find_cve_values(cve_list[index_application_server])[1], 246 | self.find_cve_values(cve_list[index_application_server])[2]]) 247 | except ValueError: 248 | print("No application server") 249 | try: 250 | index_active_directory_server = devices_list.index("active_directory_server") 251 | if index_active_directory_server >= 0: 252 | writer.writerow(["active_directory_server", cve_list[index_active_directory_server], 253 | self.find_cve_values(cve_list[index_active_directory_server])[0], 254 | self.find_cve_values(cve_list[index_active_directory_server])[1], 255 | self.find_cve_values(cve_list[index_active_directory_server])[2]]) 256 | except ValueError: 257 | print("No active directory server") 258 | try: 259 | index_historian_server = devices_list.index("historian_server") 260 | if index_historian_server >= 0: 261 | writer.writerow(["historian_server", cve_list[index_historian_server], 262 | self.find_cve_values(cve_list[index_historian_server])[0], 263 | self.find_cve_values(cve_list[index_historian_server])[1], 264 | self.find_cve_values(cve_list[index_historian_server])[2]]) 265 | except ValueError: 266 | print("No historian server") 267 | try: 268 | index_communication_server = devices_list.index("communication_server") 269 | if index_communication_server >= 0: 270 | writer.writerow(["communication_server", cve_list[index_communication_server], 271 | self.find_cve_values(cve_list[index_communication_server])[0], 272 | self.find_cve_values(cve_list[index_communication_server])[1], 273 | self.find_cve_values(cve_list[index_communication_server])[2]]) 274 | except ValueError: 275 | print("No communication server") 276 | try: 277 | index_hmi = devices_list.index("hmi") 278 | if index_hmi >= 0: 279 | writer.writerow(["hmi", cve_list[index_hmi], 280 | self.find_cve_values(cve_list[index_hmi])[0], 281 | self.find_cve_values(cve_list[index_hmi])[1], 282 | self.find_cve_values(cve_list[index_hmi])[2]]) 283 | except ValueError: 284 | print("No hmi") 285 | try: 286 | index_work_station = devices_list.index("work_station") 287 | if index_work_station >= 0: 288 | writer.writerow(["work_station", cve_list[index_work_station], 289 | self.find_cve_values(cve_list[index_work_station])[0], 290 | self.find_cve_values(cve_list[index_work_station])[1], 291 | self.find_cve_values(cve_list[index_work_station])[2]]) 292 | except ValueError: 293 | print("No work station") 294 | try: 295 | index_iccp_server = devices_list.index("iccp_server") 296 | if index_iccp_server >= 0: 297 | writer.writerow(["iccp_server", cve_list[index_iccp_server], 298 | self.find_cve_values(cve_list[index_iccp_server])[0], 299 | self.find_cve_values(cve_list[index_iccp_server])[1], 300 | self.find_cve_values(cve_list[index_iccp_server])[2]]) 301 | except ValueError: 302 | print("iccp server") 303 | try: 304 | index_ems_drp_server = devices_list.index("ems_drp_server") 305 | if index_ems_drp_server >= 0: 306 | writer.writerow(["ems_drp_server", cve_list[index_ems_drp_server], 307 | self.find_cve_values(cve_list[index_ems_drp_server])[0], 308 | self.find_cve_values(cve_list[index_ems_drp_server])[1], 309 | self.find_cve_values(cve_list[index_ems_drp_server])[2]]) 310 | except ValueError: 311 | print("No ems_drp_server") 312 | #End SCADA Devices 313 | 314 | 315 | for i in range(len(street_list)): 316 | for j in range(len(house_list)): 317 | if j == 0: 318 | try: 319 | index_local_terminal = devices_list.index("local_terminal") 320 | if index_local_terminal >= 0: 321 | writer.writerow(["local_terminal_" + street_list[i], cve_list[index_local_terminal], 322 | self.find_cve_values(cve_list[index_local_terminal])[0], 323 | self.find_cve_values(cve_list[index_local_terminal])[1], 324 | self.find_cve_values(cve_list[index_local_terminal])[2]]) 325 | entry_target.append("local_terminal_" + street_list[i]) 326 | except Exception as e: 327 | print("There is no local_terminal!") 328 | try: 329 | index_pmu = devices_list.index("pmu") 330 | if index_pmu >= 0: 331 | writer.writerow(["pmu_" + street_list[i], cve_list[index_pmu], 332 | self.find_cve_values(cve_list[index_pmu])[0], 333 | self.find_cve_values(cve_list[index_pmu])[1], 334 | self.find_cve_values(cve_list[index_pmu])[2]]) 335 | entry_target.append("pmu_" + street_list[i]) 336 | except Exception as e: 337 | print("There is no pmu!") 338 | try: 339 | index_substation_rtu = devices_list.index("substation_rtu") 340 | if index_substation_rtu >= 0: 341 | writer.writerow(["substation_rtu_"+street_list[i], cve_list[index_substation_rtu], 342 | self.find_cve_values(cve_list[index_substation_rtu])[0], 343 | self.find_cve_values(cve_list[index_substation_rtu])[1], 344 | self.find_cve_values(cve_list[index_substation_rtu])[2]]) 345 | entry_target.append("substation_rtu_"+street_list[i]) 346 | except Exception as e: 347 | print("There is no rtu!") 348 | try: 349 | index_plc = devices_list.index("plc") 350 | if index_plc >= 0: 351 | writer.writerow(["plc_" + street_list[i], cve_list[index_plc], 352 | self.find_cve_values(cve_list[index_plc])[0], 353 | self.find_cve_values(cve_list[index_plc])[1], 354 | self.find_cve_values(cve_list[index_plc])[2]]) 355 | entry_target.append("plc_" + street_list[i]) 356 | except Exception as e: 357 | print("There is no plc!") 358 | try: 359 | index_concentrator = devices_list.index("concentrator") 360 | if index_concentrator >= 0: 361 | writer.writerow(["concentrator_"+street_list[i], cve_list[index_concentrator], 362 | self.find_cve_values(cve_list[index_concentrator])[0], 363 | self.find_cve_values(cve_list[index_concentrator])[1], 364 | self.find_cve_values(cve_list[index_concentrator])[2]]) 365 | entry_target.append("concentrator_"+street_list[i]) 366 | except Exception as e: 367 | print("There is no Street Device!") 368 | for k in range(len(devices_list)): 369 | if devices_list[k] not in ["concentrator", "central_concentrator", 370 | "local_terminal", "pmu", "substation_rtu", 371 | "plc", 372 | "fep", "application_server", 373 | "active_directory_server", "historian_server", 374 | "communication_server", "hmi", 375 | "work_station", "iccp_server", 376 | "ems_drp_server" 377 | ] : 378 | writer.writerow([devices_list[k]+"_"+street_list[i]+"_"+str(j), cve_list[k], self.find_cve_values(cve_list[k])[0], self.find_cve_values(cve_list[k])[1], self.find_cve_values(cve_list[k])[2]]) 379 | entry_target.append(devices_list[k]+"_"+street_list[i]+"_"+str(j)) 380 | else: 381 | with open(source_file_name, 'w', newline='') as file: 382 | writer = csv.writer(file) 383 | writer.writerow(["Device", "CVE", "CVSS Base Score 2.0", "Impact Subcore", "Exploitability Subscore"]) 384 | try: 385 | index_central_concentrator = devices_list.index("central_concentrator") 386 | if index_central_concentrator >= 0: 387 | term = self.getRandomCVEVals("CENTRAL CONCENTRATOR") 388 | writer.writerow(["central_concentrator", term, 389 | self.find_cve_values(term)[0], 390 | self.find_cve_values(term)[1], 391 | self.find_cve_values(term)[2]]) 392 | except Exception as e: 393 | print("There is no Central Concentrator!") 394 | 395 | try: 396 | index_fep = devices_list.index("fep") 397 | if index_fep >= 0: 398 | term = self.getRandomCVEVals("fep".upper().replace("_", " ")) 399 | writer.writerow(["fep", term, 400 | self.find_cve_values(term)[0], 401 | self.find_cve_values(term)[1], 402 | self.find_cve_values(term)[2]]) 403 | except Exception as e: 404 | print("There is no FEP") 405 | 406 | try: 407 | index_application_server = devices_list.index("application_server") 408 | if index_application_server >= 0: 409 | term = self.getRandomCVEVals("application_server".upper().replace("_", " ")) 410 | writer.writerow(["application_server", term, 411 | self.find_cve_values(term)[0], 412 | self.find_cve_values(term)[1], 413 | self.find_cve_values(term)[2]]) 414 | except Exception as e: 415 | print("There is no APPLICATION SERVER") 416 | 417 | try: 418 | index_active_directory_server = devices_list.index("active_directory_server") 419 | if index_active_directory_server >= 0: 420 | term = self.getRandomCVEVals("active_directory_server".upper().replace("_", " ")) 421 | writer.writerow(["active_directory_server", term, 422 | self.find_cve_values(term)[0], 423 | self.find_cve_values(term)[1], 424 | self.find_cve_values(term)[2]]) 425 | except Exception as e: 426 | print("There is no active_directory_server") 427 | 428 | try: 429 | index_historian_server = devices_list.index("historian_server") 430 | if index_historian_server >= 0: 431 | term = self.getRandomCVEVals("historian_server".upper().replace("_", " ")) 432 | writer.writerow(["historian_server", term, 433 | self.find_cve_values(term)[0], 434 | self.find_cve_values(term)[1], 435 | self.find_cve_values(term)[2]]) 436 | except Exception as e: 437 | print("There is no historian_server") 438 | 439 | try: 440 | index_communication_server = devices_list.index("communication_server") 441 | if index_communication_server >= 0: 442 | term = self.getRandomCVEVals("communication_server".upper().replace("_", " ")) 443 | writer.writerow(["communication_server", term, 444 | self.find_cve_values(term)[0], 445 | self.find_cve_values(term)[1], 446 | self.find_cve_values(term)[2]]) 447 | except Exception as e: 448 | print("There is no communication_server") 449 | 450 | try: 451 | index_hmi = devices_list.index("hmi") 452 | if index_hmi >= 0: 453 | term = self.getRandomCVEVals("hmi".upper().replace("_", " ")) 454 | writer.writerow(["hmi", term, 455 | self.find_cve_values(term)[0], 456 | self.find_cve_values(term)[1], 457 | self.find_cve_values(term)[2]]) 458 | except Exception as e: 459 | print("There is no hmi") 460 | 461 | try: 462 | index_work_station = devices_list.index("work_station") 463 | if index_work_station >= 0: 464 | term = self.getRandomCVEVals("work_station".upper().replace("_", " ")) 465 | writer.writerow(["work_station", term, 466 | self.find_cve_values(term)[0], 467 | self.find_cve_values(term)[1], 468 | self.find_cve_values(term)[2]]) 469 | except Exception as e: 470 | print("There is no work_station") 471 | try: 472 | index_iccp_server = devices_list.index("iccp_server") 473 | if index_iccp_server >= 0: 474 | term = self.getRandomCVEVals("iccp_server".upper().replace("_", " ")) 475 | writer.writerow(["iccp_server", term, 476 | self.find_cve_values(term)[0], 477 | self.find_cve_values(term)[1], 478 | self.find_cve_values(term)[2]]) 479 | except Exception as e: 480 | print("There is no iccp_server") 481 | try: 482 | index_ems_drp_server = devices_list.index("ems_drp_server") 483 | if index_ems_drp_server >= 0: 484 | term = self.getRandomCVEVals("ems_drp_server".upper().replace("_", " ")) 485 | writer.writerow(["ems_drp_server", term, 486 | self.find_cve_values(term)[0], 487 | self.find_cve_values(term)[1], 488 | self.find_cve_values(term)[2]]) 489 | except Exception as e: 490 | print("There is no ems_drp_server") 491 | 492 | for i in range(len(street_list)): 493 | for j in range(len(house_list)): 494 | if j == 0: 495 | try: 496 | index_local_terminal = devices_list.index("local_terminal") 497 | if index_local_terminal >= 0: 498 | term = self.getRandomCVEVals("local_terminal".upper().replace("_", " ")) 499 | writer.writerow(["local_terminal_" + street_list[i], term, 500 | self.find_cve_values(term)[0], 501 | self.find_cve_values(term)[1], 502 | self.find_cve_values(term)[2]] 503 | ) 504 | entry_target.append("local_terminal_" + street_list[i]) 505 | except Exception as e: 506 | print("There is no local_terminal!") 507 | try: 508 | index_pmu = devices_list.index("pmu") 509 | if index_pmu >= 0: 510 | term = self.getRandomCVEVals("pmu".upper().replace("_", " ")) 511 | writer.writerow(["pmu_" + street_list[i], term, 512 | self.find_cve_values(term)[0], 513 | self.find_cve_values(term)[1], 514 | self.find_cve_values(term)[2]] 515 | ) 516 | entry_target.append("pmu_" + street_list[i]) 517 | except Exception as e: 518 | print("There is no pmu!") 519 | try: 520 | index_substation_rtu = devices_list.index("substation_rtu") 521 | if index_substation_rtu >= 0: 522 | term = self.getRandomCVEVals("substation_rtu".upper().replace("_", " ")) 523 | writer.writerow(["substation_rtu_" + street_list[i], term, 524 | self.find_cve_values(term)[0], 525 | self.find_cve_values(term)[1], 526 | self.find_cve_values(term)[2]] 527 | ) 528 | entry_target.append("substation_rtu_"+street_list[i]) 529 | except Exception as e: 530 | print("There is no rtu!") 531 | try: 532 | index_plc = devices_list.index("plc") 533 | if index_plc >= 0: 534 | term = self.getRandomCVEVals("plc".upper().replace("_", " ")) 535 | writer.writerow(["plc_" + street_list[i], term, 536 | self.find_cve_values(term)[0], 537 | self.find_cve_values(term)[1], 538 | self.find_cve_values(term)[2]] 539 | ) 540 | entry_target.append("plc_" + street_list[i]) 541 | except Exception as e: 542 | print("There is no plc!") 543 | try: 544 | index_concentrator = devices_list.index("concentrator") 545 | if index_concentrator >= 0: 546 | term = self.getRandomCVEVals("CONCENTRATOR") 547 | writer.writerow(["concentrator_" + street_list[i], term, 548 | self.find_cve_values(term)[0], 549 | self.find_cve_values(term)[1], 550 | self.find_cve_values(term)[2]] 551 | ) 552 | entry_target.append("concentrator_" + street_list[i]) 553 | except Exception as e: 554 | print("There is no Concentrator!") 555 | for k in range(len(devices_list)): 556 | if devices_list[k] not in ["concentrator", "central_concentrator", 557 | "local_terminal", "pmu", "substation_rtu", 558 | "plc", 559 | "fep", "application_server", 560 | "active_directory_server", "historian_server", 561 | "communication_server", "hmi", 562 | "work_station", "iccp_server", 563 | "ems_drp_server" 564 | ] : 565 | term = self.getRandomCVEVals(devices_list[k].upper().replace("_", " ")) 566 | writer.writerow([devices_list[k] + "_" + street_list[i] + "_" + str(j), term, 567 | self.find_cve_values(term)[0], 568 | self.find_cve_values(term)[1], 569 | self.find_cve_values(term)[2]] 570 | ) 571 | entry_target.append(devices_list[k] + "_" + street_list[i] + "_" + str(j)) 572 | 573 | 574 | self.text_file.set("File was generated\n"+str(os.getcwd())+"/"+source_file_name) 575 | self.updtcblist(entry_target, list(set(entry_target)-set(devices_list))) 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | def show_powergrid_model(self): 591 | path_1 = "GLM/"+combo_smartgrid_model.get().replace(" ", "_")+'.glm' 592 | path_2 = "GLM/"+combo_smartgrid_model.get().replace(" ", "_") +'.dot' 593 | os.system('python3 ' + path + 'glmMap.py ' + path_1 + ' ' + path_2) 594 | #os.system('ruby glm2dot.rb ' + path_1 + ' ' + path_2+ " GridAttackAnalyer") 595 | s = Source.from_file(path_2) 596 | s.view() 597 | 598 | def on_edit_device_change(self, index): 599 | #combo_edit_cve['values'] = combo_to_edit_device.get() 600 | device_list = combo_to_edit_device.get().split("_") 601 | device_list = device_list[:len(device_list) - 2] 602 | device_list_to_find = [] 603 | device_list_to_find.append((' '.join(map(str, device_list))).title()) 604 | cve_list_to_find = [] 605 | for key in range(len(distros_dict['object'][1]['devices_list'])): 606 | if (distros_dict['object'][1]['devices_list'][key]["device_name"]).upper() == device_list_to_find[0].upper(): 607 | cve_list_to_find.extend(distros_dict['object'][1]['devices_list'][key]["CVE_list"]) 608 | combo_edit_cve['values'] = cve_list_to_find 609 | combo_edit_cve.current(0) # set the selected item 610 | break 611 | 612 | 613 | 614 | def plotmetrics(self): 615 | os.system('python3 ' + path + 'plotMetrics.py') 616 | 617 | def on_combo_connection_model_change(self, event): 618 | for i in range(number_device_list+1): 619 | if(globals()["devices_list" + str(i)].cget("text")=="Gateway"): 620 | #print(globals()["devices_list" + str(i)].cget("text")) 621 | if (combo_connection_model.get() == "Direct Connection"): 622 | globals()["devices_list" + str(i)].configure(state='disabled') 623 | globals()["CVE_list" + str(i)].configure(state='disabled') 624 | globals()["btn_CVE" + str(i)].configure(state='disabled') 625 | else: 626 | globals()["devices_list" + str(i)].configure(state='enable') 627 | globals()["CVE_list" + str(i)].configure(state='enable') 628 | globals()["btn_CVE" + str(i)].configure(state='enable') 629 | break 630 | 631 | 632 | def on_cve_vals_selection_change(self, event): 633 | if (combo_cve_vals_selection.get() == "Automatically"): 634 | for i in range(number_device_list+1): 635 | globals()["CVE_list" + str(i)].configure(state='disabled') 636 | globals()["btn_CVE" + str(i)].configure(state='disabled') 637 | else: 638 | for i in range(number_device_list+1): 639 | globals()["CVE_list" + str(i)].configure(state='enable') 640 | globals()["btn_CVE" + str(i)].configure(state='enable') 641 | 642 | 643 | 644 | 645 | 646 | def update_csv_file(self): 647 | filename = source_file_name 648 | tempfile = NamedTemporaryFile(mode='w', delete=False) 649 | cve = str(combo_edit_cve.get()) 650 | devices = str(combo_to_edit_device.get()) 651 | 652 | fields = ['Device', 'CVE', 'CVSS Base Score 2.0', 'Impact Subcore', 'Exploitability Subscore'] 653 | with open(filename, 'r') as csvfile, tempfile: 654 | reader = csv.DictReader(csvfile, fieldnames=fields) 655 | writer = csv.DictWriter(tempfile, fieldnames=fields) 656 | for row in reader: 657 | if row['Device'] == devices: 658 | print('updating row', row['Device']) 659 | row['CVE'], row['CVSS Base Score 2.0'], row['Impact Subcore'], row['Exploitability Subscore'] = \ 660 | cve, str(self.find_cve_values(cve)[0]), str(self.find_cve_values(cve)[1]), str(self.find_cve_values(cve)[2]) 661 | row = {'Device': row['Device'], 'CVE': row['CVE'], 'CVSS Base Score 2.0': row['CVSS Base Score 2.0'], 'Impact Subcore': row['Impact Subcore'], 'Exploitability Subscore': row['Exploitability Subscore']} 662 | writer.writerow(row) 663 | 664 | shutil.move(tempfile.name, filename) 665 | messagebox.showinfo("Information", 666 | "New CVE has been updated!\n" + 667 | "- Device: " + devices + 668 | "\n- CVE: " + cve + 669 | "\n\n- CVSS Base Score v2.0: " + str(self.find_cve_values(cve)[0]) + 670 | "\n\n- Impact Subscore: " + str(self.find_cve_values(cve)[1]) + 671 | "\n\n- Exploitability Subscore: " + str(self.find_cve_values(cve)[2]) 672 | ) 673 | 674 | 675 | 676 | 677 | def create_widgets(self, root): 678 | 679 | lbl = Label(root, text="Smart Grid Attack Analysis System", font=("Times", 18), background="#D6E2F3", foreground="#000280") 680 | lbl.grid(row=0, columnspan=5) 681 | style = ttk.Style() 682 | style.configure("TButton", foreground="blue", background="orange", font="Times 14", width=18) 683 | 684 | 685 | 686 | 687 | 688 | i = 1 689 | lbl_powergrid_model = Label(root, text=" Smart Grid Model\n", background="#D6E2F3", foreground="#000280") 690 | # sticky="W" left align 691 | 692 | lbl_powergrid_model.grid(row = i, column=0, sticky="W") 693 | lbl_powergrid_model.config(width=18) 694 | 695 | powergrid =[] 696 | 697 | 698 | for key_1 in range(len(distros_dict['object'][0]['model_list'])): 699 | powergrid.append(distros_dict['object'][0]['model_list'][key_1]['list_name']) 700 | #for key_2 in distros_dict['object'][0]['model_list'][key_1]["streets_and_houses"][0]: 701 | #print(key_2) 702 | 703 | global combo_smartgrid_model 704 | combo_smartgrid_model = Combobox(root, width=35) 705 | 706 | combo_smartgrid_model['values']= (powergrid) 707 | combo_smartgrid_model.current(0) #set the selected item 708 | combo_smartgrid_model.grid(row = i, column=1, columnspan=2, sticky="W") 709 | 710 | 711 | btn_show_smartgrid_model = Button(root, text='Show', command=self.show_powergrid_model, style="TButton") 712 | btn_show_smartgrid_model.grid(row = i, column=3, columnspan=2, sticky="W") 713 | 714 | 715 | i = i+1 716 | 717 | lbl_network_model = Label(root, text=" Smart Meter Connection\n", background="#D6E2F3", foreground="#000280") 718 | lbl_network_model.grid(row = i, column=0, sticky="W") 719 | lbl_network_model.config(width=23) 720 | 721 | 722 | global combo_connection_model 723 | combo_connection_model = Combobox(root, width=35) 724 | combo_connection_model.bind('<>', self.on_combo_connection_model_change) 725 | combo_connection_model['values']= ("Via a Gateway", "Direct Connection") 726 | combo_connection_model.current(0) #set the selected item 727 | combo_connection_model.grid(row = i, column=1, columnspan=2, sticky="W") 728 | i = i + 1 729 | 730 | lbl_cve_vals_selection = Label(root, text=" CVE Selection\n", background="#D6E2F3", foreground="#000280") 731 | lbl_cve_vals_selection.grid(row=i, column=0, sticky="W") 732 | lbl_cve_vals_selection.config(width=23) 733 | global combo_cve_vals_selection 734 | combo_cve_vals_selection = Combobox(root, width=35) 735 | combo_cve_vals_selection.bind('<>', self.on_cve_vals_selection_change) 736 | combo_cve_vals_selection['values'] = ("Manually", "Automatically") 737 | combo_cve_vals_selection.current(0) # set the selected item 738 | combo_cve_vals_selection.grid(row=i, column=1, columnspan=2, sticky="W") 739 | 740 | 741 | i = i + 1 742 | 743 | 744 | 745 | #filename = filedialog.asksaveasfilename(initialdir="/", title="Select file", filetypes=(("jpeg files", "*.jpg"), ("all files", "*.*"))) 746 | #filename.grid(row = i, column=1) 747 | 748 | i = i+1 749 | 750 | lbl_devices = Label(root, text=" Devices and \n Vulnerability\n", background="#D6E2F3", foreground="#000280") 751 | lbl_devices.grid(row = i, column=0, sticky="W", rowspan = len(distros_dict['object'][1]['devices_list'])) 752 | lbl_devices.config(width=18) 753 | i = i+1 754 | 755 | devices_list = [] 756 | global number_device_list 757 | global devices_and_cve_list 758 | devices_and_cve_list = dict() 759 | j = 0 760 | for key in range(len(distros_dict['object'][1]['devices_list'])): 761 | globals() ["devices_list"+str(key)] = Checkbutton(root, width=17, text=distros_dict['object'][1]['devices_list'][key]["device_name"], variable=distros_dict['object'][1]['devices_list'][key]["device_name"]) 762 | 763 | devices_list.append(distros_dict['object'][1]['devices_list'][key]["device_name"]) 764 | #globals() ["devices_list"+str(key)].config(width=18, font="Times") 765 | globals() ["CVE_list"+str(key)] = Combobox(root, width=15) 766 | globals() ["CVE_list"+str(key)] ['values'] = distros_dict['object'][1]['devices_list'][key]["CVE_list"] 767 | globals() ["CVE_list"+str(key)].current(0) 768 | 769 | devices_and_cve_list.update({str(distros_dict['object'][1]['devices_list'][key]["device_name"]).upper(): distros_dict['object'][1]['devices_list'][key]["CVE_list"]}) 770 | globals() ["btn_CVE"+str(key)] = Button(root, text='Info', command = lambda key=key: self.OnButtonClick(key)) 771 | if j%2 == 0: 772 | globals()["devices_list" + str(key)].grid(row=i, column=1, sticky="W") 773 | globals()["CVE_list" + str(key)].grid(row=i, column=2, sticky="W") 774 | globals() ["btn_CVE"+str(key)].grid(row = i, column = 3, sticky="W") 775 | else: 776 | globals()["devices_list" + str(key)].grid(row=i, column=4, sticky="W") 777 | globals()["CVE_list" + str(key)].grid(row=i, column=5, sticky="W") 778 | globals()["btn_CVE" + str(key)].grid(row=i, column=6, sticky="W") 779 | i = i + 1 780 | j = j + 1 781 | 782 | 783 | 784 | number_device_list = key 785 | 786 | i = i+2 787 | lbl_entry = Label(root, text=" \n \n ", background="#D6E2F3", foreground="#000280") 788 | lbl_entry.grid(row = i, column=0, sticky="W", rowspan = len(distros_dict['object'][1]['devices_list'])) 789 | lbl_entry.config(width=15) 790 | 791 | i = i+1 792 | global valores 793 | valores = StringVar() 794 | valores.set("") 795 | 796 | i = i+3 797 | btn_generate_file = Button(root, text='Generate File', command=lambda: self.generate_file()) 798 | btn_generate_file.grid(row=i, column=0) 799 | i = i + 1 800 | lbl_generate_file = Label(root, textvariable=self.text_file, background="#D6E2F3", foreground="#000280") 801 | lbl_generate_file.grid(row=i, column=0, sticky="W", columnspan=4) 802 | lbl_generate_file.config() 803 | i = i + 1 804 | 805 | btn_open_file_to_edit = Button(root, text='1 - Manual Edit', command=lambda: self.open_csv_file()) 806 | btn_open_file_to_edit.grid(row=i, column=0) 807 | i = i+1 808 | lbl_edit_device = Label(root, text="2 - Edit a Device & CVE", background="#D6E2F3", foreground="#000280") 809 | lbl_edit_device.grid(row=i, column=0, sticky="W") 810 | lbl_edit_device.config(width=21) 811 | global combo_to_edit_device 812 | #string_edit_device = StringVar() 813 | #string_edit_device.trace('w', self.on_edit_device_change) 814 | 815 | combo_to_edit_device = Combobox(root, width=35) 816 | combo_to_edit_device['values'] = [""] 817 | combo_to_edit_device.bind('<>', self.on_edit_device_change) 818 | combo_to_edit_device.current(0) # set the selected item 819 | combo_to_edit_device.grid(row=i, column=1, columnspan=2, sticky="W") 820 | 821 | 822 | 823 | global combo_edit_cve 824 | combo_edit_cve = Combobox(root, width=35) 825 | combo_edit_cve['values'] = [""] 826 | combo_edit_cve.current(0) # set the selected item 827 | combo_edit_cve.grid(row=i, column=3, columnspan=2, sticky="W") 828 | 829 | btn_update_cve = Button(root, text='Update', command=self.update_csv_file) 830 | btn_update_cve.grid(row=i, column=5) 831 | 832 | 833 | 834 | 835 | i = i + 1 836 | 837 | lbl_entry = Label(root, text=" ", background="#D6E2F3", foreground="#000280") 838 | lbl_entry.grid(row=i, column=0, sticky="W") 839 | lbl_entry.config(width=15) 840 | i = i + 3 841 | 842 | 843 | 844 | 845 | lbl_entry = Label(root, text=" Entry Point", background="#D6E2F3", foreground="#000280") 846 | lbl_entry.grid(row=i, column=0, sticky="W") 847 | lbl_entry.config(width=15) 848 | 849 | 850 | global lstbox_target 851 | lstbox_target = Listbox(root, listvariable=valores, selectmode=MULTIPLE, width=35, height=5) 852 | lstbox_target.configure(exportselection=False) 853 | lstbox_target.grid(column=1, row=i, columnspan=2, sticky="W") 854 | 855 | scrollbar = Scrollbar(root, orient="vertical") 856 | 857 | scrollbar.config(command=lstbox_target.yview) 858 | scrollbar.grid(column=1, row=i, columnspan=2, sticky="E") 859 | lstbox_target.config(yscrollcommand=scrollbar.set) 860 | 861 | # combo_entry 862 | #combo_entry = Combobox(root, width=35) 863 | #combo_entry['values'] = [""] 864 | #combo_entry.current(0) # set the selected item 865 | #combo_entry.grid(row=i, column=1) 866 | 867 | lbl_target = Label(root, text=" Target", background="#D6E2F3", foreground="#000280") 868 | lbl_target.grid(row=i, column=3, sticky="E") 869 | lbl_target.config(width=15) 870 | 871 | global combo_target 872 | 873 | combo_target = Combobox(root, width=35) 874 | combo_target['values'] = [""] 875 | combo_target.current(0) # set the selected item 876 | combo_target.grid(row=i, column=4, columnspan = 2, sticky="W") 877 | i = i+1 878 | 879 | 880 | 881 | 882 | 883 | i = i + 1 884 | 885 | 886 | btn_tree_generate = Button(root, text='Run', command=self.generate_tree) 887 | btn_tree_generate.grid(row = i, column = 0) 888 | i = i+1 889 | 890 | label = Label(root, text="\n Results \n", background="#D6E2F3", foreground="#000280") 891 | label.grid(row = i, column = 0, sticky="W") 892 | i = i+1 893 | 894 | btn_attack_graph = Button(root, text='Attack Graph', command=self.open_attack_graph) 895 | btn_attack_graph.grid(row = i, column=0) 896 | 897 | 898 | 899 | btn_attack_paths = Button(root, text='Graph Source File', command=self.open_graph_source_file, style="TButton") 900 | btn_attack_paths.grid(row = i, column=1) 901 | btn_security_metrics = Button(root, text='Security Metrics', command=lambda: self.open_security_csv_file()) 902 | btn_security_metrics.grid(row = i, column=2) 903 | 904 | btn_security_metrics = Button(root, text='Plot Metrics', command=lambda: self.plotmetrics()) 905 | btn_security_metrics.grid(row=i, column=3, columnspan=2) 906 | 907 | i = i+1 908 | lbl_entry = Label(root, text=" ", background="#D6E2F3", foreground="#000280") 909 | lbl_entry.grid(row = i, column=0, sticky="W", rowspan = len(distros_dict['object'][1]['devices_list'])) 910 | lbl_entry.config(width=15) 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | window = tk.Tk() 919 | window.title("GridAttackAnalysis") 920 | window.option_add( "*font", "Times 14" ) 921 | window.geometry() 922 | window.config(bg="#D6E2F3") 923 | app = MyApplication(window) 924 | window.mainloop() --------------------------------------------------------------------------------