Skip to content

Commit 6a89fdc

Browse files
authored
Fix issue #55 (#58)
* Fix issue #55 * Split hints into two separate files
1 parent f8ccb2b commit 6a89fdc

File tree

7 files changed

+170
-55
lines changed

7 files changed

+170
-55
lines changed

Season-1/Level-2/code.h

+64-13
Original file line numberDiff line numberDiff line change
@@ -15,42 +15,93 @@
1515

1616
#define MAX_USERNAME_LEN 39
1717
#define SETTINGS_COUNT 10
18-
int userid_next = 1;
18+
#define MAX_USERS 100
19+
#define INVALID_USER_ID -1
1920

21+
/*
22+
To keep things simple, both private (implementation specific) and public (API) parts of
23+
the application have been bundled inside this header file. In reality, you would
24+
only keep the API here. That being said, assume that the private sections would not be
25+
known to casual users of this module.
26+
*/
27+
28+
// Internal counter of user accounts.
29+
int userid_next = 0;
30+
31+
// This whole structure is purely an implementation detail and is supposed to be
32+
// unknown for normal users.
2033
typedef struct {
2134
bool isAdmin;
2235
long userid;
2336
char username[MAX_USERNAME_LEN + 1];
2437
long setting[SETTINGS_COUNT];
2538
} user_account;
2639

