Skip to content

Commit 5f79a6b

Browse files
authored
Merge pull request #27 from Logisek/develop
Merge to main
2 parents 2116f55 + 5290e0b commit 5f79a6b

17 files changed

+571
-335
lines changed

README.md

+18-12
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
_____ _
2-
(____ \ | |_
3-
_ \ \ ____| | |_ ____ ___ ____ ____ ____
4-
| | | / _ ) | _)/ _ |/___)/ ___) _ | _ \
5-
| |__/ ( (/ /| | |_( ( | |___ ( (__( ( | | | | |
6-
|_____/ \____)_|\___)_||_(___/ \____)_||_|_| |_|
7-
8-
DeltaScan is an advanced port scanning tool designed to detect and report changes in open ports and services over time. It offers scan results manipulation functionalities like export, import, differential check between scans and an interactive shell.
1+
## Deltascan
2+
3+
DeltaScan is an advanced port scanning tool designed to detect and report changes in open ports and services over time. It offers scan results manipulation functionalities like export, import, differential check between scans and an interactive shell.
4+
5+
##### View scan results
6+
![Use screenshot](images/example_scr1.png?raw=true "Title")
7+
8+
##### View diff results
9+
![Use screenshot](images/example_scr2.png?raw=true "Title")
10+
911
### Installation
1012
Install `pipenv`:
1113
```bash
@@ -76,7 +78,7 @@ options:
7678
select scanning profile that exists in config file or already in database
7779
-c CONF_FILE, --conf-file CONF_FILE
7880
path to configuration file
79-
-s, --suppress suppress output
81+
-v, --verbose verbose output
8082
--n-scans N_SCANS limit of scans databse queries. It is applied in scans view as well as scans diff
8183
--n-diffs N_DIFFS limit of the diff results
8284
--from-date FROM_DATE
@@ -150,6 +152,10 @@ sudo -E env PATH=${PATH} deltascan diff -c config.yaml -p MY_PROFILE --from-date
150152
# The below command uses a custom template file (it has to be an .html file)
151153
sudo -E env PATH=${PATH} deltascan diff -c config.yaml -p MY_PROFILE --from-date "2024-01-01 10:00:00" --to-date "2024-01-02 10:00:00" --n-scans 20 --n-diffs -2 -t 192.168.0.100 --template your_template.html
152154
```
155+
Diff raw, nmap, comma separated files and dump them in json file:
156+
```bash
157+
sudo -E env PATH=${PATH} deltascan diff --diff-files tcp_services_10.10.10.1.xml,tcp_services_10.10.10.2.xml -o dump.json
158+
```
153159

154160
##### View:
155161
Listing scan results is a simple query to the deltascan database. The query takes into account the given parameters (`host`, `profile`, `--from-date`, `--to-date`, `--port-type`)
@@ -200,13 +206,13 @@ deltascan>: conf # Display current configuration
200206
n_diffs: 1
201207
From date [fdate]: None
202208
To date [tdate]: None
203-
suppress: False
209+
verbose: True
204210
host: 0.0.0.0
205211
profile: None
206-
deltascan>: conf suppress=true # Modify configuration value
212+
deltascan>: conf verbose=true # Modify configuration value
207213
deltascan>: view # View result based on current configuration parameters
208214
# ... Results ...
209-
deltascan>: diff 1,2 # Difference between previous view results (always use suppress=True to find diff indexes)
215+
deltascan>: diff 1,2 # Difference between previous view results (always use verbose=False to find diff indexes)
210216
deltascan>: imp nmap_dump_file.0.0.0.0.xml # Import nmap dump file
211217
deltascan>: imp nmap_dump_file.0.0.0.0.csv # Import deltascan csv exported file
212218
deltascan>: report # Report last results (must set an output_file before with: conf output_file=filename.(html|pdf|csv))

config.yaml

+6-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@ profiles:
55
arguments: "-vv -n -sn -PA21,22,23,25,53,80,88,110,111,135,139,143,199,443,445,465,587,993,995,1025,1433,1723,3306,3389,5900,8080,8443"
66
TCP_PORTS_TOP_1000_NO_PING_NO_DNS:
77
arguments: "-sS -n -Pn -vv --top-ports 1000 --reason --open"
8+
TCP_PORT_80:
9+
arguments: "-sS -n -Pn -vv -p 80"
10+
TCP_PORT_80_SCRIPTS:
11+
arguments: "-sS -vv -n -A --osscan-guess --version-all -Pn -p 80 --script=default,safe,discovery,external,vuln"
12+
TCP_PORT_80_OS_SERVICE_DETECTION:
13+
arguments: "-sS -n -Pn -vv -p 80 -O -sV"
814
TCP_PORTS_FULL_NO_PING_NO_DNS:
915
arguments: "-sS -n -Pn -vv -p- --reason --open"
10-
tcp-services-scan-noping:
11-
arguments: "-sS -vv -A --osscan-guess --version-all --top-ports 1000"
1216
UDP_PORTS_TOP_1000_NO_PING_NO_DNS:
1317
arguments: "-sU -n -Pn -vv --top-ports 1000 --reason --open"
1418
UDP_PORTS_FULL_NO_PING_NO_DNS:

deltascan/cli/cli_output.py

+31-52
Original file line numberDiff line numberDiff line change
@@ -212,59 +212,38 @@ def _display_scan_diffs(self):
212212
"for the given arguments[/]")
213213
return [table]
214214

215-
if self.verbose is False:
216-
_sup_table = Table(show_header=True)
217-
_sup_table.add_column("Host", style=colors[0], no_wrap=True)
218-
_sup_table.add_column("Dates", style=colors[1], no_wrap=True)
219-
_sup_table.add_column("Scan uuids", style=colors[0], no_wrap=True)
220-
_sup_table.add_column("Profile", style=colors[1], no_wrap=True)
221-
_sup_table.add_column("Arguments", style=colors[0], no_wrap=True)
222-
223215
for row in self.data:
224-
if self.verbose is True:
225-
table = Table()
226-
table.title = f"[dim]Host: [/][rosy_brown]" \
227-
f"{self._print_generic_information_if_different(row['generic'][1]['host'], row['generic'][0]['host'])}[/]\n" \
228-
f"[dim]Dates: [/][rosy_brown]{self._print_is_today(row['date_from'])} " \
229-
f"[red]->[/] {self._print_is_today(row['date_to'])}[/]\n" \
230-
f"[dim]Scan uuids: [/][rosy_brown]{self._print_generic_information_if_different(row['uuids'][1], row['uuids'][0])}[/]\n" \
231-
f"[dim]Profile: [/][rosy_brown]" \
232-
f"{self._print_generic_information_if_different(row['generic'][1]['profile_name'], row['generic'][0]['profile_name'])}[/]\n" \
233-
f"[dim]Arguments: [/][rosy_brown]" \
234-
f"{self._print_generic_information_if_different(row['generic'][1]['arguments'], row['generic'][0]['arguments'])}[/]"
235-
c = 0
236-
_w = 20
237-
for _, f in enumerate(field_names):
238-
if "field" in f:
239-
_w = 35
240-
else:
241-
_w = 53
242-
243-
table.add_column(format_string(f), style=colors[c], no_wrap=True, width=_w)
244-
c = 0 if c >= len(colors)-1 else c+1
245-
lines = self._construct_exported_diff_data(row, field_names)
246-
247-
for r in lines:
248-
fields = self._dict_diff_fields_to_list(r)
249-
table.add_row(*fields)
250-
table.border_style = "dim"
251-
table.title_justify = "left"
252-
table.caption_justify = "left"
253-
table.leading = False
254-
table.title_style = "frame"
255-
tables.append(table)
256-
else:
257-
_sup_table.add_row(
258-
row['generic']['host'],
259-
self._print_is_today(row['date_from']),
260-
f"{row['uuids'][1]} -> {row['uuids'][0]}",
261-
row['generic']['profile_name'],
262-
row['generic']['arguments']
263-
)
264-
265-
if self.verbose is False:
266-
tables.append(_sup_table)
267-
216+
table = Table()
217+
table.title = f"[dim]Host: [/][rosy_brown]" \
218+
f"{self._print_generic_information_if_different(row['generic'][1]['host'], row['generic'][0]['host'])}[/]\n" \
219+
f"[dim]Dates: [/][rosy_brown]{self._print_is_today(row['date_from'])} " \
220+
f"[red]->[/] {self._print_is_today(row['date_to'])}[/]\n" \
221+
f"[dim]Scan uuids: [/][rosy_brown]{self._print_generic_information_if_different(row['uuids'][1], row['uuids'][0])}[/]\n" \
222+
f"[dim]Profile: [/][rosy_brown]" \
223+
f"{self._print_generic_information_if_different(row['generic'][1]['profile_name'], row['generic'][0]['profile_name'])}[/]\n" \
224+
f"[dim]Arguments: [/][rosy_brown]" \
225+
f"{self._print_generic_information_if_different(row['generic'][1]['arguments'], row['generic'][0]['arguments'])}[/]"
226+
c = 0
227+
_w = 20
228+
for _, f in enumerate(field_names):
229+
if "field" in f:
230+
_w = 35
231+
else:
232+
_w = 53
233+
234+
table.add_column(format_string(f), style=colors[c], no_wrap=True, width=_w)
235+
c = 0 if c >= len(colors)-1 else c+1
236+
lines = self._construct_exported_diff_data(row, field_names)
237+
238+
for r in lines:
239+
fields = self._dict_diff_fields_to_list(r)
240+
table.add_row(*fields)
241+
table.border_style = "dim"
242+
table.title_justify = "left"
243+
table.caption_justify = "left"
244+
table.leading = False
245+
table.title_style = "frame"
246+
tables.append(table)
268247
return tables
269248

270249
def _dict_diff_fields_to_list(self, diff_dict):

deltascan/cli/cmd.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ def do_conf(self, v):
119119
print(f"{'From date [fdate]: ' + '':<20} {self._app.fdate}")
120120
if conf_key == "tdate" or conf_key == "":
121121
print(f"{'To date [tdate]: ' + '':<20} {self._app.tdate}")
122-
if conf_key == "suppress" or conf_key == "":
123-
print(f"{'suppress: ' + '':<20} {self._app.suppress}")
122+
if conf_key == "verbose" or conf_key == "":
123+
print(f"{'verbose: ' + '':<20} {self._app.verbose}")
124124
if conf_key == "host" or conf_key == "":
125125
print(f"{'host: ' + '':<20} {self._app.host}")
126126
if conf_key == "profile" or conf_key == "":
@@ -147,8 +147,8 @@ def __norm_value(v):
147147
self._app.fdate = __norm_value(conf_value)
148148
elif conf_key == "tdate":
149149
self._app.tdate = __norm_value(conf_value)
150-
elif conf_key == "suppress":
151-
self._app.suppress = False if __norm_value(conf_value).lower() == "false" else True
150+
elif conf_key == "verbose":
151+
self._app.verbose = False if __norm_value(conf_value).lower() == "false" else True
152152
elif conf_key == "host":
153153
self._app.host = __norm_value(conf_value)
154154
elif conf_key == "profile":

deltascan/core/deltascan.py

+10-9
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,6 @@ def diffs(self, uuids=None):
416416
diffs = self._list_scans_with_diffs([_s for _scans in _split_scans_in_hosts.values() for _s in _scans])
417417
if self._config.output_file is not None and self._config.is_interactive is False:
418418
self._report_diffs(diffs, output_file=f"diffs_{self._config.output_file}")
419-
420419
# getting the current date and time in order not to override existing files
421420
_now = datetime.now().strftime(FILE_DATE_FORMAT)
422421
self._result.append({
@@ -563,8 +562,7 @@ def _list_scans_with_diffs(self, scans):
563562
for i, _ in enumerate(scans, 1):
564563
if i == len(scans) or len(scan_list_diffs) == self._config.n_diffs:
565564
break
566-
if (scans[i-1]["result_hash"] != scans[i]["result_hash"] or scans[i-1]["results"] !=
567-
scans[i]["results"]) and scans[i-1]["results"]["host"] == scans[i]["results"]["host"]:
565+
if scans[i-1]["result_hash"] != scans[i]["result_hash"] and scans[i-1]["results"]["host"] == scans[i]["results"]["host"]:
568566
try:
569567
scan_list_diffs.append(
570568
{
@@ -700,12 +698,14 @@ def __find_changed(self, changed_scan, old_scan):
700698
if key in self._ignore_fields_for_diffs:
701699
continue
702700
if key in old_scan:
703-
if json.dumps(changed_scan[key]) != json.dumps(old_scan[key]) and \
704-
isinstance(changed_scan[key], dict) and isinstance(old_scan[key], dict):
705-
diffs[key] = self.__find_changed(changed_scan[key], old_scan[key])
706-
else:
707-
if changed_scan[key] != old_scan[key]:
708-
diffs[key] = {"from": old_scan[key], "to": changed_scan[key]}
701+
if json.dumps(changed_scan[key]) != json.dumps(old_scan[key]):
702+
if isinstance(changed_scan[key], dict) and isinstance(old_scan[key], dict):
703+
diffs[key] = self.__find_changed(changed_scan[key], old_scan[key])
704+
elif isinstance(changed_scan[key], list) and isinstance(old_scan[key], list):
705+
diffs[key] = {"from": list(set([json.dumps(_el) for _el in old_scan[key]]) - set([json.dumps(_el) for _el in changed_scan[key]])),
706+
"to": list(set([json.dumps(_el) for _el in changed_scan[key]]) - set([json.dumps(_el) for _el in old_scan[key]]))}
707+
else:
708+
diffs[key] = {"from": json.dumps(old_scan[key], sort_keys=True), "to": json.dumps(changed_scan[key], sort_keys=True)}
709709
return diffs
710710

711711
def __find_removed(self, changed_scan, old_scan):
@@ -762,6 +762,7 @@ def view(self):
762762
to_date=self._config.tdate,
763763
from_date=self._config.fdate,
764764
pstate=self._config.port_type)
765+
765766
if self._config.output_file is not None:
766767
self._report_scans(scans, output_file=f"scans_{self._config.output_file}")
767768

deltascan/core/export.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ def _diffs_report_to_html_string(self):
231231
for diffs_on_date in self.data:
232232
# These are the fields for the HTML template
233233
# TODO: create a specific function to handle these comparisons
234+
234235
_augmented_diff = {
235236
"date_from": diffs_on_date["date_from"],
236237
"date_to": diffs_on_date["date_to"],
@@ -266,8 +267,8 @@ def _diffs_report_to_html_string(self):
266267
report = template.render(data)
267268
return report
268269
except Exception as e: # TODO: remove generic exception
269-
self.logger.error("Error generating PDF report: " + str(e))
270-
raise ExporterExceptions.DScanExporterErrorProcessingData("Error generating PDF report: " + str(e))
270+
self.logger.error("Error generating report: " + str(e))
271+
raise ExporterExceptions.DScanExporterErrorProcessingData("Error generating report: " + str(e))
271272

272273
def _scans_report_to_html_string(self):
273274
"""
@@ -282,6 +283,7 @@ def _scans_report_to_html_string(self):
282283
try:
283284
with open(self.template_file, 'r') as file:
284285
html_string = file.read()
286+
285287
data = {
286288
'field_names': ["Port", "State", "Service", "Service FP", "Service Product"],
287289
'scans': self.data,
@@ -294,8 +296,8 @@ def _scans_report_to_html_string(self):
294296

295297
return report
296298
except Exception as e:
297-
self.logger.error("Error generating HTML report: " + str(e))
298-
raise ExporterExceptions.DScanExporterErrorProcessingData("Error generating HTML report: " + str(e))
299+
self.logger.error("Error generating report: " + str(e))
300+
raise ExporterExceptions.DScanExporterErrorProcessingData("Error generating report: " + str(e))
299301

300302
def _diffs_to_html(self):
301303
"""

deltascan/core/output.py

+8
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ def _construct_exported_diff_data(row, field_names):
3030
list: A list of exported diff data.
3131
3232
"""
33+
34+
# This method does all the preparation for the final output format of the diff results
35+
# Diff results: The method basically calculates the depth of the changed fields and depending on the largest depth,
36+
# it adds the keywords "field_n" where "n" is the depth in the diffs dict.
37+
# It also adds the keywords "from" and "to"
38+
# This is why they are refered as articulated results inside the repo. They are constructed in a way that
39+
# is human readable. So the final fomat is e.g. ["changed", "ports", "80", "state", "from", "open", "to", "closed"]
40+
3341
exported_diffs = []
3442
for _k in row["diffs"]["changed"]:
3543
_start_index = 0

0 commit comments

Comments
 (0)