-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Citrix Netscaler OS Plugin (#357)
- Loading branch information
Showing
9 changed files
with
240 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
from __future__ import annotations | ||
|
||
import re | ||
from typing import Iterator, Optional | ||
|
||
from dissect.target.filesystem import Filesystem, VirtualFilesystem | ||
from dissect.target.helpers.record import UnixUserRecord | ||
from dissect.target.plugin import OperatingSystem, export | ||
from dissect.target.plugins.os.unix.bsd._os import BsdPlugin | ||
from dissect.target.target import Target | ||
|
||
RE_CONFIG_IP = re.compile(r"-IPAddress (?P<ip>[^ ]+) ") | ||
RE_CONFIG_HOSTNAME = re.compile(r"set ns hostName (?P<hostname>[^\n]+)\n") | ||
RE_CONFIG_TIMEZONE = re.compile( | ||
r'set ns param -timezone "GMT\+(?P<hours>[0-9]+):(?P<minutes>[0-9]+)-.*-(?P<zone_name>.+)"' | ||
) | ||
RE_CONFIG_USER = re.compile(r"bind system user (?P<user>[^ ]+) ") | ||
RE_LOADER_CONFIG_KERNEL_VERSION = re.compile(r'kernel="/(?P<version>.*)"') | ||
|
||
|
||
class CitrixBsdPlugin(BsdPlugin): | ||
def __init__(self, target: Target): | ||
super().__init__(target) | ||
self._ips = [] | ||
self._hostname = None | ||
self.config_usernames = [] | ||
self._parse_netscaler_configs() | ||
|
||
def _parse_netscaler_configs(self) -> None: | ||
ips = set() | ||
usernames = set() | ||
for config_path in self.target.fs.path("/flash/nsconfig/").glob("ns.conf*"): | ||
with config_path.open("rt") as config_file: | ||
config = config_file.read() | ||
for match in RE_CONFIG_IP.finditer(config): | ||
ips.add(match.groupdict()["ip"]) | ||
for match in RE_CONFIG_USER.finditer(config): | ||
usernames.add(match.groupdict()["user"]) | ||
if config_path.name == "ns.conf": | ||
# Current configuration of the netscaler | ||
if hostname_match := RE_CONFIG_HOSTNAME.search(config): | ||
self._hostname = hostname_match.groupdict()["hostname"] | ||
if timezone_match := RE_CONFIG_TIMEZONE.search(config): | ||
tzinfo = timezone_match.groupdict() | ||
self.target.timezone = tzinfo["zone_name"] | ||
|
||
self._config_usernames = list(usernames) | ||
self._ips = list(ips) | ||
|
||
@classmethod | ||
def detect(cls, target: Target) -> Optional[Filesystem]: | ||
newfilesystem = VirtualFilesystem() | ||
is_citrix = False | ||
for fs in target.filesystems: | ||
if fs.exists("/bin/freebsd-version"): | ||
newfilesystem.map_fs("/", fs) | ||
break | ||
for fs in target.filesystems: | ||
if fs.exists("/nsconfig") and fs.exists("/boot"): | ||
newfilesystem.map_fs("/flash", fs) | ||
is_citrix = True | ||
elif fs.exists("/netscaler"): | ||
newfilesystem.map_fs("/var", fs) | ||
is_citrix = True | ||
if is_citrix: | ||
return newfilesystem | ||
return None | ||
|
||
@export(property=True) | ||
def hostname(self) -> Optional[str]: | ||
return self._hostname | ||
|
||
@export(property=True) | ||
def version(self) -> Optional[str]: | ||
version_path = self.target.fs.path("/flash/.version") | ||
version = version_path.read_text().strip() | ||
loader_conf = self.target.fs.path("/flash/boot/loader.conf").read_text() | ||
if match := RE_LOADER_CONFIG_KERNEL_VERSION.search(loader_conf): | ||
kernel_version = match.groupdict()["version"] | ||
return f"{version} ({kernel_version})" | ||
self.target.log.warn("Could not determine kernel version") | ||
return version | ||
|
||
@export(property=True) | ||
def ips(self) -> list[str]: | ||
return self._ips | ||
|
||
@export(record=UnixUserRecord) | ||
def users(self) -> Iterator[UnixUserRecord]: | ||
nstmp_users = set() | ||
nstmp_path = "/var/nstmp/" | ||
|
||
nstmp_user_path = nstmp_path + "{username}" | ||
|
||
for entry in self.target.fs.scandir(nstmp_path): | ||
if entry.is_dir() and entry.name != "#nsinternal#": | ||
nstmp_users.add(entry.name) | ||
for username in self._config_usernames: | ||
nstmp_home = nstmp_user_path.format(username=username) | ||
user_home = nstmp_home if self.target.fs.exists(nstmp_home) else None | ||
|
||
if user_home: | ||
# After this loop we will yield all users who are not in the config, but are listed in /var/nstmp/ | ||
# To prevent double records, we remove entries from the set that we are already yielding here. | ||
nstmp_users.remove(username) | ||
|
||
if username == "root" and self.target.fs.exists("/root"): | ||
# If we got here, 'root' is present both in /var/nstmp and in /root. In such cases, we yield | ||
# the 'root' user as having '/root' as a home, not in /var/nstmp. | ||
user_home = "/root" | ||
|
||
yield UnixUserRecord(name=username, home=user_home) | ||
|
||
for username in nstmp_users: | ||
yield UnixUserRecord(name=username, home=nstmp_user_path.format(username=username)) | ||
|
||
@export(property=True) | ||
def os(self) -> str: | ||
return OperatingSystem.CITRIX.value |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
NetScaler 13.1 build 30 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
autoboot_delay=3 | ||
boot_verbose=1 | ||
kernel="/ns-13.1-30.52" | ||
vfs.root.mountfrom="ufs:/dev/md0c" | ||
console="vidconsole,comconsole" |
23 changes: 23 additions & 0 deletions
23
tests/data/plugins/os/unix/bsd/citrix/flash/nsconfig/ns.conf
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#NS13.1 Build 30.52 | ||
# Modified version of netscaler configuration, to use as test data | ||
set ns config -IPAddress 10.0.0.69 -netmask 255.255.255.240 | ||
set ns config -nsvlan 10 -ifnum 0/1 -tagged NO | ||
enable ns feature LB SSL SSLVPN CH | ||
enable ns mode MBF USNIP PMTUD | ||
set system parameter -forcePasswordChange ENABLED | ||
set system user nsroot 3bb5d2fcab40405ab6d31cf1b33c19955b5a8b6ad8fa1e4f41e39cdcaf7e39d08ad19a614d2e03c98d18870ed89cb2b2c2646239ae3dfde7ecff14e4ba28abbc0 -encrypted | ||
add system user batman a30909560528fe97220cdbc22f22731d76dc4f937554819cbd6467c22d70cbd17ae595aabc90bf5b512f02ddaa400f76896bad8fdf8148cde4fa9548dbf9e5904 -encrypted | ||
add system user root 4514b74ab6ab3a0656ba32b3ad52c2bc80370436daf6fd322c42e7f0abc224494aeb18abf33856d5fac50dde9c8a3e576bdec4b39b45a5d8d2e42bd6c729cb2ec -encrypted | ||
add system user robin 9252ac8551824127fdf5d6c7d24ec86e533cc1299553dca3264a50021e74ee7dee833b9ade76fdc0b9e37cf209a82602b5d064274e1381842a872370233cf8162 -encrypted | ||
set rsskeytype -rsstype ASYMMETRIC | ||
add ns ip 10.164.69.69 255.255.255.128 -vServer DISABLED | ||
set ns encryptionParams -method AES256 -keyValue bea2ce77be21320a17a2ea0fe9f9c3bf5c1d1d9de3e081a4cce4ac3ce8279499a158c19ab34e37a0925ac8b6253a8e402d27510e2fa3c55f71e0366d51736b69612d492e85f15581260532a8df58a9bf -encrypted -encryptmethod ENCMTHD_3 -kek -suffix 2022_12_13_13_07_37 | ||
set cmp parameter -externalCache YES | ||
add server 169.254.169.254 169.254.169.254 | ||
add route 0.0.0.0 0.0.0.0 10.164.0.1 | ||
bind system user batman superuser 0 | ||
bind system user root superuser 0 | ||
bind system user robin superuser 0 | ||
set ns hostName mynetscaler | ||
set ns param -timezone "GMT+02:00-CEST-Europe/Amsterdam" | ||
set videooptimization parameter -RandomSamplingPercentage 0.00e+00 |
24 changes: 24 additions & 0 deletions
24
tests/data/plugins/os/unix/bsd/citrix/flash/nsconfig/ns.conf.0
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
#NS13.1 Build 30.52 | ||
# Modified version of netscaler configuration, to use as test data (backup file) | ||
set ns config -IPAddress 10.0.0.68 -netmask 255.255.255.240 | ||
set ns config -nsvlan 10 -ifnum 0/1 -tagged NO | ||
enable ns feature LB SSL SSLVPN CH | ||
enable ns mode MBF USNIP PMTUD | ||
set system parameter -forcePasswordChange ENABLED | ||
set system user nsroot 3bb5d2fcab40405ab6d31cf1b33c19955b5a8b6ad8fa1e4f41e39cdcaf7e39d08ad19a614d2e03c98d18870ed89cb2b2c2646239ae3dfde7ecff14e4ba28abbc0 -encrypted | ||
add system user batman a30909560528fe97220cdbc22f22731d76dc4f937554819cbd6467c22d70cbd17ae595aabc90bf5b512f02ddaa400f76896bad8fdf8148cde4fa9548dbf9e5904 -encrypted | ||
add system user root 4514b74ab6ab3a0656ba32b3ad52c2bc80370436daf6fd322c42e7f0abc224494aeb18abf33856d5fac50dde9c8a3e576bdec4b39b45a5d8d2e42bd6c729cb2ec -encrypted | ||
add system user robin 9252ac8551824127fdf5d6c7d24ec86e533cc1299553dca3264a50021e74ee7dee833b9ade76fdc0b9e37cf209a82602b5d064274e1381842a872370233cf8162 -encrypted | ||
add system user jasontodd a9de86d10286a181796e1d95235dc0c9fe3a96fcc331cc959fa9dd7660e5612023b5b0c9d2c099223cf353265f44cf098f0beeca05ebe21af7559a4d6b2dd36d5 -encrypted | ||
set rsskeytype -rsstype ASYMMETRIC | ||
add ns ip 10.164.69.69 255.255.255.128 -vServer DISABLED | ||
set ns encryptionParams -method AES256 -keyValue bea2ce77be21320a17a2ea0fe9f9c3bf5c1d1d9de3e081a4cce4ac3ce8279499a158c19ab34e37a0925ac8b6253a8e402d27510e2fa3c55f71e0366d51736b69612d492e85f15581260532a8df58a9bf -encrypted -encryptmethod ENCMTHD_3 -kek -suffix 2022_12_13_13_07_37 | ||
set cmp parameter -externalCache YES | ||
add server 169.254.169.254 169.254.169.254 | ||
add route 0.0.0.0 0.0.0.0 10.164.0.1 | ||
bind system user batman superuser 0 | ||
bind system user root superuser 0 | ||
bind system user robin superuser 0 | ||
bind system user jasontodd superuser 0 | ||
set ns hostName mynetscaler | ||
set videooptimization parameter -RandomSamplingPercentage 0.00e+00 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
from io import BytesIO | ||
|
||
from dissect.target.plugins.os.unix.bsd.citrix._os import CitrixBsdPlugin | ||
|
||
|
||
def test_unix_bsd_citrix_os(target_citrix): | ||
target_citrix.add_plugin(CitrixBsdPlugin) | ||
|
||
assert target_citrix.os == "citrix-netscaler" | ||
|
||
target_citrix.fs.mounts["/"].map_file_fh("/root/.cli_history", BytesIO(b'echo "hello world"')) | ||
target_citrix.fs.mounts["/"].map_file_fh("/var/nstmp/robin/.cli_history", BytesIO(b'echo "hello world"')) | ||
target_citrix.fs.mounts["/"].map_file_fh("/var/nstmp/alfred/.cli_history", BytesIO(b'echo "bye world"')) | ||
|
||
hostname = target_citrix.hostname | ||
version = target_citrix.version | ||
users = sorted(list(target_citrix.users()), key=lambda user: (user.name, user.home if user.home else "")) | ||
ips = target_citrix.ips | ||
ips.sort() | ||
|
||
assert hostname == "mynetscaler" | ||
assert version == "NetScaler 13.1 build 30 (ns-13.1-30.52)" | ||
|
||
assert ips == ["10.0.0.68", "10.0.0.69"] | ||
|
||
assert target_citrix.timezone == "Europe/Amsterdam" | ||
|
||
assert len(users) == 5 | ||
|
||
assert users[0].name == "alfred" # Only listed in /var/nstmp | ||
assert users[0].home == "/var/nstmp/alfred" | ||
|
||
assert users[1].name == "batman" # Only listed in config | ||
assert users[1].home is None | ||
|
||
assert users[2].name == "jasontodd" # Only listed in config backup | ||
assert users[2].home is None | ||
|
||
assert users[3].name == "robin" # Listed in config and /var/nstmp | ||
assert users[3].home == "/var/nstmp/robin" | ||
|
||
assert users[4].name == "root" # User entry for /root | ||
assert users[4].home == "/root" |