Skip to content

Commit 1440f3d

Browse files
committed
Merge pull request #18 from strongloop/feature/fix-sql-injection
Fix SQL injection
2 parents b680af0 + a8eef61 commit 1440f3d

File tree

2 files changed

+188
-7
lines changed

2 files changed

+188
-7
lines changed

lib/mssql.js

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,50 @@ function dateToMsSql(val) {
423423
}
424424
}
425425

426+
function escape(val) {
427+
if (val === undefined || val === null) {
428+
return 'NULL';
429+
}
430+
431+
switch (typeof val) {
432+
case 'boolean':
433+
return (val) ? 1 : 0;
434+
case 'number':
435+
return val + '';
436+
}
437+
438+
if (typeof val === 'object') {
439+
val = (typeof val.toISOString === 'function')
440+
? val.toISOString()
441+
: val.toString();
442+
}
443+
444+
val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function (s) {
445+
switch (s) {
446+
case "\0":
447+
return "\\0";
448+
case "\n":
449+
return "\\n";
450+
case "\r":
451+
return "\\r";
452+
case "\b":
453+
return "\\b";
454+
case "\t":
455+
return "\\t";
456+
case "\x1a":
457+
return "\\Z";
458+
case "\'":
459+
return "''"; // For sql server, double quote
460+
case "\"":
461+
return s; // For oracle
462+
default:
463+
return "\\" + s;
464+
}
465+
});
466+
// return "q'#"+val+"#'";
467+
return "'" + val + "'";
468+
}
469+
426470
//toDatabase is used for formatting data when inserting/updating records
427471
// it is also used when building a where clause for filtering selects
428472
// in the case of update/insert we want the data to be returned raw
@@ -437,7 +481,7 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) {
437481
return null;
438482
}
439483
if (prop.type && prop.type.modelName) {
440-
return "'" + JSON.stringify(val) + "'";
484+
return escape(JSON.stringify(val));
441485
}
442486
if (val.constructor && val.constructor.name === 'Object') {
443487
var operator = Object.keys(val)[0]
@@ -456,12 +500,12 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) {
456500
//check if it is an array of string, because in that cause we need to
457501
// wrap them in single quotes
458502
if (typeof val[0] === 'string') {
459-
return "'" + val.join("','") + "'";
503+
return escape(val.join("','"));
460504
}
461505
return val.join(',');
462506
} else {
463507
if (typeof val === 'string') {
464-
val = "'" + val + "'";
508+
val = escape(val);
465509
}
466510
return val;
467511
}
@@ -471,12 +515,12 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) {
471515
}
472516
if (!prop) {
473517
if (typeof val === 'string' && wrap) {
474-
val = "'" + val + "'";
518+
val = escape(val);
475519
}
476520
return val;
477521
}
478522
if (prop.type.name === 'Number') {
479-
return val;
523+
return escape(val);
480524
}
481525
if (prop.type.name === 'Date') {
482526
if (!val) {
@@ -488,7 +532,7 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) {
488532
}
489533
val = dateToMsSql(val);
490534
if (wrap) {
491-
val = "'" + val + "'";
535+
val = escape(val);
492536
}
493537
return val;
494538
}
@@ -501,7 +545,7 @@ MsSQL.prototype.toDatabase = function (prop, val, wrap) {
501545
}
502546

503547
if (wrap) {
504-
return "'" + val.toString().replace(/'/g, "''") + "'";
548+
return escape(val.toString());
505549
}
506550
return val.toString();
507551
};

test/mssql.test.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
require('./init');
2+
3+
var should = require('should');
4+
5+
var Post, db;
6+
7+
describe('mssql connector', function () {
8+
9+
before(function () {
10+
db = getDataSource();
11+
12+
Post = db.define('PostWithBoolean', {
13+
title: { type: String, length: 255, index: true },
14+
content: { type: String },
15+
approved: Boolean
16+
});
17+
});
18+
19+
it('should run migration', function (done) {
20+
db.automigrate('PostWithBoolean', function () {
21+
done();
22+
});
23+
});
24+
25+
var post;
26+
it('should support boolean types with true value', function(done) {
27+
Post.create({title: 'T1', content: 'C1', approved: true}, function(err, p) {
28+
should.not.exists(err);
29+
post = p;
30+
Post.findById(p.id, function(err, p) {
31+
should.not.exists(err);
32+
p.should.have.property('approved', true);
33+
done();
34+
});
35+
});
36+
});
37+
38+
it('should support updating boolean types with false value', function(done) {
39+
Post.update({id: post.id}, {approved: false}, function(err) {
40+
should.not.exists(err);
41+
Post.findById(post.id, function(err, p) {
42+
should.not.exists(err);
43+
p.should.have.property('approved', false);
44+
done();
45+
});
46+
});
47+
});
48+
49+
50+
it('should support boolean types with false value', function(done) {
51+
Post.create({title: 'T2', content: 'C2', approved: false}, function(err, p) {
52+
should.not.exists(err);
53+
post = p;
54+
Post.findById(p.id, function(err, p) {
55+
should.not.exists(err);
56+
p.should.have.property('approved', false);
57+
done();
58+
});
59+
});
60+
});
61+
62+
it('should single quote escape', function(done) {
63+
Post.create({title: "T2", content: "C,D", approved: false}, function(err, p) {
64+
should.not.exists(err);
65+
post = p;
66+
Post.findById(p.id, function(err, p) {
67+
should.not.exists(err);
68+
p.should.have.property('content', "C,D");
69+
done();
70+
});
71+
});
72+
});
73+
74+
it('should return the model instance for upsert', function(done) {
75+
Post.upsert({id: post.id, title: 'T2_new', content: 'C2_new',
76+
approved: true}, function(err, p) {
77+
p.should.have.property('id', post.id);
78+
p.should.have.property('title', 'T2_new');
79+
p.should.have.property('content', 'C2_new');
80+
p.should.have.property('approved', true);
81+
done();
82+
});
83+
});
84+
85+
it('should return the model instance for upsert when id is not present',
86+
function(done) {
87+
Post.upsert({title: 'T2_new', content: 'C2_new', approved: true},
88+
function(err, p) {
89+
p.should.have.property('id');
90+
p.should.have.property('title', 'T2_new');
91+
p.should.have.property('content', 'C2_new');
92+
p.should.have.property('approved', true);
93+
done();
94+
});
95+
});
96+
97+
it('should escape number values to defect SQL injection in findById',
98+
function(done) {
99+
Post.findById('(SELECT 1+1)', function(err, p) {
100+
should.exists(err);
101+
done();
102+
});
103+
});
104+
105+
it('should escape number values to defect SQL injection in find',
106+
function(done) {
107+
Post.find({where: {id: '(SELECT 1+1)'}}, function(err, p) {
108+
should.exists(err);
109+
done();
110+
});
111+
});
112+
113+
it('should escape number values to defect SQL injection in find with gt',
114+
function(done) {
115+
Post.find({where: {id: {gt: '(SELECT 1+1)'}}}, function(err, p) {
116+
should.exists(err);
117+
done();
118+
});
119+
});
120+
121+
it('should escape number values to defect SQL injection in find',
122+
function(done) {
123+
Post.find({limit: '(SELECT 1+1)'}, function(err, p) {
124+
should.exists(err);
125+
done();
126+
});
127+
});
128+
129+
it('should escape number values to defect SQL injection in find with inq',
130+
function(done) {
131+
Post.find({where: {id: {inq: ['(SELECT 1+1)']}}}, function(err, p) {
132+
should.exists(err);
133+
done();
134+
});
135+
});
136+
137+
});

0 commit comments

Comments
 (0)