diff --git a/.gitignore b/.gitignore index 1dbc687..688c77e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ __pycache__/ *.py[cod] *$py.class +.idea/ # C extensions *.so diff --git a/LICENSE b/LICENSE index 8dada3e..d645695 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,4 @@ + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -178,7 +179,7 @@ APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" + boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a @@ -186,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..f83dcf5 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,15 @@ +include LICENSE requirements.txt requirements-extra.txt + +recursive-exclude * __pycache__ +recursive-exclude * *.pyc +recursive-exclude * *.pyo +recursive-exclude * *.orig +recursive-exclude * .DS_Store +global-exclude __pycache__/* +global-exclude .deps/* +global-exclude *.so +global-exclude *.pyd +global-exclude *.pyc +global-exclude .git* +global-exclude .DS_Store +global-exclude .mailmap \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 1cedae2..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# zap-api-python -OWASP ZAP Python API diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..7883855 --- /dev/null +++ b/README.rst @@ -0,0 +1,26 @@ +# zap-api-python + + +# Install + +.. code-block:: bash + + # pip install python-owasp-zap + +# Basic usage + +.. code-block:: python + + from zap import ZAPv24 + + zap = ZAPv24() + + print('Spidering target %s' % target) + scanid = zap.spider.scan(target) + + # Give the Spider a chance to start + time.sleep(2) + + while int(zap.spider.status(scanid)) < 100: + print('Spider progress %: ' + zap.spider.status(scanid)) + time.sleep(2) diff --git a/examples/usage_example.py b/examples/usage_example.py new file mode 100644 index 0000000..ac7193a --- /dev/null +++ b/examples/usage_example.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +from __future__ import print_function + +import time + +from pprint import pprint +from zap import ZAPv24 + +target = 'http://127.0.0.1' + +# By default ZAP API client will connect to port 8080 +zap = ZAPv24() + +# Or, you can configure your own IP/Port +# zap_9090 = ZAPv24(proxies={'http': '127.0.0.1:9090', 'https': '127.0.0.1:9090'}) + +# Use the line below if ZAP is not listening on port 8080, for example, if listening on port 8090 +# zap = ZAPv24(proxies={'http': 'http://127.0.0.1:8090', 'https': 'http://127.0.0.1:8090'}) + +# do stuff +print('Accessing target %s' % target) + +# try have a unique enough session... +zap.urlopen(target) + +# Give the sites tree a chance to get updated +time.sleep(2) + +print('Spidering target %s' % target) +scanid = zap.spider.scan(target) + +# Give the Spider a chance to start +time.sleep(2) + +while int(zap.spider.status(scanid)) < 100: + print('Spider progress %: ' + zap.spider.status(scanid)) + time.sleep(2) + +print('Spider completed') + +# Give the passive scanner a chance to finish +time.sleep(5) + +print('Scanning target %s' % target) + +scanid = zap.ascan.scan(target) +while int(zap.ascan.status(scanid)) < 100: + print('Scan progress %: ' + zap.ascan.status(scanid)) + time.sleep(5) + +print('Scan completed') + +# Report the results + +print('Hosts: ' + ', '.join(zap.core.hosts)) +print('Alerts: ') +pprint((zap.core.alerts())) diff --git a/requirements-extra.txt b/requirements-extra.txt new file mode 100644 index 0000000..53072c2 --- /dev/null +++ b/requirements-extra.txt @@ -0,0 +1 @@ +ujson \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1e74064 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +six +requests +requests-cache \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..2a65bb8 --- /dev/null +++ b/setup.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python + +""" +Standard build script. +""" + +from __future__ import print_function + +from os.path import dirname, join + +try: + from setuptools import setup, find_packages +except ImportError: + print("You must have setuptools installed to use setup.py. Exiting...") + raise SystemExit(1) + +__docformat__ = 'restructuredtext' + +# Import requirements +with open(join(dirname(__file__), 'requirements.txt')) as f: + required = f.read().splitlines() + +# Import extra requirements +with open(join(dirname(__file__), 'requirements-extra.txt')) as f: + extra_required = f.read().splitlines() + +setup( + name="python-owasp-zap", + version="1.0.0", + description="OWASP ZAP API client. Supported versions: 2.4", + install_requires=required, + extras_require={ + 'performance': extra_required, + }, + long_description="OWASP Zed Attack Proxy API python client", + author="ZAP development team", + author_email='', + url="https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project", + download_url="https://github.com/zaproxy/zap-api-python/archive/master.zip", + platforms=['any'], + license="ASL2.0", + packages=find_packages(), + classifiers=[ + 'License :: OSI Approved :: Apache Software License', + 'Topic :: Security', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python'], +) diff --git a/zap/__init__.py b/zap/__init__.py new file mode 100644 index 0000000..8a67977 --- /dev/null +++ b/zap/__init__.py @@ -0,0 +1,29 @@ +# Zed Attack Proxy (ZAP) and its related class files. +# +# ZAP is an HTTP/HTTPS proxy for assessing web application security. +# +# Copyright 2016 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. + +""" +Client implementation for using the ZAP pentesting proxy remotely. +""" + +from .zap_24 import ZAPv24 + +# In next releases It will be available more version includes, like: +# from .zap_25 import ZAPv25 + + +__all__ = ["ZAPv24"] diff --git a/zap/base.py b/zap/base.py new file mode 100644 index 0000000..1bb0f76 --- /dev/null +++ b/zap/base.py @@ -0,0 +1,114 @@ +# Zed Attack Proxy (ZAP) and its related class files. +# +# ZAP is an HTTP/HTTPS proxy for assessing web application security. +# +# Copyright 2016 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. + +""" +Client implementation for using the ZAP pentesting proxy remotely. +""" + +try: + # High performance json library + import ujson as json +except ImportError: + import json + +import six +import requests +import requests_cache +requests_cache.install_cache('zap_cache', backend="memory") + +# Improving Python 2 & 3 compatibility +if six.PY2: + from urllib import urlencode, urlopen + from urlparse import urlparse, urljoin +else: + from urllib.parse import urlparse, urlencode, urljoin + from urllib.request import urlopen + + +class _ZAP(object): + """ + Client API implementation for integrating with ZAP v2. + """ + + def _expect_ok(self, json_data): + """ + Checks that we have an OK response, else raises an exception. + + :param json_data: the json data to look at. + :type json_data: json + """ + if isinstance(json_data, list) and json_data[0] == u'OK': + return json_data + + raise ZapError(*json_data.values()) + + def urlopen(self, *args, **kwargs): + """ + Opens a url forcing the proxies to be used. + + :param args: all non-keyword arguments. + :type args: list() + + :param kwarg: all non-keyword arguments. + :type kwarg: dict() + """ + # return urlopen(*args, **kwargs).read() + return requests.get(*args, proxies=self.__proxies).text + + def status_code(self, *args, **kwargs): + """ + Open a url forcing the proxies to be used. + + :param args: all non-keyword arguments. + :type args: list() + + :param kwarg: all non-keyword arguments. + :type kwarg: dict() + """ + # return urlopen(*args, **kwargs).getcode() + return requests.get(*args, proxies=self.__proxies).status_code + + def _request(self, url, get=None): + """ + Shortcut for a GET request. + + :param url: the url to GET at. + :type url: str + + :param get: the dictionary to turn into GET variables. + :type get: dict(str:str) + """ + if get is None: + get = {} + + return json.loads(self.urlopen("%s?%s" % (url, urlencode(get)))) + + def _request_other(self, url, get=None): + """ + Shortcut for an API OTHER GET request. + + :param url: the url to GET at. + :type url: str + + :param get: the dictionary to turn into GET variables. + :type get: dict(str:str) + """ + if get is None: + get = {} + + return self.urlopen("%s?%s" % (url, urlencode(get))) diff --git a/zap/zap_24/__init__.py b/zap/zap_24/__init__.py new file mode 100644 index 0000000..b7dbe74 --- /dev/null +++ b/zap/zap_24/__init__.py @@ -0,0 +1,99 @@ +# Zed Attack Proxy (ZAP) and its related class files. +# +# ZAP is an HTTP/HTTPS proxy for assessing web application security. +# +# Copyright 2016 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. + +""" +Client implementation for using the ZAP pentesting proxy remotely. +""" + +from ..base import _ZAP + + +class ZAPv24(_ZAP): + """ + Client API implementation for integrating with ZAP v2. + """ + + # base JSON api url + base = 'http://zap/JSON/' + # base OTHER api url + base_other = 'http://zap/OTHER/' + + def __init__(self, proxies=None): + """ + Creates an instance of the ZAP api client. + + Example: + >>> z=ZAPv24() + + Example with custom proxies + >>> my_proxies = {'http': 'http://10.0.1.1:9090', 'https': 'http://10.0.1.1:9090'} + >>> z=ZAPv24(proxies=my_proxies) + + :param proxies: Dict with the scheme as key and PROXY url as value + :type proxies: dict(str:str) + + """ + from .acsrf import acsrf + from .ascan import ascan + from .ajaxSpider import ajaxSpider + from .authentication import authentication + from .autoupdate import autoupdate + from .brk import brk + from .context import context + from .core import core + from .forcedUser import forcedUser + from .httpSessions import httpSessions + from .importLogFiles import importLogFiles + from .params import params + from .pnh import pnh + from .pscan import pscan + from .reveal import reveal + from .script import script + from .search import search + from .selenium import selenium + from .sessionManagement import sessionManagement + from .spider import spider + from .users import users + + if proxies is None: + # Set default + proxies = {'http': 'http://127.0.0.1:8080', + 'https': 'http://127.0.0.1:8080'} + self.__proxies = proxies + + self.acsrf = acsrf(self) + self.ajaxSpider = ajaxSpider(self) + self.ascan = ascan(self) + self.authentication = authentication(self) + self.autoupdate = autoupdate(self) + self.brk = brk(self) + self.context = context(self) + self.core = core(self) + self.forcedUser = forcedUser(self) + self.httpsessions = httpSessions(self) + self.importLogFiles = importLogFiles(self) + self.params = params(self) + self.pnh = pnh(self) + self.pscan = pscan(self) + self.reveal = reveal(self) + self.script = script(self) + self.search = search(self) + self.selenium = selenium(self) + self.sessionManagement = sessionManagement(self) + self.spider = spider(self) + self.users = users(self)