본문 바로가기

일상

톰캣(Tomcat) allowLinking 취약점 조치하면서 기능은 살리기 (심볼릭 링크 대신 전용 Context 매핑)

반응형

서버 취약점 점검에서 링크 사용 금지 지적을 받았는데 그 링크로 서비스가 돌아가고 있을 때, 원인을 확인하고 해결하는 방법입니다.


1. 문제: 지우면 취약점은 없어지는데 기능이 죽는다
톰캣(Tomcat) 설정에 allowLinking="true" 가 들어 있었습니다. 웹 루트 아래에 심볼릭 링크를 걸어 두고, 그 링크 너머의 외부 저장소 파일을 웹으로 서빙하려고 넣은 옵션입니다.


취약점 점검에서 이 설정이 두 항목으로 걸렸습니다. 상위 디렉터리 접근 제한(SRV-042) 과 링크 사용 금지(SRV-047) 입니다. 조치 자체는 한 줄 지우면 끝입니다. 문제는 그 한 줄이 실제 기능을 떠받치고 있다는 점입니다.


이런 상황에서 선택지는 보통 셋입니다. 지우고 기능을 포기하거나, 안 지우고 미조치로 남기거나, 허용 범위를 좁혀서 둘 다 살리거나. 세 번째로 갔습니다.


- allowLinking="true" 가 취약점으로 지적됨
- 그런데 그 옵션이 외부 저장소 이미지 서빙을 떠받치고 있음
- 지우기 전에 "무엇이 이 옵션에 의존하는지"부터 갈라야 함


2. 이 옵션이 왜 취약으로 판정되나
지우기 전에 왜 지적됐는지부터 이해해야 대체 방법을 고를 수 있습니다. 이유는 세 가지입니다.


첫째, 웹 루트 경계가 무력화됩니다. 톰캣은 docBase 아래만 공개한다는 전제로 동작하고, .. 같은 경로 이탈은 정규화 단계에서 막습니다. 그런데 심볼릭 링크는 OS 레벨에서 해석되므로 톰캣의 경로 검사를 통과한 뒤에 웹 루트 밖으로 연결됩니다. 컨테이너가 보장한다고 믿었던 격리 경계가 우회되는 겁니다.


둘째, 허용 범위가 링크 하나 단위가 아닙니다. 이 옵션은 특정 링크가 아니라 해당 컨텍스트 아래의 모든 심볼릭 링크를 대상으로 합니다. 나중에 누군가 링크를 하나 더 만들면 아무 검토 없이 즉시 웹으로 공개됩니다. 감사 관점에서 진짜 문제는 이쪽입니다. 통제 범위가 고정되지 않습니다.


셋째, 톰캣 공식 문서가 대소문자를 구분하지 않는 파일시스템에서는 WEB-INF/, META-INF/ 내용이 노출될 수 있다고 경고합니다. 리눅스처럼 대소문자를 구분하는 파일시스템이면 이 항목의 실제 위험은 낮지만, 앞의 둘은 파일시스템과 무관하게 성립합니다.


- 심볼릭 링크는 OS가 해석 -> 톰캣의 경로 검증을 지나서 밖으로 나감
- 링크 하나가 아니라 컨텍스트 전체가 대상 -> 이후 추가되는 링크도 자동 공개
- 대소문자 미구분 파일시스템에선 WEB-INF 노출 경고까지 있음


3. 링크의 역할부터 가릅니다
설정을 지우기 전에 확인한 건 "링크가 몇 개고 각각 무슨 일을 하나"였습니다. 여기가 이 작업의 핵심입니다.



웹 루트 아래 링크는 두 개였는데 성격이 완전히 달랐습니다.


하나는 브라우저가 URL로 직접 요청하는 이미지 경로였습니다. 애플리케이션이 화면에 이미지 URL을 찍어 주고 브라우저가 그 주소로 받아 갑니다. 즉 톰캣의 정적 리소스 서빙을 타므로 이 링크는 allowLinking 이 있어야 동작합니다.


다른 하나는 애플리케이션이 java.io 로 직접 읽고 쓰는 경로였습니다. URL로 서빙되지 않으므로 톰캣 설정과 아무 관계가 없습니다. 이건 옵션을 지워도 그대로 동작합니다.


이 구분을 안 하면 "링크 2개가 다 위험하다"거나 반대로 "지우면 다 죽는다"로 뭉뚱그리게 됩니다. 실제로 웹 서빙을 타는 링크만 대체 대상입니다.


- 링크가 URL로 서빙되는지, 앱이 파일로 읽는지부터 확인
- URL 서빙만 톰캣 설정에 의존 -> 대체 필요
- 파일 입출력은 OS 동작 -> 설정과 무관, 건드릴 필요 없음


4. 해결: 열린 허용을 닫힌 매핑으로 바꿉니다
allowLinking 을 지우고, 대신 그 경로만 전용 Context 로 명시 매핑했습니다.



컨텍스트 경로가 더 긴 쪽이 먼저 매칭되므로, 루트 컨텍스트보다 이쪽이 우선합니다. 결과적으로 해당 URL만 외부 저장소로 연결되고 나머지는 그대로입니다.


바뀐 건 기능이 아니라 허용의 성격입니다.


- 조치 전: 웹 루트 아래 모든 링크가 서빙됨 / 링크를 추가하면 자동으로 공개
- 조치 후: 지정한 한 경로만 연결됨 / 링크를 추가해도 서빙되지 않음
- 즉 범위가 열린 허용에서 범위가 닫힌 명시적 매핑으로 전환


