Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 38 additions & 5 deletions command/challenge.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,44 @@ def list_challenges(self):

filter_category_id = None
cat_filter_list = self._get_filter_category_list(category_dict)
challenge_list: List[ChallengeList] = self.client.get_challenge_list(retired=self.args.retired,
unsolved=unsolved,
filter_todo=self.args.todo,
filter_category_list=cat_filter_list,
filter_difficulty=self.args.difficulty)

# Handle --all, --active, --retired flags
if hasattr(self.args, 'all') and self.args.all:
# Fetch both active and retired challenges
active_challenges: List[ChallengeList] = self.client.get_challenge_list(
retired=False,
unsolved=unsolved,
filter_todo=self.args.todo,
filter_category_list=cat_filter_list,
filter_difficulty=self.args.difficulty,
)
retired_challenges: List[ChallengeList] = self.client.get_challenge_list(
retired=True,
unsolved=unsolved,
filter_todo=self.args.todo,
filter_category_list=cat_filter_list,
filter_difficulty=self.args.difficulty,
)
challenge_list = active_challenges + retired_challenges
elif hasattr(self.args, 'retired') and self.args.retired:
# Fetch only retired challenges
challenge_list: List[ChallengeList] = self.client.get_challenge_list(
retired=True,
unsolved=unsolved,
filter_todo=self.args.todo,
filter_category_list=cat_filter_list,
filter_difficulty=self.args.difficulty,
)
else:
# Default behavior (backwards compatible): fetch active challenges
# This covers both explicit --active and no flag at all
challenge_list: List[ChallengeList] = self.client.get_challenge_list(
retired=False,
unsolved=unsolved,
filter_todo=self.args.todo,
filter_category_list=cat_filter_list,
filter_difficulty=self.args.difficulty,
)

self.console.print((create_table_challenge_list(challenge_list=sorted([x.to_dict() for x in challenge_list], key=lambda x: x["difficulty_num"]), category_dict=category_dict)))

Expand Down
23 changes: 14 additions & 9 deletions command/machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class MachineCommand(BaseCommand):
limit_search: Optional[int]
retired_machines: Optional[bool]
active_machines: Optional[bool]
all_machines: Optional[bool]


# noinspection PyUnresolvedReferences
Expand All @@ -52,6 +53,7 @@ def __init__(self, htb_cli: "HtbCLI", args: argparse.Namespace):
self.search_keyword = args.search if hasattr(args, "search") else None
self.limit_search = args.limit if hasattr(args, "limit") else None
self.active_machines = args.active if hasattr(args, "active") else None
self.all_machines = args.all if hasattr(args, "all") else None


