CVE-2020-28472: Insufficient Information (NVD-CWE-noinfo)
value: This affects the package @aws-sdk/shared-ini-file-loader before 1.0.0-rc.9; the package aws-sdk before 2.814.0. If an attacker submits a malicious INI file to an application that parses it with loadSharedConfigFiles , they will pollute the prototype on the application. This can be exploited further depending on the context.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 9.8
Published: 2021-01-19T11:15Z
Repository: https://github.com/aws/aws-sdk-js-v3
Language: TypeScript
Files changed: packages/shared-ini-file-loader/src/index.spec.ts, packages/shared-ini-file-loader/src/index.ts
Commit date: 2020-12-11 12:55:54 -0800
Commit message: fix(shared-ini-file-loader): ignore prohibited profile name (#1764)

* fix(shared-ini-file-loader): ignore prohibited profile name

* fix: address feedbacks
Diff: diff --git a/packages/shared-ini-file-loader/src/index.spec.ts b/packages/shared-ini-file-loader/src/index.spec.ts
index 8e80ba40a5..b4235235e6 100644
--- a/packages/shared-ini-file-loader/src/index.spec.ts
+++ b/packages/shared-ini-file-loader/src/index.spec.ts
@@ -287,10 +287,24 @@ aws_session_token = ${FOO_CREDS.sessionToken}`.trim()
         credentialsFile: {
           default: parsed.default,
         },
       });
     });
+
+    it("should ignore profile name in block list", async () => {
+      __addMatcher(
+        DEFAULT_PATH,
+        `
+[__proto__]
+foo = not_exist`.trim()
+      );
+
+      expect(await loadSharedConfigFiles()).toEqual({
+        configFile: {},
+        credentialsFile: {},
+      });
+    });
   });
 
   describe("shared config file", () => {
     const SIMPLE_CONFIG_FILE = `[default]
 aws_access_key_id = ${DEFAULT_CREDS.accessKeyId}
@@ -525,7 +539,21 @@ aws_session_token = ${FOO_CREDS.sessionToken}`.trim()
       expect(await loadSharedConfigFiles()).toEqual({
         credentialsFile: {},
         configFile: { default: parsed.default },
       });
     });
+
+    it("should ignore profile name in block list", async () => {
+      __addMatcher(
+        DEFAULT_PATH,
+        `
+[profile __proto__]
+foo = not_exist`.trim()
+      );
+
+      expect(await loadSharedConfigFiles()).toEqual({
+        configFile: {},
+        credentialsFile: {},
+      });
+    });
   });
 });
diff --git a/packages/shared-ini-file-loader/src/index.ts b/packages/shared-ini-file-loader/src/index.ts
index f04d61541b..818208aedb 100644
--- a/packages/shared-ini-file-loader/src/index.ts
+++ b/packages/shared-ini-file-loader/src/index.ts
@@ -34,11 +34,11 @@ export interface SharedConfigFiles {
   configFile: ParsedIniData;
 }
 
 const swallowError = () => ({});
 
-export function loadSharedConfigFiles(init: SharedConfigInit = {}): Promise<SharedConfigFiles> {
+export const loadSharedConfigFiles = (init: SharedConfigInit = {}): Promise<SharedConfigFiles> => {
   const {
     filepath = process.env[ENV_CREDENTIALS_PATH] || join(getHomeDir(), ".aws", "credentials"),
     configFilepath = process.env[ENV_CONFIG_PATH] || join(getHomeDir(), ".aws", "config"),
   } = init;
 
@@ -50,14 +50,14 @@ export function loadSharedConfigFiles(init: SharedConfigInit = {}): Promise<Shar
     return {
       configFile,
       credentialsFile,
     };
   });
-}
+};
 
 const profileKeyRegex = /^profile\s(["'])?([^\1]+)\1$/;
-function normalizeConfigFile(data: ParsedIniData): ParsedIniData {
+const normalizeConfigFile = (data: ParsedIniData): ParsedIniData => {
   const map: ParsedIniData = {};
   for (const key of Object.keys(data)) {
     let matches: Array<string> | null;
     if (key === "default") {
       map.default = data.default;
@@ -69,48 +69,51 @@ function normalizeConfigFile(data: ParsedIniData): ParsedIniData {
       }
     }
   }
 
   return map;
-}
+};
 
-function parseIni(iniData: string): ParsedIniData {
+const profileNameBlockList = ["__proto__", "profile __proto__"];
+const parseIni = (iniData: string): ParsedIniData => {
   const map: ParsedIniData = {};
   let currentSection: string | undefined;
   for (let line of iniData.split(/\r?\n/)) {
     line = line.split(/(^|\s)[;#]/)[0]; // remove comments
     const section = line.match(/^\s*\[([^\[\]]+)]\s*$/);
     if (section) {
       currentSection = section[1];
+      if (profileNameBlockList.includes(currentSection)) {
+        throw new Error(`Found invalid profile name "${currentSection}"`);
+      }
     } else if (currentSection) {
       const item = line.match(/^\s*(.+?)\s*=\s*(.+?)\s*$/);
       if (item) {
         map[currentSection] = map[currentSection] || {};
         map[currentSection][item[1]] = item[2];
       }
     }
   }
 
   return map;
-}
+};
 
-function slurpFile(path: string): Promise<string> {
-  return new Promise((resolve, reject) => {
+const slurpFile = (path: string): Promise<string> =>
+  new Promise((resolve, reject) => {
     readFile(path, "utf8", (err, data) => {
       if (err) {
         reject(err);
       } else {
         resolve(data);
       }
     });
   });
-}
 
-function getHomeDir(): string {
+const getHomeDir = (): string => {
   const { HOME, USERPROFILE, HOMEPATH, HOMEDRIVE = `C:${sep}` } = process.env;
 
   if (HOME) return HOME;
   if (USERPROFILE) return USERPROFILE;
   if (HOMEPATH) return `${HOMEDRIVE}${HOMEPATH}`;
 
   return homedir();
-}
+};

Vulnerable code: export function loadSharedConfigFiles(init: SharedConfigInit = {}): Promise<SharedConfigFiles> {
}
function normalizeConfigFile(data: ParsedIniData): ParsedIniData {
}
function parseIni(iniData: string): ParsedIniData {
}
function slurpFile(path: string): Promise<string> {
  return new Promise((resolve, reject) => {
}
function getHomeDir(): string {
}
Fixed code: 
    it("should ignore profile name in block list", async () => {
      __addMatcher(
        DEFAULT_PATH,
        `
[__proto__]
foo = not_exist`.trim()
      );

      expect(await loadSharedConfigFiles()).toEqual({
        configFile: {},
        credentialsFile: {},
      });
    });

    it("should ignore profile name in block list", async () => {
      __addMatcher(
        DEFAULT_PATH,
        `
[profile __proto__]
foo = not_exist`.trim()
      );

      expect(await loadSharedConfigFiles()).toEqual({
        configFile: {},
        credentialsFile: {},
      });
    });
export const loadSharedConfigFiles = (init: SharedConfigInit = {}): Promise<SharedConfigFiles> => {
};
const normalizeConfigFile = (data: ParsedIniData): ParsedIniData => {
};
const profileNameBlockList = ["__proto__", "profile __proto__"];
const parseIni = (iniData: string): ParsedIniData => {
      if (profileNameBlockList.includes(currentSection)) {
        throw new Error(`Found invalid profile name "${currentSection}"`);
      }
};
const slurpFile = (path: string): Promise<string> =>
  new Promise((resolve, reject) => {
const getHomeDir = (): string => {
};