SSRF란?

서버 측 요청 위조(Server-Side Request Forgery)는 공격자가 서버 측 애플리케이션이 의도하지 않은 위치로 요청을 보내도록 만들 수 있는 웹 보안 취약점입니다.

일반적인 SSRF 공격에서 공격자는 서버가 조직 인프라 내의 내부 전용 서비스에 연결하도록 만들 수 있습니다. 다른 경우에는 서버가 임의의 외부 시스템에 연결하도록 강제할 수 있습니다. 이로 인해 인가 자격 증명과 같은 민감한 데이터가 유출될 수 있습니다.

서버를 대상으로 한 SSRF 공격 (SSRF Attacks Against the Server)

서버를 대상으로 한 SSRF 공격에서 공격자는 애플리케이션이 루프백 네트워크 인터페이스를 통해 애플리케이션을 호스팅하고 있는 서버 자체로 HTTP 요청을 보내도록 만듭니다. 이는 일반적으로 127.0.0.1(루프백 어댑터를 가리키는 예약된 IP 주소) 또는 localhost(동일한 어댑터에 대해 일반적으로 사용되는 이름)와 같은 호스트명을 가진 URL을 제공하는 것을 포함합니다.

예를 들어, 사용자가 특정 매장에 상품의 재고가 있는지 확인할 수 있는 쇼핑 애플리케이션을 상상해 보겠습니다. 재고 정보를 제공하기 위해 애플리케이션은 프론트엔드 HTTP 요청을 통해 관련 백엔드 API 엔드포인트의 URL을 전달합니다.

POST /product/stock HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 118

stockApi=http://stock.weliketoshop.net:8080/product/stock/check%3FproductId%3D6%26storeId%3D1

이 예시에서 공격자는 요청을 수정하여 서버 로컬 URL을 지정할 수 있습니다.

POST /product/stock HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 118

stockApi=http://localhost/admin

서버는 /admin URL의 내용을 가져와 사용자에게 반환합니다. 공격자가 직접 /admin URL을 방문하면 관리 기능은 인증된 사용자만 접근할 수 있어 아무것도 볼 수 없습니다. 그러나 요청이 로컬 머신에서 오는 경우, 일반적인 접근 제어가 우회됩니다. 요청이 신뢰할 수 있는 위치에서 발생한 것으로 보이기 때문에 애플리케이션은 관리 기능에 대한 전체 접근을 허용합니다.

왜 애플리케이션은 로컬 요청을 신뢰하는가?

이는 다양한 이유로 발생할 수 있습니다.

  • 접근 제어 검사가 애플리케이션 서버 앞에 위치한 다른 구성 요소에서 구현될 수 있습니다. 서버로 다시 연결이 이루어지면 해당 검사가 우회됩니다.
  • 재해 복구 목적으로, 애플리케이션은 로컬 머신에서 접근하는 모든 사용자에게 로그인 없이 관리자 접근을 허용할 수 있습니다. 이는 완전히 신뢰할 수 있는 사용자만이 서버에서 직접 접근할 것이라고 가정합니다.
  • 관리 인터페이스가 메인 애플리케이션과 다른 포트 번호에서 수신 대기할 수 있으며, 사용자가 직접 도달할 수 없을 수 있습니다.

로컬 머신에서 발생하는 요청이 일반 요청과 다르게 처리되는 이러한 종류의 신뢰 관계는 종종 SSRF를 심각한 취약점으로 만듭니다.

실습: 로컬 서버를 대상으로 한 기본 SSRF

이 랩에는 내부 시스템에서 데이터를 가져오는 재고 확인 기능이 있습니다. 재고 확인 URL을 변경하여 http://localhost/admin의 관리자 인터페이스에 접근하고 carlos 사용자를 삭제하세요.

상품 페이지로 이동한 뒤 하단에 Check stock 버튼을 클릭합니다.

요청 내 stockApi 파라미터의 값으로 URL이 전송되고 있으며, 디코딩하면 URL을 통해 재고를 조회하는 것을 확인할 수 있습니다.

