Nouvelle réglementation de cybersécurité 2026 en vigueur au Maroc. Obtenir un audit de conformité gratuit →
← Retour au blog
OWASP 2025-10-23 ⏱️ 11 min

Les URL présignées ne sont pas une autorisation : contrôle d'accès objet dans le stockage cloud

Les URL présignées ne sont pas une autorisation : contrôle d'accès objet dans le stockage cloud

Les URL présignées sont l'une des primitives les plus utiles du cloud. Elles permettent à un backend de remettre au navigateur un lien signé et de courte durée pour téléverser ou télécharger un gros fichier sans faire transiter des gigaoctets par le serveur applicatif. C'est aussi l'un des endroits les plus faciles à introduire un IDOR. Une signature prouve que le lien a été émis par une entité disposant d'identifiants ; elle ne prouve pas que la personne qui détient le lien est autorisée à accéder à cet objet. Confondre ces deux affirmations a produit de véritables fuites de données dans AWS S3, Azure Blob Storage et Google Cloud Storage.


1. Ce que prouve réellement une URL présignée

Une URL présignée AWS Signature Version 4 contient l'identifiant de clé, la portée des identifiants, les en-têtes signés, l'expiration et une signature HMAC. Le service de stockage recalcule la signature et vérifie que la requête correspond. Si la signature est valide et la clé autorisée, la requête réussit. L'URL est donc une capacité au porteur : quiconque la possède peut l'utiliser jusqu'à expiration.

Cette propriété est acceptable lorsque l'URL est générée pour l'objet de l'utilisateur authentifié. Elle devient une vulnérabilité lorsque la clé d'objet est choisie par le client :

GET /api/download?key=invoices/2025/INV-1042.pdf HTTP/1.1
Host: app.example.com
Cookie: session=...

Si le backend signe n'importe quelle clé reçue, l'utilisateur peut substituer la clé d'un autre locataire et obtenir un lien valide. La signature est authentique. La décision d'autorisation n'a jamais été prise.


2. Trois schémas d'échec courants

2.1 Clés d'objet contrôlées par le client

La faille la plus directe est un endpoint de téléchargement ou de téléversement qui accepte une clé complète et la signe sans vérifier la propriété. L'énumération est triviale car les clés suivent souvent des schémas prévisibles : tenant/TENANT_ID/..., users/{uuid}/avatar.png ou invoices/2025/INV-{n}.pdf. Le testeur demande son propre objet, puis remplace l'identifiant de locataire, l'UUID utilisateur ou le numéro de facture en conservant la même session.

2.2 Identifiants de signature trop larges

Même lorsque la clé est dérivée côté serveur, l'identité de signature peut disposer de s3:GetObject sur *. Une URL générée pour un objet peut alors être rejouée avec un chemin modifié si la signature ne couvre pas le chemin — ce qui est toujours le cas en SigV4, mais pas nécessairement pour des schémas maison ou des proxys mal configurés. Plus souvent, le backend signe un préfixe au lieu d'un objet, ou renvoie au client des identifiants STS limités à tout un bucket.

2.3 Expiration longue et liens réutilisables

Les URL SigV4 peuvent être valides jusqu'à sept jours (604 800 secondes). Un lien collé dans un ticket, un chat ou l'historique du navigateur reste utilisable pendant toute cette fenêtre. Si l'objet contient des données personnelles, l'exposition survit à la réponse à incident. Les jetons SAS Azure et les URL signées Google Cloud ont des durées configurables similaires ; la sécurité dépend entièrement du choix d'une durée courte.


3. Détails propres à chaque fournisseur

  • AWS S3 : l'authentification SigV4 en query utilise X-Amz-Algorithm, X-Amz-Credential, X-Amz-Date, X-Amz-Expires, X-Amz-SignedHeaders et X-Amz-Signature. L'expiration maximale est de sept jours. Les URL héritent des permissions du principal signataire, y compris des politiques de session des identifiants temporaires.
  • Azure Blob Storage : un SAS de service ou de délégation utilisateur encode les permissions (sp), le début et l'expiration (st, se), la ressource (sr) et la signature (sig). Le SAS de délégation utilisateur est préférable car il est lié à une identité Entra ID et peut être révoqué en faisant tourner la clé de délégation.
  • Google Cloud Storage : les URL signées V4 incluent X-Goog-Algorithm, X-Goog-Credential, X-Goog-Date, X-Goog-Expires, X-Goog-SignedHeaders et X-Goog-Signature. La signature doit être effectuée par un compte de service disposant uniquement de la permission objet requise.

Dans tous les cas, la signature couvre la méthode HTTP, l'hôte, le chemin et certains en-têtes. Modifier le chemin invalide la signature. C'est précisément pourquoi la vulnérabilité vit dans l'application : c'est elle qui décide quel chemin signer.


4. Un schéma serveur sûr

