3 puntos por GN⁺ 2024-11-25 | 1 comentarios | Compartir por WhatsApp
  • Daniel Mangum muestra que, usando la estructura de carga de blobs y referencias de records del AT Protocol, es posible servir archivos text/html subidos a un PDS de Bluesky como si fueran páginas web que se abren en el navegador
  • El punto clave es que un blob subido no se vuelve público de inmediato; el PDS solo lo cambia a un estado de acceso público cuando algún record hace referencia al blob
  • Los embeds de imagen normales de Bluesky solo permiten tipos MIME image/*, pero un record con un lexicon desconocido o un embed personalizado basado en open union puede guardar referencias a blobs text/html
  • getBlob puede consultarse sin autenticación, y las solicitudes a bsky.social se redirigen al PDS real, porcini.us-east.host.bsky.network, que devuelve el HTML
  • Bluesky aplica encabezados CSP y nosniff en getBlob, y limita la carga de blobs por defecto a 5 MB, pero para los servicios que operan PDS sigue existiendo la carga de alojar archivos arbitrarios y asumir sus costos de almacenamiento y transferencia

Cómo subir un sitio web con blobs de AT Protocol

  • Este caso se basa más en cómo funcionan AT Protocol y la API de PDS que en una función normal de la app de Bluesky
  • Bluesky ofrece un PDS entryway para acceder al PDS del usuario, exponiendo varias instancias de PDS bajo el dominio bsky.social
  • También se puede acceder directamente a cada instancia de PDS, y el sitio web de ejemplo se abre desde el endpoint com.atproto.sync.getBlob de porcini.us-east.host.bsky.network
  • Antes de publicar el artículo, hubo comunicación con el equipo de Bluesky; este comportamiento no es un uso previsto de la aplicación, pero sí es conocido y no se trató como un proceso de divulgación de vulnerabilidades

Por qué records y blobs están separados

  • En AT Protocol, los datos de la aplicación se dividen en dos grandes grupos: records y blobs
    • Los records son las entidades principales que crea el usuario y pueden tener estructura y metadatos
    • Los blobs son datos no estructurados más grandes, como imágenes, y por lo general deben ser referenciados por un record para quedar expuestos
  • Cuando en Bluesky se crea una publicación con imágenes, para el usuario parece una sola acción, pero a nivel de API la carga del blob y la creación del record están separadas
  • Según la especificación de blobs, un blob debe subirse primero al PDS antes de que un record pueda referenciarlo
  • Como en el momento de la carga el servidor no sabe en qué Lexicon se usará el blob, al inicio solo aplica restricciones generales de blob y deja las restricciones basadas en Lexicon para cuando se crea el record
  • Un blob cargado con éxito entra en almacenamiento temporal; en ese estado no puede descargarse ni distribuirse, y tampoco aparece en la salida de listBlobs
  • Cuando la creación del record se completa y ese record referencia el blob, el servidor cambia el blob a un estado de acceso público

Carga de un blob HTML y cambio a estado público

  • El token de autenticación se obtiene intercambiando credenciales del usuario mediante el método XRPC com.atproto.server.createSession
  • En el ejemplo, se verificó la cantidad de blobs existentes con com.atproto.sync.listBlobs, y antes de la carga había 391
  • El contenido del sitio web era un archivo index.html simple y se subió con com.atproto.repo.uploadBlob
    • Content-Type era text/html
    • Los metadatos devueltos del blob incluían el CID bafkreic5fmelmhqoqxfjz2siw5ey43ixwlzg5gvv2pkkz7o25ikepv4zeq, mimeType: text/html, size: 268
  • Justo después de subirlo, intentar obtenerlo con com.atproto.sync.getBlob no funcionaba, y la cantidad en listBlobs seguía siendo 391
  • Si se referencia el blob HTML como embed de imagen en una publicación de Bluesky, falla porque el esquema app.bsky.embed.image exige un tipo MIME image/*
    • Si se deja text/html, aparece el error Wrong type of file. It is text/html but it must match image/*.
    • Si se cambia mimeType a image/jpeg, aparece un error indicando que el MIME del blob guardado y el MIME de la referencia no coinciden

Hacer público el blob con un record de lexicon desconocido

  • El tipo blob no es exclusivo de Bluesky, sino parte del modelo de datos de AT Protocol
  • La implementación del PDS de Bluesky es open source, y findBlobRefs recorre recursivamente LexValue para encontrar referencias $type: blob
  • Como un PDS debe poder soportar nuevos lexicons con el tiempo, también necesita manejar lexicons que todavía no conoce
  • Al crear un record de tipo com.danielmangum.hack.website y hacer que referenciara el blob HTML, el PDS guardó el record
    • La respuesta incluía validationStatus: unknown
    • Como el PDS no conocía el lexicon com.danielmangum.hack.*, no podía validarlo, pero aun así permitió guardar el record
  • Después, la cantidad en listBlobs subió a 392, confirmando que el blob había quedado almacenado de forma persistente
  • getBlob puede invocarse sin token de autenticación, porque incluso entidades no autenticadas que procesan blobs junto con records necesitan poder obtenerlos
  • La llamada a getBlob en bsky.social devolvió una redirección 302 hacia la URL real del PDS, y al seguir la redirección con -L, el contenido HTML se devolvía tal cual

Encabezados de seguridad, CDN y la carga del acceso directo

  • La sección Security Considerations de la especificación de blobs de AT Protocol trata los riesgos de seguridad de servir archivos arbitrarios subidos por usuarios desde un servidor web
    • Un ejemplo típico es el problema de XSS cuando se ejecutan scripts o contenido SVG bajo el mismo origin
    • Aplicar Content Security Policy al endpoint getBlob es, en la práctica, imprescindible
    • Proveer directamente el repositorio de blobs a navegadores y agentes web no está realmente soportado; las aplicaciones deben pasar por un CDN independiente, un proxy u otro servicio web
  • El handler getBlob del PDS de Bluesky aplica los encabezados de seguridad recomendados
    • x-content-type-options: nosniff
    • content-security-policy: default-src 'none'; sandbox
  • El límite predeterminado para subir blobs es de 5 MB
  • Los blobs de imagen normales de la aplicación Bluesky no se sirven directamente desde el PDS, sino mediante URLs de CDN de cdn.bsky.app
    • Existen por separado la URL del thumbnail del feed y la URL del tamaño completo
    • El record de la publicación incluye el CID de la imagen, y la aplicación debe saber cómo resolver la forma en que el CDN la entrega
  • El acceso directo a getBlob sigue existiendo por temas discutidos en issues de GitHub, como el etiquetado de imágenes, la exportación de contenido de usuarios y casos de uso futuros
    • En esa misma discusión también se contempla la posibilidad de que usuarios hagan hotlinking de contenido o usen Bluesky como hosting gratuito
    • La implementación inicial no tenía encabezados de seguridad, y estos se agregaron después
  • Las plataformas sociales tradicionales suelen limitar más los tipos de contenido válidos, por lo que pueden aplicar restricciones más fuertes al blob desde el momento de la carga; en cambio, la extensibilidad de Bluesky y AT Protocol exige un tratamiento más complejo

Usar open union para meter un embed personalizado en una publicación

  • El tipo app.bsky.feed.post define su lista de embeds válidos como una union
  • En los lexicons de AT Protocol, una union es open por defecto a menos que esté marcada explícitamente como closed
    • Como en futuras revisiones del esquema se pueden añadir tipos, las implementaciones deben ser flexibles al validar
    • Si existe la bandera closed, el conjunto de tipos queda fijo
  • La union de embeds de las publicaciones de Bluesky no está marcada como closed
  • Por lo tanto, es posible crear publicaciones incluso con un $type de embed que no esté enumerado
  • En el ejemplo, dentro de un record app.bsky.feed.post se añadió un embed com.danielmangum.hack.sites, y dentro de este se referenciaba el blob HTML
    • El validationStatus de la respuesta fue valid
    • En la aplicación de Bluesky, ese embed simplemente se ignora en silencio
    • Como el contenido y la referencia quedan almacenados de forma persistente en el record, otras aplicaciones sí podrían renderizar ese embed
  • Este tipo de propiedad del lexicon puede aprovecharse para añadir pequeñas extensiones sobre casos de uso ya existentes; por ejemplo, se menciona la posibilidad de ejecutar pequeños fragmentos de código dentro de una publicación usando un sandbox de WebAssembly

1 comentarios

 
GN⁺ 2024-11-25
Comentarios en Hacker News
  • Agradecieron que Daniel contactara directamente al equipo, y dijeron que el hosting de blobs es un área que inevitablemente seguirá ajustándose conforme entiendan mejor las vías de abuso
    Por ahora les parece divertido ver este tipo de usos funcionando en la práctica, y la PDS está diseñada con la intención de que un host de base de datos cumpla un rol parecido al de un servidor web que aloja sitios web

    • Me pregunto si hay planes de revivir Beaker Browser
      Antes era muy divertido para experimentar, y no sabía que se habían pasado a Bluesky, lo cual me parece bastante interesante
    • Si el contenido enlazara al dominio propio del usuario en vez de pasar por una app view específica como bsky, quizá se reduciría el potencial de abuso
      Bsky ya permite adjuntar dominios de usuario como ALIAS a redirect.bsky.com: https://bsky.app/profile/jacob.gold/post/3kh6rnpdzmp2v
    • Beaker fue un proyecto realmente genial de seguir, y esa experiencia probablemente sea bastante útil en Bluesky
    • Este enfoque se está metiendo de lleno en el pozo de la moderación de contenido relacionado con copyright, CSAM y porno
  • Tenía curiosidad por saber en qué contexto de seguridad se ejecuta esto, así que revisé la URL del blob de la PDS con curl, y en los headers de respuesta aparecían cosas como access-control-allow-origin: *, content-security-policy: default-src 'none'; sandbox, x-content-type-options: nosniff, etc.
    access-control-allow-origin: * es interesante porque significa que el JavaScript de una página web en cualquier dominio puede acceder a este contenido con fetch(), y default-src 'none'; sandbox es una configuración muy restrictiva que impide cargar scripts o imágenes adicionales y también bloquea la ejecución de JavaScript, lo cual está bien: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Co...
    ratelimit-limit: 3000 probablemente sea un límite por IP

    • Bloquear todo JavaScript o limitarlo a una lista permitida es prácticamente la única forma de encerrar por completo una app con CSP y evitar la exfiltración de datos, y si hay prefetch, quizá ni siquiera eso sea suficiente
      La sugerencia al final del artículo de usar WebAssembly va en la dirección correcta, y el problema de los clics en enlaces se puede limitar poniendo código no confiable en un iframe y haciendo que el padre permita solo dominios específicos o data: mediante child-src
      https://github.com/w3c/webappsec/issues/656#issuecomment-246...
      https://www.w3.org/TR/CSP3/#exfiltration
    • Me pregunto si, al usar sandbox, sigue siendo necesario default-src o si es una configuración redundante
  • Me entusiasma bastante la posibilidad de usar Bluesky como almacenamiento de datos blob
    Pensé con un amigo en guardar WADs de DOOM en Bluesky para compartir “map packs” como si fueran publicaciones, y si sigues cuentas, listas y starter packs, tal vez se podría modificar un cliente como GZDoom para que busque y muestre los WADs subidos por esas cuentas
    Sería algo parecido a Steam Workshop, pero pasando por Bluesky

    • La idea está genial, pero me pregunto si planean alojarlo ustedes mismos o subirlo a los servidores de Bluesky
      Tampoco sé si Bluesky ha mencionado alguna guía de uso para almacenamiento de blobs, y los archivos de DOOM son un buen ejemplo porque son pequeños, pero también me pregunto si esto podría abusarse para externalizar grandes cantidades de espacio de servidor
    • Al final, parece que se estaría usando Bluesky como una especie de feed RSS para datos arbitrarios
  • No viví esa época en particular, pero ver a los usuarios de BlueSky meterse en aguas técnicas más profundas me recuerda a cuando la gente aprendía HTML por primera vez en MySpace
    El mercado de redes sociales está mucho más saturado que antes, pero me pregunto si de BlueSky podría salir una nueva generación de programadores

    • MySpace y los foros antiguos ofrecían una escalera de abstracción que empezaba con poner texto en una caja para que apareciera en una página web, luego agregar imágenes, BBCode/marcado, HTML y CSS, y finalmente escribir scripts completos
      Bluesky solo ofrece el primer escalón, y a partir de ahí el salto parece bastante grande
    • Dicen que son “14 minutos”, pero se ven como una docena de enlaces que parecerían lectura obligatoria para probar ideas nuevas
      Si fuera HTTP/HTML, podrías enseñarle incluso a alguien que solo conoce Python básico u otro lenguaje de programación cómo montar un servidor desde cero en 14 minutos
      Creo que hace falta un protocolo de comunicación con direccionamiento por hash para evitar que las grandes tecnológicas exploten a las comunidades, pero no estoy seguro de que AT Protocol haya dado tan bien en el blanco como para detonar una revolución al nivel de HTTP
  • Uno de los puntos clave aquí es que la PDS de la que se habla no es el producto Bluesky en sí, sino parte de un Personal Data Server, por lo que al final puede parecer almacenamiento de datos gratuito e infinito
    El servicio de suscripción que el equipo de Bluesky ha mencionado a veces parece poder conectarse con esto, y ofrecer más espacio, ancho de banda y video en alta calidad desde la PDS suena como algo adecuado para venderse como un nivel premium

    • Si lo entendí bien, esta PDS sí estaba alojada por Bluesky
      Como también podría alojarse en otros lados, parece cierto que se puede interactuar más allá de la app de Bluesky misma
  • Si haces posible subir cosas a internet, al final alguien va a intentar usarlo para alojar archivos arbitrarios; parece que este fenómeno necesita un nombre

    • Probablemente esa también sea una razón importante por la que las cuentas de Google ahora tienen un límite de 15 GB hasta que pagues más
      En la época en que era ilimitado, salieron varios proyectos open source que intentaban respaldar sistemas de archivos en Gmail y similares, y después de que apareció Drive se puso todavía peor
      Los servicios gratuitos deberían asumir desde el inicio que este problema va a surgir y poner límites por defecto
    • Ya existe el término “parasitic computation”, así que quizá podría llamarse “parasitic data storage
    • Ley de Johnson: cuanto más atención recibe algo, mayor se vuelve también su alcance de impacto
    • Se parece más al efecto de plataforma interna
      https://en.wikipedia.org/wiki/Inner-platform_effect
  • Si este tema te parece interesante, también vale la pena ver atfile: https://github.com/electricduck/atfile

  • Está bastante genial, y puedes ir directo a ver el interesante issue de GitHub enlazado al final del post
    También aparece pfrazee, una figura conocida de Bluesky: https://github.com/bluesky-social/atproto/issues/523
    Hay muchas expectativas puestas en AT, y seguramente también hay gente muy capaz que ha hecho un gran trabajo en el Fediverse, pero este paradigma se ve más sostenible y más realista
    La base se ofrece de forma centralizada, pero cuando hace falta, o para usuarios avanzados, se da soporte real de descentralización

    • En términos de sostenibilidad, esperaría un modelo de negocio mejor que “recibir financiamiento de Blockchain Capital”
      Hace falta alguna forma de obtener retorno de inversión del mirroring del firehose y, por ejemplo, podría existir una alternativa a Discord donde algunos usuarios paguen por alojar videos más largos, una estructura tipo Patreon donde los relays administren las llaves de acceso y descifrado y cobren una comisión, o incluso un mercado social al estilo Bandcamp
      De todos modos, como es una plataforma abierta, tampoco hay ninguna razón por la que no pueda hacerse ya ahora
      https://www.blockchaincapital.com/blog/bluesky-13m-users-and...
      https://bsky.social/about/blog/09-11-2024-video
  • Da la impresión de que este método podría usarse mal para phishing o distribución de malware

    • Cuesta pensar en algún sitio de hosting que no termine siendo mal usado
      Parece casi una ley de la computación que, si creas un sitio de hosting, alguien va a querer usarlo con fines maliciosos
    • En la práctica, sí va a pasar
      Hubo el mismo problema con los archivos adjuntos de Matrix
    • Después de unos 5 minutos, probablemente *.bsky.network empiece a ser bloqueado por Google Safe Browsing, Palo Alto, Bluecoat y similares
    • No estoy tan seguro
      Este es un enlace directo al servidor de Bluesky del autor, es decir, a su PDS, así que naturalmente está bajo el control del autor
  • Viendo los cambios recientes en la API de Strava, me acordé de lo restringido que es el acceso a los datos almacenados en su plataforma
    Como es el servicio dominante en el sector fitness, podrían ir moviendo funciones poco a poco detrás de un muro de suscripción
    Puede haber preocupaciones de privacidad o seguridad, pero me pregunto si AT Protocol podría servir como plataforma de almacenamiento para archivos GPX o FIT

    • Estaría bien tener una alternativa federada a Strava
      Aunque, hasta donde sé, AT Protocol todavía no soporta publicaciones privadas o con visibilidad restringida, y me parece que esa función es bastante clave para un caso de uso como Strava