Saltar a contenido

IAM policy mishaps: Case 1 - S3

Continuando con la serie de posts relacionados con missconfigurations de IAM vamos a indagar un poco sobre su uso enfocado al servicio de AWS S3.

Para ello veremos las distintas formas que tenemos para controlar la seguridad del servicio y lo peligroso que es aplicar una política sin tener claro lo que hace.

Info

Si quieres probar directamente los ejemplos que vamos a presentar echa un vistazo a nuestro repo .

Os hemos dejado preparados los diferentes escenarios en Terraform .

📣 Os recomendamos pasar por la primera parte de la serie, IAM policy mishaps: Intro to IAM, en caso de que no lo hayáis hecho aún.

Ahora si, hablemos de Cloud ☁.

For the cloud is dark and full of terrors

El servicio de S3 (Simple Storage Service) es uno de los más conocidos y usados de AWS. Este nos permite almacenar objetos con todo tipo de datos, por lo que es usado por multitud de aplicaciones.

AWS S3

Escenario 1

Objetivo: Conceder permiso al rol de nuestra aplicación, App_A_role, para que pueda leer y escribir archivos en nuestro bucket de facturas, invoices-bucket.

Dado que nuestro bucket contendrá información privada, queremos evitar que otros usuarios accedan a nuestra información.

Escenario 1

Hay diferentes formas de dar acceso a un bucket de S3:

  1. No! Esto no! 🚫

Por razones obvias, vamos a descartar la primera opción, y como este post es sobre IAM nos centraremos en la segunda. Además de ser la opción recomendada para lo que necesitamos.

La forma más fácil de construir una política de IAM es utilizando la propia consola de AWS.

En ella, podemos seleccionar el servicio de S3 y nos encontraremos con las 158 acciones disponibles en el momento que estamos escribiendo esto.

S3 actions

Podríamos revisarlas una a una...

Lo más probable (y lo menos recomendado) es que nos abrume y acabemos teniendo una policy como esta:

{
    "Version": "2012-10-17",
    "Statement": {
        "Sid": "RWS3Access",
        "Effect": "Allow",
        "Action": [
            "s3:*", // (1)!
        ],
        "Resource": [
            "arn:aws:s3:::sh3llcon-invoices-bucket", // (2)!
            "arn:aws:s3:::sh3llcon-invoices-bucket/*", // (3)!
        ]
    }
}
  1. s3:* permite todas las acciones disponibles (ahora y en el futuro) de S3.
  2. Las acciones se permiten sobre el propio recurso del bucket.
  3. Las acciones se permiten sobre todos los objetos dentro del bucket.

Si probáis esta política comprobaréis que vuestra aplicación puede leer y escribir en vuestro bucket perfectamente, pero... sabías que también puede hacer, entre otras muchas cosas, todo esto?

  • Eliminar el bucket.
  • Hacerlo público.
  • Deshabilitar los logs, en caso de que estén habilitados.
  • Desactivar el versionado, en caso de que esté habilitado.
  • Desactivar protecciones de retención de datos, en caso de que estén habilitadas.

Nuestra aplicación solo necesita leer y escribir archivos, no queremos que pueda hacer ninguna de estas acciones, ya sea por error o por un fallo de seguridad en nuestra aplicación.

(Os sorprenderíais de la cantidad de veces que se ven políticas similares. 🫤)

Vamos a mejorar un poco la policy.

En las acciones de IAM tienes la posibilidad de utilizar wildcards, por lo que si utilizamos s3:Put* autorizaremos todas las acciones de S3 que sean o empiecen por Put. Como solo queremos permitir lectura List/Get y escritura Put/Delete probaremos con la siguiente policy:

{
    "Version": "2012-10-17",
    "Statement": {
        "Sid": "RWS3Access",
        "Effect": "Allow",
        "Action": [
            "s3:Delete*", // (1)!
            "s3:Get*", // (2)!
            "s3:List*", // (3)!
            "s3:Put*", // (4)!
        ],
        "Resource": [
            "arn:aws:s3:::sh3llcon-invoices-bucket",
            "arn:aws:s3:::sh3llcon-invoices-bucket/*",
        ]
    }
}
  1. s3:Delete* incluye s3:DeleteBucket, entre otros permisos.
  2. s3:Get* permite acciones para obtener información sobre el bucket, por ejemplo s3:GetBucketPolicy.
  3. s3:List* solo incluye s3:ListBucket, pero AWS podría añadir nuevos permisos en el futuro.
  4. s3:Put* incluye s3:PutBucketPolicy, entre otros permisos.

Con esta policy nos hemos quitado algunos de los permisos que no son necesarios para la aplicación, pero seguimos pudiendo hacer las mismas acciones que hemos nombrado anteriormente.

Vamos a seguir afinando la policy:

{
    "Version": "2012-10-17",
    "Statement": {
        "Sid": "RWS3Access",
        "Effect": "Allow",
        "Action": [
            "s3:DeleteObject",
            "s3:GetObject",
            "s3:ListBucket",
            "s3:PutObject",
        ],
        "Resource": [
            "arn:aws:s3:::sh3llcon-invoices-bucket",
            "arn:aws:s3:::sh3llcon-invoices-bucket/*", // (1)!
        ]
    }
}
  1. Este * aplica a todos los objetos dentro del bucket, sería equivalente a indicar la ruta completa de cada archivo. También se podría poner la condición a nivel de carpeta. Si utilizas buckets compartidos ten cuidado aplicando estos wildcards en el recurso.

Recuerda

Los asteriscos * son peligrosos, asegúrate de saber lo que estás permitiendo antes de usarlos en tus policies.

Tip

Si la aplicación no debe borrar ficheros elimina s3:DeleteObject. No queremos permitir borrados accidentales.

