# NPM 패키지 취약점 조치하기

## NPM Audit 이란?

2018년에 발표된 NPM v6에서 처음 도입되었다. 이 명령어는 프로젝트의 의존성 트리를 검사하여 보안 취약점을 찾고, 이를 보고하는 기능을 제공한다. NPM 사용자들이 프로젝트에서 사용하고 있는 패키지들의 보안 상태를 쉽게 확인하고, 필요한 경우 취약점을 수정할 수 있도록 하기 위한 중요한 기능이다. npm audit 명령어는 CLI 커맨드이며, Node Security Platform (NSP)의 데이터베이스에서 취약점에 대한 정보를 얻을 수 있다.

## NPM Audit 활용 방안

사용 커맨드는 비교적 간단하다. npm audit을 통해 패키지 내의 의존성 트리들을 검사할 수 있고, 관련하여 프로젝트 내에 패키지들이 어떤 위험성을 가지고 있는지 알려준다. 여기서 취약성의 심각도를 알려주는 Severity 라는 속성이 존재한다. 이는 취약성의 심각도를 나타내며, 취약점의 영향과 악용 가능성에 따라 결정된다. 기본적으로 취약점은 CVSS (Common Vulnerability Scoring System)에 의해 스코어와 심각도를 부여받는다.

| Tier | Description |
| --- | --- |
| Critical | Highest severity; requires immediate action as it can be easily exploited, often remotely. (최고 심각도; 즉각적인 조치가 필요하며 원격으로 쉽게 악용될 수 있음) - 가장 위험한 취약점을 의미한다. - 즉시 수정 조치를 필요로 한다. - 원격 공격에 노출될 수 있다. |
| High | Very serious; should be addressed as quickly as possible. (매우 심각함; 가능한 한 빨리 해결해야 함) - 심각한 보안 취약점을 의미한다. - 최우선적으로 수정 조치를 필요로 한다. - 빠른 시일 내 패치를 권장한다. |
| Moderate | Can become risky under specific conditions; address as time allows. (특정 조건에서 위험해질 수 있음; 시간이 허락하는대로 해결) - 중간 수준의 위험을 의미한다. - 특정 상황에서 보안 문제가 발생할 수 있다. - High 보다는 우선순위가 낮지만, 그래도 처리를 권장한다. |
| Low | Minor issues, potentially harmless edge cases; address at your discretion. (경미한 문제, 잠재적으로 무해한 엣지 케이스; 재량에 따라 해결) - 사소한 취약점을 의미한다. - 실제 피해 가능성이 낮으며, 필요 때에 처리를 권장한다. |

## 해당 패키지에서 어떤 보안 취약점이 발생했는지 확인하기

아래 명령어를 통해 리포트처럼 자세한 내용들을 확인할 수 있다. 자세한 내용에 대한 구성요소는 아래와 같다.

- 취약점 ID

- 영향받는 패키지

- dependency path

- fix 가능 여부 (**fixAvailable**)

```javascript
npm audit --json

// 혹여나 json 파일로 추출해서 확인을 하고 싶다면, 아래 명령어를 사용하면 된다.
npm audit --json > audit.json
```

`--json` 키워드를 붙여서 npm audit을 실행하게 되면, 보안 취약점이 있는 패키지들이 나열되는데 이때 아래와 같은 구조로 각 패키지에 대한 취약 정보들을 확인할 수 있다. 아래 코드 블럭에서 설명하는 패키지는 보안 취약점 레벨이 high인 micromatch 패키지에 대한 설명이다.

```javascript
    "micromatch": {
      "name": "micromatch", // 취약점이 발견된 패키지 이름
      "severity": "high", // 전체 심각도: 높음 (매우 심각)
      "isDirect": false, // 직접 설치한 패키지가 아님 (간접 의존성)
      "via": [ // 취약점 상세 정보
        {
          "source": 1098681, // Github Advisory Database Id
          "name": "micromatch", // 취약점 패키지
          "dependency": "micromatch", // 의존성 이름
          "title": "Regular Expression Denial of Service (ReDoS) in micromatch", // 제목 : micromatch에서 정규 표현식 서비스 거부 공격 취약점
          "url": "https://github.com/advisories/GHSA-952p-6rrq-rcjv", // 취약점 상세 정보 URL (Github Security Advisory)
          "severity": "moderate", // 이 취약점 자체의 심각도: 보통
          "cwe": [
            "CWE-1333" // CWE 분류: 비효율적인 정규표현식 복잡도
          ],
          "cvss": {
            "score": 5.3, // CVSS 점수 (0-10, 높을수록 위험)
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L" // CVSS 벡터 스트링
          },
          "range": "<4.0.8" // 취약한 버전 범위: 4.0.8 미만
        },
        "braces" // braces 패키지가 micromatch를 의존함
      ],
      "effects": [ // 영향받는 패키지
        "jscodeshift" // jscodeshift가 이 취약점에 영향받음
      ],
      "range": "<=4.0.7", // 취약한 버전 범위: 4.0.7 이하
      "nodes": [
        "node_modules/jscodeshift/node_modules/micromatch" // 실제 설치된 위치
      ],
      "fixAvailable": false // 자동 수정 불가능
    },
```

