1. 程式人生 > 其它 >CVE-2021-35042 Django order_by SQL注入 漏洞復現和分析

CVE-2021-35042 Django order_by SQL注入 漏洞復現和分析

CVE-2021-35042 Django order_by SQL注入 漏洞復現和分析

目錄

0 簡介

在特定限制條件下,Django的order_by方法會導致SQL注入

  • 影響版本:3.1.x < 3.1.13, 3.2.x < 3.2.5

  • 條件:

    • Debug=True
    • 介面使用order_by方法
  • 復現/分析環境:

    • python 3.8.1
    • Django 3.2.4
    • MySQL 5.7.26

1 漏洞復現

order_by

的傳入引數中包含.時,可進行SQL注入,但需要正確的列名,可以通過猜測id, _id或輸入錯誤的列名,從報錯資訊中得到列名

如果傳入錯誤的列名,會因為列明不存在而產生Exception退出,進入不到執行SQL注入語句的部分

利用updatexml.id);select%20updatexml(1,%20concat(0x7e,(select%20database())),1)%23即可進行報錯注入

2 漏洞分析

Demo

# views.py 省略import
def vul(request):
    query = request.GET.get('order', default='id')
    q = Collection.objects.order_by(query)
	return HttpResponse(q.values())
    
    
# models.py view.py中呼叫的Collection定義
from django.db import models


class Collection(models.Model):
    name = models.CharField(max_length=128)

傳入poc,打斷點除錯:

  • 先經過初始化,建立了QuerySet例項,(db:資料庫,model:模型,ordered:是否已排序,query:sql語句)
  • 進入django.db.models.query line 1143 QuerySet.order_by方法,objself複製得到的物件
  • 跟進line 1149 add_ordering(poc)->line 1960 add_ordering()

  • 傳入的poc為字串,且其中包含.,所以直接continue跳出for迴圈(傳入的order_by引數只有一個,for迴圈只有一次),沒有進入到names_to_path

    方法,之後執行self.order_by += ordering

    引數ordering是我們傳入的poc

    此時的SQL語句是SELECT vuln_collection.id, vuln_collection.nameFROMvuln_collection ORDER BY (``.id);select updatexml(1, concat(0x7e,(select database())),1)#) ASC,因為這裡的QuerySet._querysql.Query例項化物件,會根據引數自動更新SQL語句

    def add_ordering(self, *ordering):
        errors = []
        for item in ordering:
            if isinstance(item, str):
                if '.' in item:
                    warnings.warn(
                        'Passing column raw column aliases to order_by() is '
                        'deprecated. Wrap %r in a RawSQL expression before '
                        'passing it to order_by().' % item,
                        category=RemovedInDjango40Warning,
                        stacklevel=3,
                    )
                    continue
                if item == '?':
                    continue
                if item.startswith('-'):
                    item = item[1:]
                if item in self.annotations:
                    continue
                if self.extra and item in self.extra:
                    continue
                # names_to_path() validates the lookup. A descriptive
                # FieldError will be raise if it's not.
                self.names_to_path(item.split(LOOKUP_SEP), self.model._meta)
            elif not hasattr(item, 'resolve_expression'):
                errors.append(item)
            if getattr(item, 'contains_aggregate', False):
                raise FieldError(
                    'Using an aggregate in order_by() without also including '
                    'it in annotate() is not allowed: %s' % item
                )
        if errors:
            raise FieldError('Invalid order_by arguments: %s' % errors)
        if ordering:
            self.order_by += ordering
        else:
            self.default_ordering = False

漏洞成因就是上面程式碼第13行的continue,修復方法也很簡單,去掉continue,確保每一個引數進入到names_to_path即可

names_to_path為引數合理性檢驗的方法,對order_by傳入的列名進行檢驗,以下為部分程式碼

  • 對於order_by傳入的每一個引數,通過model模型獲取對應欄位,如果欄位不存在,且不是註釋欄位,不是_filtered_relations(可用於join連線),則會報錯,並返回可選欄位
    def names_to_path(self, names, opts, allow_many=True, fail_on_missing=False):
        path, names_with_path = [], []
        for pos, name in enumerate(names):
            cur_names_with_path = (name, [])
            if name == 'pk':
                name = opts.pk.name

            field = None
            filtered_relation = None
            try:
                field = opts.get_field(name)
            except FieldDoesNotExist:
                if name in self.annotation_select:
                    field = self.annotation_select[name].output_field
                elif name in self._filtered_relations and pos == 0:
                    filtered_relation = self._filtered_relations[name]
                    if LOOKUP_SEP in filtered_relation.relation_name:
                        parts = filtered_relation.relation_name.split(LOOKUP_SEP)
                        filtered_relation_path, field, _, _ = self.names_to_path(
                            parts, opts, allow_many, fail_on_missing,
                        )
                        path.extend(filtered_relation_path[:-1])
                    else:
                        field = opts.get_field(filtered_relation.relation_name)
            if field is not None:
                if field.is_relation and not field.related_model:
                    raise FieldError(
                        "Field %r does not generate an automatic reverse "
                        "relation and therefore cannot be used for reverse "
                        "querying. If it is a GenericForeignKey, consider "
                        "adding a GenericRelation." % name
                    )
                try:
                    model = field.model._meta.concrete_model
                except AttributeError:
                    model = None
            else:
                pos -= 1
                if pos == -1 or fail_on_missing:
                    available = sorted([
                        *get_field_names_from_opts(opts),
                        *self.annotation_select,
                        *self._filtered_relations,
                    ])
                    raise FieldError("Cannot resolve keyword '%s' into field. "
                                     "Choices are: %s" % (name, ", ".join(available)))
                break

以下部分為後續程式碼執行過程,與漏洞成因無關

  • 返回到order_by方法,完成obj物件的封裝並返回 (上層還有manager_method()方法,通過反射來呼叫對應函式,直接略過了)
  • 執行完q = Collection.objects.order_by(query)即完成了QuerySet物件的封裝,之後的q.values()才會執行SQL語句
  • 繼續跟進django.db.models.query valuese()方法 ,可以看到fieldsexpressions都為空
    • self_values(*fields, **expressions)又是一次物件拷貝
  • 跟進clone._iterable_class = ValuesIterable

    • 該語句執行後,return clone,返回的是可迭代物件,本身並沒有值,而且也並沒有執行SQL語句,當獲取可迭代物件的值時,才會執行

      所以除錯時,需要進入到HttpResponse的self.content = content時才會觸發SQL注入

    • 一直到yield語句,SQL語句執行

3 參考