ShellScriptInjection
¶
Shell script vulnerable to script injection.
Defined by ScriptInjectionRule
which supports workflows, actions in the "Default" ruleset along with JSScriptInjection
.
Description¶
Using ${{ }}
in shell scripts is vulnerable to script injection.
Script injection is when a user can control part of the script, and can inject arbitrary code.
In most cases this is a security vulnerability, but at the very least it's a bug.
All user input must be sanitized before being executed as shell script.
The simplest way to achieve this is using environment variables to pass data as inputs to run:
scripts.
Shells know how to handle them: ${XXX}
.
With environment variables data travels in memory, rather than becoming part of the executable code.
References:
Compliant examples¶
Compliant example #1¶
Capturing the input in an environment variable prevents shell injection.
The output is as expected:
Warning: Quotation mark (") needs a pair.
example.yml
on: push jobs: example: runs-on: ubuntu-latest steps: - name: "Produce some output" id: producer run: | echo 'result=Warning: Quotation mark (") needs a pair.' >> "${GITHUB_OUTPUT}" - name: "Consume some output" env: RESULT: ${{ steps.producer.outputs.result }} run: echo "${RESULT}"
Compliant example #2¶
A note on GitHub variables vs contexts.
There are a few examples where using ${{ github.* }}
would result in an unsafe script,
for example:
- run: cp "${{ github.workspace }}" "${{ runner.temp }}"
Instead of introducing an env:
section like this:
- env:
WS: ${{ github.workspace }}
RT: ${{ runner.temp }}
run: cp -r "${WS}" "${RT}"
${GITHUB_*}
and ${RUNNER_*}
environment variables as shown in the example.
Compare:
An exception to this might be when there's a project-specific path that is appended and used multiple times:
- env:
WS: ${{ github.workspace }}/some/thing
RT: ${{ runner.temp }}/other/place
run: |
unzip "${WS}/foo.zip" -d "${RT}"
rm -rf "${WS}"
mv "${RT}" "${WS}"
example.yml
on: push jobs: example: runs-on: ubuntu-latest steps: - run: cp -r "${GITHUB_WORKSPACE}" "${RUNNER_TEMP}"
Non-compliant examples¶
Non-compliant example #1¶
Directly using the output in the shell script is vulnerable to script injection. Depending on the actual contents of the result
- in the best case, the script fails,
- in normal cases, the output is just wrong,
- in the worst case, this could lead to arbitrary code execution.
In this example, the output is:
/home/runner/work/_temp/d3ddaaab-5e34-4eb3-be73-4e830012fe4e.sh: line 1: syntax error near unexpected token `)'
Error: Process completed with exit code 2.
${{ ... }}
expression, the script becomes:
echo "Warning: Quotation mark (") needs a pair."
echo "Warning: Quotation mark ("
) needs a pair."
.
example.yml
on: push jobs: example: runs-on: ubuntu-latest steps: - name: "Produce some output" id: producer run: | echo 'result=Warning: Quotation mark (") needs a pair.' >> "${GITHUB_OUTPUT}" - name: "Consume some output directly" run: echo "${{ steps.producer.outputs.result }}"
- Line 11: Step["Consume some output directly"] in Job[example] shell script contains GitHub Expressions.
Non-compliant example #2¶
Directly using the output in the shell script is vulnerable to script injection.
In this example, the output is:
{add:one,remove:[two,three]}
{"add":"one","remove":["two","three"]}
${{ ... }}
expression, the script becomes:
echo "{"add":"one","remove":["two","three"]}"
echo "{" add ":" one "," remove ":[" two "," three "]}"
example.yml
on: push jobs: example: runs-on: ubuntu-latest steps: - name: "Produce some output" id: producer run: | echo 'result={"add":"one","remove":["two","three"]}' >> "${GITHUB_OUTPUT}" - name: "Consume some output directly" run: echo "${{ steps.producer.outputs.result }}"
- Line 11: Step["Consume some output directly"] in Job[example] shell script contains GitHub Expressions.