NML Says

Data Security 6, Web Applications - I

References for this Part

Model Solutions Previous Lesson

DS.5.0

Example 1. Input to John bftest
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
┌──(root㉿d119cd163bf4)-[/home/shared/ds5/ex50]
└─# cat bftest
Adelaide:a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
Beatrice:a6979dc879a84b82499ca8719c46bf4f7ff03b70
Caroline:7110eda4d09e062aa5e4a390b0a572ac0d2c0220
Dorothea:59af431c63aed95587d38766c3ebc6f827d83e3d
Emmeline:a51dda7c7ff50b61eaea0444371f4a6a9301e501
Florence:c8d99c2f7cd5f432c163abcd422672b9f77550bb
Gretchen:efdb8f7f2fe9c47e34dfe1fb7c491d0638ec2d86
Hermione:7110eda4d09e062aa5e4a390b0a572ac0d2c0220
Isabella:59af431c63aed95587d38766c3ebc6f827d83e3d
Example 2. Console Output from the John Solution
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
┌──(root㉿d119cd163bf4)-[/home/shared/ds5/ex50]
└─# john bftest
Warning: detected hash type "Raw-SHA1", but the string is also recognized as "Raw-SHA1-AxCrypt"
Use the "--format=Raw-SHA1-AxCrypt" option to force loading these as that type instead
Warning: detected hash type "Raw-SHA1", but the string is also recognized as "Raw-SHA1-Linkedin"
Use the "--format=Raw-SHA1-Linkedin" option to force loading these as that type instead
Warning: detected hash type "Raw-SHA1", but the string is also recognized as "ripemd-160"
Use the "--format=ripemd-160" option to force loading these as that type instead
Warning: detected hash type "Raw-SHA1", but the string is also recognized as "has-160"
Use the "--format=has-160" option to force loading these as that type instead
Using default input encoding: UTF-8
Loaded 9 password hashes with no different salts (Raw-SHA1 [SHA1 256/256 AVX2 8x])
Warning: no OpenMP support for this hash type, consider --fork=4
Proceeding with single, rules:Single
Press 'q' or Ctrl-C to abort, almost any other key for status
Almost done: Processing the remaining buffered candidate passwords, if any.
Proceeding with wordlist:/usr/share/john/password.lst
1234             (Caroline)     
1234             (Hermione)     
john             (Emmeline)     
test             (Adelaide)     
jake             (Florence)     
Proceeding with incremental:ASCII
fede             (Beatrice)     
emma             (Gretchen)     
7g 0:00:01:43  3/3 0.06776g/s 23659Kp/s 23659Kc/s 48008KC/s alilmide..alilmid3
7g 0:00:01:44  3/3 0.06711g/s 23666Kp/s 23666Kc/s 48014KC/s kheoot23..kheoot29
Fede             (Dorothea)     
Fede             (Isabella)     
9g 0:00:06:15 DONE 3/3 (2025-02-17 16:44) 0.02394g/s 23573Kp/s 23573Kc/s 47298KC/s Feds..Fed.
Use the "--show --format=Raw-SHA1" options to display all of the cracked passwords reliably
Session completed. 

┌──(root㉿d119cd163bf4)-[/home/shared/ds5/ex50]
└─# john bftest --show
Adelaide:test
Beatrice:fede
Caroline:1234
Dorothea:Fede
Emmeline:john
Florence:jake
Gretchen:emma
Hermione:1234
Isabella:Fede

9 password hashes cracked, 0 left
Example 3. Content of Johns /root/.john/john.pot
1
2
3
4
5
6
7
$dynamic_26$7110eda4d09e062aa5e4a390b0a572ac0d2c0220:1234
$dynamic_26$a51dda7c7ff50b61eaea0444371f4a6a9301e501:john
$dynamic_26$a94a8fe5ccb19ba61c4c0873d391e987982fbbd3:test
$dynamic_26$c8d99c2f7cd5f432c163abcd422672b9f77550bb:jake
$dynamic_26$a6979dc879a84b82499ca8719c46bf4f7ff03b70:fede
$dynamic_26$efdb8f7f2fe9c47e34dfe1fb7c491d0638ec2d86:emma
$dynamic_26$59af431c63aed95587d38766c3ebc6f827d83e3d:Fede

