|
| 1 | +/** |
| 2 | + * Contributed by Astra Security (https://www.getastra.com/) |
| 3 | + * @author Prince Mendiratta <prince.mendiratta@getastra.com> |
| 4 | + */ |
| 5 | +var pluginid = 100031; |
| 6 | +var providerAddress = "dns.google"; |
| 7 | + |
| 8 | +var URI = Java.type("org.apache.commons.httpclient.URI"); |
| 9 | +var HttpSender = Java.type("org.parosproxy.paros.network.HttpSender"); |
| 10 | +var Model = Java.type("org.parosproxy.paros.model.Model"); |
| 11 | +var HistoryReference = Java.type("org.parosproxy.paros.model.HistoryReference"); |
| 12 | +var Control = Java.type("org.parosproxy.paros.control.Control"); |
| 13 | +var ExtensionAlert = Java.type("org.zaproxy.zap.extension.alert.ExtensionAlert"); |
| 14 | +var Alert = Java.type("org.parosproxy.paros.core.scanner.Alert"); |
| 15 | + |
| 16 | +var session = Model.getSingleton().getSession(); |
| 17 | +var connectionParams = Model.getSingleton().getOptionsParam().getConnectionParam(); |
| 18 | +var extLoader = Control.getSingleton().getExtensionLoader(); |
| 19 | + |
| 20 | +// Print statements using script name |
| 21 | +function logger() { |
| 22 | + print("[" + this["zap.script.name"] + "] " + arguments[0]); |
| 23 | +} |
| 24 | + |
| 25 | +/** |
| 26 | + * Check for SPF / DMARC policies on a website. |
| 27 | + * |
| 28 | + * A function which will be invoked against a specific "targeted" message. |
| 29 | + * |
| 30 | + * @param msg - the HTTP message being acted upon. This is an HttpMessage object. |
| 31 | + */ |
| 32 | +function invokeWith(msg) { |
| 33 | + |
| 34 | + var url = msg.getRequestHeader().getURI().toString(); |
| 35 | + // To check if script is running |
| 36 | + logger("Testing script against URL - " + url); |
| 37 | + |
| 38 | + // Regex to detect DMARC / SPF records |
| 39 | + var spfRegex = /^v=spf1.*/g; |
| 40 | + var dmarcRegex = /^v=DMARC1.*/g; |
| 41 | + |
| 42 | + testForPolicy(msg, "SPF", spfRegex, url); |
| 43 | + testForPolicy(msg, "DMARC", dmarcRegex, url); |
| 44 | + |
| 45 | + logger("Script run completed successfully."); |
| 46 | +} |
| 47 | + |
| 48 | +/** |
| 49 | + * Function that tests if a policy has been configured |
| 50 | + * @param {Object.<HttpMessage>} msg - The HttpMessage Object being scanned |
| 51 | + * @param {String} policy - The policy name |
| 52 | + * @param {RegExp} policyRegex - The regex expression for detecting the policy |
| 53 | + * @param {String} url - The URL against which script has been invoked |
| 54 | + */ |
| 55 | +function testForPolicy(msg, policy, policyRegex, url) { |
| 56 | + var newReq = msg.cloneRequest(); |
| 57 | + // Fetch TXT DNS records for root domain |
| 58 | + var fetchedTxtRecords = fetchRecords(newReq, policy); |
| 59 | + logger("Checking for presence of " + policy + " records."); |
| 60 | + checkIfPolicy(fetchedTxtRecords, policyRegex, policy, url, newReq); |
| 61 | +} |
| 62 | + |
| 63 | +/** |
| 64 | + * Checks if a specific DNS TXT record policy exists or not, |
| 65 | + * Raises an alert if the policy is not present |
| 66 | + * |
| 67 | + * @param {Object.object} txtRecords - The fetched DNS Records response |
| 68 | + * @param {RegExp} policyRegex - The regex expression for detecting the policy |
| 69 | + * @param {String} policyName - The policy name |
| 70 | + * @param {String} metaData - The extra meta data that will be sent in otherInfo field |
| 71 | + * @param {Object.<HttpMessage>} msg - The HttpMessage Object being scanned |
| 72 | + */ |
| 73 | +function checkIfPolicy(txtRecords, policyRegex, policyName, url, msg) { |
| 74 | + |
| 75 | + var cweId = 290; |
| 76 | + var wascId = 12; |
| 77 | + // All TXT records are under the "Answer" key in the response object |
| 78 | + var foundPolicy = checkForPolicy(txtRecords["Answer"], policyRegex); |
| 79 | + |
| 80 | + if (foundPolicy !== true) { |
| 81 | + var alertName = policyName + " Records not configured"; |
| 82 | + var alertSol = policyName + " Records should be configured properly on domain to prevent email spoofing. "; |
| 83 | + var alertDesc = "Phishing and email spam are the biggest opportunities for hackers to enter the network. " + |
| 84 | + "If a single user clicks on some malicious email attachment, it can compromise an entire enterprise with ransomware, " + |
| 85 | + "cryptojacking scripts, data leakages or privilege escalation exploits. This can be prevented with the help of " + |
| 86 | + policyName + " records."; |
| 87 | + raiseAlert( |
| 88 | + pluginid, |
| 89 | + 2, // risk: 0: info, 1: low, 2: medium, 3: high |
| 90 | + 3, // confidence: 0: falsePositive, 1: low, 2: medium, 3: high, 4: confirmed |
| 91 | + alertName, |
| 92 | + alertDesc, |
| 93 | + alertSol, |
| 94 | + cweId, |
| 95 | + wascId, |
| 96 | + msg, |
| 97 | + url |
| 98 | + ); |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | +/** |
| 103 | + * Function to fetch DNS TXT records over DoH |
| 104 | + * @param {Object.<HttpMessage>} msg - The HttpMessage Object being scanned |
| 105 | + * @param {String} policy - The policy name to fetch records for. |
| 106 | + * @return {Object.object} - The fetched records. |
| 107 | + */ |
| 108 | +function fetchRecords(msg, policy) { |
| 109 | + var domain = msg.getRequestHeader().getURI().getHost(); |
| 110 | + if (domain.startsWith("www")) { |
| 111 | + domain = domain.replace("www.", ""); |
| 112 | + } |
| 113 | + if (policy === "DMARC") { |
| 114 | + domain = "_dmarc." + domain; |
| 115 | + } |
| 116 | + var path = "/resolve?name=" + domain + "&type=TXT"; |
| 117 | + var targetUri = "https://" + providerAddress + path; |
| 118 | + var requestUri = new URI(targetUri, false); |
| 119 | + msg.getRequestHeader().setURI(requestUri); |
| 120 | + logger("Fetching TXT records for domain - " + domain); |
| 121 | + |
| 122 | + var sender = new HttpSender(connectionParams, true, 6); |
| 123 | + sender.sendAndReceive(msg); |
| 124 | + // Debugging |
| 125 | + // logger("Request Header -> " + msg.getRequestHeader().toString()) |
| 126 | + // logger("Request Body -> " + msg.getRequestBody().toString()) |
| 127 | + // logger("Response Header -> " + msg.getResponseHeader().toString()) |
| 128 | + // logger("Response Body -> " + msg.getResponseBody().toString()) |
| 129 | + |
| 130 | + var fetchedTxtRecords = JSON.parse(msg.getResponseBody().toString()); |
| 131 | + return fetchedTxtRecords; |
| 132 | +} |
| 133 | + |
| 134 | +/** |
| 135 | + * Function to check for a policy in TXT records |
| 136 | + * @param {Object.object} txtRecords - The fetched DNS Records response to test regex against |
| 137 | + * @param {RegExp} policyRegex - The regex expression for detecting the policy |
| 138 | + * @returns {boolean} - Return true if found record |
| 139 | + */ |
| 140 | +function checkForPolicy(txtRecords, policyRegex) { |
| 141 | + if (txtRecords === undefined) { |
| 142 | + logger("No TXT records found for the domain."); |
| 143 | + return false; |
| 144 | + } |
| 145 | + for (var txtRecord in txtRecords) { |
| 146 | + if (policyRegex.test(txtRecords[txtRecord]["data"])) { |
| 147 | + return true; |
| 148 | + } |
| 149 | + } |
| 150 | + return false; |
| 151 | +} |
| 152 | + |
| 153 | +/** |
| 154 | + * Raise an alert. |
| 155 | + * @see https://www.javadoc.io/doc/org.zaproxy/zap/latest/org/parosproxy/paros/core/scanner/Alert.html |
| 156 | + */ |
| 157 | +function raiseAlert(pluginid, alertRisk, alertConfidence, alertName, alertDesc, alertSol, cweId, wascId, msg, url) { |
| 158 | + var extensionAlert = extLoader.getExtension(ExtensionAlert.NAME); |
| 159 | + var ref = new HistoryReference(session, HistoryReference.TYPE_ZAP_USER, msg); |
| 160 | + |
| 161 | + var alert = new Alert(pluginid, alertRisk, alertConfidence, alertName); |
| 162 | + alert.setDescription(alertDesc); |
| 163 | + alert.setSolution(alertSol); |
| 164 | + alert.setCweId(cweId); |
| 165 | + alert.setWascId(wascId); |
| 166 | + alert.setMessage(msg); |
| 167 | + alert.setUri(url); |
| 168 | + |
| 169 | + extensionAlert.alertFound(alert, ref); |
| 170 | +} |
0 commit comments