[Juice Shop] 01. Confidential Document

asdf·3일 전

web

목록 보기
29/32

OWASP Juice Shop은 국제 웹 보안 표준 기구인 OWASP에서 모의해킹 및 보안 교육을 목적으로 의도적으로 취약하게 만든 웹 애플리케이션입니다.

단순히 취약한 페이지들의 모음이 아니라, Node.js, Express, Angular 등 현대적인 웹 기술 스택을 사용하여 개발된 완전한 형태의 온라인 과일 주스 쇼핑몰입니다. 사용자 로그인, 상품 검색, 장바구니, 리뷰 작성, 결제 등 실제 서비스와 동일한 비즈니스 로직을 갖추고 있으며, 그 이면에 인젝션(Injection), XSS, 인증 우회, 보안 설정 오류 등 OWASP Top 10을 아우르는 수십 가지의 취약점들이 촘촘하게 숨겨져 있습니다.

단일 취약점의 핵심 원리 파악과 CTF 방식의 문제 해결에 집중된 webhacking.kr 과 달리 실제 모의해킹 업무에 좀 더 가까운 연습을 할 수 있습니다.


Docker에서 juice-shop을 다운받은 후 실행하여 접속해보겠습니다.

사진처럼 쇼핑몰같은 사이트가 나옵니다. Account, 검색, 리뷰 등 다양한 기능들이 있습니다. 그 중 Help getting started를 눌러보겠습니다.

Score Board에서 exploit 진행 상황을 확인할 수 있다고 합니다. 따라서 저희의 첫번째 과제는 Score Board를 찾는 것입니다.

Score Board가 주어진 메뉴에는 없기 때문에 zap의 spider로 크롤링을 해보겠습니다. 실제 버그바운티에서는 의도치 않게 DoS 공격이 될 수 있으므로 전송 속도를 조절하거나 gobuster같은 도구로 브루트포싱을 수행 후 의심되는 경로에서 크롤링을 수행합니다.

하지만 Juice Shop은 Docker로 실행하기 때문에 편하게 zap spider를 사용해 보았습니다.

실행을 하니 URL들이 정말 많이 나옵니다. Processed에서 초록불은 분석한 경로들이고, 빨간불은 Flags에 Out of Scope라고 나와있습니다. URL을 보시면 localhost:3000이 아닌 외부 도메인 링크들이기 때문에 무한정 스캔하는 것을 방지하기 위한 것입니다.

zap spider를 실행하니 2개의 Challenge가 해결되었다는 알림이 떴습니다. 아직 Scoreboard를 찾지도 못한 상황에서 의도치 않게 풀려버렸네요.

첫 문제는 Confidential Document (Access a confidential document.) 라고 되어있습니다. 기밀 문서에 접근을 해서 풀렸다고 하는데 아마 크롤링하는 과정에서 자연스럽게 접근이 된 것 같습니다.

URL 중에서 robots.txt가 있는데 /ftp 라는 경로가 Disallow로 숨겨져 있습니다. 일반적으로 사용하는 크롤러들은 웹 표준을 준수하기 때문에 Disallow로 설정된 경로는 수집하지 않습니다. 하지만 zap과 같이 웹 해킹/보안 진단 도구들은 의도적으로 무시할 뿐만 아니라 가장 먼저 공략하는 타겟으로 설정합니다.

따라서 zap에서는 /ftp의 하위 경로까지 전부 탐색을 하고, 여기서 기밀 문서에 자연스럽게 접근을 하면서 문제가 해결된 것 같습니다.

