|
54 | 54 | #include <csv_writer.h> |
55 | 55 | #include <cf-agent-enterprise-stubs.h> |
56 | 56 | #include <cf-windows-functions.h> |
| 57 | +#include <cf3.defs.h> |
57 | 58 |
|
58 | 59 | /* Called structure: |
59 | 60 |
|
@@ -104,6 +105,7 @@ typedef enum |
104 | 105 | PACKAGE_PROMISE_TYPE_NEW_ERROR |
105 | 106 | } PackagePromiseType; |
106 | 107 |
|
| 108 | +static bool SanitizePackagePromiser(EvalContext *ctx, const Attributes *a, const Promise *pp); |
107 | 109 | static bool PackageSanityCheck(EvalContext *ctx, const Attributes *a, const Promise *pp); |
108 | 110 |
|
109 | 111 | static bool VerifyInstalledPackages(EvalContext *ctx, PackageManager **alllists, const char *default_arch, const Attributes *a, const Promise *pp, PromiseResult *result); |
@@ -186,6 +188,12 @@ PromiseResult VerifyPackagesPromise(EvalContext *ctx, const Promise *pp) |
186 | 188 | PackagePromiseType package_promise_type = |
187 | 189 | GetPackagePromiseVersion(&a.packages, &a.new_packages); |
188 | 190 |
|
| 191 | + if (!SanitizePackagePromiser(ctx, &a, pp)) |
| 192 | + { |
| 193 | + Log(LOG_LEVEL_VERBOSE, "Package promise %s failed sanitization check", pp->promiser); |
| 194 | + return PROMISE_RESULT_FAIL; |
| 195 | + } |
| 196 | + |
189 | 197 | switch (package_promise_type) |
190 | 198 | { |
191 | 199 | case PACKAGE_PROMISE_TYPE_NEW: |
@@ -368,6 +376,51 @@ static PromiseResult HandleOldPackagePromiseType(EvalContext *ctx, const Promise |
368 | 376 | return result; |
369 | 377 | } |
370 | 378 |
|
| 379 | +static bool SanitizePackagePromiser(EvalContext *ctx, const Attributes *a, const Promise *pp) |
| 380 | +{ |
| 381 | + assert(a != NULL); |
| 382 | + assert(pp != NULL); |
| 383 | + |
| 384 | + const char *promiser = pp->promiser; |
| 385 | + |
| 386 | + /* Shell metacharacters that could be used for command injection |
| 387 | + * |
| 388 | + * Here's the rationale for each shell metacharacter in the list: |
| 389 | + * ; Command separator, allows running additional commands |
| 390 | + * | Pipe, can redirect output to another command |
| 391 | + * & Background execution or command chaining (`&&`) |
| 392 | + * ` Command substitution (backticks execute enclosed command) |
| 393 | + * $ Variable expansion and command substitution (`$(...)`) |
| 394 | + * ( Subshell execution, command grouping |
| 395 | + * < Input/output redirection, can overwrite files |
| 396 | + * { Brace expansion, command grouping |
| 397 | + * [ Character class matching |
| 398 | + * * Wildcard, matches any characters |
| 399 | + * ? Wildcard, matches single character |
| 400 | + * ~ Home directory expansion |
| 401 | + * \ Escape character, can break out of quoting |
| 402 | + * ' Quotes, can break parsing if unbalanced |
| 403 | + * ! History expansion in some shells |
| 404 | + * # Comment character, can truncate commands |
| 405 | + * \n \r Newlines can inject additional commands |
| 406 | + */ |
| 407 | + const char *shell_metacharacters = ";|&`$(){}[]<>!#*?~\\'\"\n\r"; |
| 408 | + |
| 409 | + if (a->packages.package_commands_useshell) |
| 410 | + { |
| 411 | + const char *bad_char = strpbrk(promiser, shell_metacharacters); |
| 412 | + if (bad_char != NULL) |
| 413 | + { |
| 414 | + cfPS_HELPER_2ARG(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, |
| 415 | + "Package promiser '%s' contains shell metacharacter '%c' which could allow command injection", |
| 416 | + promiser, *bad_char); |
| 417 | + return false; |
| 418 | + } |
| 419 | + } |
| 420 | + |
| 421 | + return true; |
| 422 | +} |
| 423 | + |
371 | 424 | /** |
372 | 425 | @brief Pre-check of promise contents |
373 | 426 |
|
|
0 commit comments