CVE-2021-23568: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') (CWE-1321)
value: The package extend2 before 1.0.1 are vulnerable to Prototype Pollution via the extend function due to unsafe recursive merge.
CWE description: The product receives input from an upstream component that specifies attributes that are to be initialized or updated in an object, but it does not properly control modifications of attributes of the object prototype.
CVSS v3 base score: 9.8
Published: 2022-01-10T14:10Z
Repository: https://github.com/eggjs/extend2
Language: JavaScript
Files changed: .github/workflows/nodejs.yml, index.js, package.json, test/index.js
Commit date: 2021-12-31 21:19:26 +0800
Commit message: fix: __proto__ copy (#2)


Diff: diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml
new file mode 100644
index 0000000..ad28488
--- /dev/null
+++ b/.github/workflows/nodejs.yml
@@ -0,0 +1,46 @@
+# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
+
+name: Node.js CI
+
+on:
+  push:
+    branches:
+      - main
+      - master
+  pull_request:
+    branches:
+      - main
+      - master
+  schedule:
+    - cron: '0 2 * * *'
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      fail-fast: false
+      matrix:
+        node-version: [6, 8, 10, 12, 14, 16]
+        os: [ubuntu-latest, windows-latest, macos-latest]
+
+    steps:
+    - name: Checkout Git Source
+      uses: actions/checkout@v2
+
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+
+    - name: Install Dependencies
+      run: npm i -g npminstall@latest-3 && npminstall
+
+    - name: Continuous Integration
+      run: npm run ci
+
+    - name: Code Coverage
+      uses: codecov/codecov-action@v1
+      with:
+        token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 5ec1467..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-sudo: false
-language: node_js
-node_js:
-  - '4'
-  - '6'
-  - '7'
-install:
-  - npm i npminstall && npminstall
-script:
-  - npm run ci
-after_script:
-  - npminstall codecov && codecov
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 2efd0fa..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-environment:
-  matrix:
-    - nodejs_version: '4'
-    - nodejs_version: '6'
-    - nodejs_version: '7'
-
-install:
-  - ps: Install-Product node $env:nodejs_version
-  - npm i npminstall && node_modules\.bin\npminstall
-
-test_script:
-  - node --version
-  - npm --version
-  - npm run ci
-
-build: off
diff --git a/index.js b/index.js
index b4f40f3..4367a37 100644
--- a/index.js
+++ b/index.js
@@ -45,10 +45,12 @@ module.exports = function extend() {
     // Only deal with non-null/undefined values
     if (options == null) continue;
 
     // Extend the base object
     for (name in options) {
+      if (name === '__proto__') continue;
+
       src = target[name];
       copy = options[name];
 
       // Prevent never-ending loop
       if (target === copy) continue;
diff --git a/package.json b/package.json
index 3f4d00b..c5f6c83 100644
--- a/package.json
+++ b/package.json
@@ -23,18 +23,18 @@
     "url": "https://github.com/eggjs/extend2.git"
   },
   "dependencies": {},
   "devDependencies": {
     "covert": "^1.1.0",
-    "egg-ci": "^1.5.0",
     "eslint": "^3.2.2",
     "eslint-config-egg": "^3.2.0",
     "tape": "^4.6.0"
   },
   "license": "MIT",
   "files": [
     "index.js"
   ],
   "ci": {
-    "version": "4, 6, 7"
+    "type": "github",
+    "version": "6, 8, 10, 12, 14, 16"
   }
 }
diff --git a/test/index.js b/test/index.js
index 8c4fd32..e5341c8 100644
--- a/test/index.js
+++ b/test/index.js
@@ -617,5 +617,20 @@ test('works without Array.isArray', function (t) {
     'It works without Array.isArray'
   );
   Array.isArray = savedIsArray;
   t.end();
 });
+
+test('fix __proto__ copy', function (t) {
+  var r = extend(true, {}, JSON.parse('{"__proto__": {"polluted": "yes"}}'));
+  t.deepEqual(
+    JSON.stringify(r),
+    '{}',
+    'It should not copy __proto__'
+  );
+  t.deepEqual(
+    ''.polluted,
+    undefined,
+    'It should not affect object prototype'
+  );
+  t.end();
+});

Vulnerable code: sudo: false
language: node_js
node_js:
  - '4'
  - '6'
  - '7'
install:
  - npm i npminstall && npminstall
script:
  - npm run ci
after_script:
  - npminstall codecov && codecov
environment:
  matrix:
    - nodejs_version: '4'
    - nodejs_version: '6'
    - nodejs_version: '7'

install:
  - ps: Install-Product node $env:nodejs_version
  - npm i npminstall && node_modules\.bin\npminstall

test_script:
  - node --version
  - npm --version
  - npm run ci

build: off
    "egg-ci": "^1.5.0",
    "version": "4, 6, 7"
Fixed code: # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI

on:
  push:
    branches:
      - main
      - master
  pull_request:
    branches:
      - main
      - master
  schedule:
    - cron: '0 2 * * *'

jobs:
  build:
    runs-on: ${{ matrix.os }}

    strategy:
      fail-fast: false
      matrix:
        node-version: [6, 8, 10, 12, 14, 16]
        os: [ubuntu-latest, windows-latest, macos-latest]

    steps:
    - name: Checkout Git Source
      uses: actions/checkout@v2

    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}

    - name: Install Dependencies
      run: npm i -g npminstall@latest-3 && npminstall

    - name: Continuous Integration
      run: npm run ci

    - name: Code Coverage
      uses: codecov/codecov-action@v1
      with:
        token: ${{ secrets.CODECOV_TOKEN }}
      if (name === '__proto__') continue;

    "type": "github",
    "version": "6, 8, 10, 12, 14, 16"

test('fix __proto__ copy', function (t) {
  var r = extend(true, {}, JSON.parse('{"__proto__": {"polluted": "yes"}}'));
  t.deepEqual(
    JSON.stringify(r),
    '{}',
    'It should not copy __proto__'
  );
  t.deepEqual(
    ''.polluted,
    undefined,
    'It should not affect object prototype'
  );
  t.end();
});