stockApi 파라미터의 값으로 http://localhost/admin을 지정한 뒤 요청을 전송합니다.

외부에서 접근이 불가능한 내부 웹 페이지가 응답으로 나타나게 됩니다. 로컬 요청에 대한 접근 제어가 우회되어 관리자 인터페이스에 접근할 수 있는 것입니다.

다른 백엔드 시스템을 대상으로 한 SSRF 공격 (SSRF Attacks Against Other Back-end Systems)

경우에 따라 애플리케이션 서버는 사용자가 직접 도달할 수 없는 백엔드 시스템과 상호작용할 수 있습니다. 이러한 시스템은 종종 라우팅이 불가능한 사설 IP 주소를 가지고 있습니다. 백엔드 시스템은 일반적으로 네트워크 토폴로지에 의해 보호되므로, 보안 태세가 더 약한 경우가 많습니다. 많은 경우, 내부 백엔드 시스템은 해당 시스템과 상호작용할 수 있는 누구나 인증 없이 접근할 수 있는 민감한 기능을 포함하고 있습니다.

예를 들어, 백엔드 URL https://192.168.0.68/admin에 관리 인터페이스가 있다면, 공격자는 SSRF 취약점을 익스플로잇하여 다음과 같은 요청을 제출할 수 있습니다.

POST /product/stock HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 118

stockApi=http://192.168.0.68/admin

실습: 다른 백엔드 시스템을 대상으로 한 기본 SSRF

이 랩에는 내부 시스템에서 데이터를 가져오는 재고 확인 기능이 있습니다. 재고 확인 기능을 사용하여 내부 192.168.0.X 범위에서 포트 8080의 관리자 인터페이스를 스캔한 다음, 이를 사용하여 carlos 사용자를 삭제하세요.

stockApi 파라미터를 이용하여 192.168.0.x에서 1부터 255까지의 번호를 전부 탐색합니다.

특정 IP(예: 192.168.0.113)가 반응하는 것을 확인하고 접속하면, 내부 서버의 관리자 페이지에 접근이 가능합니다.

파일 업로드 취약점이란?

파일 업로드 취약점은 웹 서버가 파일의 이름, 유형, 내용 또는 크기와 같은 것을 충분히 검증하지 않고 사용자가 파일 시스템에 파일을 업로드할 수 있도록 허용하는 경우에 발생합니다. 이러한 제한 사항을 적절히 적용하지 못하면, 기본적인 이미지 업로드 기능조차도 임의의 잠재적으로 위험한 파일을 업로드하는 데 사용될 수 있습니다. 이는 원격 코드 실행을 가능하게 하는 서버 측 스크립트 파일까지 포함할 수 있습니다.

경우에 따라 파일을 업로드하는 행위 자체만으로도 피해를 유발하기에 충분합니다. 다른 공격에서는 서버에 의한 실행을 트리거하기 위해 해당 파일에 대한 후속 HTTP 요청이 필요할 수 있습니다.

파일 업로드 취약점은 어떻게 발생하는가?

상당히 명백한 위험성을 고려하면, 실제 웹사이트에서 사용자가 업로드할 수 있는 파일에 대해 아무런 제한이 없는 경우는 드뭅니다. 더 일반적으로, 개발자는 본질적으로 결함이 있거나 쉽게 우회할 수 있는 검증을 구현합니다.

예를 들어, 위험한 파일 유형을 블랙리스트에 등록하려고 시도할 수 있지만, 파일 확장자를 검사할 때 파싱 불일치를 고려하지 못할 수 있습니다. 모든 블랙리스트와 마찬가지로, 여전히 위험할 수 있는 더 모호한 파일 유형을 실수로 누락하기 쉽습니다.

다른 경우에는, 웹사이트가 Burp Proxy나 Repeater와 같은 도구를 사용하여 공격자가 쉽게 조작할 수 있는 속성을 확인하여 파일 유형을 검증하려고 시도할 수 있습니다. 궁극적으로, 견고한 검증 조치도 웹사이트를 구성하는 호스트와 디렉터리의 네트워크 전반에 걸쳐 일관되지 않게 적용될 수 있으며, 이로 인해 익스플로잇할 수 있는 불일치가 발생할 수 있습니다.

