├── conftest.py ├── robozap ├── __init__.py ├── requirements.txt ├── RoboZap.py └── robozap_keywords.html ├── .gitignore ├── setup.py ├── tests └── test_robozap.py └── README.md /conftest.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /robozap/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /robozap/requirements.txt: -------------------------------------------------------------------------------- 1 | python-owasp-zap-v2.4==0.0.14 2 | requests==2.18.4 3 | robotframework==3.0.4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | NmapTest.robot 2 | RoboNmap.py 3 | selenium-screenshot-1.png 4 | output.xml 5 | report.html 6 | log.html 7 | AZapTest.robot 8 | build/lib 9 | RoboJuice.robot 10 | *.png 11 | TestRobot.robot 12 | wecare.json 13 | zap_scan.xml 14 | flask_api.json 15 | FlaskApiTestBot.robot 16 | dist/ 17 | robozap/RoboZap.egg-info/ 18 | venv/* 19 | .vscode/* 20 | venv -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | from codecs import open 3 | from os import path 4 | 5 | here = path.abspath(path.dirname(__file__)) 6 | 7 | with open(path.join(here, 'README.md'), encoding='utf-8') as f: 8 | long_description = f.read() 9 | 10 | setup( 11 | name='RoboZap', 12 | version='1.3.1', 13 | packages=[''], 14 | package_dir={'': 'robozap'}, 15 | url='https://www.github.com/we45/RoboZap', 16 | license='MIT', 17 | author='we45', 18 | author_email='info@we45.com', 19 | description='Robot Framework Library for the OWASP ZAP Application Vulnerability Scanner' , 20 | install_requires=[ 21 | 'requests==2.18.4', 22 | 'python-owasp-zap-v2.4==0.0.14', 23 | 'robotframework==3.0.4', 24 | 'boto3==1.17.49' 25 | ], 26 | long_description = long_description, 27 | long_description_content_type='text/markdown' 28 | ) 29 | -------------------------------------------------------------------------------- /tests/test_robozap.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from robozap.RoboZap import RoboZap 3 | import time 4 | 5 | context = "" 6 | spider_id = "" 7 | scan_id = "" 8 | 9 | def test_start_gui_zap(): 10 | robo = RoboZap("http://127.0.0.1:8090/", "8090") 11 | robo.start_gui_zap("/Applications/ZAP_29.app/Contents/Java/") 12 | 13 | def test_zap_open_url(): 14 | robo = RoboZap("http://127.0.0.1:8090/", "8090") 15 | robo.zap_open_url('http://localhost:5050') 16 | 17 | def test_zap_define_context(): 18 | robo = RoboZap("http://127.0.0.1:8090/", "8090") 19 | global context 20 | context = robo.zap_define_context("test", "http://localhost:5050") 21 | print("context", context) 22 | 23 | def test_zap_spider(): 24 | robo = RoboZap("http://127.0.0.1:8090/", "8090") 25 | global spider_id 26 | spider_id = robo.zap_start_spider("test", 'http://localhost:5050') 27 | print(spider_id) 28 | 29 | def test_zap_spider_status(): 30 | robo = RoboZap("http://127.0.0.1:8090/", "8090") 31 | robo.zap_spider_status(spider_id) 32 | 33 | def test_zap_active_scan(): 34 | robo = RoboZap("http://127.0.0.1:8090/", "8090") 35 | global scan_id 36 | scan_id = robo.zap_start_ascan(context, "http://localhost:5050/") 37 | print("scan_id", scan_id) 38 | 39 | def test_zap_active_scan_status(): 40 | time.sleep(4) 41 | robo = RoboZap("http://127.0.0.1:8090/", "8090") 42 | robo.zap_scan_status(scan_id) 43 | 44 | 45 | def test_zap_export_report(): 46 | time.sleep(3) 47 | robo = RoboZap("http://127.0.0.1:8090/", "8090") 48 | robo.zap_export_report("/Users/abhaybhargav/Downloads/hello.json", "json", "Test Report", "Abhay Bhargav") 49 | 50 | 51 | 52 | def test_zap_shutdown(): 53 | robo = RoboZap("http://127.0.0.1:8090/", "8090") 54 | robo.zap_shutdown() 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RoboZap 2 | ======= 3 | Documentation for test library ``RoboZap``. 4 | 5 | Installing 6 | ---------- 7 | - Install RoboZap libraries into the virtualenv with `pip install RoboZap` 8 | 9 | Importing 10 | --------- 11 | Arguments: [proxy] 12 | 13 | ZAP Library can be imported with one argument 14 | 15 | Arguments: 16 | - ``proxy``: Proxy is required to initialize the ZAP Proxy at that location. Must include PortSpec 17 | - ``port``: Port is required to be set as a global/suite variable for the rest of the suite to access 18 | location 19 | 20 | 21 | Examples: 22 | 23 | | = Keyword Definition = | = Description = | 24 | 25 | `| Library `|` RoboZap | proxy| port | ` 26 | 27 | 28 | Major Keywords 29 | ================ 30 | 31 | Start Headless Zap 32 | ------------------ 33 | Arguments: [path] 34 | 35 | Start OWASP ZAP without a GUI 36 | 37 | Examples: 38 | 39 | `| start headless zap | path |` 40 | 41 | Start GUI Zap 42 | ------------------ 43 | Arguments: [path] 44 | 45 | Start OWASP ZAP without a GUI 46 | 47 | Examples: 48 | 49 | `| start gui zap | path |` 50 | 51 | Zap Define Context 52 | ------------------ 53 | Arguments: [contextname, url] 54 | 55 | Add Target to a context and use the context to perform all scanning/spidering 56 | operations 57 | 58 | Examples: 59 | 60 | `| zap define context | contextname | target |` 61 | 62 | Zap Open Url 63 | ------------ 64 | Arguments: [url] 65 | 66 | Invoke URLOpen with ZAP 67 | 68 | Examples: 69 | 70 | `| zap open url | target |` 71 | 72 | Zap Scan Status 73 | --------------- 74 | Arguments: [scan_id] 75 | 76 | Fetches the status for the spider id provided by the user 77 | 78 | Examples: 79 | 80 | `| zap scan status | scan_id |` 81 | 82 | Zap Shutdown 83 | ------------ 84 | Arguments: [] 85 | 86 | Shutdown process for ZAP Scanner 87 | 88 | Zap Spider Status 89 | ----------------- 90 | Arguments: [spider_id] 91 | 92 | Fetches the status for the spider id provided by the user 93 | Examples: 94 | `| zap spider status | spider_id |` 95 | 96 | Zap Start Ascan 97 | --------------- 98 | Arguments: [context, url, policy=Default Policy] 99 | 100 | Initiates ZAP Active Scan on the target url and context 101 | 102 | Examples: 103 | 104 | `| zap start ascan | context | url |` 105 | 106 | Zap Start Spider 107 | ---------------- 108 | Arguments: [target, url] 109 | 110 | Start ZAP Spider with ZAP's inbuilt spider mode 111 | 112 | Examples: 113 | 114 | `| zap start spider | target | url |` 115 | 116 | Zap Write To Json File 117 | ---------------------- 118 | Arguments: [scan_id] 119 | 120 | Fetches all the results from zap.core.alerts() and writes to json file. 121 | 122 | Examples: 123 | 124 | `| zap write to json | scan_id |` 125 | 126 | 127 | Zap Generate Report (Export Report Plugin) 128 | ---------------------- 129 | Arguments: [file_path, report_format, report_title, report_author] 130 | 131 | Uses the `Export Report` from ZAP to generate reports in multiple formats. 132 | - file_path: needs to be an absolute path and include the file name with extension. 133 | - format: can be `json|xml|xhtml|pdf|doc` 134 | - report title: Any title you deem fit for the exported report 135 | - report auhor: Any name you want for the author of the report 136 | 137 | Examples: 138 | 139 | `| zap export report | file_path | format | report title | report author` -------------------------------------------------------------------------------- /robozap/RoboZap.py: -------------------------------------------------------------------------------- 1 | import os 2 | from zapv2 import ZAPv2 as ZAP 3 | import time 4 | import subprocess 5 | from robot.api import logger 6 | import base64 7 | import uuid 8 | import json 9 | import requests 10 | from datetime import datetime 11 | import boto3 12 | 13 | 14 | class RoboZap(object): 15 | ROBOT_LIBRARY_SCOPE = "GLOBAL" 16 | 17 | def __init__(self, proxy, port): 18 | """ 19 | ZAP Library can be imported with one argument 20 | 21 | Arguments: 22 | - ``proxy``: Proxy is required to initialize the ZAP Proxy at that location. This MUST include the port specification as well 23 | - ``port``: This is a portspecification that will be used across the suite 24 | 25 | 26 | Examples: 27 | 28 | | = Keyword Definition = | = Description = | 29 | 30 | | Library `|` RoboZap | proxy | port | 31 | """ 32 | self.zap = ZAP(proxies={"http": proxy, "https": proxy}) 33 | self.port = port 34 | 35 | def start_headless_zap(self, path): 36 | """ 37 | Start OWASP ZAP without a GUI 38 | 39 | Examples: 40 | 41 | | start gui zap | path | port | 42 | 43 | """ 44 | try: 45 | cmd = path + "zap.sh -daemon -config api.disablekey=true -port {0}".format( 46 | self.port 47 | ) 48 | print(cmd) 49 | subprocess.Popen(cmd.split(" "), stdout=open(os.devnull, "w")) 50 | time.sleep(10) 51 | except IOError: 52 | print("ZAP Path is not configured correctly") 53 | 54 | def start_gui_zap(self, path): 55 | """ 56 | Start OWASP ZAP with a GUI 57 | 58 | Examples: 59 | 60 | | start gui zap | path | port | 61 | 62 | """ 63 | try: 64 | cmd = path + "zap.sh -config api.disablekey=true -port {0}".format( 65 | self.port 66 | ) 67 | print(cmd) 68 | subprocess.Popen(cmd.split(" "), stdout=open(os.devnull, "w")) 69 | time.sleep(10) 70 | except IOError: 71 | print("ZAP Path is not configured correctly") 72 | 73 | def zap_open_url(self, url): 74 | """ 75 | Invoke URLOpen with ZAP 76 | 77 | Examples: 78 | 79 | | zap open url | target | 80 | 81 | """ 82 | self.zap.urlopen(url) 83 | time.sleep(4) 84 | 85 | def zap_define_context(self, contextname, url): 86 | """ 87 | Add Target to a context and use the context to perform all scanning/spidering operations 88 | 89 | Examples: 90 | 91 | | zap define context | contextname | target | 92 | 93 | """ 94 | regex = "{0}.*".format(url) 95 | context_id = self.zap.context.new_context(contextname=contextname) 96 | time.sleep(1) 97 | self.zap.context.include_in_context(contextname, regex=regex) 98 | time.sleep(5) 99 | return context_id 100 | 101 | def zap_start_spider(self, target, url): 102 | """ 103 | Start ZAP Spider with ZAP's inbuilt spider mode 104 | 105 | Examples: 106 | 107 | | zap start spider | target | url | 108 | 109 | """ 110 | try: 111 | 112 | spider_id = self.zap.spider.scan(url=url, contextname=target) 113 | time.sleep(2) 114 | return spider_id 115 | except Exception as e: 116 | print((e.message)) 117 | 118 | def zap_spider_status(self, spider_id): 119 | """ 120 | Fetches the status for the spider id provided by the user 121 | Examples: 122 | | zap spider status | spider_id | 123 | """ 124 | while int(self.zap.spider.status(spider_id)) < 100: 125 | logger.info( 126 | "Spider running at {0}%".format(int(self.zap.spider.status(spider_id))) 127 | ) 128 | time.sleep(10) 129 | 130 | def zap_start_ascan(self, context, url, policy="Default Policy"): 131 | """ 132 | Initiates ZAP Active Scan on the target url and context 133 | 134 | Examples: 135 | 136 | | zap start ascan | context | url | 137 | 138 | """ 139 | try: 140 | scan_id = self.zap.ascan.scan( 141 | contextid=context, url=url, scanpolicyname=policy 142 | ) 143 | time.sleep(2) 144 | return scan_id 145 | except Exception as e: 146 | print(e.message) 147 | 148 | def zap_scan_status(self, scan_id): 149 | """ 150 | Fetches the status for the spider id provided by the user 151 | 152 | Examples: 153 | 154 | | zap scan status | scan_id | 155 | 156 | """ 157 | while int(self.zap.ascan.status(scan_id)) < 100: 158 | logger.info( 159 | "Scan running at {0}%".format(int(self.zap.ascan.status(scan_id))) 160 | ) 161 | time.sleep(10) 162 | 163 | def zap_write_to_json_file(self, base_url): 164 | """ 165 | 166 | Fetches all the results from zap.core.alerts() and writes to json file. 167 | 168 | Examples: 169 | 170 | | zap write to json | scan_id | 171 | 172 | """ 173 | core = self.zap.core 174 | all_vuls = [] 175 | for i, na in enumerate(core.alerts(baseurl=base_url)): 176 | vul = {} 177 | vul["name"] = na["alert"] 178 | vul["confidence"] = na.get("confidence", "") 179 | if na.get("risk") == "High": 180 | vul["severity"] = 3 181 | elif na.get("risk") == "Medium": 182 | vul["severity"] = 2 183 | elif na.get("risk") == "Low": 184 | vul["severity"] = 1 185 | else: 186 | vul["severity"] = 0 187 | 188 | vul["cwe"] = na.get("cweid", 0) 189 | vul["uri"] = na.get("url", "") 190 | vul["param"] = na.get("param", "") 191 | vul["attack"] = na.get("attack", "") 192 | vul["evidence"] = na.get("evidence", "") 193 | message_id = na.get("messageId", "") 194 | message = core.message(message_id) 195 | if isinstance(message, dict): 196 | request = base64.b64encode( 197 | "{0}{1}".format(message["requestHeader"], message["requestBody"]) 198 | ) 199 | response = base64.b64encode( 200 | "{0}{1}".format(message["responseHeader"], message["responseBody"]) 201 | ) 202 | vul["request"] = request 203 | vul["response"] = response 204 | vul["rtt"] = int(message["rtt"]) 205 | all_vuls.append(vul) 206 | 207 | filename = "{0}.json".format(str(uuid.uuid4())) 208 | with open(filename, "wb") as json_file: 209 | json_file.write(json.dumps(all_vuls)) 210 | 211 | return filename 212 | 213 | def zap_write_to_orchy(self, report_file, secret, access, hook_uri): 214 | """ 215 | Generates an XML Report and writes said report to orchestron over a webhook. 216 | 217 | Mandatory Fields: 218 | - Report_file: Absolute Path of Report File - JSON or XML 219 | - Token: Webhook Token 220 | - hook_uri: the unique URI to post the XML Report to 221 | 222 | Examples: 223 | 224 | | zap write to orchy | report_file_path | token | hook_uri 225 | 226 | """ 227 | # xml_report = self.zap.core.xmlreport() 228 | # with open('zap_scan.xml','w') as zaprep: 229 | # zaprep.write(xml_report) 230 | try: 231 | files = {"file": open(report_file, "rb")} 232 | auth = {"Secret-Key": secret, "Access-Key": access} 233 | r = requests.post(hook_uri, headers=auth, files=files) 234 | if r.status_code == 200: 235 | return "Successfully posted to Orchestron" 236 | else: 237 | raise Exception("Unable to post successfully") 238 | except Exception as e: 239 | print(e) 240 | 241 | def zap_export_report( 242 | self, export_file, export_format, report_title, report_author 243 | ): 244 | """ 245 | This functionality works on ZAP 2.7.0 only. It leverages the Export Report Library to generate a report. 246 | Currently ExportReport doesnt have an API endpoint in python. We will be using the default ZAP REST API for this 247 | 248 | :param export_file: location to which the export needs to happen. Absolute path with the export file name and extension 249 | :param export_format: file extension of the exported file. Can be XML, JSON, HTML, PDF, DOC 250 | :param report_title: Title of the exported report 251 | :param report_author: Name of the Author of the report 252 | Examples: 253 | 254 | | zap export report | export_path | export_format | 255 | 256 | """ 257 | 258 | url = "http://localhost:{0}/JSON/exportreport/action/generate/".format( 259 | self.port 260 | ) 261 | export_path = export_file 262 | extension = export_format 263 | report_time = datetime.now().strftime("%I:%M%p on %B %d, %Y") 264 | source_info = "{0};{1};ZAP Team;{2};{3};v1;v1;{4}".format( 265 | report_title, report_author, report_time, report_time, report_title 266 | ) 267 | alert_severity = "t;t;t;t" # High;Medium;Low;Info 268 | alert_details = "t;t;t;t;t;t;t;t;t;t" # CWEID;#WASCID;Description;Other Info;Solution;Reference;Request Header;Response Header;Request Body;Response Body 269 | data = { 270 | "absolutePath": export_path, 271 | "fileExtension": extension, 272 | "sourceDetails": source_info, 273 | "alertSeverity": alert_severity, 274 | "alertDetails": alert_details, 275 | } 276 | 277 | r = requests.post(url, data=data) 278 | if r.status_code == 200: 279 | pass 280 | else: 281 | raise Exception("Unable to generate report") 282 | 283 | def zap_write_to_s3_bucket(self, filename, bucket_name): 284 | s3 = boto3.client("s3") 285 | outfile_name = "ZAP-RESULT-{}.json".format(str(uuid.uuid4())) 286 | s3.upload_file(filename, bucket_name, outfile_name) 287 | logger.warn("Filename uploaded to S3 is: {}".format(outfile_name)) 288 | 289 | def retrieve_secret_from_ssm(self, secret, region="us-west-2", decrypt=True): 290 | db = boto3.client("ssm", region_name=region) 291 | param = db.get_parameter(Name=secret, WithDecryption=decrypt)["Parameter"][ 292 | "Value" 293 | ] 294 | return param 295 | 296 | def zap_load_script( 297 | self, 298 | script_name, 299 | script_type, 300 | script_engine, 301 | script_file, 302 | desc="Generic Description of a ZAP Script", 303 | ): 304 | """ 305 | :param script_name: 306 | :param script_type: 307 | :param script_engine: 308 | :param script_file: 309 | :param desc: 310 | :return: 311 | """ 312 | zap_script_status = self.zap.script.load( 313 | scriptname=script_name, 314 | scripttype=script_type, 315 | scriptengine=script_engine, 316 | filename=script_file, 317 | scriptdescription=desc, 318 | ) 319 | logger.info(zap_script_status) 320 | 321 | def zap_run_standalone_script(self, script_name): 322 | zap_script_run_status = self.zap.script.run_stand_alone_script(script_name) 323 | logger.info(zap_script_run_status) 324 | 325 | def zap_shutdown(self): 326 | """ 327 | Shutdown process for ZAP Scanner 328 | """ 329 | self.zap.core.shutdown() 330 | -------------------------------------------------------------------------------- /robozap/robozap_keywords.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 160 | 230 | 243 | 266 | 317 | 520 | 524 | 536 | 549 | 552 |