Post

SSRF


SSRF(Sever Side Request Forgery)란?



서버 측 공격 위조. 공격자가 원격 서버에 HTTP 요청을 위조하도록 하는 기법.
서버가 임의의 요청을 하게 하는 공격으로, 원리는 CSRF(클라이언트 측 공격 위조)와 같다.
서버가 외부 자원(리소스)를 이용하는 곳이나, 파라미터로 url을 받는 요청에서 일어날 수 있다.

보통 공격자는 서버가 내부망 서비스나 관리자용 인터페이스 등, 일반 사용자가 접근할 수 없는 곳에 요청을 보내게 만들 수 있다.
경우에 따라서는 외부 임의의 시스템으로도 연결을 강제할 수 있어서, 민감한 정보(예: 인증 토큰, 내부 서비스 데이터)가 유출될 수 있다.



SSRF 공격의 영향


  • 조직 내부 데이터 접근: 취약 애플리케이션이나 백엔드 시스템에 무단 접근 가능.
  • 임의 명령 실행: 경우에 따라 서버에서 직접 명령 실행으로 이어질 수 있음.
  • 제3자 공격: 서버가 외부로 공격 트래픽을 보내도록 악용될 수 있음. 즉, 공격이 마치 “피해 조직에서 시작된 것처럼” 보이게 됨.



일반적인 SSRF 공격


🔴 서버 자체 공격 (Loopback SSRF)

공격자가 서버가 자기 자신(127.0.0.1 / localhost)으로 요청을 보내게 만듦.

예: 쇼핑몰 앱이 재고 확인 API를 호출하는 경우

1
2
POST /product/stock
stockApi=http://stock.weliketoshop.net:8080/product/stock/check?productId=6&storeId=1

→ 원래는 정상 API 호출.

공격자는 이를 조작:

1
2
POST /product/stock
stockApi=http://localhost/admin

→ 서버가 자기 자신의 /admin 페이지를 대신 요청해서 응답을 사용자에게 반환. → 원래는 관리자만 접근 가능한 페이지지만, 서버 내부 요청은 신뢰되기 때문에 인증 없이 관리자 기능에 접근 가능.


애플리케이션이 로컬에서 오는 요청을 암묵적으로 신뢰하는 이유:

  • 접근 제어 우회: 액세스 제어가 애플리케이션 앞단에서만 적용되어, 서버 내부에서 오는 요청은 검사 없이 통과됨.
  • 재해 복구 목적: 관리자가 비밀번호를 잃어버렸을 때를 대비해, 로컬 요청에는 로그인 없이 관리자 권한을 부여하도록 설계된 경우.
  • 포트 분리: 관리자 인터페이스가 일반 사용자와 다른 포트에서만 열려 있어, 외부 사용자는 접근할 수 없다고 가정한 경우.

따라서 이런 신뢰 구조 때문에 SSRF는 치명적인 취약점이 될 수 있음.

❗즉,
SSRF는 서버를 프록시 삼아 내부망/관리자 페이지/외부 시스템에 요청을 보내게 만드는 취약점이고, 그 결과 인증 우회, 민감 정보 유출, 공격 확산이 가능해짐.


🔴 다른 백엔드 시스템 공격

애플리케이션 서버가 내부망에 있는 다른 시스템(예: DB 관리 인터페이스, 내부 REST API 등)에 접근할 수 있는 경우.

이 시스템들은 보통 외부에 노출되지 않는다는 가정으로 보안이 허술한 경우가 많음.

예:

1
2
POST /product/stock
stockApi=http://192.168.0.68/admin

→ 내부망 IP(192.168.x.x)로 요청이 전송되어, 원래라면 접근 불가능한 관리자 페이지에 접근 가능.



랩1 풀이

랩1 👉 https://portswigger.net/web-security/ssrf/lab-basic-ssrf-against-localhost
위 문제는 상품의 재고를 확인하기 위해 서버 내에서 데이터를 가지고 오는 웹페이지로,

http://localhost/admin에 접속한 뒤 carlos 유저를 삭제해야 한다.

