From ed3fae1f0d09853817ed4e9ce46bf9c2f28fa72e Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Sat, 6 Jun 2026 09:20:35 -0700 Subject: [PATCH] Escape all knife wrapper arguments to prevent shell command injection 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; " 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 --- src/chef-server-ctl/plugins/wrap-knife.rb | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/chef-server-ctl/plugins/wrap-knife.rb b/src/chef-server-ctl/plugins/wrap-knife.rb index f9e87dd93f..38c396920b 100644 --- a/src/chef-server-ctl/plugins/wrap-knife.rb +++ b/src/chef-server-ctl/plugins/wrap-knife.rb @@ -117,16 +117,13 @@ def self.parse_args(args) auth_args << "-c" << knife_config # auth_args << "--config-option" << "ssl_verify_mode=verify_none" - # Build command args - don't escape config options with = signs + # Build command args. Escape every argument before it is interpolated + # into the shell command string. Shellwords.escape leaves benign + # characters (including "=") untouched, so "key=value" style config + # options still pass through unchanged, while shell metacharacters in + # any argument are neutralized rather than executed. all_args = transformed_args + auth_args - 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(" ") + escaped_args = all_args.map { |arg| Shellwords.escape(arg) }.join(" ") knife_command = "#{knife_cmd} #{opc_noun} #{opc_cmd} #{escaped_args}" status = run_command(knife_command)