-
Notifications
You must be signed in to change notification settings - Fork 0
🛡️ Sentinel: [CRITICAL] Fix predictable temporary file vulnerability (CWE-377) #175
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |
| """ | ||
|
|
||
| import logging | ||
| import uuid | ||
| from automation.python.config import KscConfig | ||
| from automation.python.remote import connect_ksc_host, run_remote_sudo | ||
| from automation.python.logging_utils import ( | ||
|
|
@@ -57,15 +58,17 @@ def reconfigure_ksc_service(config: KscConfig, apply: bool = False) -> None: | |
| KLSRV_UNATT_KLADMINS_PASSWORD={config.ksc_admin_password} | ||
| """ | ||
|
|
||
| temp_filename = f"/tmp/reconfig_ans_{uuid.uuid4().hex}.txt" | ||
|
|
||
| if not apply: | ||
| run_logger.info( | ||
| "[CHECK] Seria gerado um arquivo de respostas KLAUTOANSWERS em '/tmp/reconfig_ans.txt' via SFTP (modo 0600)." | ||
| f"[CHECK] Seria gerado um arquivo de respostas KLAUTOANSWERS em '{temp_filename}' via SFTP (modo 0600)." | ||
| ) | ||
| run_logger.info( | ||
| "[CHECK] Seria executado: KLAUTOANSWERS=/tmp/reconfig_ans.txt /opt/kaspersky/ksc64/lib/bin/setup/postinstall.pl" | ||
| f"[CHECK] Seria executado: KLAUTOANSWERS={temp_filename} /opt/kaspersky/ksc64/lib/bin/setup/postinstall.pl" | ||
| ) | ||
| run_logger.info( | ||
| "[CHECK] O arquivo temporário '/tmp/reconfig_ans.txt' seria removido do servidor remoto." | ||
| f"[CHECK] O arquivo temporário '{temp_filename}' seria removido do servidor remoto." | ||
| ) | ||
| run_logger.info( | ||
| "[CHECK] Os serviços (kladminserver_srv.service e ksc-web-console.service) seriam reiniciados." | ||
|
|
@@ -79,10 +82,10 @@ def reconfigure_ksc_service(config: KscConfig, apply: bool = False) -> None: | |
|
|
||
| # Upload do arquivo de respostas via SFTP de forma isolada e segura | ||
| run_logger.info( | ||
| "Gerando arquivo de respostas em /tmp/reconfig_ans.txt via SFTP..." | ||
| f"Gerando arquivo de respostas em {temp_filename} via SFTP..." | ||
| ) | ||
| sftp = client.open_sftp() | ||
| f = sftp.file("/tmp/reconfig_ans.txt", "w") | ||
| f = sftp.file(temp_filename, "w") | ||
| # Força permissão apenas de leitura/escrita pelo owner para evitar vazamento local | ||
| f.chmod(0o600) | ||
| f.write(ans_content) | ||
|
|
@@ -91,22 +94,22 @@ def reconfigure_ksc_service(config: KscConfig, apply: bool = False) -> None: | |
|
|
||
| # Executa postinstall.pl | ||
| postinstall_cmd = ( | ||
| "KLAUTOANSWERS=/tmp/reconfig_ans.txt " | ||
| f"KLAUTOANSWERS={temp_filename} " | ||
| "/opt/kaspersky/ksc64/lib/bin/setup/postinstall.pl" | ||
| ) | ||
| run_cmd = f"-E bash -c '{postinstall_cmd}'" | ||
| log_json(run_logger, "run_command_start", cmd="postinstall.pl (silencioso)") | ||
|
|
||
| out, err, status = run_remote_sudo( | ||
| client, f"-E bash -c '{postinstall_cmd}'", config.ksc_pass | ||
| client, run_cmd, config.ksc_pass | ||
| ) | ||
| if out: | ||
| for line in out.splitlines(): | ||
| run_logger.info(line.strip()) | ||
| log_json(run_logger, "run_command_end", status=status, stderr=err) | ||
|
|
||
| # Limpeza do arquivo temporário | ||
| client.exec_command("rm -f /tmp/reconfig_ans.txt") | ||
| client.exec_command(f"rm -f {temp_filename}") | ||
|
Comment on lines
+97
to
+112
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- target file outline ---'
ast-grep outline automation/ops/reconfigure_ksc_service.py
printf '%s\n' '--- target context ---'
sed -n '1,180p' automation/ops/reconfigure_ksc_service.py
printf '%s\n' '--- remote helper context ---'
sed -n '1,110p' automation/python/remote.py
printf '%s\n' '--- relevant call sites and cleanup paths ---'
rg -n -C 4 'run_remote_sudo|temp_filename|exec_command|finally|close' automation/ops/reconfigure_ksc_service.py automation/python/remote.pyRepository: portosoft/ksc-deployment-runbook Length of output: 17660 🏁 Script executed: #!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
p = Path("automation/ops/reconfigure_ksc_service.py")
text = p.read_text()
for i, line in enumerate(text.splitlines(), 1):
if "temp_filename" in line or "run_remote_sudo" in line or "exec_command" in line or "finally" in line:
print(f"{i}: {line}")
PYRepository: portosoft/ksc-deployment-runbook Length of output: 1045 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- tests and documentation references ---'
rg -n -C 3 'reconfigure_ksc_service|reconfig_ans_|postinstall\.pl|recv_exit_status|rm -f' . \
-g '!*.pyc' -g '!node_modules' -g '!dist' -g '!build' || true
printf '%s\n' '--- standalone control-flow verifier ---'
python3 - <<'PY'
class Channel:
def __init__(self, status):
self.status = status
self.waited = False
def recv_exit_status(self):
self.waited = True
return self.status
class Stream:
def __init__(self, status=0):
self.channel = Channel(status)
def read(self):
return b""
def decode(self, *args, **kwargs):
return ""
class Client:
def __init__(self, fail_run=False):
self.fail_run = fail_run
self.commands = []
self.closed = False
self.cleanup_stream = None
def exec_command(self, command):
self.commands.append(command)
if command.startswith("sudo "):
if self.fail_run:
raise RuntimeError("remote operation failed")
return Stream(), Stream(), Stream()
self.cleanup_stream = Stream()
return None, self.cleanup_stream, Stream()
def close(self):
self.closed = True
def run_remote_sudo(client):
client.exec_command("sudo postinstall")
if client.fail_run:
raise RuntimeError("remote operation failed")
return "", "", 1
def current_flow(client):
try:
run_remote_sudo(client)
client.exec_command("rm -f /tmp/reconfig_ans_uuid.txt")
finally:
client.close()
for label, client in (
("run raises", Client(fail_run=True)),
("run returns failure", Client(fail_run=False)),
):
try:
current_flow(client)
except RuntimeError:
pass
cleanup_started = any(c.startswith("rm -f ") for c in client.commands)
cleanup_waited = bool(client.cleanup_stream and client.cleanup_stream.channel.waited)
print(label, {
"cleanup_started": cleanup_started,
"cleanup_waited": cleanup_waited,
"client_closed": client.closed,
})
PYRepository: portosoft/ksc-deployment-runbook Length of output: 11778 🌐 Web query:
💡 Result: In Paramiko, the SSHClient.exec_command method is used to execute a command on a remote SSH server [1][2]. Return Values The exec_command method returns a 3-tuple containing three file-like objects: (stdin, stdout, stderr) [1][3]. - stdin: A write-only file-like object used to send input to the remote command [3][4]. - stdout: A read-only file-like object used to read the command's standard output [3][4]. - stderr: A read-only file-like object used to read the command's standard error [3][4]. Accessing Exit Status To retrieve the exit status (exit code) of the command, you access the underlying channel associated with the streams [5]. The recv_exit_status method is available on the channel object [4][6]. Since the stdout stream is linked to this channel, you can access it via stdout.channel [5]. Example Usage: stdin, stdout, stderr = client.exec_command("ls -l") # Read output output = stdout.read # Get exit status exit_status = stdout.channel.recv_exit_status Important Considerations - Blocking Behavior: The recv_exit_status method will block and wait until the remote command finishes or the channel is closed [4][6]. - Potential Hangs: If the remote command produces output that exceeds the SSH transport's window size, calling recv_exit_status before reading all output (e.g., via stdout.read) can cause the script to hang indefinitely [6][7]. It is recommended to read from the streams (stdout/stderr) before calling recv_exit_status to ensure the buffer is cleared [6][7]. - Polling: If you wish to avoid blocking, you can use exit_status_ready on the channel to check if the process has finished before calling recv_exit_status [6][8]. Citations:
Remove the response file in an exception-safe, synchronous cleanup path. If the SFTP upload or 🤖 Prompt for AI Agents |
||
|
|
||
| if status != 0: | ||
| raise OpsError(f"Erro na execução do postinstall.pl: {err}") | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: portosoft/ksc-deployment-runbook
Length of output: 28513
🏁 Script executed:
Repository: portosoft/ksc-deployment-runbook
Length of output: 11350
🌐 Web query:
Paramiko SFTPClient.file mode "w" open flags O_EXCL symlink behavior SFTPFile official documentation💡 Result:
In Paramiko, the 'x' mode flag passed to SFTPClient.open implements exclusive creation logic equivalent to the POSIX O_EXCL flag [1][2]. This flag ensures that the operation only succeeds if the file is successfully created and did not previously exist [1][3]. Regarding your query on symlink behavior: 1. The 'x' mode is specifically an argument for the SFTPClient.open method, which is used for opening files on a remote server [1][4]. 2. The Paramiko SFTPClient API provides a distinct method, symlink(source, dest), for creating symbolic links [1][2]. The 'x' mode flag used in open does not apply to the symlink method [1][2]. 3. The open method does not inherently follow symlinks in a way that allows for exclusive creation checks on the target of a link via the 'x' flag itself; rather, it attempts to open (and potentially create) the specified filename [2]. If a file already exists at the target path (whether it is a regular file or a symlink), the 'x' flag will cause the operation to fail, as the creation condition is not met [1][5]. The underlying implementation in SFTPClient.open maps the 'x' mode to the SFTP_FLAG_EXCL and SFTP_FLAG_CREATE flags, which are sent to the remote SFTP server [2]. The server is then responsible for enforcing this exclusive creation logic [4][5].
Citations:
🏁 Script executed:
Repository: portosoft/ksc-deployment-runbook
Length of output: 2456
Reduce the prevention claim to risk reduction.
Paramiko requests exclusive creation only with mode
x;sftp.file(temp_filename, "w")does not requestO_EXCL. Change “avoiding race conditions or symlink attacks” to “reducing the risk of race conditions or symlink attacks.”🤖 Prompt for AI Agents