CVE-2016-2515 (published 2016-04-13T16:59Z)

Hawk before 3.1.3 and 4.x before 4.1.1 allow remote attackers to cause a denial of service (CPU consumption or partial outage) via a long (1) header or (2) URI that is matched against an improper regular expression.

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

Weakness information:
cwe_id: CWE-399
cwe_name: Resource Management Errors
description: Weaknesses in this category are related to improper management of system resources.

Fix information:
repo_url: https://github.com/hueniverse/hawk
commit_details: repo_url: https://github.com/hueniverse/hawk
author: Eran Hammer
author_date: 2016-01-19 11:53:57-08:00
msg: Fix minor DoS attack on long headers or uris. Closes #168
repository: repo_url: https://github.com/hueniverse/hawk
repo_name: mozilla/hawk
description: INACTIVE - HTTP Holder-Of-Key Authentication Scheme
repo_language: JavaScript
owner: hueniverse
file_changes: filename: server.js
change_type: ModificationType.MODIFY
diff: @@ -310,6 +310,11 @@ exports.header = function (credentials, artifacts, options) {
  * 'hostHeaderName', 'localtimeOffsetMsec', 'host', 'port'
  */
 
+
+//                       1     2             3           4
+internals.bewitRegex = /^(\/.*)([\?&])bewit\=([^&$]*)(?:&(.+))?$/;
+
+
 exports.authenticateBewit = function (req, credentialsFunc, options, callback) {
 
     callback = Hoek.nextTick(callback);
@@ -327,8 +332,11 @@ exports.authenticateBewit = function (req, credentialsFunc, options, callback) {
 
     // Extract bewit
 
-    //                                 1     2             3           4
-    const resource = request.url.match(/^(\/.*)([\?&])bewit\=([^&$]*)(?:&(.+))?$/);
+    if (request.url.length > Utils.limits.maxMatchLength) {
+        return callback(Boom.badRequest('Resource path exceeds max length'));
+    }
+
+    const resource = request.url.match(internals.bewitRegex);
     if (!resource) {
         return callback(Utils.unauthorized());
     }

programming_language: JavaScript
filename: utils.js
change_type: ModificationType.MODIFY
diff: @@ -17,6 +17,11 @@ exports.version = function () {
 };
 
 
+exports.limits = {
+    maxMatchLength: 4096            // Limit the length of uris and headers to avoid a DoS attack on string matching
+};
+
+
 // Extract host and port from request
 
 //                                            $1                            $2
@@ -31,6 +36,10 @@ exports.parseHost = function (req, hostHeaderName) {
         return null;
     }
 
+    if (hostHeader.length > exports.limits.maxMatchLength) {
+        return null;
+    }
+
     const hostParts = hostHeader.match(internals.hostHeaderRegex);
     if (!hostParts) {
         return null;
@@ -100,6 +109,10 @@ exports.nowSecs = function (localtimeOffsetMsec) {
 };
 
 
+internals.authHeaderRegex = /^(\w+)(?:\s+(.*))?$/;                                      // Header: scheme[ something]
+internals.attributeRegex = /^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~]+$/;   // !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9
+
+
 // Parse Hawk HTTP Authorization header
 
 exports.parseAuthorizationHeader = function (header, keys) {
@@ -110,7 +123,11 @@ exports.parseAuthorizationHeader = function (header, keys) {
         return Boom.unauthorized(null, 'Hawk');
     }
 
-    const headerParts = header.match(/^(\w+)(?:\s+(.*))?$/);       // Header: scheme[ something]
+    if (header.length > exports.limits.maxMatchLength) {
+        return Boom.badRequest('Header length too long');
+    }
+
+    const headerParts = header.match(internals.authHeaderRegex);
     if (!headerParts) {
         return Boom.badRequest('Invalid header syntax');
     }
@@ -136,9 +153,9 @@ exports.parseAuthorizationHeader = function (header, keys) {
             return;
         }
 
-        // Allowed attribute value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9
+        // Allowed attribute value characters
 
-        if ($2.match(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~]+$/) === null) {
+        if ($2.match(internals.attributeRegex) === null) {
             errorMessage = 'Bad attribute value: ' + $1;
             return;
         }

programming_language: JavaScript
filename: package.json
change_type: ModificationType.MODIFY
diff: @@ -1,7 +1,7 @@
 {
   "name": "hawk",
   "description": "HTTP Hawk Authentication Scheme",
-  "version": "4.1.0",
+  "version": "4.1.1",
   "author": "Eran Hammer <eran@hammer.io> (http://hueniverse.com)",
   "repository": "git://github.com/hueniverse/hawk",
   "main": "lib/index.js",

programming_language: JavaScript
filename: server.js
change_type: ModificationType.MODIFY
diff: @@ -971,6 +971,33 @@ describe('Server', () => {
         });
     });
 
+    describe('authenticateBewit()', () => {
+
+        it('errors on uri too long', (done) => {
+
+            let long = '/';
+            for (let i = 0; i < 5000; ++i) {
+                long += 'x';
+            }
+
+            const req = {
+                method: 'GET',
+                url: long,
+                host: 'example.com',
+                port: 8080,
+                authorization: 'Hawk id="1", ts="1353788437", nonce="k3j4h2", mac="zy79QQ5/EYFmQqutVnYb73gAc/U=", ext="hello"'
+            };
+
+            Hawk.server.authenticateBewit(req, credentialsFunc, {}, (err, credentials, bewit) => {
+
+                expect(err).to.exist();
+                expect(err.output.statusCode).to.equal(400);
+                expect(err.message).to.equal('Resource path exceeds max length');
+                done();
+            });
+        });
+    });
+
     describe('authenticateMessage()', () => {
 
         it('errors on invalid authorization (ts)', (done) => {

programming_language: TypeScript
filename: utils.js
change_type: ModificationType.MODIFY
diff: @@ -95,6 +95,34 @@ describe('Utils', () => {
             expect(host.name).to.equal('[123:123:123]');
             done();
         });
+
+        it('errors on header too long', (done) => {
+
+            let long = '';
+            for (let i = 0; i < 5000; ++i) {
+                long += 'x';
+            }
+
+            expect(Hawk.utils.parseHost({ headers: { host: long } })).to.be.null();
+            done();
+        });
+    });
+
+    describe('parseAuthorizationHeader()', () => {
+
+        it('errors on header too long', (done) => {
+
+            let long = 'Scheme a="';
+            for (let i = 0; i < 5000; ++i) {
+                long += 'x';
+            }
+            long += '"';
+
+            const err = Hawk.utils.parseAuthorizationHeader(long, ['a']);
+            expect(err).to.be.instanceof(Error);
+            expect(err.message).to.equal('Header length too long');
+            done();
+        });
     });
 
     describe('version()', () => {

programming_language: TypeScript