재고를 확인하는 버튼을 클릭하면 stockApi 파라미터에 url이 붙으며 재고 확인 페이지를 불러온다. 이 url에 http://localhost/admin으로 변조하여 보내면 된다. 그러면 관리자 인터페이스에 접속할 수 있다.


여기서 계정이 2개가 나오는데, carlos 유저를 삭제하기 위해 그냥 삭제 버튼을 클릭하면 외부에서 요청한 것이라 삭제 되지 않는다. 소스코드를 확인하면 삭제 버튼을 클릭할 때 /admin/delete?username=carlos 로 리다이렉트 되므로, 이 주소를 stockApi 파라미터 값으로 다시 보내면 된다. 즉, 재고를 확인하는 요청을 보낼 때 stockApi=http://localhost/admin/delete?username=carlos 로 변조함으로써 풀 수 있다.



랩2 풀이

랩2 👉 https://portswigger.net/web-security/ssrf/lab-basic-ssrf-against-backend-system
192.168.0.X에서 포트 8080에 대한 관리자 인터페이스를 찾아서 사용하여 사용자 carlos를 삭제해야 한다.


재고를 확인할 때 stockApi 파라미터에 “http://192.168.0.1:8080/product/stock/check?productId=1&storeId=1” 라는 url이 붙는다. 따라서 http://192.168.0.X:8080/admin 을 입력하면 관리자 인터페이스에 접근할 수 있다.


X자리에 어떤 숫자가 오는 지는 intruder 탭에서 브루트포싱으로 1부터 255까지 숫자를 임의로 넣어서 관리자 페이지 주소가 http://192.168.0.167:8080/admin 임을 알 수 있다.


이제 첫 번째 문제와 같이, 관리자 인터페이스 소스코드에서 carlos를 삭제하는 주소가 http://192.168.0.167:8080/admin/delete?username=carlos 임을 확인하였고, 이 주소를 다시 stockApi 파라미터에 붙여 보냄으로써 풀게된다.



블랙리스트 기반 SSRF 필터 우회


