POSIX wordexp(3) is dangerous
POSIX wordexp(3)는 위험합니다
C의 POSIX wordexp(3)는 기본 설정에서 셸 명령 치환까지 실행하며, WRDE_NOCMD를 지정해도 일부 libc 구현에서는 우회 가능성이 있다고 설명합니다. 글은 신뢰할 수 없는 입력에 wordexp(3)를 사용하지 말라고 권고합니다.
- 주제
에디터 노트
WRDE_NOCMD가 있다고 해서 wordexp(3)를 신뢰할 수 없는 입력에 안전하게 쓸 수 있다는 보장은 없습니다. 글은 musl의 검사와 셸 처리 사이에서 생기는 우회 가능성을 지적하며, macOS 사례는 2023년 보고를 인용하고 현재 상태는 확인하지 않았다고 선을 긋습니다. 댓글은 여기에 glibc의 환경 변수 변경과 멀티스레드 제약을 보탭니다. 반면 i3 설정처럼 입력 자체를 신뢰하는 환경에서는 명령 치환이나 glob 확장이 유용하다는 반응도 있습니다. 쟁점은 wordexp 자체를 일률적으로 금지하는 데 있지 않고, 어떤 입력을 허용하는지와 어떤 확장이 필요한지를 API가 명확히 구분하지 못한다는 데 있습니다. 제한된 확장만 제공하는 별도 API가 낫다는 제안은 이 문제를 직접 겨냥합니다.
AI 요약
C의 POSIX API인 wordexp(3)는 셸 방식의 확장을 수행합니다. 예를 들어 ~/foo를 홈 디렉터리 경로로 바꾸는 용도로 권장되기도 하지만, 기본 플래그 값인 0에서는 명령 치환을 포함한 모든 확장 기능을 켭니다. 입력에 명령 치환 구문이 있으면 셸 명령이 실행될 수 있습니다.
WRDE_NOCMD도 충분한 방어가 아닙니다
명령 치환을 막는 플래그 WRDE_NOCMD가 있지만, 글은 일부 구현에서 이를 우회할 수 있다고 지적합니다. musl은 시스템 셸에 처리를 맡기기 전에 명령 치환 패턴을 검사하지만, 셸이 유효한 명령 치환으로 받아들이는 일부 형태를 놓칩니다. macOS libc도 /usr/lib/system/wordexp-helper에 처리를 맡기는 방식이며, Julian Squires에 따르면 2023년에도 musl과 같은 우회가 가능했습니다. 글쓴이는 현재 macOS에서 같은 문제가 남아 있는지는 시험하지 않았다고 덧붙입니다.
따라서 글의 권고는 신뢰할 수 없는 입력에 WRDE_NOCMD를 지정하더라도 wordexp(3)를 쓰지 말라는 것입니다.
Lobsters 반응
- @andyc — libc에 이 함수가 왜 있는지 모르겠습니다. 셸이 기능 구현에 쓰는 것도 아닙니다. 틸드 확장은 훨씬 간단한 알고리즘으로 구현할 수 있습니다. 셸을 포크해서 실행한다면 정말 위험합니다. 셸도 이 함수를 쓰기 어렵게 됩니다. 명령 치환 안에 셸 언어 전체가 들어갈 수 있으니 사양 자체가 셸 전체를 요구하는 셈입니다. libc에서 빼야 할 것 같습니다.
- @yossarian — 셸 렉서 전체나 C 렉서 전체가 있다고 전제하는 POSIX API가 몇 가지 있습니다. 하지만 셸이나 C 렉서를 libc에서 재사용할 수 있게 만든 POSIX 시스템은 거의 없어서 이런 기괴한 우회 구현이 나옵니다. 링크한 글에는 glob에 기능을 계속 추가해 달라는 요구 끝에 모든 경우를 포괄하려고 지나치게 넓은 wordexp를 표준에 넣었다는 설명이 있습니다.
- @seb — 참고로 glibc는 명령 치환에는 시스템 셸을 쓰지만 다른 단어 확장 로직은 자체 구현합니다.
- @fanf — wordexp(3)는 대개 셸을 포크하므로 멀티스레드 프로그램에서 쓰기 어렵습니다. glibc 구현은 셸 확장에서 변수를 설정할 때 setenv()를 호출하는데, setenv()는 스레드 안전하지 않고 호출한 프로세스의 환경도 바꿉니다.
- @seb — Hare 표준 라이브러리에 wordexp 모듈을 넣을지 검토했을 때, 신뢰할 수 없는 입력에 안전하게 쓸 수 없고 구현도 사양을 제대로 따르기 어렵다고 판단했습니다. Hare는 POSIX를 따르지 않고 시스템 셸을 사용하며 WRDE_NOCMD도 제공하지 않습니다. 문서에는 사용자 입력으로 임의의 셸 명령을 실행하므로 신뢰할 수 있는 환경에서만 쓰라고 명시합니다.
- @andyc — 구현체마다 파서가 다르면 한쪽은 허용하고 다른 쪽은 놓치는 파서 차이(parser differential)가 생깁니다. 셸처럼 파싱이 어려운 언어를 불완전한 별도 파서로 처리하면 이런 문제가 반복됩니다.
- @stapelberg — i3의 설정 파일 include 지시문은 wordexp를 씁니다. 사용자 가이드에는
~/.config/i3/config.d/*.conf같은 와일드카드 확장과 명령 치환 예시가 있습니다.- @quasi_qua_quasi — 설정 파일을 쓸 수 있는 공격자가 이미 프로그램을 장악할 수 있는 환경이라면 이런 사용은 괜찮습니다. 문제는 틸드나 변수, glob만 확장하려는 경우에도 안전 기능을 제대로 구현하기 어렵다는 점입니다. 필요한 확장만 선택하는 별도 API가 있으면 좋겠습니다.
원문: yossarian.net / 번역·요약: Trawling