
SSRF(Server-Side Request Forgery) 취약점 개념과 코딩 관점 설명
개요
SSRF(Server-Side Request Forgery, 서버 사이드 요청 위조)는 웹 애플리케이션이 외부나 내부 리소스에 HTTP 요청을 보내는 기능을 악용하여, 공격자가 서버로 하여금 의도하지 않은 곳으로 요청을 보내게 만드는 취약점이다. 사용자가 직접 다른 서버에 요청을 보내는 것이 아니라, 취약한 애플리케이션 서버가 대신 요청을 보내기 때문에 "서버 사이드"라는 이름이 붙는다.

이 취약점은 단순히 외부 URL을 호출하는 기능에서 시작되는 것처럼 보이지만, 실제로는 내부망 스캔, 메타데이터 서비스 접근, Redis·MySQL 같은 내부 서비스 공격 등으로 이어질 수 있어 영향 범위가 매우 크다. 특히 클라우드 환경에서 인스턴스 메타데이터(예: AWS 169.254.169.254)에 접근할 수 있을 경우, 토큰 탈취와 계정 권한 상승으로 이어질 수 있어 매우 위험한 취약점으로 분류된다.
SSRF는 "입력값 검증 미비"라는 전통적인 원인과 함께, "서버가 자기 자신과 내부망을 신뢰한다"는 구조적 특성을 동시에 이용한다. 개발자 관점에서는 외부 연동 기능 구현 시 URL을 어떻게 입력받고 검증해야 하는지, 어떤 라이브러리 옵션을 막아야 하는지, 인프라 측면에서 어떤 네트워크 제약을 걸어야 하는지 등을 함께 고려해야 한다.
SSRF의 기본 개념
SSRF를 이해하려면 먼저 "클라이언트가 요청하는 것처럼 보이지만 실제로 요청을 보내는 주체는 서버"라는 점을 이해하는 것이 중요하다. 예를 들어 사용자가 웹 서비스에 "이 URL의 내용을 가져와서 보여줘"라는 기능을 요청할 때, 브라우저가 직접 해당 URL에 접속하는 것이 아니라 웹 서버가 대신 HTTP 요청을 보내고, 받은 결과를 사용자에게 전달하는 구조일 수 있다.
이때 애플리케이션이 사용자가 입력한 URL을 거의 그대로 받아서 서버 측에서 요청을 보내면, 공격자는 이 기능을 이용해 서버가 접근 가능한 어떤 주소로든 요청을 보내도록 조작할 수 있다. 이렇게 되면 원래는 외부에서 직접 접근할 수 없는 내부망 주소(예: 10.x.x.x, 192.168.x.x)나 로컬호스트(127.0.0.1)에 대한 요청도 가능해진다. 즉, 서버는 공격자의 "프록시"처럼 동작하게 된다.
SSRF의 핵심은 "서버의 네트워크 권한을 빌려 쓰는 것"이다. 공격자는 인터넷에서 직접 보이지 않는 네트워크 영역, 방화벽으로 보호된 영역, 혹은 클라우드 내부 메타데이터 서비스에 대해 서버를 이용해 스캐닝하고, 민감한 정보나 토큰을 획득하려고 시도한다. 따라서 애플리케이션은 "사용자가 마음대로 지정한 URL로 서버가 접속해서는 안 된다"를 기본 원칙으로 삼아야 한다.
SSRF가 발생하는 전형적인 상황
SSRF는 주로 "URL을 입력받아 서버가 대신 호출하는" 기능에서 발생한다. 대표적인 예로는 이미지 프록시, 웹 페이지 미리보기(OG 태그 수집), 웹훅 검증, 특정 API를 대신 호출하는 게이트웨이 기능 등이 있다.
예를 들어, 게시판에서 사용자가 링크를 달면 서버가 해당 URL에 접속해서 제목, 썸네일 이미지 등을 읽어오는 "링크 미리보기" 기능이 있다고 하자. 이때 서버가 사용자가 준 URL을 검증 없이 그대로 열어보면, 공격자는 http://127.0.0.1:8000/admin 이나 http://169.254.169.254/latest/meta-data/ 같은 내부 자원 주소를 넣어 서버로 하여금 이를 요청하게 만들 수 있다. 이 과정에서 응답 내용이 그대로 사용자에게 표시되거나 로그에 남으면 민감 정보 유출로 이어진다.
또 다른 상황은 파일 다운로드 프록시다. 클라이언트가 직접 다운로드하기 어려운 리소스를 서버가 대신 받아서 전달하는 기능이 있을 수 있는데, 이 기능이 URL 검증을 제대로 하지 않으면 공격자는 내부 PDF 서버, 내부 API 문서, 심지어는 데이터베이스 관리 콘솔 같은 곳에 대한 요청을 유도할 수 있다. 내부 서비스가 인증 없이 내부 IP에서 오는 요청을 신뢰하도록 설계되어 있다면, SSRF 한 번으로 상당한 공격이 가능해진다.
공격 흐름과 악용 방식
SSRF 공격은 크게 세 단계로 생각할 수 있다. 첫째, 공격자는 서버가 외부 URL을 요청하는 기능이 있는 지점을 찾아낸다. 보통 "URL 입력", "리소스 가져오기", "미리보기" 같은 키워드가 있는 기능들이 후보가 된다. 둘째, 그 기능이 실제로 서버 측에서 요청을 보내는지, 그리고 내부 주소나 특수 프로토콜을 허용하는지를 테스트한다. 셋째, 가능하다면 이를 확장해 내부망 스캔, 서비스 탐색, 인증 우회, 토큰 탈취 등의 추가 공격을 수행한다.
악용 방식 중 하나는 포트 스캐닝이다. 공격자는 http://10.0.0.10:80, http://10.0.0.10:6379 같은 주소를 순차적으로 입력하며 서버 응답 시간을 측정하거나 오류 메시지를 분석해 어떤 포트에 서비스가 열려 있는지 확인할 수 있다. 또 하나는 클라우드 메타데이터 접근으로, AWS, GCP, Azure 등에 따라 잘 알려진 메타데이터 URL에 접근해 임시 자격 증명, IAM 역할 정보 등을 획득할 수 있다.
더 나아가, SSRF를 통해 단순 HTTP가 아닌 다른 프로토콜을 사용하는 경우도 있다. 일부 HTTP 클라이언트 라이브러리는 file://, gopher://, ftp:// 같은 스킴을 지원하는데, 이를 통해 로컬 파일을 읽거나 외부로 데이터를 내보낼 수 있는 경우도 존재했다. 최신 환경에서는 많이 차단되는 추세지만, 구버전 라이브러리나 잘못된 옵션 설정으로 이런 공격이 여전히 가능할 수 있다.
코딩 관점에서 본 SSRF의 원인
개발자의 코드 관점에서 보면 SSRF의 가장 큰 원인은 "사용자 입력값을 신뢰하고 네트워크 요청에 직접 사용"하는 것이다. 예를 들어, 다음과 같이 단순하게 URL을 받아서 요청하는 코드는 SSRF에 취약하다.
// 취약한 예 (Node.js 스타일 예시)
const axios = require('axios');
app.post('/fetch', async (req, res) => {
const url = req.body.url; // 사용자 입력 그대로 사용
const response = await axios.get(url); // 서버가 대신 요청
res.send(response.data);
});여기서 url에 무엇이 들어올지 전혀 검증하지 않기 때문에, 공격자는 내부 IP, 로컬호스트, 메타데이터 서비스 주소 등 어떤 것이든 넣을 수 있다. 개발자가 단지 "외부 사이트 내용을 프록시하겠다"는 생각으로 이 코드를 작성했더라도, 서버 입장에서는 "접근 가능한 모든 네트워크에 대한 프록시"가 되어버리는 셈이다.
또 다른 원인은 라이브러리와 프레임워크의 기본 설정을 충분히 이해하지 못한 채 사용하는 것이다. 일부 HTTP 클라이언트는 리다이렉트를 자동으로 따라가는데, 개발자가 허용한 도메인은 https://example.com 뿐이지만, 해당 URL이 내부망 주소로 리다이렉트되면 결국 SSRF가 가능해지는 구조가 된다. 코딩 시 "리다이렉트 허용 여부"와 "최대 리다이렉트 횟수", "허용되는 스킴과 포트 범위" 등을 세심하게 제어해야 하는 이유다.
안전한 URL 검증 전략
코드에서 SSRF를 방지하기 위한 핵심 전략은 "허용 목록 기반 검증(Allowlist)"이다. 이상적인 방향은 사용자가 URL 전체를 마음대로 입력하게 하지 않고, 미리 정해진 도메인이나 경로 집합만 선택할 수 있도록 만드는 것이다. 예를 들어 "우리 서비스가 연동하는 파트너 API 서버 목록"을 서버 코드에 상수로 두고, 그 중 하나만 선택하도록 한다면 SSRF 위험이 크게 줄어든다.
만약 비즈니스 특성상 임의 URL을 받아야 한다면, 다음과 같은 검증을 단계적으로 수행하는 것이 도움이 된다. 먼저, URL 파싱 라이브러리를 사용해 스킴(프로토콜)이 http 또는 https인지 확인하고, file, gopher, ftp 등의 위험한 스킴은 모두 차단한다. 그 다음으로 도메인 이름을 DNS 조회하여 실제 IP 주소를 얻고, 이 IP가 사설 IP 범위(10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)나 로컬 주소(127.0.0.0/8, ::1), 메타데이터 주소(169.254.169.254 등)에 해당하는지 검사하여 차단한다.
또한 DNS 리바인딩 공격을 고려해, 요청 직전에 다시 한 번 DNS를 확인하거나, IP 기준 허용 목록/차단 목록을 적용하는 방식을 사용할 수 있다. 단순 문자열 검사만으로는 우회가 가능하기 때문에, 실제 네트워크 수준에서 어떤 IP로 연결되는지를 기준으로 검증하는 것이 보다 안전하다. 이 검증 로직은 코드에서 한 번만 쓰지 말고 공용 함수나 미들웨어로 만들어 모든 외부 요청 전에 공통으로 사용하도록 하는 것이 좋다.
예제 코드: 기본적인 SSRF 방어 구현
다음은 Node.js 환경에서 간단한 SSRF 방어를 구현한 예시이다. 실제 서비스에서는 환경에 맞춰 IP 검사 범위 등을 더 보강해야 한다.
const axios = require('axios');
const dns = require('dns').promises;
const { URL } = require('url');
const ip = require('ip'); // npm 모듈이라고 가정
async function isPrivateAddress(hostname) {
// 도메인을 IP로 변환
const addresses = await dns.lookup(hostname, { all: true });
for (const addr of addresses) {
const ipAddr = addr.address;
// 사설망, 로컬호스트, 링크 로컬 등 차단
if (
ip.isPrivate(ipAddr) ||
ipAddr === '127.0.0.1' ||
ipAddr === '0.0.0.0' ||
ipAddr === '169.254.169.254'
) {
return true;
}
}
return false;
}
app.post('/fetch', async (req, res) => {
try {
const urlString = req.body.url;
const parsed = new URL(urlString);
// 스킴 검증
if (!['http:', 'https:'].includes(parsed.protocol)) {
return res.status(400).send('허용되지 않는 프로토콜입니다.');
}
// 호스트 검증
if (await isPrivateAddress(parsed.hostname)) {
return res.status(400).send('내부망 주소는 요청할 수 없습니다.');
}
const response = await axios.get(urlString, {
maxRedirects: 3, // 리다이렉트 제한
validateStatus: (s) => s < 400, // 4xx 이상이면 예외
});
res.send(response.data);
} catch (e) {
res.status(400).send('요청 처리 중 오류가 발생했습니다.');
}
});이 코드는 스킴 검증, DNS 기반 IP 검사, 리다이렉트 제한 등을 통해 기본적인 SSRF 공격을 막는 예시이다. 실제로는 IPv6 주소 처리, 프록시 환경, DNS 캐싱 등 더 많은 요소를 고려해야 하지만, 최소한 "사용자 입력 URL을 그대로 쓰지 않는다"는 원칙을 코딩으로 표현한 형태이다.
인프라·설계 관점에서의 추가 방어
코드 레벨의 검증만으로는 모든 상황을 막기 어렵기 때문에, 인프라와 네트워크 설계 측면에서도 방어가 필요하다. 예를 들어, 웹 애플리케이션 서버가 있는 서브넷에서 데이터베이스, Redis, 내부 관리 콘솔 등에 직접 HTTP/HTTPS로 접근할 수 없도록 보안 그룹이나 방화벽을 설정하는 방식이 있다. 이렇게 하면 혹시 SSRF 취약점이 남아 있더라도, 네트워크 레벨에서 내부 자원 접근이 차단된다.
클라우드 환경에서는 인스턴스 메타데이터 서비스에 대한 접근을 제한하거나, IMDSv2(세션 기반 메타데이터 접근)처럼 보다 안전한 메커니즘을 사용하도록 설정하는 것도 중요하다. 또한 내부 서비스는 "같은 VPC에서 오는 HTTP 요청이면 모두 신뢰"하는 식으로 설계하지 말고, 별도의 인증 토큰이나 MTLS 등을 사용해 정당한 요청인지 검증하도록 구성해야 한다.
서비스 설계 단계에서도 "굳이 서버가 대신 외부 URL에 접속해야 하는가?"를 한 번 더 고민하는 것이 좋다. 가능한 경우에는 브라우저에서 직접 외부 리소스에 접근하도록 만들고, 서버는 최소한의 메타 정보만 처리하거나, 검증된 큐·백엔드 서비스만 사용하도록 분리하는 식의 설계가 SSRF 위험을 줄인다.
개발 시 체크 리스트와 실무 팁
실무에서 SSRF를 예방하기 위해서는 개별 개발자의 주의뿐 아니라 팀 차원의 코딩 규약과 리뷰 문화가 중요하다. 코드 리뷰 시 "외부 URL에 대한 요청"이 등장하는 부분은 자동으로 보안 리뷰 대상에 포함시키고, 공통 HTTP 클라이언트 래퍼를 통해 항상 동일한 검증 로직이 적용되도록 만드는 방식이 효과적이다.
또한 보안 테스트 단계에서 SSRF를 자동 탐지하는 도구를 사용하는 것도 도움이 된다. 예를 들어, QA 환경에 내부 테스트 서버를 하나 두고, 도구가 다양한 URL 변형을 이용해 그 서버에 접근할 수 있는지 확인하게 하면, 코드에서 놓친 부분을 조기에 발견할 수 있다. 로그에 "내부 IP로의 요청 시도"나 "메타데이터 주소 요청"이 발견되면, 이를 경보로 삼아 즉시 확인하는 모니터링 체계를 구축하는 것도 좋은 전략이다.
무엇보다 중요한 것은 "URL을 입력받아 서버가 네트워크 요청을 보낸다"는 패턴의 위험성을 팀 전체가 공유하는 것이다. 이 패턴이 등장하는 순간 SSRF 가능성을 자동으로 떠올리고, 허용 목록, IP 검증, 리다이렉트 제한, 네트워크 레벨 차단 등을 종합적으로 적용하는 습관이 자리 잡으면, SSRF로 인한 심각한 사고를 상당 부분 예방할 수 있다.