One may wonder where te names in the john bftest --show are found from this.

DS.5.1

Example 4. Input files to hashcat
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
┌──(root㉿d119cd163bf4)-[/home/shared/ds5/ex51]
└─# cat bftest2
d4656cd76152f93e6a70374ff9b0e54f84363d6a
a9993e364706816aba3e25717850c26c9cd0d89d
cd3f0c85b158c08a2b113464991810cf2cdfc387
ace445715d71cd2614bf1ab16ea3fda5162c15e3

┌──(root㉿d119cd163bf4)-[/home/shared/ds5/ex51]
└─# cat bftest3
d4656cd76152f93e6a70374ff9b0e54f84363d6a
a9993e364706816aba3e25717850c26c9cd0d89d
cd3f0c85b158c08a2b113464991810cf2cdfc387
ace445715d71cd2614bf1ab16ea3fda5162c15e3
5316157bc1017ef46a8fda61701ba35618820814
6aed2d31b342216b8eb17efb38fea65acadba793
cc1a9d0c2908cf24d19ec516ead4bf9c57825d6f
82ac0b5f2b95574b3b9edb15fc58d8b9f1293646
Example 5. Terminal Output from Running Hashcat Solutions
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# hashcat -m 100 bftest2 -a3 -1?l?u?d ?1?1?1
hashcat (v6.2.6) starting

OpenCL API (OpenCL 3.0 PoCL 6.0+debian  Linux, None+Asserts, RELOC, LLVM 18.1.8, SLEEF, DISTRO, POCL_DEBUG) - Platform #1 [The pocl project]
============================================================================================================================================
* Device #1: cpu-haswell-Intel(R) Core(TM) i7-4600U CPU @ 2.10GHz, 2756/5577 MB (1024 MB allocatable), 4MCU

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256

Hashes: 4 digests; 4 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates

Optimizers applied:
* Zero-Byte
* Early-Skip
* Not-Salted
* Not-Iterated
* Single-Salt
* Brute-Force
* Raw-Hash

ATTENTION! Pure (unoptimized) backend kernels selected.
Pure kernels can crack longer passwords, but drastically reduce performance.
If you want to switch to optimized kernels, append -O to your commandline.
See the above message to find out about the exact limits.

Watchdog: Temperature abort trigger set to 90c

Host memory required for this attack: 1 MB

a9993e364706816aba3e25717850c26c9cd0d89d:abc
cd3f0c85b158c08a2b113464991810cf2cdfc387:666
ace445715d71cd2614bf1ab16ea3fda5162c15e3:a1a
d4656cd76152f93e6a70374ff9b0e54f84363d6a:NML

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 100 (SHA1)
Hash.Target......: bftest2
Time.Started.....: Mon Feb 17 16:25:49 2025 (0 secs)
Time.Estimated...: Mon Feb 17 16:25:49 2025 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Mask.......: ?1?1?1 [3]
Guess.Charset....: -1 ?l?u?d, -2 Undefined, -3 Undefined, -4 Undefined
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:   684.2 kH/s (2.22ms) @ Accel:512 Loops:62 Thr:1 Vec:8
Recovered........: 4/4 (100.00%) Digests (total), 4/4 (100.00%) Digests (new)
Progress.........: 126976/238328 (53.28%)
Rejected.........: 0/126976 (0.00%)
Restore.Point....: 0/3844 (0.00%)
Restore.Sub.#1...: Salt:0 Amplifier:0-62 Iteration:0-62
Candidate.Engine.: Device Generator
Candidates.#1....: sar -> Xo7
Hardware.Mon.#1..: Temp: 69c Util: 24%

Started: Mon Feb 17 16:25:31 2025
Stopped: Mon Feb 17 16:25:51 2025

┌──(root㉿d119cd163bf4)-[/home/shared/ds5/ex51]
└─# hashcat -m 100 bftest2 -a3 -1?l?u?d ?1?1?1 --show
d4656cd76152f93e6a70374ff9b0e54f84363d6a:NML
a9993e364706816aba3e25717850c26c9cd0d89d:abc
cd3f0c85b158c08a2b113464991810cf2cdfc387:666
ace445715d71cd2614bf1ab16ea3fda5162c15e3:a1a