제한 없는 파일 업로드를 익스플로잇하여 웹 셸 배포하기

보안 관점에서 최악의 시나리오는 웹사이트가 PHP, Java 또는 Python 파일과 같은 서버 측 스크립트를 업로드할 수 있도록 허용하고, 이를 코드로 실행하도록 구성되어 있는 경우입니다. 이는 서버에 자체 웹 셸을 생성하는 것을 매우 쉽게 만듭니다.

웹 셸(Web Shell): 공격자가 올바른 엔드포인트로 HTTP 요청을 보내는 것만으로 원격 웹 서버에서 임의의 명령을 실행할 수 있게 하는 악성 스크립트입니다.

웹 셸을 성공적으로 업로드할 수 있다면, 사실상 서버를 완전히 제어할 수 있게 됩니다. 임의 파일을 읽고 쓰고, 민감한 데이터를 유출하고, 심지어 서버를 사용하여 내부 인프라와 네트워크 외부의 다른 서버를 대상으로 피벗 공격을 수행할 수 있습니다.

예를 들어, 다음과 같은 PHP 한 줄 코드를 사용하여 서버의 파일 시스템에서 임의 파일을 읽을 수 있습니다.

<?php echo file_get_contents('/path/to/target/file'); ?>

업로드가 완료되면, 이 악성 파일에 대한 요청을 보내면 응답에 대상 파일의 내용이 반환됩니다. 더 다목적인 웹 셸은 다음과 같을 수 있습니다.

<?php echo system($_GET['command']); ?>

이 스크립트를 사용하면 다음과 같이 쿼리 매개변수를 통해 임의의 시스템 명령을 전달할 수 있습니다.

GET /example/exploit.php?command=id HTTP/1.1

파일 업로드의 결함 있는 검증 익스플로잇하기

실제 환경에서는 파일 업로드 공격에 대한 보호가 전혀 없는 웹사이트를 발견하기는 어렵습니다. 그러나 방어 조치가 마련되어 있다고 해서 반드시 견고하다는 것을 의미하지는 않습니다.

HTML 폼을 제출할 때, 브라우저는 일반적으로 콘텐츠 유형 application/x-www-form-urlencoded로 데이터를 전송합니다. 그러나 이미지 파일이나 PDF 문서와 같은 대량의 바이너리 데이터를 전송할 때는 multipart/form-data가 사용됩니다.

POST /images HTTP/1.1
Host: normal-website.com
Content-Type: multipart/form-data; boundary=---------------------------012345678901234567890123456

---------------------------012345678901234567890123456
Content-Disposition: form-data; name="image"; filename="example.jpg"
Content-Type: image/jpeg

[...example.jpg의 바이너리 내용...]

---------------------------012345678901234567890123456
Content-Disposition: form-data; name="description"

This is an interesting description of my image.

---------------------------012345678901234567890123456
Content-Disposition: form-data; name="username"

wiener
---------------------------012345678901234567890123456--

메시지 본문은 폼의 각 입력에 대해 별도의 파트로 분할됩니다. 각 파트에는 Content-Disposition 헤더가 포함되어 있으며, 개별 파트에는 자체 Content-Type 헤더도 포함될 수 있습니다.

웹사이트가 파일 업로드를 검증하려고 시도하는 한 가지 방법은 이 입력별 Content-Type 헤더가 예상되는 MIME 유형과 일치하는지 확인하는 것입니다. 예를 들어, 서버가 이미지 파일만 예상하는 경우 image/jpegimage/png과 같은 유형만 허용할 수 있습니다. 이 헤더의 값이 서버에 의해 암묵적으로 신뢰되고, 파일의 내용이 실제로 가정된 MIME 유형과 일치하는지 확인하기 위한 추가 검증이 수행되지 않으면, Burp Repeater와 같은 도구를 사용하여 쉽게 우회할 수 있습니다.

OS 명령어 인젝션란?