일부 애플리케이션은 127.0.0.1, localhost 같은 호스트명이나 /admin 같은 민감한 URL을 차단한다.
하지만 이런 필터링은 아래와 같은 방법으로 쉽게 우회할 수 있다.


  • IP 주소 대체 표현 사용 127.0.0.1 대신 2130706433(10진), 017700000001(8진), 127.1 등 다른 표기법 활용

  • 도메인 등록 활용 127.0.0.1로 해석되도록 도메인을 등록 후 사용 예: spoofed.burpcollaborator.net

  • 문자열 난독화 URL 인코딩(%31%32%37…) 대소문자 변형(LoCaLhOsT)

  • 리다이렉트 이용 공격자가 제어하는 URL로 먼저 요청을 보낸 뒤, 최종적으로 대상 URL로 리다이렉트 다양한 리다이렉트 코드(301, 302 등)와 프로토콜 전환(http:// → https://)을 시도하면 일부 필터를 우회 가능



즉, IP 변형·도메인·인코딩·리다이렉트 기법을 활용하면 블랙리스트 기반 SSRF 필터를 쉽게 우회할 수 있다.



랩3 풀이

랩2 👉 https://portswigger.net/web-security/ssrf/lab-ssrf-with-blacklist-filter
재고 확인 URL을 변경해서 관리자 인터페이스(http://localhost/admin) 에 접근한 뒤, 사용자 carlos를 삭제해야 한다.
개발자는 여기에 두 가지 약한 SSRF 방어 기법을 적용해두었으므로, 이를 우회해야 하는 문제이다.


위의 랩 2개와 마찬가지로, stockapi 파라미터로 링크를 받는다.
해당 파라미터값으로 http://localhost/admin 페이지를 불러오면 되는데, ‘127.0.0.1’과 ‘admin’ 둘 다 단순하게 문자열로만 블랙리스트 기반으로 필터링되고 있는 듯 했다.


따라서 127.0.0.1은 127.1로 우회하고, admin은 url 인코딩 한 번으로는 필터링에 걸려서.. 두 번 인코딩하면 우회된다.


처음엔 냅다 http://localhost/admin를 두 번 인코딩했는데, 안 되더라..
생각해보니 서버 측에서 한 번 디코딩하면 그냥 문자열되고.. 두 번 디코딩하면 예쁘게 필터링에 걸린다.

그래서 인코딩을 두 번 이상 하는 경우는 우회하기 위한 최소한의 문자열만 하는 게 좋다고 한다.


아무튼 stockapi 파라미터 값으로 http://127.1/%2561dmin를 보내면 admin 페이지를 불러올 수 있고, carlos 계정의 존재를 확인할 수 있다.

해당 계정을 삭제하기 위해 다시 stockapi 파라미터 값으로 http://127.1/%2561dmin/delete%3Fusername%3Dcarlos 를 보내면 된다.



화이트리스트 기반 SSRF 필터 우회


일부 애플리케이션은 입력값을 화이트리스트(허용된 값 목록) 와 비교해 일치할 때만 허용한다.
보통은 입력값의 시작 부분이나 포함 여부를 검사하는데, 이 방식은 URL 파싱의 복잡성을 간과하기 때문에 아래와 같은 방법으로 우회할 수 있다.

  • URL에 인증정보 삽입 (@) https:/expected-host:fakepassword@evil-host → 필터는 expected-host만 보고 통과하지만, 실제 요청은 evil-host로 전송된다.

  • 프래그먼트(#) 활용 https://evil-host#expected-host → 필터는 expected-host가 있으니 허용, 그러나 # 뒤 내용은 서버에 전송되지 않는다.

  • DNS 네이밍 계층 활용 https://expected-host.evil-host → 필터는 expected-host가 들어 있으니 통과, 실제 요청은 evil-host로 향한다.

  • 문자열 인코딩 / 이중 인코딩 %xx 형태의 URL 인코딩을 이용해 필터와 백엔드 요청 처리 로직의 불일치를 노림. 어떤 서버는 재귀적으로 디코딩하기 때문에 %25xx 같은 이중 인코딩이 먹히기도 한다.

  • 기법 조합 위 방법들을 함께 사용해 필터를 혼란시키고 우회 가능.



즉, 화이트리스트 검증은 URL 파싱의 모든 경우를 고려하기 어렵다. 따라서 @, #, DNS 계층, 인코딩, 조합 기법을 활용하여 우회 가능하다.



랩4 풀이

랩4 👉 https://portswigger.net/web-security/ssrf/lab-ssrf-with-whitelist-filter
랩3과 마찬가지로 http://localhost/admin에 접근하여 carlos를 삭제해야 한다.


sockApi 파라미터로 http://stock.weliketoshop.net:8080/product/stock/check?productId=4&storeId=1 링크를 받아오고 있는데, 이를 http://localhost/admin/delete?username=carlos 로 위조해야 한다.


화이트리스트 필터링이 되고 있음을 아니까… 정상적인 도메인인 ‘stock.weliketoshop.net’ 가 파라미터 값에 포함되어야 할 것이다.


따라서 위에서 쓴 우회 방법 중 두 번째, #을 활용하여 http://localhost#stock.weliketoshop.net/admin/delete?username=carlos 를 보내봤는데 안 됐다.
당연함 # 뒤의 내용은 서버에 전달 안 되잖아…

다음으로 랩3의 입력값을 조금 수정해서, 아래와 같이 써서 링크를 보내봤다.
http://stock.weliketoshop.net@127.1/%2561dmin/delete%3Fusername%3Dcarlos
이번에도 안 됨… 이외에도 몇 가지 시도하다가…


솔루션을 통해 아래의 입력값을 통해 우회 가능했다.
http://localhost:80%2523@stock.weliketoshop.net/admin/delete?username=carlos



왜 위와 같은 입력값으로 우회가능한지 알아보자.


입력에서 호스트를 추출할 때, @ 뒤쪽을 호스트로 본다.
그래서 호스트가 stock.weliketoshop.net 로 보이고, 화이트리스트에 매칭되어 통과한다.
이후 서버 측 실제 요청이 처리될 때 stock.weliketoshop.net 부분이 무시되도록 @ 앞에 #을 이중인코딩하여 붙이면,
일부 백엔드/중간 계층은 url인코딩을 먼저(또는 두 번) 디코딩한 뒤 URL을 재해석하므로
localhost:80%2523 → localhost:80# 로 변하면서, #가 동작하게 된다.
결과적으로 localhost로 요청이 간다.
경로(/admin/delete?username=carlos)는 백엔드 구현에 따라 원본 경로가 유지되므로(검증단/라우팅단이 따로 붙이는 형태) 삭제 엔드포인트가 실행된다.



오픈 리다이렉션을 이용한 SSRF 필터 우회


경우에 따라, 오픈 리다이렉션 취약점을 활용해 필터 기반 방어를 우회할 수 있다.


예를 들어, 사용자 입력 URL이 SSRF 악용을 막기 위해 엄격히 검증된다고 하자. 그런데 허용된 도메인의 애플리케이션 자체에 오픈 리다이렉션 취약점이 존재한다면? 백엔드 요청에서 리다이렉션을 지원하는 경우, 이 취약점을 이용해 원하는 내부 대상에 접근할 수 있다.


🔴시나리오

애플리케이션에 다음과 같은 오픈 리다이렉션 취약점이 있다:

1
/product/nextProduct?currentProductId=6&path=http://evil-user.net

→ 위 URL을 요청하면 다음과 같이 리다이렉션 된다:

http://evil-user.net


🔴 SSRF 공격 활용 예시

1
2
3
4
5
POST /product/stock HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 118

stockApi=http://weliketoshop.net/product/nextProduct?currentProductId=6&path=http://192.168.0.68/admin

여기서 stockApi 값은 화이트리스트 검증을 통과한다. (weliketoshop.net 도메인 포함)

서버는 해당 URL로 요청을 보낸다.

하지만 요청을 받은 애플리케이션은 오픈 리다이렉션으로 인해 공격자가 지정한 내부 URL(http://192.168.0.68/admin) 로 다시 요청을 보낸다.

즉, 화이트리스트 검증을 우회하기 위해
허용된 도메인 안의 오픈 리다이렉션 취약점을 이용해 최종적으로 내부망 SSRF를 달성하는 방식이다.



랩5 풀이

랩5 👉 https://portswigger.net/web-security/ssrf/lab-ssrf-filter-bypass-via-open-redirection
재고 확인 URL을 수정해 관리자 인터페이스(http://192.168.0.12:8080/admin) 에 접근한 뒤 사용자 carlos를 삭제해야 한다.
단, 재고 확인 기능은 로컬 애플리케이션에만 접근 가능하도록 제한되어 있으므로, 먼저 애플리케이션 내의 오픈 리다이렉션 취약점을 찾아 이를 이용해야 한다.


next product 버튼을 클릭하면 path 파라미터 값이 Location 헤더에 그대로 반영되어 리다이렉션이 발생한다.
따라서 이 기능에는 오픈 리다이렉션 취약점이 존재한다.


처음엔 잘못 이해해서… next product 페이지를 요청하는 부분에서 path 파라미터 값으로 http://192.168.0.12:8080/admin/delete?username=carlos 를 보냈었다…


오픈 리다이렉션 취약점으로 인해 해당 삭제 페이지를 GET 해오기는 하나 실제로 삭제가 수행되지 않았다.


SSRF 취약점을 이용해야지요… 돌아가서 ssrf취약점이 존재하는 stockapi 파라미터 값으로 /product/nextProduct?path=http://192.168.0.12:8080/admin/delete%3Fusername%3Dcarlos 를 보내면 실제로 삭제된다.


정리하면,
오픈 리다이렉션 페이지를 직접 GET 하면 단순히 브라우저가 리다이렉션을 따라갈 뿐, 서버가 내부망에 요청하는 SSRF 동작은 발생하지 않는다.
SSRF 취약한 stockApi 파라미터를 통해 오픈 리다이렉션 경로(/product/nextProduct)를 호출해야 서버가 내부망 삭제 요청을 실행한다.

This post is licensed under CC BY 4.0 by the author.