┌──(root㉿d119cd163bf4)-[/home/shared/ds5/ex51]
└─# hashcat -m 100 bftest3 -a3 -1?l?u?d ?1?1?1?1 --increment --increment-min 3
hashcat (v6.2.6) starting

OpenCL API (OpenCL 3.0 PoCL 6.0+debian  Linux, None+Asserts, RELOC, LLVM 18.1.8, SLEEF, DISTRO, POCL_DEBUG) - Platform #1 [The pocl project]
============================================================================================================================================
* Device #1: cpu-haswell-Intel(R) Core(TM) i7-4600U CPU @ 2.10GHz, 2756/5577 MB (1024 MB allocatable), 4MCU

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256

Hashes: 8 digests; 8 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates

Optimizers applied:
* Zero-Byte
* Early-Skip
* Not-Salted
* Not-Iterated
* Single-Salt
* Brute-Force
* Raw-Hash

ATTENTION! Pure (unoptimized) backend kernels selected.
Pure kernels can crack longer passwords, but drastically reduce performance.
If you want to switch to optimized kernels, append -O to your commandline.
See the above message to find out about the exact limits.

Watchdog: Temperature abort trigger set to 90c

INFO: Removed 4 hashes found as potfile entries.

Host memory required for this attack: 1 MB

Approaching final keyspace - workload adjusted.


Session..........: hashcat
Status...........: Exhausted
Hash.Mode........: 100 (SHA1)
Hash.Target......: bftest3
Time.Started.....: Mon Feb 17 16:29:04 2025 (0 secs)
Time.Estimated...: Mon Feb 17 16:29:04 2025 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Mask.......: ?1?1?1 [3]
Guess.Charset....: -1 ?l?u?d, -2 Undefined, -3 Undefined, -4 Undefined
Guess.Queue......: 1/2 (50.00%)
Speed.#1.........: 42831.2 kH/s (2.20ms) @ Accel:512 Loops:62 Thr:1 Vec:8
Recovered........: 4/8 (50.00%) Digests (total), 0/8 (0.00%) Digests (new)
Progress.........: 238328/238328 (100.00%)
Rejected.........: 0/238328 (0.00%)
Restore.Point....: 3844/3844 (100.00%)
Restore.Sub.#1...: Salt:0 Amplifier:0-62 Iteration:0-62
Candidate.Engine.: Device Generator
Candidates.#1....: se8 -> XQz
Hardware.Mon.#1..: Temp: 60c Util: 27%

6aed2d31b342216b8eb17efb38fea65acadba793:ditr
cc1a9d0c2908cf24d19ec516ead4bf9c57825d6f:ztfd
5316157bc1017ef46a8fda61701ba35618820814:otou
82ac0b5f2b95574b3b9edb15fc58d8b9f1293646:r2d2

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 100 (SHA1)
Hash.Target......: bftest3
Time.Started.....: Mon Feb 17 16:29:04 2025 (0 secs)
Time.Estimated...: Mon Feb 17 16:29:04 2025 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Mask.......: ?1?1?1?1 [4]
Guess.Charset....: -1 ?l?u?d, -2 Undefined, -3 Undefined, -4 Undefined
Guess.Queue......: 2/2 (100.00%)
Speed.#1.........: 54930.6 kH/s (2.07ms) @ Accel:512 Loops:62 Thr:1 Vec:8
Recovered........: 8/8 (100.00%) Digests (total), 4/8 (50.00%) Digests (new)
Progress.........: 4825088/14776336 (32.65%)
Rejected.........: 0/4825088 (0.00%)
Restore.Point....: 75776/238328 (31.79%)
Restore.Sub.#1...: Salt:0 Amplifier:0-62 Iteration:0-62
Candidate.Engine.: Device Generator
Candidates.#1....: s9EW -> X0m0
Hardware.Mon.#1..: Temp: 60c Util: 70%

Started: Mon Feb 17 16:29:02 2025
Stopped: Mon Feb 17 16:29:06 2025