5. 남는 리스크와 근본 해결
여기서 끝났다고 보고하면 안 됩니다. 이 방식에는 남는 게 셋 있습니다.


첫째, 디스크의 링크는 그대로 남습니다. 톰캣이 더 이상 따라가지 않으니 설정 점검에는 양호로 잡히지만, 점검이 "웹 루트 아래 심볼릭 링크의 물리적 존재"를 보는 방식이면 다시 지적될 수 있습니다.


둘째, 전용 Context 에는 애플리케이션의 인증 필터가 걸리지 않습니다. 이건 확인해 볼 사항이 아니라 구조상 그렇습니다. 서블릿 필터는 컨텍스트 단위로 적용되므로, 컨텍스트를 새로 만든 순간 그 안의 요청은 기존 앱의 필터 체인을 아예 타지 않습니다.


그래서 조치 전에 그 경로가 인증 대상이었다면 이번 변경은 그대로 회귀입니다. 판단 기준은 간단합니다. 앱의 인증 필터 url-pattern 이 그 파일을 덮고 있었는지 보면 됩니다. /* 였다면 회귀가 맞고, *.do *.jsp 처럼 확장자로 잡혀 있었다면 이미지 같은 정적 파일은 원래부터 필터를 안 탔던 것이라 조치 전후가 같습니다. 후자라면 회귀는 아니지만, 원래부터 비로그인으로 뚫려 있었다는 뜻이므로 그것대로 별건입니다.



고치려면 컨테이너 쪽 security-constraint 를 새 컨텍스트에 붙이는 방법이 먼저 떠오르는데, 이건 답이 아닙니다. 컨테이너 인증은 Realm 기반이라 애플리케이션의 세션 로그인과 별개 체계입니다. 로그인해 둔 세션을 인식하지 못합니다.


유효한 방법은 다운로드 서블릿 전환입니다. 이미지 URL을 애플리케이션이 처리하는 확장자로 받아서 java.io 로 읽어 스트리밍하면, 인증 필터가 정상적으로 걸립니다. 덤으로 정적 서빙 자체가 사라지므로 전용 Context 도 심볼릭 링크도 둘 다 필요 없어집니다.


셋째, 이건 결국 증상 대응입니다. 근본 원인은 애플리케이션 설정에서 파일 저장 경로가 웹 디렉터리 안쪽을 가리키고 있다는 점입니다. 실제 저장소는 밖에 있는데 경로만 안쪽이니, 그 지점에 링크가 필요했던 겁니다. 링크 하나가 "파일 입출력"과 "웹 서빙" 두 역할을 겸하는 구조가 이 옵션을 요구한 원인입니다.


저장 경로를 처음부터 외부 저장소의 절대경로로 잡으면 링크 두 개가 다 사라집니다. 웹 루트 안에 실데이터가 없어지므로 경로 분리 기준에도 같이 맞습니다. 다만 이건 애플리케이션 설정 변경이라 이번 조치 범위 밖이었고, 차기 개선 사항으로 분리해서 보고했습니다.


- 디스크 링크 잔존 -> 물리적 존재를 보는 점검이면 재지적 가능
- 별도 컨텍스트는 인증 필터가 구조상 미적용 -> 기존 필터의 url-pattern 부터 확인
- 컨테이너 security-constraint 는 대안이 아님(앱 세션과 별개 체계) -> 다운로드 서블릿 전환
- 근본 해결은 저장 경로를 웹 루트 밖 절대경로로 재설계


6. 정리
취약점 조치는 "설정을 지웠다"가 아니라 "그 설정에 뭐가 매달려 있었고, 그걸 어떻게 좁혔다" 까지 써야 조치입니다. 지우기만 하면 기능이 죽고, 안 지우면 미조치입니다. 중간에 범위를 좁히는 선택지가 거의 항상 있습니다.


그리고 조치 결과를 보고할 땐 아직 확인 못 한 것을 같이 적는 게 낫습니다. 이번에도 검증 환경에 외부 저장소가 붙어 있지 않아 이미지 표시 자체는 확인하지 못했고, 그건 그대로 적어서 운영 적용 시 확인 항목으로 넘겼습니다. 조용히 넘어가면 나중에 회귀가 났을 때 조치 자체가 의심받습니다.


- 지우기 전에 "무엇이 이 설정에 의존하나"부터 가를 것
- 열린 허용은 닫힌 매핑으로 좁히면 기능과 조치를 같이 살릴 수 있음
- 대체 방식이 만든 새 리스크(컨텍스트 분리 = 인증 필터 미적용)를 같이 점검할 것
- 증상 대응과 근본 해결을 구분해서, 후자는 차기 개선으로 분리 보고
- 미검증 항목은 숨기지 말고 확인 시점과 방법까지 명시


함께 보면 좋은 글:
- 한글이 분명 있는데 grep이 안 될 때 (EUC-KR과 UTF-8 인코딩 함정) - 에러 없이 결과만 다르게 나오는 함정 편
- 웹 3티어에서 WAS 앞에 사설망 L4를 또 두는 이유 - 같은 인프라 구조 편
- 톰캣(Tomcat) 6에서 9로 올릴 때 만나는 함정 5가지 (기동 실패, 한글 전멸, 400 에러) - 같은 이관 작업의 나머지 함정 편
- 톰캣을 새로 깔았는데 권한 점검에 또 걸릴 때 (tar가 남긴 디렉터리 모드와 umask) - 같은 톰캣 권한 조치 편


여기까지 톰캣 allowLinking 취약점을 조치하면서 기능은 유지하는 방법에 대해서 작성해봤습니다. 여기까지 읽어주셔서 감사합니다!

반응형