From f2a14cec4bfc0598fd33de2634bfc0b0f6c25637 Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Sat, 6 Jun 2026 09:21:36 -0700 Subject: [PATCH] Prevent shell command injection in psql --options The psql command built a single command string and ran it with `exec cmd`. Because the string contained the user-supplied --options value and was passed to exec as one string, Ruby executed it through a shell, so shell metacharacters in --options were interpreted. For example: chef-server-ctl psql opscode_chef --options '; ' would run the injected command instead of treating it as a psql option. Build an explicit argument vector and call exec(*cmd) so no shell is involved. The --options value is tokenized with Shellwords.split and each token is passed to psql as a literal argument, preserving the ability to pass real psql options without allowing shell interpretation. Signed-off-by: Tim Smith --- src/chef-server-ctl/plugins/psql.rb | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/chef-server-ctl/plugins/psql.rb b/src/chef-server-ctl/plugins/psql.rb index d4817958aa..0337c43c5e 100644 --- a/src/chef-server-ctl/plugins/psql.rb +++ b/src/chef-server-ctl/plugins/psql.rb @@ -14,6 +14,7 @@ # limitations under the License. require "json" +require "shellwords" known_dbs = { "opscode_chef" => { "dbname" => "opscode_chef", "config_key" => "opscode-erchef", "hashseed" => "private_chef" }, @@ -34,8 +35,12 @@ service_name = ARGV[1] write_arg = "--write" options_arg = "--options" + psql_options = [] if ARGV.include?(options_arg) - psql_options = " #{ARGV[ARGV.index(options_arg) + 1]}" + # Split the user-supplied options string into individual tokens + # (honoring shell-style quoting) so each becomes a literal argv + # element passed to psql. This avoids handing the string to a shell. + psql_options = Shellwords.split(ARGV[ARGV.index(options_arg) + 1].to_s) end known_db_names = known_dbs.keys.sort.join(", ") @@ -96,6 +101,14 @@ ENV["PGPASSWORD"] = db_password ENV["PAGER"] = "less" ENV["LESS"] = "-iMSx4 -FX" - cmd = "/opt/opscode/embedded/bin/psql --host #{db_host} --username #{db_username} --port #{db_port} --dbname #{db_name}#{psql_options}" - exec cmd + # Exec psql with an explicit argument vector (no shell). Passing the + # command as separate arguments means user-supplied --options tokens are + # delivered to psql verbatim and are never interpreted as shell syntax. + cmd = ["/opt/opscode/embedded/bin/psql", + "--host", db_host.to_s, + "--username", db_username.to_s, + "--port", db_port.to_s, + "--dbname", db_name.to_s, + *psql_options] + exec(*cmd) end