Prévenir les SSRF dans Strapi 5 : configuration et codage sécurisés
Découvrez comment empêcher le Server‑Side Request Forgery dans Strapi 5 en utilisant les paramètres de proxy, la liste blanche d’URL, les en‑têtes de sécurité, la limitation du débit et la surveillance des journaux d’audit.
Par l’équipe Revisionary · · 4 min de lecture
Strapi 5 peut être vulnérable aux Server‑Side Request Forgery (SSRF) lorsque les développeurs laissent le CMS récupérer des URL arbitraires — par exemple dans les webhooks ou les appels personnalisés strapi.fetch. La protection la plus simple consiste à bloquer toute requête qui ne pointe pas vers un hôte approuvé et à appliquer cette règle à la fois dans la configuration et dans le code. Ce guide montre comment exploiter les paramètres de proxy intégrés de Strapi, le middleware de sécurité, la limitation du débit et les fonctionnalités de journal d’audit, ainsi que des modèles de codage défensif, pour éliminer le risque SSRF.
1. Comprendre la surface d’exposition SSRF dans Strapi
Strapi utilise un helper interne strapi.fetch pour les appels HTTP sortants. Ces appels sont utilisés par :
- La livraison de webhooks (URL définies par l’administrateur)
- Les vérifications de licence et de télémétrie
- Les plugins personnalisés qui interrogent des services externes
Si un attaquant peut fournir une URL qui résout en une adresse interne—localhost, 127.0.0.1 ou toute IP privée—il peut atteindre des services internes, des points de terminaison de métadonnées ou le socket Docker. L’avis de sécurité Strapi GHSA‑v8wj‑f5c7‑pvxf (pré‑4.25.2) a mis en avant ce problème, et le même chemin de code existe dans Strapi 5.
2. Configurer un proxy strict pour les requêtes sortantes
La configuration serveur de Strapi (/config/server.js) inclut un objet proxy qui vous permet de fournir un agent HTTP/HTTPS personnalisé pour toutes les appels strapi.fetch.
// /config/server.js
module.exports = ({ env }) => ({
// …autres paramètres du serveur
proxy: {
// Activer la gestion du proxy – laissez true sauf si vous désactivez intentionnellement
koa: env.bool('IS_PROXIED', true),
// Optionnel : définir l’en‑tête contenant l’IP client d’origine
ipHeader: env('PROXY_IP_HEADER', 'X-Forwarded-For'),
maxIpsCount: env.int('MAX_IPS_COUNT', 1),
// --- Début de la protection SSRF ---
fetch: {
// Utiliser un agent vérifiant la liste blanche (voir le code ci‑dessous)
request: (url, options) => {
const { URL } = require('url');
const allowedHosts = new Set([
'api.trustedpartner.com',
'hooks.mycompany.com',
]);
const u = new URL(url);
if (u.protocol !== 'https:') {
throw new Error('Only HTTPS destinations are allowed');
}
if (!allowedHosts.has(u.host)) {
throw new Error(`Host ${u.host} is not whitelisted`);
}
// Transmettre à l’implémentation réelle de fetch
return fetch(url, options);
},
},
// Optionnel : router tout le HTTP simple via un proxy d’entreprise
http: env('HTTP_PROXY_URL'),
https: env('HTTPS_PROXY_URL'),
},
});La propriété fetch surcharge le gestionnaire de requête par défaut. Elle valide l’URL avant l’envoi, rejetant tout schéma non‑HTTPS et tout hôte absent de la liste blanche. Ce point de contrôle unique protège toutes les utilisations intégrées et personnalisées de strapi.fetch.
2.1 Utiliser un agent keep‑alive tiers (optionnel)
Si vous avez besoin de réutiliser les connexions, encapsulez la logique de liste blanche autour d’un agent keep‑alive :
const { HttpsAgent } = require('agentkeepalive');
const keepAliveAgent = new HttpsAgent({ keepAlive: true });
fetch: {
request: (url, options) => {
// Validation de l’URL comme ci‑dessus …
return fetch(url, { ...options, httpsAgent: keepAliveAgent });
},
},3. Renforcer l’interface des webhooks
Les webhooks sont définis par les administrateurs, mais un compte administrateur compromis pourrait injecter une URL malveillante. Appliquez également une validation côté UI :
// Exemple de validation dans un plugin webhook personnalisé
module.exports = ({ strapi }) => ({
async createWebhook(ctx) {
const { url } = ctx.request.body;
const { URL } = require('url');
const allowedHosts = ['hooks.mycompany.com'];
const u = new URL(url);
if (u.protocol !== 'https:' || !allowedHosts.includes(u.host)) {
return ctx.badRequest('Invalid webhook URL');
}
// Continuer avec la création
return strapi.plugin('webhooks').service('webhook').create(ctx.request.body);
},
});4. Ajouter des en‑têtes Content‑Security‑Policy (CSP)
Même si CSP protège principalement les navigateurs, configurer une directive stricte connect-src réduit la probabilité que du code front‑end malveillant déclenche des requêtes similaires à du SSRF depuis le client.
// /config/middlewares.js
module.exports = [
'strapi::errors',
'strapi::security',
// autres middlewares …
{
name: 'strapi::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
"connect-src": ["'self'", "https:"],
// Conserver les autres valeurs par défaut (img-src, script-src, etc.)
},
},
},
},
];Le drapeau useDefaults: true garantit que le CSP de base de Strapi ('self') est conservé, tandis que le connect-src personnalisé n’autorise que les origines HTTPS.
5. Limiter le débit des points d’accès acceptant des URL
Si vous exposez une API qui permet aux utilisateurs de soumettre une URL (par ex. un point d’accès « preview fetch »), ajoutez une limitation du débit pour empêcher le balayage en force d’adresses internes.
// /config/plugins.js
module.exports = ({ env }) => ({
'users-permissions': {
ratelimit: {
enabled: true,
interval: 60000, // 1 minute
max: 10, // 10 requêtes par minute par IP
},
},
});Pour un contrôle plus granulaire, vous pouvez ajouter un middleware global Koa de limitation du débit :
// /config/middlewares.js (after existing middlewares)
const RateLimit = require('koa2-ratelimit').RateLimit;
module.exports = [
// …middlewares précédents
{
name: 'global::rateLimit',
config: {
interval: { min: 5 }, // fenêtre de 5 minutes
max: 100, // 100 requêtes par fenêtre par IP
headers: true,
},
},
];6. Surveiller avec les journaux d’audit
La fonctionnalité d’audit‑log de Strapi enregistre chaque création de webhook, génération de token et authentification admin. Activez‑la et conservez les logs au moins 30 jours pour détecter les tentatives SSRF suspectes.
// /config/admin.js
module.exports = ({ env }) => ({
auditLogs: {
enabled: true,
retentionDays: 30,
// Optionnel : limiter la taille de l’export pour éviter un DoS lors de l’extraction des journaux
exportMaxRows: 500000,
},
});Lorsque qu’une requête bloquée survient (la liste blanche lève une exception), vous pouvez consigner un événement personnalisé dans le même hook de cycle de vie :
strapi.log.warn('Blocked SSRF attempt', { url, user: ctx.state.user?.id });
await strapi.services['admin::audit-log'].create({
action: 'ssrf.blocked',
user: ctx.state.user?.id,
payload: { url },
});Ces entrées apparaissent dans l’interface d’audit‑log et peuvent être exportées pour les audits de conformité.
7. Faire tourner régulièrement les secrets
Même si le SSRF est un problème au niveau réseau, garder JWT_SECRET, APP_KEYS et les clés d’API utilisées pour les appels sortants à jour réduit l’impact d’un serveur compromis.
- Stockez les secrets dans des variables d’environnement, pas dans le code.
- Faites‑les tourner au moins chaque trimestre.
- Après rotation, invalidez les sessions existantes via l’interface admin ou en vidant le store de session.
8. Hygiène de la chaîne d’approvisionnement
Les exploits SSRF combinent souvent une dépendance vulnérable avec une requête sortante ouverte. Maintenez votre installation Strapi à jour et scannez les vulnérabilités connues :
- Activez Dependabot ou les alertes de sécurité GitHub sur le dépôt.
- Exécutez
npm audit --audit-level=highdans le CI. - Abonnez‑vous au flux d’avis de sécurité Strapi (par ex. GHSA‑v8wj‑f5c7‑pvxf).
9. Checklist rapide
| ✅ | Action |
|---|---|
| 1 | Définir une liste blanche statique des hôtes autorisés dans /config/server.js. |
| 2 | Imposer le schéma HTTPS pour toutes les URL sortantes. |
| 3 | Ajouter une validation d’URL dans chaque point d’accès qui accepte des URL fournies par l’utilisateur. |
| 4 | Configurer le CSP de strapi::security avec un connect-src restrictif. |
| 5 | Activer la limitation du débit sur les routes acceptant des URL. |
| 6 | Activer les journaux d’audit et surveiller les événements ssrf.blocked. |
| 7 | Faire tourner régulièrement les JWT et APP_KEYS. |
| 8 | Tenir Strapi et toutes les dépendances npm à jour. |
10. Quand faire appel à Revisionary
Si vous utilisez déjà le plugin Revisionary pour le versionnage du contenu, vous pouvez exploiter son intégration aux journaux d’audit pour conserver un enregistrement permanent de chaque tentative SSRF bloquée. L’export du journal d’audit du plugin facilite la production de preuves d’actions défensives lors d’audits GDPR ou de conformité interne.
En appliquant la configuration de proxy intégrée, les en‑têtes CSP, la limitation du débit et la surveillance des journaux d’audit, les développeurs peuvent éliminer la surface d’attaque SSRF dans Strapi 5 sans ajouter de bibliothèques externes. L’approche est déclarative, fonctionne pour tous les appels fetch natifs et personnalisés, et s’aligne sur les recommandations OWASP de prévention des SSRF.
Lectures complémentaires
- OWASP Server‑Side Request Forgery Prevention Cheat Sheet
- Documentation de configuration serveur Strapi
- Guide du middleware de sécurité Strapi
- Page fonctionnalité audit‑log Strapi
- Article de blog Strapi sur les bonnes pratiques de limitation du débit
Sources
L’équipe Revisionary
Les développeurs de Revisionary, un plugin d’historique des versions pour Strapi 5. Nous écrivons sur Strapi, le développement web, l’IA et la sécurité des applications.
Cet article a été rédigé avec l'aide d'une IA.