├── 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 | 
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 | 
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 | 
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 | 
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()
--------------------------------------------------------------------------------