OS 명령어 인젝션은 셸 인젝션(Shell Injection)이라고도 합니다. 이를 통해 공격자는 애플리케이션이 실행되고 있는 서버에서 운영 체제(OS) 명령어를 실행할 수 있으며, 일반적으로 애플리케이션과 그 데이터를 완전히 침해할 수 있습니다. 종종 공격자는 OS 명령어 인젝션 취약점을 활용하여 호스팅 인프라의 다른 부분을 침해하고, 신뢰 관계를 익스플로잇하여 조직 내 다른 시스템으로 공격을 피벗할 수 있습니다.

유용한 명령어 (Useful Commands)

OS 명령어 인젝션 취약점을 식별한 후, 시스템에 대한 정보를 얻기 위해 일부 초기 명령어를 실행하는 것이 유용합니다. 아래는 Linux 및 Windows 플랫폼에서 유용한 명령어의 요약입니다.

명령어의 목적 Linux Windows
현재 사용자 이름 whoami whoami
운영 체제 uname -a ver
네트워크 구성 ifconfig ipconfig /all
네트워크 연결 netstat -an netstat -an
실행 중인 프로세스 ps -ef tasklist

OS 명령어 인젝션하기 (Injecting OS Commands)

쇼핑 애플리케이션이 사용자가 특정 매장에 상품의 재고가 있는지 확인할 수 있도록 한다고 가정합니다. 이 정보는 다음 URL을 통해 접근됩니다.

https://insecure-website.com/stockStatus?productID=381&storeID=29

재고 정보를 제공하기 위해 애플리케이션은 상품 및 매장 ID를 인수로 하여 셸 명령어를 호출하는 방식으로 구현되어 있습니다.

stockreport.pl 381 29

이 애플리케이션은 OS 명령어 인젝션에 대한 어떠한 방어도 구현하지 않았으므로, 공격자는 임의의 명령어를 실행하기 위해 다음 입력을 제출할 수 있습니다.

& echo aiwefwlguh &

이 입력이 productID 매개변수에 제출되면, 애플리케이션이 실행하는 명령어는 다음과 같습니다.

stockreport.pl & echo aiwefwlguh & 29

& 문자는 셸 명령어 구분자입니다. 이 예시에서는 세 개의 개별 명령어가 차례로 실행됩니다. 사용자에게 반환되는 출력은 다음과 같습니다.

Error - productID was not provided
aiwefwlguh
29: command not found

원래의 stockreport.pl 명령어가 예상 인수 없이 실행되어 오류 메시지를 반환했고, 인젝션된 echo 명령어가 실행되어 제공된 문자열이 출력에 에코되었으며, 원래 인수 29가 명령어로 실행되어 오류가 발생했습니다. 인젝션된 명령어 뒤에 추가 명령어 구분자 &를 배치하면, 뒤에 오는 내용이 인젝션된 명령어의 실행을 방해할 가능성을 줄여줍니다.

실습: OS 명령어 인젝션, 단순 사례

이 랩에는 제품 재고 확인 기능에 OS 명령어 인젝션 취약점이 포함되어 있습니다. 애플리케이션은 사용자가 제공한 제품 및 매장 ID를 포함하는 셸 명령어를 실행하고, 명령어의 원시 출력을 응답에 반환합니다. whoami 명령어를 실행하여 현재 사용자의 이름을 확인하세요.

상세 게시글을 클릭한 뒤, 하단에 Check stock 버튼을 클릭합니다.

storeId 파라미터의 값에 1;whoami를 전송합니다. 서버 응답 값으로 whoami 명령어가 실행되어 현재 사용자의 이름이 반환되는 것을 확인할 수 있습니다. 세미콜론(;)이 셸 명령어 구분자로 작용하여, 원래의 재고 확인 명령어 뒤에 whoami가 별도의 명령어로 실행된 것입니다.

SQL 인젝션(SQLi)이란?

