pyntcli 0.1.71__py3-none-any.whl → 0.1.73__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyntcli/__init__.py +1 -1
- pyntcli/commands/burp.py +311 -0
- pyntcli/commands/command.py +154 -63
- pyntcli/commands/har.py +62 -25
- pyntcli/commands/listen.py +140 -71
- pyntcli/commands/newman.py +81 -33
- pyntcli/commands/postman.py +1 -1
- pyntcli/commands/pynt_cmd.py +2 -1
- pyntcli/commands/root.py +44 -23
- pyntcli/main.py +28 -20
- pyntcli/transport/pynt_requests.py +21 -9
- pyntcli/ui/ui_thread.py +4 -0
- {pyntcli-0.1.71.dist-info → pyntcli-0.1.73.dist-info}/METADATA +2 -1
- {pyntcli-0.1.71.dist-info → pyntcli-0.1.73.dist-info}/RECORD +17 -16
- {pyntcli-0.1.71.dist-info → pyntcli-0.1.73.dist-info}/WHEEL +1 -1
- {pyntcli-0.1.71.dist-info → pyntcli-0.1.73.dist-info}/entry_points.txt +0 -0
- {pyntcli-0.1.71.dist-info → pyntcli-0.1.73.dist-info}/top_level.txt +0 -0
pyntcli/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.73"
|
pyntcli/commands/burp.py
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
import os
|
|
4
|
+
import webbrowser
|
|
5
|
+
from http import HTTPStatus
|
|
6
|
+
import time
|
|
7
|
+
import tempfile
|
|
8
|
+
import json
|
|
9
|
+
from subprocess import Popen, PIPE
|
|
10
|
+
from functools import partial
|
|
11
|
+
import xmltodict
|
|
12
|
+
import base64
|
|
13
|
+
from xml.parsers.expat import ExpatError
|
|
14
|
+
|
|
15
|
+
from pyntcli.pynt_docker import pynt_container
|
|
16
|
+
from pyntcli.ui import ui_thread
|
|
17
|
+
from pyntcli.commands import util, sub_command
|
|
18
|
+
from pyntcli.ui import report as cli_reporter
|
|
19
|
+
from pyntcli.transport import pynt_requests
|
|
20
|
+
|
|
21
|
+
methods = [
|
|
22
|
+
"get",
|
|
23
|
+
"post",
|
|
24
|
+
"put",
|
|
25
|
+
"delete",
|
|
26
|
+
"patch",
|
|
27
|
+
"options",
|
|
28
|
+
"head",
|
|
29
|
+
"trace",
|
|
30
|
+
"connect",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def is_valid_method(method: str):
|
|
35
|
+
if method.lower() in methods:
|
|
36
|
+
return True
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def replay_req(item, proxy_port):
|
|
41
|
+
url = item["url"]
|
|
42
|
+
decoded_req = base64.b64decode(item["request"]["#text"]).decode("utf-8")
|
|
43
|
+
method = decoded_req.split("\r\n")[0].split(" ")[0]
|
|
44
|
+
|
|
45
|
+
if not is_valid_method(method):
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
lines = decoded_req.split("\r\n")
|
|
49
|
+
if len(lines) < 2:
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
headers = {}
|
|
53
|
+
|
|
54
|
+
for line in lines[1:]:
|
|
55
|
+
if not line:
|
|
56
|
+
break
|
|
57
|
+
|
|
58
|
+
if ": " not in line:
|
|
59
|
+
break
|
|
60
|
+
|
|
61
|
+
key, value = line.split(": ")
|
|
62
|
+
headers[key] = value
|
|
63
|
+
|
|
64
|
+
body = decoded_req.split("\r\n\r\n")[1]
|
|
65
|
+
pynt_requests.request_from_xml(
|
|
66
|
+
method=method,
|
|
67
|
+
url=url,
|
|
68
|
+
headers=headers,
|
|
69
|
+
data=body,
|
|
70
|
+
proxies={
|
|
71
|
+
"http": "0.0.0.0:{}".format(proxy_port),
|
|
72
|
+
"https": "0.0.0.0:{}".format(proxy_port),
|
|
73
|
+
},
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def run_burp_xml(doc, proxy_port):
|
|
78
|
+
items = doc["items"]["item"]
|
|
79
|
+
if isinstance(items, dict):
|
|
80
|
+
replay_req(item=items, proxy_port=proxy_port)
|
|
81
|
+
else:
|
|
82
|
+
[replay_req(i, proxy_port=proxy_port) for i in doc["items"]["item"]]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def parse_xml(xml_path):
|
|
86
|
+
try:
|
|
87
|
+
with open(xml_path) as fd:
|
|
88
|
+
return xmltodict.parse(fd.read())
|
|
89
|
+
except ExpatError:
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def is_valid_xml(doc) -> bool:
|
|
94
|
+
return "items" in doc
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def burp_usage():
|
|
98
|
+
return (
|
|
99
|
+
ui_thread.PrinterText(
|
|
100
|
+
"Burp integration to Pynt. Run a security scan with a given burp xml output file."
|
|
101
|
+
)
|
|
102
|
+
.with_line("")
|
|
103
|
+
.with_line("Usage:", style=ui_thread.PrinterText.HEADER)
|
|
104
|
+
.with_line("\tpynt burp [OPTIONS]")
|
|
105
|
+
.with_line("")
|
|
106
|
+
.with_line("Options:", style=ui_thread.PrinterText.HEADER)
|
|
107
|
+
.with_line("\t--xml - Path to the xml to run tests on")
|
|
108
|
+
.with_line("\t--port - Set the port pynt will listen to (DEFAULT: 5001)")
|
|
109
|
+
.with_line("\t--ca-path - The path to the CA file in PEM format")
|
|
110
|
+
.with_line(
|
|
111
|
+
"\t--proxy-port - Set the port proxied traffic should be routed to (DEFAULT: 6666)"
|
|
112
|
+
)
|
|
113
|
+
.with_line(
|
|
114
|
+
"\t--report - If present will save the generated report in this path."
|
|
115
|
+
)
|
|
116
|
+
.with_line("\t--insecure - Use when target uses self signed certificates")
|
|
117
|
+
.with_line(
|
|
118
|
+
"\t--application-id - Attach the scan to an application, you can find the ID in your applications area at app.pynt.io"
|
|
119
|
+
)
|
|
120
|
+
.with_line(
|
|
121
|
+
"\t--host-ca - Path to the CA file in PEM format to enable SSL certificate verification for pynt when running through a VPN."
|
|
122
|
+
)
|
|
123
|
+
.with_line(
|
|
124
|
+
"\t--return-error - 'all-findings' (warnings, or errors), 'errors-only', 'never' (default), "
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class BurpCommand(sub_command.PyntSubCommand):
|
|
130
|
+
def __init__(self, name) -> None:
|
|
131
|
+
super().__init__(name)
|
|
132
|
+
self.scan_id = ""
|
|
133
|
+
self.proxy_sleep_interval = 2
|
|
134
|
+
self.proxy_healthcheck_buffer = 10
|
|
135
|
+
self.proxy_server_base_url = "http://localhost:{}/api"
|
|
136
|
+
|
|
137
|
+
def print_usage(self, *args):
|
|
138
|
+
ui_thread.print(burp_usage())
|
|
139
|
+
|
|
140
|
+
def add_cmd(self, parent: argparse._SubParsersAction) -> argparse.ArgumentParser:
|
|
141
|
+
burp_cmd = parent.add_parser(self.name)
|
|
142
|
+
burp_cmd.add_argument("--port", "-p", help="", type=int, default=5001)
|
|
143
|
+
burp_cmd.add_argument("--proxy-port", help="", type=int, default=6666)
|
|
144
|
+
burp_cmd.add_argument("--xml", help="", default="", required=True)
|
|
145
|
+
burp_cmd.add_argument("--ca-path", type=str, default="")
|
|
146
|
+
burp_cmd.add_argument("--report", type=str, default="")
|
|
147
|
+
burp_cmd.add_argument(
|
|
148
|
+
"--return-error",
|
|
149
|
+
choices=["all-findings", "errors-only", "never"],
|
|
150
|
+
default="never",
|
|
151
|
+
)
|
|
152
|
+
burp_cmd.print_usage = self.print_usage
|
|
153
|
+
burp_cmd.print_help = self.print_usage
|
|
154
|
+
return burp_cmd
|
|
155
|
+
|
|
156
|
+
def _updated_environment(self, args):
|
|
157
|
+
env_copy = deepcopy(os.environ)
|
|
158
|
+
return env_copy.update(
|
|
159
|
+
{
|
|
160
|
+
"HTTP_PROXY": "http://localhost:{}".format(args.proxy_port),
|
|
161
|
+
"HTTPS_PROXY": "http://localhost:{}".format(args.proxy_port),
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def _start_proxy(self, args):
|
|
166
|
+
res = pynt_requests.put(
|
|
167
|
+
self.proxy_server_base_url.format(args.port) + "/proxy/start"
|
|
168
|
+
)
|
|
169
|
+
res.raise_for_status()
|
|
170
|
+
self.scan_id = res.json()["scanId"]
|
|
171
|
+
|
|
172
|
+
def _stop_proxy(self, args):
|
|
173
|
+
start = time.time()
|
|
174
|
+
while start + self.proxy_healthcheck_buffer > time.time():
|
|
175
|
+
res = pynt_requests.put(
|
|
176
|
+
self.proxy_server_base_url.format(args.port) + "/proxy/stop",
|
|
177
|
+
json={"scanId": self.scan_id},
|
|
178
|
+
)
|
|
179
|
+
if res.status_code == HTTPStatus.OK:
|
|
180
|
+
return
|
|
181
|
+
time.sleep(self.proxy_sleep_interval)
|
|
182
|
+
raise TimeoutError()
|
|
183
|
+
|
|
184
|
+
def _get_report(self, args, report_format):
|
|
185
|
+
while True:
|
|
186
|
+
res = pynt_requests.get(
|
|
187
|
+
self.proxy_server_base_url.format(args.port)
|
|
188
|
+
+ "/report?format={}".format(report_format),
|
|
189
|
+
params={"scanId": self.scan_id},
|
|
190
|
+
)
|
|
191
|
+
if res.status_code == HTTPStatus.OK:
|
|
192
|
+
return res.text
|
|
193
|
+
if res.status_code == HTTPStatus.ACCEPTED:
|
|
194
|
+
time.sleep(self.proxy_sleep_interval)
|
|
195
|
+
continue
|
|
196
|
+
if res.status_code == 517: # pynt did not recieve any requests
|
|
197
|
+
ui_thread.print(
|
|
198
|
+
ui_thread.PrinterText(
|
|
199
|
+
res.json()["message"], ui_thread.PrinterText.WARNING
|
|
200
|
+
)
|
|
201
|
+
)
|
|
202
|
+
return
|
|
203
|
+
ui_thread.print("Error in polling for scan report: {}".format(res.text))
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
def run_cmd(self, args: argparse.Namespace):
|
|
207
|
+
container = pynt_container.get_container_with_arguments(
|
|
208
|
+
args,
|
|
209
|
+
pynt_container.PyntDockerPort(args.port, args.port, "--port"),
|
|
210
|
+
pynt_container.PyntDockerPort(
|
|
211
|
+
args.proxy_port, args.proxy_port, "--proxy-port"
|
|
212
|
+
),
|
|
213
|
+
)
|
|
214
|
+
if "ca_path" in args and args.ca_path:
|
|
215
|
+
if not os.path.isfile(args.ca_path):
|
|
216
|
+
ui_thread.print(
|
|
217
|
+
ui_thread.PrinterText(
|
|
218
|
+
"Could not find the provided ca path, please provide with a valid path",
|
|
219
|
+
ui_thread.PrinterText.WARNING,
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
return
|
|
223
|
+
|
|
224
|
+
ca_name = os.path.basename(args.ca_path)
|
|
225
|
+
container.docker_arguments += ["--ca-path", ca_name]
|
|
226
|
+
container.mounts.append(
|
|
227
|
+
pynt_container.create_mount(
|
|
228
|
+
os.path.abspath(args.ca_path), "/etc/pynt/{}".format(ca_name)
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
if not os.path.isfile(args.xml):
|
|
233
|
+
ui_thread.print(
|
|
234
|
+
ui_thread.PrinterText(
|
|
235
|
+
"Could not find the provided xml path, please provide with a valid xml path",
|
|
236
|
+
ui_thread.PrinterText.WARNING,
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
doc = parse_xml(args.xml)
|
|
242
|
+
if not doc:
|
|
243
|
+
ui_thread.print(
|
|
244
|
+
ui_thread.PrinterText(
|
|
245
|
+
"Invalid file format. please provide a valid xml",
|
|
246
|
+
ui_thread.PrinterText.WARNING,
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
if not is_valid_xml(doc):
|
|
252
|
+
ui_thread.print(
|
|
253
|
+
ui_thread.PrinterText(
|
|
254
|
+
"Invalid xml file. please provide a valid xml output generated from burp",
|
|
255
|
+
ui_thread.PrinterText.WARNING,
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
return
|
|
259
|
+
|
|
260
|
+
proxy_docker = pynt_container.PyntContainer(
|
|
261
|
+
image_name=pynt_container.PYNT_DOCKER_IMAGE,
|
|
262
|
+
tag="proxy-latest",
|
|
263
|
+
detach=True,
|
|
264
|
+
base_container=container,
|
|
265
|
+
)
|
|
266
|
+
proxy_docker.run()
|
|
267
|
+
ui_thread.print_generator(proxy_docker.stdout)
|
|
268
|
+
|
|
269
|
+
util.wait_for_healthcheck("http://localhost:{}".format(args.port))
|
|
270
|
+
self._start_proxy(args)
|
|
271
|
+
|
|
272
|
+
run_burp_xml(doc, args.proxy_port)
|
|
273
|
+
|
|
274
|
+
self._stop_proxy(args)
|
|
275
|
+
|
|
276
|
+
with ui_thread.progress(
|
|
277
|
+
"ws://localhost:{}/progress?scanId={}".format(args.port, self.scan_id),
|
|
278
|
+
partial(lambda *args: None),
|
|
279
|
+
"scan in progress...",
|
|
280
|
+
100,
|
|
281
|
+
):
|
|
282
|
+
html_report = self._get_report(args, "html")
|
|
283
|
+
html_report_path = os.path.join(
|
|
284
|
+
tempfile.gettempdir(), "pynt_report_{}.html".format(int(time.time()))
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
json_report = self._get_report(args, "json")
|
|
288
|
+
json_report_path = os.path.join(
|
|
289
|
+
tempfile.gettempdir(), "pynt_report_{}.json".format(int(time.time()))
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
if "report" in args and args.report:
|
|
293
|
+
full_path = os.path.abspath(args.report)
|
|
294
|
+
html_report_path = util.get_user_report_path(full_path, "html")
|
|
295
|
+
json_report_path = util.get_user_report_path(full_path, "json")
|
|
296
|
+
|
|
297
|
+
if html_report:
|
|
298
|
+
with open(html_report_path, "w") as html_file:
|
|
299
|
+
html_file.write(html_report)
|
|
300
|
+
webbrowser.open("file://{}".format(html_report_path))
|
|
301
|
+
|
|
302
|
+
if json_report:
|
|
303
|
+
with open(json_report_path, "w") as json_file:
|
|
304
|
+
json_file.write(json_report)
|
|
305
|
+
reporter = cli_reporter.PyntReporter(json_report_path)
|
|
306
|
+
reporter.print_summary()
|
|
307
|
+
|
|
308
|
+
if json_report:
|
|
309
|
+
json_obj = json.loads(json_report)
|
|
310
|
+
if json_obj:
|
|
311
|
+
util.check_for_findings_or_warnings(args, json_obj)
|
pyntcli/commands/command.py
CHANGED
|
@@ -16,127 +16,219 @@ from pyntcli.ui import report as cli_reporter
|
|
|
16
16
|
from pyntcli.transport import pynt_requests
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
|
|
20
19
|
def command_usage():
|
|
21
|
-
return
|
|
22
|
-
.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
.with_line("")
|
|
26
|
-
.with_line("
|
|
27
|
-
.with_line("\
|
|
28
|
-
.with_line("
|
|
29
|
-
.with_line("
|
|
30
|
-
.with_line("\t--
|
|
31
|
-
.with_line(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
.with_line("\t--
|
|
35
|
-
.with_line(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
20
|
+
return (
|
|
21
|
+
ui_thread.PrinterText(
|
|
22
|
+
"Command integration to Pynt. Run a security scan with a given command."
|
|
23
|
+
)
|
|
24
|
+
.with_line("")
|
|
25
|
+
.with_line("Usage:", style=ui_thread.PrinterText.HEADER)
|
|
26
|
+
.with_line("\tpynt command [OPTIONS]")
|
|
27
|
+
.with_line("")
|
|
28
|
+
.with_line("Options:", style=ui_thread.PrinterText.HEADER)
|
|
29
|
+
.with_line("\t--cmd - The command that runs the functional tests")
|
|
30
|
+
.with_line(
|
|
31
|
+
'\t--captured-domains - Pynt will scan only these domains and subdomains. For all domains write "*"'
|
|
32
|
+
)
|
|
33
|
+
.with_line("\t--port - Set the port pynt will listen to (DEFAULT: 5001)")
|
|
34
|
+
.with_line(
|
|
35
|
+
"\t--allow-errors - If present will allow command to fail and continue execution"
|
|
36
|
+
)
|
|
37
|
+
.with_line("\t--ca-path - The path to the CA file in PEM format")
|
|
38
|
+
.with_line(
|
|
39
|
+
"\t--proxy-port - Set the port proxied traffic should be routed to (DEFAULT: 6666)"
|
|
40
|
+
)
|
|
41
|
+
.with_line(
|
|
42
|
+
"\t--report - If present will save the generated report in this path."
|
|
43
|
+
)
|
|
44
|
+
.with_line("\t--insecure - Use when target uses self signed certificates")
|
|
45
|
+
.with_line(
|
|
46
|
+
"\t--application-id - Attach the scan to an application, you can find the ID in your applications area at app.pynt.io"
|
|
47
|
+
)
|
|
48
|
+
.with_line(
|
|
49
|
+
"\t--host-ca - Path to the CA file in PEM format to enable SSL certificate verification for pynt when running through a VPN."
|
|
50
|
+
)
|
|
51
|
+
.with_line(
|
|
52
|
+
"\t--return-error - 'all-findings' (warnings, or errors), 'errors-only', 'never' (default), "
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CommandSubCommand(sub_command.PyntSubCommand):
|
|
39
58
|
def __init__(self, name) -> None:
|
|
40
59
|
super().__init__(name)
|
|
41
60
|
self.scan_id = ""
|
|
42
61
|
self.proxy_sleep_interval = 2
|
|
43
62
|
self.proxy_healthcheck_buffer = 10
|
|
44
63
|
self.proxy_server_base_url = "http://localhost:{}/api"
|
|
45
|
-
|
|
64
|
+
|
|
46
65
|
def print_usage(self, *args):
|
|
47
66
|
ui_thread.print(command_usage())
|
|
48
67
|
|
|
49
68
|
def add_cmd(self, parent: argparse._SubParsersAction) -> argparse.ArgumentParser:
|
|
50
69
|
proxy_cmd = parent.add_parser(self.name)
|
|
51
70
|
proxy_cmd.add_argument("--port", "-p", help="", type=int, default=5001)
|
|
52
|
-
proxy_cmd.add_argument("--proxy-port", help="", type=int, default=6666)
|
|
71
|
+
proxy_cmd.add_argument("--proxy-port", help="", type=int, default=6666)
|
|
53
72
|
proxy_cmd.add_argument("--cmd", help="", default="", required=True)
|
|
73
|
+
proxy_cmd.add_argument(
|
|
74
|
+
"--captured-domains", nargs="+", help="", default="", required=False
|
|
75
|
+
)
|
|
54
76
|
proxy_cmd.add_argument("--allow-errors", action="store_true")
|
|
55
77
|
proxy_cmd.add_argument("--ca-path", type=str, default="")
|
|
56
78
|
proxy_cmd.add_argument("--report", type=str, default="")
|
|
57
|
-
proxy_cmd.add_argument(
|
|
79
|
+
proxy_cmd.add_argument(
|
|
80
|
+
"--return-error",
|
|
81
|
+
choices=["all-findings", "errors-only", "never"],
|
|
82
|
+
default="never",
|
|
83
|
+
)
|
|
58
84
|
proxy_cmd.print_usage = self.print_usage
|
|
59
85
|
proxy_cmd.print_help = self.print_usage
|
|
60
86
|
return proxy_cmd
|
|
61
|
-
|
|
87
|
+
|
|
62
88
|
def _updated_environment(self, args):
|
|
63
89
|
env_copy = deepcopy(os.environ)
|
|
64
|
-
return env_copy.update(
|
|
65
|
-
|
|
66
|
-
|
|
90
|
+
return env_copy.update(
|
|
91
|
+
{
|
|
92
|
+
"HTTP_PROXY": "http://localhost:{}".format(args.proxy_port),
|
|
93
|
+
"HTTPS_PROXY": "http://localhost:{}".format(args.proxy_port),
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
|
|
67
97
|
def _start_proxy(self, args):
|
|
68
|
-
res = pynt_requests.put(
|
|
98
|
+
res = pynt_requests.put(
|
|
99
|
+
self.proxy_server_base_url.format(args.port) + "/proxy/start"
|
|
100
|
+
)
|
|
69
101
|
res.raise_for_status()
|
|
70
102
|
self.scan_id = res.json()["scanId"]
|
|
71
|
-
|
|
103
|
+
|
|
72
104
|
def _stop_proxy(self, args):
|
|
73
105
|
start = time.time()
|
|
74
|
-
while start + self.proxy_healthcheck_buffer > time.time():
|
|
75
|
-
res = pynt_requests.put(
|
|
76
|
-
|
|
77
|
-
|
|
106
|
+
while start + self.proxy_healthcheck_buffer > time.time():
|
|
107
|
+
res = pynt_requests.put(
|
|
108
|
+
self.proxy_server_base_url.format(args.port) + "/proxy/stop",
|
|
109
|
+
json={"scanId": self.scan_id},
|
|
110
|
+
)
|
|
111
|
+
if res.status_code == HTTPStatus.OK:
|
|
112
|
+
return
|
|
78
113
|
time.sleep(self.proxy_sleep_interval)
|
|
79
114
|
raise TimeoutError()
|
|
80
|
-
|
|
81
|
-
def _get_report(self, args,report_format):
|
|
82
|
-
while True:
|
|
83
|
-
res = pynt_requests.get(
|
|
115
|
+
|
|
116
|
+
def _get_report(self, args, report_format):
|
|
117
|
+
while True:
|
|
118
|
+
res = pynt_requests.get(
|
|
119
|
+
self.proxy_server_base_url.format(args.port)
|
|
120
|
+
+ "/report?format={}".format(report_format),
|
|
121
|
+
params={"scanId": self.scan_id},
|
|
122
|
+
)
|
|
84
123
|
if res.status_code == HTTPStatus.OK:
|
|
85
124
|
return res.text
|
|
86
125
|
if res.status_code == HTTPStatus.ACCEPTED:
|
|
87
126
|
time.sleep(self.proxy_sleep_interval)
|
|
88
127
|
continue
|
|
89
|
-
if res.status_code == 517:
|
|
90
|
-
ui_thread.print(
|
|
91
|
-
|
|
128
|
+
if res.status_code == 517: # pynt did not recieve any requests
|
|
129
|
+
ui_thread.print(
|
|
130
|
+
ui_thread.PrinterText(
|
|
131
|
+
res.json()["message"], ui_thread.PrinterText.WARNING
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
return
|
|
92
135
|
ui_thread.print("Error in polling for scan report: {}".format(res.text))
|
|
93
|
-
return
|
|
136
|
+
return
|
|
94
137
|
|
|
95
|
-
|
|
96
138
|
def run_cmd(self, args: argparse.Namespace):
|
|
97
|
-
container = pynt_container.get_container_with_arguments(
|
|
98
|
-
|
|
139
|
+
container = pynt_container.get_container_with_arguments(
|
|
140
|
+
args,
|
|
141
|
+
pynt_container.PyntDockerPort(args.port, args.port, "--port"),
|
|
142
|
+
pynt_container.PyntDockerPort(
|
|
143
|
+
args.proxy_port, args.proxy_port, "--proxy-port"
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if args.captured_domains:
|
|
148
|
+
for host in args.captured_domains:
|
|
149
|
+
container.docker_arguments += ["--host-targets", host]
|
|
150
|
+
|
|
99
151
|
if "ca_path" in args and args.ca_path:
|
|
100
152
|
if not os.path.isfile(args.ca_path):
|
|
101
|
-
ui_thread.print(
|
|
153
|
+
ui_thread.print(
|
|
154
|
+
ui_thread.PrinterText(
|
|
155
|
+
"Could not find the provided ca path, please provide with a valid path",
|
|
156
|
+
ui_thread.PrinterText.WARNING,
|
|
157
|
+
)
|
|
158
|
+
)
|
|
102
159
|
return
|
|
103
160
|
|
|
104
161
|
ca_name = os.path.basename(args.ca_path)
|
|
105
162
|
container.docker_arguments += ["--ca-path", ca_name]
|
|
106
|
-
container.mounts.append(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
163
|
+
container.mounts.append(
|
|
164
|
+
pynt_container.create_mount(
|
|
165
|
+
os.path.abspath(args.ca_path), "/etc/pynt/{}".format(ca_name)
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
proxy_docker = pynt_container.PyntContainer(
|
|
170
|
+
image_name=pynt_container.PYNT_DOCKER_IMAGE,
|
|
171
|
+
tag="proxy-latest",
|
|
172
|
+
detach=True,
|
|
173
|
+
base_container=container,
|
|
174
|
+
)
|
|
112
175
|
proxy_docker.run()
|
|
113
176
|
ui_thread.print_generator(proxy_docker.stdout)
|
|
114
|
-
|
|
177
|
+
|
|
115
178
|
util.wait_for_healthcheck("http://localhost:{}".format(args.port))
|
|
116
|
-
self._start_proxy(args)
|
|
117
179
|
|
|
118
|
-
|
|
180
|
+
if args.captured_domains:
|
|
181
|
+
ui_thread.print(
|
|
182
|
+
"\nWill scan APIs that belong to {} domains only".format(
|
|
183
|
+
args.captured_domains
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
self._start_proxy(args)
|
|
188
|
+
|
|
189
|
+
user_process = Popen(
|
|
190
|
+
args.cmd,
|
|
191
|
+
shell=True,
|
|
192
|
+
stdout=PIPE,
|
|
193
|
+
stderr=PIPE,
|
|
194
|
+
env=self._updated_environment(args),
|
|
195
|
+
)
|
|
119
196
|
ui_thread.print_generator(user_process.stdout)
|
|
120
197
|
ui_thread.print_generator(user_process.stderr)
|
|
121
198
|
rc = user_process.wait()
|
|
122
199
|
if rc != 0 and not args.allow_errors:
|
|
123
200
|
proxy_docker.stop()
|
|
124
|
-
ui_thread.print(
|
|
201
|
+
ui_thread.print(
|
|
202
|
+
ui_thread.PrinterText(
|
|
203
|
+
"Command finished with error return code {}, If you wish Pynt to run anyway, run with --allow-errors".format(
|
|
204
|
+
rc
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
)
|
|
125
208
|
return
|
|
126
209
|
|
|
127
210
|
self._stop_proxy(args)
|
|
128
|
-
|
|
129
|
-
with ui_thread.progress("ws://localhost:{}/progress?scanId={}".format(args.port, self.scan_id),partial(lambda *args: None), "scan in progress...", 100):
|
|
130
|
-
html_report = self._get_report(args,"html")
|
|
131
|
-
html_report_path = os.path.join(tempfile.gettempdir(), "pynt_report_{}.html".format(int(time.time())))
|
|
132
211
|
|
|
133
|
-
|
|
134
|
-
|
|
212
|
+
with ui_thread.progress(
|
|
213
|
+
"ws://localhost:{}/progress?scanId={}".format(args.port, self.scan_id),
|
|
214
|
+
partial(lambda *args: None),
|
|
215
|
+
"scan in progress...",
|
|
216
|
+
100,
|
|
217
|
+
):
|
|
218
|
+
html_report = self._get_report(args, "html")
|
|
219
|
+
html_report_path = os.path.join(
|
|
220
|
+
tempfile.gettempdir(), "pynt_report_{}.html".format(int(time.time()))
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
json_report = self._get_report(args, "json")
|
|
224
|
+
json_report_path = os.path.join(
|
|
225
|
+
tempfile.gettempdir(), "pynt_report_{}.json".format(int(time.time()))
|
|
226
|
+
)
|
|
135
227
|
|
|
136
228
|
if "report" in args and args.report:
|
|
137
229
|
full_path = os.path.abspath(args.report)
|
|
138
|
-
html_report_path = util.get_user_report_path(full_path,"html")
|
|
139
|
-
json_report_path = util.get_user_report_path(full_path,"json")
|
|
230
|
+
html_report_path = util.get_user_report_path(full_path, "html")
|
|
231
|
+
json_report_path = util.get_user_report_path(full_path, "json")
|
|
140
232
|
|
|
141
233
|
if html_report:
|
|
142
234
|
with open(html_report_path, "w") as html_file:
|
|
@@ -153,4 +245,3 @@ class CommandSubCommand(sub_command.PyntSubCommand):
|
|
|
153
245
|
json_obj = json.loads(json_report)
|
|
154
246
|
if json_obj:
|
|
155
247
|
util.check_for_findings_or_warnings(args, json_obj)
|
|
156
|
-
|