CVE-2015-10021: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: A vulnerability was found in ritterim definely. It has been classified as problematic. Affected is an unknown function of the file src/database.js. The manipulation leads to cross site scripting. It is possible to launch the attack remotely. The name of the patch is b31a022ba4d8d17148445a13ebb5a42ad593dbaa. It is recommended to apply a patch to fix this issue. The identifier of this vulnerability is VDB-217608.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2023-01-07T12:15Z
Repository: https://github.com/ritterim/definely
Language: JavaScript
Files changed: gulpfile.js, src/database.js, src/sanitize.js, src/sanitizeTests.js
Commit date: 2015-01-24 01:07:05 -0500
Commit message: Fix XSS on term-detailed web client view.
Previous behavior would render unencoded HTML in the view based on the term's definition-data.
New behavior encodes HTML-tags in the DB-module via a separate sanitize module.
However, be aware this functionality should be called prior to any markdown-related conversion in the future.
Otherwise, the resulting HTML will be encoded.
Related to https://github.com/ritterim/definely/issues/49.

Also added a new gulp task for running TDD-style tests.
And, incorporated this into the existing test-task.

Diff: diff --git a/gulpfile.js b/gulpfile.js
index da48631..d6d318d 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -2,22 +2,12 @@ var gulp = require('gulp')
 require('traceur/bin/traceur-runtime')
 var $ = require('gulp-load-plugins')({
     lazy: true
 });
 
-
 gulp.task('default', ['serve-dev'])
 
