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
方法,obj
是self
複製得到的物件
-
跟進
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.
nameFROM
vuln_collectionORDER BY (``.id);select updatexml(1, concat(0x7e,(select database())),1)#) ASC
,因為這裡的QuerySet._query
是sql.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()方法
,可以看到fields
和expressions
都為空-
self_values(*fields, **expressions)
又是一次物件拷貝
-
-
跟進
clone._iterable_class = ValuesIterable
-
該語句執行後,return clone,返回的是可迭代物件,本身並沒有值,而且也並沒有執行SQL語句,當獲取可迭代物件的值時,才會執行
所以除錯時,需要進入到HttpResponse的
self.content = content
時才會觸發SQL注入 -
一直到
yield
語句,SQL語句執行
-
3 參考
- https://github.com/vulhub/vulhub/tree/master/django/CVE-2021-35042
- https://xz.aliyun.com/t/9834
- https://www.djangoproject.com/weblog/2021/jul/01/security-releases/
- https://www.freebuf.com/vuls/283262.html
- https://www.venustech.com.cn/new_type/aqtg/20210706/22850.html
- https://www.bugxss.com/vulnerability-report/3095.html
- https://nvd.nist.gov/vuln/detail/CVE-2021-35042