diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c5ec24..cdeb4e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added +- Add the APIs of the following add-ons: + - Client Side Integration version 0.20.0; + - Postman Support version 0.7.0. + +### Changed +- Update core APIs for 2.17. +- Update the APIs of the following add-ons: + - Automation Framework version 0.58.0; + - OpenAPI Support version 48; + - Passive Scanner version 0.6.0; + - Selenium version 15.43.0; + - Spider version 0.18.0. + ### Fixed - Ensure `requests.Session` is closed to prevent lingering TCP connections. diff --git a/src/zapv2/__init__.py b/src/zapv2/__init__.py index d61521a..21505b4 100644 --- a/src/zapv2/__init__.py +++ b/src/zapv2/__init__.py @@ -36,6 +36,8 @@ from .automation import automation from .autoupdate import autoupdate from .brk import brk +from .client import client +from .clientSpider import clientSpider from .context import context from .core import core from .custompayloads import custompayloads @@ -49,6 +51,7 @@ from .openapi import openapi from .params import params from .pnh import pnh +from .postman import postman from .pscan import pscan from .replacer import replacer from .reports import reports @@ -103,6 +106,8 @@ def __init__(self, proxies=None, apikey=None, validate_status_code=False): self.automation = automation(self) self.autoupdate = autoupdate(self) self.brk = brk(self) + self.client = client(self) + self.clientSpider = clientSpider(self) self.context = context(self) self.core = core(self) self.custompayloads = custompayloads(self) @@ -116,6 +121,7 @@ def __init__(self, proxies=None, apikey=None, validate_status_code=False): self.openapi = openapi(self) self.params = params(self) self.pnh = pnh(self) + self.postman = postman(self) self.pscan = pscan(self) self.replacer = replacer(self) self.reports = reports(self) diff --git a/src/zapv2/alert.py b/src/zapv2/alert.py index e14d540..9008aa7 100644 --- a/src/zapv2/alert.py +++ b/src/zapv2/alert.py @@ -33,7 +33,7 @@ def alert(self, id): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'alert/view/alert/', {'id': id}))) - def alerts(self, baseurl=None, start=None, count=None, riskid=None, contextname=None): + def alerts(self, baseurl=None, start=None, count=None, riskid=None, contextname=None, falsepositive=None): """ Gets the alerts raised by ZAP, optionally filtering by URL or riskId, and paginating with 'start' position and 'count' of alerts """ @@ -48,6 +48,8 @@ def alerts(self, baseurl=None, start=None, count=None, riskid=None, contextname= params['riskId'] = riskid if contextname is not None: params['contextName'] = contextname + if falsepositive is not None: + params['falsePositive'] = falsepositive return six.next(six.itervalues(self.zap._request(self.zap.base + 'alert/view/alerts/', params))) def alerts_summary(self, baseurl=None): diff --git a/src/zapv2/alertFilter.py b/src/zapv2/alertFilter.py index d380c1e..062ac09 100644 --- a/src/zapv2/alertFilter.py +++ b/src/zapv2/alertFilter.py @@ -44,7 +44,7 @@ def global_alert_filter_list(self): def add_alert_filter(self, contextid, ruleid, newlevel, url=None, urlisregex=None, parameter=None, enabled=None, parameterisregex=None, attack=None, attackisregex=None, evidence=None, evidenceisregex=None, methods=None, apikey=''): """ - Adds a new alert filter for the context with the given ID. + Adds a new alert filter for the context with the given ID. This component is optional and therefore the API will only work if it is installed """ params = {'contextId': contextid, 'ruleId': ruleid, 'newLevel': newlevel} @@ -100,7 +100,7 @@ def remove_alert_filter(self, contextid, ruleid, newlevel, url=None, urlisregex= def add_global_alert_filter(self, ruleid, newlevel, url=None, urlisregex=None, parameter=None, enabled=None, parameterisregex=None, attack=None, attackisregex=None, evidence=None, evidenceisregex=None, methods=None, apikey=''): """ - Adds a new global alert filter. + Adds a new global alert filter. This component is optional and therefore the API will only work if it is installed """ params = {'ruleId': ruleid, 'newLevel': newlevel} diff --git a/src/zapv2/ascan.py b/src/zapv2/ascan.py index 3c5aa2d..bc399be 100644 --- a/src/zapv2/ascan.py +++ b/src/zapv2/ascan.py @@ -247,6 +247,13 @@ def option_encode_cookie_values(self): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'ascan/view/optionEncodeCookieValues/'))) + @property + def option_exclude_anti_csrf_tokens(self): + """ + Tells whether or not the active scanner should exclude anti-csrf tokens from the scan. + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'ascan/view/optionExcludeAntiCsrfTokens/'))) + @property def option_inject_plugin_id_in_header(self): """ @@ -254,6 +261,13 @@ def option_inject_plugin_id_in_header(self): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'ascan/view/optionInjectPluginIdInHeader/'))) + @property + def option_persist_temporary_messages(self): + """ + Tells whether or not the temporary HTTP messages sent while active scanning should be persisted. + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'ascan/view/optionPersistTemporaryMessages/'))) + @property def option_prompt_in_attack_mode(self): """ @@ -585,6 +599,12 @@ def set_option_encode_cookie_values(self, boolean, apikey=''): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'ascan/action/setOptionEncodeCookieValues/', {'Boolean': boolean}))) + def set_option_exclude_anti_csrf_tokens(self, boolean, apikey=''): + """ + Sets whether or not the active scanner should exclude anti-csrf tokens from the scan. + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'ascan/action/setOptionExcludeAntiCsrfTokens/', {'Boolean': boolean}))) + def set_option_handle_anti_csrf_tokens(self, boolean, apikey=''): """ @@ -639,6 +659,12 @@ def set_option_max_scans_in_ui(self, integer, apikey=''): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'ascan/action/setOptionMaxScansInUI/', {'Integer': integer}))) + def set_option_persist_temporary_messages(self, boolean, apikey=''): + """ + Sets whether or not the temporary HTTP messages sent while active scanning should be persisted. + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'ascan/action/setOptionPersistTemporaryMessages/', {'Boolean': boolean}))) + def set_option_prompt_in_attack_mode(self, boolean, apikey=''): """ diff --git a/src/zapv2/automation.py b/src/zapv2/automation.py index 1f67906..7380c1c 100644 --- a/src/zapv2/automation.py +++ b/src/zapv2/automation.py @@ -29,18 +29,28 @@ def __init__(self, zap): def plan_progress(self, planid): """ + Returns the progress details for the specified planId This component is optional and therefore the API will only work if it is installed """ return (self.zap._request(self.zap.base + 'automation/view/planProgress/', {'planId': planid})) def run_plan(self, filepath, apikey=''): """ + Loads and asynchronously runs the plan in the specified file, returning a planId This component is optional and therefore the API will only work if it is installed """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'automation/action/runPlan/', {'filePath': filepath}))) + def stop_plan(self, planid, apikey=''): + """ + Stops the running plan identified by the planId + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'automation/action/stopPlan/', {'planId': planid}))) + def end_delay_job(self, apikey=''): """ + Ends the currently running delay job, if any This component is optional and therefore the API will only work if it is installed """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'automation/action/endDelayJob/', {}))) diff --git a/src/zapv2/client.py b/src/zapv2/client.py new file mode 100644 index 0000000..64bbd87 --- /dev/null +++ b/src/zapv2/client.py @@ -0,0 +1,59 @@ +# Zed Attack Proxy (ZAP) and its related class files. +# +# ZAP is an HTTP/HTTPS proxy for assessing web application security. +# +# Copyright 2025 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. +""" +This file was automatically generated. +""" + +import six + + +class client(object): + + def __init__(self, zap): + self.zap = zap + + def report_object(self, objectjson, apikey=''): + """ + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'client/action/reportObject/', {'objectJson': objectjson}))) + + def report_event(self, eventjson, apikey=''): + """ + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'client/action/reportEvent/', {'eventJson': eventjson}))) + + def report_zest_statement(self, statementjson, apikey=''): + """ + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'client/action/reportZestStatement/', {'statementJson': statementjson}))) + + def report_zest_script(self, scriptjson, apikey=''): + """ + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'client/action/reportZestScript/', {'scriptJson': scriptjson}))) + + def export_client_map(self, pathyaml, apikey=''): + """ + Exports the Client Map to a file. + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'client/action/exportClientMap/', {'pathYaml': pathyaml}))) diff --git a/src/zapv2/clientSpider.py b/src/zapv2/clientSpider.py new file mode 100644 index 0000000..73026d2 --- /dev/null +++ b/src/zapv2/clientSpider.py @@ -0,0 +1,68 @@ +# Zed Attack Proxy (ZAP) and its related class files. +# +# ZAP is an HTTP/HTTPS proxy for assessing web application security. +# +# Copyright 2025 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. +""" +This file was automatically generated. +""" + +import six + + +class clientSpider(object): + + def __init__(self, zap): + self.zap = zap + + def status(self, scanid): + """ + Gets the status of a client spider scan. + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'clientSpider/view/status/', {'scanId': scanid}))) + + def scan(self, browser=None, url=None, contextname=None, username=None, subtreeonly=None, maxcrawldepth=None, pageloadtime=None, numberofbrowsers=None, scopecheck=None, apikey=''): + """ + Starts a client spider scan. + This component is optional and therefore the API will only work if it is installed + """ + params = {} + if browser is not None: + params['browser'] = browser + if url is not None: + params['url'] = url + if contextname is not None: + params['contextName'] = contextname + if username is not None: + params['userName'] = username + if subtreeonly is not None: + params['subtreeOnly'] = subtreeonly + if maxcrawldepth is not None: + params['maxCrawlDepth'] = maxcrawldepth + if pageloadtime is not None: + params['pageLoadTime'] = pageloadtime + if numberofbrowsers is not None: + params['numberOfBrowsers'] = numberofbrowsers + if scopecheck is not None: + params['scopeCheck'] = scopecheck + return six.next(six.itervalues(self.zap._request(self.zap.base + 'clientSpider/action/scan/', params))) + + def stop(self, scanid, apikey=''): + """ + Stops a client spider scan. + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'clientSpider/action/stop/', {'scanId': scanid}))) diff --git a/src/zapv2/openapi.py b/src/zapv2/openapi.py index 4e680ca..72533f4 100644 --- a/src/zapv2/openapi.py +++ b/src/zapv2/openapi.py @@ -27,7 +27,7 @@ class openapi(object): def __init__(self, zap): self.zap = zap - def import_file(self, file, target=None, contextid=None, apikey=''): + def import_file(self, file, target=None, contextid=None, userid=None, apikey=''): """ Imports an OpenAPI definition from a local file. This component is optional and therefore the API will only work if it is installed @@ -37,9 +37,11 @@ def import_file(self, file, target=None, contextid=None, apikey=''): params['target'] = target if contextid is not None: params['contextId'] = contextid + if userid is not None: + params['userId'] = userid return six.next(six.itervalues(self.zap._request(self.zap.base + 'openapi/action/importFile/', params))) - def import_url(self, url, hostoverride=None, contextid=None, apikey=''): + def import_url(self, url, hostoverride=None, contextid=None, userid=None, apikey=''): """ Imports an OpenAPI definition from a URL. This component is optional and therefore the API will only work if it is installed @@ -49,4 +51,6 @@ def import_url(self, url, hostoverride=None, contextid=None, apikey=''): params['hostOverride'] = hostoverride if contextid is not None: params['contextId'] = contextid + if userid is not None: + params['userId'] = userid return six.next(six.itervalues(self.zap._request(self.zap.base + 'openapi/action/importUrl/', params))) diff --git a/src/zapv2/postman.py b/src/zapv2/postman.py new file mode 100644 index 0000000..c368dd7 --- /dev/null +++ b/src/zapv2/postman.py @@ -0,0 +1,42 @@ +# Zed Attack Proxy (ZAP) and its related class files. +# +# ZAP is an HTTP/HTTPS proxy for assessing web application security. +# +# Copyright 2025 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. +""" +This file was automatically generated. +""" + +import six + + +class postman(object): + + def __init__(self, zap): + self.zap = zap + + def import_file(self, file, apikey=''): + """ + Imports a Postman collection from a file. + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'postman/action/importFile/', {'file': file}))) + + def import_url(self, url, apikey=''): + """ + Imports a Postman collection from a URL. + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'postman/action/importUrl/', {'url': url}))) diff --git a/src/zapv2/pscan.py b/src/zapv2/pscan.py index f9535e0..5a0a6ba 100644 --- a/src/zapv2/pscan.py +++ b/src/zapv2/pscan.py @@ -75,6 +75,14 @@ def max_alerts_per_rule(self): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'pscan/view/maxAlertsPerRule/'))) + @property + def max_body_size_in_bytes(self): + """ + Gets the maximum body size in bytes that the passive scanner will scan. + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'pscan/view/maxBodySizeInBytes/'))) + def set_enabled(self, enabled, apikey=''): """ Sets whether or not the passive scanning is enabled (Note: the enabled state is not persisted). @@ -131,6 +139,13 @@ def set_max_alerts_per_rule(self, maxalerts, apikey=''): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'pscan/action/setMaxAlertsPerRule/', {'maxAlerts': maxalerts}))) + def set_max_body_size_in_bytes(self, maxsize, apikey=''): + """ + Sets the maximum body size in bytes that the passive scanner will scan. + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'pscan/action/setMaxBodySizeInBytes/', {'maxSize': maxsize}))) + def disable_all_tags(self, apikey=''): """ Disables all passive scan tags. diff --git a/src/zapv2/selenium.py b/src/zapv2/selenium.py index c3fd78d..c5528ee 100644 --- a/src/zapv2/selenium.py +++ b/src/zapv2/selenium.py @@ -50,6 +50,22 @@ def option_chrome_driver_path(self): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'selenium/view/optionChromeDriverPath/'))) + @property + def option_edge_binary_path(self): + """ + Returns the current path to Edge binary + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'selenium/view/optionEdgeBinaryPath/'))) + + @property + def option_edge_driver_path(self): + """ + Returns the current path to EdgeDriver + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'selenium/view/optionEdgeDriverPath/'))) + @property def option_firefox_binary_path(self): """ @@ -115,6 +131,20 @@ def set_option_chrome_driver_path(self, string, apikey=''): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'selenium/action/setOptionChromeDriverPath/', {'String': string}))) + def set_option_edge_binary_path(self, string, apikey=''): + """ + Sets the current path to Edge binary + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'selenium/action/setOptionEdgeBinaryPath/', {'String': string}))) + + def set_option_edge_driver_path(self, string, apikey=''): + """ + Sets the current path to EdgeDriver + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'selenium/action/setOptionEdgeDriverPath/', {'String': string}))) + def set_option_firefox_binary_path(self, string, apikey=''): """ Sets the current path to Firefox binary diff --git a/src/zapv2/spider.py b/src/zapv2/spider.py index e91d569..22fbcba 100644 --- a/src/zapv2/spider.py +++ b/src/zapv2/spider.py @@ -147,7 +147,7 @@ def option_max_duration(self): @property def option_max_parse_size_bytes(self): """ - Gets the maximum size, in bytes, that a response might have to be parsed. + Gets the maximum size, in bytes, that a response might have to be parsed, or 0 for unlimited. This component is optional and therefore the API will only work if it is installed """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'spider/view/optionMaxParseSizeBytes/'))) @@ -160,14 +160,6 @@ def option_max_scans_in_ui(self): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'spider/view/optionMaxScansInUI/'))) - @property - def option_request_wait_time(self): - """ - - This component is optional and therefore the API will only work if it is installed - """ - return six.next(six.itervalues(self.zap._request(self.zap.base + 'spider/view/optionRequestWaitTime/'))) - @property def option_skip_url_string(self): """ @@ -208,6 +200,14 @@ def option_handle_o_data_parameters_visited(self): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'spider/view/optionHandleODataParametersVisited/'))) + @property + def option_logout_avoidance(self): + """ + Gets whether or not the spider should attempt to avoid logout related paths/functionality. + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'spider/view/optionLogoutAvoidance/'))) + @property def option_parse_comments(self): """ @@ -476,6 +476,13 @@ def set_option_handle_o_data_parameters_visited(self, boolean, apikey=''): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'spider/action/setOptionHandleODataParametersVisited/', {'Boolean': boolean}))) + def set_option_logout_avoidance(self, boolean, apikey=''): + """ + Sets whether or not the Spider should attempt to avoid logout related paths/functionality. + This component is optional and therefore the API will only work if it is installed + """ + return six.next(six.itervalues(self.zap._request(self.zap.base + 'spider/action/setOptionLogoutAvoidance/', {'Boolean': boolean}))) + def set_option_max_children(self, integer, apikey=''): """ Sets the maximum number of child nodes (per node) that can be crawled, 0 means no limit. @@ -566,13 +573,6 @@ def set_option_process_form(self, boolean, apikey=''): """ return six.next(six.itervalues(self.zap._request(self.zap.base + 'spider/action/setOptionProcessForm/', {'Boolean': boolean}))) - def set_option_request_wait_time(self, integer, apikey=''): - """ - - This component is optional and therefore the API will only work if it is installed - """ - return six.next(six.itervalues(self.zap._request(self.zap.base + 'spider/action/setOptionRequestWaitTime/', {'Integer': integer}))) - def set_option_send_referer_header(self, boolean, apikey=''): """ Sets whether or not the 'Referer' header should be sent while spidering.