-gulp.task('test', ['build'], function () {
-    return gulp.src('build/**/*Spec.js', {
-            read: false
-        })
-        .pipe($.mocha({
-            reporter: 'nyan'
-        }))
-})
-
 gulp.task('serve-dev', ['build'], function () {
     serve(true)
 })
 
 gulp.task('serve-build', ['build'], function () {
@@ -40,17 +30,25 @@ gulp.task('build', ['clean'], function () {
         .pipe($.traceur({annotations:true, types:true, typeAssertions:true, typeAssertionModule:'../assert'}))
         .pipe($.sourcemaps.write('.'))
         .pipe(gulp.dest('build'))
 })
 
-gulp.task('test', ['build'], function () {
+gulp.task('test', ['build', 'tdd-tests'], function () {
     return gulp.src('build/**/*Spec.js', {
             read: false
         })
         .pipe($.mocha())
 })
 
+gulp.task('tdd-tests', ['build'], function() {
+    return gulp.src('build/**/*Tests.js', {
+        read: false
+    }).pipe($.mocha({
+        ui: 'tdd'
+    }));
+})
+
 function serve(isDev) {
     require('./build')
 
     var debug = true //args.debug || args.debugBrk
 
diff --git a/src/database.js b/src/database.js
index 7c256aa..6bfed5c 100644
--- a/src/database.js
+++ b/src/database.js
@@ -1,12 +1,14 @@
 import pg from 'pg';
 import Term from './models/Term'
+import Sanitize from './sanitize'
 
 export
 default class Database {
     constructor(connectionUri) {
         this.connectionUri = connectionUri;
+        this.sanitize = new Sanitize();
     };
 
     add(term, callback) {
         pg.connect(this.connectionUri, function (err, client, done) {
             if (err) {
@@ -25,10 +27,12 @@ default class Database {
             });
         });
     };
 
     find(id, callback) {
+        var self = this;
+
         pg.connect(this.connectionUri, function (err, client, done) {
             if (err) {
                 return console.error('Could not connect to postgres', err);
                 done(client);
             }
@@ -42,18 +46,20 @@ default class Database {
 
                     done();
                     var term = null
                     if (result.rows.length > 0) {
                         var row = result.rows[0]
-                        term = new Term(row.id, row.term, row.definition, row.tags || undefined)
+                        term = new Term(row.id, row.term, self.sanitize.htmlSanitize(row.definition), row.tags || undefined)
                     }
                     callback(term);
                 });
         });
     };
 
     search(searchTerm, callback) {
+        var self = this;
+
         pg.connect(this.connectionUri, function (err, client, done) {
             if (err) {
                 return console.error('Could not connect to postgres', err);
                 done(client);
             }
@@ -62,11 +68,11 @@ default class Database {
             searchTerm = (searchTerm || '').trim().replace(/\s+/g, ' | ');
 
             if (!searchTerm) {
                 client.query('select id, term, tags, definition from terms', function (err, result) {
 
-                    var terms = result.rows.map(row => new Term(row.id, row.term, row.definition, row.tags || undefined))
+                    var terms = result.rows.map(row => new Term(row.id, row.term, self.sanitize.htmlSanitize(row.definition), row.tags || undefined))
                     callback(terms)
                 })
             } else {
                 client.query('select id, term, tags, definition, rank from terms, to_tsquery($1) as query, ts_rank_cd(weightedVector, query) as rank where weightedVector @@ query order by rank desc;', [searchTerm],
                     function (err, result) {
@@ -74,11 +80,11 @@ default class Database {
                             return console.error('Error running query', err);
                             done(client);
                         }
 
                         done();
-                        var terms = result.rows.map(row => new Term(row.id, row.term, row.definition, row.tags || undefined))
+                        var terms = result.rows.map(row => new Term(row.id, row.term, self.sanitize.htmlSanitize(row.definition), row.tags || undefined))
                         callback(terms);
                     })
             }
         })
     }
diff --git a/src/sanitize.js b/src/sanitize.js
new file mode 100644
index 0000000..c9fb719
--- /dev/null
+++ b/src/sanitize.js
@@ -0,0 +1,15 @@
+export default class Sanitize {
+    constructor() {
+    }
+
+    /**
+     * This function replaces all instances of lt and gt with their
+     * HTML entity.
+     */
+    htmlSanitize(html) {
+        html = html.replace(/</g, '&lt;');
+        html = html.replace(/>/g, '&gt;');
+
+        return html;
+    }
+}
diff --git a/src/sanitizeTests.js b/src/sanitizeTests.js
new file mode 100644
index 0000000..2f7ffe2
--- /dev/null
+++ b/src/sanitizeTests.js
@@ -0,0 +1,13 @@
+import assert from 'assert'
+import sut from './sanitize'
+
+suite('Sanitize', function() {
+    suite('#htmlSanitize()', function() {
+        test('should encode all lt and gt-symbols', function() {
+            var result = new sut()
+                .htmlSanitize('<test></test>');
+
+            assert.equal('&lt;test&gt;&lt;/test&gt;', result);
+        });
+    });
+});

Vulnerable code: 
gulp.task('test', ['build'], function () {
    return gulp.src('build/**/*Spec.js', {
            read: false
        })
        .pipe($.mocha({
            reporter: 'nyan'
        }))
})

gulp.task('test', ['build'], function () {
                        term = new Term(row.id, row.term, row.definition, row.tags || undefined)
                    var terms = result.rows.map(row => new Term(row.id, row.term, row.definition, row.tags || undefined))
                        var terms = result.rows.map(row => new Term(row.id, row.term, row.definition, row.tags || undefined))
Fixed code: gulp.task('test', ['build', 'tdd-tests'], function () {
gulp.task('tdd-tests', ['build'], function() {
    return gulp.src('build/**/*Tests.js', {
        read: false
    }).pipe($.mocha({
        ui: 'tdd'
    }));
})

import Sanitize from './sanitize'
        this.sanitize = new Sanitize();
        var self = this;

                        term = new Term(row.id, row.term, self.sanitize.htmlSanitize(row.definition), row.tags || undefined)
        var self = this;

                    var terms = result.rows.map(row => new Term(row.id, row.term, self.sanitize.htmlSanitize(row.definition), row.tags || undefined))
                        var terms = result.rows.map(row => new Term(row.id, row.term, self.sanitize.htmlSanitize(row.definition), row.tags || undefined))
export default class Sanitize {
    constructor() {
    }

    /**
     * This function replaces all instances of lt and gt with their
     * HTML entity.
     */
    htmlSanitize(html) {
        html = html.replace(/</g, '&lt;');
        html = html.replace(/>/g, '&gt;');

        return html;
    }
}
import assert from 'assert'
import sut from './sanitize'

suite('Sanitize', function() {
    suite('#htmlSanitize()', function() {
        test('should encode all lt and gt-symbols', function() {
            var result = new sut()
                .htmlSanitize('<test></test>');

            assert.equal('&lt;test&gt;&lt;/test&gt;', result);
        });
    });
});