diff --git a/splunklib/searchcommands/environment.py b/splunklib/searchcommands/environment.py index 83ee939f4..474dacfeb 100644 --- a/splunklib/searchcommands/environment.py +++ b/splunklib/searchcommands/environment.py @@ -97,6 +97,12 @@ def configure_logging(logger_name, filename=None): global _current_logging_configuration_file filename = path.realpath(filename) + app_root_real = path.realpath(app_root) + if path.commonpath([filename, app_root_real]) != app_root_real: # pyright: ignore[reportUnknownArgumentType] + raise ValueError( + f'Logging configuration file "{filename}" is outside the app directory' + ) + if filename != _current_logging_configuration_file: working_directory = getcwd() chdir(app_root) @@ -114,9 +120,22 @@ def configure_logging(logger_name, filename=None): _current_logging_configuration_file = None + +def _find_app_root(app_file: str) -> str: + """Return the app root directory for a search command script.""" + directory: str = path.abspath(path.dirname(app_file)) + while True: + parent, name = path.split(directory) + if name == "bin": + return parent + if parent == directory: + return path.dirname(path.abspath(path.dirname(app_file))) + directory = parent + + splunk_home = path.abspath(path.join(getcwd(), environ.get("SPLUNK_HOME", ""))) app_file = getattr(sys.modules["__main__"], "__file__", sys.executable) -app_root = path.dirname(path.abspath(path.dirname(app_file))) +app_root = _find_app_root(app_file) splunklib_logger, logging_configuration = configure_logging("splunklib") diff --git a/tests/unit/searchcommands/test_builtin_options.py b/tests/unit/searchcommands/test_builtin_options.py index 911321251..f2827c5ef 100644 --- a/tests/unit/searchcommands/test_builtin_options.py +++ b/tests/unit/searchcommands/test_builtin_options.py @@ -137,6 +137,39 @@ def test_logging_configuration(self): f"Expected ValueError, but logging_configuration={command.logging_configuration}" ) + inside_app_root_logging_configuration = os.path.join( + environment.app_root, "default", "logging.conf" + ) + command.logging_configuration = inside_app_root_logging_configuration + assert command.logging_configuration == inside_app_root_logging_configuration, ( + "logging_configuration should accept an absolute path inside the app directory" + ) + + try: + command.logging_configuration = os.path.realpath(__file__) + except ValueError: + pass + except BaseException as e: + pytest.fail( + f"Expected ValueError for a path outside the app directory, but {type(e)} was raised" + ) + else: + pytest.fail( + f"Expected ValueError for a path outside the app directory, but {command.logging_configuration=}" + ) + + # logging_configuration raises a value error when a relative path traverses outside the app directory (RCE guard) + try: + command.logging_configuration = os.path.join("..", "..", "..", "__init__.py") + except ValueError: + pass + except BaseException as e: + pytest.fail(f"Expected ValueError, but {type(e)} was raised") + else: + pytest.fail( + f"Expected ValueError, but logging_configuration={command.logging_configuration}" + ) + def test_logging_level(self): rebase_environment("app_without_logging_configuration") command = StubbedSearchCommand()