# Writing into hosts file needs root/admin privileges
Expand Down Expand Up @@ -327,9 +329,9 @@ def start_machine(self):
active_machine: ActiveMachineInfo = self.client.get_active_machine()
if active_machine is None or active_machine.id != machine.id:
if active_machine is not None and active_machine.id != machine.id:
start_new_maschine: bool = (input(
start_new_machine: bool = (input(
f'{Fore.LIGHTYELLOW_EX}The machine "{active_machine.name}" (ID: {active_machine.id}) already spawned. Do you want to stop the machine to start the machine "{machine.name}" (y/N): {Style.RESET_ALL}').strip().lower() == "y")
if not start_new_maschine:
if not start_new_machine:
return None
else:
self.args.stop_vpn = active_machine.vpn_server_id not in [x.server_id for x in self.client.get_active_connections()]
Expand Down Expand Up @@ -393,15 +395,18 @@ def list(self):
retiring_machines: List[MachineInfo] = [x[1] for x in scheduled_machines]

machines_result: List[MachineInfo] = []
# No filter set? -> Get all machines (both active and retired ones)
if self.retired_machines is None and self.active_machines is None or (not self.retired_machines and not self.active_machines):
# Handle --all, --active, --retired flags
if self.all_machines:
# Fetch both active and retired machines
machines_result += self.client.get_machine_list(retired=False, keyword=self.search_keyword, limit=self.limit_search, os_filter=os_filter)
machines_result += self.client.get_machine_list(retired=True, keyword=self.search_keyword, limit=self.limit_search, os_filter=os_filter)
machines_result += [x[0] for x in scheduled_machines]

elif self.retired_machines:
# Fetch only retired machines
machines_result += self.client.get_machine_list(retired=True, keyword=self.search_keyword, limit=self.limit_search, os_filter=os_filter)
elif self.active_machines:
else:
# Default behavior (backwards compatible): fetch active machines
# This covers both explicit --active and no flag at all
machines_result += self.client.get_machine_list(retired=False, keyword=self.search_keyword, limit=self.limit_search, os_filter=os_filter)

if len(machines_result) == 0:
Expand All @@ -411,14 +416,14 @@ def list(self):
if self.args.group_by_os:
self.console.print(create_machine_list_group_by_os(machine_info=[x.to_dict() for x in machines_result]))
else:
maschine_dict_list = []
machine_dict_list = []
for machine in machines_result:
machine_dict = machine.to_dict()
if machine in retiring_machines:
machine_dict["retiring"] = True
maschine_dict_list.append(machine_dict)
machine_dict_list.append(machine_dict)

self.console.print(create_machine_list_group_by_retired(machine_info=maschine_dict_list))
self.console.print(create_machine_list_group_by_retired(machine_info=machine_dict_list))


def reset_machine(self):
Expand Down
34 changes: 31 additions & 3 deletions command/sherlock.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class SherlockCommand(BaseCommand):
sherlock_command: Optional[str]
sherlock_only_active: Optional[bool]
sherlock_only_retired: Optional[bool]
sherlock_all: Optional[bool]
filter_category: Optional[str]

# noinspection PyUnresolvedReferences
Expand All @@ -20,6 +21,7 @@ def __init__(self, htb_cli: "HtbCLI", args: argparse.Namespace):
self.sherlock_command = args.sherlock if hasattr(args, "sherlock") else None
self.sherlock_only_active = args.active if hasattr(args, "active") else None
self.sherlock_only_retired = args.retired if hasattr(args, "retired") else None
self.sherlock_all = args.all if hasattr(args, "all") else None
self.filter_category = args.filter_category if hasattr(args, "filter_category") else None

def list(self):
Expand All @@ -33,9 +35,35 @@ def list(self):
continue
cats.append(filter_dict[filter_element.lower()])

sherlocks: List[SherlockInfo] = self.client.get_sherlocks(only_active=self.sherlock_only_active,
only_retired=self.sherlock_only_retired,
filter_sherlock_category=cats)
# Handle --all, --active, --retired flags
if self.sherlock_all:
# Fetch both active and retired sherlocks
active_sherlocks: List[SherlockInfo] = self.client.get_sherlocks(
only_active=True,
only_retired=False,
filter_sherlock_category=cats,
)
retired_sherlocks: List[SherlockInfo] = self.client.get_sherlocks(
only_active=False,
only_retired=True,
filter_sherlock_category=cats,
)
sherlocks = active_sherlocks + retired_sherlocks
elif self.sherlock_only_retired:
# Fetch only retired sherlocks
sherlocks: List[SherlockInfo] = self.client.get_sherlocks(
only_active=False,
only_retired=True,
filter_sherlock_category=cats,
)
else:
# Default behavior: fetch active sherlocks
# This covers both explicit --active and no flag at all
sherlocks: List[SherlockInfo] = self.client.get_sherlocks(
only_active=True,
only_retired=False,
filter_sherlock_category=cats,
)

self.console.print(create_sherlock_list_group_by_retired_panel(
sherlock_info=sorted([x.to_dict() for x in sherlocks],
Expand Down
57 changes: 36 additions & 21 deletions console/argument_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,14 @@ def _create_sherlock_command_parser(subparsers):
sherlock_parser: ArgumentParser = subparsers.add_parser("sherlock", help="Commands for Sherlock")
sherlock_parser.set_defaults(func=SherlockCommand)
sherlock_sub_parser = sherlock_parser.add_subparsers(title="commands", description="Available commands", dest="sherlock")
sherlock_list_parser = sherlock_sub_parser.add_parser(name="list", help="List all sherlock")
sherlock_list_parser.add_argument("--active", action="store_true",help="Only active shelocks are displayed")
sherlock_list_parser.add_argument("--retired", action="store_true", help='Only retired shelocks are displayed. If "--active" is also indicated, "--retired" is ignored')
sherlock_list_parser = sherlock_sub_parser.add_parser(name="list", help="List active sherlocks")
sherlock_status_group = sherlock_list_parser.add_mutually_exclusive_group()
sherlock_status_group.add_argument("--active", action="store_true", default=False,
help="only active sherlocks are listed (default behavior)")
sherlock_status_group.add_argument("--retired", action="store_true", default=False,
help="only retired sherlocks are listed")
sherlock_status_group.add_argument("--all", action="store_true", default=False,
help="list all sherlocks (both active and retired)")
sherlock_list_parser.add_argument("--filter-category", type=str, default=None, metavar="<CATEGORY-NAME>", help='Displays only the sherlocks which belongs to the given category. Adding more than one category must be seperated by commas [,]')


Expand Down Expand Up @@ -247,29 +252,34 @@ def add_id_name_arguments(parser: ArgumentParser):
machine_start.add_argument("--vhost-hostname", type=str, metavar="<HOSTNAME>", required=False, help="Add <HOSTNAME> to the hosts file. Adding more than one host must be seperated by commas [,]")
machine_start.add_argument("--vhost-no-machine-hostname", action="store_true", help="If indicated, the machine hostname will not be added automatically to the vhost.")

maschine_stop_parser = machine_sub_parser.add_parser(name="stop", help="Stop the active machine. If no machine is active, this command will have no effect.")
maschine_stop_parser.add_argument("--clean-hosts-file", action="store_true", help='The machine\'s hosts will be removed from the hosts file. SUDO/Root privileges are required!')
maschine_stop_parser.add_argument("--stop-vpn", action="store_true", help='Stops all HTB VPN connection.')
maschine_reset_parser = machine_sub_parser.add_parser(name="reset", help="Reset the active machine. If no machine is active, this command will have no effect.")
maschine_reset_parser.add_argument("--update-hosts-file", action="store_true", help='The machine\'s hosts will be added to or be updated the hosts file after an IP is assigned. The machine name plus ".htb" is used as hostname. SUDO/Root privileges are required!')
machine_stop_parser = machine_sub_parser.add_parser(name="stop", help="Stop the active machine. If no machine is active, this command will have no effect.")
machine_stop_parser.add_argument("--clean-hosts-file", action="store_true", help='The machine\'s hosts will be removed from the hosts file. SUDO/Root privileges are required!')
machine_stop_parser.add_argument("--stop-vpn", action="store_true", help='Stops all HTB VPN connection.')
machine_reset_parser = machine_sub_parser.add_parser(name="reset", help="Reset the active machine. If no machine is active, this command will have no effect.")
machine_reset_parser.add_argument("--update-hosts-file", action="store_true", help='The machine\'s hosts will be added to or be updated the hosts file after an IP is assigned. The machine name plus ".htb" is used as hostname. SUDO/Root privileges are required!')

machine_sub_parser.add_parser(name="status", help="Displays the active machine(s)")
machine_sub_parser.add_parser(name="extend",help="Extends the uptime of the active machine")

machine_info_parser = machine_sub_parser.add_parser(name="info", help="Displays detailed information for a machine")
add_id_name_arguments(machine_info_parser)

maschine_list_parser = machine_sub_parser.add_parser(name="list", help="Lists all machines")
maschine_list_parser.add_argument("--retired", action="store_true", help="Lists only retired machines")
maschine_list_parser.add_argument("--active", action="store_true", help="Lists only active machines")
maschine_list_parser.add_argument("--limit", type=int, metavar="<LIMIT>", default=20, help="Lists the recent <LIMIT> machines of each type, separately (active vs. retired). Default: 20")
maschine_list_parser.add_argument("--search", type=str, metavar="<Keyword>", required=False, default=None, help="Search for machines which contains <KEYWORD> in their names.")
maschine_list_parser.add_argument("--filter-win", action="store_true", help="Filter machines with Windows OS")
maschine_list_parser.add_argument("--filter-linux", action="store_true", help="Filter machines with Linux OS")
maschine_list_parser.add_argument("--filter-freebsd", action="store_true", help="Filter machines with FreeBSD OS")
maschine_list_parser.add_argument("--filter-openbsd", action="store_true", help="Filter machines with OpenBSD OS")
maschine_list_parser.add_argument("--filter-other-os", action="store_true", help="Filter machines with other OS")
maschine_list_parser.add_argument("--group-by-os", action="store_true", help="Groups the results by OS")
machine_list_parser = machine_sub_parser.add_parser(name="list", help="List active machines")
machine_status_group = machine_list_parser.add_mutually_exclusive_group()
machine_status_group.add_argument("--active", action="store_true", default=False,
help="only active machines are listed (default behavior)")
machine_status_group.add_argument("--retired", action="store_true", default=False,
help="only retired machines are listed")
machine_status_group.add_argument("--all", action="store_true", default=False,
help="list all machines (both active and retired)")
machine_list_parser.add_argument("--limit", type=int, metavar="<LIMIT>", default=20, help="Lists the recent <LIMIT> machines of each type, separately (active vs. retired). Default: 20")
machine_list_parser.add_argument("--search", type=str, metavar="<Keyword>", required=False, default=None, help="Search for machines which contains <KEYWORD> in their names.")
machine_list_parser.add_argument("--filter-win", action="store_true", help="Filter machines with Windows OS")
machine_list_parser.add_argument("--filter-linux", action="store_true", help="Filter machines with Linux OS")
machine_list_parser.add_argument("--filter-freebsd", action="store_true", help="Filter machines with FreeBSD OS")
machine_list_parser.add_argument("--filter-openbsd", action="store_true", help="Filter machines with OpenBSD OS")
machine_list_parser.add_argument("--filter-other-os", action="store_true", help="Filter machines with other OS")
machine_list_parser.add_argument("--group-by-os", action="store_true", help="Groups the results by OS")

machine_submit_flag: ArgumentParser = machine_sub_parser.add_parser(name="submit", help="Submit the flag to the active machine")
machine_submit_flag.add_argument("-ufl", "--user-flag", type=str, metavar="Flag", help="The user flag")
Expand Down Expand Up @@ -297,9 +307,14 @@ def add_id_name_arguments(parser: ArgumentParser):
challenge_sub_parser = challenge_parser.add_subparsers(title="commands", description="Available commands",
dest="challenge")
challenge_list_parser: ArgumentParser = challenge_sub_parser.add_parser(name="list",
help="List all available challenges")
challenge_list_parser.add_argument("--retired", action="store_true", default=False,
help="List active challenges")
challenge_status_group = challenge_list_parser.add_mutually_exclusive_group()
challenge_status_group.add_argument("--active", action="store_true", default=False,
help="only active challenges are listed (default behavior)")
challenge_status_group.add_argument("--retired", action="store_true", default=False,
help="only retired challenges are listed")
challenge_status_group.add_argument("--all", action="store_true", default=False,
help="list all challenges (both active and retired)")
challenge_list_parser.add_argument("--unsolved", action="store_true", help="only unsolved challenges are listed")
challenge_list_parser.add_argument("--solved", action="store_true",
help="only solved challenges are listed. If both --solved and --unsolved are specified, just unsolved will be returned")
Expand Down