Diviser par zéro dans ORDER BY CLAUSE
Je viens de recevoir un résultat PEN-Test d'une application Web. Le testeur a déclaré qu'il était en mesure d'utiliser la clause ORDER BY d'une instruction SQL dynamique pour récupérer des informations. Le nom de la colonne à trier est passé dans la requête par une application. Le testeur a modifié la valeur pour qu'elle contienne une instruction complexe qui ressemble à CASE WHEN ... THEN 1 ELSE 1/0 END
.
Cependant, j'ai construit un cas de test très simple. J'ai une base de données Oracle et un serveur SQL. Les deux contiennent les mêmes tableaux. Je questionne
SELECT * FROM users ORDER BY 1/0
Lorsque j'exécute cela dans Oracle, la requête s'exécute correctement. Dans SQLServer, j'obtiens une erreur 8134 Divide by zero error encountered.
.
Comme le PEN-Tester utilisait l'application sur un serveur Oracle différent de celui que j'utilise actuellement et qu'ils signalent qu'ils abusaient du fait qu'Oracle a finalement généré des erreurs, je me demande s'il existe un paramètre Oracle qui empêche l'exécution des clauses ORDER BY qui évalue à une division par zéro. Existe-t-il un tel cadre? S'agit-il de la version d'Oracle utilisée? Je suis 12.1.0.2.0
là où aucune exception n'est lancée. Existe-t-il également un tel paramètre dans SQLServer?
Je vais bien sûr modifier la génération de l'instruction, afin qu'elle vérifie si les valeurs qui sont passées sont des alias de colonne disponibles pour le tri.
Réponses
Oracle:
order by 1/0
réussit, car cette clause seule n'a pas de sens, l'optimiseur l'élimine automatiquement de la requête au moment de l'analyse et elle n'est jamais exécutée.
SQL> select username from t1 where username like 'SYS%' order by 1/0;
USERNAME
--------------------------------------------------------------
SYS
SYSTEM
SYS$UMF
SYSBACKUP
SYSRAC
SYSKM
SYSDG
7 rows selected.
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------
SQL_ID cnnmg28k0vspg, child number 0
-------------------------------------
select username from t1 where username like 'SYS%' order by 1/0
Plan hash value: 3617692013
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 3 (100)| |
|* 1 | TABLE ACCESS FULL| T1 | 1 | 9 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("USERNAME" LIKE 'SYS%')
Aucun tri n'a été effectué.
Ajoutez quelque chose d'autre et cela échouera:
SQL> select username from t1 where username like 'SYS%' order by 1/0, 1;
select username from t1 where username like 'SYS%' order by 1/0, 1
*
ERROR at line 1:
ORA-01476: divisor is equal to zero
SQL>
Ou:
SQL> select username from t1 where username like 'SYS%' order by 1/0;
USERNAME
--------------------------------------------------------------------------------
SYS
SYSTEM
SYS$UMF
SYSBACKUP
SYSRAC
SYSKM
SYSDG
7 rows selected.
SQL> select /*+ opt_param('_optimizer_order_by_elimination_enabled', 'false') */ username from t1 where username like 'SYS%' order by 1/0;
select /*+ opt_param('_optimizer_order_by_elimination_enabled', 'false') */ username from t1 where username like 'SYS%' order by 1/0
*
ERROR at line 1:
ORA-01476: divisor is equal to zero
SQL>
Aussi si la valeur n'est pas fixée au moment de l'analyse (par exemple, il s'agit d'une variable):
SQL> variable B1 number
SQL> exec :B1 := 0;
PL/SQL procedure successfully completed.
SQL> select username from t1 where username like 'SYS%' order by 1/:B1;
select username from t1 where username like 'SYS%' order by 1/:B1
*
ERROR at line 1:
ORA-01476: divisor is equal to zero
SQL>
Pour plus 1/0
précisément, vous pouvez éviter l'erreur en exécutant les SET
instructions suivantes (notez que je ne recommande pas cela comme «solution» à votre problème, car la désactivation de ces paramètres peut causer beaucoup de confusion et masquer des erreurs importantes):
SET ANSI_WARNINGS, ARITHABORT OFF;
La ORDER BY
clause prend en charge la spécification d'une colonne dans la liste de sélection par sa position ordinale à trier. En d'autres termes, " ORDER BY 1
" signifie commander selon le premier élément de la liste de sélection.
Cet exemple utilise l'exemple de base de données "AdventureWorks" de Microsoft:
SELECT p.BusinessEntityID, p.FirstName
FROM Person.Person p
ORDER BY 2;

Cependant, SQL Server ne prend pas en charge les expressions constantes :
SELECT p.BusinessEntityID, p.FirstName
FROM Person.Person p
ORDER BY 2-1;
Msg 408, niveau 16, état 1, ligne 18
Une expression constante a été rencontrée dans la liste ORDER BY, position 1.
Dans votre cas, 1/0
est une expression constante. Cependant, étant donné que le calcul qui entraînerait une erreur, SQL Server le traite différemment. Vous pouvez lire à ce sujet dans l'article d'Itzik Ben Gan Numéros de ligne avec un ordre non déterministe :
Ce qui se passe, c'est que d'une part, SQL Server ne parvient pas à appliquer le repliement constant et, par conséquent, l'ordre est basé sur une expression qui n'est pas une seule constante. D'un autre côté, l'optimiseur estime que la valeur de classement est la même pour toutes les lignes, donc il ignore complètement l'expression de classement.
Vous pouvez le voir dans le plan d'exécution si nous exécutons la 1/0
version de la requête avec ces deux paramètres désactivés :
SET ANSI_WARNINGS, ARITHABORT OFF;
GO
SET STATISTICS XML ON;
GO
SELECT p.BusinessEntityID, p.FirstName
FROM Person.Person p
ORDER BY 1/0;

Dans ce cas, vous pouvez voir qu'il n'y a pas d'opération de tri. Compute Scalar tente de calculer 1/0
, mais échoue. Étant donné que ces deux paramètres sont désactivés, l'erreur «diviser par zéro» est supprimée afin que la requête se termine (avec un ordre de tri non déterministe).
Une meilleure solution pour le tri dynamique est présentée dans l'article d'Erland Sommarskog, Conditions de recherche dynamiques dans T-SQL . L'essentiel de cette solution consiste à utiliser une CASE
instruction pour transformer la colonne de tri d'entrée utilisateur en une valeur de colonne connue:
SELECT @sql += ' ORDER BY ' +
CASE @sortcol WHEN 'OrderID' THEN 'o.OrderID'
WHEN 'EmplyoeeID' THEN 'o.EmployeeID'
WHEN 'ProductID' THEN 'od.ProductID'
WHEN 'CustomerName' THEN 'c.CompanyName'
WHEN 'ProductName' THEN 'p.ProductName'
ELSE 'o.OrderID'
END + CASE @isdesc WHEN 0 THEN ' ASC' ELSE ' DESC' END
Cela empêche les valeurs inattendues d'affecter l'exécution des requêtes et contribue à la protection contre l'injection SQL.
Je ne sais pas à quel point cette approche est viable pour vous, car vous semblez essayer de maintenir la compatibilité entre plusieurs plates-formes de bases de données.