┌──(root㉿d119cd163bf4)-[/home/shared/ds5/ex51]
└─# hashcat -m 100 bftest3 -a3 -1?l?u?d ?1?1?1?1 --increment --increment-min 3 --show
d4656cd76152f93e6a70374ff9b0e54f84363d6a:NML
a9993e364706816aba3e25717850c26c9cd0d89d:abc
cd3f0c85b158c08a2b113464991810cf2cdfc387:666
ace445715d71cd2614bf1ab16ea3fda5162c15e3:a1a
5316157bc1017ef46a8fda61701ba35618820814:otou
6aed2d31b342216b8eb17efb38fea65acadba793:ditr
cc1a9d0c2908cf24d19ec516ead4bf9c57825d6f:ztfd
82ac0b5f2b95574b3b9edb15fc58d8b9f1293646:r2d2

┌──(root㉿d119cd163bf4)-[/home/shared/ds5/ex51]
└─#
Example 6. Content of ~/.local/share/hashcat/hashcat.potfile
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
┌──(root㉿d119cd163bf4)-[/home/shared/ds5/ex51]
└─# cat ~/.local/share/hashcat/hashcat.potfile
a9993e364706816aba3e25717850c26c9cd0d89d:abc
cd3f0c85b158c08a2b113464991810cf2cdfc387:666
ace445715d71cd2614bf1ab16ea3fda5162c15e3:a1a
d4656cd76152f93e6a70374ff9b0e54f84363d6a:NML
6aed2d31b342216b8eb17efb38fea65acadba793:ditr
cc1a9d0c2908cf24d19ec516ead4bf9c57825d6f:ztfd
5316157bc1017ef46a8fda61701ba35618820814:otou
82ac0b5f2b95574b3b9edb15fc58d8b9f1293646:r2d2

Security in Web Applications

Generating a Dynamic Express Site

Normally, probably, we generate dynamic, not static, sites. This is what most web development is about. Doing that we must reference a templating engine that is capable of interpolating variable dynamic data into an HTML5 template. Because there is a choice of templating engines we denominate one, pug. The default is, strangely, an ancestor of pug, jade that nobody uses any more.

Example 7. Create Dynamic Express Site
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
 $ npx express-generator -v pug --git firstlocal

   create : firstlocal/
   create : firstlocal/public/
   create : firstlocal/public/javascripts/
   create : firstlocal/public/images/
   create : firstlocal/public/stylesheets/
   create : firstlocal/public/stylesheets/style.css
   create : firstlocal/routes/
   create : firstlocal/routes/index.js
   create : firstlocal/routes/users.js
   create : firstlocal/views/
   create : firstlocal/views/error.pug
   create : firstlocal/views/index.pug
   create : firstlocal/views/layout.pug
   create : firstlocal/.gitignore
   create : firstlocal/app.js
   create : firstlocal/package.json
   create : firstlocal/bin/
   create : firstlocal/bin/www

   change directory:
     $ cd firstlocal

   install dependencies:
     $ npm install

   run the app:
     $ DEBUG=firstlocal:* npm start

$ cd firstlocal
$ mkdir controllers
$ touch controllers/controllers.js
$ mkdir models
$ touch models/dbHandler.js
$ git init
$ npm i
$ npm audit fix --force
$ npm audit fix --force
$ DEBUG=firstlocal:* npm start

In your browser navigate to http://localhost:3000 and see the welcome page.

In order to be up-to-date with JavaScript and practical work, first edit package.json

Example 8. Edited package.json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
$ cat package.json
{
  "name": "firstlocal",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www",
    "pretest": "npm install",
    "test": "DEBUG=firstlocal:* node ./bin/www"
  },
  "dependencies": {
    "cookie-parser": "~1.4.4",
    "debug": "~2.6.9",
    "express": "^4.21.2",
    "http-errors": "~1.6.3",
    "morgan": "~1.9.1",
    "pug": "^3.0.3"
  }
}

Lines 7 and 8 are new, and allow you to start a testrun with npm test. This will install all dependencies, and then start the server.

Then modernize the first lines of the app.js setup file so that all occurrences of var are replaced by const. It must look similar to the following:

Example 9. The First Lines of the Essential app.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
require('dotenv').config();

const cookieParser = require('cookie-parser');
const cors = require('cors');
const createError = require('http-errors');
const express = require('express');
const helmet = require('helmet');
const logger = require('morgan');
const path = require('path');

const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');

const app = express();

// view engine setup 
// ... rest untouched

The first line is new, actually optional, but needed more often than not.

Requiring cors, re https://www.npmjs.com/package/cors, and helmet, re https://www.npmjs.com/package/helmet are essentially security related and should be considered in any project.

