pyntcli 0.1.72__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 +133 -38
- pyntcli/commands/command.py +122 -39
- pyntcli/commands/har.py +52 -21
- pyntcli/commands/listen.py +112 -37
- pyntcli/commands/newman.py +81 -33
- pyntcli/commands/postman.py +1 -1
- pyntcli/commands/root.py +44 -23
- {pyntcli-0.1.72.dist-info → pyntcli-0.1.73.dist-info}/METADATA +1 -1
- {pyntcli-0.1.72.dist-info → pyntcli-0.1.73.dist-info}/RECORD +13 -13
- {pyntcli-0.1.72.dist-info → pyntcli-0.1.73.dist-info}/WHEEL +1 -1
- {pyntcli-0.1.72.dist-info → pyntcli-0.1.73.dist-info}/entry_points.txt +0 -0
- {pyntcli-0.1.72.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
CHANGED
|
@@ -18,7 +18,17 @@ from pyntcli.commands import util, sub_command
|
|
|
18
18
|
from pyntcli.ui import report as cli_reporter
|
|
19
19
|
from pyntcli.transport import pynt_requests
|
|
20
20
|
|
|
21
|
-
methods = [
|
|
21
|
+
methods = [
|
|
22
|
+
"get",
|
|
23
|
+
"post",
|
|
24
|
+
"put",
|
|
25
|
+
"delete",
|
|
26
|
+
"patch",
|
|
27
|
+
"options",
|
|
28
|
+
"head",
|
|
29
|
+
"trace",
|
|
30
|
+
"connect",
|
|
31
|
+
]
|
|
22
32
|
|
|
23
33
|
|
|
24
34
|
def is_valid_method(method: str):
|
|
@@ -29,7 +39,7 @@ def is_valid_method(method: str):
|
|
|
29
39
|
|
|
30
40
|
def replay_req(item, proxy_port):
|
|
31
41
|
url = item["url"]
|
|
32
|
-
decoded_req = base64.b64decode(item["request"]["#text"]).decode(
|
|
42
|
+
decoded_req = base64.b64decode(item["request"]["#text"]).decode("utf-8")
|
|
33
43
|
method = decoded_req.split("\r\n")[0].split(" ")[0]
|
|
34
44
|
|
|
35
45
|
if not is_valid_method(method):
|
|
@@ -52,7 +62,16 @@ def replay_req(item, proxy_port):
|
|
|
52
62
|
headers[key] = value
|
|
53
63
|
|
|
54
64
|
body = decoded_req.split("\r\n\r\n")[1]
|
|
55
|
-
pynt_requests.request_from_xml(
|
|
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
|
+
)
|
|
56
75
|
|
|
57
76
|
|
|
58
77
|
def run_burp_xml(doc, proxy_port):
|
|
@@ -76,20 +95,35 @@ def is_valid_xml(doc) -> bool:
|
|
|
76
95
|
|
|
77
96
|
|
|
78
97
|
def burp_usage():
|
|
79
|
-
return
|
|
80
|
-
.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
.with_line("")
|
|
84
|
-
.with_line("
|
|
85
|
-
.with_line("\
|
|
86
|
-
.with_line("
|
|
87
|
-
.with_line("
|
|
88
|
-
.with_line("\t--
|
|
89
|
-
.with_line("\t--
|
|
90
|
-
.with_line("\t--
|
|
91
|
-
.with_line(
|
|
92
|
-
|
|
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
|
+
)
|
|
93
127
|
|
|
94
128
|
|
|
95
129
|
class BurpCommand(sub_command.PyntSubCommand):
|
|
@@ -110,25 +144,38 @@ class BurpCommand(sub_command.PyntSubCommand):
|
|
|
110
144
|
burp_cmd.add_argument("--xml", help="", default="", required=True)
|
|
111
145
|
burp_cmd.add_argument("--ca-path", type=str, default="")
|
|
112
146
|
burp_cmd.add_argument("--report", type=str, default="")
|
|
113
|
-
burp_cmd.add_argument(
|
|
147
|
+
burp_cmd.add_argument(
|
|
148
|
+
"--return-error",
|
|
149
|
+
choices=["all-findings", "errors-only", "never"],
|
|
150
|
+
default="never",
|
|
151
|
+
)
|
|
114
152
|
burp_cmd.print_usage = self.print_usage
|
|
115
153
|
burp_cmd.print_help = self.print_usage
|
|
116
154
|
return burp_cmd
|
|
117
155
|
|
|
118
156
|
def _updated_environment(self, args):
|
|
119
157
|
env_copy = deepcopy(os.environ)
|
|
120
|
-
return env_copy.update(
|
|
121
|
-
|
|
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
|
+
)
|
|
122
164
|
|
|
123
165
|
def _start_proxy(self, args):
|
|
124
|
-
res = pynt_requests.put(
|
|
166
|
+
res = pynt_requests.put(
|
|
167
|
+
self.proxy_server_base_url.format(args.port) + "/proxy/start"
|
|
168
|
+
)
|
|
125
169
|
res.raise_for_status()
|
|
126
170
|
self.scan_id = res.json()["scanId"]
|
|
127
171
|
|
|
128
172
|
def _stop_proxy(self, args):
|
|
129
173
|
start = time.time()
|
|
130
174
|
while start + self.proxy_healthcheck_buffer > time.time():
|
|
131
|
-
res = pynt_requests.put(
|
|
175
|
+
res = pynt_requests.put(
|
|
176
|
+
self.proxy_server_base_url.format(args.port) + "/proxy/stop",
|
|
177
|
+
json={"scanId": self.scan_id},
|
|
178
|
+
)
|
|
132
179
|
if res.status_code == HTTPStatus.OK:
|
|
133
180
|
return
|
|
134
181
|
time.sleep(self.proxy_sleep_interval)
|
|
@@ -136,47 +183,86 @@ class BurpCommand(sub_command.PyntSubCommand):
|
|
|
136
183
|
|
|
137
184
|
def _get_report(self, args, report_format):
|
|
138
185
|
while True:
|
|
139
|
-
res = pynt_requests.get(
|
|
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
|
+
)
|
|
140
191
|
if res.status_code == HTTPStatus.OK:
|
|
141
192
|
return res.text
|
|
142
193
|
if res.status_code == HTTPStatus.ACCEPTED:
|
|
143
194
|
time.sleep(self.proxy_sleep_interval)
|
|
144
195
|
continue
|
|
145
196
|
if res.status_code == 517: # pynt did not recieve any requests
|
|
146
|
-
ui_thread.print(
|
|
197
|
+
ui_thread.print(
|
|
198
|
+
ui_thread.PrinterText(
|
|
199
|
+
res.json()["message"], ui_thread.PrinterText.WARNING
|
|
200
|
+
)
|
|
201
|
+
)
|
|
147
202
|
return
|
|
148
203
|
ui_thread.print("Error in polling for scan report: {}".format(res.text))
|
|
149
204
|
return
|
|
150
205
|
|
|
151
206
|
def run_cmd(self, args: argparse.Namespace):
|
|
152
|
-
container = pynt_container.get_container_with_arguments(
|
|
153
|
-
|
|
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
|
+
)
|
|
154
214
|
if "ca_path" in args and args.ca_path:
|
|
155
215
|
if not os.path.isfile(args.ca_path):
|
|
156
|
-
ui_thread.print(
|
|
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
|
+
)
|
|
157
222
|
return
|
|
158
223
|
|
|
159
224
|
ca_name = os.path.basename(args.ca_path)
|
|
160
225
|
container.docker_arguments += ["--ca-path", ca_name]
|
|
161
|
-
container.mounts.append(
|
|
226
|
+
container.mounts.append(
|
|
227
|
+
pynt_container.create_mount(
|
|
228
|
+
os.path.abspath(args.ca_path), "/etc/pynt/{}".format(ca_name)
|
|
229
|
+
)
|
|
230
|
+
)
|
|
162
231
|
|
|
163
232
|
if not os.path.isfile(args.xml):
|
|
164
|
-
ui_thread.print(
|
|
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
|
+
)
|
|
165
239
|
return
|
|
166
240
|
|
|
167
241
|
doc = parse_xml(args.xml)
|
|
168
242
|
if not doc:
|
|
169
|
-
ui_thread.print(
|
|
243
|
+
ui_thread.print(
|
|
244
|
+
ui_thread.PrinterText(
|
|
245
|
+
"Invalid file format. please provide a valid xml",
|
|
246
|
+
ui_thread.PrinterText.WARNING,
|
|
247
|
+
)
|
|
248
|
+
)
|
|
170
249
|
return
|
|
171
250
|
|
|
172
251
|
if not is_valid_xml(doc):
|
|
173
|
-
ui_thread.print(
|
|
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
|
+
)
|
|
174
258
|
return
|
|
175
259
|
|
|
176
|
-
proxy_docker = pynt_container.PyntContainer(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
+
)
|
|
180
266
|
proxy_docker.run()
|
|
181
267
|
ui_thread.print_generator(proxy_docker.stdout)
|
|
182
268
|
|
|
@@ -187,12 +273,21 @@ class BurpCommand(sub_command.PyntSubCommand):
|
|
|
187
273
|
|
|
188
274
|
self._stop_proxy(args)
|
|
189
275
|
|
|
190
|
-
with ui_thread.progress(
|
|
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
|
+
):
|
|
191
282
|
html_report = self._get_report(args, "html")
|
|
192
|
-
html_report_path = os.path.join(
|
|
283
|
+
html_report_path = os.path.join(
|
|
284
|
+
tempfile.gettempdir(), "pynt_report_{}.html".format(int(time.time()))
|
|
285
|
+
)
|
|
193
286
|
|
|
194
287
|
json_report = self._get_report(args, "json")
|
|
195
|
-
json_report_path = os.path.join(
|
|
288
|
+
json_report_path = os.path.join(
|
|
289
|
+
tempfile.gettempdir(), "pynt_report_{}.json".format(int(time.time()))
|
|
290
|
+
)
|
|
196
291
|
|
|
197
292
|
if "report" in args and args.report:
|
|
198
293
|
full_path = os.path.abspath(args.report)
|
pyntcli/commands/command.py
CHANGED
|
@@ -17,22 +17,41 @@ from pyntcli.transport import pynt_requests
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def command_usage():
|
|
20
|
-
return
|
|
21
|
-
.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
.with_line("")
|
|
25
|
-
.with_line("
|
|
26
|
-
.with_line("\
|
|
27
|
-
.with_line("
|
|
28
|
-
.with_line("
|
|
29
|
-
.with_line("\t--
|
|
30
|
-
.with_line(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.with_line("\t--
|
|
34
|
-
.with_line(
|
|
35
|
-
|
|
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
|
+
)
|
|
36
55
|
|
|
37
56
|
|
|
38
57
|
class CommandSubCommand(sub_command.PyntSubCommand):
|
|
@@ -51,29 +70,44 @@ class CommandSubCommand(sub_command.PyntSubCommand):
|
|
|
51
70
|
proxy_cmd.add_argument("--port", "-p", help="", type=int, default=5001)
|
|
52
71
|
proxy_cmd.add_argument("--proxy-port", help="", type=int, default=6666)
|
|
53
72
|
proxy_cmd.add_argument("--cmd", help="", default="", required=True)
|
|
54
|
-
proxy_cmd.add_argument(
|
|
73
|
+
proxy_cmd.add_argument(
|
|
74
|
+
"--captured-domains", nargs="+", help="", default="", required=False
|
|
75
|
+
)
|
|
55
76
|
proxy_cmd.add_argument("--allow-errors", action="store_true")
|
|
56
77
|
proxy_cmd.add_argument("--ca-path", type=str, default="")
|
|
57
78
|
proxy_cmd.add_argument("--report", type=str, default="")
|
|
58
|
-
proxy_cmd.add_argument(
|
|
79
|
+
proxy_cmd.add_argument(
|
|
80
|
+
"--return-error",
|
|
81
|
+
choices=["all-findings", "errors-only", "never"],
|
|
82
|
+
default="never",
|
|
83
|
+
)
|
|
59
84
|
proxy_cmd.print_usage = self.print_usage
|
|
60
85
|
proxy_cmd.print_help = self.print_usage
|
|
61
86
|
return proxy_cmd
|
|
62
87
|
|
|
63
88
|
def _updated_environment(self, args):
|
|
64
89
|
env_copy = deepcopy(os.environ)
|
|
65
|
-
return env_copy.update(
|
|
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
|
+
)
|
|
67
96
|
|
|
68
97
|
def _start_proxy(self, args):
|
|
69
|
-
res = pynt_requests.put(
|
|
98
|
+
res = pynt_requests.put(
|
|
99
|
+
self.proxy_server_base_url.format(args.port) + "/proxy/start"
|
|
100
|
+
)
|
|
70
101
|
res.raise_for_status()
|
|
71
102
|
self.scan_id = res.json()["scanId"]
|
|
72
103
|
|
|
73
104
|
def _stop_proxy(self, args):
|
|
74
105
|
start = time.time()
|
|
75
106
|
while start + self.proxy_healthcheck_buffer > time.time():
|
|
76
|
-
res = pynt_requests.put(
|
|
107
|
+
res = pynt_requests.put(
|
|
108
|
+
self.proxy_server_base_url.format(args.port) + "/proxy/stop",
|
|
109
|
+
json={"scanId": self.scan_id},
|
|
110
|
+
)
|
|
77
111
|
if res.status_code == HTTPStatus.OK:
|
|
78
112
|
return
|
|
79
113
|
time.sleep(self.proxy_sleep_interval)
|
|
@@ -81,21 +115,34 @@ class CommandSubCommand(sub_command.PyntSubCommand):
|
|
|
81
115
|
|
|
82
116
|
def _get_report(self, args, report_format):
|
|
83
117
|
while True:
|
|
84
|
-
res = pynt_requests.get(
|
|
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
|
+
)
|
|
85
123
|
if res.status_code == HTTPStatus.OK:
|
|
86
124
|
return res.text
|
|
87
125
|
if res.status_code == HTTPStatus.ACCEPTED:
|
|
88
126
|
time.sleep(self.proxy_sleep_interval)
|
|
89
127
|
continue
|
|
90
128
|
if res.status_code == 517: # pynt did not recieve any requests
|
|
91
|
-
ui_thread.print(
|
|
129
|
+
ui_thread.print(
|
|
130
|
+
ui_thread.PrinterText(
|
|
131
|
+
res.json()["message"], ui_thread.PrinterText.WARNING
|
|
132
|
+
)
|
|
133
|
+
)
|
|
92
134
|
return
|
|
93
135
|
ui_thread.print("Error in polling for scan report: {}".format(res.text))
|
|
94
136
|
return
|
|
95
137
|
|
|
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
|
+
)
|
|
99
146
|
|
|
100
147
|
if args.captured_domains:
|
|
101
148
|
for host in args.captured_domains:
|
|
@@ -103,44 +150,80 @@ class CommandSubCommand(sub_command.PyntSubCommand):
|
|
|
103
150
|
|
|
104
151
|
if "ca_path" in args and args.ca_path:
|
|
105
152
|
if not os.path.isfile(args.ca_path):
|
|
106
|
-
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
|
+
)
|
|
107
159
|
return
|
|
108
160
|
|
|
109
161
|
ca_name = os.path.basename(args.ca_path)
|
|
110
162
|
container.docker_arguments += ["--ca-path", ca_name]
|
|
111
|
-
container.mounts.append(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
+
)
|
|
117
175
|
proxy_docker.run()
|
|
118
176
|
ui_thread.print_generator(proxy_docker.stdout)
|
|
119
177
|
|
|
120
178
|
util.wait_for_healthcheck("http://localhost:{}".format(args.port))
|
|
121
179
|
|
|
122
180
|
if args.captured_domains:
|
|
123
|
-
ui_thread.print(
|
|
181
|
+
ui_thread.print(
|
|
182
|
+
"\nWill scan APIs that belong to {} domains only".format(
|
|
183
|
+
args.captured_domains
|
|
184
|
+
)
|
|
185
|
+
)
|
|
124
186
|
|
|
125
187
|
self._start_proxy(args)
|
|
126
188
|
|
|
127
|
-
user_process = Popen(
|
|
189
|
+
user_process = Popen(
|
|
190
|
+
args.cmd,
|
|
191
|
+
shell=True,
|
|
192
|
+
stdout=PIPE,
|
|
193
|
+
stderr=PIPE,
|
|
194
|
+
env=self._updated_environment(args),
|
|
195
|
+
)
|
|
128
196
|
ui_thread.print_generator(user_process.stdout)
|
|
129
197
|
ui_thread.print_generator(user_process.stderr)
|
|
130
198
|
rc = user_process.wait()
|
|
131
199
|
if rc != 0 and not args.allow_errors:
|
|
132
200
|
proxy_docker.stop()
|
|
133
|
-
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
|
+
)
|
|
134
208
|
return
|
|
135
209
|
|
|
136
210
|
self._stop_proxy(args)
|
|
137
211
|
|
|
138
|
-
with ui_thread.progress(
|
|
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
|
+
):
|
|
139
218
|
html_report = self._get_report(args, "html")
|
|
140
|
-
html_report_path = os.path.join(
|
|
219
|
+
html_report_path = os.path.join(
|
|
220
|
+
tempfile.gettempdir(), "pynt_report_{}.html".format(int(time.time()))
|
|
221
|
+
)
|
|
141
222
|
|
|
142
223
|
json_report = self._get_report(args, "json")
|
|
143
|
-
json_report_path = os.path.join(
|
|
224
|
+
json_report_path = os.path.join(
|
|
225
|
+
tempfile.gettempdir(), "pynt_report_{}.json".format(int(time.time()))
|
|
226
|
+
)
|
|
144
227
|
|
|
145
228
|
if "report" in args and args.report:
|
|
146
229
|
full_path = os.path.abspath(args.report)
|
pyntcli/commands/har.py
CHANGED
|
@@ -10,17 +10,26 @@ from pyntcli.commands import sub_command, util
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def har_usage():
|
|
13
|
-
return
|
|
14
|
-
.
|
|
15
|
-
.with_line("Usage:", style=ui_thread.PrinterText.HEADER) \
|
|
16
|
-
.with_line("\tpynt har [OPTIONS]") \
|
|
17
|
-
.with_line("") \
|
|
18
|
-
.with_line("Options:", style=ui_thread.PrinterText.HEADER) \
|
|
19
|
-
.with_line("\t--har - Path to har file") \
|
|
20
|
-
.with_line("\t--captured-domains - Pynt will scan only these domains and subdomains. For all domains write \"*\"") \
|
|
21
|
-
.with_line("\t--reporters output results to json") \
|
|
22
|
-
.with_line("\t--host-ca - path to the CA file in PEM format to enable SSL certificate verification for pynt when running through a VPN.") \
|
|
13
|
+
return (
|
|
14
|
+
ui_thread.PrinterText("Integration with static har file testing")
|
|
23
15
|
.with_line("")
|
|
16
|
+
.with_line("Usage:", style=ui_thread.PrinterText.HEADER)
|
|
17
|
+
.with_line("\tpynt har [OPTIONS]")
|
|
18
|
+
.with_line("")
|
|
19
|
+
.with_line("Options:", style=ui_thread.PrinterText.HEADER)
|
|
20
|
+
.with_line("\t--har - Path to har file")
|
|
21
|
+
.with_line(
|
|
22
|
+
'\t--captured-domains - Pynt will scan only these domains and subdomains. For all domains write "*"'
|
|
23
|
+
)
|
|
24
|
+
.with_line("\t--reporters - Output results to json")
|
|
25
|
+
.with_line(
|
|
26
|
+
"\t--application-id - Attach the scan to an application, you can find the ID in your applications area at app.pynt.io"
|
|
27
|
+
)
|
|
28
|
+
.with_line(
|
|
29
|
+
"\t--host-ca - Path to the CA file in PEM format to enable SSL certificate verification for pynt when running through a VPN."
|
|
30
|
+
)
|
|
31
|
+
.with_line("")
|
|
32
|
+
)
|
|
24
33
|
|
|
25
34
|
|
|
26
35
|
class HarSubCommand(sub_command.PyntSubCommand):
|
|
@@ -33,23 +42,36 @@ class HarSubCommand(sub_command.PyntSubCommand):
|
|
|
33
42
|
def add_cmd(self, parent: argparse._SubParsersAction) -> argparse.ArgumentParser:
|
|
34
43
|
har_cmd = parent.add_parser(self.name)
|
|
35
44
|
har_cmd.add_argument("--har", type=str, required=True)
|
|
36
|
-
har_cmd.add_argument(
|
|
37
|
-
|
|
45
|
+
har_cmd.add_argument(
|
|
46
|
+
"--captured-domains", nargs="+", help="", default="", required=True
|
|
47
|
+
)
|
|
48
|
+
har_cmd.add_argument("--reporters", action="store_true")
|
|
38
49
|
har_cmd.print_usage = self.usage
|
|
39
50
|
har_cmd.print_help = self.usage
|
|
40
51
|
return har_cmd
|
|
41
52
|
|
|
42
53
|
def run_cmd(self, args: argparse.Namespace):
|
|
43
54
|
port = str(util.find_open_port())
|
|
44
|
-
container = pynt_container.get_container_with_arguments(
|
|
55
|
+
container = pynt_container.get_container_with_arguments(
|
|
56
|
+
args, pynt_container.PyntDockerPort(src=port, dest=port, name="--port")
|
|
57
|
+
)
|
|
45
58
|
|
|
46
59
|
if not os.path.isfile(args.har):
|
|
47
|
-
ui_thread.print(
|
|
60
|
+
ui_thread.print(
|
|
61
|
+
ui_thread.PrinterText(
|
|
62
|
+
"Could not find the provided har path, please provide with a valid har path",
|
|
63
|
+
ui_thread.PrinterText.WARNING,
|
|
64
|
+
)
|
|
65
|
+
)
|
|
48
66
|
return
|
|
49
67
|
|
|
50
68
|
har_name = os.path.basename(args.har)
|
|
51
69
|
container.docker_arguments += ["--har", har_name]
|
|
52
|
-
container.mounts.append(
|
|
70
|
+
container.mounts.append(
|
|
71
|
+
pynt_container.create_mount(
|
|
72
|
+
os.path.abspath(args.har), "/etc/pynt/{}".format(har_name)
|
|
73
|
+
)
|
|
74
|
+
)
|
|
53
75
|
|
|
54
76
|
for host in args.captured_domains:
|
|
55
77
|
container.docker_arguments += ["--host-targets", host]
|
|
@@ -58,16 +80,25 @@ class HarSubCommand(sub_command.PyntSubCommand):
|
|
|
58
80
|
|
|
59
81
|
container.mounts += m
|
|
60
82
|
|
|
61
|
-
har_docker = pynt_container.PyntContainer(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
83
|
+
har_docker = pynt_container.PyntContainer(
|
|
84
|
+
image_name=pynt_container.PYNT_DOCKER_IMAGE,
|
|
85
|
+
tag="har-latest",
|
|
86
|
+
detach=True,
|
|
87
|
+
base_container=container,
|
|
88
|
+
)
|
|
65
89
|
|
|
66
90
|
har_docker.run()
|
|
67
91
|
|
|
68
|
-
healthcheck = partial(
|
|
92
|
+
healthcheck = partial(
|
|
93
|
+
util.wait_for_healthcheck, "http://localhost:{}".format(port)
|
|
94
|
+
)
|
|
69
95
|
ui_thread.print_generator(ui_thread.AnsiText.wrap_gen(har_docker.stdout))
|
|
70
96
|
|
|
71
|
-
with ui_thread.progress(
|
|
97
|
+
with ui_thread.progress(
|
|
98
|
+
"ws://localhost:{}/progress".format(port),
|
|
99
|
+
healthcheck,
|
|
100
|
+
"scan in progress...",
|
|
101
|
+
100,
|
|
102
|
+
):
|
|
72
103
|
while har_docker.is_alive():
|
|
73
104
|
time.sleep(1)
|
pyntcli/commands/listen.py
CHANGED
|
@@ -17,20 +17,37 @@ from pyntcli.transport import pynt_requests
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def listen_usage():
|
|
20
|
-
return
|
|
21
|
-
.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
.with_line("")
|
|
25
|
-
.with_line("
|
|
26
|
-
.with_line("\
|
|
27
|
-
.with_line("
|
|
28
|
-
.with_line("
|
|
29
|
-
.with_line(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
.with_line("\t--
|
|
33
|
-
.with_line("\t--
|
|
20
|
+
return (
|
|
21
|
+
ui_thread.PrinterText(
|
|
22
|
+
"Listen integration to Pynt. Run a security scan with routed traffic."
|
|
23
|
+
)
|
|
24
|
+
.with_line("")
|
|
25
|
+
.with_line("Usage:", style=ui_thread.PrinterText.HEADER)
|
|
26
|
+
.with_line("\tpynt listen [OPTIONS]")
|
|
27
|
+
.with_line("")
|
|
28
|
+
.with_line("Options:", style=ui_thread.PrinterText.HEADER)
|
|
29
|
+
.with_line(
|
|
30
|
+
'\t--captured-domains - Pynt will scan only these domains and subdomains. For all domains write "*"'
|
|
31
|
+
)
|
|
32
|
+
.with_line("\t--port - Set the port pynt will listen to (DEFAULT: 5001)")
|
|
33
|
+
.with_line("\t--ca-path - The path to the CA file in PEM format")
|
|
34
|
+
.with_line(
|
|
35
|
+
"\t--proxy-port - Set the port proxied traffic should be routed to (DEFAULT: 6666)"
|
|
36
|
+
)
|
|
37
|
+
.with_line(
|
|
38
|
+
"\t--report - If present will save the generated report in this path."
|
|
39
|
+
)
|
|
40
|
+
.with_line(
|
|
41
|
+
"\t--application-id - Attach the scan to an application, you can find the ID in your applications area at app.pynt.io"
|
|
42
|
+
)
|
|
43
|
+
.with_line("\t--insecure - use when target uses self signed certificates")
|
|
44
|
+
.with_line(
|
|
45
|
+
"\t--host-ca - path to the CA file in PEM format to enable SSL certificate verification for pynt when running through a VPN."
|
|
46
|
+
)
|
|
47
|
+
.with_line(
|
|
48
|
+
"\t--return-error - 'all-findings' (warnings, or errors), 'errors-only', 'never' (default), "
|
|
49
|
+
)
|
|
50
|
+
)
|
|
34
51
|
|
|
35
52
|
|
|
36
53
|
class ListenSubCommand(sub_command.PyntSubCommand):
|
|
@@ -48,24 +65,35 @@ class ListenSubCommand(sub_command.PyntSubCommand):
|
|
|
48
65
|
listen_cmd = parent.add_parser(self.name)
|
|
49
66
|
listen_cmd.add_argument("--port", "-p", help="", type=int, default=5001)
|
|
50
67
|
listen_cmd.add_argument("--proxy-port", help="", type=int, default=6666)
|
|
51
|
-
listen_cmd.add_argument(
|
|
68
|
+
listen_cmd.add_argument(
|
|
69
|
+
"--captured-domains", nargs="+", help="", default="", required=True
|
|
70
|
+
)
|
|
52
71
|
listen_cmd.add_argument("--allow-errors", action="store_true")
|
|
53
72
|
listen_cmd.add_argument("--ca-path", type=str, default="")
|
|
54
73
|
listen_cmd.add_argument("--report", type=str, default="")
|
|
55
|
-
listen_cmd.add_argument(
|
|
74
|
+
listen_cmd.add_argument(
|
|
75
|
+
"--return-error",
|
|
76
|
+
choices=["all-findings", "errors-only", "never"],
|
|
77
|
+
default="never",
|
|
78
|
+
)
|
|
56
79
|
listen_cmd.print_usage = self.print_usage
|
|
57
80
|
listen_cmd.print_help = self.print_usage
|
|
58
81
|
return listen_cmd
|
|
59
82
|
|
|
60
83
|
def _start_proxy(self, args):
|
|
61
|
-
res = pynt_requests.put(
|
|
84
|
+
res = pynt_requests.put(
|
|
85
|
+
self.proxy_server_base_url.format(args.port) + "/proxy/start"
|
|
86
|
+
)
|
|
62
87
|
res.raise_for_status()
|
|
63
88
|
self.scan_id = res.json()["scanId"]
|
|
64
89
|
|
|
65
90
|
def _stop_proxy(self, args):
|
|
66
91
|
start = time.time()
|
|
67
92
|
while start + self.proxy_healthcheck_buffer > time.time():
|
|
68
|
-
res = pynt_requests.put(
|
|
93
|
+
res = pynt_requests.put(
|
|
94
|
+
self.proxy_server_base_url.format(args.port) + "/proxy/stop",
|
|
95
|
+
json={"scanId": self.scan_id},
|
|
96
|
+
)
|
|
69
97
|
if res.status_code == HTTPStatus.OK:
|
|
70
98
|
return
|
|
71
99
|
time.sleep(self.proxy_sleep_interval)
|
|
@@ -73,38 +101,62 @@ class ListenSubCommand(sub_command.PyntSubCommand):
|
|
|
73
101
|
|
|
74
102
|
def _get_report(self, args, report_format):
|
|
75
103
|
while True:
|
|
76
|
-
res = pynt_requests.get(
|
|
104
|
+
res = pynt_requests.get(
|
|
105
|
+
self.proxy_server_base_url.format(args.port)
|
|
106
|
+
+ "/report?format={}".format(report_format),
|
|
107
|
+
params={"scanId": self.scan_id},
|
|
108
|
+
)
|
|
77
109
|
if res.status_code == HTTPStatus.OK:
|
|
78
110
|
return res.text
|
|
79
111
|
if res.status_code == HTTPStatus.ACCEPTED:
|
|
80
112
|
time.sleep(self.proxy_sleep_interval)
|
|
81
113
|
continue
|
|
82
114
|
if res.status_code == 517: # pynt did not recieve any requests
|
|
83
|
-
ui_thread.print(
|
|
115
|
+
ui_thread.print(
|
|
116
|
+
ui_thread.PrinterText(
|
|
117
|
+
res.json()["message"], ui_thread.PrinterText.WARNING
|
|
118
|
+
)
|
|
119
|
+
)
|
|
84
120
|
return
|
|
85
121
|
ui_thread.print("Error in polling for scan report: {}".format(res.text))
|
|
86
122
|
return
|
|
87
123
|
|
|
88
124
|
def run_cmd(self, args: argparse.Namespace):
|
|
89
|
-
container = pynt_container.get_container_with_arguments(
|
|
90
|
-
|
|
125
|
+
container = pynt_container.get_container_with_arguments(
|
|
126
|
+
args,
|
|
127
|
+
pynt_container.PyntDockerPort(args.port, args.port, "--port"),
|
|
128
|
+
pynt_container.PyntDockerPort(
|
|
129
|
+
args.proxy_port, args.proxy_port, "--proxy-port"
|
|
130
|
+
),
|
|
131
|
+
)
|
|
91
132
|
|
|
92
133
|
for host in args.captured_domains:
|
|
93
134
|
container.docker_arguments += ["--host-targets", host]
|
|
94
135
|
|
|
95
136
|
if "ca_path" in args and args.ca_path:
|
|
96
137
|
if not os.path.isfile(args.ca_path):
|
|
97
|
-
ui_thread.print(
|
|
138
|
+
ui_thread.print(
|
|
139
|
+
ui_thread.PrinterText(
|
|
140
|
+
"Could not find the provided ca path, please provide with a valid path",
|
|
141
|
+
ui_thread.PrinterText.WARNING,
|
|
142
|
+
)
|
|
143
|
+
)
|
|
98
144
|
return
|
|
99
145
|
|
|
100
146
|
ca_name = os.path.basename(args.ca_path)
|
|
101
147
|
container.docker_arguments += ["--ca-path", ca_name]
|
|
102
|
-
container.mounts.append(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
148
|
+
container.mounts.append(
|
|
149
|
+
pynt_container.create_mount(
|
|
150
|
+
os.path.abspath(args.ca_path), "/etc/pynt/{}".format(ca_name)
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
proxy_docker = pynt_container.PyntContainer(
|
|
155
|
+
image_name=pynt_container.PYNT_DOCKER_IMAGE,
|
|
156
|
+
tag="proxy-latest",
|
|
157
|
+
detach=True,
|
|
158
|
+
base_container=container,
|
|
159
|
+
)
|
|
108
160
|
proxy_docker.run()
|
|
109
161
|
ui_thread.print_generator(proxy_docker.stdout)
|
|
110
162
|
|
|
@@ -112,22 +164,45 @@ class ListenSubCommand(sub_command.PyntSubCommand):
|
|
|
112
164
|
|
|
113
165
|
self._start_proxy(args)
|
|
114
166
|
|
|
115
|
-
ui_thread.print(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
167
|
+
ui_thread.print(
|
|
168
|
+
ui_thread.PrinterText(
|
|
169
|
+
"\nListening to traffic on port: {}".format(args.proxy_port),
|
|
170
|
+
ui_thread.PrinterText.DEFAULT,
|
|
171
|
+
)
|
|
172
|
+
.with_line(
|
|
173
|
+
"Will scan APIs that belong to '{}' domains only".format(
|
|
174
|
+
args.captured_domains
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
.with_line("")
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
ui_thread.print(
|
|
181
|
+
ui_thread.PrinterText(
|
|
182
|
+
"Press Enter to stop recording traffic and run security scan...",
|
|
183
|
+
ui_thread.PrinterText.HEADER,
|
|
184
|
+
)
|
|
185
|
+
)
|
|
120
186
|
|
|
121
187
|
input()
|
|
122
188
|
|
|
123
189
|
self._stop_proxy(args)
|
|
124
190
|
|
|
125
|
-
with ui_thread.progress(
|
|
191
|
+
with ui_thread.progress(
|
|
192
|
+
"ws://localhost:{}/progress?scanId={}".format(args.port, self.scan_id),
|
|
193
|
+
partial(lambda *args: None),
|
|
194
|
+
"scan in progress...",
|
|
195
|
+
100,
|
|
196
|
+
):
|
|
126
197
|
html_report = self._get_report(args, "html")
|
|
127
|
-
html_report_path = os.path.join(
|
|
198
|
+
html_report_path = os.path.join(
|
|
199
|
+
tempfile.gettempdir(), "pynt_report_{}.html".format(int(time.time()))
|
|
200
|
+
)
|
|
128
201
|
|
|
129
202
|
json_report = self._get_report(args, "json")
|
|
130
|
-
json_report_path = os.path.join(
|
|
203
|
+
json_report_path = os.path.join(
|
|
204
|
+
tempfile.gettempdir(), "pynt_report_{}.json".format(int(time.time()))
|
|
205
|
+
)
|
|
131
206
|
|
|
132
207
|
if "report" in args and args.report:
|
|
133
208
|
full_path = os.path.abspath(args.report)
|
pyntcli/commands/newman.py
CHANGED
|
@@ -8,32 +8,51 @@ from pyntcli.commands import sub_command, util
|
|
|
8
8
|
from pyntcli.ui import ui_thread
|
|
9
9
|
from pyntcli.ui.progress import PyntProgress
|
|
10
10
|
|
|
11
|
+
|
|
11
12
|
def newman_usage():
|
|
12
|
-
return
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
return (
|
|
14
|
+
ui_thread.PrinterText(
|
|
15
|
+
"Integration with newman, run scan using postman collection from the CLI"
|
|
16
|
+
)
|
|
17
|
+
.with_line("")
|
|
18
|
+
.with_line("Usage:", style=ui_thread.PrinterText.HEADER)
|
|
19
|
+
.with_line("\tpynt newman [OPTIONS]")
|
|
20
|
+
.with_line("")
|
|
21
|
+
.with_line("Options:", style=ui_thread.PrinterText.HEADER)
|
|
22
|
+
.with_line("\t--collection - Postman collection file name")
|
|
23
|
+
.with_line("\t--environment - Postman environment file name")
|
|
24
|
+
.with_line("\t--reporters Output results to json")
|
|
25
|
+
.with_line(
|
|
26
|
+
"\t--host-ca - Path to the CA file in PEM format to enable SSL certificate verification for pynt when running through a VPN."
|
|
27
|
+
)
|
|
28
|
+
.with_line(
|
|
29
|
+
"\t--application-id - Attach the scan to an application, you can find the ID in your applications area at app.pynt.io"
|
|
30
|
+
)
|
|
31
|
+
.with_line(
|
|
32
|
+
"\t--return-error - 'all-findings' (warnings, or errors), 'errors-only', 'never' (default), "
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
|
|
23
36
|
|
|
24
37
|
class NewmanSubCommand(sub_command.PyntSubCommand):
|
|
25
38
|
def __init__(self, name) -> None:
|
|
26
39
|
super().__init__(name)
|
|
27
40
|
|
|
28
|
-
def usage(self, *args):
|
|
41
|
+
def usage(self, *args):
|
|
29
42
|
ui_thread.print(newman_usage())
|
|
30
43
|
|
|
31
44
|
def add_cmd(self, parent: argparse._SubParsersAction) -> argparse.ArgumentParser:
|
|
32
45
|
newman_cmd = parent.add_parser(self.name)
|
|
33
|
-
newman_cmd.add_argument("--collection", type=str, required=True)
|
|
34
|
-
newman_cmd.add_argument("--environment", nargs=
|
|
35
|
-
newman_cmd.add_argument(
|
|
36
|
-
|
|
46
|
+
newman_cmd.add_argument("--collection", type=str, required=True)
|
|
47
|
+
newman_cmd.add_argument("--environment", nargs="+", required=False)
|
|
48
|
+
newman_cmd.add_argument(
|
|
49
|
+
"--reporters", action="store_true", default=False, required=False
|
|
50
|
+
)
|
|
51
|
+
newman_cmd.add_argument(
|
|
52
|
+
"--return-error",
|
|
53
|
+
choices=["all-findings", "errors-only", "never"],
|
|
54
|
+
default="never",
|
|
55
|
+
)
|
|
37
56
|
|
|
38
57
|
newman_cmd.print_usage = self.usage
|
|
39
58
|
newman_cmd.print_help = self.usage
|
|
@@ -41,38 +60,67 @@ class NewmanSubCommand(sub_command.PyntSubCommand):
|
|
|
41
60
|
|
|
42
61
|
def run_cmd(self, args: argparse.Namespace):
|
|
43
62
|
port = str(util.find_open_port())
|
|
44
|
-
container = pynt_container.get_container_with_arguments(
|
|
63
|
+
container = pynt_container.get_container_with_arguments(
|
|
64
|
+
args, pynt_container.PyntDockerPort(src=port, dest=port, name="--port")
|
|
65
|
+
)
|
|
45
66
|
|
|
46
|
-
if not os.path.isfile(args.collection):
|
|
47
|
-
ui_thread.print(
|
|
67
|
+
if not os.path.isfile(args.collection):
|
|
68
|
+
ui_thread.print(
|
|
69
|
+
ui_thread.PrinterText(
|
|
70
|
+
"Could not find the provided collection path, please provide with a valid collection path",
|
|
71
|
+
ui_thread.PrinterText.WARNING,
|
|
72
|
+
)
|
|
73
|
+
)
|
|
48
74
|
return
|
|
49
|
-
|
|
75
|
+
|
|
50
76
|
collection_name = os.path.basename(args.collection)
|
|
51
77
|
container.docker_arguments += ["-c", collection_name]
|
|
52
|
-
container.mounts.append(
|
|
53
|
-
|
|
78
|
+
container.mounts.append(
|
|
79
|
+
pynt_container.create_mount(
|
|
80
|
+
os.path.abspath(args.collection), "/etc/pynt/{}".format(collection_name)
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
|
|
54
84
|
if "environment" in args and args.environment:
|
|
55
85
|
env_names = []
|
|
56
86
|
for environ in args.environment:
|
|
57
87
|
if not os.path.isfile(environ):
|
|
58
|
-
ui_thread.print(
|
|
88
|
+
ui_thread.print(
|
|
89
|
+
ui_thread.PrinterText(
|
|
90
|
+
f"Could not find the provided environment path: {environ}, please provide with a valid environment path",
|
|
91
|
+
ui_thread.PrinterText.WARNING,
|
|
92
|
+
)
|
|
93
|
+
)
|
|
59
94
|
return
|
|
60
95
|
env_name = os.path.basename(environ)
|
|
61
96
|
env_names.append(env_name)
|
|
62
|
-
container.mounts.append(
|
|
97
|
+
container.mounts.append(
|
|
98
|
+
pynt_container.create_mount(
|
|
99
|
+
os.path.abspath(environ), "/etc/pynt/{}".format(env_name)
|
|
100
|
+
)
|
|
101
|
+
)
|
|
63
102
|
container.docker_arguments += ["-e", ",".join(env_names)]
|
|
64
|
-
|
|
103
|
+
|
|
65
104
|
with util.create_default_file_mounts(args) as m:
|
|
66
105
|
container.mounts += m
|
|
67
|
-
newman_docker = pynt_container.PyntContainer(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
106
|
+
newman_docker = pynt_container.PyntContainer(
|
|
107
|
+
image_name=pynt_container.PYNT_DOCKER_IMAGE,
|
|
108
|
+
tag="latest",
|
|
109
|
+
detach=True,
|
|
110
|
+
base_container=container,
|
|
111
|
+
)
|
|
71
112
|
newman_docker.run()
|
|
72
|
-
|
|
73
|
-
healthcheck = partial(
|
|
113
|
+
|
|
114
|
+
healthcheck = partial(
|
|
115
|
+
util.wait_for_healthcheck, "http://localhost:{}".format(port)
|
|
116
|
+
)
|
|
74
117
|
ui_thread.print_generator(ui_thread.AnsiText.wrap_gen(newman_docker.stdout))
|
|
75
|
-
|
|
76
|
-
with ui_thread.progress(
|
|
118
|
+
|
|
119
|
+
with ui_thread.progress(
|
|
120
|
+
"ws://localhost:{}/progress".format(port),
|
|
121
|
+
healthcheck,
|
|
122
|
+
"scan in progress...",
|
|
123
|
+
100,
|
|
124
|
+
):
|
|
77
125
|
while newman_docker.is_alive():
|
|
78
126
|
time.sleep(1)
|
pyntcli/commands/postman.py
CHANGED
|
@@ -112,7 +112,7 @@ class PostmanSubCommand(sub_command.PyntSubCommand):
|
|
|
112
112
|
html_report_path = os.path.join(tempfile.gettempdir(), "pynt_report_{}.html".format(int(time.time())))
|
|
113
113
|
|
|
114
114
|
if html_report:
|
|
115
|
-
with open(html_report_path, "w") as html_file:
|
|
115
|
+
with open(html_report_path, "w", encoding="utf-8") as html_file:
|
|
116
116
|
html_file.write(html_report)
|
|
117
117
|
webbrowser.open("file://{}".format(html_report_path))
|
|
118
118
|
|
pyntcli/commands/root.py
CHANGED
|
@@ -7,33 +7,48 @@ from pyntcli.auth import login
|
|
|
7
7
|
from pyntcli.ui import ui_thread
|
|
8
8
|
from pyntcli.analytics import send as analytics
|
|
9
9
|
|
|
10
|
+
|
|
10
11
|
def root_usage():
|
|
11
|
-
return
|
|
12
|
-
.
|
|
13
|
-
.with_line("")
|
|
14
|
-
.with_line("
|
|
15
|
-
.with_line("
|
|
16
|
-
.with_line(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
.with_line("
|
|
12
|
+
return (
|
|
13
|
+
ui_thread.PrinterText("Usage:", style=ui_thread.PrinterText.HEADER)
|
|
14
|
+
.with_line("\tpynt [COMMAND] [OPTIONS]")
|
|
15
|
+
.with_line("")
|
|
16
|
+
.with_line("Commands:", style=ui_thread.PrinterText.HEADER)
|
|
17
|
+
.with_line(
|
|
18
|
+
"\tpostman - integration with postman, run scan from pynt postman collection"
|
|
19
|
+
)
|
|
20
|
+
.with_line("\tnewman - run postman collection from the CLI")
|
|
21
|
+
.with_line("\thar - run scan on static har file")
|
|
22
|
+
.with_line("\tcommand - run scan with a given command")
|
|
23
|
+
.with_line("\tlisten - run scan with a routed traffic")
|
|
24
|
+
.with_line("\tburp - run scan on a burp xml output file")
|
|
25
|
+
.with_line(
|
|
26
|
+
"\tpynt-id - view your pynt-id to use when running pynt in CI pipeline"
|
|
27
|
+
)
|
|
28
|
+
.with_line("")
|
|
29
|
+
.with_line(
|
|
30
|
+
"Run pynt [COMMAND] -h to get help on a specific command",
|
|
31
|
+
style=ui_thread.PrinterText.INFO,
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
|
|
20
35
|
|
|
21
36
|
def usage(*args):
|
|
22
37
|
ui_thread.print(root_usage())
|
|
23
38
|
|
|
24
39
|
|
|
25
40
|
def check_cicd_context():
|
|
26
|
-
if environ.get(
|
|
41
|
+
if environ.get("GITHUB_ACTION") is not None:
|
|
27
42
|
analytics.emit(analytics.CICD, {"env": "GitHub"})
|
|
28
43
|
return
|
|
29
|
-
if environ.get(
|
|
44
|
+
if environ.get("GITLAB_USER_ID") is not None:
|
|
30
45
|
analytics.emit(analytics.CICD, {"env": "GitLab"})
|
|
31
46
|
return
|
|
32
|
-
if environ.get(
|
|
47
|
+
if environ.get("JENKINS_HOME") is not None:
|
|
33
48
|
analytics.emit(analytics.CICD, {"env": "Jenkins"})
|
|
34
|
-
|
|
35
49
|
|
|
36
|
-
|
|
50
|
+
|
|
51
|
+
class BaseCommand:
|
|
37
52
|
def __init__(self) -> None:
|
|
38
53
|
self.base: argparse.ArgumentParser = None
|
|
39
54
|
self.subparser: argparse._SubParsersAction = None
|
|
@@ -41,15 +56,21 @@ class BaseCommand():
|
|
|
41
56
|
def cmd(self):
|
|
42
57
|
if self.base:
|
|
43
58
|
return self.base
|
|
44
|
-
|
|
59
|
+
|
|
45
60
|
self.base = argparse.ArgumentParser(prog="pynt")
|
|
46
61
|
self.base.print_usage = usage
|
|
47
62
|
self.base.print_help = usage
|
|
48
|
-
return self.base
|
|
49
|
-
|
|
63
|
+
return self.base
|
|
64
|
+
|
|
50
65
|
def add_base_arguments(self, parser):
|
|
51
|
-
parser.add_argument(
|
|
52
|
-
|
|
66
|
+
parser.add_argument(
|
|
67
|
+
"--insecure",
|
|
68
|
+
default=False,
|
|
69
|
+
required=False,
|
|
70
|
+
action="store_true",
|
|
71
|
+
help="use when target uses self signed certificates",
|
|
72
|
+
)
|
|
73
|
+
parser.add_argument("--dev-flags", type=str, default="", help=argparse.SUPPRESS)
|
|
53
74
|
parser.add_argument("--host-ca", type=str, default="")
|
|
54
75
|
parser.add_argument("--transport-config", type=str, default="")
|
|
55
76
|
parser.add_argument("--application-id", type=str, default="", required=False)
|
|
@@ -58,7 +79,7 @@ class BaseCommand():
|
|
|
58
79
|
def get_subparser(self) -> argparse._SubParsersAction:
|
|
59
80
|
if self.subparser is None:
|
|
60
81
|
self.subparser = self.base.add_subparsers(help="", dest="command")
|
|
61
|
-
|
|
82
|
+
|
|
62
83
|
return self.subparser
|
|
63
84
|
|
|
64
85
|
def run_cmd(self, args: argparse.Namespace):
|
|
@@ -67,11 +88,11 @@ class BaseCommand():
|
|
|
67
88
|
if login.should_login():
|
|
68
89
|
l = login.Login().login()
|
|
69
90
|
else:
|
|
70
|
-
login.refresh_token()
|
|
71
|
-
|
|
91
|
+
login.refresh_token()
|
|
92
|
+
|
|
72
93
|
analytics.emit(analytics.LOGIN_DONE)
|
|
73
94
|
user_id = login.user_id()
|
|
74
95
|
if user_id:
|
|
75
96
|
analytics.set_user_id(user_id)
|
|
76
97
|
|
|
77
|
-
check_cicd_context()
|
|
98
|
+
check_cicd_context()
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
pyntcli/__init__.py,sha256=
|
|
1
|
+
pyntcli/__init__.py,sha256=B2xptQoAZtCL_O_fLMSWF71JDtlCZp8jLmdtHyMdosE,23
|
|
2
2
|
pyntcli/main.py,sha256=cqratxd_ZJXLsP4xRbF2fmwj3Eo4kQ8WlbrwUtfZQ94,4125
|
|
3
3
|
pyntcli/analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
pyntcli/analytics/send.py,sha256=ewNAKnn3KbKwfDuOasgAsSGbUWILLD8rPaXIAlLucnM,2507
|
|
5
5
|
pyntcli/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
pyntcli/auth/login.py,sha256=WgF5r00bpM4c1__thzD6zCJ6207qxgtT0ixPvD1cEHA,5109
|
|
7
7
|
pyntcli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
pyntcli/commands/burp.py,sha256=
|
|
9
|
-
pyntcli/commands/command.py,sha256=
|
|
10
|
-
pyntcli/commands/har.py,sha256=
|
|
8
|
+
pyntcli/commands/burp.py,sha256=wd-HuNyj6v6IHk6x9-DAaqXcm1cQa-RA5FlxveBd6wA,10427
|
|
9
|
+
pyntcli/commands/command.py,sha256=tSjJj0fhQt2lCvtdu8H3IuiBlAllesx4f6ptNfuinTw,9435
|
|
10
|
+
pyntcli/commands/har.py,sha256=Iq0455umKvlcMxcU4l70-NIZh7Wovry3f76u68drmQ4,3654
|
|
11
11
|
pyntcli/commands/id_command.py,sha256=2J5oEa39uyBMMSCRHzuKh3_11CeUEYi7YLoBk1yuu_I,942
|
|
12
|
-
pyntcli/commands/listen.py,sha256=
|
|
13
|
-
pyntcli/commands/newman.py,sha256=
|
|
14
|
-
pyntcli/commands/postman.py,sha256=
|
|
12
|
+
pyntcli/commands/listen.py,sha256=aANo5DwZ6_TAuXdJX3L8eDk9I6ompPAqtaUgxnEbR54,8536
|
|
13
|
+
pyntcli/commands/newman.py,sha256=_AspmTk98nY0Ct26WNK4fFgoYbqzmU90FfkygxJjgjY,4828
|
|
14
|
+
pyntcli/commands/postman.py,sha256=x5sPR-KaawKzAkLRK9vJXDzZ3sx9-6TuH6SNd5tnM4Y,4946
|
|
15
15
|
pyntcli/commands/pynt_cmd.py,sha256=NkzT8X4mI_fhzui_9KdAQXY5AnMGdoQkzDH6pcreLHU,2453
|
|
16
|
-
pyntcli/commands/root.py,sha256=
|
|
16
|
+
pyntcli/commands/root.py,sha256=FEf2gXvFyO_RZi6XzFakSVnOQw_sGBopY5SL5gldudg,3248
|
|
17
17
|
pyntcli/commands/sub_command.py,sha256=GF3-rE_qk2L4jGPFqHLm9SdGINmu3EakhjJTFyWjRms,374
|
|
18
18
|
pyntcli/commands/util.py,sha256=IREZvHYJ7wEth8ujOkk5ZXZ4UrUA7jDv134_UueED4s,2995
|
|
19
19
|
pyntcli/log/__init__.py,sha256=cOGwOYzMoshEbZiiasBGkj6wF0SBu3Jdpl-AuakDesw,19
|
|
@@ -34,8 +34,8 @@ pyntcli/ui/ui_thread.py,sha256=OVTbiIFMg2KgxAvHf7yy86xGm4RVS2vj_VYZkMi-SRY,4956
|
|
|
34
34
|
tests/conftest.py,sha256=gToq5K74GtgeGQXjFvXSzMaE6axBYxAzcFG5XJPOXjI,427
|
|
35
35
|
tests/auth/test_login.py,sha256=M6JRFTQRZrL6M2-iph_r-aBSQMMiFDncQbVYeObBFYU,3296
|
|
36
36
|
tests/store/test_cred_store.py,sha256=_7-917EtNC9eKEumO2_lt-7KuDmCwOZFaowCm7DbA_A,254
|
|
37
|
-
pyntcli-0.1.
|
|
38
|
-
pyntcli-0.1.
|
|
39
|
-
pyntcli-0.1.
|
|
40
|
-
pyntcli-0.1.
|
|
41
|
-
pyntcli-0.1.
|
|
37
|
+
pyntcli-0.1.73.dist-info/METADATA,sha256=YG6eADsoX_vyR3rTTkWvpkrKZeY8okAneTVRDvu3yZU,463
|
|
38
|
+
pyntcli-0.1.73.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
39
|
+
pyntcli-0.1.73.dist-info/entry_points.txt,sha256=kcGmqAxXDttNk2EPRcqunc_LTVp61gzakz0v-GEE2SY,43
|
|
40
|
+
pyntcli-0.1.73.dist-info/top_level.txt,sha256=u9MDStwVHB7UG8PUcODeWCul_NvzL2EzoLvSlgwLHFs,30
|
|
41
|
+
pyntcli-0.1.73.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|