/*
* Zed Attack Proxy (ZAP) and its related class files.
*
* ZAP is an HTTP/HTTPS proxy for assessing web application security.
*
* 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.zap.extension.ascan;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.log4j.Logger;
import org.parosproxy.paros.control.Control;
import org.parosproxy.paros.core.scanner.Alert;
import org.parosproxy.paros.core.scanner.ScannerParam;
import org.parosproxy.paros.model.Session;
import org.zaproxy.zap.extension.alert.ExtensionAlert;
import org.zaproxy.zap.extension.ruleconfig.ExtensionRuleConfig;
import org.zaproxy.zap.extension.ruleconfig.RuleConfigParam;
import org.zaproxy.zap.extension.script.ScriptCollection;
import org.zaproxy.zap.model.ScanController;
import org.zaproxy.zap.model.Target;
import org.zaproxy.zap.model.TechSet;
import org.zaproxy.zap.users.User;
public class ActiveScanController implements ScanController<ActiveScan> {
private ExtensionActiveScan extension;
private static final Logger logger = Logger.getLogger(ActiveScanController.class);
private ExtensionAlert extAlert = null;
/**
* The {@code Lock} for exclusive access of instance variables related to multiple active scans.
*
* @see #activeScanMap
* @see #scanIdCounter
*/
private final Lock activeScansLock;
/**
* The counter used to give an unique ID to active scans.
* <p>
* <strong>Note:</strong> All accesses (both write and read) should be done while holding the {@code Lock}
* {@code activeScansLock}.
* </p>
*
* @see #activeScansLock
* @see #startScan(String, Target, User, Object[])
*/
private int scanIdCounter;
/**
* A map that contains all {@code ActiveScan}s created (and not yet removed). Used to control (i.e. pause/resume and
* stop) the multiple active scans and get its results. The instance variable is never {@code null}. The map key is the ID
* of the scan.
* <p>
* <strong>Note:</strong> All accesses (both write and read) should be done while holding the {@code Lock}
* {@code activeScansLock}.
* </p>
*
* @see #activeScansLock
* @see #startScan(String, Target, User, Object[])
* @see #scanIdCounter
*/
private Map<Integer, ActiveScan> activeScanMap;
/**
* An ordered list of all of the {@code ActiveScan}s created (and not yet removed). Used to get provide the 'last'
* scan for client using the 'old' API that didnt support concurrent scans.
*/
private List<ActiveScan> activeScanList;
public ActiveScanController (ExtensionActiveScan extension) {
this.activeScansLock = new ReentrantLock();
this.extension = extension;
this.activeScanMap = new HashMap<>();
this.activeScanList = new ArrayList<ActiveScan>();
}
public void setExtAlert(ExtensionAlert extAlert) {
this.extAlert = extAlert;
}
@Override
public int startScan(String name, Target target, User user, Object[] contextSpecificObjects) {
activeScansLock.lock();
try {
int id = this.scanIdCounter++;
RuleConfigParam ruleConfigParam = null;
ExtensionRuleConfig extRC =
Control.getSingleton().getExtensionLoader().getExtension(ExtensionRuleConfig.class);
if (extRC != null) {
ruleConfigParam = extRC.getRuleConfigParam();
}
ActiveScan ascan = new ActiveScan(name, extension.getScannerParam(),
extension.getModel().getOptionsParam().getConnectionParam(),
null, ruleConfigParam) {
@Override
public void alertFound(Alert alert) {
alert.setSource(Alert.Source.ACTIVE);
if (extAlert!= null) {
extAlert.alertFound(alert, null);
}
super.alertFound(alert);
}
};
Session session = extension.getModel().getSession();
List<String> excludeList = new ArrayList<>();
excludeList.addAll(extension.getExcludeList());
excludeList.addAll(session.getExcludeFromScanRegexs());
excludeList.addAll(session.getGlobalExcludeURLRegexs());
ascan.setExcludeList(excludeList);
ScanPolicy policy = null;
ascan.setId(id);
ascan.setUser(user);
boolean techOverridden = false;
if (contextSpecificObjects != null) {
for (Object obj : contextSpecificObjects) {
if (obj instanceof ScannerParam) {
logger.debug("Setting custom scanner params");
ascan.setScannerParam((ScannerParam)obj);
} else if (obj instanceof ScanPolicy) {
policy = (ScanPolicy)obj;
logger.debug("Setting custom policy " + policy.getName());
ascan.setScanPolicy(policy);
} else if (obj instanceof TechSet) {
ascan.setTechSet((TechSet) obj);
techOverridden = true;
} else if (obj instanceof ScriptCollection) {
ascan.addScriptCollection((ScriptCollection)obj);
} else {
logger.error("Unexpected contextSpecificObject: " + obj.getClass().getCanonicalName());
}
}
}
if (policy == null) {
// use the default
policy = extension.getPolicyManager().getDefaultScanPolicy();
logger.debug("Setting default policy " + policy.getName());
ascan.setScanPolicy(policy);
}
if (!techOverridden && target.getContext() != null) {
ascan.setTechSet(target.getContext().getTechSet());
}
this.activeScanMap.put(id, ascan);
this.activeScanList.add(ascan);
ascan.start(target);
return id;
} finally {
activeScansLock.unlock();
}
}
public int registerScan(ActiveScan ascan) {
activeScansLock.lock();
try {
int id = this.scanIdCounter++;
ascan.setScanId(id);
this.activeScanMap.put(id, ascan);
this.activeScanList.add(ascan);
return id;
} finally {
activeScansLock.unlock();
}
}
@Override
public ActiveScan getScan(int id) {
return this.activeScanMap.get(id);
}
@Override
public ActiveScan getLastScan() {
activeScansLock.lock();
try {
if (activeScanList.size() == 0) {
return null;
}
return activeScanList.get(activeScanList.size()-1);
} finally {
activeScansLock.unlock();
}
}
@Override
public List<ActiveScan> getAllScans() {
List<ActiveScan> list = new ArrayList<ActiveScan>();
activeScansLock.lock();
try {
for (ActiveScan scan : activeScanList) {
list.add(scan);
}
return list;
} finally {
activeScansLock.unlock();
}
}
@Override
public List<ActiveScan> getActiveScans() {
List<ActiveScan> list = new ArrayList<ActiveScan>();
activeScansLock.lock();
try {
for (ActiveScan scan : activeScanList) {
if (!scan.isStopped()) {
list.add(scan);
}
}
return list;
} finally {
activeScansLock.unlock();
}
}
@Override
public ActiveScan removeScan(int id) {
activeScansLock.lock();
try {
ActiveScan ascan = this.activeScanMap.get(id);
if (! activeScanMap.containsKey(id)) {
//throw new IllegalArgumentException("Unknown id " + id);
return null;
}
ascan.stopScan();
activeScanMap.remove(id);
activeScanList.remove(ascan);
return ascan;
} finally {
activeScansLock.unlock();
}
}
public int getTotalNumberScans() {
return activeScanMap.size();
}
@Override
public void stopAllScans() {
activeScansLock.lock();
try {
for (ActiveScan scan : activeScanMap.values()) {
scan.stopScan();
}
} finally {
activeScansLock.unlock();
}
}
@Override
public void pauseAllScans() {
activeScansLock.lock();
try {
for (ActiveScan scan : activeScanMap.values()) {
scan.pauseScan();
}
} finally {
activeScansLock.unlock();
}
}
@Override
public void resumeAllScans() {
activeScansLock.lock();
try {
for (ActiveScan scan : activeScanMap.values()) {
scan.resumeScan();
}
} finally {
activeScansLock.unlock();
}
}
@Override
public int removeAllScans() {
activeScansLock.lock();
try {
int count = 0;
for (Iterator<ActiveScan> it = activeScanMap.values().iterator(); it.hasNext();) {
ActiveScan ascan = it.next();
ascan.stopScan();
it.remove();
activeScanList.remove(ascan);
count++;
}
return count;
} finally {
activeScansLock.unlock();
}
}
@Override
public int removeFinishedScans() {
activeScansLock.lock();
try {
int count = 0;
for (Iterator<ActiveScan> it = activeScanMap.values().iterator(); it.hasNext();) {
ActiveScan ascan = it.next();
if (ascan.isStopped()) {
ascan.stopScan();
it.remove();
activeScanList.remove(ascan);
count ++;
}
}
return count;
} finally {
activeScansLock.unlock();
}
}
@Override
public void stopScan(int id) {
activeScansLock.lock();
try {
if (this.activeScanMap.containsKey(id)) {
this.activeScanMap.get(id).stopScan();
}
} finally {
activeScansLock.unlock();
}
}
@Override
public void pauseScan(int id) {
activeScansLock.lock();
try {
if (this.activeScanMap.containsKey(id)) {
this.activeScanMap.get(id).pauseScan();
}
} finally {
activeScansLock.unlock();
}
}
@Override
public void resumeScan(int id) {
activeScansLock.lock();
try {
if (this.activeScanMap.containsKey(id)) {
this.activeScanMap.get(id).resumeScan();
}
} finally {
activeScansLock.unlock();
}
}
public void reset() {
this.removeAllScans();
activeScansLock.lock();
try {
this.scanIdCounter = 0;
} finally {
activeScansLock.unlock();
}
}
}