Data Security 6, Web Applications - I
References for this Part
Model Solutions Previous Lesson
DS.5.0
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
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
- 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.