I have ordered the lines so that the external requirements are sorted alphabetically. Makes them easier on the maintainers eye.

JWT, Json Web Tokens

In order to safeguard security in a web application the user must authenticate. For regular applications this results in the creation of a session. A session is signified by a token, a variable, that is created at succesful login, and it will exist until the user logs out. Its existence will be checked when privileged activities in the application require login.

When web applications are using the REST API, the users must still authenticate to achieve certain privileged permissions, but the session is sometimes replaced by a token that, by and large, has the same function as a session cookie. The formalities behind these token are found in [JSON Web Token (JWT) Response for OAuth Token Introspection)(https://www.rfc-editor.org/rfc/rfc9701.txt)

In node application we have an implementation in https://www.npmjs.com/package/jsonwebtoken that may be installed by npm i jsonwebtoken.

The latter link has several usage examples. A token should be created at succesful uthentication by code similar to

Example 10. Create Json Web Token. From coursecode/eduapi
1
2
3
4
5
6
7
8
9
const jwt = require("jsonwebtoken");

authUser(req, res, next) {
	...	
	const payload = { uid: user.uid };
	const lifetime = { expiresIn: 3600 };
	let token = await jwt.sign(payload, process.env.SECRET, lifetime);
	...
},
Example 11. Verify Token
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
isAuth: async function (req, res, next) {

	...
	let token = req.headers.authorization.split(' ')[1];
	if (!token)
		throw new Error(errmsg);

	errmsg = 'Failed to authenticate token';
	let jwt = await jwt.verify(token, process.env.SECRET);
	if (!res)
		throw new Error(errmsg);

	// in casu jwt.uid holds the payload
	// if the lifetime of the token has not expired
	// the existence of the token should grant access
	...
},

Both code fragments are from a controller function. The variable process.env.SECRET will be a cat’s footprint kept in the .env file of the application.

Exercises

The rules for handing in assignments may be found in the README

Exercise DS.6.0

Todays exercise will consist of completing a partly done REST API application.

The skeleton application is to be forked from

https://codeberg.org/arosano/restapi.git

The Database sampleAPI.db

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
sqlite> .tables
city             country          user           
continent        countrylanguage

sqlite> .schema user
CREATE TABLE user (
        email varchar(64) unique not null,
        password blob not null,
        profile int not null default 99,
        bio text not null
);

sqlite> .schema continent
CREATE TABLE continent(
	name varchar(16) unique not null
);

sqlite> .schema country
CREATE TABLE country(
	code char(3) unique not null,
	name varchar(52) default null,
	continent varchar(16) not null,
	region varchar(26) default null,
	surfacearea float(10,2) not null default 0.0,
	indepyear int default null,
	population int not null default 0,
	lifeexpectancy float(3,1) default null,
	gnp float(10,2) default null,
	gnpold float(10,2) default null,
	localname varchar(45) default null,
	governmentform varchar(32) not null default '',
	headofstate varchar(32) default null,
	capital int default null,
	code2 char(2) unique not null,
	foreign key(continent) references continent(name)
);

sqlite> .schema city
CREATE TABLE city (
	name varchar(35) not null,
	countrycode char(3) not null,
	district varchar(20) default null,
	population int not null default 0,
	foreign key(countrycode) references country(code)
);

sqlite> .schema countrylanguage
CREATE TABLE countrylanguage (
	countrycode char(3) not null,
	language varchar(30) not null,
	isofficial boolean default 'false' not null,
	percentage float(4,1) not null default 0.0,
	primary key(countrycode, language),
	foreign key(countrycode) references country(code)
);

sqlite> select count(*) from user;
0
sqlite> select count(*) from continent;
7
sqlite> select count(*) from country;
239
sqlite> select count(*) from city;
4082
sqlite> select count(*) from countrylanguage;
991

Completion means:

  • In routes/users.js create whatever may be missing routes for
    • registering
    • logging in
  • Create controller function in controllersworld.js for adding a city to the database. Usage must require authentication, and admin privileges.
  • All reading and displaying of data must require authentication as at least a regular user.
  • Only an admin may change the entries in the user table ie change profiles.
  • Controller functions must be in controllers.js

The architecture is MVC, Model-View-Controller. You must not change that.

Please keep the separation of functionality between the two routers.