Le backend doit résoudre l'objet à partir d'une décision d'autorisation, jamais d'une entrée brute. L'exemple Node.js suivant dérive la clé de la session authentifiée et d'une requête en base :

// Express + AWS SDK v3
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3 = new S3Client({ region: process.env.AWS_REGION });

app.get('/api/invoices/:invoiceId/download', requireAuth, async (req, res) => {
    const invoice = await db.invoices.findOne({
        where: { id: req.params.invoiceId, tenantId: req.user.tenantId }
    });

    if (!invoice) {
        return res.status(404).json({ error: 'Not found' });
    }

    const command = new GetObjectCommand({
        Bucket: process.env.INVOICE_BUCKET,
        Key: invoice.storageKey // valeur contrôlée par le serveur, issue de la base
    });

    const url = await getSignedUrl(s3, command, { expiresIn: 300 });
    return res.json({ url });
});

Trois propriétés : la clé vient de la base, la requête inclut l'identifiant de locataire, et l'URL expire en cinq minutes. Un utilisateur qui change invoiceId reçoit un 404 car le filtre de propriété échoue avant toute signature.

Les téléversements exigent la même discipline

Pour les uploads, ne laissez pas le client choisir une clé arbitraire. Générez une clé côté serveur, stockez le propriétaire et le type de contenu prévus, et signez une requête PutObject de courte durée. Si l'application accepte un nom de fichier client, normalisez-le, supprimez les séparateurs de chemin et préfixez-le par un identifiant généré par le serveur.


5. Méthodologie de test

  1. Cartographiez chaque endpoint de signature. Cherchez dans le bundle frontend et la documentation presign, signed-url, download, upload, attachment et export.
  2. Capturez une requête légitime. Notez les paramètres exacts, l'URL retournée et l'expiration.
  3. Modifiez un identifiant à la fois. Changez les identifiants de locataire, les UUID, les noms d'objet et les segments de chemin. Gardez le cookie de session inchangé.
  4. Vérifiez la réponse, pas seulement le code HTTP. Un 200 avec une URL signée pour un autre objet est une finding même si l'objet est ensuite inaccessible. Décodez l'URL et comparez le chemin.
  5. Testez l'expiration. Attendez au-delà de la date annoncée et réessayez. Si le lien fonctionne encore, le backend n'applique pas le paramètre.
  6. Testez la confusion de méthode. Essayez une URL signée GET avec PUT ou DELETE. SigV4 doit la rejeter ; les implémentations maison parfois non.
# Inspecter les paramètres d'une URL présignée capturée
python3 - <<'PY'
from urllib.parse import urlparse, parse_qs
u = urlparse('https://bucket.s3.amazonaws.com/invoices/1042.pdf?X-Amz-Algorithm=...')
for k, v in parse_qs(u.query).items():
    print(k, '=', v[0])
print('path =', u.path)
PY

6. Détection et durcissement

  • Autorisez avant de signer. La fonction de signature doit accepter une référence interne, pas une chaîne client. Résolvez la propriété dans la même transaction que la clé.
  • Limitez les identifiants. Le signataire ne doit avoir s3:GetObject que sur le préfixe requis, jamais *. Utilisez des rôles distincts pour les uploads et les téléchargements.
  • Gardez des expirations courtes. Cinq à quinze minutes suffisent généralement. Réservez les durées longues aux traitements hors ligne documentés.
  • Liez les liens à un utilisateur ou une session quand c'est possible. Les URL signées natives ne le permettent pas seules ; placez un jeton applicatif de courte durée devant l'objet, ou diffusez le fichier via un endpoint authentifié pour les données très sensibles.
  • Journalisez l'émission et l'usage. Enregistrez qui a demandé quel objet, quand l'URL a été émise et si l'accès au stockage a réussi. Alertez lorsqu'une identité demande de nombreux locataires distincts en peu de temps.
  • Préférez la délégation utilisateur aux clés de compte. Sur Azure, le SAS de délégation utilisateur évite d'embarquer une clé de compte dans la configuration. Sur AWS, utilisez des identifiants STS temporaires plutôt que des clés IAM de longue durée.

7. Conclusion

Une URL présignée est une délégation d'une permission de stockage étroite, pas un substitut à un contrôle d'autorisation. La signature répond à « ce lien a-t-il été créé par un principal de confiance ? ». Elle ne répond pas à « ce principal devrait-il lire cet objet ? ». Les applications qui confondent les deux produisent des IDOR invisibles pour la validation de signature et souvent manqués par les scanners. Le correctif est architectural : dériver les clés de registres de propriété côté serveur, limiter les identifiants au préfixe minimal et garder une durée assez courte pour qu'un lien divulgué reste un incident bref plutôt qu'une fuite permanente.

Besoin d'un audit de sécurité ?

Contactez Cayvora pour une consultation gratuite et protégez votre entreprise contre les cybermenaces.

✉️ Contact via Email

Articles connexes