Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IIS plugin does not process logs in default dir without ApplicationHo… #549

Merged
merged 9 commits into from
Feb 27, 2024
1 change: 1 addition & 0 deletions dissect/target/helpers/fsutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"generate_addr",
"glob_ext",
"glob_split",
"has_glob_magic",
"isabs",
"join",
"normalize",
Expand Down
43 changes: 35 additions & 8 deletions dissect/target/plugins/apps/webserver/iis.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from dissect.target import plugin
from dissect.target.exceptions import FileNotFoundError as DissectFileNotFoundError
from dissect.target.exceptions import PluginError, UnsupportedPluginError
from dissect.target.helpers.fsutil import has_glob_magic
from dissect.target.helpers.record import TargetRecordDescriptor
from dissect.target.plugins.apps.webserver.webserver import (
WebserverAccessLogRecord,
Expand Down Expand Up @@ -54,41 +55,67 @@

APPLICATION_HOST_CONFIG = "sysvol/windows/system32/inetsrv/config/applicationHost.config"

DEFAULT_LOG_PATHS = [
"sysvol\\Windows\\System32\\LogFiles\\W3SVC*\\*.log",
"sysvol\\Windows.old\\Windows\\System32\\LogFiles\\W3SVC*\\*.log",
"sysvol\\inetpub\\logs\\LogFiles\\*.log",
"sysvol\\inetpub\\logs\\LogFiles\\W3SVC*\\*.log",
"sysvol\\Resources\\Directory\\*\\LogFiles\\Web\\W3SVC*\\*.log",
]

__namespace__ = "iis"

def __init__(self, target):
super().__init__(target)
self.config = self.target.fs.path(self.APPLICATION_HOST_CONFIG)
self.log_dirs = self.get_log_dirs()

self._create_extended_descriptor = lru_cache(4096)(self._create_extended_descriptor)

def check_compatible(self) -> None:
if not self.config.exists() and not self.target.fs.path("sysvol/files").exists():
raise UnsupportedPluginError("No ApplicationHost config file found")
if not self.log_dirs:
raise UnsupportedPluginError("No IIS log files found")

@plugin.internal
def get_log_dirs(self) -> list[tuple[str, Path]]:
log_paths = []
log_paths = set()

if (sysvol_files := self.target.fs.path("sysvol/files")).exists():
log_paths.append(("auto", sysvol_files))
log_paths.add(("auto", sysvol_files))

Check warning on line 84 in dissect/target/plugins/apps/webserver/iis.py

View check run for this annotation

Codecov / codecov/patch

dissect/target/plugins/apps/webserver/iis.py#L84

Added line #L84 was not covered by tests

try:
xml_data = ElementTree.fromstring(self.config.open().read(), forbid_dtd=True)
for log_file_element in xml_data.findall("*/sites/*/logFile"):
log_format = log_file_element.get("logFormat") or "W3C"
if log_dir := log_file_element.get("directory"):
log_dir = self.target.resolve(log_dir)
log_paths.append((log_format, log_dir))
log_paths.add((log_format, log_dir))

except (ElementTree.ParseError, DissectFileNotFoundError) as e:
self.target.log.warning(f"Error while parsing {self.config}: {e}")
self.target.log.warning("Error while parsing %s:%s", self.config, e)

for log_path in self.DEFAULT_LOG_PATHS:
try:
# later on we use */*.log to collect the files, so we need to move up 2 levels
log_dir = self.target.fs.path(log_path).parents[1]
except IndexError:
self.target.log.error("Incompatible path found: %s", log_path)
continue

if not has_glob_magic(str(log_dir)) and log_dir.exists():
log_paths.add(("auto", log_dir))
continue

for _log_dir_str in self.target.fs.glob(str(log_dir)):
if not (_log_dir := self.target.fs.path(_log_dir_str)).is_dir():
continue

Check warning on line 111 in dissect/target/plugins/apps/webserver/iis.py

View check run for this annotation

Codecov / codecov/patch

dissect/target/plugins/apps/webserver/iis.py#L111

Added line #L111 was not covered by tests
log_paths.add(("auto", _log_dir))

return log_paths
return list(log_paths)

@plugin.internal
def iter_log_format_path_pairs(self) -> list[tuple[str, str]]:
for log_format, log_dir_path in self.get_log_dirs():
for log_format, log_dir_path in self.log_dirs:
for log_file in log_dir_path.glob("*/*.log"):
yield (log_format, log_file)

Expand Down
27 changes: 27 additions & 0 deletions tests/plugins/apps/webserver/test_iis.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,30 @@ def test_plugins_apps_webservers_iis_access_w3c_format(target_win: Target, fs_wi
== "Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/93.0.4577.82+Safari/537.36+Edg/93.0.961.52" # noqa: E501
)
assert w3c_record_3.source == "C:/Users/John/w3c-logs/W3SVC1/u_ex211001_x.log"


@pytest.mark.parametrize(
"map_dir",
[
("inetpub/logs/LogFiles/W3SVC1"),
("inetpub/logs/LogFiles"),
("Windows/System32/LogFiles/W3SVC1"),
("Windows.old/Windows/System32/LogFiles/W3SVC2"),
("Resources/Directory/aaa/LogFiles/Web/W3SVC1"),
],
)
@pytest.mark.parametrize(
"log_format",
[
("iis"),
("w3c"),
],
)
def test_plugins_apps_webservers_iis_access_iis_format_noconfig(
target_win_tzinfo: Target, fs_win: VirtualFilesystem, map_dir: str, log_format: str
) -> None:
data_dir = absolute_path(f"_data/plugins/apps/webserver/iis/iis-logs-{log_format}/W3SVC1")
fs_win.map_dir(map_dir, data_dir)
target_win_tzinfo.add_plugin(iis.IISLogsPlugin)
results = list(target_win_tzinfo.iis.access())
assert len(results) > 0
Loading