Java tutorial
/**************************************************************************************** * This file is part of FIWARE CyberCAPTOR, * * instance of FIWARE Cyber Security Generic Enabler * * Copyright (C) 2012-2015 Thales Services S.A.S., * * 20-22 rue Grande Dame Rose 78140 VELIZY-VILACOUBLAY FRANCE * * * * FIWARE CyberCAPTOR is free software; you can redistribute * * it and/or modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 3 of the License, * * or (at your option) any later version. * * * * FIWARE CyberCAPTOR is distributed in the hope * * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with FIWARE CyberCAPTOR. * * If not, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ package org.fiware.cybercaptor.server.attackgraph; import org.fiware.cybercaptor.server.attackgraph.Vertex.VertexType; import org.fiware.cybercaptor.server.attackgraph.fact.DatalogCommand; import org.fiware.cybercaptor.server.attackgraph.fact.Fact.FactType; import org.fiware.cybercaptor.server.informationsystem.InformationSystem; import org.fiware.cybercaptor.server.informationsystem.InformationSystemHost; import org.fiware.cybercaptor.server.informationsystem.Service; import org.fiware.cybercaptor.server.informationsystem.graph.InformationSystemGraph; import org.fiware.cybercaptor.server.remediation.*; import org.fiware.cybercaptor.server.remediation.RemediationAction.ActionType; import org.fiware.cybercaptor.server.topology.asset.IPAddress; import org.fiware.cybercaptor.server.topology.asset.component.FirewallRule; import org.fiware.cybercaptor.server.topology.asset.component.FirewallRule.Action; import org.fiware.cybercaptor.server.topology.asset.component.FirewallRule.Protocol; import org.fiware.cybercaptor.server.topology.asset.component.FirewallRule.Table; import org.fiware.cybercaptor.server.topology.asset.component.PortRange; import org.fiware.cybercaptor.server.vulnerability.Vulnerability; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.input.SAXBuilder; import java.io.FileInputStream; import java.sql.Connection; import java.util.*; /** * Class used to represent an attack path. * An attack path is in fact a special attack graph with several leaves and one goal * * @author Francois-Xavier Aguessy */ public class AttackPath extends MulvalAttackGraph implements Cloneable { /** * The scoring of the attack path (should be between 0 and 1) */ public double scoring = 0; /** * The goal of the attacker */ Vertex goal = null; /** * @param leavesToCorrect the list of leaves that should be corrected * @param indexInPath the index where we are in the list of leaves * @param howToRemediateLeaves a vector of how each leaf can be corrected * @return The list of remediation actions : A1 OR A2 OR A3 where A1 = A1.1 AND A1.2 AND A1.3 ... */ static private List<List<RemediationAction>> computeRemediationToPathWithLeafRemediations( List<Vertex> leavesToCorrect, int indexInPath, HashMap<Integer, List<List<RemediationAction>>> howToRemediateLeaves) { //Recursive function on the number of remaining leaves to correct if (indexInPath > leavesToCorrect.size() - 1) { //path empty //Normally this case should never happen return new ArrayList<List<RemediationAction>>(); } else if (indexInPath == leavesToCorrect.size() - 1) { //one leaf //Stop case of the recursion, we return the mean to correct only one leaf Vertex currentLeaf = leavesToCorrect.get(indexInPath); return howToRemediateLeaves.get(currentLeaf.id); } else { //more than one leaf in path Vertex currentLeaf = leavesToCorrect.get(indexInPath); //First we correct the other leaves and get the result List<List<RemediationAction>> result_remediation_path_without_last_leaf = computeRemediationToPathWithLeafRemediations( leavesToCorrect, indexInPath + 1, howToRemediateLeaves); List<List<RemediationAction>> resultWithoutLastLeaf = new ArrayList<List<RemediationAction>>(); for (List<RemediationAction> aResult_remediation_path_without_last_leaf1 : result_remediation_path_without_last_leaf) { List<RemediationAction> contentResultToDuplicate = new ArrayList<RemediationAction>( aResult_remediation_path_without_last_leaf1); resultWithoutLastLeaf.add(contentResultToDuplicate); } List<List<RemediationAction>> result_to_duplicate = new ArrayList<List<RemediationAction>>(); for (List<RemediationAction> aResult_remediation_path_without_last_leaf : result_remediation_path_without_last_leaf) { List<RemediationAction> contentResultToDuplicate = new ArrayList<RemediationAction>( aResult_remediation_path_without_last_leaf); result_to_duplicate.add(contentResultToDuplicate); } List<List<RemediationAction>> result = new ArrayList<List<RemediationAction>>(); //Then, we add each element of remediation concerning the current leaf: List<List<RemediationAction>> howToRemediateLeaf = howToRemediateLeaves.get(currentLeaf.id); //FOR ALL OR CONDITIONS (remediations of the current leaf) //we duplicate the old result (result_remediation_path_without_last_leaf) howToRemediateLeaf.size() - 1 times //and add all the howToRemediateLeaf.get(i) to each duplicates //For the first OR, no duplicate is useful //Add the howToRemediateLeaf.get(i) to all OR elements for (List<RemediationAction> aResultWithoutLastLeaf : resultWithoutLastLeaf) { aResultWithoutLastLeaf.addAll(new ArrayList<RemediationAction>(howToRemediateLeaf.get(0))); } result.addAll(resultWithoutLastLeaf); //For the other OR, we can duplicate for (int i = 1; i < howToRemediateLeaf.size(); i++) { //Create the duplicate and add the howToRemediateLeaf.get(i) results into it List<List<RemediationAction>> duplicate = new ArrayList<List<RemediationAction>>(); for (List<RemediationAction> aResult_to_duplicate : result_to_duplicate) { List<RemediationAction> duplicateContent = new ArrayList<RemediationAction>( aResult_to_duplicate); duplicate.add(duplicateContent); } //For all OR of the duplicate, add the howToRemediateLeaf.get(i) for (List<RemediationAction> aDuplicate : duplicate) { aDuplicate.addAll(new ArrayList<RemediationAction>(howToRemediateLeaf.get(i))); } result.addAll(duplicate); } return result; } } /** * Compute all possible combination of k integers < to n * * @param k integer < n * @param n integer * @return the list of list of k integers */ public static List<List<Integer>> combination(int k, int n) { List<Integer> listNumber = new ArrayList<Integer>(); for (int i = 0; i < n; i++) { listNumber.add(i); } List<List<Integer>> result = new ArrayList<List<Integer>>(); combinationRecursive(k, listNumber, 0, new ArrayList<Integer>(), result); return result; } /** * Main recursive function used to calculate the combination list combination(k,n) */ private static void combinationRecursive(int k, List<Integer> listNumber, int startListNumber, List<Integer> temporaryResult, List<List<Integer>> result) { if (k == 0) { result.add(new ArrayList<Integer>(temporaryResult)); temporaryResult.clear(); return; } if (listNumber.size() == startListNumber) return; List<Integer> temporaryResult2 = new ArrayList<Integer>(temporaryResult); temporaryResult.add(listNumber.get(startListNumber)); combinationRecursive(k - 1, listNumber, startListNumber + 1, temporaryResult, result); combinationRecursive(k, listNumber, startListNumber + 1, temporaryResult2, result); } /** * Get all the machines that are involved in this attack path considered only as a list of vertices * * @param topology the Network topology * @param attackPath the attack path to explore * @return the list of machine involved in this attack path * @throws Exception */ public static List<InformationSystemHost> getInvolvedMachines(InformationSystem topology, List<Vertex> attackPath) throws Exception { List<InformationSystemHost> result = new ArrayList<InformationSystemHost>(); for (Vertex vertex : attackPath) { if (vertex.fact != null && vertex.fact.type != FactType.RULE) { InformationSystemHost machine = vertex.getRelatedMachine(topology); result.add(machine); } } return result; } public static List<AttackPath> loadAttackPathsFromFile(String attackPathsFilePath, AttackGraph relatedAttackGraph) throws Exception { FileInputStream file = new FileInputStream(attackPathsFilePath); SAXBuilder sxb = new SAXBuilder(); Document document = sxb.build(file); Element root = document.getRootElement(); List<AttackPath> result = new ArrayList<AttackPath>(); List<Element> attackPathsElements = root.getChildren("attack_path"); if (!attackPathsElements.isEmpty()) { for (Element attackPathElement : attackPathsElements) { if (attackPathElement != null) { AttackPath attackPath = new AttackPath(); attackPath.loadFromDomElementAndAttackGraph(attackPathElement, relatedAttackGraph); result.add(attackPath); } } } sortAttackPaths(result); return result; } /** * Sort attack paths with their scoring in descending order */ public static void sortAttackPaths(List<AttackPath> attackPathList) { Collections.sort(attackPathList, new AttackPath.AttackPathComparator()); } /** * @return the leaves of the attack graph */ public List<Vertex> getLeavesThatCanBeRemediated() { List<Vertex> result = new ArrayList<Vertex>(); for (int i : this.vertices.keySet()) { Vertex vertex = this.vertices.get(i); vertex.computeParentsAndChildren(this); if (vertex.parents.size() == 0) { if (vertex.fact != null && vertex.fact.datalogCommand != null && vertex.fact.datalogCommand.command != null) { String command = vertex.fact.datalogCommand.command; if (command.equals("vulExists") || command.equals("hacl") || command.equals("haclprimit") || command.toLowerCase().contains("vlan") || command.contains("attackerLocated")) result.add(vertex); } } else { if (vertex.fact != null && vertex.fact.datalogCommand != null && vertex.fact.datalogCommand.command != null && vertex.fact.datalogCommand.command.equals("hacl")) { result.add(vertex); } } } return result; } /** * @return the goal of the attack graph */ public Vertex getGoal() { if (goal == null) { for (int i : this.vertices.keySet()) { Vertex vertex = this.vertices.get(i); vertex.computeParentsAndChildren(this); if (vertex.children.size() == 0) goal = vertex; } } return goal; } /** * @param topology the network topology * @param conn database connection * @return the list of possible remediation actions to remediate this attack path : remediation[1] OR remediation[2] OR remediation[3] ; remediation[1] = remediation[1][1] AND remediation[1][2]... */ public List<List<RemediationAction>> getRemediationAction(InformationSystem topology, Connection conn, String costParametersFolder) throws Exception { List<List<RemediationAction>> result = new ArrayList<List<RemediationAction>>(); List<Vertex> leaves = this.getLeavesThatCanBeRemediated(); //Compute all possible sufficient combination of leaves to cut the attack path List<List<Vertex>> sufficientLeavesToCutPath = new ArrayList<List<Vertex>>(); List<Vertex> remainingLeaves = new ArrayList<Vertex>(leaves); int simultaneousLeavesNumber = 1; while (simultaneousLeavesNumber <= remainingLeaves.size()) { //While we take a number of leaves lower than the number of remaining leaves List<List<Integer>> combination = combination(simultaneousLeavesNumber, remainingLeaves.size()); List<Vertex> leavesSufficient = new ArrayList<Vertex>(); for (List<Integer> aCombination : combination) { List<Vertex> leavesToTest = new ArrayList<Vertex>(); for (Integer anACombination : aCombination) { leavesToTest.add(remainingLeaves.get(anACombination)); } if (leavesMandatoryForGoal(leavesToTest)) { sufficientLeavesToCutPath.add(leavesToTest); leavesSufficient.addAll(leavesToTest); } } for (Vertex aLeavesSufficient : leavesSufficient) { remainingLeaves.remove(aLeavesSufficient); } simultaneousLeavesNumber++; } //Create a hashlist of the list of remediation for each leaf (possible_actions[1] OR possible_actions[2] OR possible_actions[3] .... with possible_actions[1] = possible_actions[1][1] AND possible_actions[1][2] AND possible_actions[1][3] HashMap<Integer, List<List<RemediationAction>>> howToRemediateLeaves = new HashMap<Integer, List<List<RemediationAction>>>(); for (Vertex leaf : leaves) { howToRemediateLeaves.put(leaf.id, getRemediationActionForLeaf(leaf, topology, conn, costParametersFolder)); } //Try to see how to remediate each list of leaf for (List<Vertex> aSufficientLeavesToCutPath : sufficientLeavesToCutPath) { //List<RemediationAction> pathRemediations = new ArrayList<RemediationAction>(); boolean pathCanBeRemediated = true; //For all leaf list that can cut the attack path for (Vertex leaf : aSufficientLeavesToCutPath) { if (howToRemediateLeaves.get(leaf.id).isEmpty()) pathCanBeRemediated = false; } if (pathCanBeRemediated) { result.addAll(computeRemediationToPathWithLeafRemediations(aSufficientLeavesToCutPath, 0, howToRemediateLeaves)); } } return result; } /** * @param topology the network topology * @param conn database connection * @return the list of possible remediation actions to remediate this attack path : remediation[1] OR remediation[2] OR remediation[3] ; remediation[1] = remediation[1][1] AND remediation[1][2]... [Withour snort rules] */ public List<List<RemediationAction>> getRemedationActions(InformationSystem topology, Connection conn, String costParametersFolder) throws Exception { List<List<RemediationAction>> result = new ArrayList<List<RemediationAction>>(); List<Vertex> leaves = this.getLeavesThatCanBeRemediated(); //Compute all possible sufficient combination of leaves to cut the attack path List<List<Vertex>> sufficientLeavesToCutPath = new ArrayList<List<Vertex>>(); List<Vertex> remainingLeaves = new ArrayList<Vertex>(leaves); int simultaneousLeavesNumber = 1; while (simultaneousLeavesNumber <= remainingLeaves.size()) { //While we take a number of leaves lower than the number of remaining leaves List<List<Integer>> combination = combination(simultaneousLeavesNumber, remainingLeaves.size()); List<Vertex> leavesSufficient = new ArrayList<Vertex>(); //TODO : improve this function : can be much faster. for (List<Integer> aCombination : combination) { List<Vertex> leavesToTest = new ArrayList<Vertex>(); for (Integer anACombination : aCombination) { leavesToTest.add(remainingLeaves.get(anACombination)); } if (leavesMandatoryForGoal(leavesToTest)) { sufficientLeavesToCutPath.add(leavesToTest); leavesSufficient.addAll(leavesToTest); } } for (Vertex aLeavesSufficient : leavesSufficient) { remainingLeaves.remove(aLeavesSufficient); } simultaneousLeavesNumber++; } //Create a hashlist of the list of remediation for each leaf (possible_actions[1] OR possible_actions[2] OR possible_actions[3] .... with possible_actions[1] = possible_actions[1][1] AND possible_actions[1][2] AND possible_actions[1][3] HashMap<Integer, List<List<RemediationAction>>> howToRemediateLeaves = new HashMap<Integer, List<List<RemediationAction>>>(); for (Vertex leaf : leaves) { howToRemediateLeaves.put(leaf.id, getRemediationActionForLeaf(leaf, topology, conn, costParametersFolder, true)); } //Try to see how to remediate each list of leaf for (List<Vertex> aSufficientLeavesToCutPath : sufficientLeavesToCutPath) { //List<RemediationAction> pathRemediations = new ArrayList<RemediationAction>(); boolean pathCanBeRemediated = true; //For all leaf list that can cut the attack path for (Vertex leaf : aSufficientLeavesToCutPath) { if (howToRemediateLeaves.get(leaf.id).isEmpty()) pathCanBeRemediated = false; } if (pathCanBeRemediated) { result.addAll(computeRemediationToPathWithLeafRemediations(aSufficientLeavesToCutPath, 0, howToRemediateLeaves)); } } return result; } /** * @param topology the network topology * @param conn the database connection * @param costParametersFolder the folder where the cost parameters are stored * @return the list of deployable remediations without snort rules * @throws Exception */ public List<DeployableRemediation> getDeployableRemediations(InformationSystem topology, Connection conn, String costParametersFolder) throws Exception { List<List<RemediationAction>> remediationActions = this.getRemedationActions(topology, conn, costParametersFolder); List<DeployableRemediation> result = new ArrayList<DeployableRemediation>(); //For all "OR" remediations for (List<RemediationAction> remediationAction1 : remediationActions) { List<DeployableRemediation> result_tmp = new ArrayList<DeployableRemediation>(); //Result for only this group of remediations DeployableRemediation dr = new DeployableRemediation(this, topology); dr.setActions(new ArrayList<DeployableRemediationAction>()); result_tmp.add(dr); //For all "AND" remediations for (RemediationAction remediationAction : remediationAction1) { if (remediationAction.getPossibleMachines().size() > 1) { //ALL MACHINES EXCEPT THE FIRST ONE List<DeployableRemediation> resultForThisRemediationAction = new ArrayList<DeployableRemediation>(); //Result for only this group of remediations for (int m = 1; m < remediationAction.getPossibleMachines().size(); m++) { //For all the previously added Deployable Remediation Actions for (DeployableRemediation aResult_tmp : result_tmp) { DeployableRemediation newdr = new DeployableRemediation(this, topology); resultForThisRemediationAction.add(newdr); newdr.getActions().addAll(aResult_tmp.getActions()); DeployableRemediationAction deployableRemediationAction = new DeployableRemediationAction(); deployableRemediationAction.setRemediationAction(remediationAction); deployableRemediationAction.setHost(remediationAction.getPossibleMachines().get(m)); newdr.getActions().add(deployableRemediationAction); } } //FIRST MACHINE for (DeployableRemediation aResult_tmp : result_tmp) { //For all the previously added Deployable Remediation Actions DeployableRemediationAction deployableRemediationAction = new DeployableRemediationAction(); deployableRemediationAction.setRemediationAction(remediationAction); deployableRemediationAction.setHost(remediationAction.getPossibleMachines().get(0)); aResult_tmp.getActions().add(deployableRemediationAction); } result_tmp.addAll(resultForThisRemediationAction); } else if (remediationAction.getPossibleMachines().size() == 1) { for (DeployableRemediation aResult_tmp : result_tmp) { DeployableRemediationAction deployableRemediationAction = new DeployableRemediationAction(); deployableRemediationAction.setRemediationAction(remediationAction); deployableRemediationAction.setHost(remediationAction.getPossibleMachines().get(0)); aResult_tmp.getActions().add(deployableRemediationAction); } } } //Add all the non empty Deployable Remediation Action to the result for (DeployableRemediation aResult_tmp : result_tmp) { if (aResult_tmp.getActions().size() > 0) result.add(aResult_tmp); } } //Compute the cost of all remediation action for (DeployableRemediation aResult : result) { aResult.computeCost(); } Collections.sort(result, new DeployableRemediationComparator()); return result; } /** * This function compute the scoring of this attack path (float between 0 and 1 : 1 = will arrive ; 0 = can't arrive */ public void computeScoring() { scoring = 1.; for (int i : this.vertices.keySet()) { Vertex vertex = this.vertices.get(i); if (vertex.fact != null && vertex.fact.type == FactType.DATALOG_FACT) { DatalogCommand command = vertex.fact.datalogCommand; if (command.command.equals("vulExists")) { scoring *= 1. / 2; } if (command.command.equals("cvss") && command.params[1].equals("l")) { scoring *= 1. / 5; } if (command.command.equals("cvss") && command.params[1].equals("m")) { scoring *= 1. / 10; } if (command.command.equals("cvss") && command.params[1].equals("h")) { scoring *= 1. / 20; } else if (command.command.equals("inCompetent")) { scoring *= 1. / 100; } //Cheat to have the good attack path for demo first : if (command.command.equals("vulExists") && command.params[1].equals("CVE-2004-1315")) { scoring *= 10; } if (command.command.equals("vulExists") && command.params[1].equals("CVE-2012-3951")) { scoring *= 10; } if (command.command.equals("networkServiceInfo") && command.params[1].equals("sonicwall_scrutinizer")) { scoring *= 10; } if (command.command.equals("hacl") && command.params[0].equals("192.168.240.200") && command.params[1].equals("192.168.240.100") && command.params[3].equals("3306")) { scoring *= 10; } if (command.command.equals("hacl") && command.params[0].equals("internet") && command.params[1].equals("192.168.240.200") && command.params[3].equals("80")) { scoring *= 10; } if (command.command.equals("execCode") && command.params[0].equals("192.168.240.100")) { scoring *= 10; } if (command.command.equals("execCode") && command.params[0].equals("192.168.240.200")) { scoring *= 10; } if (command.command.equals("netAccess") && command.params[0].equals("192.168.240.200")) { scoring *= 10; } } } } /** * Print the list of remediations that could be applied to prevent the attack * * @param topology the network topology * @param conn the database connection * @throws Exception */ public void printListRemediations(InformationSystem topology, Connection conn) throws Exception { List<Vertex> leaves = this.getLeavesThatCanBeRemediated(); if (this.getGoal() != null) { System.out.print("The goal of this attack is vertex " + this.getGoal().id); if (this.getGoal().fact != null && this.getGoal().fact.factString != null) System.out.print(" : " + this.getGoal().fact.factString); System.out.println(); } System.out.println("The attack path contains " + leaves.size() + " leaves : "); List<List<Vertex>> sufficientLeavesToCutPath = new ArrayList<List<Vertex>>(); List<Vertex> remainingLeaves = new ArrayList<Vertex>(leaves); int simultaneousLeavesNumber = 1; while (simultaneousLeavesNumber <= remainingLeaves.size()) { //While we take a number of leaves lower than the number of remaining leaves List<List<Integer>> combination = combination(simultaneousLeavesNumber, remainingLeaves.size()); List<Vertex> leavesSufficient = new ArrayList<Vertex>(); for (List<Integer> aCombination : combination) { List<Vertex> leavesToTest = new ArrayList<Vertex>(); for (Integer anACombination : aCombination) { leavesToTest.add(remainingLeaves.get(anACombination)); } if (leavesMandatoryForGoal(leavesToTest)) { sufficientLeavesToCutPath.add(leavesToTest); leavesSufficient.addAll(leavesToTest); } } for (Vertex aLeavesSufficient : leavesSufficient) { remainingLeaves.remove(aLeavesSufficient); } simultaneousLeavesNumber++; } System.out .println("Here are the list of all the combinations of leaves that permit to cut the attack path "); for (int i = 0; i < sufficientLeavesToCutPath.size(); i++) { System.out.print("Combination " + (i + 1) + " : "); for (int j = 0; j < sufficientLeavesToCutPath.get(i).size(); j++) { System.out.print("Leaf " + sufficientLeavesToCutPath.get(i).get(j).id); if (j < sufficientLeavesToCutPath.get(i).size() - 1) System.out.print(" + "); } System.out.println(); } System.out.println("\n------------------------------------"); System.out.println("Here is how to remediate each leaf :"); System.out.println("------------------------------------"); for (Vertex leaf : leaves) { System.out.print(remediateLeaf(leaf, topology, conn)); System.out.println("----------------------------------"); } } /** * @param leaf An attack path leaf * @param topology the network topology * @param conn the database connection * @return The string that contains the text to remediate a leaf * @throws Exception */ public String remediateLeaf(Vertex leaf, InformationSystem topology, Connection conn) throws Exception { String result = ""; result += "* Leaf " + leaf.id + "\n"; if (leaf.fact != null && leaf.fact.type == FactType.DATALOG_FACT && leaf.fact.datalogCommand != null) { DatalogCommand command = leaf.fact.datalogCommand; result += "Datalog fact : " + command.command + "\n"; switch (command.command) { case "vulExists": { Vulnerability vuln = new Vulnerability(conn, Vulnerability.getIdVulnerabilityFromCVE(command.params[1], conn)); List<List<InformationSystemHost>> attackerPath = getAttackerRouteToAVulnerability(leaf, topology); result += "To exploit the vulnerability " + vuln.cve + " the packets of the attacker will pass the following machines : " + "\n"; for (int j = 0; j < attackerPath.size(); j++) { if (attackerPath.size() > 1) result += "Path " + (j + 1) + " : " + "\n"; result += "- "; for (int k = 0; k < attackerPath.get(j).size(); k++) { result += attackerPath.get(j).get(k).getName(); if (k < attackerPath.get(j).size() - 1) result += " -> "; } result += "\n"; List<Patch> patches = vuln.getPatchs(conn); List<Rule> rules = vuln.getRules(conn); if (patches.size() > 0) { result += "To protect against this vulnerability, the following patch(es) can be applied on machine \"" + leaf.getRelatedMachine(topology).getName() + "\" : " + "\n"; for (int m = 0; m < patches.size(); m++) { result += patches.get(m).getLink(); if (m < patches.size() - 1) result += " ; "; } result += "\n"; } if (rules.size() > 0) { result += "To protect against this vulnerability, the following rule(s) can be used on at least one machine of the attacker path : " + "\n"; for (Rule rule : rules) { result += "-" + rule.getRule() + "\n"; } } } break; } case "inCompetent": result += "To protect against this attack, the user \"" + command.params[0] + "\" should be trained " + "\n"; break; case "attackerLocated": result += "To protect against this attack, people should know that the attacker is located on \"" + command.params[0] + "\" \n"; break; case "hasAccount": result += "To protect against this attack, the account \"" + command.params[2] + "\" on the machine \"" + leaf.getRelatedMachine(topology).getName() + "\" should be closed\n"; break; case "hacl": { InformationSystemHost from = topology.getHostByNameOrIPAddress(command.params[0]); InformationSystemHost to = topology.getHostByNameOrIPAddress(command.params[1]); List<List<InformationSystemHost>> attackerPath = command.getRoutesBetweenHostsOfHacl(topology); result += "The attacker packets will use one of the following paths : \n"; for (int j = 0; j < attackerPath.size(); j++) { if (attackerPath.size() > 1) result += "Path " + (j + 1) + " : " + "\n"; result += "- "; for (int k = 0; k < attackerPath.get(j).size(); k++) { result += attackerPath.get(j).get(k).getName(); if (k < attackerPath.get(j).size() - 1) result += " -> "; } result += "\n"; } System.out.println("The following firewall rule should be deployed on all the paths between \"" + from.getName() + "\" and \"" + to.getName() + "\". (See above)"); if (command.params[0].equals("internet")) { result += "DROP\t" + Protocol.getProtocolFromString(command.params[2]) + "\t--\t0.0.0.0/0\t" + to.getFirstIPAddress().getAddress() + "/32\t" + "dpt:" + Service.portStringToInt(command.params[3]) + "\n"; } else if (command.params[1].equals("internet")) { result += "DROP\t" + Protocol.getProtocolFromString(command.params[2]) + "\t--\t" + from.getFirstIPAddress().getAddress() + "/32\t0.0.0.0/0\t" + "dpt:" + Service.portStringToInt(command.params[3]) + "\n"; } else { result += "DROP\t" + Protocol.getProtocolFromString(command.params[2]) + "\t--\t" + from.getFirstIPAddress().getAddress() + "/32\t" + to.getFirstIPAddress().getAddress() + "/32\t" + "dpt:" + Service.portStringToInt(command.params[3]) + "\n"; } break; } } } else { throw new Exception("Leaf is not a datalog fact"); } if (leafMandatoryForGoal(leaf)) result += "If this can be corrected, this attack path will be broken" + "\n"; else result += "Deleting only this vulnerability is not sufficient to cut the attack path" + "\n"; return result; } /** * @param leaf An attack path leaf * @param topology the network topology * @param conn the database connection * @return the possible remediation action to remediate this leaf. To remediate the leaf, we can apply remediation[1] OR remadiation[2] OR remediation[3] * the remediation[1] is remediation[1][1] AND remediation[1][2] AND remediation[1][3] etc... * @throws Exception */ public List<List<RemediationAction>> getRemediationActionForLeaf(Vertex leaf, InformationSystem topology, Connection conn, String costParametersFolder) throws Exception { return getRemediationActionForLeaf(leaf, topology, conn, costParametersFolder, true); } /** * @param leaf An attack path leaf * @param topology the network topology * @param conn the database connection * @param useSnortRule : if true, use the snort rules else don't use it for remediation * @return the possible remediation action to remediate this leaf. To remediate the leaf, we can apply remediation[1] OR remadiation[2] OR remediation[3] * the remediation[1] is remediation[1][1] AND remediation[1][2] AND remediation[1][3] etc... * @throws Exception */ public List<List<RemediationAction>> getRemediationActionForLeaf(Vertex leaf, InformationSystem topology, Connection conn, String costParametersFolder, boolean useSnortRule) throws Exception { List<List<RemediationAction>> result = new ArrayList<List<RemediationAction>>(); if (leaf.fact != null && leaf.fact.type == FactType.DATALOG_FACT && leaf.fact.datalogCommand != null) { DatalogCommand command = leaf.fact.datalogCommand; switch (command.command) { case "vulExists": { List<RemediationAction> remediateVulnerability = new ArrayList<RemediationAction>(); Vulnerability vulnerability = new Vulnerability(conn, Vulnerability.getIdVulnerabilityFromCVE(command.params[1], conn)); List<List<InformationSystemHost>> attackerPath = getAttackerRouteToAVulnerability(leaf, topology); List<Patch> patches = vulnerability.getPatchs(conn); //Get the path of this vulnerability List<Rule> rules = vulnerability.getRules(conn); //Get the snort rules related to this vulnerability if (patches.size() > 0) { RemediationAction remediation = new RemediationAction(ActionType.APPLY_PATCH, costParametersFolder); remediation.setRelatedVertex(leaf); remediation.getPossibleMachines().add(leaf.getRelatedMachine(topology)); for (Patch patche : patches) { remediation.getRemediationParameters().add(patche); } remediateVulnerability.add(remediation); result.add(remediateVulnerability); } if (rules.size() > 0 && useSnortRule) { //If there is a rule, to remediate the leaf, we can deploy rule 1 OR rule 2 OR rule 3 but on ALL possible paths List<RemediationAction> detectVulnerabilityOnAllPath = new ArrayList<RemediationAction>(); //For all possible path for (List<InformationSystemHost> anAttackerPath : attackerPath) { RemediationAction remediation = new RemediationAction(ActionType.DEPLOY_SNORT_RULE, costParametersFolder); remediation.setRelatedVertex(leaf); for (Rule rule : rules) { rule.setRule(rule.getRule().replaceFirst("alert", "reject")); remediation.getRemediationParameters().add(rule); } for (InformationSystemHost anAnAttackerPath : anAttackerPath) { //TODO : Add the machine to the "possible machines" only if snort is installed (need a way to specify this in the input file) //if(attackerPath.get(j).get(incr).getServices().containsKey("snort")) remediation.getPossibleMachines().add(anAnAttackerPath); //The path } detectVulnerabilityOnAllPath.add(remediation); } //Add the detection of vulnerability on all path result.add(detectVulnerabilityOnAllPath); } break; } case "inCompetent": List<RemediationAction> trainUser = new ArrayList<RemediationAction>(); RemediationAction remediation = new RemediationAction(ActionType.TRAIN_USER, costParametersFolder); remediation.setRelatedVertex(leaf); remediation.getRemediationParameters().add(command.params[0]); trainUser.add(remediation); result.add(trainUser); break; case "hacl": case "haclprimit": { InformationSystemHost from = topology.getHostByNameOrIPAddress(command.params[0]); InformationSystemHost to = topology.getHostByNameOrIPAddress(command.params[1]); List<List<InformationSystemHost>> attackerPath = command.getRoutesBetweenHostsOfHacl(topology); //For HACL Rules, we can block the packets with firewall rules //These rules can be deployed either on the input firewall table or on the output firewall table //It depends of the machines //TODO : things needs to be corrected, to add remediations with "alternating" between INPUT and OUTPUT rules //FIRST LET'S DO THE INPUT RULES List<RemediationAction> blockAttackerOnAllPath = new ArrayList<RemediationAction>(); //For all possible path for (List<InformationSystemHost> anAttackerPath1 : attackerPath) { remediation = new RemediationAction(ActionType.DEPLOY_FIREWALL_RULE, costParametersFolder); remediation.setRelatedVertex(leaf); if (command.params[0].equals("internet")) { FirewallRule fwRule = new FirewallRule(Action.DROP, Protocol.getProtocolFromString(command.params[2]), new IPAddress("0.0.0.0"), new IPAddress("0.0.0.0"), new PortRange(true), to.getFirstIPAddress(), new IPAddress("255.255.255.255"), PortRange.fromString(command.params[3]), Table.INPUT); remediation.getRemediationParameters().add(fwRule); } else if (command.params[1].equals("internet")) { FirewallRule fwRule = new FirewallRule(Action.DROP, Protocol.getProtocolFromString(command.params[2]), from.getFirstIPAddress(), new IPAddress("255.255.255.255"), new PortRange(true), new IPAddress("0.0.0.0"), new IPAddress("0.0.0.0"), PortRange.fromString(command.params[3]), Table.INPUT); remediation.getRemediationParameters().add(fwRule); } else { FirewallRule fwRule = new FirewallRule(Action.DROP, Protocol.getProtocolFromString(command.params[2]), from.getFirstIPAddress(), new IPAddress("255.255.255.255"), new PortRange(true), to.getFirstIPAddress(), new IPAddress("255.255.255.255"), PortRange.fromString(command.params[3]), Table.INPUT); remediation.getRemediationParameters().add(fwRule); } //The only thing that change between INPUT and OUTPUT is the machines on which this rule can be deployed for (InformationSystemHost currentAttackerPathHost : anAttackerPath1) { if (command.params[0].equals("internet")) {//Source packet is internet, input will block on all hosts remediation.getPossibleMachines().add(currentAttackerPathHost); } else { InformationSystemHost sourceHost = topology .existingMachineByNameOrIPAddress(command.params[0]); if (sourceHost == null || !sourceHost.equals(currentAttackerPathHost)) {//we add this machine to the possible machines if it is not the sender of the packets remediation.getPossibleMachines().add(currentAttackerPathHost); } } } blockAttackerOnAllPath.add(remediation); } //Add the block of the attacker on all path result.add(blockAttackerOnAllPath); //THEN LET'S DO THE OUTPUT RULES blockAttackerOnAllPath = new ArrayList<RemediationAction>(); //For all possible path for (List<InformationSystemHost> anAttackerPath : attackerPath) { remediation = new RemediationAction(ActionType.DEPLOY_FIREWALL_RULE, costParametersFolder); remediation.setRelatedVertex(leaf); if (command.params[0].equals("internet")) { FirewallRule fwRule = new FirewallRule(Action.DROP, Protocol.getProtocolFromString(command.params[2]), new IPAddress("0.0.0.0"), new IPAddress("0.0.0.0"), new PortRange(true), to.getFirstIPAddress(), new IPAddress("255.255.255.255"), PortRange.fromString(command.params[3]), Table.OUTPUT); remediation.getRemediationParameters().add(fwRule); } else if (command.params[1].equals("internet")) { FirewallRule fwRule = new FirewallRule(Action.DROP, Protocol.getProtocolFromString(command.params[2]), from.getFirstIPAddress(), new IPAddress("255.255.255.255"), new PortRange(true), new IPAddress("0.0.0.0"), new IPAddress("0.0.0.0"), PortRange.fromString(command.params[3]), Table.OUTPUT); remediation.getRemediationParameters().add(fwRule); } else { FirewallRule fwRule = new FirewallRule(Action.DROP, Protocol.getProtocolFromString(command.params[2]), from.getFirstIPAddress(), new IPAddress("255.255.255.255"), new PortRange(true), to.getFirstIPAddress(), new IPAddress("255.255.255.255"), PortRange.fromString(command.params[3]), Table.OUTPUT); remediation.getRemediationParameters().add(fwRule); } //The only thing that change between INPUT and OUTPUT is the machines on which this rule can be deployed for (InformationSystemHost currentAttackerPathHost : anAttackerPath) { if (command.params[1].equals("internet")) {//Source packet is internet, input will block on all hosts remediation.getPossibleMachines().add(currentAttackerPathHost); } else { InformationSystemHost destinationHost = topology .existingMachineByNameOrIPAddress(command.params[1]); if (destinationHost == null || !destinationHost.equals(currentAttackerPathHost)) {//we add this machine to the possible machines if it is not the receiver of the packets remediation.getPossibleMachines().add(currentAttackerPathHost); } } } blockAttackerOnAllPath.add(remediation); } //Add the block of the attacker on all path result.add(blockAttackerOnAllPath); break; } } } return result; } /** * Get the attacker route to access a vulnerability according to the information in the attack path * * @param leaf the leaf of the vulnerability * @param topology the network topology * @return the attacker route * @throws Exception */ public List<List<InformationSystemHost>> getAttackerRouteToAVulnerability(Vertex leaf, InformationSystem topology) throws Exception { Vertex child = leaf.children.get(0); if (child != null) { Vertex netAccessVertex = this.getParentOfVertexWithFactCommand(child, "netAccess"); if (netAccessVertex == null) { netAccessVertex = this.getParentOfVertexWithFactCommand(child, "accessMaliciousInput"); } if (netAccessVertex != null) { Vertex ruleAccessVertex = netAccessVertex.parents.get(0); if (ruleAccessVertex != null) { Vertex haclVertex = this.getParentOfVertexWithFactCommand(ruleAccessVertex, "hacl"); if (haclVertex != null) { return haclVertex.fact.datalogCommand.getRoutesBetweenHostsOfHacl(topology); } else throw new Exception("No hacl."); } else { throw new Exception("Problem while going up to the hacl for the leaf " + leaf.id); } } else { throw new Exception("Problem while going up to the hacl for the leaf " + leaf.id); } } else { throw new Exception("The leaf a no child"); } } /** * @param leaf an attack path leaf * @return true if the leaf is mandatory to reach the goal of the attack Path */ public boolean leafMandatoryForGoal(Vertex leaf) { Vertex goal = this.getGoal(); if (goal != null) { List<Vertex> leaf_list = new ArrayList<Vertex>(); leaf_list.add(leaf); return leavesMandatoryForVertex(leaf_list, goal, new ArrayList<Vertex>()); } return false; } /** * @param leaves a list of leaves * @return true if the leaves are mandatory to reach the goal */ public boolean leavesMandatoryForGoal(List<Vertex> leaves) { Vertex goal = this.getGoal(); return goal != null && leavesMandatoryForVertex(leaves, goal, new ArrayList<Vertex>()); } /** * Recursive function that test whether or not leaves are mandatory to access a vertex * * @param leaves the leaves that will be tested * @param v the vertex * @return true if the leaves are mandatory / else false */ private boolean leavesMandatoryForVertex(List<Vertex> leaves, Vertex v, List<Vertex> alreadySeen) { if (leaves.contains(v)) return true; else if (v.type == VertexType.AND) { boolean result = false; v.computeParentsAndChildren(this); alreadySeen.add(v); for (int i = 0; i < v.parents.size(); i++) { Vertex parent = v.parents.get(i); if (!alreadySeen.contains(parent)) result = result || leavesMandatoryForVertex(leaves, parent, alreadySeen); } alreadySeen.remove(v); return result; } else if (v.type == VertexType.OR) { boolean result = true; v.computeParentsAndChildren(this); alreadySeen.add(v); for (int i = 0; i < v.parents.size(); i++) { Vertex parent = v.parents.get(i); if (!alreadySeen.contains(parent)) result = result && leavesMandatoryForVertex(leaves, parent, alreadySeen); } alreadySeen.remove(v); return result; } else { return false; } } /** * Load the attack path from a DOM element of a XML file (the XML file contains only the arcs, the vertices are in the attack graph) * * @param root the DOM element * @param attackGraph the corresponding attack graph */ public void loadFromDomElementAndAttackGraph(Element root, AttackGraph attackGraph) { Element scoringElement = root.getChild("scoring"); if (scoringElement != null) { this.scoring = Double.parseDouble(scoringElement.getText()); } /* Add all the arcs */ Element arcs_element = root.getChild("arcs"); if (arcs_element != null) { List<Element> arcs = arcs_element.getChildren("arc"); for (Element arc_element : arcs) { //All arcs Element src_element = arc_element.getChild("dst"); //MULVAL XML FILES INVERSE DESTINATION AND DESTINATION Element dst_element = arc_element.getChild("src"); //MULVAL XML FILES INVERSE DESTINATION AND DESTINATION if (src_element != null && dst_element != null) { Vertex destination = getVertexFromAttackGraph((int) Double.parseDouble(dst_element.getText()), attackGraph); Vertex source = getVertexFromAttackGraph((int) Double.parseDouble(src_element.getText()), attackGraph); Arc arc = new Arc(source, destination); this.arcs.add(arc); } } } } /** * @param vertexID the vertex number * @param attackGraph an attack graph * @return the existing vertex if it is already in the attack path else add this vertex from the attack graph */ public Vertex getVertexFromAttackGraph(int vertexID, AttackGraph attackGraph) { if (this.vertices.containsKey(vertexID)) return this.vertices.get(vertexID); else { Vertex result = attackGraph.getExistingOrCreateVertex(vertexID); this.vertices.put(vertexID, result); return result; } } /** * @return the dom element corresponding to this attack path XML file */ public Element toDomXMLElement() { Element root = new Element("attack_path"); Element scoringElement = new Element("scoring"); scoringElement.setText(this.scoring + ""); root.addContent(scoringElement); //arcs Element arcsElement = new Element("arcs"); root.addContent(arcsElement); for (Arc arc : arcs) { Element arcElement = new Element("arc"); arcsElement.addContent(arcElement); Element srcElement = new Element("src"); srcElement.setText(arc.destination.id + ""); arcElement.addContent(srcElement); Element dstElement = new Element("dst"); dstElement.setText(arc.source.id + ""); arcElement.addContent(dstElement); } return root; } @Override public String toString() { String result = "AttackPath : "; for (int i : this.vertices.keySet()) { result += this.vertices.get(i).id + " - "; } return result; } /** * @param informationSystem the information system * @return The topology Graph associated to this attack path * @throws Exception */ public InformationSystemGraph getRelatedTopologyGraph(InformationSystem informationSystem) throws Exception { InformationSystemGraph result = super.getRelatedTopologyGraph(informationSystem); result.addTarget(this.getGoal().getRelatedMachine(informationSystem)); return result; } @Override public AttackPath clone() throws CloneNotSupportedException { AttackPath copie = (AttackPath) super.clone(); if (this.goal != null) copie.goal = copie.vertices.get(this.goal.id); return copie; } public static class AttackPathComparator implements Comparator<AttackPath> { public int compare(AttackPath a1, AttackPath a2) { //descending order if (a1.scoring == a2.scoring) { return 0; } else if (a1.scoring < a2.scoring) { return 1; } else { return -1; } } } /** * Comparator to compare deployable remediation according to their cost */ private class DeployableRemediationComparator implements Comparator<DeployableRemediation> { public int compare(DeployableRemediation dr1, DeployableRemediation dr2) { if (dr1.getCost() == dr2.getCost()) { return 0; } else if (dr1.getCost() < dr2.getCost()) { return -1; } else { return 1; } } } }