Fix potential OS command injection in jekyll-hook.js - #43
Open
DongZifan wants to merge 2 commits into
Open
Conversation
Add validation for repository, branch, and owner names to prevent invalid characters in webhook data.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Command Injection Reproduction Notes for
jekyll-hook.jsSummary
Hello,
I am writing to report a potential OS Command Injection vulnerability in the following file:
jekyll-hook/jekyll-hook.jsThe issue occurs when user-controlled input from a GitHub Webhook POST request is passed directly as arguments to
child_process.spawn()without any sanitization. The application extracts the repository name (data.repository.name), branch name (fromdata.ref), and owner name (data.repository.owner.name) from the webhook request body, pushes them into aparamsarray, and passes them to therun()function which callsspawn(file, params).On Windows, when
fileis a batch script (e.g.,build.bat), Node.js implicitly invokescmd.exeto execute it. Sincecmd.exeinterprets shell metacharacters (such as&,|,") within arguments, an attacker who controls the repository name or other webhook fields can inject arbitrary commands that will be executed on the host system. This could potentially allow Remote Code Execution (RCE) on the server processing the webhook.Root Cause
The issue is caused by unsanitized external input reaching
child_process.spawn()in the following function:The
paramsarray is populated directly from the webhook request body without any input validation:Reproduction Material
A minimal reproduction script is provided in:
poc_jekyll_hook.jspoc_jekyll_hook.js
This Proof of Concept (PoC) is intended to demonstrate that external input can reach dangerous command execution logic through the vulnerable code path.
What the PoC Does
The PoC performs a minimal end-to-end trigger of the vulnerable code path:
child_process.spawn()to intercept and inspect the arguments passed to the execution sink.config.json,express,queue-async,emailjs) via aModule._loadhook so that no real server or external service is needed.jekyll-hook.jsdirectly viarequire(), which starts the mocked HTTP server.POSTrequest to/hooks/jekyll//masterwith:X-Hub-Signatureheader (computed with the mocked secret)repository.namevalue containing shell metacharacters:my_repo" & calc #data.repo = 'my_repo" & calc #'from the request body.paramsarray and passed tospawn(file, params)in therun()function.spawn()detects the& calcsubstring in the arguments, confirming the injection.In the provided example, the injected payload is crafted so that, on Windows, successful command execution opens the Calculator application. This serves as a visible indicator that external input can reach the OS command execution sink without proper sanitization.
Example Payload
The PoC uses the following payload for
repository.namein the Webhook JSON body:{ "ref": "refs/heads/master", "repository": { "name": "my_repo\" & calc #", "owner": { "name": "attacker_user" } }, "pusher": { "email": "test@example.com" } }How to Run
Run from the 939 directory:
Expected Output
When the PoC runs, you should see output similar to:
In the vulnerable version, after the crafted request is processed, the local machine will launch the Calculator application as a benign demonstration effect.
This shows that the attacker-controlled
repository.namevalue can influence command execution behavior through the vulnerablerun()path.Patch Explanation
This branch also includes a patched version of
jekyll-hook.js(new_jekyll-hook.js) intended to mitigate the command injection risk described above.What the patch changes
The patch adds strict validation before webhook data values are used by the processing logic.
In the vulnerable version, user-controlled values such as:
data.repository.namedata.ref(used to derive the branch name)data.repository.owner.namecould reach
child_process.spawn(...)through theparamsarray without any validation. Additionally, the HMAC-SHA1 signature verification could be silently bypassed by omitting the secret from the configuration — the original code simply returned early instead of rejecting the request.The patched version introduces input checks before these values are passed into command execution logic, and makes HMAC signature verification mandatory.
Validation introduced by the patch
The patched version ensures that all external inputs are verified against a strict whitelist before they are used:
repo,branch,owner) must match the regular expression/^[a-zA-Z0-9._-]+$/.-), underscores (_), and dots (.) are permitted.&,|,;,$,>,<) or double quotes is rejected immediately, and a security error is logged.config.secretis configured, the server now throws a 500 error instead of silently skipping signature verification. Requests missing theX-Hub-Signatureheader or containing an improperly formatted signature are rejected with a 403 status.By performing these checks at the application layer, the patch effectively prevents command injection through shell metacharacter interpretation when
spawn()interacts withcmd.exeon Windows.