-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathnihilist.py
1844 lines (1508 loc) · 85.9 KB
/
nihilist.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Copyright (c) 2025 Magama Bazarov
# Licensed under the Apache 2.0 License
# This project is not affiliated with or endorsed by Cisco Systems, Inc.
import argparse
import re
import datetime
import sys
from netmiko import ConnectHandler, NetmikoAuthenticationException, NetmikoTimeoutException
from colorama import Fore, Style
# This is banner
def banner():
banner_text = r"""
_______ .__.__ .__.__ .__ __
\ \ |__| |__ |__| | |__| _______/ |_
/ | \| | | \| | | | |/ ___/\ __\
/ | \ | Y \ | |_| |\___ \ | |
\____|__ /__|___| /__|____/__/____ > |__|
\/ \/ \/
"""
banner_text = " " + banner_text.replace("\n", "\n ")
print(banner_text)
print(" " + Fore.YELLOW + "Nihilist: Cisco IOS Security Inspector" + Style.RESET_ALL)
print(" " + Fore.YELLOW + "Author: " + Style.RESET_ALL + "Magama Bazarov, <magamabazarov@mailbox.org>")
print(" " + Fore.YELLOW + "Alias: " + Style.RESET_ALL + "Caster")
print(" " + Fore.YELLOW + "Version: " + Style.RESET_ALL + "1.0")
print(" " + Fore.YELLOW + "Codename: " + Style.RESET_ALL + "Gestalt")
print(" " + Fore.YELLOW + "How to Use: " + Style.RESET_ALL + "https://github.com/casterbyte/Nihilist")
print(" " + Fore.YELLOW + "Detailed Documentation: " + Style.RESET_ALL + "https://github.com/casterbyte/Nihilist/wiki/Mechanism-of-the-tool\n")
print(" " + Fore.MAGENTA + "❝He who fights with monsters should look to it that he himself does not become a monster❞")
print(" " + Fore.MAGENTA + "— Friedrich Nietzsche, 1886\n" + Style.RESET_ALL)
# Connect to the Cisco IOS
def connect_to_device(ip, username, password, port, device_type):
print(Fore.WHITE + f"[*] Running on Python {sys.version.split()[0]}" + Style.RESET_ALL)
device = {
"device_type": "cisco_ios",
"host": ip,
"username": username,
"password": password,
"port": port,
"timeout": 10,
}
try:
print(Fore.GREEN + f"[*] Connecting to {device_type} at {ip}:{port}..." + Style.RESET_ALL)
connection = ConnectHandler(**device)
print(Fore.WHITE + "[*] Connection successful!\n" + Style.RESET_ALL)
return connection
except NetmikoAuthenticationException:
print(Fore.RED + "[-] Authentication failed! Check your credentials." + Style.RESET_ALL)
exit(1)
except NetmikoTimeoutException:
print(Fore.RED + "[-] Connection timed out! Check device availability." + Style.RESET_ALL)
exit(1)
except Exception as e:
print(Fore.RED + f"[-] Connection failed: {e}" + Style.RESET_ALL)
exit(1)
# Simple separator
def print_separator():
print(Fore.WHITE + Style.BRIGHT + "=" * 50 + Style.RESET_ALL)
# Display Uptime
def check_device_uptime(connection):
try:
# Execute command to get device uptime
output = connection.send_command("show version | include uptime")
# Match the hostname and uptime from the output
match = re.match(r'(\S+) uptime is (.+)', output)
if match:
hostname, uptime = match.groups()
print(Fore.GREEN + "[*] Device " + Fore.WHITE + f"'{hostname}'" + Fore.GREEN + f" Uptime: {uptime}" + Style.RESET_ALL)
else:
print(Fore.YELLOW + "[!] Unable to parse device uptime." + Style.RESET_ALL)
except Exception as e:
# Handle any errors during command execution
print(Fore.RED + f"[-] Failed to retrieve uptime: {e}" + Style.RESET_ALL)
# Checking Configuration Size
def checking_config_size(connection):
try:
# Retrieve the configuration size from running config
config_size_output = connection.send_command("show running-config | include Current configuration").strip()
# Extract the configuration size using regex
match = re.search(r"Current configuration : (\d+) bytes", config_size_output)
if match:
config_size = int(match.group(1))
print(Fore.GREEN + "[*] Configuration size: " + Fore.WHITE + f"{config_size} bytes" + Style.RESET_ALL)
else:
print(Fore.RED + "[!] WARNING: Unable to determine configuration size." + Style.RESET_ALL)
except Exception as e:
# Handle errors during command execution
print(Fore.RED + f"[-] Failed to retrieve configuration size: {e}" + Style.RESET_ALL)
# PAD Status
def checking_pad_service(connection):
# Prints a visual separator for clarity
print_separator()
print(Fore.WHITE + Style.BRIGHT + "[*] Checking PAD Service (X.25)..." + Style.RESET_ALL)
try:
# Retrieve the current PAD service configuration from running config
pad_config = connection.send_command("show running-config | include service pad").strip()
except Exception as e:
# Handle errors if the command fails
print(Fore.RED + f"[-] Failed to retrieve PAD configuration: {e}" + Style.RESET_ALL)
return
# Check if PAD service is explicitly disabled
if 'no service pad' in pad_config:
print(Fore.GREEN + "[OK] PAD service explicitly disabled ('no service pad')." + Style.RESET_ALL)
# Check if PAD service is enabled, which is a potential security risk
elif 'service pad' in pad_config:
print(Fore.RED + "[!] WARNING: 'service pad' is enabled. Attackers could exploit PAD (X.25) for unauthorized access." + Style.RESET_ALL)
# If no explicit setting is found, assume PAD is disabled by default
else:
print(Fore.GREEN + "[OK] PAD service is disabled by default." + Style.RESET_ALL)
# Checking service password-encryption
def checking_service_password_encryption(connection):
# Prints a visual separator for clarity
print_separator()
print(Fore.WHITE + Style.BRIGHT + "[*] Checking Password Protection Policy" + Style.RESET_ALL)
try:
# Retrieve the full running configuration
config = connection.send_command("show running-config")
except Exception as e:
# Handle errors if the command fails
print(Fore.RED + f"[-] Failed to retrieve configuration: {e}" + Style.RESET_ALL)
return
# Check if 'service password-encryption' is present and not explicitly disabled
service_encryption_match = re.search(r"(?<!no )service password-encryption", config)
if service_encryption_match:
# Warn if weak encryption is enabled
print(Fore.YELLOW + "[!] WARNING: 'service password-encryption' is enabled!" + Style.RESET_ALL)
print(Fore.YELLOW + " - This feature encrypts plaintext passwords using a weak Vigenère cipher (Type 7)" + Style.RESET_ALL)
print(Fore.YELLOW + " - Consider using 'secret' instead of 'password' for stronger encryption (Type 8 or 9)" + Style.RESET_ALL)
else:
# Confirm that weak encryption is disabled, advising proper password hashing
print(Fore.GREEN + Style.BRIGHT + "[OK] The 'service password-encryption' function is disabled. When you create accounts, define the password hashing algorithm yourself with 'algorithm-type'" + Style.RESET_ALL)
# Checking Password Hashing (type4/5/7/8/9)
def checking_password_hashing(connection):
# Prints a visual separator for clarity
print_separator()
print(Fore.WHITE + Style.BRIGHT + "[*] Checking hashing of account passwords" + Style.RESET_ALL)
try:
# Retrieve the full running configuration
config = connection.send_command("show running-config")
except Exception as e:
# Handle errors if the command fails
print(Fore.RED + f"[-] Failed to retrieve configuration: {e}" + Style.RESET_ALL)
return
# Extract usernames and their password hashing types from the config
password_hashes = re.findall(r'username\s+(\S+)\s+(?:privilege\s+\d+\s+)?(password|secret)\s+(\d+)', config)
if password_hashes:
for user, method, hash_type in password_hashes:
# Type 7 passwords use a weak Vigenère cipher (easily reversible)
if hash_type == "7":
print(Fore.RED + f"[!] WARNING: User '{user}' uses weak Type 7 password encryption (easily reversible)" + Style.RESET_ALL)
# Type 4 passwords are deprecated and considered insecure
elif hash_type == "4":
print(Fore.RED + f"[!] WARNING: User '{user}' uses Type 4 password (deprecated, insecure)" + Style.RESET_ALL)
# Type 5 passwords use MD5 hashing, which is outdated and vulnerable to attacks
elif hash_type == "5":
print(Fore.YELLOW + f"[*] CAUTION: User '{user}' uses Type 5 password (better than type 7 but still outdated)" + Style.RESET_ALL)
# Type 8 passwords use PBKDF2, which provides strong encryption
elif hash_type == "8":
print(Fore.GREEN + f"[OK] User '{user}' uses Type 8 PBKDF2 password (strong encryption)" + Style.RESET_ALL)
# Type 9 passwords use SCRYPT, which is currently the most secure option
elif hash_type == "9":
print(Fore.GREEN + f"[OK] User '{user}' uses Type 9 SCRYPT password (the strongest, hardest to crack)" + Style.RESET_ALL)
# Checking RBAC
def checking_rbac(connection):
# Prints a visual separator for clarity
print_separator()
print(Fore.WHITE + Style.BRIGHT + "[*] Checking RBAC" + Style.RESET_ALL)
try:
# Retrieve the full running configuration
config = connection.send_command("show running-config")
except Exception as e:
# Handle errors if the command fails
print(Fore.RED + f"[-] Failed to retrieve configuration: {e}" + Style.RESET_ALL)
return
# Extract usernames and their privilege levels from the config
users = re.findall(r'username\s+(\S+)(?:\s+privilege\s+(\d+))?', config)
if users:
# Display a warning about configured users and their privilege levels
print(Fore.YELLOW + "[!] List of configured users and their privilege levels:" + Style.RESET_ALL)
for user, privilege in users:
# If no privilege level is explicitly set, assume default (1)
privilege_level = privilege if privilege else "default (1)"
print(Fore.YELLOW + " - User '" + Fore.WHITE + f"{user}" + Fore.YELLOW + "' has privilege level: " + Fore.WHITE + f"{privilege_level}" + Style.RESET_ALL)
# Security warning regarding high-privilege accounts
print(Fore.YELLOW + Style.BRIGHT + "[!] Watch who you give high privileges to in the system!" + Style.RESET_ALL)
else:
# If no user accounts are found in the configuration, assume secure state
print(Fore.GREEN + Style.BRIGHT + "[OK] No user accounts found in the configuration." + Style.RESET_ALL)
# Checking VTP
def checking_vtp_status(connection):
# Prints a visual separator for clarity
print_separator()
print(Fore.WHITE + Style.BRIGHT + "[*] Checking VTP Operation" + Style.RESET_ALL)
try:
# Retrieve the current VTP status
vtp_status = connection.send_command("show vtp status")
except Exception as e:
# Handle errors if the command fails
print(Fore.RED + f"[-] Failed to retrieve VTP status: {e}" + Style.RESET_ALL)
return
# Extract the VTP operating mode from the output
match = re.search(r'VTP Operating Mode\s+:\s+(\S+)', vtp_status)
if match:
vtp_mode = match.group(1).strip()
# Secure modes: "Off" or "Transparent" prevent VTP attacks
if vtp_mode.lower() in ["off", "transparent"]:
print(Fore.GREEN + f"[OK] VTP is running in a secure mode ({vtp_mode})" + Style.RESET_ALL)
else:
# Warn if VTP is in Server or Client mode, as it can introduce security risks
print(Fore.YELLOW + f"[!] WARNING: VTP is active and operating in '{vtp_mode}' mode." + Style.RESET_ALL)
print(Fore.YELLOW + " - VTP may be vulnerable to unauthorized VLAN modifications." + Style.RESET_ALL)
print(Fore.YELLOW + " - Consider setting it to 'Transparent' or 'Off' if not needed." + Style.RESET_ALL)
else:
# Handle cases where VTP mode is not found in the output
print(Fore.RED + "[-] Failed to determine VTP mode." + Style.RESET_ALL)
# Checking DTP
def checking_dtp_status(connection):
# Prints a visual separator for clarity
print_separator()
print(Fore.WHITE + Style.BRIGHT + "[*] Checking DTP Operation" + Style.RESET_ALL)
try:
# Retrieve the DTP status for all interfaces
dtp_output = connection.send_command("show dtp interface")
except Exception as e:
# Handle errors if the command fails
print(Fore.RED + f"[-] Failed to retrieve DTP status: {e}" + Style.RESET_ALL)
return
# Extract interface names where DTP is active
dtp_interfaces = re.findall(r"DTP information for (\S+):", dtp_output)
# Extract DTP negotiation status (TOS/TAS/TNS: Mode)
dtp_statuses = re.findall(r"TOS/TAS/TNS:\s+\S+/(\S+)/\S+", dtp_output)
if not dtp_interfaces or not dtp_statuses:
# If the regex didn't match expected patterns, output is unexpected or empty
print(Fore.RED + "[-] Unable to parse DTP status output!" + Style.RESET_ALL)
return
active_dtp_interfaces = []
# Identify interfaces where DTP is still in auto-negotiation mode
for iface, status in zip(dtp_interfaces, dtp_statuses):
if status.lower() == "auto":
active_dtp_interfaces.append(iface)
if active_dtp_interfaces:
# Warn if any interfaces have DTP enabled
print(Fore.YELLOW + "[!] WARNING: DTP is enabled on the following interfaces:" + Style.RESET_ALL)
for iface in active_dtp_interfaces:
print(Fore.YELLOW + f" - {iface}" + Style.RESET_ALL)
# Highlight the security risk of VLAN hopping via DTP
print(Fore.RED + "[!] DTP can be exploited for VLAN hopping attacks. Consider disabling it!" + Style.RESET_ALL)
else:
# Confirm that DTP is disabled on all interfaces
print(Fore.GREEN + "[OK] DTP is disabled on all interfaces." + Style.RESET_ALL)
# Checking Native VLAN Settings
def checking_native_vlan(connection):
# Prints a visual separator for clarity
print_separator()
print(Fore.WHITE + Style.BRIGHT + "[*] Checking Native VLAN Configuration" + Style.RESET_ALL)
try:
# Retrieve the trunk interface configuration
trunk_output = connection.send_command("show interfaces trunk").strip()
except Exception as e:
# Handle errors if the command fails
print(Fore.RED + f"[-] Failed to retrieve Native VLAN configuration: {e}" + Style.RESET_ALL)
return
native_vlan_ports = []
# Parse trunk interface details and extract the native VLAN assignments
for line in trunk_output.splitlines():
match = re.search(r'(\S+)\s+\S+\s+\S+\s+\S+\s+(\d+)', line)
if match:
port, native_vlan = match.groups()
# Identify ports where VLAN 1 is used as the Native VLAN
if native_vlan == "1":
native_vlan_ports.append(port)
if native_vlan_ports:
# Warn if VLAN 1 is set as the Native VLAN on any trunk ports
print(Fore.YELLOW + "[!] WARNING: The following trunk ports allow VLAN 1 as Native VLAN:" + Style.RESET_ALL)
for port in native_vlan_ports:
print(Fore.YELLOW + f" - {port}" + Style.RESET_ALL)
# Highlight the security risk of VLAN hopping due to Native VLAN 1
print(Fore.RED + "[!] VLAN 1 as Native VLAN can lead to VLAN hopping attacks. Change it using 'switchport trunk native vlan <VLAN>'." + Style.RESET_ALL)
else:
# Confirm that VLAN 1 is not used as the Native VLAN on any trunk ports
print(Fore.GREEN + "[OK] No trunk ports are using VLAN 1 as the Native VLAN." + Style.RESET_ALL)
# Checking CDP
def checking_cdp(connection):
# Prints a visual separator for clarity
print_separator()
print(Fore.WHITE + Style.BRIGHT + "[*] Checking CDP Operation" + Style.RESET_ALL)
try:
# Enable privileged EXEC mode
connection.enable()
# Disable terminal paging for uninterrupted output
connection.send_command("terminal length 0")
# Retrieve CDP status for all interfaces
cdp_output = connection.send_command("show cdp interface")
except Exception as e:
# Handle errors if the command fails
print(Fore.RED + f"[-] Failed to retrieve CDP status: {e}" + Style.RESET_ALL)
return
# Regex pattern to extract all interface blocks
pattern = re.compile(
r'(?P<intf>\S+)\s+is\s+(?:up|down),\s+line\s+protocol\s+is\s+(?:up|down)\n'
r'(?:\s+.*\n)*?',
re.MULTILINE
)
all_blocks = pattern.findall(cdp_output)
cdp_enabled_interfaces = []
# Regex pattern to match CDP-enabled interfaces
block_pattern = re.compile(
r'(?P<block>(?P<intf>\S+)\s+is\s+(?:up|down),\s+line\s+protocol\s+is\s+(?:up|down)\n'
r'(?:\s+.*\n)*?)'
r'(?=\S+\s+is\s+(?:up|down),|$)',
re.MULTILINE
)
# Iterate through all detected blocks and check if CDP is active
blocks = block_pattern.finditer(cdp_output)
for match_block in blocks:
block_text = match_block.group('block')
intf_name = match_block.group('intf')
# If the block mentions CDP packet transmission, CDP is enabled
if re.search(r'Sending CDP packets every \d+ seconds', block_text):
cdp_enabled_interfaces.append(intf_name)
if cdp_enabled_interfaces:
# Warn if CDP is enabled on any interfaces
print(Fore.YELLOW + "[!] WARNING: CDP is enabled on the following interfaces:" + Style.RESET_ALL)
for interface in cdp_enabled_interfaces:
print(Fore.YELLOW + f" - {interface}" + Style.RESET_ALL)
# Highlight security risks associated with CDP
print(Fore.YELLOW + "[!] CDP frames carry sensitive information about the equipment." + Style.RESET_ALL)
print(Fore.WHITE + "[*] Keep track of where CDP is active." + Style.RESET_ALL)
print(Fore.WHITE + "[*] When disabling CDP, be careful not to break VoIP." + Style.RESET_ALL)
else:
# Confirm that CDP is disabled on all interfaces
print(Fore.GREEN + "[OK] CDP is disabled on all interfaces." + Style.RESET_ALL)
# Checking VTY Lines
def checking_vty_security(connection):
# Prints a visual separator for clarity
print_separator()
print(Fore.WHITE + Style.BRIGHT + "[*] Checking VTY Lines" + Style.RESET_ALL)
try:
# Retrieve VTY line configuration
config = connection.send_command("show running-config | section line vty")
# Retrieve full configuration to check global settings like HTTP server status
global_config = connection.send_command("show running-config")
except Exception as e:
# Handle errors if the command fails
print(Fore.RED + f"[-] Failed to retrieve VTY configuration: {e}" + Style.RESET_ALL)
return
insecure_methods = []
ssh_enabled = False
access_class_found = False
login_local_found = False
# Split configuration into VTY blocks for analysis
vty_blocks = re.split(r"line vty \d+ \d+", config)
for vty_block in vty_blocks:
if not vty_block.strip():
continue
# Check for transport input settings (protocols allowed for remote access)
if "transport input" in vty_block:
if "telnet" in vty_block:
insecure_methods.append("Telnet")
if "rlogin" in vty_block:
insecure_methods.append("RLogin")
if "ssh" in vty_block:
ssh_enabled = True
# Check if access-class is applied (restricts remote access)
if "access-class" in vty_block:
access_class_found = True
# Check if login local authentication is used (local username-based auth)
if "login local" in vty_block:
login_local_found = True
# Warn if insecure transport methods are enabled (Telnet or RLogin)
if insecure_methods:
print(Fore.RED + "[!] WARNING: Insecure transport methods detected!" + Style.RESET_ALL)
for method in set(insecure_methods):
print(Fore.RED + f" - {method} is enabled on VTY lines. Consider disabling it (`transport input ssh`)." + Style.RESET_ALL)
# Confirm if SSH is enabled (preferred secure access method)
if ssh_enabled:
print(Fore.GREEN + "[OK] SSH is enabled for secure remote access." + Style.RESET_ALL)
# Inform if local authentication is used
if login_local_found:
print(Fore.WHITE + "[*] Local authentication (login local) is used for VTY access." + Style.RESET_ALL)
# Warn if no access-class is applied (leaving remote access open)
if not access_class_found:
print(Fore.RED + "[!] WARNING: No 'access-class' applied to VTY lines!" + Style.RESET_ALL)
print(Fore.RED + " - Your device is vulnerable to unauthorized remote access." + Style.RESET_ALL)
print(Fore.RED + " - Consider applying an ACL using `access-class ACL_NAME in`." + Style.RESET_ALL)
else:
print(Fore.GREEN + "[OK] Access-class is applied, restricting remote access." + Style.RESET_ALL)
# Web Service Activity: Checking if HTTP/HTTPS management is enabled
http_server_disabled = "no ip http server" in global_config
https_server_disabled = "no ip http secure-server" in global_config
# Warn if web-based management interfaces are enabled (potential security risks)
if not http_server_disabled or not https_server_disabled:
print(Fore.YELLOW + "[!] WARNING: Web management interface (HTTP/HTTPS) is enabled!" + Style.RESET_ALL)
print(Fore.YELLOW + " - Check your hardware for CVE-2023-20273 & CVE-2023-20198" + Style.RESET_ALL)
print(Fore.YELLOW + " - If you're not using this as a control, you're better off turning it off" + Style.RESET_ALL)
else:
# Confirm if HTTP/HTTPS management is properly disabled
print(Fore.GREEN + "[OK] HTTP/HTTPS management interface is disabled." + Style.RESET_ALL)
# Checking AAA
def checking_aaa(connection):
# Prints a visual separator for clarity
print_separator()
print(Fore.WHITE + Style.BRIGHT + "[*] Checking AAA Configuration" + Style.RESET_ALL)
try:
# Check if AAA is enabled by looking for 'aaa new-model'
aaa_new_model = connection.send_command("show running-config | include aaa new-model").strip()
# If AAA is not enabled, warn the user and stop further checks
if not aaa_new_model or "no aaa new-model" in aaa_new_model:
print(Fore.YELLOW + "[!] WARNING: AAA is not enabled (no 'aaa new-model' found). The device relies on local authentication only." + Style.RESET_ALL)
return
print(Fore.GREEN + "[OK] AAA is enabled on this device." + Style.RESET_ALL)
# Dictionary to track authentication methods in use
auth_methods = {
"enable": [],
"local": [],
"none": [],
"radius": [],
"tacacs": []
}
# Retrieve AAA authentication methods from running config
method_lines = connection.send_command("show running-config | include aaa authentication login").strip().splitlines()
# Parse authentication methods from configuration
for line in method_lines:
match = re.search(r'aaa authentication login (\S+) (.+)', line)
if match:
list_name, methods = match.groups()
methods_list = methods.split()
if "enable" in methods_list:
auth_methods["enable"].append(list_name)
if "local" in methods_list:
auth_methods["local"].append(list_name)
if "none" in methods_list:
auth_methods["none"].append(list_name)
if "group radius" in methods:
auth_methods["radius"].append(list_name)
if "group tacacs+" in methods:
auth_methods["tacacs"].append(list_name)
# Warn if 'none' is used in authentication (bypassing authentication)
if auth_methods["none"]:
for method in auth_methods["none"]:
if method == "default":
print(Fore.RED + "[!] CRITICAL: 'none' is used as the primary authentication method! Unauthorized access is possible!" + Style.RESET_ALL)
else:
print(Fore.YELLOW + f"[!] WARNING: 'none' is present in authentication list '{method}'. Consider removing it." + Style.RESET_ALL)
# Warn if 'enable' password authentication is used (considered weak)
if auth_methods["enable"]:
print(Fore.YELLOW + "[!] WARNING: Authentication uses 'enable' password. Consider switching to more secure methods like RADIUS/TACACS+." + Style.RESET_ALL)
# Confirm local authentication is in use
if auth_methods["local"]:
print(Fore.GREEN + "[OK] Local authentication is configured." + Style.RESET_ALL)
# Confirm RADIUS authentication is enabled
if auth_methods["radius"]:
print(Fore.GREEN + "[OK] RADIUS authentication is enabled for login." + Style.RESET_ALL)
# Confirm TACACS+ authentication is enabled
if auth_methods["tacacs"]:
print(Fore.GREEN + "[OK] TACACS+ authentication is enabled for login." + Style.RESET_ALL)
# Warn if only local authentication is used without RADIUS/TACACS+
if not (auth_methods["radius"] or auth_methods["tacacs"]) and auth_methods["local"]:
print(Fore.YELLOW + "[!] WARNING: Only local authentication is used. Ensure strong passwords for local users." + Style.RESET_ALL)
# Check if AAA accounting is configured
accounting_config = connection.send_command("show running-config | include aaa accounting").strip()
# Warn if AAA accounting is missing (no logging of actions)
if not accounting_config:
print(Fore.YELLOW + "[!] WARNING: AAA accounting is not configured. Actions on the device are not logged." + Style.RESET_ALL)
else:
print(Fore.GREEN + "[OK] AAA accounting is enabled. Actions on the device are logged." + Style.RESET_ALL)
except Exception as e:
# Handle errors if the command execution fails
print(Fore.RED + f"[-] Failed to retrieve AAA configuration: {e}" + Style.RESET_ALL)
# Checking Sessions Limit
def checking_session_limit(connection):
# Prints a visual separator for clarity
print_separator()
print(Fore.WHITE + Style.BRIGHT + "[*] Checking Sessions Limit" + Style.RESET_ALL)
try:
# Retrieve session limit configuration from running config
session_limit_output = connection.send_command("show running-config | include session-limit").strip()
# Warn if no session limit is explicitly configured
if not session_limit_output:
print(Fore.RED + "[!] WARNING: No session limit is set! Default (16) sessions are allowed." + Style.RESET_ALL)
return
# Extract the configured session limit value
match = re.search(r'session-limit (\d+)', session_limit_output)
if match:
session_limit = int(match.group(1))
print(Fore.GREEN + f"[OK] Session limit is set to {session_limit} concurrent sessions." + Style.RESET_ALL)
# Warn if the session limit is higher than the recommended value (default: 3)
if session_limit > 3:
print(Fore.YELLOW + f"[!] WARNING: The session limit is higher than recommended (3). Consider lowering it." + Style.RESET_ALL)
print(Fore.YELLOW + f"[*] However, base it on your needs." + Style.RESET_ALL)
else:
# If session limit couldn't be parsed, display a warning
print(Fore.RED + "[!] WARNING: Could not parse session limit value." + Style.RESET_ALL)
except Exception as e:
# Handle errors if the command execution fails
print(Fore.RED + f"[-] Failed to retrieve session limit configuration: {e}" + Style.RESET_ALL)
# Checking Login Block
def checking_login_block_protection(connection):
# Prints a visual separator for clarity
print_separator()
print(Fore.WHITE + Style.BRIGHT + "[*] Checking Login Block" + Style.RESET_ALL)
try:
# Retrieve login block protection configuration
login_block_output = connection.send_command("show running-config | include login block-for").strip()
# Warn if brute-force protection is not configured
if not login_block_output:
print(Fore.RED + "[!] WARNING: No brute-force protection (login block) is configured." + Style.RESET_ALL)
return
# Extract login block settings from the configuration
match = re.search(r'login block-for (\d+) attempts (\d+) within (\d+)', login_block_output)
if match:
block_time, attempts, within_time = match.groups()
print(Fore.GREEN + f"[OK] Brute-force protection is enabled: {attempts} failed attempts within {within_time} sec → block for {block_time} sec." + Style.RESET_ALL)
# Warn if the number of allowed failed attempts is too high
if int(attempts) > 5:
print(Fore.YELLOW + f"[!] WARNING: The failed attempts threshold ({attempts}) is too high. Recommended: 3." + Style.RESET_ALL)
# Warn if the block time is too short for effective protection
if int(block_time) < 30:
print(Fore.YELLOW + f"[!] WARNING: The block time ({block_time} sec) is too short. Recommended: 60 sec or more." + Style.RESET_ALL)
else:
# If parsing fails, notify the user
print(Fore.RED + "[!] WARNING: Could not parse brute-force protection configuration." + Style.RESET_ALL)
except Exception as e:
# Handle errors if the command execution fails
print(Fore.RED + f"[-] Failed to retrieve login block configuration: {e}" + Style.RESET_ALL)
# Checking SSH Security Settings
def checking_ssh_security(connection):
# Prints a visual separator for clarity
print_separator()
print(Fore.WHITE + Style.BRIGHT + "[*] Checking SSH Security Settings" + Style.RESET_ALL)
try:
# Retrieve SSH-related configuration lines
ssh_config = connection.send_command("show running-config | include ^ip ssh").strip().splitlines()
# Default settings (Cisco default values)
ssh_version = "Compatibility (1 & 2)"
auth_retries = 3 # Default number of authentication retries
timeout = 120 # Default session timeout in seconds
maxstartups = 10 # Default max simultaneous SSH sessions
# Parse SSH configuration for specific settings
for line in ssh_config:
if "ip ssh version" in line:
ssh_version = line.split()[-1] # Extracts SSH version
elif "ip ssh authentication-retries" in line:
auth_retries = int(line.split()[-1]) # Extracts authentication retry count
elif "ip ssh time-out" in line:
timeout = int(line.split()[-1]) # Extracts session timeout value
elif "ip ssh maxstartups" in line:
maxstartups = int(line.split()[-1]) # Extracts max startup sessions value
# Check SSH version (should be explicitly set to version 2)
if ssh_version != "2":
print(Fore.RED + "[!] WARNING: SSH version is not explicitly set to 2. Set with 'ip ssh version 2'." + Style.RESET_ALL)
else:
print(Fore.GREEN + "[OK] SSH version 2 is explicitly configured." + Style.RESET_ALL)
# Check SSH authentication retry limit (should not be too high)
if auth_retries > 3:
print(Fore.YELLOW + f"[!] NOTICE: SSH authentication-retries ({auth_retries}) is slightly high. Recommended: ≤ 3." + Style.RESET_ALL)
elif auth_retries == 3:
print(Fore.GREEN + f"[OK] SSH authentication-retries ({auth_retries}) is secure." + Style.RESET_ALL)
else:
print(Fore.GREEN + f"[OK] SSH authentication-retries ({auth_retries}) is optimally low." + Style.RESET_ALL)
# Check SSH session timeout (should be limited for security)
if timeout > 120:
print(Fore.RED + f"[!] WARNING: SSH timeout ({timeout}s) is too high. Recommended: ≤ 90s, ideally ≤ 60s." + Style.RESET_ALL)
elif 90 < timeout <= 120:
print(Fore.YELLOW + f"[!] NOTICE: SSH timeout ({timeout}s) is moderate. Consider ≤ 90s for better security." + Style.RESET_ALL)
else:
print(Fore.GREEN + f"[OK] SSH timeout ({timeout}s) is optimal." + Style.RESET_ALL)
# Check max simultaneous SSH sessions allowed (should be restricted)
if maxstartups > 4:
print(Fore.RED + f"[!] WARNING: SSH maxstartups ({maxstartups}) is too high. Recommended: ≤ 4." + Style.RESET_ALL)
elif maxstartups == 4:
print(Fore.YELLOW + f"[!] NOTICE: SSH maxstartups ({maxstartups}) is reasonable, but ≤ 3 is preferred." + Style.RESET_ALL)
else:
print(Fore.GREEN + f"[OK] SSH maxstartups ({maxstartups}) is secure." + Style.RESET_ALL)
except Exception as e:
# Handle errors if the command execution fails
print(Fore.RED + f"[-] Failed to retrieve SSH configuration: {e}" + Style.RESET_ALL)
# Checking LLDP
def checking_lldp(connection):
# Prints a visual separator for clarity
print_separator()
print(Fore.WHITE + Style.BRIGHT + "[*] Checking LLDP Operation" + Style.RESET_ALL)
try:
# Retrieve LLDP status for all interfaces
lldp_output = connection.send_command("show lldp interface")
except Exception as e:
# Handle errors if the command fails
print(Fore.RED + f"[-] Failed to retrieve LLDP status: {e}" + Style.RESET_ALL)
return
# Extract interfaces where LLDP is enabled for both transmission (Tx) and reception (Rx)
lldp_enabled_interfaces = re.findall(r'(\S+):\n\s+Tx: enabled\n\s+Rx: enabled', lldp_output)
if lldp_enabled_interfaces:
# Warn if LLDP is enabled on any interfaces
print(Fore.YELLOW + "[!] WARNING: LLDP is enabled on the following interfaces:" + Style.RESET_ALL)
for interface in lldp_enabled_interfaces:
print(Fore.YELLOW + f" - {Fore.YELLOW}{interface}{Style.RESET_ALL}")
# Highlight security risks associated with LLDP
print(Fore.YELLOW + "[!] LLDP frames carry sensitive information about the equipment." + Style.RESET_ALL)
print(Fore.WHITE + "[*] Keep track of where LLDP is active." + Style.RESET_ALL)
print(Fore.WHITE + "[*] When disabling LLDP, be careful not to break VoIP." + Style.RESET_ALL)
else:
# Confirm that LLDP is disabled on all interfaces
print(Fore.GREEN + "[OK] LLDP is disabled on all interfaces." + Style.RESET_ALL)
# Checking Default Usernames
def checking_default_usernames(connection):
# Prints a visual separator for clarity
print_separator()
print(Fore.WHITE + Style.BRIGHT + "[*] Checking Default Usernames" + Style.RESET_ALL)
try:
# Retrieve all configured usernames from the running configuration
config = connection.send_command("show running-config | sec username")
except Exception as e:
# Handle errors if the command execution fails
print(Fore.RED + f"[-] Failed to retrieve usernames: {e}" + Style.RESET_ALL)
return
# List of commonly used default usernames that should be avoided
default_usernames = {"user", "test", "cisco", "ciscoadmin", "root", "ciscoios", "c1sc0", "administrator", "admin"}
# Extract all usernames from the configuration
found_users = re.findall(r'username\s+(\S+)', config)
# Identify usernames that match known default usernames
flagged_users = [user for user in found_users if user.lower() in default_usernames]
if flagged_users:
# Warn if any default usernames are found
print(Fore.YELLOW + "[!] WARNING: Default usernames detected!" + Style.RESET_ALL)
for user in flagged_users:
print(Fore.YELLOW + f" - {Fore.WHITE}{user}{Style.RESET_ALL}")
# Highlight the security risk of using common usernames
print(Fore.YELLOW + "[!] Using default usernames increases the risk of brute-force attacks. Change them to something more unique." + Style.RESET_ALL)
else:
# Confirm that no default usernames are present
print(Fore.GREEN + "[OK] No default usernames found." + Style.RESET_ALL)
# Checking HSRP
def checking_hsrp(connection):
# Prints a visual separator for clarity
print_separator()
print(Fore.WHITE + Style.BRIGHT + "[*] Checking HSRP Operation" + Style.RESET_ALL)
try:
# Retrieve HSRP configuration from the running configuration
hsrp_config = connection.send_command("show running-config | section standby").strip()
# If no HSRP configuration is found, assume the feature is not in use
if not hsrp_config:
print(Fore.GREEN + "[OK] No HSRP configuration found on this device." + Style.RESET_ALL)
return
# Retrieve HSRP active status information
hsrp_status = connection.send_command("show standby brief")
except Exception as e:
# Handle errors if the command execution fails
print(Fore.RED + f"[-] Failed to retrieve HSRP configuration: {e}" + Style.RESET_ALL)
return
# Extract all unique HSRP group numbers from the configuration
hsrp_groups = list(set(re.findall(r'standby\s+(\d+)', hsrp_config)))
priority_issues = [] # Stores HSRP groups with low priority
no_auth = [] # Stores HSRP groups without authentication
md5_auth = [] # Stores HSRP groups using MD5 authentication
plain_auth = [] # Stores HSRP groups using plaintext authentication
for group in hsrp_groups:
# Extract HSRP priority for each group
pm = re.search(rf'standby {group} priority (\d+)', hsrp_config)
priority = int(pm.group(1)) if pm else 100 # Default priority is 100 if not set
# Check if the group is active and its priority is below 255 (not ideal)
ac = re.search(rf'^\S+\s+{group}\s+(\d+)\s+\S*\s+Active', hsrp_status, re.MULTILINE)
if ac and priority < 255:
priority_issues.append(
f" - Group {group}: priority is {priority}. {Fore.RED}Should be 255 for Active role{Style.RESET_ALL}"
)
# Extract HSRP authentication settings
auth_line = re.search(rf'^.*standby {group} authentication (.*)$', hsrp_config, re.MULTILINE)
if auth_line:
if 'md5' in auth_line.group(1).lower():
md5_auth.append(f" - Group {group}")
else:
plain_auth.append(f" - Group {group}")
else:
no_auth.append(f" - Group {group}")
issues_found = (priority_issues or no_auth or plain_auth)
if issues_found:
# Warn about HSRP security risks and possible MITM attacks
print(Fore.YELLOW + "[!] WARNING: HSRP security issues detected. Possible MITM risk." + Style.RESET_ALL)
if priority_issues:
print(Fore.YELLOW + "[!] HSRP groups with priority issues:" + Style.RESET_ALL)
for issue in priority_issues:
print(issue)
if no_auth:
print(Fore.YELLOW + "[!] HSRP groups without any authentication:" + Style.RESET_ALL)
for group in no_auth:
print(Fore.YELLOW + group + Style.RESET_ALL)
if plain_auth:
print(Fore.RED + "[!] HSRP groups using plaintext authentication:" + Style.RESET_ALL)
for group in plain_auth:
print(Fore.RED + group + Style.RESET_ALL)
# Confirm if MD5 authentication is in use for HSRP groups
if md5_auth:
print(Fore.GREEN + "[OK] HSRP MD5 authentication is enabled on the following groups:" + Style.RESET_ALL)
for group in md5_auth:
print(Fore.GREEN + group + Style.RESET_ALL)
# Inform that HSRP priority values may vary depending on infrastructure design
print(Fore.WHITE + "[*] HSRP priorities can be configured differently in different infrastructures, you may even get MHSRP." + Style.RESET_ALL)
if not issues_found:
# Confirm that no security vulnerabilities were detected in HSRP configuration
print(Fore.GREEN + "[OK] No security issues found with HSRP configuration." + Style.RESET_ALL)
# Checking VRRP
def checking_vrrp(connection):
# Prints a visual separator for clarity
print_separator()
print(Fore.WHITE + Style.BRIGHT + "[*] Checking VRRP Operation" + Style.RESET_ALL)
try:
# Retrieve VRRP configuration from the running configuration
vrrp_config = connection.send_command("show running-config | section vrrp").strip()
# If no VRRP configuration is found, assume the feature is not in use
if not vrrp_config:
print(Fore.GREEN + "[OK] No VRRP configuration found on this device." + Style.RESET_ALL)
return
except Exception as e:
# Handle errors if the command execution fails
print(Fore.RED + f"[-] Failed to retrieve VRRP configuration: {e}" + Style.RESET_ALL)
return
# Extract all unique VRRP group numbers from the configuration
vrrp_instances = re.findall(r'vrrp\s+(\d+)', vrrp_config)
vrrp_instances = list(set(vrrp_instances))
priority_warnings = [] # Stores VRRP groups with low priority
auth_issues = [] # Stores VRRP groups without authentication
auth_md5 = [] # Stores VRRP groups using MD5 authentication
weak_auth = [] # Stores VRRP groups using plaintext authentication
for group in vrrp_instances:
# Extract VRRP priority for each group
priority_match = re.search(rf'vrrp {group} priority (\d+)', vrrp_config)
priority = int(priority_match.group(1)) if priority_match else 100 # Default priority is 100 if not set
priority_warnings.append(f" - Group {group}: priority is {priority} (Max possible is 254. You can protect yourself from a MITM attack with authentication)")
# Extract VRRP authentication settings
auth_text_match = re.search(rf'vrrp {group} authentication text ', vrrp_config)
auth_md5_keychain_match = re.search(rf'vrrp {group} authentication md5 key-chain ', vrrp_config)
auth_md5_keystring_match = re.search(rf'vrrp {group} authentication md5 key-string ', vrrp_config)
if auth_md5_keychain_match or auth_md5_keystring_match:
auth_md5.append(f" - Group {group}")
elif auth_text_match:
weak_auth.append(f" - Group {group}")
else:
auth_issues.append(f" - Group {group}")
if priority_warnings or auth_issues or weak_auth:
# Warn about VRRP security risks and possible MITM attacks
print(Fore.YELLOW + "[!] WARNING: VRRP security issues detected. Possible MITM risk." + Style.RESET_ALL)
if priority_warnings:
print(Fore.YELLOW + "[!] VRRP groups and their priorities:" + Style.RESET_ALL)
for issue in priority_warnings:
print(Fore.YELLOW + issue + Style.RESET_ALL)
if weak_auth:
print(Fore.RED + "[!] VRRP groups using plaintext authentication:" + Style.RESET_ALL)
for issue in weak_auth:
print(Fore.RED + issue + Style.RESET_ALL)
if auth_issues:
print(Fore.RED + "[!] VRRP groups without authentication:" + Style.RESET_ALL)
for issue in auth_issues:
print(Fore.RED + issue + Style.RESET_ALL)
# Confirm if MD5 authentication is in use for VRRP groups
if auth_md5:
print(Fore.GREEN + "[OK] VRRP MD5 authentication is enabled on the following groups:" + Style.RESET_ALL)
for success in auth_md5:
print(Fore.GREEN + success + Style.RESET_ALL)
# Inform that VRRP priority values may vary depending on infrastructure design
print(Fore.WHITE + "[*] VRRP priorities can be configured differently in different infrastructures, you may even get MVRRP." + Style.RESET_ALL)
if not priority_warnings and not auth_issues and not weak_auth:
# Confirm that no security vulnerabilities were detected in VRRP configuration
print(Fore.GREEN + "[OK] No security issues found with VRRP configuration." + Style.RESET_ALL)
# Checking GLBP
def checking_glbp(connection):
# Prints a visual separator for clarity
print_separator()
print(Fore.WHITE + Style.BRIGHT + "[*] Checking GLBP Operation" + Style.RESET_ALL)
try:
# Retrieve GLBP configuration from the running configuration
glbp_config = connection.send_command("show running-config | section glbp").strip()
# If no GLBP configuration is found, assume the feature is not in use
if not glbp_config:
print(Fore.GREEN + "[OK] No GLBP configuration found on this device." + Style.RESET_ALL)
return
# Retrieve GLBP brief status information
glbp_brief = connection.send_command("show glbp brief")
except Exception as e:
# Handle errors if the command execution fails
print(Fore.RED + f"[-] Failed to retrieve GLBP configuration: {e}" + Style.RESET_ALL)
return
# Extract all unique GLBP group numbers from the configuration
glbp_groups = list(set(re.findall(r'glbp\s+(\d+)\s', glbp_config)))
priority_issues = [] # Stores GLBP groups with low AVG priority
no_auth = [] # Stores GLBP groups without authentication
md5_auth = [] # Stores GLBP groups using MD5 authentication
plain_auth = [] # Stores GLBP groups using plaintext authentication
# Regular expression pattern for parsing GLBP brief output
pattern = re.compile(r'^(?P<intf>\S+)\s+(?P<grp>\d+)\s+(?P<fwd>-|\d+)\s+(?P<pri>\d+|-)\s+(?P<state>\S+)', re.MULTILINE)
brief_matches = pattern.findall(glbp_brief)
# Dictionary to store actual roles and priorities of GLBP groups
actual_roles = {}
for intf, grp, fwd, pri, state in brief_matches:
if grp not in actual_roles:
actual_roles[grp] = []
is_avg = (fwd == '-') # Check if the group acts as the AVG (Active Virtual Gateway)
real_pri = int(pri) if pri.isdigit() else 100 # Default priority is 100 if not explicitly set
actual_roles[grp].append({
"interface": intf,
"fwd": fwd,
"priority": real_pri,
"state": state.lower(),
"is_avg": is_avg
})
for group in glbp_groups:
# Extract GLBP priority for each group
match_priority = re.search(rf'glbp {group} priority (\d+)', glbp_config)
config_prio = int(match_priority.group(1)) if match_priority else 100
# Extract authentication settings
auth_line = re.search(rf'^.*glbp {group} authentication (\S+)\s+(.*)$', glbp_config, re.MULTILINE)
if auth_line:
auth_type = auth_line.group(1).lower()
if auth_type == "md5":
md5_auth.append(f" - Group {group}")
elif auth_type == "text":
plain_auth.append(f" - Group {group}")
else:
no_auth.append(f" - Group {group}")
else:
no_auth.append(f" - Group {group}")
# Analyze GLBP roles and priority settings
group_role_info = actual_roles.get(group, [])
for role in group_role_info:
if role["is_avg"] and role["state"] == "active":
if config_prio < 255:
priority_issues.append(
f" - Group {group}: priority is {config_prio}. {Fore.RED}Should be 255 for the AVG{Style.RESET_ALL}"