3 points par GN⁺ 2024-11-25 | 1 commentaires | Partager sur WhatsApp
  • Daniel Mangum montre qu’en exploitant l’upload de blobs et la structure de référence des records de l’AT Protocol, il est possible de servir un fichier text/html téléversé sur un PDS Bluesky comme une page web qui s’ouvre dans le navigateur
  • Le mécanisme clé est que le blob téléversé n’est pas rendu public immédiatement : il faut qu’un record référence le blob pour que le PDS le fasse passer dans un état accessible publiquement
  • Les embeds d’image Bluesky classiques n’acceptent que les types MIME image/*, mais un record avec un lexicon inconnu ou un embed personnalisé fondé sur une open union peut stocker une référence vers un blob text/html
  • getBlob est accessible sans authentification, et les requêtes vers bsky.social sont redirigées vers le vrai PDS, porcini.us-east.host.bsky.network, qui renvoie alors le HTML
  • Bluesky applique des en-têtes CSP et nosniff à getBlob, et limite par défaut les uploads de blobs à 5 Mo, mais le service d’hébergement PDS conserve la charge d’un hébergement de fichiers arbitraires ainsi que les coûts de stockage et de transfert

Comment publier un site web avec un blob AT Protocol

  • Ce cas repose moins sur les fonctionnalités classiques de l’app Bluesky que sur le fonctionnement de l’AT Protocol et de l’API PDS
  • Bluesky fournit un PDS entryway qui accède au PDS de l’utilisateur et expose plusieurs instances PDS sous le domaine bsky.social
  • Il est aussi possible d’accéder directement à chaque instance PDS, et le site d’exemple s’ouvre via l’endpoint com.atproto.sync.getBlob de porcini.us-east.host.bsky.network
  • L’auteur a échangé avec l’équipe Bluesky avant publication ; ce comportement n’est pas un usage applicatif prévu, mais il est connu et ne relève pas d’une procédure de divulgation de vulnérabilité

Pourquoi records et blobs sont séparés

  • Dans l’AT Protocol, les données applicatives se divisent en deux grandes catégories : les records et les blobs
    • les records sont les entités principales créées par l’utilisateur, avec leur structure et leurs métadonnées
    • les blobs sont des données non structurées plus volumineuses, comme des images, et ne sont généralement exposés que lorsqu’un record les référence
  • Quand on crée un post avec image dans Bluesky, cela semble être une seule action côté utilisateur, mais au niveau de l’API, l’upload du blob et la création du record sont distincts
  • D’après la spécification des blobs, un blob doit d’abord être téléversé vers le PDS avant qu’un record puisse le référencer
  • Au moment de l’upload, le serveur ne sait pas encore dans quel Lexicon le blob sera utilisé ; il n’applique donc d’abord que les restrictions générales sur les blobs, puis les restrictions fondées sur le Lexicon au moment de la création du record
  • Un blob téléversé avec succès est placé dans un stockage temporaire ; dans cet état, il ne peut ni être téléchargé ni être diffusé, et n’apparaît pas non plus dans la sortie de listBlobs
  • Si la création du record réussit et que ce record référence le blob, le serveur fait alors passer ce blob dans un état accessible publiquement

Upload d’un blob HTML et passage en accès public

  • Le jeton d’authentification s’obtient en échangeant les identifiants de l’utilisateur via la méthode XRPC com.atproto.server.createSession
  • Dans l’exemple, com.atproto.sync.listBlobs a servi à vérifier le nombre de blobs existants, qui était de 391 avant l’upload
  • Le contenu du site web était un simple fichier index.html, téléversé via com.atproto.repo.uploadBlob
    • Content-Type était text/html
    • les métadonnées du blob renvoyées incluaient le CID bafkreic5fmelmhqoqxfjz2siw5ey43ixwlzg5gvv2pkkz7o25ikepv4zeq, mimeType: text/html, size: 268
  • Juste après l’upload, une tentative de récupération via com.atproto.sync.getBlob échouait, et le total de listBlobs restait à 391
  • Si l’on référence le blob HTML comme embed d’image dans un post Bluesky, cela échoue car le schéma app.bsky.embed.image exige un type MIME image/*
    • en laissant text/html tel quel, l’erreur renvoyée est Wrong type of file. It is text/html but it must match image/*.
    • en remplaçant mimeType par image/jpeg, une autre erreur indique que le type MIME du blob stocké ne correspond pas au type MIME de la référence

Rendre un blob public avec un record au lexicon inconnu

  • Le type blob n’est pas propre à Bluesky, il fait partie du modèle de données de l’AT Protocol
  • L’implémentation PDS de Bluesky est open source, et findBlobRefs parcourt récursivement LexValue pour trouver les références $type: blob
  • Comme le PDS doit pouvoir prendre en charge de nouveaux lexicons avec le temps, il doit aussi être capable de traiter ceux qu’il ne connaît pas
  • En créant un record de type com.danielmangum.hack.website qui référence le blob HTML, le PDS a bien enregistré le record
    • la réponse incluait validationStatus: unknown
    • le PDS ne connaissait pas le lexicon com.danielmangum.hack.*, donc il ne pouvait pas le valider, mais l’enregistrement du record restait autorisé
  • Ensuite, le nombre de blobs dans listBlobs est passé à 392, ce qui confirme que le blob a été conservé de manière persistante
  • getBlob peut être appelé sans jeton d’authentification, car des acteurs non authentifiés qui traitent les blobs avec les records doivent aussi pouvoir récupérer ces blobs
  • L’appel à getBlob via bsky.social renvoyait une redirection 302 vers l’URL du vrai PDS, et en suivant la redirection avec -L, le contenu HTML était retourné tel quel

En-têtes de sécurité, CDN et coût de l’accès direct

  • Les considérations de sécurité de la spécification des blobs de l’AT Protocol traitent des risques de sécurité liés au fait de servir des fichiers arbitraires téléversés par les utilisateurs depuis un serveur web
    • l’exemple classique est un problème de XSS, quand du script ou du contenu SVG s’exécute depuis la même origin
    • l’application d’une Content Security Policy sur l’endpoint getBlob est pratiquement indispensable
    • fournir directement le stockage de blobs aux navigateurs et agents web n’est, dans les faits, pas pris en charge ; les applications doivent passer par un CDN distinct, un proxy ou un service web dédié
  • Le handler getBlob du PDS Bluesky applique les en-têtes de sécurité recommandés
    • x-content-type-options: nosniff
    • content-security-policy: default-src 'none'; sandbox
  • La limite par défaut d’upload de blobs est de 5 Mo
  • Les blobs d’images ordinaires de l’application Bluesky ne sont pas servis directement par le PDS, mais via des URL CDN sur cdn.bsky.app
    • il existe des URL distinctes pour les miniatures du feed et pour la taille complète
    • le record du post inclut le CID de l’image, et l’application doit savoir comment ce contenu est servi par le CDN
  • L’accès direct via getBlob subsiste, d’après un ticket GitHub, pour des usages comme le labellisation d’images, l’export de contenu utilisateur et de futurs cas d’usage
    • la même discussion mentionne aussi la possibilité que des utilisateurs fassent du hotlinking vers leur contenu ou utilisent Bluesky comme un hébergement gratuit
    • dans l’implémentation initiale, les en-têtes de sécurité n’étaient pas présents, puis ils ont été ajoutés ensuite
  • Les plateformes sociales traditionnelles limitent davantage les types de contenus valides, ce qui leur permet d’imposer des restrictions plus strictes sur les blobs dès l’upload ; mais l’extensibilité de Bluesky et de l’AT Protocol impose un traitement plus complexe

Ajouter un embed personnalisé à un post via open union

  • Le type app.bsky.feed.post définit la liste des embeds valides sous forme d’union
  • Dans le lexicon de l’AT Protocol, une union est par défaut open tant qu’elle n’est pas explicitement marquée closed
    • comme de nouveaux types peuvent être ajoutés lors de futures révisions du schéma, les implémentations doivent rester permissives lors de la validation
    • si le drapeau closed est présent, l’ensemble des types est figé
  • L’union des embeds d’un post Bluesky n’est pas marquée closed
  • Il est donc possible de créer un post avec un embed $type qui n’est pas énuméré
  • Dans l’exemple, un embed com.danielmangum.hack.sites a été placé dans un record app.bsky.feed.post, et cet embed référençait le blob HTML
    • validationStatus de la réponse était valid
    • dans l’application Bluesky, cet embed est simplement ignoré
    • le contenu et la référence restent enregistrés durablement dans le record, ce qui permet à d’autres applications de rendre cet embed
  • Ce type de propriété du lexicon peut servir à ajouter de petites extensions par-dessus des cas d’usage existants ; un exemple envisagé serait de faire exécuter de petits fragments de code dans un post au sein d’un sandbox WebAssembly

1 commentaires

 
GN⁺ 2024-11-25
Avis sur Hacker News
  • J’ai apprécié que Daniel se soit renseigné directement auprès de l’équipe, et l’hébergement de blobs est un domaine qui devra forcément continuer à être ajusté à mesure que l’on comprend mieux les vecteurs d’abus
    Pour l’instant, c’est amusant de voir ce genre d’usage fonctionner réellement, et l’architecture prévue est bien que le PDS devienne un hôte de base de données, comme un serveur web héberge un site web

    • Je me demande s’il est prévu de relancer Beaker Browser
      C’était sympa à bidouiller à l’époque, et je ne savais pas qu’il avait migré vers Bluesky ; c’est assez intéressant
    • Si le contenu pointe vers le domaine propre à l’utilisateur, sans passer par une appview spécifique comme bsky, cela ne réduirait-il pas les risques d’abus ?
      Bsky prend déjà en charge une méthode consistant à rattacher le domaine utilisateur à redirect.bsky.com via un ALIAS : https://bsky.app/profile/jacob.gold/post/3kh6rnpdzmp2v
    • Beaker était un projet vraiment passionnant à suivre, et cette expérience semble pouvoir être assez utile pour Bluesky
    • Cette approche revient à foncer droit dans le bourbier de la modération de contenus comme le droit d’auteur, les CSAM et la pornographie
  • Je me suis demandé dans quel contexte de sécurité cela s’exécutait, donc j’ai vérifié une URL de blob PDS avec curl, et les en-têtes de réponse contenaient notamment access-control-allow-origin: *, content-security-policy: default-src 'none'; sandbox, x-content-type-options: nosniff
    access-control-allow-origin: * est intéressant, car cela signifie que le JavaScript d’une page web de n’importe quel domaine peut accéder à ce contenu avec fetch(), tandis que default-src 'none'; sandbox est une configuration très restrictive et appréciable, qui empêche de charger des scripts ou images supplémentaires et bloque aussi l’exécution de JavaScript : https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Co...
    ratelimit-limit: 3000 semble probablement être une limite par IP

    • Bloquer entièrement JavaScript, ou le limiter à une liste d’autorisation, est à peu près la seule façon d’enfermer complètement une app avec CSP et d’empêcher l’exfiltration de données ; avec le prefetch, même cela peut ne pas suffire
      La suggestion, en fin d’article, d’utiliser WebAssembly va dans le bon sens, et le problème des clics sur les liens peut être limité en plaçant le code non fiable dans une iframe, puis en n’autorisant depuis le parent, via child-src, que certains domaines ou data:
      https://github.com/w3c/webappsec/issues/656#issuecomment-246...
      https://www.w3.org/TR/CSP3/#exfiltration
    • Je me demande si default-src est nécessaire même quand on utilise sandbox, ou si c’est une configuration redondante
  • Je suis assez enthousiaste à l’idée d’utiliser Bluesky comme stockage de données blob
    Avec un ami, on a imaginé stocker des WAD DOOM sur Bluesky et partager des « packs de cartes » comme des publications ; en suivant des comptes, des listes ou des starter packs, on pourrait modifier un client comme GZDoom pour rechercher et afficher les WAD publiés par ces comptes
    Ce serait similaire au Steam Workshop, mais en passant par Bluesky

    • C’est une chouette idée, mais je me demande si tu comptes héberger ça toi-même ou le mettre sur les serveurs de Bluesky
      Je ne sais pas non plus si Bluesky a déjà évoqué des consignes d’usage pour le stockage de blobs ; les fichiers DOOM sont petits, donc c’est un bon exemple, mais je me demande aussi si cela pourrait être détourné pour externaliser de gros volumes d’espace serveur
    • Au final, n’est-ce pas un peu utiliser Bluesky comme un flux RSS pour des données arbitraires ?
  • Je n’ai pas connu cette époque précise, mais voir les utilisateurs de BlueSky se jeter dans des eaux techniques profondes me rappelle l’époque où l’on apprenait ses premiers bouts de HTML sur MySpace
    Le marché des réseaux sociaux est bien plus saturé qu’avant, mais je me demande si une nouvelle génération de programmeurs pourrait émerger de BlueSky

    • MySpace et les anciens forums offraient une échelle d’abstraction : on commençait par mettre du texte dans une boîte pour le voir apparaître sur une page web, puis on ajoutait des images, du BBCode/du balisage, du HTML et du CSS, jusqu’à écrire de vrais scripts
      Bluesky ne propose que la première étape, puis la marche suivante semble assez grande
    • Même si c’est censé être « 14 minutes », je vois une douzaine de liens qui semblent indispensables à lire pour essayer une nouvelle idée
      Avec HTTP/HTML, on peut montrer à quelqu’un qui ne connaît que les bases de Python ou d’un autre langage de programmation comment créer un serveur à partir de zéro en 14 minutes
      Je pense qu’il faut un protocole de communication à adressage par hash pour empêcher les géants de la tech d’exploiter les communautés, mais je ne suis pas sûr qu’AT Protocol ait aussi bien calibré son objectif que HTTP pour déclencher une révolution
  • L’un des points clés est que le PDS évoqué ici fait partie du Personal Data Server, et non du produit Bluesky lui-même, ce qui peut le faire ressembler, au final, à un stockage de données gratuit et illimité
    Le service d’abonnement dont l’équipe Bluesky a parfois parlé pourrait être lié à ce genre de choses, et un hébergement offrant davantage d’espace, de bande passante ou de vidéo haute qualité sur un PDS semble bien se prêter à une offre premium

    • Si j’ai bien compris, ce PDS était hébergé par Bluesky
      Il devrait être possible de l’héberger ailleurs aussi, donc l’idée qu’on puisse interagir au-delà de l’app Bluesky semble correcte
  • Le phénomène par lequel, dès qu’on permet de mettre quelque chose sur Internet, quelqu’un finit toujours par essayer d’y faire de l’hébergement de fichiers arbitraires, mériterait sans doute un nom

    • C’est probablement aussi une raison importante pour laquelle les comptes Google sont désormais limités à 15 Go avant paiement supplémentaire
      À l’époque où c’était illimité, plusieurs projets open source étaient apparus pour sauvegarder des systèmes de fichiers dans Gmail, entre autres, et cela s’est encore aggravé après l’arrivée de Drive
      Les services gratuits doivent anticiper ce genre de problème et imposer des limites par défaut
    • Comme l’expression “parasitic computation” existe déjà, on pourrait appeler cela “parasitic data storage
    • Loi de Johnson : plus une chose reçoit d’attention, plus son rayon d’impact s’élargit
    • Cela se rapproche de l’Inner Platform effect
      https://en.wikipedia.org/wiki/Inner-platform_effect
  • Si ce sujet vous intéresse, atfile vaut aussi le détour : https://github.com/electricduck/atfile

  • C’est plutôt impressionnant, et on peut consulter directement l’intéressante issue GitHub liée en bas de l’article
    La personnalité bien connue de Bluesky, pfrazee, y apparaît aussi : https://github.com/bluesky-social/atproto/issues/523
    J’ai beaucoup d’attentes vis-à-vis d’AT, et il y a aussi beaucoup de gens brillants qui ont fait un excellent travail dans le Fediverse, mais ce paradigme semble plus durable et plus réaliste
    L’idée est de fournir par défaut un service centralisé, tout en offrant une vraie prise en charge de la décentralisation quand c’est nécessaire ou pour les utilisateurs avancés

    • Côté viabilité, j’espère un modèle économique meilleur que “se faire financer par Blockchain Capital”
      Il faut trouver un moyen de générer un retour sur investissement avec le mirroring du firehose ; par exemple, une alternative à Discord où certains utilisateurs paient pour l’hébergement de vidéos plus longues, une structure à la Patreon où les relais gèrent l’accès et les clés de déchiffrement en échange d’une commission, ou encore une marketplace sociale à la Bandcamp semblent possibles
      En tout cas, puisque c’est une plateforme ouverte, il n’y a aucune raison de ne pas pouvoir le faire dès maintenant
      https://www.blockchaincapital.com/blog/bluesky-13m-users-and...
      https://bsky.social/about/blog/09-11-2024-video
  • J’ai l’impression que cette approche risque d’être détournée pour du phishing ou la distribution de malwares

    • Existe-t-il seulement un site d’hébergement qui ne soit pas détourné ?
      Quand on crée un site d’hébergement, quelqu’un essaiera de l’utiliser à des fins malveillantes : cela ressemble presque à une loi de l’informatique
    • C’est effectivement ce qui arrivera
      Le même problème existait avec les pièces jointes de Matrix
    • Au bout d’environ 5 minutes, *.bsky.network commencera probablement à être bloqué par Google Safe Browsing, Palo Alto, Bluecoat, etc.
    • Je ne sais pas trop
      Ici, c’est un lien direct vers le serveur Bluesky de l’auteur, c’est-à-dire vers son PDS, donc il est évidemment sous le contrôle de l’auteur
  • Les récents changements d’API de Strava m’ont rappelé à quel point l’accès aux données stockées sur leur plateforme est limité
    Étant donné que c’est le service dominant dans le domaine du fitness, ils pourraient progressivement placer des fonctionnalités derrière un abonnement
    Il y aurait certes des préoccupations de confidentialité ou de sécurité, mais je me demande si l’AT Protocol pourrait convenir comme plateforme de stockage de fichiers GPX ou FIT

    • Ce serait bien d’avoir une alternative fédérée à Strava
      Cela dit, à ma connaissance, l’AT Protocol ne prend pas encore en charge les publications privées ou à visibilité restreinte, et je pense que cette fonctionnalité est assez essentielle pour un usage de type Strava