SQL 인젝션(SQLi)은 공격자가 애플리케이션이 데이터베이스에 수행하는 쿼리를 간섭할 수 있게 하는 웹 보안 취약점입니다. 이를 통해 공격자는 일반적으로 검색할 수 없는 데이터를 볼 수 있습니다. 여기에는 다른 사용자에게 속한 데이터나 애플리케이션이 접근할 수 있는 기타 모든 데이터가 포함될 수 있습니다. 많은 경우, 공격자는 이 데이터를 수정하거나 삭제할 수 있으며, 이는 애플리케이션의 내용이나 동작에 영구적인 변경을 초래합니다.

일부 상황에서는 공격자가 SQL 인젝션 공격을 확대하여 기반 서버나 기타 백엔드 인프라를 침해할 수 있습니다. 또한 서비스 거부(DoS) 공격을 수행할 수 있게 할 수도 있습니다.

SQL 인젝션 취약점을 탐지하는 방법

애플리케이션의 모든 입력 지점에 대해 체계적인 테스트 세트를 사용하여 수동으로 SQL 인젝션을 탐지할 수 있습니다. 이를 위해 일반적으로 다음과 같은 방법으로 확인합니다.

  • 작은따옴표 문자 '를 제출하고 오류나 기타 이상 현상을 확인합니다.
  • 입력 지점의 기본(원래) 값과 다른 값으로 평가되는 SQL 특정 구문을 제출하고, 애플리케이션 응답의 체계적인 차이를 확인합니다.
  • OR 1=1OR 1=2와 같은 부울 조건을 제출하고, 애플리케이션 응답의 차이를 확인합니다.
  • SQL 쿼리 내에서 실행될 때 시간 지연을 트리거하도록 설계된 페이로드를 제출하고, 응답에 걸리는 시간의 차이를 확인합니다.
  • SQL 쿼리 내에서 실행될 때 대역 외(Out-of-Band) 네트워크 상호작용을 트리거하도록 설계된 OAST 페이로드를 제출하고, 결과적인 상호작용을 모니터링합니다.

대안적으로, Burp Scanner를 사용하여 대부분의 SQL 인젝션 취약점을 빠르고 안정적으로 찾을 수 있습니다.

숨겨진 데이터 검색 (Retrieving Hidden Data)

다양한 카테고리의 제품을 표시하는 쇼핑 애플리케이션을 상상해 보겠습니다. 사용자가 선물(Gifts) 카테고리를 클릭하면, 브라우저는 다음 URL을 요청합니다.

https://insecure-website.com/products?category=Gifts

이로 인해 애플리케이션은 데이터베이스에서 관련 제품의 세부 정보를 검색하기 위해 SQL 쿼리를 수행합니다.

SELECT * FROM products WHERE category = 'Gifts' AND released = 1

released = 1 제한은 출시되지 않은 제품을 숨기는 데 사용됩니다. 이 애플리케이션은 SQL 인젝션 공격에 대한 어떠한 방어도 구현하지 않았으므로, 공격자가 다음과 같은 공격을 구성할 수 있습니다.

https://insecure-website.com/products?category=Gifts'--

이는 다음 SQL 쿼리를 생성합니다.

SELECT * FROM products WHERE category = 'Gifts'--' AND released = 1

--가 SQL에서 주석 표시자이므로, 쿼리의 나머지 부분이 주석으로 해석되어 AND released = 1이 사실상 제거됩니다. 결과적으로 아직 출시되지 않은 제품을 포함하여 모든 제품이 표시됩니다.

유사한 공격을 사용하여 모든 카테고리의 모든 제품을 표시하도록 만들 수 있습니다.

https://insecure-website.com/products?category=Gifts'+OR+1=1--

이는 다음 SQL 쿼리를 생성합니다.

SELECT * FROM products WHERE category = 'Gifts' OR 1=1--' AND released = 1

1=1은 항상 참이므로 쿼리는 모든 항목을 반환합니다.

경고: SQL 쿼리에 OR 1=1 조건을 인젝션할 때 주의하세요. 애플리케이션이 단일 요청의 데이터를 여러 다른 쿼리에 사용하는 것은 흔한 일입니다. 조건이 UPDATE 또는 DELETE 문에 도달하면 의도치 않은 데이터 손실이 발생할 수 있습니다.

