Skip to content

Commit

Permalink
DNS plugin: currently reviews if there is SPF entry
Browse files Browse the repository at this point in the history
Signed-off-by: Miguel Angel Garcia <[email protected]>
  • Loading branch information
magmax committed Nov 5, 2023
1 parent c16fa91 commit 74cfc78
Show file tree
Hide file tree
Showing 17 changed files with 534 additions and 0 deletions.
10 changes: 10 additions & 0 deletions addOns/spf/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Changelog
All notable changes to this add-on will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## Unreleased

- First version.
- Detection if SPF record is not present
- Detection of more than one SPF record
2 changes: 2 additions & 0 deletions addOns/spf/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
version=0.0.1
release=false
21 changes: 21 additions & 0 deletions addOns/spf/spf.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
description = "Check SPF configuration"

zapAddOn {
addOnName.set("SPF")

manifest {
author.set("ZAP Dev Team")
}
}

crowdin {
configuration {
val resourcesPath = "org/zaproxy/addon/${zapAddOn.addOnId.get()}/resources/"
tokens.put("%messagesPath%", resourcesPath)
tokens.put("%helpPath%", resourcesPath)
}
}

dependencies {
testImplementation(project(":testutils"))
}
56 changes: 56 additions & 0 deletions addOns/spf/src/main/java/org/zaproxy/addon/spf/DNSClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2023 The ZAP Development Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.zaproxy.addon.spf;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import javax.naming.NamingEnumeration;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class DNSClient {
private static final Logger LOGGER = LogManager.getLogger(DNSClient.class);

public List<String> getTxtRecord(String host) {
Hashtable<String, String> env = new Hashtable<String, String>();
env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
ArrayList<String> result = new ArrayList<String>();

try {
DirContext dirContext = new InitialDirContext(env);
Attributes attrs = dirContext.getAttributes(host, new String[] {"TXT"});
Attribute attr = attrs.get("TXT");

NamingEnumeration<?> attrenum = attr.getAll();
while (attrenum.hasMore()) {
result.add(attrenum.next().toString());
}

} catch (javax.naming.NamingException e) {
LOGGER.debug("There was a problem getting the TXT record", e);
}
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2023 The ZAP Development Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.zaproxy.addon.spf;

import org.parosproxy.paros.Constant;
import org.parosproxy.paros.extension.ExtensionAdaptor;

/** An ZAP extension which performs DNS operations to get information or vulnerabilities. */
public class ExtensionSpfScanner extends ExtensionAdaptor {

// The name is public so that other extensions can access it
public static final String NAME = "ExtensionSpfScanner";

protected static final String PREFIX = "spfscanner";

public ExtensionSpfScanner() {
super(NAME);
setI18nPrefix(PREFIX);
}

@Override
public void init() {
super.init();
}

@Override
public String getDescription() {
return Constant.messages.getString(PREFIX + ".desc");
}
}
55 changes: 55 additions & 0 deletions addOns/spf/src/main/java/org/zaproxy/addon/spf/SPFCheckerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2023 The ZAP Development Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.zaproxy.addon.spf;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.util.ArrayList;
import org.junit.jupiter.api.Test;

class SPFCheckerTest {

@Test
void noTxtRecordsFindsNoSPFRecord() throws TooManyRecords {
ArrayList<String> records = new ArrayList<String>();
SPFParser sut = new SPFParser(records);
assertThat(sut.hasSpfRecord(), is(nullValue()));
}

@Test
void txtRecordsWithoutSPFFindsNoSPFRecord() throws TooManyRecords {
ArrayList<String> records = new ArrayList<String>();
records.add("foo-verification=foo");
records.add("bar-verification=bar");
SPFParser sut = new SPFParser(records);
assertThat(sut.hasSpfRecord(), is(nullValue()));
}

@Test
void severalSPFRecordsRaiseAnException() {
ArrayList<String> records = new ArrayList<String>();
records.add("v=spf1 -all");
records.add("v=spf1 -all");
assertThrows(TooManyRecords.class, () -> new SPFParser(records));
}
}
43 changes: 43 additions & 0 deletions addOns/spf/src/main/java/org/zaproxy/addon/spf/SPFParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2023 The ZAP Development Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.zaproxy.addon.spf;

import java.util.List;

public class SPFParser {

private String record = null;

public SPFParser(List<String> txtRecord) throws TooManyRecords {
for (String entry : txtRecord) {
if (!entry.startsWith("v=spf1 ")) {
continue;
}
if (record != null) {
throw new TooManyRecords();
}
record = entry;
}
}

public boolean hasSpfRecord() {
return record != null;
}
}
147 changes: 147 additions & 0 deletions addOns/spf/src/main/java/org/zaproxy/addon/spf/SpfScanner.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* Copyright 2023 The ZAP Development Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.zaproxy.addon.spf;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.httpclient.URIException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.core.scanner.AbstractAppPlugin;
import org.parosproxy.paros.core.scanner.Alert;
import org.parosproxy.paros.core.scanner.Category;
import org.parosproxy.paros.network.HttpMessage;

public class SpfScanner extends AbstractAppPlugin {
private static final int id = 90040; // assigned on ZAPROXY repository
private static final Logger LOGGER = LogManager.getLogger(SpfScanner.class);
private static final String MESSAGE_PREFIX = "spfscanner.";
private volatile boolean enabled = true;
private static List<String> reviewedDomains = new ArrayList<String>();

private String getConstantString(String key) {
return Constant.messages.getString(MESSAGE_PREFIX + key);
}

private String getHigherSubdomain(String host) {
String[] hostarray = host.split("\\.");
if (hostarray.length < 2) {
return null;
}
return String.join(".", Arrays.copyOfRange(hostarray, 1, hostarray.length));
}

@Override
public void scan() {
final HttpMessage originalMsg = getBaseMsg();
try {
String host = originalMsg.getRequestHeader().getURI().getHost();
DNSClient dns = new DNSClient();
SPFParser spf = findValidSpfRecord(host, dns);
if (spf == null) {
newAlert()
.setMessage(getBaseMsg())
.setRisk(Alert.RISK_INFO)
.setConfidence(Alert.CONFIDENCE_MEDIUM)
.setDescription(getConstantString("nospfrecord.description"))
.raise();
}
} catch (URIException e) {
LOGGER.debug("There was a problem getting the TXT records", e);
} catch (TooManyRecords e) {
newAlert()
.setMessage(getBaseMsg())
.setRisk(Alert.RISK_INFO)
.setConfidence(Alert.CONFIDENCE_MEDIUM)
.setDescription(getConstantString("toomanyspfrecords.description"))
.raise();
}
}

private SPFParser findValidSpfRecord(String host, DNSClient dns) throws TooManyRecords {
SPFParser spf = null;
while (host != null) {
if (hasBeenAlreadyAnalyzed(host)) {
return null;
}
markAsAnalyzed(host);
spf = new SPFParser(dns.getTxtRecord(host));
if (spf.hasSpfRecord()) {
break;
}
host = getHigherSubdomain(host);
}
return spf;
}

private void markAsAnalyzed(String host) {
reviewedDomains.add(host);
}

private boolean hasBeenAlreadyAnalyzed(String host) {
return reviewedDomains.contains(host);
}

@Override
public void setEnabled(boolean enabled) {
this.enabled = enabled;
if (enabled == false) {
// Reset the scanner
reviewedDomains.clear();
}
}

@Override
public boolean isEnabled() {
return enabled;
}

@Override
public int getId() {
return id;
}

@Override
public int getCategory() {
return Category.MISC;
}

@Override
public String getName() {
return getConstantString("scanner");
}

@Override
public String getDescription() {
return getConstantString("description");
}

@Override
public String getSolution() {
return getConstantString("solution");
}

@Override
public String getReference() {
return getConstantString("reference");
}
}
Loading

0 comments on commit 74cfc78

Please sign in to comment.