Security Check

Security Approves!

Escenario 2

Vamos a complicar nuestro escenario un poco.

Objetivo: Ahora el bucket estará en una cuenta separada y querremos tener permiso de lectura desde dos roles, App_A_role y App_B_role, localizados en cuentas diferentes.

Escenario 2

Las aplicaciones únicamente deberán poder listar y leer los archivos disponibles en el bucket.

Con lo visto anteriormente les asignaremos la siguiente policy a ambos roles:

{
    "Version": "2012-10-17",
    "Statement": {
        "Sid": "ROS3Access",
        "Effect": "Allow",
        "Action": [
            "s3:GetObject",
            "s3:ListBucket",
        ],
        "Resource": [
            "arn:aws:s3:::sh3llcon-invoices-bucket",
            "arn:aws:s3:::sh3llcon-invoices-bucket/*",
        ]
    }
}

El bucket, al estar localizado en una cuenta separada, necesitamos añadir otro mecanismo de autorización. Porque las policies asignadas a nuestros roles de IAM no tienen efecto fuera de nuestra cuenta.

Por lo que vamos a usar las resource policies (en este caso llamadas bucket policies).

Añadimos al bucket la siguiente bucket policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Sh3llconPolicy",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": [
        "s3:ListBucket",
        "s3:GetObject"
      ],
      "Resource": [
        "arn:aws:s3:::sh3llcon-invoices-bucket/*",
        "arn:aws:s3:::sh3llcon-invoices-bucket"
      ],
      "Condition": {
        "ForAllValues:StringEquals": {
          "aws:PrincipalArn": [
            "arn:aws:iam::111111111111:role/App_A_role",
            "arn:aws:iam::222222222222:role/App_B_role"
          ]
        }
      }
    }
  ]
}

Si probamos nuestras aplicaciones veremos que tienen el acceso necesario, pero vamos a probar una cosa más:

curl https://sh3llcon-invoices-bucket.s3-us-west-2.amazonaws.com/
Output
<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01">
    <Name>sh3llcon-invoices-bucket</Name>
    <Prefix></Prefix><Marker></Marker><MaxKeys>1000</MaxKeys>
    <IsTruncated>false</IsTruncated>
    <Contents><key>flag.txt</key>
    <LastModified>2024-01-24T09:03:12.000Z</LastModified>
    <ETag>&quot;e82274fac36cf343b567493489557f84&quot;</ETag><Size>22</Size>
    <Owner><ID>e8c8232133f49c01bd96e5dc81337b571fb3daa0819936e9d3f470bd1ff21f60</ID><DisplayName>cloudsec+shellcon</DisplayName></Owner>
    <StorageClass>STANDARD</StorageClass></Contents>
</ListBucketResult>
curl https://sh3llcon-invoices-bucket.s3-us-west-2.amazonaws.com/flag.txt
Output
FLAG{Hello sh3llcon!}

Esto lo podemos probar también usando la CLI de AWS, pero añadiendo la flag --no-sign-request para asegurarnos de que no estemos usando un rol nuestro autorizado.

aws s3 cp s3://sh3llcon-invoices-bucket.s3-us-west-2.amazonaws.com/flag.txt - --no-sign-request
Output
FLAG{Hello sh3llcon!}

Vaya... Parece que el bucket ha quedado un poco expuesto, vamos a corregir la policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Sh3llconPolicy",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": [
        "s3:ListBucket",
        "s3:GetObject"
      ],
      "Resource": [
        "arn:aws:s3:::sh3llcon-invoices-bucket/*",
        "arn:aws:s3:::sh3llcon-invoices-bucket"
      ],
      "Condition": {
        "StringEquals": {
          "aws:PrincipalArn": [
            "arn:aws:iam::111111111111:role/App_A_role",
            "arn:aws:iam::222222222222:role/App_B_role"
          ],
          "aws:PrincipalOrgID": "0-XXXXXXXX" // (1)!
        }
      }
    }
  ]
}
  1. Esto es opcional, pero de esta forma nos aseguramos que solo permitimos accesos desde nuestra Organización en AWS.

No se debe usar ForAllValues or ForAnyValue con claves de contexto de un solo valor ya que puede dar lugar a missconfigurations.

Como en el caso de ForAllValues que como explica la documentación:

También devuelve verdadero si no hay claves de contexto en la solicitud o si los valores de clave de contexto se resuelven en un conjunto de datos nulo, como una cadena vacía.

Eso hace que en nuestras pruebas, en las que no estamos usando ningún rol (y por tanto no hay aws:PrincipalArn) la condición evalúe a Verdadero.

Aquí tenéis un articulo de Michael Kirchner que lo explica en detalle.

Tip

Para evitar estos problemas debemos asegurarnos de que si no necesitamos acceder al bucket desde fuera de AWS habilitemos el block public access.

Block Public Access

Conclusión

No todo son problemas, AWS ha ido incluyendo nuevas medidas que nos ayudan a proteger nuestros buckets:

  • Buckets privados por defecto.
  • Block public Access habilitado por defecto.
  • Las ACLs están deshabilitadas por defecto.
  • Access Analyzer (un servicio que detecta findings en políticas de IAM) en el editor de bucket policies.

IAM Access Analyzer

Pero también han añadido nuevas formas de dar acceso a un bucket, por lo que tendremos que estar siempre al día para revisar cómo mantener nuestros buckets seguros.

Tweet from @Frichette_n

Por lo que al final lo importante es mantener tus sistemas y a ti mismo actualizado 😉

Old man yells at cloud...

Y si no siempre podremos gritarle a la nube...

Nos vemos en el próximo episodio con SNS.

Saludos, y que la fuerza os acompañe.