여기서 cvss 안에 vectorString이 의미하는 바는 아래와 같다.

```javascript
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

AV:N  (Attack Vector: Network)
      → 네트워크를 통한 원격 공격 가능

AC:L  (Attack Complexity: Low)
      → 공격 복잡도 낮음 (쉽게 공격 가능)

PR:N  (Privileges Required: None)
      → 권한 불필요 (누구나 공격 가능)

UI:N  (User Interaction: None)
      → 사용자 상호작용 불필요 (자동 공격 가능)

S:U   (Scope: Unchanged)
      → 영향 범위가 취약한 컴포넌트에 한정

C:N   (Confidentiality: None)
      → 기밀성 영향 없음 (데이터 유출 없음)

I:N   (Integrity: None)
      → 무결성 영향 없음 (데이터 변조 없음)

A:L   (Availability: Low)
      → 가용성에 낮은 영향 (서비스가 느려지거나 일시적 중단)
```

## 어떻게 취약점을 대응할 수 있을까?

관련 정보들을 보고 취약점을 확인했다면 대응을 시작해야하는데, NPM에서 공식적으로 제공하고 있는 메뉴얼은 3가지 정도가 있다.

### Suggested Update

취약점을 해결하기 위해 특정 버전을 설치하라고 가이드를 해주는 경우가 많다. 취약한 버전을 피해 새롭게 개별로 업데이트를 진행해도 좋은 방법이지만, npm audit fix 명령어로도 취약한 종속성에 대한 호환 가능한 업데이트를 한 번에 자동으로 설치할 수 있다. 

### Semver Warning

현재 사용 중인 패키지와 메이저 버전이 다른 경우에 띄어주는 경고이다. 메이저 버전 업그레이드는 패키지의 인터페이스에 큰 변화가 있을 확률 높기 때문에, 취약점의 Severity나 내용에 따라 업그레이드를 해야할지 판단이 필요하다. 이 경우에는 `npm audit fix`로 업데이트되지는 않고, `npm audit fix`에 `--force` 옵션을 통해 한 번에 업데이트가 가능하다. 다만 프로젝트에 영향을 줄 수 있기 때문에 조심스럽게 접근해야한다.

> `npm audit fix` 명령어를 실행하게 되면, 패키지를 최신 버젼으로 업데이트하는 것이 아닌 `npm audit --json`으로 노출된 `fixAvailable`에 기재된 해결 가능한 버젼으로 변경을 하니 이 부분을 주의하자.

### Manual Review

취약점이 보완된 버전이 나오지 않는 경우에는 Maunal Review로 따로 나오게 되는데, 단순 버전 업데이트로는 해결되지 않기 떄문에 별도의 대응이 필요하다. 이 경우에는 NPM 공식 페이지에서 **4가지** 정도로 해결책을 제시하고 있다.

- **제한적인 사용**

해당 취약점은 특정 운영체제에서 사용할 경우, 또는 특정 함수를 호출할 때만 발생할 수 있기 때문에 그렇지 않은 경우 사용해도 된다.

- **수정사항이 있는 경우 의존하는 패키지를 업데이트하기**

취약점을 해결하는 패치가 존재하지만, 해당 패치에 의존하는 패키지들이 아직 수정된 버전을 포함하도록 업데이트되지 않은 경우가 일부 있다. 해당 패키지 저장소에 PR 또는 MR을 통해 수정된 버전을 사용하도록 요청해야한다.

- **취약점 수정하기**

취약점이 개선된 버전이 아예 나오지 않는 것처럼 해결책이 존재하지 않는 경우, 패키지 저장소에서 PR 또는 MR을 통해 관리자에게 취약점을 해결하는 변경 사항을 제안할 수 있다.

- **해당 패키지 또는 의존하는 패키지에 이슈를 등록하기**

취약점을 직접 수정하거나 의존하는 패키지를 직접 수정하고 싶지 않다면 해당 패키지 또는 의존하는 패키지의 이슈 트래커에 이슈를 등록할 수 있다.

## 이런 취약점들을 무시하면 어떤 일이 발생할까?

정말 많은 보안 위험유형이 존재하지만 대표적으로 몇가지 예시를 들어보자면 아래와 같다.

### Prototype Pollution

자바스크립트의 프로토타입 체인을 조작해 모든 객체에 영향을 주는 값을 주입하는 공격이다.

```javascript
{}.isAdmin === true
```

위와 같은 상태가 만들어질 수 있고, 그 결과로 권한 체크 로직을 우회하거나, 인증되지 않는 사용자로 접근하는 등의 보안 사고로 이어질 수 있다. 이를테면 lodash, minimist, qs, deepmerge 등의 자바스크립트 라이브러리에서 많이 보일 수 있는 형태이다.

### XSS (Cross-Site Scrpition)

XSS는 공격자가 악성 스크립트를 웹 페이지에 삽입하여 사용자의 브라우저에서 실행시키는 공격이다. 

