浏览代码

[ui-security] add xss fix for select2 via ko-binding

* [ui-security] add xss fix for select2 via ko-binding

* [ui-security] remove unused select2 from package.json

* [ui-security] clean up test

* [ui] update comment in select2.full.patched.js
Bjorn Alm - personal 2 月之前
父节点
当前提交
8c86920b83

+ 16 - 0
desktop/core/src/desktop/js/ko/bindings/ko.select2.js

@@ -16,6 +16,18 @@
 
 import $ from 'jquery';
 import * as ko from 'knockout';
+import deXSS from 'utils/html/deXSS';
+
+// Export the security fix function for testing
+export function applySelect2SecurityFix(options) {
+  const originalFormatResult = options.formatResult;
+  options.formatResult = function(data) {
+    if (data && typeof data.text === 'string') {
+      data.text = deXSS(data.text);
+    }
+    return (originalFormatResult ? originalFormatResult.call(this, data) : (data ? data.text : undefined));
+  };
+}
 
 // TODO: Depends on Role
 
@@ -118,6 +130,10 @@ ko.bindingHandlers.select2 = {
         }
       }
     }
+    // XSS FIX: Apply HTML escaping to Select2 options when used via Knockout Options binding
+    // E.g. <select data-bind="options: model.dataX, select2: {...}"></select>
+    applySelect2SecurityFix(options);
+    
     $element
       .select2(options)
       .on('change', e => {

+ 82 - 0
desktop/core/src/desktop/js/ko/bindings/ko.select2.test.js

@@ -0,0 +1,82 @@
+import * as ko from 'knockout';
+import { applySelect2SecurityFix } from './ko.select2';
+
+describe('ko.select2.js - XSS Security Fix', () => {
+
+  it('should apply security fix that sanitizes HTML in formatResult function', () => {
+    // Test the exported security fix function directly
+    const options = { placeholder: 'Test' };
+    
+    // Apply the security fix
+    applySelect2SecurityFix(options);
+    
+    // Verify that formatResult function was created
+    expect(options).toHaveProperty('formatResult');
+    expect(typeof options.formatResult).toBe('function');
+
+    // Test the formatResult function
+    const formatResult = options.formatResult;
+    
+    // Test with malicious script - should be sanitized by deXSS
+    const maliciousData = { text: '<script>alert("xss")</script>' };
+    const result = formatResult(maliciousData);
+    expect(result).toBe(''); // deXSS removes script tags completely
+    
+    // Test with safe HTML - should be preserved
+    const safeData = { text: '<b>Bold text</b>' };
+    const safeResult = formatResult(safeData);
+    expect(safeResult).toBe('<b>Bold text</b>'); // Safe HTML is preserved
+    
+    // Test with mixed content - script removed, safe text preserved
+    const mixedData = { text: '<script>alert("xss")</script>Safe text' };
+    const mixedResult = formatResult(mixedData);
+    expect(mixedResult).toBe('Safe text'); // Script removed, safe text preserved
+
+    // Test with null/undefined data - should handle gracefully
+    expect(formatResult(null)).toBeUndefined();
+    expect(formatResult({ id: '1' })).toBeUndefined(); // No text property
+  });
+
+  it('should preserve original formatResult behavior when provided', () => {
+    // Test that if there's an original formatResult function, it's called properly
+    const originalFormatResult = jest.fn((data) => `Original: ${data.text}`);
+    const options = { 
+      placeholder: 'Test',
+      formatResult: originalFormatResult
+    };
+    
+    // Apply the security fix
+    applySelect2SecurityFix(options);
+    
+    const formatResult = options.formatResult;
+    
+    // Test with safe data - should call original function with sanitized data
+    const safeData = { text: '<b>Bold text</b>' };
+    const result = formatResult(safeData);
+    
+    // Should call original function and get its result
+    expect(originalFormatResult).toHaveBeenCalledWith(safeData);
+    expect(result).toBe('Original: <b>Bold text</b>');
+    
+    // Test with malicious data - should sanitize before calling original
+    const maliciousData = { text: '<script>alert("xss")</script>Malicious' };
+    originalFormatResult.mockClear();
+    formatResult(maliciousData);
+    
+    // Original function should be called with sanitized data
+    expect(originalFormatResult).toHaveBeenCalledWith({ text: 'Malicious' });
+  });
+
+  it('should verify the binding exists and contains the security fix', () => {
+    // Verify that the binding exists and has the security fix
+    expect(ko.bindingHandlers.select2).toBeDefined();
+    expect(typeof ko.bindingHandlers.select2.init).toBe('function');
+    
+    // Check that the security fix is present in the source code.
+    // Not the ideal way to test this but mocking all required jquery and select2 
+    // would have been very complex for this test.
+    const initFunctionString = ko.bindingHandlers.select2.init.toString();
+    expect(initFunctionString).toContain('applySelect2SecurityFix');
+  });
+
+});

+ 4 - 0
desktop/core/src/desktop/static/desktop/js/select2.full.patched.js

@@ -16,6 +16,10 @@
 
 // see https://github.com/select2/select2/issues/3992 for the patch, hopefully it's gonna be fixed in a next release of Select2
 
+// Note on Cross-site scripting (XSS)
+// This legacy version of select2 is only used via the Knockout.js binding in ko.select2.js
+// where a patch for XSS is applied. See CVE-2016-10744.
+
 /*!
  * Select2 4.0.3
  * https://select2.github.io

+ 0 - 6
package-lock.json

@@ -55,7 +55,6 @@
         "regenerator-runtime": "0.13.7",
         "removeNPMAbsolutePaths": "1.0.6",
         "sanitize-html": "2.13.0",
-        "select2": "4.0.13",
         "selectize-plugin-clear": "0.0.3",
         "sprintf-js": "1.1.2",
         "sql-formatter": "12.2.1",
@@ -17332,11 +17331,6 @@
       "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
       "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0="
     },
-    "node_modules/select2": {
-      "version": "4.0.13",
-      "resolved": "https://registry.npmjs.org/select2/-/select2-4.0.13.tgz",
-      "integrity": "sha512-1JeB87s6oN/TDxQQYCvS5EFoQyvV6eYMZZ0AeA4tdFDYWN3BAGZ8npr17UBFddU0lgAt3H0yjX3X6/ekOj1yjw=="
-    },
     "node_modules/selectize-plugin-clear": {
       "version": "0.0.3",
       "resolved": "https://registry.npmjs.org/selectize-plugin-clear/-/selectize-plugin-clear-0.0.3.tgz",

+ 0 - 1
package.json

@@ -76,7 +76,6 @@
     "regenerator-runtime": "0.13.7",
     "removeNPMAbsolutePaths": "1.0.6",
     "sanitize-html": "2.13.0",
-    "select2": "4.0.13",
     "selectize-plugin-clear": "0.0.3",
     "sprintf-js": "1.1.2",
     "sql-formatter": "12.2.1",