CVE-2023-52137 (published 2023-12-29T17:16Z)

The [`tj-actions/verify-changed-files`](https://github.com/tj-actions/verify-changed-files) action allows for command injection in changed filenames, allowing an attacker to execute arbitrary code and potentially leak secrets. The [`verify-changed-files`](https://github.com/tj-actions/verify-changed-files) workflow returns the list of files changed within a workflow execution. This could potentially allow filenames that contain special characters such as `;` which can be used by an attacker to take over the [GitHub Runner](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners) if the output value is used in a raw fashion (thus being directly replaced before execution) inside a `run` block. By running custom commands, an attacker may be able to steal secrets such as `GITHUB_TOKEN` if triggered on other events than `pull_request`.\n\nThis has been patched in versions [17](https://github.com/tj-actions/verify-changed-files/releases/tag/v17) and [17.0.0](https://github.com/tj-actions/verify-changed-files/releases/tag/v17.0.0) by enabling `safe_output` by default and returning filename paths escaping special characters for bash environments.

CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-77
cwe_name: Improper Neutralization of Special Elements used in a Command ('Command Injection')
description: The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.
extended_description: 
            Many protocols and products have their own custom command language. While OS or shell command strings are frequently discovered and targeted, developers may not realize that these other command languages might also be vulnerable to attacks.
         
         

Fix information:
repo_url: https://github.com/tj-actions/verify-changed-files
commit_details: repo_url: https://github.com/tj-actions/verify-changed-files
author: Jorge
author_date: 2023-12-28 14:39:29+01:00
msg: Merge pull request from GHSA-ghm2-rq8q-wrhc

* feat: add `safe_output` input enabled by default

* fix: migrate README to safe uses of interpolation

* fix: also sanitize `)`

* fix: remove sanitization of `'`

* fix: also sanitize `|`

* fix: also sanitize `&`

* fix: also sanitize `;`
repository: repo_url: https://github.com/tj-actions/verify-changed-files
repo_name: tj-actions/verify-changed-files
description: :octocat: Github action to verify file changes that occur during the workflow execution.
repo_language: Shell
owner: tj-actions
file_changes: filename: README.md
change_type: ModificationType.MODIFY
diff: @@ -60,6 +60,7 @@ Verify that certain files or directories did or did not change during the workfl
         uses: tj-actions/verify-changed-files@v16
         id: verify-changed-files
         with:
+          safe_output: false # true by default, set to false because we are using an environment variable to store the output and avoid command injection.
           files: |
              *.txt
              test_directory
@@ -69,8 +70,11 @@ Verify that certain files or directories did or did not change during the workfl
 
       - name: Run step only when any of the above files change.
         if: steps.verify-changed-files.outputs.files_changed == 'true'
+        env:
+          FILES_CHANGED: |-
+            ${{ steps.verify-changed-files.outputs.changed_files }}
         run: |
-          echo "Changed files: ${{ steps.verify-changed-files.outputs.changed_files }}"
+          echo "Changed files: $FILES_CHANGED"
         # Outputs: "Changed files: new.txt test_directory/new.txt"
 ```
 
@@ -82,6 +86,7 @@ Verify that certain files or directories did or did not change during the workfl
         uses: tj-actions/verify-changed-files@v16
         id: verify-changed-files
         with:
+          safe_output: false
           files: |
              new.txt
              test_directory
@@ -99,10 +104,15 @@ Verify that certain files or directories did or did not change during the workfl
       - name: Verify Changed files
         uses: tj-actions/verify-changed-files@v16
         id: verify-changed-files
+        with:
+          safe_output: false
       
       - name: List all changed files tracked and untracked files
+        env:
+          FILES_CHANGED: |-
+            ${{ steps.verify-changed-files.outputs.changed_files }}
         run: |
-          echo "Changed files: ${{ steps.verify-changed-files.outputs.changed_files }}"
+          echo "Changed files: $FILES_CHANGED"
 ```
 
 If you feel generous and want to show some extra appreciation:

programming_language: Markdown
filename: action.yml
change_type: ModificationType.MODIFY
diff: @@ -25,6 +25,10 @@ inputs:
     description: 'Message to display when files have changed and the `fail-if-changed` input is set to `true`.'
     default: "Files have changed."
     required: false
+  safe_output:
+    description: "Apply sanitization to output filenames before being set as output."
+    required: false
+    default: "true"
 
 outputs:
   files_changed:
@@ -61,6 +65,7 @@ runs:
         INPUT_MATCH_GITIGNORE_FILES: ${{ inputs.match-gitignore-files }}
         INPUT_FAIL_IF_CHANGED: ${{ inputs.fail-if-changed }}
         INPUT_FAIL_MSG: ${{ inputs.fail-message }}
+        INPUT_SAFE_OUTPUT: ${{ inputs.safe_output }}
 
 branding:
   icon: file-text

programming_language: CoffeeScript
filename: entrypoint.sh
change_type: ModificationType.MODIFY
diff: @@ -66,6 +66,16 @@ if [[ -n "$CHANGED_FILES" ]]; then
 
   CHANGED_FILES=$(echo "$CHANGED_FILES" | awk '{gsub(/\|/,"\n"); print $0;}' | awk -v d="$INPUT_SEPARATOR" '{s=(NR==1?s:s d)$0}END{print s}')
 
+  if [[ "$INPUT_SAFE_OUTPUT" == "true" ]]; then
+    CHANGED_FILES=${CHANGED_FILES//$/\\$} # Replace $ with \$
+    CHANGED_FILES=${CHANGED_FILES//\(/\\\(}} # Replace ( with \(
+    CHANGED_FILES=${CHANGED_FILES//\)/\\\)}} # Replace ) with \)
+    CHANGED_FILES=${CHANGED_FILES//\`/\\\`} # Replace ` with \`
+    CHANGED_FILES=${CHANGED_FILES//|/\\|} # Replace | with \|
+    CHANGED_FILES=${CHANGED_FILES//&/\\&} # Replace & with \&
+    CHANGED_FILES=${CHANGED_FILES//;/\\;} # Replace ; with \;
+  fi
+
   echo "files_changed=true" >> "$GITHUB_OUTPUT"
   echo "changed_files=$CHANGED_FILES" >> "$GITHUB_OUTPUT"
 

programming_language: Shell
repo_url: https://github.com/tj-actions/verify-changed-files
commit_details: repo_url: https://github.com/tj-actions/verify-changed-files
author: Tonye Jack
author_date: 2023-12-28 19:31:51-07:00
msg: chore: update entrypoint.sh (#357)
repository: repo_url: https://github.com/tj-actions/verify-changed-files
repo_name: tj-actions/verify-changed-files
description: :octocat: Github action to verify file changes that occur during the workflow execution.
repo_language: Shell
owner: tj-actions
file_changes: filename: entrypoint.sh
change_type: ModificationType.MODIFY
diff: @@ -39,17 +39,26 @@ fi
 
 CHANGED_FILES=""
 
-# Function to concatenate non-empty strings with a separator
+# Function to concatenate non-empty file names with a separator
 concatenate() {
   local separator=$1
   shift
   local result=""
-  for str in "$@"; do
-    if [[ -n $str ]]; then
+  for filename in "$@"; do
+    if [[ "$INPUT_SAFE_OUTPUT" == "true" ]]; then
+      filename=${filename//$/\\$} # Replace $ with \$
+      filename=${filename//\(/\\\(} # Replace ( with \(
+      filename=${filename//\)/\\\)} # Replace ) with \)
+      filename=${filename//\`/\\\`} # Replace ` with \`
+      filename=${filename//|/\\|} # Replace | with \|
+      filename=${filename//&/\\&} # Replace & with \&
+      filename=${filename//;/\\;} # Replace ; with \;
+    fi
+    if [[ -n $filename ]]; then
       if [[ -n $result ]]; then
-        result+="$separator$str"
+        result+="$separator$filename"
       else
-        result="$str"
+        result="$filename"
       fi
     fi
   done
@@ -62,20 +71,10 @@ CHANGED_FILES=$(concatenate "|" "$TRACKED_FILES" "$UNTRACKED_OR_IGNORED_FILES" "
 CHANGED_FILES=$(echo "$CHANGED_FILES"  | awk '{gsub(/\|/,"\n"); print $0;}' | sort -u | awk -v d="|" '{s=(NR==1?s:s d)$0}END{print s}')
 
 if [[ -n "$CHANGED_FILES" ]]; then
-  echo "Found uncommited changes"
+  echo "Found uncommitted changes"
 
   CHANGED_FILES=$(echo "$CHANGED_FILES" | awk '{gsub(/\|/,"\n"); print $0;}' | awk -v d="$INPUT_SEPARATOR" '{s=(NR==1?s:s d)$0}END{print s}')
 
-  if [[ "$INPUT_SAFE_OUTPUT" == "true" ]]; then
-    CHANGED_FILES=${CHANGED_FILES//$/\\$} # Replace $ with \$
-    CHANGED_FILES=${CHANGED_FILES//\(/\\\(}} # Replace ( with \(
-    CHANGED_FILES=${CHANGED_FILES//\)/\\\)}} # Replace ) with \)
-    CHANGED_FILES=${CHANGED_FILES//\`/\\\`} # Replace ` with \`
-    CHANGED_FILES=${CHANGED_FILES//|/\\|} # Replace | with \|
-    CHANGED_FILES=${CHANGED_FILES//&/\\&} # Replace & with \&
-    CHANGED_FILES=${CHANGED_FILES//;/\\;} # Replace ; with \;
-  fi
-
   echo "files_changed=true" >> "$GITHUB_OUTPUT"
   echo "changed_files=$CHANGED_FILES" >> "$GITHUB_OUTPUT"
 
@@ -85,7 +84,6 @@ if [[ -n "$CHANGED_FILES" ]]; then
     fi
     exit 1
   fi
-
 else
   echo "No changes found."
   echo "files_changed=false" >> "$GITHUB_OUTPUT"

programming_language: Shell