마크다운을 파서하거나, 템플릿 엔진, WYSIWYG 에디터와 같이 DOM에서 엘리먼트를 직접적으로 다루는 경우에 발생할 수 있다. 

악의적인 사용자에 의해 HTML/JS가 그대로 렌더링되면서 세션 쿠키 및 사용자 계정이 탈취되거나, 악성 사이트로 리다이렉트되는 보안사고로 이어질 수 있다.

### Arbitrary File Access

Arbitrary File Read/Write은 공격자가 서버의 임의 파일을 읽거나 수정할 수 있는 취약점 및 공격이다. 

압축 해제 라이브러리(tar, unzip), 파일 업로드 관련 패키지, path 처리 유틸과 같은 패키지에서 발생할 수 있다.

.env, API Key과 노출되거나, 설정 파일의 악의적인 변경, 인증 정보 탈취 등의 보안 사고로 이어질 수 있다.

**이외에 발견된 취약점 및 위험을 사전에 조치를 하지 않게 되면 내부 어플리케이션에 SSRF, Supply Chain Attack 등의 다양한 보안 위험으로 이어질 수 있다.**

## 실제로 취약점 대응해보기

사내 백오피스 프론트엔드 프로젝트는 node 20 버전과 npm 10 버전을 사용하고 있으며, `npm audit —fix` 명령어를 통해 취약점을 조치해보았을 때, 아래와 같은 결과를 얻을 수 있었다.

![Image](https://upload.cafenono.com/image/slashpagePost/20251229/161931_t87I1dfatLunDwTT7T?q=80&s=1280x180&t=outside&f=webp)

![Image](https://upload.cafenono.com/image/slashpagePost/20251229/162011_nxxN6U1sgnEIClF6pj?q=80&s=1280x180&t=outside&f=webp)

그리고 `fixAvailble` 한 취약점의 경우 조치를 하기 위해 `npm audit —fix` 명령어를 1차적으로 수행했고, 아래와 같은 결과가 나왔다. 고쳐지면서 또 다른 버젼으로 이동하며 또 다른 취약점이 나온 것 같다.

![Image](https://upload.cafenono.com/image/slashpagePost/20251229/162108_HLXA1FUC94ictmMCIN?q=80&s=1280x180&t=outside&f=webp)

기존 백오피스 프론트엔드 프로젝트 같은 경우 `vue-cli` 를 통해 스캐폴딩 된 프로젝트였기 때문에, 웹팩 기반의 플러그인을 많이 사용하는 (일부는 `Deprecated` 된 플러그인도 존재한다) 번들링 코드베이스를 `vite` 로 전환하고자 하는 계획이 있었다. 

취약점 관련하여 `high` 와 `critical` 의 취약점 대부분 `vue-cli` 와 이에 따른 파생 플러그인에 대한 내용들이 많았기 때문에 `vue-cli` 를 걷어내고 `vite` 번들러로 전환을 진행했다. 진행하면서 `npm audit —json` 을 통해 어떤 요소에 어떤 위험성과 수정 가능 여부가 존재하는지 확인할 수 있었고, 일부는 버전 업데이트를 통해 빠르게 해결할 수 있었다. (메이저 버젼을 업데이트 해야하는 경우에는 코드 베이스가 많이 달라지는지 확인하는 과정도 거쳤다)

```javascript
// 아래와 같이 자동으로 수정할 수 있는 경우에는 fixAvailable 란에 버젼과 이름이 노출된다.
    "fixAvailable": {
        "name": "vitest",
        "version": "4.0.16",
        "isSemVerMajor": true
      }
```

취약점을 고치다가, 내가 프로젝트 안에 설치한 패키지가 아님에도 취약점으로 뜨는 경우에는 이 패키지가 어떤 패키지에 존재하는지 아래 명령어를 통해 빠르게 파악할 수 있다.

```javascript
npm ls { 취약점 패키지 이름 }
```

![Image](https://upload.cafenono.com/image/slashpagePost/20251229/162638_dYOwSsaew5Y2zjz7ZI?q=80&s=1280x180&t=outside&f=webp)

최종적으로 `high` , `critical` 위험성의 취약점은 모두 해결했지만, 현재 백오피스 내에 `CKEditor` 와 같은 리치에디터의 경우 확정된 패키지가 아니다보니 언제든 변경 가능성이 있어 `CKEditor` 패키지는 제외하고 모두 해결을 하였다. `CKEditor` 관련 플러그인이 워낙 많다보니 `moderate` 로 발생하는 취약점 갯수도 생각보다 많은 편이다.

처음에는 패키지를 일일히 따라가는 과정이나, PR을 하나하나 찾아보는 과정들이 많이 복잡했는데 최근 보안 위험성에 대한 이슈가 대두되면서 패키지 취약점을 대응하는 이슈가 생긴다면 이러한 과정들을 녹여 빠르게 해결할 수 있을 것 같다.

![Image](https://upload.cafenono.com/image/slashpagePost/20251229/163349_XzRh3Yf6hWsPlUJU77?q=80&s=1280x180&t=outside&f=webp)

For the site tree, see the [root Markdown](https://slashpage.com/timmy.md).
