Skip to content

Commit 93cac1d

Browse files
authored
Merge pull request #166 from OWASP/crypto-breaker-module
Crypto Breaker Module
2 parents aaa0404 + 76198b4 commit 93cac1d

24 files changed

+1240
-145
lines changed

trainingportal/challenges.js

Lines changed: 76 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const validator = require('validator');
2424
const crypto = require('crypto');
2525
const aescrypto = require(path.join(__dirname, 'aescrypto'));
2626
const https = require('https');
27+
const qna = require(path.join(__dirname, 'qna'));
2728

2829
var modules = {};
2930
var moduleVer = 0;
@@ -33,7 +34,7 @@ var solutions = [];
3334
var descriptions = [];
3435
var masterSalt = "";
3536

36-
loadModules = () => {
37+
let loadModules = () => {
3738
let modsPath;
3839
if(!util.isNullOrUndefined(process.env.DATA_DIR)){
3940
modsPath = path.join(process.env.DATA_DIR, "modules.json");
@@ -58,11 +59,11 @@ loadModules = () => {
5859
}
5960

6061

61-
function getModulePath(moduleId){
62+
let getModulePath = (moduleId) => {
6263
return path.join('static/lessons/', moduleId);
6364
}
6465

65-
function getDefinitionsForModule(moduleId){
66+
let getDefinitionsForModule = (moduleId) => {
6667

6768
try {
6869
var defs = Object.freeze(require(path.join(__dirname, getModulePath(moduleId), '/definitions.json')));
@@ -104,21 +105,30 @@ let init = async () => {
104105
masterSalt=process.env.CHALLENGE_MASTER_SALT;
105106
}
106107

107-
let dbModuleVersion = await db.getModuleVersion();
108-
if(dbModuleVersion < moduleVer){
109-
util.log("New training modules version, updating module completion for all users.")
110-
recreateBadgesOnModulesUpdate();
111-
db.updateModuleVersion(moduleVer);
112-
}
108+
try {
109+
let dbModuleVersion = await db.getModuleVersion();
110+
if(dbModuleVersion < moduleVer){
111+
util.log("New training modules version, updating module completion for all users.")
112+
recreateBadgesOnModulesUpdate();
113+
db.updateModuleVersion(moduleVer);
114+
if(dbModuleVersion < moduleVer){
115+
util.log("New training modules version, updating module completion for all users.")
116+
recreateBadgesOnModulesUpdate();
117+
db.updateModuleVersion(moduleVer);
118+
}
119+
}
120+
} catch (error) {
121+
console.log(`Error handling module version ${error.message}`);
122+
}
123+
113124
}
114125

115126
init();
116127

117-
getModules = function(){ return modules; }
118-
getChallengeNames = function(){ return challengeNames; }
119-
getChallengeDefinitions = function(){ return challengeDefinitions; }
128+
let getModules = function(){ return modules; }
129+
let getChallengeNames = function(){ return challengeNames; }
120130

121-
isPermittedModule = async (user, moduleId) => {
131+
let isPermittedModule = async (user, moduleId) => {
122132
let badges = await db.fetchBadges(user.id);
123133
if(util.isNullOrUndefined(modules[moduleId])){
124134
return false;
@@ -144,7 +154,7 @@ isPermittedModule = async (user, moduleId) => {
144154
/**
145155
* Get the user level based on the amount of passed challenges
146156
*/
147-
getUserLevelForModule = async (user,moduleId) => {
157+
let getUserLevelForModule = async (user,moduleId) => {
148158
let moduleDefinitions = getDefinitionsForModule(moduleId);
149159
let passedChallenges = await db.fetchChallengeEntriesForUser(user);
150160
let userLevel=-1;
@@ -170,7 +180,7 @@ getUserLevelForModule = async (user,moduleId) => {
170180
/**
171181
* Get permitted challenges for module
172182
*/
173-
getPermittedChallengesForUser = async (user, moduleId) => {
183+
let getPermittedChallengesForUser = async (user, moduleId) => {
174184
if(util.isNullOrUndefined(moduleId)) return [];
175185
if(util.isNullOrUndefined(modules[moduleId])) return [];
176186

@@ -189,10 +199,9 @@ getPermittedChallengesForUser = async (user, moduleId) => {
189199

190200
/**
191201
* Construct the challenge definitions loaded on the client side based on the users level
192-
* @param {Object} user The session user object
193202
* @param {Array} moduleIds The lesson module ids
194203
*/
195-
getChallengeDefinitionsForUser = async (user, moduleId) => {
204+
let getChallengeDefinitions = async (moduleId) => {
196205
var returnChallenges = [];
197206

198207
if(util.isNullOrUndefined(moduleId)) return [];
@@ -209,10 +218,13 @@ getChallengeDefinitionsForUser = async (user, moduleId) => {
209218
if (!util.isNullOrUndefined(playLink)) {
210219
challenge.playLink = playLink;
211220
}
212-
var description = challenge.description;
213-
if(!util.isNullOrUndefined(description) && description.indexOf(modulePath) === -1){
214-
challenge.description = path.join(modulePath, description);
215-
}
221+
}
222+
var description = challenge.description;
223+
if(!util.isNullOrUndefined(description) && description.indexOf(modulePath) === -1){
224+
challenge.description = path.join(modulePath, description);
225+
}
226+
if(challenge.type === "quiz"){
227+
challenge.question = qna.getCode(challenge.id);
216228
}
217229
}
218230
returnChallenges.push(level);
@@ -227,7 +239,7 @@ getChallengeDefinitionsForUser = async (user, moduleId) => {
227239
* Returns the solution html (converted from markdown)
228240
* @param {The challenge id} challengeId
229241
*/
230-
getSolution = function (challengeId) {
242+
let getSolution = function (challengeId) {
231243
var solution = solutions[challengeId];
232244
var solutionHtml = "";
233245
if(!util.isNullOrUndefined(solution)){
@@ -242,7 +254,7 @@ getSolution = function (challengeId) {
242254
* Returns the description html (converted from markdown if applicable)
243255
* @param {The challenge id} challengeId
244256
*/
245-
getDescription = function (challengeId) {
257+
let getDescription = function (challengeId) {
246258
var description = descriptions[challengeId];
247259
var descriptionHtml = "";
248260
if(util.isNullOrUndefined(description)) return "";
@@ -265,7 +277,7 @@ getDescription = function (challengeId) {
265277
/**
266278
* Checks if the user has completed the module and issue a badge
267279
*/
268-
verifyModuleCompletion = async (user, moduleId) => {
280+
let verifyModuleCompletion = async (user, moduleId) => {
269281
var userLevel = await getUserLevelForModule(user, moduleId);
270282
let moduleDefinitions = getDefinitionsForModule(moduleId);
271283
var lastLevel = moduleDefinitions[moduleDefinitions.length-1];
@@ -293,7 +305,7 @@ verifyModuleCompletion = async (user, moduleId) => {
293305
/**
294306
* Iterates through the entire list of users to insert badges where needed
295307
*/
296-
recreateBadgesOnModulesUpdate = async () => {
308+
let recreateBadgesOnModulesUpdate = async () => {
297309

298310
let users = await db.fetchUsersWithId();
299311

@@ -320,7 +332,7 @@ recreateBadgesOnModulesUpdate = async () => {
320332
* Retrieves a code to verify completion of the level
321333
* @param {Badge} badge
322334
*/
323-
getBadgeCode = (badge, user) => {
335+
let getBadgeCode = (badge, user) => {
324336
let module = modules[badge.moduleId];
325337

326338
if(util.isNullOrUndefined(module) || util.isNullOrUndefined(module.badgeInfo)) return null;
@@ -347,7 +359,7 @@ getBadgeCode = (badge, user) => {
347359
* Verifies a badge code and returns parsed info
348360
* @param {Base64} badgeCode
349361
*/
350-
verifyBadgeCode = (badgeCode) => {
362+
let verifyBadgeCode = (badgeCode) => {
351363
urlDecoded = decodeURIComponent(badgeCode);
352364
let parts = urlDecoded.split(".");
353365
if(parts.length !== 2) return null;
@@ -370,7 +382,7 @@ verifyBadgeCode = (badgeCode) => {
370382
* @param {*} badgrInfo
371383
* @param {*} user
372384
*/
373-
badgrCall = function(badgrInfo, user){
385+
let badgrCall = function(badgrInfo, user){
374386
if(!util.isNullOrUndefined(badgrInfo) && !util.isNullOrUndefined(config.encBadgrToken)){
375387
if(user.email===null){
376388
util.log("Cannot issue badge for this user. E-mail is null.", user);
@@ -418,26 +430,42 @@ badgrCall = function(badgrInfo, user){
418430
/**
419431
* Logic to for the api challenge code
420432
*/
421-
apiChallengeCode = async (req) => {
433+
let apiChallengeCode = async (req) => {
422434
if(util.isNullOrUndefined(req.body.challengeId) ||
423435
util.isNullOrUndefined(req.body.challengeCode) ||
424436
util.isNullOrUndefined(req.body.moduleId)){
425437
throw Error("invalidRequest");
426438
}
427439

440+
428441
var moduleId = req.body.moduleId.trim();
429442
var challengeId = req.body.challengeId.trim();
430443
var challengeCode = req.body.challengeCode.trim();
431444

432-
if(util.isNullOrUndefined(challengeCode) || validator.isBase64(challengeCode) == false){
445+
let challengeType = "page";
446+
if(!util.isNullOrUndefined(req.body.challengeType)){
447+
challengeType = req.body.challengeType;
448+
}
449+
450+
if(["page","quiz"].indexOf(challengeType) === -1){
451+
throw Error("invalidChallengeType");
452+
}
453+
454+
let answer = null;
455+
if(!util.isNullOrUndefined(req.body.answer)){
456+
answer = req.body.answer.trim();
457+
}
458+
459+
if(util.isNullOrUndefined(challengeCode) ||
460+
(validator.isAlphanumeric(challengeCode) === false && validator.isBase64(challengeCode) === false) ){
433461
throw Error("invalidCode");
434462
}
435463

436-
if(util.isNullOrUndefined(moduleId) || validator.isAlphanumeric(moduleId) == false){
464+
if(util.isNullOrUndefined(moduleId) || validator.isAlphanumeric(moduleId) === false){
437465
throw Error("invalidModuleId");
438466
}
439467

440-
if(util.isNullOrUndefined(challengeId) || util.isAlphanumericOrUnderscore(challengeId) == false){
468+
if(util.isNullOrUndefined(challengeId) || util.isAlphanumericOrUnderscore(challengeId) === false){
441469
throw Error("invalidChallengeId");
442470
}
443471

@@ -466,13 +494,24 @@ apiChallengeCode = async (req) => {
466494
if(util.isNullOrUndefined(modules[moduleId].skipMasterSalt) || modules[moduleId].skipMasterSalt===false){
467495
ms = masterSalt;
468496
}
497+
498+
if(challengeType !== "quiz"){
499+
answer = challengeId+req.user.codeSalt;
500+
}
501+
469502
//either hex or base64 formats should work
470-
//we're looking at the first 10 characters only for situations where the challenge code may get truncated - pcaps, IPS logs
471-
var verificationHashB64 = crypto.createHash('sha256').update(challengeId+req.user.codeSalt+ms).digest('base64').substr(0,10);
472-
var verificationHashHex = crypto.createHash('sha256').update(challengeId+req.user.codeSalt+ms).digest('hex').substr(0,10);
503+
let verificationHashB64 = crypto.createHash('sha256').update(answer+ms).digest('base64');
504+
let verificationHashHex = crypto.createHash('sha256').update(answer+ms).digest('hex');
505+
473506
if(challengeCode.indexOf(verificationHashB64)!==0 && challengeCode.indexOf(verificationHashHex)!==0){
474-
throw Error("invalidCode");
507+
if(challengeType === "quiz"){
508+
throw Error("invalidAnswer");
509+
}
510+
else{
511+
throw Error("invalidCode");
512+
}
475513
}
514+
476515
//success update challenge
477516
curChallengeObj.moduleId = moduleId;
478517
return insertChallengeEntry(req.user, curChallengeObj, moduleId);
@@ -482,7 +521,7 @@ apiChallengeCode = async (req) => {
482521
/**
483522
* Inserts a challenge entry
484523
*/
485-
insertChallengeEntry = async (user,curChallengeObj, moduleId) => {
524+
let insertChallengeEntry = async (user,curChallengeObj, moduleId) => {
486525
await db.getPromise(db.insertChallengeEntry, [user.id,curChallengeObj.id]);
487526
//issue badgr badge if enabled
488527
badgrCall(curChallengeObj.badgrInfo,user);
@@ -509,14 +548,12 @@ insertChallengeEntry = async (user,curChallengeObj, moduleId) => {
509548

510549

511550

512-
513551
module.exports = {
514552
apiChallengeCode,
515553
badgrCall,
516554
getBadgeCode,
517555
getChallengeNames,
518556
getChallengeDefinitions,
519-
getChallengeDefinitionsForUser,
520557
getDescription,
521558
getModules,
522559
getPermittedChallengesForUser,

trainingportal/db.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,9 @@ getPromise = (dbFunc, params) => {
161161
return promise;
162162
};
163163

164-
init = async () => {
164+
165+
166+
let init = async () => {
165167
var con = getConn();
166168
var sql = "";
167169
var dbSetup = "";
@@ -251,6 +253,9 @@ init = async () => {
251253
}
252254
};
253255

256+
let initSync = async() => {
257+
await init()
258+
}
254259

255260
//Creates a user in the database
256261
insertUser = function(user,errCb,doneCb){
@@ -655,6 +660,7 @@ module.exports = {
655660
fetchUsers,
656661
fetchUsersWithId,
657662
init,
663+
initSync,
658664
insertBadge,
659665
insertChallengeEntry,
660666
insertUser,

0 commit comments

Comments
 (0)