Sqlnet Encryption Demystified

Avec la 12c, l’encryption sqlnet est devenue gratuite… ou plutôt inclus dans la licence Oracle database SE2 ou EE. Avant en 11g et version antérieure il fallait payer l’option Advanced Security et donc avoir une licence EE.

L’encryption sqlnet permet de chiffrer les échanges réseaux entre le client et la base de données dans le flux sqlnet. Il faut savoir que par défaut que tous les échanges sont en clair à l’exception de certaines commandes comme « password » qui est une rare commande qui crypte le passage du mot de passe dans le flux sqlnet.  Cela signifie, que si on fait à partir d’un client « select nom,cb from client where nom='WINROC'; » sur le réseau on pourra lire en claire « select nom,cb from client where nom='WINROC' » mais surtout quand la base répondra « 2050804010306050» cela apparaitra en clair et lisible avec un simple tcpdump ou un client en mode trace.

Les données peuvent être interceptées très simplement en mettant en trace la couche sqlnet ou bien avec un dump sur le client ou le serveur.  

 

Pour bien se rendre compte de la facilité, commençons par mettre en place la couche sqlnet en mode trace maximum du côté du client par exemple. Il suffit d’aller dans le $ORACLE_HOME/network/admin du client et d’éditer ou de créer le cas échéant le fichier sqlnet.ora et d’ajouter la ligne suivante :

trace_level_client = 16

Puis on se connecte avec le client sur la base de données et on exécute la requête :

SQL> select nom,cb from client where nom='WINROC';

NOM           CB
-------   -----------------
WINROC   2050804010306050

Et en vérifiant le contenu du fichier trace associé à la session :

[root@client trace]# pwd
/oracle/oradiag_winroc/diag/clients/user_winroc/host_10620789_76/trace
[root@client trace]# vi ora_2741_139963561969408.trc
[...]
2016-01-03 21:03:04.412237 : nsbasic_bsd:00 2C 73 65 6C 65 63 74  |.,select|
2016-01-03 21:03:04.412255 : nsbasic_bsd:20 6E 6F 6D 2C 63 62 20  |.nom,cb.|
2016-01-03 21:03:04.412272 : nsbasic_bsd:66 72 6F 6D 20 63 6C 69  |from.cli|
2016-01-03 21:03:04.412289 : nsbasic_bsd:65 6E 74 20 77 68 65 72  |ent.wher|
2016-01-03 21:03:04.412307 : nsbasic_bsd:65 20 6E 6F 6D 3D 27 57  |e.nom='W|
2016-01-03 21:03:04.412324 : nsbasic_bsd:49 4E 52 4F 43 27 01 00  |INROC'..|
[...]
2016-01-03 21:03:04.413546 : nsbasic_brc:00 00 07 06 57 49 4E 52  |....WINR|
2016-01-03 21:03:04.413564 : nsbasic_brc:4F 43 10 32 30 35 30 38  |OC.20508|
2016-01-03 21:03:04.413581 : nsbasic_brc:30 34 30 31 30 33 30 36  |04010306|
2016-01-03 21:03:04.413598 : nsbasic_brc:30 35 30 08 06 00 5A 06  |050...Z.|
[...]

… effectivement tout apparait en clair.

Testons maintenant avec un tcpdump.  Il suffit de lancer le dump tcp sur le port 1521 (1521 si on connait le port du listener cela permet de capturer que les échanges base-client), et d’attendre que l’application demande le numéro de carte bleu de winroc.

[root@client ~]# tcpdump -w spy.res -s0 tcp port 1521

Avec un analyseur de fichier tcpdump ou carrément avec un string si pas trop ligne:

[root@client ~]# strings spy.res
[...]
,select nom,cb from client where nom='WINROC'
Y6KD
WINROC
2050804010306050
[...]

Un sniffage réseau serait possible aussi si celui-ci n’est pas chiffré.

 

Pour éviter cela il faut mettre donc en place le cryptage de la couche sqlnet. Sa mise en place est extrêmement simple. Il n’y a aucune clef de chiffrement à générer, juste le fichier sqlnet.ora côté serveur à modifier et dans certaines configurations le sqlnet.ora du côté des clients. C’est quand on dit que c’est très simple, juste un fichier à modifier et même pas de génération de clef que notre interlocuteur se met à douter de notre sérieux, il demande alors à son expert sécurité. L’expert sécurité n’est pas forcément caler Oracle. Et là le plus simple c’est de dire : Oracle utilise le bien connu algorithme de négociation de clef Diffie-Hellman pour la distribution sécurisée des clefs de chiffrage et de d’intégrité. Les clefs sont auto-générées plusieurs fois lors d’une session. Puis on copie/colle le début du chapitre « Configuring Network Data Encryption and Integrity » de la documentation Oracle « Security Guide » de la 12c (ou bien Advanced Security Guide » pour les versions 11g et antérieurs). La discussion s’arrête alors là… et la mise en place peut alors commencer.

 

Toute la mise en place se fait dans les fichiers sqlnet.ora côté serveur et/ou client.

Pour le chiffrage, il y a juste 4 paramètres maximum à positionner. Ils sont listés ci-dessous avec leurs valeurs possibles:

SQLNET.ENCRYPTION_SERVER = [accepted | rejected | requested | required]
SQLNET.ENCRYPTION_TYPES_SERVER = (valid_encryption_algorithm [,valid_encryption_algorithm])

SQLNET.ENCRYPTION_CLIENT = [accepted | rejected | requested | required]
SQLNET.ENCRYPTION_TYPES_CLIENT = (valid_encryption_algorithm [,valid_encryption_algorithm])

valid_encryption_algorithm est un algorithme de chiffrage à choisir dans la liste suivante : RC4_256, RC4_128, RC4_56, RC4_40, AES256, AES192, AES128, 3DES168, 3DES112, DES, DES40.

ENCRYPTION_SERVER s’applique pour la database (le serveur) :
-          ACCEPTED la base accepte le chiffrage si le client l’impose et le permet
-          REJECT elle refuse tout chiffrage
-          REQUESTED elle demande le chiffrage si le client le permet
-          REQUIRED la base veut impérativement chiffrer la connexion

ENCRYPTION_TYPES_SERVER : la liste des chiffrages à mettre en place à la connexion dans l’ordre de préférence.

ENCRYPTION_CLIENT et ENCRYPTION_TYPES_CLIENT sont exactement comme les deux paramètres précédents mais pour la partie cliente.

Et maintenant trois petits exemples pour comprendre la synergie des 4 paramètres:

Exemple1

Sur le sqlnet.ora du serveur il y a :

SQLNET.ENCRYPTION_SERVER = required
SQLNET.ENCRYPTION_TYPES_SERVER = AES256

Et sur le sqlnet.ora du client il y a :

SQLNET.ENCRYPTION_CLIENT = accepted

Alors la connexion sera chiffrée en AES 256-bit car le serveur impose du AES256 et le client l’accepte.

Exemple 2

Sur le sqlnet.ora du serveur il y a :

SQLNET.ENCRYPTION_SERVER = requested
SQLNET.ENCRYPTION_TYPES_SERVER = AES256,AES128

Et sur le sqlnet.ora du client il y a :

SQLNET.ENCRYPTION_CLIENT = required
SQLNET.ENCRYPTION_TYPES_CLIENT = AES128

Alors la connexion sera chiffrée en AES 128-bit car le client impose le AES128 et la base demande le chiffrage si le client le permet (ce qui est le cas car elle l’impose)

Exemple 3

Sur le sqlnet.ora du serveur il y a :

SQLNET.ENCRYPTION_SERVER = rejected

Et sur le sqlnet.ora du client il y a :

SQLNET.ENCRYPTION_CLIENT = required

Alors la connexion ne se fera pas, il y aura une erreur (ORA-12650) car le serveur refuse le chiffrement alors que le client l’exige : c’est le clash !

 

Remarques :
-          ACCEPTED est la valeur par défaut pour le client et le serveur quand on ne les définit pas.
-          On peut très bien mettre dans le sqlnet.ora du côté serveur (database) les paramètres *_CLIENT, cela a un sens et signifie que quand on passe par le couche client du serveur on hérite des paramètres *_CLIENT. Cela peut être par exemple utile pour mettre en place le chiffrage dans un réseau dataguard.

 

Le raisonnement est le même pour le contrôle de l’intégrité des données :

SQLNET.CRYPTO_CHECKSUM_SERVER = [accepted | rejected | requested | required]
SQLNET.CRYPTO_CHECKSUM_TYPES_SERVER = (valid_crypto_checksum_algorithm [,valid_crypto_checksum_algorithm])
SQLNET.CRYPTO_CHECKSUM_CLIENT = [accepted | rejected | requested | required]
SQLNET.CRYPTO_CHECKSUM_TYPES_CLIENT = (valid_crypto_checksum_algorithm [,valid_crypto_checksum_algorithm])

 valid_crypto_checksum_algorithm est un algorithme d’intégrité à choisir dans la liste suivante : MD5, SHA1, SHA256, SHA384, SHA512. Remarque : si le client ou la base est en version 11g, 10g ou 9i alors le choix est à faire uniquement entre MD5 ou SHA1.

*_SERVER pour le côté serveur, *_CLIENT pour le côté client.

 

Pour résumer toutes les configurations possibles client/serveur  pour le chiffrage et le contrôle d’intégrité, voici la liste des combinaisons possibles et leurs effets : NON pour pas de flux chiffré ou de contrôle d’intégrité, OUI pour chiffrage du flux et mis en place du contrôle d’intégrité.

Paramétrage Client

Paramétrage Serveur

Encryption/Integrity

REJECT

REJECT

NON

ACCEPTED

REJECT

NON

REQUESTED

REJECT

NON

REQUIRED

REJECT

Erreur à la connexion

REJECT

ACCEPTED

NON

ACCEPTED

ACCEPTED

NON

REQUESTED

ACCEPTED

OUI

REQUIRED

ACCEPTED

OUI

REJECT

REQUESTED

NON

ACCEPTED

REQUESTED

OUI

REQUESTED

REQUESTED

OUI

REQUIRED

REQUESTED

OUI

REJECT

REQUIRED

Erreur à la connexion

ACCEPTED

REQUIRED

OUI

REQUESTED

REQUIRED

OUI

REQUIRED

REQUIRED

OUI

(Des fois il faut savoir ne pas trop abuser sur la couleur)

 

Une mise en place suivie d’un petit test s’impose. Nous testons le cas d’un chiffrement en AES256 et un contrôle d’intégrité SHA1 obligatoire demandé par la base de données.

Sur le client :

[winroc@client admin]$ pwd
/oracle/product/11.2.0/client_1/network/admin
[winroc@client admin]$ vi sqlnet.ora

le sqlnet.ora est vide.

Sur le serveur :

[oracle@ora12cdb ~]$ cd /oracle/12.1.0.2/database/network/admin/
[oracle@ora12cdb admin]$ vi  sqlnet.ora
SQLNET.ENCRYPTION_SERVER = requested
SQLNET.ENCRYPTION_TYPES_SERVER = AES256
SQLNET.CRYPTO_CHECKSUM_SERVER = requested
SQLNET.CRYPTO_CHECKSUM_TYPES_SERVER = SHA1

 Comme précédemment après exécution de la requête sur le numéro de carte bleu de Winroc, on lit le fichier trace ou bien le fichier dump tcp :

[root@client trace]# vi ora_13600_140219188655872.trc
2016-01-03 23:14:18.285714 : nspsend:00 00 9D F6 44 86 73 29  |....D.s)|
2016-01-03 23:14:18.285735 : nspsend:6C A1 D7 80 FC DE F4 D0  |l.......|
2016-01-03 23:14:18.285756 : nspsend:30 E2 8F 14 06 62 58 2C  |0....bX,|
2016-01-03 23:14:18.285777 : nspsend:97 50 CA 77 34 45 74 B3  |.P.w4Et.|
2016-01-03 23:14:18.285798 : nspsend:10 4A 34 F4 12 A8 DF 5E  |.J4....^|