실습: 숨겨진 데이터 검색을 허용하는 WHERE 절의 SQL 인젝션 취약점

이 랩에는 제품 카테고리 필터에 SQL 인젝션 취약점이 포함되어 있습니다. 사용자가 카테고리를 선택하면, 애플리케이션은 다음과 같은 SQL 쿼리를 수행합니다.

SELECT * FROM products WHERE category = 'Gifts' AND released = 1

애플리케이션이 하나 이상의 출시되지 않은 제품을 표시하도록 하는 SQL 인젝션 공격을 수행하세요.

메인 페이지 내 카테고리에서 임의의 카테고리를 클릭합니다.

category 파라미터에 값으로 카테고리를 구분하고 있음을 확인할 수 있습니다.

해당 파라미터에 ' OR 1=1--를 전송하면 SQL 쿼리문의 뒤쪽이 주석 처리되며, released 값에 관계없이 모든 제품이 표시됩니다.

애플리케이션 로직 전복 (Subverting Application Logic)

사용자가 사용자명과 비밀번호로 로그인할 수 있는 애플리케이션을 상상해 보겠습니다. 사용자가 사용자명 wiener와 비밀번호 bluecheese를 제출하면, 애플리케이션은 다음 SQL 쿼리를 수행하여 자격 증명을 확인합니다.

SELECT * FROM users WHERE username = 'wiener' AND password = 'bluecheese'

쿼리가 사용자의 세부 정보를 반환하면 로그인이 성공하고, 그렇지 않으면 거부됩니다.

이 경우, 공격자는 SQL 주석 시퀀스 --를 사용하여 쿼리의 WHERE 절에서 비밀번호 검사를 제거함으로써, 비밀번호 없이도 모든 사용자로 로그인할 수 있습니다. 예를 들어, 사용자명 administrator'--와 빈 비밀번호를 제출하면 다음 쿼리가 생성됩니다.

SELECT * FROM users WHERE username = 'administrator'--' AND password = ''

이 쿼리는 usernameadministrator인 사용자를 반환하며, 공격자를 해당 사용자로 성공적으로 로그인시킵니다.

실습: 로그인 우회를 허용하는 SQL 인젝션 취약점

이 랩에는 로그인 기능에 SQL 인젝션 취약점이 포함되어 있습니다. administrator 사용자로 애플리케이션에 로그인하는 SQL 인젝션 공격을 수행하세요.

administrator'--로 계정명을 설정한 뒤 임의의 비밀번호를 입력하고 로그인을 시도합니다.

-- 이후의 비밀번호 검증 부분이 주석 처리되므로, 별도의 비밀번호 없이 administrator 계정으로 로그인이 가능합니다.

취약점 유형 요약

취약점 핵심 원리 대표 공격 방식
SSRF 서버가 공격자 지정 URL로 요청 전송 stockApi=http://localhost/admin
파일 업로드 서버 측 스크립트 업로드 후 실행 PHP 웹 셸 업로드 → 원격 코드 실행
OS 명령어 인젝션 셸 구분자로 임의 명령어 삽입 1;whoami
SQL 인젝션 SQL 구문 조작으로 쿼리 변조 ' OR 1=1--, administrator'--

마무리

이번 글에서는 서버 측 취약점의 핵심 유형 네 가지를 살펴보았습니다.

  • SSRF: stockApi 등 서버 측 URL 파라미터를 조작하여 내부 시스템에 접근하거나 접근 제어를 우회할 수 있다.
  • 파일 업로드: 파일 유형 검증이 미흡하면 웹 셸을 업로드하여 서버를 완전히 장악할 수 있다.
  • OS 명령어 인젝션: 셸 명령어 구분자(&, ;)를 활용하여 서버에서 임의의 운영 체제 명령어를 실행할 수 있다.
  • SQL 인젝션: SQL 주석(--)과 논리 조건(OR 1=1)을 활용하여 인증을 우회하거나 숨겨진 데이터를 탈취할 수 있다.