Skip to content

Refactor: Replace unsafe system() call with fork/execvp in open_uri to prevent shell injection#1498

Open
bytecodesky wants to merge 1 commit intotsl0922:mainfrom
bytecodesky:fix/replace-unsafe-call
Open

Refactor: Replace unsafe system() call with fork/execvp in open_uri to prevent shell injection#1498
bytecodesky wants to merge 1 commit intotsl0922:mainfrom
bytecodesky:fix/replace-unsafe-call

Conversation

@bytecodesky
Copy link

Bug: CWE-78: OS Command Injection in open_uri via Unsanitized Shell Interpolation

Impact:
The open_uri function constructs a shell command using snprintf by directly interpolating the uri argument without any sanitization, quoting, or escaping. It then executes this command via system(). If the uri parameter originates from an untrusted source and contains shell metacharacters (e.g., ;, &, |, $(), or backticks), an attacker can break out of the intended open or xdg-open command and execute arbitrary OS commands. This results in arbitrary code execution (ACE) with the privileges of the process running ttyd. This vulnerability is present in both the macOS (__APPLE__) and Linux (xdg-open) code paths.

Vulnerable Code:

int open_uri(char *uri) {
#ifdef __APPLE__
  char command[256];
  snprintf(command, sizeof(command), "open %s > /dev/null 2>&1", uri);
  return system(command);
#elif defined(_WIN32) || defined(__CYGWIN__)
  return ShellExecute(0, 0, uri, 0, 0, SW_SHOW) > (HINSTANCE)32 ? 0 : 1;
#else
  // check if X server is running
  if (system("xset -q > /dev/null 2>&1")) return 1;
  char command[256];
  snprintf(command, sizeof(command), "xdg-open %s > /dev/null 2>&1", uri);
  return system(command);
#endif
}

Proposed Fix:
To eliminate the command injection vector, we must bypass the shell entirely. The most robust solution is to replace system() with a fork() and execvp() implementation. This passes the uri strictly as an argument to the executable, neutralizing any shell metacharacters.

#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>

int open_uri(char *uri) {
#if defined(_WIN32) || defined(__CYGWIN__)
  return ShellExecute(0, 0, uri, 0, 0, SW_SHOW) > (HINSTANCE)32 ? 0 : 1;
#else
#ifndef __APPLE__
  // check if X server is running
  if (system("xset -q > /dev/null 2>&1")) return 1;
#endif

  pid_t pid = fork();
  if (pid < 0) {
    return 1; // fork failed
  } else if (pid == 0) {
    // Child process: redirect stdout and stderr to /dev/null
    int fd = open("/dev/null", O_WRONLY);
    if (fd >= 0) {
      dup2(fd, STDOUT_FILENO);
      dup2(fd, STDERR_FILENO);
      close(fd);
    }
    
#ifdef __APPLE__
    char *args[] = {"open", uri, NULL};
#else
    char *args[] = {"xdg-open", uri, NULL};
#endif
    
    execvp(args[0], args);
    exit(1); // exec failed
  } else {
    // Parent process: wait for child to maintain synchronous behavior
    int status;
    waitpid(pid, &status, 0);
    return (WIFEXITED(status) && WEXITSTATUS(status) == 0) ? 0 : 1;
  }
#endif
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant