Introduction
Vous connaissez l'expression OUT OF THE BOX? C'est pour dire qu'il faut parfois s'extraire des limites logiques d'un système et penser en dehors de celui-ci, penser en dehors de la boîte.

Avec Oracle, si je veux voir des données d'une table, je me connecte et, si j'ai les droits, je fais un SELECT sur cette table. Si je n'ai pas les droits, c'est cuit... HE BEN NON, si on pense OUT OF THE BOX (la boîte étant Oracle), on peut tenter notre chance sous Unix avec la commande STRINGS. Via celle-ci on peut voir directement les chaînes de caractères dans des fichiers binaires, s'ils sont non cryptés. Comme faille de sécurité, c'est quand même énorme, on est d'accord?

Cela n'a l'air de rien mais on peut voir non seulement les données d'une table sans se connecter à Oracle mais aussi l'historique de cette table sans utiliser les commandes FLASHBACK... Avec cette commande, on peut récupérer des adresses mail mais aussi des numéros de carte bancaire en filtrant l'affichage pour ne garder qu'une suite de 16 chiffres (au cas où le numéro de cb soit stockée dans un champ de type CHAR ou VARCHAR2).


 
Points d'attention
Aucun.



Base de tests
N'importe quelle base Oracle.



Exemples
============================================================================================
Forcer Oracle à écrire des données sur disque dur : flusher le buffer cache
============================================================================================

On crée d'abord un tablespace dédié à notre table de tests et on crée la table dans ce tablespace, pour qu'elle soit associée à un datafile précis.
     SQL> CREATE TABLESPACE TEST_STRINGS DATAFILE '/u01/app/oracle/oradata/orcl12c/orcl/test_strings.dbf' size 10m EXTENT MANAGEMENT LOCAL AUTOALLOCATE SEGMENT SPACE MANAGEMENT AUTO;
     SQL> alter user HR quota unlimited on TEST_STRINGS;
     SQL> CREATE TABLE HR.ZZTEST_STRINGS(ID VARCHAR2(20 CHAR)) tablespace test_strings;
     SQL> select tablespace_name from dba_tables where table_name = 'ZZTEST_STRINGS';
     TABLESPACE_NAME
     ------------------------------
     TEST_STRINGS
     
Les commandes STRINGS seront lancées dans un autre terminal que celui des commandes SQL pour plus de souplesse.
D'abord, on se déplace dans le répertoire où se trouvent les fichiers de données de notre base.
     [oracle@vbgeneric orcl]$ cd /u01/app/oracle/oradata/orcl12c/orcl
     
     [oracle@vbgeneric orcl]$ ls -l
     total 2156408
     -rw-r----- 1 oracle oinstall    7938048 Sep 16 06:02 APEX_1991375173370654.dbf
     -rw-r----- 1 oracle oinstall    2695168 Sep 16 06:02 APEX_1993195660370985.dbf
     -rw-r----- 1 oracle oinstall 1247813632 Sep 16 06:02 sysaux01.dbf
     -rw-r----- 1 oracle oinstall  367009792 Sep 16 06:02 system01.dbf
     -rw-r----- 1 oracle oinstall   67117056 Sep 16 05:57 temp01.dbf
     -rw-r----- 1 oracle oinstall   10493952 Sep 16 06:02 test_strings.dbf
     -rw-r----- 1 oracle oinstall  482353152 Sep 16 06:02 undotbs01.dbf
     -rw-r----- 1 oracle oinstall   81272832 Sep 16 06:02 users01.dbf
     [oracle@vbgeneric orcl]$
     
On insère une donnée dans la table : rien dans le fichier car DBWR n'a pas encore écrit sur le disque dur. A noter que deux chaînes de caractères sont déjà là : -ORCL12C et TEST_STRINGS, soit le nom de l'instance (je suis en 12c CDB, le nom de ma base est orcl mais l'instance est orcl12c) et le nom du tablespace. Noter aussi le trait d'union devant ORCL12C.
     [oracle@vbgeneric orcl]$ sqlplus HR/HR@orcl
     SQL> insert into ZZTEST_STRINGS values('TESTZZ01');
     
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     
Le COMMIT ne change rien puisque DBWR ne s'exécute pas lors de celui-ci, seul LGWR est appelé.
     SQL> commit;
     Commit complete.
     
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     
Alors comment forcer Oracle a écrire dans un datafile? La solution miracle consiste à flusher le database buffer cache!
     SQL> alter system flush BUFFER_CACHE;

A quoi correspond le AAAAAAAA du fichier? Mystère!   
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TESTZZ01


============================================================================================
Impossible de vider le datafile des anciennes données de la table
============================================================================================

Je vais essayer de supprimer dans ce datafile les données de la table mais c'est bien plus difficile que je ne pensais.
Test du ROLLBACK : tiens, l'INSERT annulé reste dans le fichier, même après le ROLLBACK!
     SQL> insert into ZZTEST_STRINGS values('TESTZZ02');
     SQL> select * from zztest_strings;
     ID
     --------------------
     TESTZZ01
     TESTZZ02
     
     SQL> alter system flush BUFFER_CACHE;
     SQL> rollback;
     SQL> alter system flush BUFFER_CACHE;
     SQL> select * from zztest_strings;
     ID
     --------------------
     TESTZZ01
     
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TESTZZ02,
     TESTZZ01
     
Alors, comment forcer Oracle à nettoyer le fichier? Avec un arrêt/relance? Non, ça ne marche toujours pas.
     SQL> connect SYS/oracle@orcl as sysdba
     SQL> shutdown immediate;
     Pluggable Database closed.
     SQL> startup
     Pluggable Database opened.
     
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TESTZZ02,
     TESTZZ01
     
Visiblement les données insérées doivent être marquées par Oracle dans le fichier comme annulées mais on n'a pas vraiment cette info avec STRINGS.
Testons maintenant le DELETE. Echec encore : le fichier n'est pas nettoyé.
     SQL> delete from HR.ZZTEST_STRINGS;
     SQL> commit;
     SQL> select * from HR.ZZTEST_STRINGS;
     no rows selected
     
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TESTZZ02,
     TESTZZ01
     
     SQL> alter system flush buffer_cache;
     System altered.
     
Bizarre, il y a un caractère < après les données éliminées, à l'exception toutefois de la dernière ligne.
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TESTZZ02<
     TESTZZ01
     
Si on visualise le fichier avec vi, c'est plus précis : un caractère < devant chaque enregistrement supprimé et la suite de caractères <^B^A^.
     [oracle@vbgeneric orcl]$ vi test_strings.dbf
     <^B^A^HTESTZZ02<^B^A^HTESTZZ01^A^F<
    
Quid du TRUNCATE : cela doit ramener le HWM à 0, la table n'a plus de bloc mais qui dit que cela met à jour le fichier?
     SQL> truncate table HR.ZZTEST_STRINGS;
     
Comme prévu, cela n'a pas d'impact sur le contenu du datafile.
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TESTZZ02<
     TESTZZ01

Pour poursuivre mes tests, je supprime le tablespace puis le datafile sous Unix (il est impossible avec Oracle de supprimer l'unique fichier d'un tablespace); ensuite je les recrée à l'identique.
     SQL> drop tablespace test_strings including contents;
     [oracle@vbgeneric orcl]$ rm test_strings.dbf
     
     SQL> CREATE TABLESPACE TEST_STRINGS DATAFILE '/u01/app/oracle/oradata/orcl12c/orcl/test_strings.dbf' size 10m EXTENT MANAGEMENT LOCAL AUTOALLOCATE SEGMENT SPACE MANAGEMENT AUTO;
     SQL> CREATE TABLE HR.ZZTEST_STRINGS(ID VARCHAR2(20 CHAR)) tablespace test_strings;

Saturer le datafile    
Je veux voir maintenant s'il est possible d'effacer les anciennes données du fichier en remplissant celui-ci avec de nouvelles données.
     SQL> insert into HR.ZZTEST_STRINGS values('TESTZZ01');
     SQL> select * from HR.ZZTEST_STRINGS;
     ID
     --------------------
     TESTZZ01

     SQL> commit;
     SQL> alter system flush buffer_cache;
     
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TESTZZ01
     
Mon objectif est de voir maintenant si, suite à un  DELETE, la donnée TESTZZ01 est bien effacée du fichier.
     SQL> delete from HR.ZZTEST_STRINGS;
     SQL> commit;
     SQL> select * from HR.ZZTEST_STRINGS;
     no rows selected
     
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TESTZZ01


Normalement Oracle réutilise l'espace des données supprimées dans les blocs.
D'abord, suite à mon CREATE TABLE, Oracle a créé 8 blocs de données. Je n'avais qu'une donnée dans ma table, "TESTZZ01", alors si je fais des INSERTs, Oracle va insérer les données dans les blocs au max avant de réutiliser l'espace deleté ou bien il utilise celui-ci tout de suite?
     SQL> select BYTES, BLOCKS from dba_extents where SEGMENT_NAME =  'ZZTEST_STRINGS';
          BYTES     BLOCKS
     ---------- ----------
          65536        8
     
Je vais exécuter N boucles pour remplir le tablespace.    
     SQL> BEGIN
         FOR i IN 1..100 LOOP
             insert into HR.ZZTEST_STRINGS values('DELETE' || to_char(i));    
         END LOOP;
     END;
     /    
     PL/SQL procedure successfully completed.
     
     SQL> select count(*) from hr.zztest_strings;
       COUNT(*)
     ----------
            100
         
     SQL> commit;
     SQL> alter system flush buffer_cache;
     
Intéressant, le "TESTZZ01" est toujours là mais avec un < après la valeur précédente insérée, comme si ce < était un signe pour dire "Après, ce sont des valeurs délétées". Pour gagner de la place, j'efface les lignes entre DELETE99 et DELETE5.
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
         DELETE100,
     DELETE99,
     ...
     DELETE5,
     DELETE4,
     DELETE3,
     DELETE2,
     DELETE1<
     TESTZZ01
     [oracle@vbgeneric orcl]$
     
On recommence, avec plus de données!    
     SQL> BEGIN
         FOR i IN 101..100000 LOOP
             insert into HR.ZZTEST_STRINGS values('DELETE' || to_char(i));    
         END LOOP;
     END;
     /    
     PL/SQL procedure successfully completed.
     
     SQL> commit;
     SQL> alter system flush buffer_cache;
     
Aïe, test KO... Oracle n'a visiblement pas réutilisé l'emplacement de l'ancienne valeur.
     [oracle@vbgeneric orcl]$ strings test_strings.dbf | grep -i TESTZZ01
     TESTZZ01
     [oracle@vbgeneric orcl]$
     
On va essayer d'aller au max du tablespace : 10 Mo avec 1 million d'enregistrements.
     SQL> BEGIN
         FOR i IN 100001..1000000 LOOP
             insert into HR.ZZTEST_STRINGS values('DELETE' || to_char(i));    
         END LOOP;
     END;
     /
     BEGIN
     *
     ERROR at line 1:
     ORA-01653: unable to extend table HR.ZZTEST_STRINGS by 128 in tablespace TEST_STRINGS
     ORA-06512: at line 3
     
     [oracle@vbgeneric orcl]$ strings test_strings.dbf | grep -i TESTZZ01
     TESTZZ01
     
Essayons avec moins de données.
     SQL> BEGIN
         FOR i IN 100001..200000 LOOP
             insert into HR.ZZTEST_STRINGS values('DELETE' || to_char(i));    
         END LOOP;
     END;
     /  
     PL/SQL procedure successfully completed.
     
     SQL> /
     SQL> /
     BEGIN
     *
     ERROR at line 1:
     ORA-01653: unable to extend table HR.ZZTEST_STRINGS by 128 in tablespace TEST_STRINGS
     ORA-06512: at line 3
     
Après de multiples INSERTs, j'arrive enfin à ne plus pouvoir en insérer ne serait-ce qu'un seul.
     SQL> insert into hr.zztest_strings select * from hr.zztest_strings where rownum < 2
                    *
     ERROR at line 1:
     ORA-01653: unable to extend table HR.ZZTEST_STRINGS by 128 in tablespace TEST_STRINGS
     
     SQL> exec dbms_stats.gather_table_stats('HR', 'ZZTEST_STRINGS');
            
     SQL> select sum(BYTES), sum(BLOCKS) from dba_extents where SEGMENT_NAME =  'ZZTEST_STRINGS' group by segment_name;
     SUM(BYTES) SUM(BLOCKS)
     ---------- -----------
        9437184      1152

     [oracle@vbgeneric orcl]$ strings test_strings.dbf | wc -l
     516668

     

     SQL> select pct_free from dba_tables where table_name = 'ZZTEST_STRINGS';
       PCT_FREE
     ----------
         10
     
Je mets le PCTFREE à 0, mais ça ne change rien, plus d'insertion possible.    
     SQL> alter table HR.ZZTEST_STRINGS PCTFREE 0;
     
     SQL> insert into hr.zztest_strings select * from hr.zztest_strings where rownum < 2;    
     insert into hr.zztest_strings select * from hr.zztest_strings where rownum < 2
                    *
     ERROR at line 1:
     ORA-01653: unable to extend table HR.ZZTEST_STRINGS by 128 in tablespace TEST_STRINGS

     SQL> select count(*) from HR.ZZTEST_STRINGS;
       COUNT(*)
     ----------
         467796
    
Essayons de shrinker l'espace de la table pour la réorganiser et voir si cela me donne de l'espace.
     SQL> alter table HR.ZZTEST_STRINGS enable row movement;
     SQL> alter table HR.ZZTEST_STRINGS shrink space;
 
Ben non, test KO : impossible de faire un nouvel INSERT.
     SQL> insert into hr.zztest_strings select * from hr.zztest_strings where rownum < 2;
     insert into hr.zztest_strings select * from hr.zztest_strings where rownum < 2
                    *
     ERROR at line 1:
     ORA-01653: unable to extend table HR.ZZTEST_STRINGS by 128 in tablespace TEST_STRINGS
     
     SQL> alter system flush buffer_cache;
     
Et la donnée supprimée est toujours présente dans le fichier! Incroyable!!!!!
     [oracle@vbgeneric orcl]$ strings test_strings.dbf | grep -i TESTZZ01
     TESTZZ01
 

============================================================================================
Accès à l'historique des données sur disque dur suite à des UPDATEs
============================================================================================

Nous allons maintenant pousser un peu plus loin nos tests et voir que Oracle nous donne même accès à l'historique des modifications d'une donnée, et ce sans passer par la techno FLASHBACK.

Je recrée la même table mais avec un nombre de colonnes différent, pour simplifier la commande UPDATE.
     SQL> connect HR/HR@orcl;    
     SQL> CREATE TABLESPACE TEST_STRINGS DATAFILE '/u01/app/oracle/oradata/orcl12c/orcl/test_strings.dbf' size 10m EXTENT MANAGEMENT LOCAL AUTOALLOCATE SEGMENT SPACE MANAGEMENT AUTO;
     SQL> CREATE TABLE ZZTEST_STRINGS(ID NUMBER, COMMENT_TEST VARCHAR2(20 CHAR)) tablespace test_strings;    
     
     SQL> insert into ZZTEST_STRINGS values(1, 'TEST1');
     SQL> insert into ZZTEST_STRINGS values(2, 'TEST2')
     SQL> insert into ZZTEST_STRINGS values(3, 'TEST3')
     SQL> commit;
     
     SQL> alter system flush BUFFER_CACHE;
     
Euh, j'ai les VARCHAR2 mais pas les nombres... Ah oui, STRINGS ne gère que les chaînes de caractères :-)
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TEST3,
     TEST2,
     TEST1
     [oracle@vbgeneric orcl]$


Voyons maintenant ce qui se passe avec un UPDATE.
     SQL> UPDATE ZZTEST_STRINGS set COMMENT_TEST = 'TEST1_UPDATE' where id = 1;
     SQL> commit;
     SQL> alter system flush BUFFER_CACHE;
     
Bingo, j'en étais sur, on voit l'historique de nos modifications :-)
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TEST1_UPDATE,
     TEST3,
     TEST2,
     TEST1
     
Allez, encore une fois! Et même résultat : on a l'historique des données modifiées via cette commande!
     SQL> UPDATE ZZTEST_STRINGS set COMMENT_TEST = 'TEST1_UPDATE_ENCORE' where id = 1;
     SQL> commit;
     SQL> alter system flush BUFFER_CACHE;
     
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TEST1_UPDATE_ENCORE,
     TEST1_UPDATE,
     TEST3,
     TEST2,
     TEST1


Bon, arrêtons là, preuve est faite que sous Unix et avec la commande STRINGS on peut lire les données d'une base sans être connecté à celle-ci et même lire des données d'une table supprimée et, plus fort, de voir son historique; attention, cela ne concerne que les données de type chaîne de caractères. Cela devrait vous convaincre de crypter vos données sensibles, non?