diff --git a/features/dist-archive.feature b/features/dist-archive.feature index 9c0981f..5e41a3a 100644 --- a/features/dist-archive.feature +++ b/features/dist-archive.feature @@ -562,3 +562,58 @@ Feature: Generate a distribution archive of a project """ foo/foo.txt """ + + Scenario: Prevents GNU tar argument injection via Version header + Given an empty directory + And a foo/.distignore file: + """ + """ + And a foo/style.css file: + """ + /* + Theme Name: Test Theme + Version: 1.0 --checkpoint=1 --checkpoint-action=exec=foo/pwn.sh --owner=x + */ + """ + And a foo/pwn.sh file: + """ + echo "RCE" > rce_proof.txt + """ + + When I run `wp dist-archive foo --format=targz` + Then STDOUT should match /^Success: Created foo.tar.gz \(Size: \d+(?:\.\d*)? [a-zA-Z]{1,3}\)$/ + And the foo.tar.gz file should exist + And the rce_proof.txt file should not exist + + Scenario: Rejects invalid version headers in plugin PHP file and composer.json + Given an empty directory + And a bar/.distignore file: + """ + """ + And a bar/bar.php file: + """ + ).*/', '', $match[1] ) ); + $matched_version = trim( (string) preg_replace( '/\s*(?:\*\/|\?>).*/', '', $match[1] ) ); + if ( preg_match( '/^[A-Za-z0-9][A-Za-z0-9._+-]*$/', $matched_version ) ) { + $version = $matched_version; + } } } @@ -299,8 +302,11 @@ private function get_version( $source_dir_path ) { $contents = str_replace( "\r", "\n", $contents ); $pattern = '/^[ \t\/*#@]*Version:(.*)$/mi'; if ( preg_match( $pattern, $contents, $match ) && $match[1] ) { - $version = trim( (string) preg_replace( '/\s*(?:\*\/|\?>).*/', '', $match[1] ) ); - break; + $matched_version = trim( (string) preg_replace( '/\s*(?:\*\/|\?>).*/', '', $match[1] ) ); + if ( preg_match( '/^[A-Za-z0-9][A-Za-z0-9._+-]*$/', $matched_version ) ) { + $version = $matched_version; + break; + } } } } @@ -311,7 +317,10 @@ private function get_version( $source_dir_path ) { */ $composer_obj = json_decode( (string) file_get_contents( $source_dir_path . '/composer.json' ) ); if ( $composer_obj && ! empty( $composer_obj->version ) ) { - $version = trim( $composer_obj->version ); + $matched_version = trim( (string) $composer_obj->version ); + if ( preg_match( '/^[A-Za-z0-9][A-Za-z0-9._+-]*$/', $matched_version ) ) { + $version = $matched_version; + } } } @@ -319,13 +328,17 @@ private function get_version( $source_dir_path ) { /** * @var WP_CLI\ProcessRun $response */ - $response = WP_CLI::launch( "cd {$source_dir_path}; git log --pretty=format:'%h' -n 1", false, true ); + $response = WP_CLI::launch( Utils\esc_cmd( 'git -C %s log --pretty=format:%%h -n 1', $source_dir_path ), false, true ); $maybe_hash = trim( $response->stdout ); - if ( $maybe_hash && 7 === strlen( $maybe_hash ) ) { + if ( $maybe_hash && 7 === strlen( $maybe_hash ) && preg_match( '/^[0-9a-f]{7}$/i', $maybe_hash ) ) { $version .= '-' . $maybe_hash; } } + if ( ! empty( $version ) && ! preg_match( '/^[A-Za-z0-9][A-Za-z0-9._+-]*$/', $version ) ) { + return ''; + } + return $version; }