그리고 Jump to related coding challenge를 누르니 자연스럽게 Score board(http://localhost:3000/#/score-board?highlightChallenge=directoryListingChallenge)로 연결이 되었습니다.

주소는 localhost:3000/#/score-board 였네요.

스코어보드에서는 Challenge 종류, 힌트 뿐만 아니라 Coding Challenge도 존재합니다. Coding Challenge에서는 문제를 유발하는 코드를 찾고, 이를 방어하는 시큐어 코딩(Secure Coding)도 연습해볼 수 있습니다.

Coding Challenge를 클릭하면 문제점이 존재하는 코드가 나옵니다. Find It은 취약점을 일으키는 코드를 좌측 체크박스로 선택하여 제출하는 문제입니다.

app.use('/ftp', serveIndexMiddleware, serveIndex('ftp', { icons: true }))
app.use('/ftp(?!/quarantine)/:file', servePublicFiles())

정답은 이 두 줄입니다. app.use()는 Express.js에서 사용하는 가장 핵심적인 메서드로 특정 URL 경로로 클라이언트의 요청이 들어왔을 때, 서버가 어떤 함수(미들웨어)를 실행할지 연결해 주는 역할을 합니다.

맨 끝의 serveIndex는 Express 환경에서 디렉토리 리스팅 기능을 쉽게 구현하기 위해 따로 설치해서 사용하는 외부 패키지(npm 모듈)입니다.

사진과 같이 /ftp 경로로 접속했을 때 사진처럼 디렉토리 리스팅을 가능하게 해줍니다.

첫 번째 줄에서는 디렉토리 리스팅을 허용하여 숨겨진 파일들의 목록을 보여줬다면 두 번째 줄에서는 기밀 파일의 다운로드 및 열람을 실제로 허용시켜줍니다.

app.use('/ftp(?!/quarantine)/:file', servePublicFiles())

'/ftp(?!/quarantine)/:file' 부터 살펴보겠습니다.

(?!/quarantine)는 정규표현식의 부정형 전방탐색(Negative Lookahead) 문법으로 앞의 /ftp 패턴에 일치한 후 바로 뒤에 이어지는 문자열이 /quarantine이 아니어야만 조건을 통과시킵니다. 즉, /quarantine에 대한 예외처리를 담당하는 구문입니다.

뒤의 /:file은 Express.js의 경로 파라미터(Path Parameter) 문법입니다. 콜론(:)이 붙으면 그 자리에 들어오는 문자열을 가변적인 변수처럼 취급합니다. Express 서버 내부에서는 req.params.file이라는 코드를 통해 사용자가 어떤 파일을 요청했는지 그 이름을 꺼내 쓸 수 있습니다.

예를 들어 /ftp/acquisitions.md를 요청 URL로 보내보겠습니다. 그럼 /ftp로 시작하고, 바로 뒤의 문자열이 /quarantine이 아니므로 file에는 acquisitions.md가 들어가서 servePublicFiles()로 전달되게 됩니다.

servePublicFiles() 함수는 개발자가 작성한 함수로 사용자가 요청한 파일 이름을 받아서, 서버의 실제 하드디스크나 스토리지에서 해당 파일을 찾아 브라우저로 전송해 주는 역할을 합니다.

이 두 줄의 코드를 통해 /ftp에 있는 파일들을 누구나 읽을 수 있게 됩니다.

/ftp 뿐만 아니라 /encryptionkeys/support/logs도 동일한 취약점이지만, 문제에서 요구하는 답은 /ftp 내부의 파일이므로 2, 3번 라인을 체크 후 제출하면 됩니다.

Fix It은 취약점을 해결하기 위한 보안 패치를 적용하는 과정으로 Fix 1, 2, 3, 4 중 하나를 선택하여 제출하는 문제입니다.

정답은 3번으로 /ftp 폴더를 완전히 없애버리는 것만이 이 데이터 유출 문제를 영구적으로 막을 수 있다고 합니다.

단순히 취약점이 발견된 특정 파일(acquisitions.md)만 예외 처리하거나 땜질식으로 코드를 고치는 것으로는 부족하다는 뜻입니다. /ftp라는 디렉토리 자체가 외부 인터넷에 공개되도록 연결해 둔 것 자체가 근본적인 보안 결함이므로, 화면에 빨간색으로 표시된 라우팅 코드 4줄을 통째로 날려버리는 것이 정답입니다.


+) 추가
zap spider 말고 gobuster를 이용한 브루트포싱도 한 번 해보겠습니다.

하던대로 http://localhost:3000으로 실행하면 the server returns a status code that matches the provided options for non existing urls. 라는 에러 로그가 나옵니다.

에러 로그를 보면 Gobuster가 본격적인 스캔을 시작하기 전에 테스트 삼아 절대 존재할 수 없는 무작위 경로(f050eecf...)를 서버에 요청해 보았는데, 서버가 404 Not Found 에러 대신 정상 페이지를 뜻하는 200 OK 상태 코드를 반환했습니다.

Juice Shop과 같은 SPA(Single Page Application) 웹앱은 사용자가 존재하지 않는 경로를 요청하더라도 메인 화면(index.html)으로 라우팅하면서 200 코드를 띄워주는 경우가 많습니다. 이렇게 되면 단어장에 있는 모든 단어가 200으로 응답하게 되므로, 의미 없는 결과가 수천 줄씩 도배되는 것을 막기 위해 Gobuster가 동작을 멈춘 것입니다.

이를 해결하기 위해서는 --exclude-length 옵션을 이요해서 200 코드 크기인 75002라는 크기를 가진 응답은 무시하도록 설정해주면 됩니다.

옵션을 준 뒤 실행하면 zap spider로 찾았던 ftp, robots.txt 같은 링크들을 동일하게 찾을 수 있습니다.


Juice Shop에 대한 첫 글이다 보니 소개를 포함해서 글이 좀 길어졌습니다. 금방 끝날 줄 알고 가볍게 시작했는데 문제 풀이가 끝나는 것 보다 취직하는게 더 빠를 수도 있겠다는 생각이 드네요;;

아무튼 모든 문제를 다 풀 때까지 열심히 달려보겠습니다. 이어서 Scoreboard 풀이로 오겠습니다!

profile
Rainy Waltz(a_hisa)

0개의 댓글