Skip to content

Commit b4faede

Browse files
committed
move to SecurityChecks
1 parent fff6b7e commit b4faede

File tree

7 files changed

+107
-107
lines changed

7 files changed

+107
-107
lines changed

spec/ParseServer.Security.spec.js

-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ describe('SecurityChecks', () => {
4747
request(options).then(res => {
4848
expect(res.data.CLP).not.toBeUndefined();
4949
expect(res.data.ServerConfiguration).not.toBeUndefined();
50-
expect(res.data.Files).not.toBeUndefined();
5150
expect(res.data.Database).not.toBeUndefined();
5251
expect(res.data.Total).not.toBeUndefined();
5352
done();

src/Adapters/Storage/Mongo/MongoStorageAdapter.js

-59
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import Parse from 'parse/node';
1818
import _ from 'lodash';
1919
import defaults from '../../../defaults';
2020
import logger from '../../../logger';
21-
import url from 'url';
2221

2322
// @flow-disable-next
2423
const mongodb = require('mongodb');
@@ -132,7 +131,6 @@ export class MongoStorageAdapter implements StorageAdapter {
132131
this._maxTimeMS = mongoOptions.maxTimeMS;
133132
this.canSortOnJoinTables = true;
134133
delete mongoOptions.maxTimeMS;
135-
registerSecurityChecks(this);
136134
}
137135

138136
connect() {
@@ -1059,61 +1057,4 @@ export class MongoStorageAdapter implements StorageAdapter {
10591057
});
10601058
}
10611059
}
1062-
const registerSecurityChecks = database => {
1063-
let databaseURI = database._uri;
1064-
const databaseCheck = new Parse.SecurityCheck({
1065-
group: Parse.SecurityCheck.Category.Database,
1066-
title: `Weak Database Password`,
1067-
warning:
1068-
'The database password set lacks complexity and length. This could potentially allow an attacker to brute force their way into the database, exposing the database.',
1069-
success: `Strong Database Password`,
1070-
});
1071-
if (databaseURI.includes('@')) {
1072-
databaseURI = `mongodb://${databaseURI.split('@')[1]}`;
1073-
const pwd = database._uri.split('//')[1].split('@')[0].split(':')[1] || '';
1074-
if (!pwd.match('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{14,})')) {
1075-
databaseCheck.setFailed();
1076-
}
1077-
} else {
1078-
databaseCheck.setFailed();
1079-
}
1080-
let databaseAdmin = '' + databaseURI;
1081-
try {
1082-
const parsedURI = url.parse(databaseAdmin);
1083-
parsedURI.port = '27017';
1084-
databaseAdmin = url.format(parsedURI);
1085-
} catch (e) {
1086-
/* */
1087-
}
1088-
new Parse.SecurityCheck({
1089-
group: Parse.SecurityCheck.Category.Database,
1090-
title: `Unrestricted access to port 27017`,
1091-
warning:
1092-
'The database requires no authentication to the admin port. This could potentially allow an attacker to easily access the database, exposing all of the database.',
1093-
success: `Restricted port 27017`,
1094-
check: async () => {
1095-
try {
1096-
await MongoClient.connect(databaseAdmin.toString(), { useNewUrlParser: true });
1097-
return false;
1098-
} catch (e) {
1099-
console.log(e);
1100-
}
1101-
},
1102-
});
1103-
new Parse.SecurityCheck({
1104-
group: Parse.SecurityCheck.Category.Database,
1105-
title: `Unrestricted access to the database`,
1106-
warning:
1107-
'The database requires no authentication to connect. This could potentially allow an attacker to easily access the database, exposing all of the database.',
1108-
success: `Restricted access to the database`,
1109-
check: async () => {
1110-
try {
1111-
await MongoClient.connect(databaseURI, { useNewUrlParser: true });
1112-
return false;
1113-
} catch (e) {
1114-
console.log(e);
1115-
}
1116-
},
1117-
});
1118-
};
11191060
export default MongoStorageAdapter;

src/ParseServer.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import { ParseServerRESTController } from './ParseServerRESTController';
4242
import * as controllers from './Controllers';
4343
import { ParseGraphQLServer } from './GraphQL/ParseGraphQLServer';
4444
import SecurityCheck from './SecurityCheck';
45-
import { registerServerSecurityChecks } from './ParseServerSecurityChecks';
45+
import { registerServerSecurityChecks } from './SecurityChecks';
4646

4747
// Mutate the Parse object to add the Cloud Code handlers
4848
addParseCloud();
@@ -394,7 +394,6 @@ class ParseServer {
394394
function addParseCloud() {
395395
const ParseCloud = require('./cloud-code/Parse.Cloud');
396396
Object.assign(Parse.Cloud, ParseCloud);
397-
Parse.SecurityCheck = SecurityCheck;
398397
global.Parse = Parse;
399398
}
400399

src/Routers/CloudCodeRouter.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import PromiseRouter from '../PromiseRouter';
22
import Parse from 'parse/node';
33
import rest from '../rest';
4+
import SecurityCheck from '../SecurityCheck';
45
const triggers = require('../triggers');
56
const middleware = require('../middlewares');
67

@@ -131,7 +132,7 @@ export class CloudCodeRouter extends PromiseRouter {
131132
if (!req.config.securityChecks || !req.config.securityChecks.enableSecurityChecks) {
132133
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Unauthorized');
133134
}
134-
const response = await Parse.SecurityCheck.getChecks();
135+
const response = await SecurityCheck.getChecks();
135136
return {
136137
response,
137138
};

src/SecurityCheck.js

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { _registerCheck, _getChecks } from './SecurityCheckStore';
21
import logger from './logger';
32
class SecurityCheck {
43
constructor(data) {
@@ -8,7 +7,7 @@ class SecurityCheck {
87
throw 'Security checks must have a group, title, and a warning.';
98
}
109
if (typeof group !== 'string') {
11-
throw '"group" of the security check must be a string, e.g Parse.SecurityCheck.Category.Database';
10+
throw '"group" of the security check must be a string, e.g SecurityCheck.Category.Database';
1211
}
1312
if (typeof success !== 'string') {
1413
throw '"success" message of the security check must be a string.';
@@ -68,7 +67,6 @@ SecurityCheck.Category = {
6867
ServerConfiguration: 'ServerConfiguration',
6968
};
7069
SecurityCheck.getChecks = async () => {
71-
const checks = _getChecks();
7270
const resultsByGroup = {};
7371
let total = 0;
7472
const resolveSecurityCheck = async check => {
@@ -87,8 +85,18 @@ SecurityCheck.getChecks = async () => {
8785
total++;
8886
}
8987
};
90-
await Promise.all(checks.map(check => resolveSecurityCheck(check)));
88+
await Promise.all(securityCheckStore.map(check => resolveSecurityCheck(check)));
9189
resultsByGroup.Total = total;
9290
return resultsByGroup;
9391
};
92+
const securityCheckStore = [];
93+
function _registerCheck(securityCheck) {
94+
for (const [i, check] of securityCheckStore.entries()) {
95+
if (check.title == securityCheck.title && check.warning == securityCheck.warning) {
96+
securityCheckStore[i] = securityCheck;
97+
return;
98+
}
99+
}
100+
securityCheckStore.push(securityCheck);
101+
}
94102
module.exports = SecurityCheck;

src/SecurityCheckStore.js

-15
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import Parse from 'parse/node';
1+
import SecurityCheck from './SecurityCheck';
22
import url from 'url';
3-
const registerServerSecurityChecks = async req => {
4-
const options = req.config || req;
5-
await Promise.all([registerCLP(options), checkServerConfig(options), checkFiles(options)]);
6-
};
7-
async function registerCLP(options) {
3+
const ServerChecks = {};
4+
const mongodb = require('mongodb');
5+
const MongoClient = mongodb.MongoClient;
6+
7+
ServerChecks.registerCLP = async options => {
88
const schema = await options.databaseController.loadSchema();
99
const all = await schema.getAllClasses();
1010
for (const field of all) {
1111
const { className, clp } = field;
12-
const clpCheck = new Parse.SecurityCheck({
13-
group: Parse.SecurityCheck.Category.CLP,
12+
const clpCheck = new SecurityCheck({
13+
group: SecurityCheck.Category.CLP,
1414
title: `No Class Level Permissions on ${className}`,
1515
warning: `Any client can create, find, count, get, update, delete, or add field on ${className}. This allows an attacker to create new objects or fieldNames without restriction and potentially flood the database. Set CLPs using Parse Dashboard.`,
1616
success: `Class Level Permissions on ${className}`,
@@ -25,14 +25,14 @@ async function registerCLP(options) {
2525
if (className === '_User' && key === 'create') {
2626
continue;
2727
}
28-
const optionCheck = new Parse.SecurityCheck({
29-
group: Parse.SecurityCheck.Category.CLP,
28+
const optionCheck = new SecurityCheck({
29+
group: SecurityCheck.Category.CLP,
3030
title: `Unrestricted access to ${key}.`,
3131
warning: `Any client can ${key} on ${className}.`,
3232
success: `${key} is restricted on ${className}`,
3333
});
34-
const addFileCheck = new Parse.SecurityCheck({
35-
group: Parse.SecurityCheck.Category.CLP,
34+
const addFileCheck = new SecurityCheck({
35+
group: SecurityCheck.Category.CLP,
3636
title: `Certain users can add fields.`,
3737
warning: `Certain users can add fields on ${className}. This allows these users to create new fieldNames and potentially flood the schema. Set CLPs using Parse Dashboard.`,
3838
success: `AddField is restricted on ${className}`,
@@ -44,10 +44,10 @@ async function registerCLP(options) {
4444
}
4545
}
4646
}
47-
}
48-
function checkServerConfig(options) {
49-
new Parse.SecurityCheck({
50-
group: Parse.SecurityCheck.Category.ServerConfiguration,
47+
};
48+
ServerChecks.checkServerConfig = async options => {
49+
new SecurityCheck({
50+
group: SecurityCheck.Category.ServerConfiguration,
5151
title: 'Client class creation allowed',
5252
warning:
5353
'Clients are currently allowed to create new classes. This allows an attacker to create new classes without restriction and potentially flood the database. Change the Parse Server configuration to allowClientClassCreation: false.',
@@ -56,8 +56,8 @@ function checkServerConfig(options) {
5656
return !options.allowClientClassCreation;
5757
},
5858
});
59-
new Parse.SecurityCheck({
60-
group: Parse.SecurityCheck.Category.ServerConfiguration,
59+
new SecurityCheck({
60+
group: SecurityCheck.Category.ServerConfiguration,
6161
title: 'Weak masterKey.',
6262
warning:
6363
'The masterKey set to your configuration lacks complexity and length. This could potentially allow an attacker to brute force the masterKey, exposing all the entire Parse Server.',
@@ -76,8 +76,8 @@ function checkServerConfig(options) {
7676
} catch (e) {
7777
/* */
7878
}
79-
new Parse.SecurityCheck({
80-
group: Parse.SecurityCheck.Category.ServerConfiguration,
79+
new SecurityCheck({
80+
group: SecurityCheck.Category.ServerConfiguration,
8181
title: `Parse Server served over HTTP`,
8282
warning:
8383
'The server url is currently HTTP. This allows an attacker to listen to all traffic in-between the server and the client. Change the Parse Server configuration serverURL to HTTPS.',
@@ -86,10 +86,10 @@ function checkServerConfig(options) {
8686
return https;
8787
},
8888
});
89-
}
90-
function checkFiles(options) {
91-
new Parse.SecurityCheck({
92-
group: Parse.SecurityCheck.Category.ServerConfiguration,
89+
};
90+
ServerChecks.checkFiles = options => {
91+
new SecurityCheck({
92+
group: SecurityCheck.Category.ServerConfiguration,
9393
title: `Public File Upload Enabled`,
9494
warning:
9595
'Public file upload is currently enabled. This allows a client to upload files without requiring login or authentication. Remove enableForPublic from fileUpload in the Parse Server configuration.',
@@ -98,7 +98,74 @@ function checkFiles(options) {
9898
return !(options.fileUpload && options.fileUpload.enableForPublic);
9999
},
100100
});
101-
}
101+
};
102+
ServerChecks.checkDatabase = options => {
103+
let databaseURI = options.databaseURI;
104+
if (options.databaseAdapter && options.databaseAdapter._uri) {
105+
databaseURI = options.databaseAdapter._uri;
106+
}
107+
const databaseCheck = new SecurityCheck({
108+
group: SecurityCheck.Category.Database,
109+
title: `Weak Database Password`,
110+
warning:
111+
'The database password set lacks complexity and length. This could potentially allow an attacker to brute force their way into the database, exposing the database.',
112+
success: `Strong Database Password`,
113+
});
114+
if (databaseURI.includes('@')) {
115+
const copyURI = `${databaseURI}`;
116+
databaseURI = `mongodb://${databaseURI.split('@')[1]}`;
117+
const pwd = copyURI.split('//')[1].split('@')[0].split(':')[1] || '';
118+
if (!pwd.match('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{14,})')) {
119+
databaseCheck.setFailed();
120+
}
121+
} else {
122+
databaseCheck.setFailed();
123+
}
124+
let databaseAdmin = '' + databaseURI;
125+
try {
126+
const parsedURI = url.parse(databaseAdmin);
127+
parsedURI.port = '27017';
128+
databaseAdmin = url.format(parsedURI);
129+
} catch (e) {
130+
/* */
131+
}
132+
new SecurityCheck({
133+
group: SecurityCheck.Category.Database,
134+
title: `Unrestricted access to port 27017`,
135+
warning:
136+
'The database requires no authentication to the admin port. This could potentially allow an attacker to easily access the database, exposing all of the database.',
137+
success: `Restricted port 27017`,
138+
check: async () => {
139+
try {
140+
await MongoClient.connect(databaseAdmin.toString(), { useNewUrlParser: true });
141+
return false;
142+
} catch (e) {
143+
console.log(e);
144+
}
145+
},
146+
});
147+
new SecurityCheck({
148+
group: SecurityCheck.Category.Database,
149+
title: `Unrestricted access to the database`,
150+
warning:
151+
'The database requires no authentication to connect. This could potentially allow an attacker to easily access the database, exposing all of the database.',
152+
success: `Restricted access to the database`,
153+
check: async () => {
154+
try {
155+
await MongoClient.connect(databaseURI, { useNewUrlParser: true });
156+
return false;
157+
} catch (e) {
158+
console.log(e);
159+
}
160+
},
161+
});
162+
};
163+
164+
const registerServerSecurityChecks = async req => {
165+
const options = req.config || req;
166+
const serverFuncs = Object.values(ServerChecks);
167+
await Promise.all(serverFuncs.map(func => func(options)));
168+
};
102169
module.exports = {
103170
registerServerSecurityChecks,
104171
};

0 commit comments

Comments
 (0)