CVE-2020-28472 (published 2021-01-19T11:15Z)

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.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/aws/aws-sdk-js-v3
commit_details: repo_url: https://github.com/aws/aws-sdk-js-v3
author: AllanZhengYP
author_date: 2020-12-11 12:55:54-08:00
msg: fix(shared-ini-file-loader): ignore prohibited profile name (#1764)

* fix(shared-ini-file-loader): ignore prohibited profile name

* fix: address feedbacks
repository: repo_url: https://github.com/aws/aws-sdk-js-v3
repo_name: aws/aws-sdk-js-v3
description: Modularized AWS SDK for JavaScript.
repo_language: TypeScript
owner: aws
file_changes: filename: index.spec.ts
change_type: ModificationType.MODIFY
diff: @@ -289,6 +289,20 @@ aws_session_token = ${FOO_CREDS.sessionToken}`.trim()
         },
       });
     });
+
+    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", () => {
@@ -527,5 +541,19 @@ aws_session_token = ${FOO_CREDS.sessionToken}`.trim()
         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: {},
+      });
+    });
   });
 });

programming_language: TypeScript
filename: index.ts
change_type: ModificationType.MODIFY
diff: @@ -36,7 +36,7 @@ export interface SharedConfigFiles {
 
 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"),
@@ -52,10 +52,10 @@ export function loadSharedConfigFiles(init: SharedConfigInit = {}): Promise<Shar
       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;
@@ -71,9 +71,10 @@ 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/)) {
@@ -81,6 +82,9 @@ function parseIni(iniData: string): ParsedIniData {
     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) {
@@ -91,10 +95,10 @@ function parseIni(iniData: string): ParsedIniData {
   }
 
   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);
@@ -103,9 +107,8 @@ function slurpFile(path: string): Promise<string> {
       }
     });
   });
-}
 
-function getHomeDir(): string {
+const getHomeDir = (): string => {
   const { HOME, USERPROFILE, HOMEPATH, HOMEDRIVE = `C:${sep}` } = process.env;
 
   if (HOME) return HOME;
@@ -113,4 +116,4 @@ function getHomeDir(): string {
   if (HOMEPATH) return `${HOMEDRIVE}${HOMEPATH}`;
 
   return homedir();
-}
+};

programming_language: TypeScript