Skip to content

Commit e1e19f5

Browse files
authored
Merge pull request #243 from Prince-Mendiratta/main
Targeted script to check for DMARC / SPF policies
2 parents 481660a + 22165de commit e1e19f5

File tree

2 files changed

+171
-0
lines changed

2 files changed

+171
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
99
- targeted/cve-2021-22214.js > A targeted script to check for Unauthorised SSRF on GitLab - CVE 2021-22214
1010
- httpsender/full-session-n-csrf-nashorn.js > full session and csrf token management.
1111
- httpfuzzerprocessor/unexpected_responses.js > compare response codes to a (pass/fail) regex and generate alerts
12+
- targeted/dns-email-spoofing > Check if DMARC / SPF policies are configured on a domain.
1213

1314
### Changed
1415
- Update links in READMEs.

targeted/dns-email-spoofing.js

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
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

Comments
 (0)