27-
user_account* create_user_account(bool isAdmin, const char* username) {
28-
user_account* ua;
29-
if (strlen(username) > MAX_USERNAME_LEN)
30-
return NULL;
40+
// Simulates an internal store of active user accounts.
41+
user_account *accounts[MAX_USERS];
42+
43+
// The signatures of the next 4 functions together with previously introduced constants (see #DEFINEs)
44+
// constitute the API of this module.
45+
46+
// Creates a new user account and returns it's unique identifier.
47+
int create_user_account(bool isAdmin, const char *username) {
48+
if (userid_next >= MAX_USERS) {
49+
fprintf(stderr, "the maximum number of users have been exceeded");
50+
return INVALID_USER_ID;
51+
}
52+
53+
user_account *ua;
54+
if (strlen(username) > MAX_USERNAME_LEN) {
55+
fprintf(stderr, "the username is too long");
56+
return INVALID_USER_ID;
57+
}
3158
ua = malloc(sizeof (user_account));
32-
if (NULL == ua) {
59+
if (ua == NULL) {
3360
fprintf(stderr, "malloc failed to allocate memory");
34-
return NULL;
61+
return INVALID_USER_ID;
3562
}
3663
ua->isAdmin = isAdmin;
3764
ua->userid = userid_next++;
3865
strcpy(ua->username, username);
3966
memset(&ua->setting, 0, sizeof ua->setting);
40-
return ua;
67+
accounts[userid_next] = ua;
68+
return userid_next++;
4169
}
4270

43-
bool update_setting(user_account* ua, const char *index, const char *value) {
71+
// Updates the matching setting for the specified user and returns the status of the operation.
72+
// A setting is some arbitrary string associated with an index as a key.
73+
bool update_setting(int user_id, const char *index, const char *value) {
74+
if (user_id < 0 || user_id >= MAX_USERS)
75+
return false;
76+
4477
char *endptr;
4578
long i, v;
4679
i = strtol(index, &endptr, 10);
4780
if (*endptr)
4881
return false;
49-
if (i >= SETTINGS_COUNT)
50-
return false;
82+
5183
v = strtol(value, &endptr, 10);
52-
if (*endptr)
84+
if (*endptr || i >= SETTINGS_COUNT)
5385
return false;
54-
ua->setting[i] = v;
86+
accounts[user_id]->setting[i] = v;
5587
return true;
88+
}
89+
90+
// Returns whether the specified user is an admin or not.
91+
bool is_admin(int user_id) {
92+
if (user_id < 0 || user_id >= MAX_USERS) {
93+
fprintf(stderr, "invalid user id");
94+
return false;
95+
}
96+
return accounts[user_id]->isAdmin;
97+
}
98+
99+
// Returns the username of the specified user.
100+
const char* username(int user_id) {
101+
// A better approach would be to signal an error.
102+
if (user_id < 0 || user_id >= MAX_USERS) {
103+
fprintf(stderr, "invalid user id");
104+
return NULL;
105+
}
106+
return accounts[user_id]->username;
56107
}

Season-1/Level-2/hack.c

+8-9
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,18 @@
2020
int main() {
2121
printf("Level 2 \n\n");
2222
// Creates a non-admin username called "pwned"
23-
user_account* ua = create_user_account(false, "pwned");
24-
printf("0. Non-admin (admin:%i) username called '%s' has been created \n", ua->isAdmin, ua->username);
23+
int user1 = create_user_account(false, "pwned");
24+
printf("0. Non-admin (admin:%i) username called '%s' has been created \n", is_admin(user1), username(user1));
2525

2626
// An outsider or an insider managed to supply the following input that originally aimed to change a dummy non-admin setting.
27-
update_setting(ua, "-7", "1");
27+
update_setting(user1, "-7", "1");
2828
printf("1. A dummy setting has been set to dummy number '1' \n");
29-
printf("2. Making sure user '%s' is not an admin by performing a check -> [Result] Admin:%i \n\n", ua->username, ua->isAdmin);
29+
printf("2. Making sure user '%s' is not an admin by performing a check -> [Result] Admin:%i \n\n", username(user1), is_admin(user1));
3030

31-
if (ua->isAdmin == 1)
32-
printf("<SOS> PRIVILEGE ESCALATION ATTACK DETECTED \n");
33-
34-
if (ua->isAdmin == 0)
35-
printf("CONGRATULATIONS LEVEL 2 PASSED!");
31+
if (is_admin(user1))
32+
printf("<SOS> PRIVILEGE ESCALATION ATTACK DETECTED\n");
33+
else
34+
printf("CONGRATULATIONS LEVEL 2 PASSED!\n");
3635

3736
return 0;
3837
}

Season-1/Level-2/hint-1.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Think about what could happen if an attacker would figure out the
2+
details of the user_account structure (see code.h).
3+
4+
Try again the exercise without looking further into hint-2.txt.

Season-1/Level-2/hint-2.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Have a look inside hack.c and look at what the attacker is passing as an argument.
2+
Then think if that value is overwriting something in memory.

Season-1/Level-2/hint.txt

-6
This file was deleted.

Season-1/Level-2/solution.c

+85-20
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
///////////////////////////////////////////////////
22
/// ///
3-
/// Vulnerability was in line 49 of code.h ///
4-
/// Fix can be found in line 47 below ///
3+
/// Vulnerability was in line 84 of code.h ///
4+
/// Fix can be found in line 83 below ///
55
/// ///
66
///////////////////////////////////////////////////
77

@@ -13,52 +13,117 @@
1313

1414
#define MAX_USERNAME_LEN 39
1515
#define SETTINGS_COUNT 10
16-
int userid_next = 1;
16+
#define MAX_USERS 100
17+
#define INVALID_USER_ID -1
1718

19+
/*
20+
To keep things simple, both private (implementation specific) and public (API) parts of
21+
the application have been bundled inside this header file. In reality, you would
22+
only keep the API here. That being said, assume that the private sections would not be
23+
known to casual users of this module.
24+
*/
25+
26+
// Internal counter of user accounts.
27+
int userid_next = 0;
28+
29+
// This whole structure is purely an implementation detail and is supposed to be
30+
// unknown for normal users.
1831
typedef struct {
1932
bool isAdmin;
2033
long userid;
2134
char username[MAX_USERNAME_LEN + 1];
2235
long setting[SETTINGS_COUNT];
2336
} user_account;
2437

25-
user_account* create_user_account(bool isAdmin, const char* username) {
26-
user_account* ua;
27-
if (strlen(username) > MAX_USERNAME_LEN)
28-
return NULL;
38+
// Simulates an internal store of active user accounts.
39+
user_account *accounts[MAX_USERS];
40+
41+
// The signatures of the next 4 functions together with previously introduced constants (see #DEFINEs)
42+
// constitute the API of this module.
43+
44+
// Creates a new user account and returns it's unique identifier.
45+
int create_user_account(bool isAdmin, const char *username) {
46+
if (userid_next >= MAX_USERS) {
47+
fprintf(stderr, "the maximum number of users have been exceeded");
48+
return INVALID_USER_ID;
49+
}
50+
51+
user_account *ua;
52+
if (strlen(username) > MAX_USERNAME_LEN) {
53+
fprintf(stderr, "the username is too long");
54+
return INVALID_USER_ID;
55+
}
2956
ua = malloc(sizeof (user_account));
30-
if (NULL == ua) {
57+
if (ua == NULL) {
3158
fprintf(stderr, "malloc failed to allocate memory");
32-
return NULL;
59+
return INVALID_USER_ID;
3360
}
3461
ua->isAdmin = isAdmin;
3562
ua->userid = userid_next++;
3663
strcpy(ua->username, username);
3764
memset(&ua->setting, 0, sizeof ua->setting);
38-
return ua;
65+
accounts[userid_next] = ua;
66+
return userid_next++;
3967
}
4068

41-
bool update_setting(user_account* ua, const char *index, const char *value) {
69+
// Updates the matching setting for the specified user and returns the status of the operation.
70+
// A setting is some arbitrary string associated with an index as a key.
71+
bool update_setting(int user_id, const char *index, const char *value) {
72+
if (user_id < 0 || user_id >= MAX_USERS)
73+
return false;
74+
4275
char *endptr;
4376
long i, v;
4477
i = strtol(index, &endptr, 10);
4578
if (*endptr)
4679
return false;
47-
if (i < 0 || i >= SETTINGS_COUNT) // FIX
48-
return false;
80+
4981
v = strtol(value, &endptr, 10);
50-
if (*endptr)
82+
// FIX: Checking for negative index values, too!
83+
if (*endptr || i < 0 || i >= SETTINGS_COUNT)
5184
return false;
52-
ua->setting[i] = v;
85+
accounts[user_id]->setting[i] = v;
5386
return true;
5487
}
5588

89+
// Returns whether the specified user is an admin or not.
90+
bool is_admin(int user_id) {
91+
if (user_id < 0 || user_id >= MAX_USERS) {
92+
fprintf(stderr, "invalid user id");
93+
return false;
94+
}
95+
return accounts[user_id]->isAdmin;
96+
}
97+
98+
// Returns the username of the specified user.
99+
const char* username(int user_id) {
100+
// A better approach would be to signal an error.
101+
if (user_id < 0 || user_id >= MAX_USERS) {
102+
fprintf(stderr, "invalid user id");
103+
return NULL;
104+
}
105+
return accounts[user_id]->username;
106+
}
107+
56108
/*
57-
Buffer Overflow Vulnerability
109+
Security through Obscurity Abuse Vulnerability
110+
--------------------------------------------
111+
You may read about the concept of security through obscurity here:
112+
https://en.wikipedia.org/wiki/Security_through_obscurity
113+
114+
In code.h the user_account structure is supposed to be an implementation
115+
detail not handed over to the user. Otherwise, they could easily modify the
116+
structure and change the isAdmin flag to true, thus gaining admin privileges.
117+
118+
Nonetheless, as this example illustrates, security through obscurity alone is not enough
119+
to secure your system. The attacker can easily reverse engineer the code and
120+
find the vulnerability. This is exposed in hack.c (see below).
58121
59-
In hack.c, an attacker escalated privileges and became an admin by abusing
60-
the fact that the code wasn't checking for negative index values.
122+
Buffer Overflow Vulnerability
123+
----------------------------
124+
In hack.c, an attacker escalated privileges and became an admin by abusing
125+
the fact that the code wasn't checking for negative index values.
61126
62-
Negative indexing here caused an unauthorized write to memory and affected a
63-
flag, changing a non-admin user to admin.
127+
Negative indexing here caused an unauthorized write to memory and affected a
128+
flag, changing a non-admin user to admin.
64129
*/

Season-1/Level-2/tests.c

+7-7
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,18 @@
1414
int main() {
1515
printf("Level 2 \n\n");
1616
// Creates a non-admin username called "pwned"
17-
user_account* ua = create_user_account(false, "pwned");
18-
printf("0. Non-admin (admin:%i) username called '%s' has been created \n\n", ua->isAdmin, ua->username);
17+
int user1 = create_user_account(false, "pwned");
18+
printf("0. Non-admin (admin:%i) username called '%s' has been created \n\n", is_admin(user1), username(user1));
1919

20-
printf("1. Non-admin users like '%s' can update some dummy numerical settings \n", ua->username);
20+
printf("1. Non-admin users like '%s' can update some dummy numerical settings \n", username(user1));
2121
printf("2. Non-admin users have no access to settings that can escalate themselves to admins \n\n");
2222

2323
// Updates the setting '1' of the pwned username to the number '10'
24-
update_setting(ua, "1", "10");
25-
printf("3. Dummy setting '1' has been now set to dummy number '10' for user '%s' \n", ua->username);
26-
printf("4. Making sure user '%s' is not an admin by performing a check -> [Result] Admin:%i \n\n", ua->username, ua->isAdmin);
24+
update_setting(user1, "1", "10");
25+
printf("3. Dummy setting '1' has been now set to dummy number '10' for user '%s' \n", username(user1));
26+
printf("4. Making sure user '%s' is not an admin by performing a check -> [Result] Admin:%i \n\n", username(user1), is_admin(user1));
2727

28-
if (ua->isAdmin == 0)
28+
if (!is_admin(user1))
2929
printf("User is not an admin so the code works as expected... is it though? \n");
3030

3131
return 0;

0 commit comments

Comments
 (0)