[root@client ~]# strings spy.res
[...]
lHX[
dOvGm
.J^F
pra@
jpQU
?ZD~Bi
]GGv
&9fG
0ZIf
+><qN>Q.
x<Vk[
#8B3aP-
jx!S
Q|uk
[...]

... il faudra trouver un autre moyen pour récupérer des numéros de cartes bleus.

Maintenant il faut bien comprendre que cela va avoir un impact sur le CPU client et serveur en cas de chiffrement. Chaque donnée envoyée ou reçue doit être chiffrée ou déchiffrée, comme la base de données est centrale c’est le serveur l’hébergeant sur lequel l’impact sera le plus fort. Il est difficile de donnée un pourcentage de surcharge. Suivant l’activité sur la base et le type de chiffrage on peut observer une perte de performance en pic de 10% voire de 15%. Mais j’ai déjà observé sur certain système pratiquement aucune perte de performance en temps de réponse mais en revanche une hausse de l’utilisation du CPU du serveur… tant qu’il ne sature pas tout va bien !

Comme le paramétrage peut ne se faire que sur le client, on peut très bien mettre uniquement le chiffrage sur un client dont transiteraient des données sensibles ou bien entre un client « en milieu hostile » et la base de données.  Cela peut permettre de contenir la consommation de CPU. Mais si le chiffrage doit se faire sur tous les flux client-serveur pour une base de données qui est elle-même sur un serveur hébergeant d’autres bases de données qui n’ont pas besoin d’avoir leur flux chiffré… alors il va falloir faire attention à la configuration sqlnet.

 

Les variables dans le sqlnet.ora ne peuvent pas être rattachées à une base de données en particulier. Il faut donc créer plusieurs sqlnet.ora si on veut différent paramétrage, et donc plusieurs répertoires. Mais comment indiquer à la base le sqlnet.ora qu’elle doit prendre en compte si il n’est plus dans $ORACLE_HOME/network/admin ? En fait à chaque démarrage, une instance prend en compte la valeur de la variable d’environnement TNS_ADMIN qui indique entre autre le chemin pour le sqlnet.ora. Et si celle-ci est nulle alors l’instance prendra comme valeur pour cette variable $ORACLE_HOME/network/admin. Il suffit donc tout simplement de créer des répertoires différents avec dans chaque un sqlnet.ora spécifique pour faire par exemple cohabiter deux instances avec chacun ses propres règles pour l’encryption sqlnet, et de bien prendre le bon environnement au démarrage de la base : implémentation dans un script par exemple.

$ export TNS_ADMIN=/$ORACLE_HOME/network/admin/sql_encrypt
$ export ORACLE_SID=SENSIBLEDB
$ sqlplus / as sysdba
SQL> startup

Et

$ export TNS_ADMIN=/$ORACLE_HOME/network/admin/sql_nocrypt
$ export ORACLE_SID=COOLDB
$ sqlplus / as sysdba
SQL> startup

A adapter donc dans les scripts de démarrage. Oui mais dans le cas d’un RAC, comment peut-on  gérer cela ? Assez facilement, il faut passer par l’utilitaire srvctl comme dans les exemples ci-dessous :

En 11g

[oracle]/home/oracle> srvctl setenv database -d TESTDB -T "TNS_ADMIN=$ORACLE_HOME/network/admin/crypt_tns"
[oracle]/home/oracle> srvctl getenv database -d TESTDB
TESTDB:
TNS_ADMIN=/oracle/11.2.0.4/database/network/admin/crypt_tns

En 12c

[oracle]/home/oracle> srvctl setenv database -db TESTDB -envs "TNS_ADMIN=$ORACLE_HOME/network/admin/crypt_tns"
[oracle]/home/oracle> srvctl getenv database -db TESTDB
TESTDB:
TNS_ADMIN=/oracle/12.1.0.2/database/network/admin/crypt_tns

Dans les exemples ci-dessus, le fichier sqlnet.ora avec les options de chiffrement sqlnet a été mis dans $ORACLE_HOME/network/admin/crypt_tns  (penser aussi à mettre le tnsnames.ora si la base doit l’utiliser). Au prochain redémarrage de la base de données TESTDB, les instances prendront donc le sqlnet.ora se trouvant dans $ORACLE_HOME/network/admin/crypt_tns alors que les autres bases de données continueront d’utiliser celui par défaut dans $ORACLE_HOME/network/admin.

 

Alors maintenant, pour vérifier que le chiffrage est bien en place, vous n’avez pas forcément envie de faire du tcpdump ou debug de couche sqlnet, alors il y a la vue gv$session_connect_info qui permet de voir les banners des sessions (colonne NETWORK_SERVICE_BANNER).  Il y a plusieurs façons de requêter suivant ce que l’on veut voir ou ne pas voir. Ci-dessous une requête trouvée sur le net (elle n’est pas de moi), qui permet de lister uniquement les sessions connectées avec chiffrement et l’algorithme activé. Elle ne permet pas de voir les sessions non chiffrées, les sessions bequeath ni les sessions tcps.

SQL> col ENCRYPTION_TYPE for A20
SQL> with sessions as
(
select /*+ MATERIALIZE */ inst_id, username, sid, serial#
from gv$session
where not ( type = 'BACKGROUND' or username is NULL )
),
session_connect_info as
(
select /*+ MATERIALIZE */ inst_id, sid, serial#,
    regexp_replace( network_service_banner,'^Oracle Advanced Security: ([[:alnum:]]+) encryption service adapter.+$','\1') encryption_type
from gv$session_connect_info
where network_service_banner like 'Oracle Advanced Security:%encryption service adapter%'
)
select s.inst_id, s.sid, s.serial#, s.username, sci.encryption_type
from sessions s
join session_connect_info sci on ( sci.inst_id = s.inst_id and
                                         sci.sid     = s.sid     and
                                         sci.serial# = s.serial#
                                        )
order by s.sid;

  INST_ID      SID     SERIAL# USERNAME            ENCRYPTION_TYPE
---------- ---------- ---------- ------------------------------ --------------------
     1       16           1 SYS                AES256
     1       24          11 SYS                AES256
     1       27           3 SYSTEM             AES256
     1      148           1 SYS                AES256

Remarque : attention si vous êtes en 11.2.0.3, cette requête peut bugger, dans certain cas la vue gv$session_connect_info affiche des valeurs négatives pour sid et serial# et donc la jointure avec gv$session ne fonctionnera pas. (bug oracle 14377082).

 

Voilà, voilà… rien de bien compliqué, vous savez à peu près tout sur l’encryption sqlnet… complétement démystifié Wink.