CVE-2020-28461 (published 2022-07-25T14:15Z)

This affects the package js-ini before 1.3.0. If an attacker submits a malicious INI file to an application that parses it with parse , they will pollute the prototype on the application. This can be exploited further depending on the context.

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: CWE-1321
cwe_name: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')
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.
extended_description: 
				By adding or modifying attributes of an object prototype, it is possible to create attributes that exist on every object, or replace critical attributes with malicious ones. This can be problematic if the product depends on existence or non-existence of certain attributes, or uses pre-defined attributes of object prototype (such as hasOwnProperty, toString or valueOf).
				This weakness is usually exploited by using a special attribute of objects called proto,  constructor or prototype. Such attributes give access to the object prototype. This weakness is often found in code that assigns object attributes based on user input, or merges or clones objects recursively.
			
			

Fix information:
repo_url: https://github.com/Sdju/js-ini
commit_details: repo_url: https://github.com/Sdju/js-ini
author: zede
author_date: 2020-12-22 20:54:05+05:00
msg: refactoring and new functional
repository: repo_url: https://github.com/Sdju/js-ini
repo_name: Sdju/js-ini
description: parsing and serialize ini files
repo_language: TypeScript
owner: Sdju
file_changes: filename: package-lock.json
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,6 @@
 {
   "name": "js-ini",
-  "version": "1.1.5",
+  "version": "1.2.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -8362,9 +8362,9 @@
       "dev": true
     },
     "typescript": {
-      "version": "3.4.3",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.3.tgz",
-      "integrity": "sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ==",
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz",
+      "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==",
       "dev": true
     },
     "uglify-js": {

programming_language: HTML
filename: package.json
change_type: ModificationType.MODIFY
diff: @@ -38,7 +38,7 @@
     "husky": "^4.2.3",
     "jest": "^22.4.3",
     "ts-jest": "^22.4.2",
-    "typescript": "3.4.3"
+    "typescript": "^4.1.3"
   },
   "jest": {
     "globals": {
@@ -50,7 +50,7 @@
       "^.+\\.(ts|tsx)$": "ts-jest"
     },
     "testMatch": [
-      "**/test/*.+(ts|tsx|js)"
+      "**/test/**/*.+(ts|tsx|js)"
     ],
     "moduleFileExtensions": [
       "ts",

programming_language: JavaScript
filename: errors-symbol.ts
change_type: ModificationType.ADD
diff: @@ -0,0 +1 @@
+export const $Errors: unique symbol = Symbol('Errors of parsing');

programming_language: SQL
filename: index.ts
change_type: ModificationType.ADD
diff: @@ -0,0 +1,3 @@
+export * from './parsing-error';
+export * from './proto-error';
+export * from './errors-symbol';

programming_language: TypeScript
filename: parsing-error.ts
change_type: ModificationType.RENAME
diff: @@ -1,8 +1,6 @@
-export const $Errors: unique symbol = Symbol('Errors of parsing');
-
 export class ParsingError extends Error {
   constructor(line: string, lineNumber: number) {
-    super(`Unsupported type of line: [${lineNumber}]"${line}"`);
+    super(`Unsupported type of line: [${lineNumber}] "${line}"`);
     this.line = line;
     this.lineNumber = lineNumber;
   }

programming_language: TypeScript
filename: proto-error.ts
change_type: ModificationType.ADD
diff: @@ -0,0 +1,8 @@
+export class ProtoError extends Error {
+  constructor(lineNumber: number) {
+    super(`Unsupported section name "__proto__": [${lineNumber}]"`);
+    this.lineNumber = lineNumber;
+  }
+
+  public lineNumber: number;
+}

programming_language: TypeScript
filename: index.ts
change_type: ModificationType.MODIFY
diff: @@ -1,3 +1,4 @@
 export * from './errors';
 export * from './parse';
 export * from './stringify';
+export * from './proto';

programming_language: TypeScript
filename: custom-typing.ts
change_type: ModificationType.MODIFY
diff: @@ -1,3 +1,3 @@
 export interface ICustomTyping {
-  (val: string, section: string, key: string): any
+  (val: string, section: string | symbol, key: string): any
 }

programming_language: TypeScript
filename: ini-object.ts
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,8 @@
 import { $Errors, ParsingError } from '../errors';
 import { IIniObjectSection } from './ini-object-section';
+import { $Proto } from '../proto';
 
 export interface IIniObject extends IIniObjectSection {
   [$Errors]?: ParsingError[];
+  [$Proto]?: IIniObjectSection;
 }

programming_language: TypeScript
filename: parse.ts
change_type: ModificationType.MODIFY
diff: @@ -1,9 +1,14 @@
-import { $Errors, ParsingError } from './errors';
+import {
+  $Errors,
+  ParsingError,
+  ProtoError,
+} from './errors';
 import { IIniObject } from './interfaces/ini-object';
 import { IniValue } from './types/ini-value';
 import { IIniObjectSection } from './interfaces/ini-object-section';
 import { autoType } from './helpers/auto-type';
 import { ICustomTyping } from './interfaces/custom-typing';
+import { $Proto } from './proto';
 
 export interface IParseConfig {
   comment?: string;
@@ -11,6 +16,7 @@ export interface IParseConfig {
   nothrow?: boolean;
   autoTyping?: boolean | ICustomTyping;
   dataSections?: string[];
+  protoSymbol?: boolean;
 }
 
 const sectionNameRegex = /\[(.*)]$/;
@@ -22,6 +28,7 @@ export function parse(data: string, params?: IParseConfig): IIniObject {
     nothrow = false,
     autoTyping = true,
     dataSections = [],
+    protoSymbol = false,
   } = { ...params };
   let typeParser: ICustomTyping;
   if (typeof autoTyping === 'function') {
@@ -45,9 +52,16 @@ export function parse(data: string, params?: IParseConfig): IIniObject {
       const match = line.match(sectionNameRegex);
       if (match !== null) {
         currentSection = match[1].trim();
+        if (currentSection === '__proto__') {
+          if (protoSymbol) {
+            currentSection = <string><any> $Proto;
+          } else {
+            throw new ProtoError(lineNumber);
+          }
+        }
         isDataSection = dataSections.includes(currentSection);
         if (!(currentSection in result)) {
-          result[currentSection] = (isDataSection) ? [] : {};
+          result[currentSection] = (isDataSection) ? [] : Object.create(null);
         }
         continue;
       }

programming_language: TypeScript
filename: proto.ts
change_type: ModificationType.ADD
diff: @@ -0,0 +1 @@
+export const $Proto: unique symbol = Symbol('__proto__');

programming_language: SQL
filename: tests.ts
change_type: ModificationType.MODIFY
diff: @@ -1,27 +1,35 @@
-import { parse, stringify } from '../src';
+import {
+  parse,
+  stringify,
+  $Proto,
+  $Errors,
+  ParsingError,
+} from '../src';
 
 const ini1 = `v1 = 2
 v-2=true
 v 3 = string
 [smbd]
 v1=5
-v2 = what 
+v2 = what
 ;comment
 v5 = who is who = who
 
 [test scope with spaces]
 mgm*1  = 2.5`;
+
 const ini2 = `v1 : 2
 v-2:true
 v 3 : string
 [smbd]
 v1:5
-v2 : what 
+v2 : what
 #comment
 v5 : who is who = who
 
 [test scope with spaces]
 mgm*1  : 2.5`;
+
 const ini3 = `v1=2
 v-2=true
 v 3=string
@@ -33,6 +41,7 @@ v5=who is who = who
 
 [test scope with spaces]
 mgm*1=2.5`;
+
 const ini4 = `v1: 2
 v-2: true
 v 3: string
@@ -42,6 +51,7 @@ v2: what
 v5: who is who = who
 [test scope with spaces]
 mgm*1: 2.5`;
+
 const ini5 = `v1: 2
 v-2: true
 v 3: string
@@ -53,6 +63,24 @@ v1: 5
 b1c,wdwd,15:68
 wx/w':wwdlw,:d,wld
 efkeofk`;
+
+const ini6 = `
+[ __proto__  ]
+polluted = "polluted"`;
+
+const ini7 = `
+[scope with trash]
+ok = value
+trash
+
+[scope with only trash]
+only trash
+
+[empty scope]
+[normal scope]
+ok = value
+`;
+
 const v1 = {
   v1: 2,
   'v-2': true,
@@ -153,3 +181,36 @@ test('ini stringify', () => {
     autoTyping: false,
   })).toEqual(v3);
 });
+
+test('ini parsing: proto', () => {
+  expect(() => parse(ini6))
+    .toThrow('Unsupported section name "__proto__": [2]"');
+
+  expect(parse(ini6, { protoSymbol: true }))
+    .toEqual({
+      [$Proto]: {
+        polluted: '"polluted"',
+      },
+    });
+});
+
+test('ini parsing: errors', () => {
+  expect(() => parse(ini7))
+    .toThrow('Unsupported type of line: [4] "trash"');
+
+  expect(parse(ini7, { nothrow: true }))
+    .toEqual({
+      'scope with trash': {
+        ok: 'value',
+      },
+      'scope with only trash': {},
+      'empty scope': {},
+      'normal scope': {
+        ok: 'value',
+      },
+      [$Errors]: [
+        new ParsingError('trash', 4),
+        new ParsingError('only trash', 7),
+      ],
+    });
+});

programming_language: TypeScript