Skip to content
This repository was archived by the owner on Apr 5, 2024. It is now read-only.

Commit 0833f99

Browse files
committed
Fix minor DoS attack on long headers or uris. Closes #168
1 parent b5d51eb commit 0833f99

File tree

5 files changed

+86
-6
lines changed

5 files changed

+86
-6
lines changed

lib/server.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,11 @@ exports.header = function (credentials, artifacts, options) {
310310
* 'hostHeaderName', 'localtimeOffsetMsec', 'host', 'port'
311311
*/
312312

313+
314+
// 1 2 3 4
315+
internals.bewitRegex = /^(\/.*)([\?&])bewit\=([^&$]*)(?:&(.+))?$/;
316+
317+
313318
exports.authenticateBewit = function (req, credentialsFunc, options, callback) {
314319

315320
callback = Hoek.nextTick(callback);
@@ -327,8 +332,11 @@ exports.authenticateBewit = function (req, credentialsFunc, options, callback) {
327332

328333
// Extract bewit
329334

330-
// 1 2 3 4
331-
const resource = request.url.match(/^(\/.*)([\?&])bewit\=([^&$]*)(?:&(.+))?$/);
335+
if (request.url.length > Utils.limits.maxMatchLength) {
336+
return callback(Boom.badRequest('Resource path exceeds max length'));
337+
}
338+
339+
const resource = request.url.match(internals.bewitRegex);
332340
if (!resource) {
333341
return callback(Utils.unauthorized());
334342
}

lib/utils.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ exports.version = function () {
1717
};
1818

1919

20+
exports.limits = {
21+
maxMatchLength: 4096 // Limit the length of uris and headers to avoid a DoS attack on string matching
22+
};
23+
24+
2025
// Extract host and port from request
2126

2227
// $1 $2
@@ -31,6 +36,10 @@ exports.parseHost = function (req, hostHeaderName) {
3136
return null;
3237
}
3338

39+
if (hostHeader.length > exports.limits.maxMatchLength) {
40+
return null;
41+
}
42+
3443
const hostParts = hostHeader.match(internals.hostHeaderRegex);
3544
if (!hostParts) {
3645
return null;
@@ -100,6 +109,10 @@ exports.nowSecs = function (localtimeOffsetMsec) {
100109
};
101110

102111

112+
internals.authHeaderRegex = /^(\w+)(?:\s+(.*))?$/; // Header: scheme[ something]
113+
internals.attributeRegex = /^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~]+$/; // !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9
114+
115+
103116
// Parse Hawk HTTP Authorization header
104117

105118
exports.parseAuthorizationHeader = function (header, keys) {
@@ -110,7 +123,11 @@ exports.parseAuthorizationHeader = function (header, keys) {
110123
return Boom.unauthorized(null, 'Hawk');
111124
}
112125

113-
const headerParts = header.match(/^(\w+)(?:\s+(.*))?$/); // Header: scheme[ something]
126+
if (header.length > exports.limits.maxMatchLength) {
127+
return Boom.badRequest('Header length too long');
128+
}
129+
130+
const headerParts = header.match(internals.authHeaderRegex);
114131
if (!headerParts) {
115132
return Boom.badRequest('Invalid header syntax');
116133
}
@@ -136,9 +153,9 @@ exports.parseAuthorizationHeader = function (header, keys) {
136153
return;
137154
}
138155

139-
// Allowed attribute value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9
156+
// Allowed attribute value characters
140157

141-
if ($2.match(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~]+$/) === null) {
158+
if ($2.match(internals.attributeRegex) === null) {
142159
errorMessage = 'Bad attribute value: ' + $1;
143160
return;
144161
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "hawk",
33
"description": "HTTP Hawk Authentication Scheme",
4-
"version": "4.1.0",
4+
"version": "4.1.1",
55
"author": "Eran Hammer <[email protected]> (http://hueniverse.com)",
66
"repository": "git://github.com/hueniverse/hawk",
77
"main": "lib/index.js",

test/server.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,33 @@ describe('Server', () => {
971971
});
972972
});
973973

974+
describe('authenticateBewit()', () => {
975+
976+
it('errors on uri too long', (done) => {
977+
978+
let long = '/';
979+
for (let i = 0; i < 5000; ++i) {
980+
long += 'x';
981+
}
982+
983+
const req = {
984+
method: 'GET',
985+
url: long,
986+
host: 'example.com',
987+
port: 8080,
988+
authorization: 'Hawk id="1", ts="1353788437", nonce="k3j4h2", mac="zy79QQ5/EYFmQqutVnYb73gAc/U=", ext="hello"'
989+
};
990+
991+
Hawk.server.authenticateBewit(req, credentialsFunc, {}, (err, credentials, bewit) => {
992+
993+
expect(err).to.exist();
994+
expect(err.output.statusCode).to.equal(400);
995+
expect(err.message).to.equal('Resource path exceeds max length');
996+
done();
997+
});
998+
});
999+
});
1000+
9741001
describe('authenticateMessage()', () => {
9751002

9761003
it('errors on invalid authorization (ts)', (done) => {

test/utils.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,34 @@ describe('Utils', () => {
9595
expect(host.name).to.equal('[123:123:123]');
9696
done();
9797
});
98+
99+
it('errors on header too long', (done) => {
100+
101+
let long = '';
102+
for (let i = 0; i < 5000; ++i) {
103+
long += 'x';
104+
}
105+
106+
expect(Hawk.utils.parseHost({ headers: { host: long } })).to.be.null();
107+
done();
108+
});
109+
});
110+
111+
describe('parseAuthorizationHeader()', () => {
112+
113+
it('errors on header too long', (done) => {
114+
115+
let long = 'Scheme a="';
116+
for (let i = 0; i < 5000; ++i) {
117+
long += 'x';
118+
}
119+
long += '"';
120+
121+
const err = Hawk.utils.parseAuthorizationHeader(long, ['a']);
122+
expect(err).to.be.instanceof(Error);
123+
expect(err.message).to.equal('Header length too long');
124+
done();
125+
});
98126
});
99127

100128
describe('version()', () => {

0 commit comments

Comments
 (0)