Skip to content

Escape all knife wrapper arguments to prevent shell command injection#4203

Open
tas50 wants to merge 1 commit into
chef:mainfrom
tas50:fix-csc-wrap-knife-shell-escape
Open

Escape all knife wrapper arguments to prevent shell command injection#4203
tas50 wants to merge 1 commit into
chef:mainfrom
tas50:fix-csc-wrap-knife-shell-escape

Conversation

@tas50

@tas50 tas50 commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Problem

The org-* / user-* management commands in src/chef-server-ctl/plugins/wrap-knife.rb assemble a knife invocation as a single string and run it through a shell via run_command:

escaped_args = all_args.map do |arg|
  # Don't escape arguments that contain = (like config options)
  if arg.include?('=')
    arg
  else
    Shellwords.escape(arg)
  end
end.join(" ")

knife_command = "#{knife_cmd} #{opc_noun} #{opc_cmd} #{escaped_args}"
status = run_command(knife_command)

Every argument is escaped with Shellwords.escape except arguments containing =, which are concatenated verbatim. The carve-out was meant to preserve key=value config options, but it means any argument containing = can inject arbitrary shell syntax. A positional field or option value such as:

x=y; touch /tmp/pwned

has its ; and following command interpreted by the shell rather than being passed to knife as a literal argument.

Fix

Escape every argument unconditionally:

escaped_args = all_args.map { |arg| Shellwords.escape(arg) }.join(" ")

Shellwords.escape leaves benign characters (including =, -, _, ., /, and alphanumerics) untouched, so legitimate key=value config options continue to work unchanged, while shell metacharacters in any argument are neutralized instead of executed.

Note: the chef-server-ctl gem bundle targets Ruby 3.1 and could not be installed in my local environment (Ruby 4.x), so I verified the change with ruby -c and am relying on CI for the rspec suite.

@tas50 tas50 requested review from a team as code owners June 6, 2026 16:20
@netlify

netlify Bot commented Jun 6, 2026

Copy link
Copy Markdown

👷 Deploy Preview for chef-server processing.

Name Link
🔨 Latest commit ed3fae1
🔍 Latest deploy log https://app.netlify.com/projects/chef-server/deploys/6a244943fcb63e0009fb8cc1

The org/user management commands in wrap-knife.rb build a knife command
as a single string that is run through a shell via run_command. Each
argument was passed through Shellwords.escape EXCEPT arguments
containing '=', which were concatenated verbatim:

    if arg.include?('=')
      arg
    else
      Shellwords.escape(arg)
    end

The '=' carve-out was intended to preserve "key=value" config options,
but it lets any argument containing '=' inject arbitrary shell syntax.
For example a field value such as "x=y; <command>" would have its
metacharacters interpreted by the shell instead of being passed to
knife as a literal argument.

Escape every argument unconditionally. Shellwords.escape leaves benign
characters such as '=' untouched, so legitimate "key=value" options
continue to work while shell metacharacters are neutralized.

Signed-off-by: Tim Smith <tsmith84@proton.me>
@tas50 tas50 force-pushed the fix-csc-wrap-knife-shell-escape branch from a73c943 to ed3fae1 Compare June 6, 2026 16:22
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