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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
|
// https://cryptopals.com/sets/2/challenges/13
#include "util/aes.h"
#include "util/random.h"
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#define MAX_KEY_LEN 16
#define MAX_VAL_LEN 128
enum state { START, KEY, EQ, VAL };
static unsigned char c_key[16];
struct profile {
int uid;
char *email;
char *role;
};
static char *serialize_profile(struct profile *p) {
char *str = NULL;
asprintf(&str, "email=%s&uid=%d&role=%s", p->email, p->uid,
p->role); // asprintf for memory safety :^)
return str;
}
// far too laborous parser...
static struct profile *parse_profile(char *str) {
struct profile *profile = malloc(sizeof(struct profile));
char *c = str;
enum state s = START;
char key[MAX_KEY_LEN] = {0}, val[MAX_VAL_LEN] = {0};
size_t key_ix = 0, val_ix = 0;
while (*c != '\0') {
switch (s) {
case START:
// expect start of key
if (isalpha(*c) || *c == '_') {
key[key_ix++] = *c;
s = KEY;
} else {
printf("expected start of key, but got: %c\n", *c);
exit(1);
}
break;
case KEY:
if (isalnum(*c) || *c == '_') {
if (key_ix == MAX_KEY_LEN) {
printf("max key len exceeded: %d chars\n", MAX_KEY_LEN);
exit(1);
}
// continue with current key
key[key_ix++] = *c;
} else if (*c == '=') {
// NULL-terminate key
key[key_ix] = '\0';
s = EQ;
} else {
printf("expected continuation of key or '=', but got: %c\n", *c);
exit(1);
}
break;
case EQ:
/*
* we could be smarter here and prevent non-printable characters,
* but actually the task itself has a solution which requires using
* '\x0b' char in the value. so we can simply skip '=' and '&' to disable
* SQL-injection style attacks.
*/
if (*c != '=' && *c != '&') {
val[val_ix++] = *c;
s = VAL;
}
break;
case VAL:
if (val_ix == MAX_VAL_LEN) {
printf("max val len exceeded: %d chars\n", MAX_VAL_LEN);
exit(1);
}
if (*c == '&') {
val[val_ix] = '\0';
s = START;
key_ix = 0;
val_ix = 0;
if (strcmp(key, "email") == 0)
profile->email = strdup(val);
else if (strcmp(key, "uid") == 0)
profile->uid = atoi(val);
else if (strcmp(key, "role") == 0)
profile->role = strdup(val);
else {
printf("unknown key: %s\n", key);
exit(1);
}
} else if (*c != '=') {
val[val_ix++] = *c;
s = VAL;
}
break;
}
c++;
}
if (s == VAL) {
val[val_ix] = '\0';
if (strcmp(key, "email") == 0)
profile->email = strdup(val);
else if (strcmp(key, "uid") == 0)
profile->uid = atoi(val);
else if (strcmp(key, "role") == 0)
profile->role = strdup(val);
else {
printf("unknown key: %s\n", key);
exit(1);
}
}
return profile;
}
static char *profile_for(char *email) {
if (strchr(email, '=') != NULL || strchr(email, '&') != NULL) {
printf("no & and = in emails pls\n");
exit(1);
}
struct profile *p = malloc(sizeof(struct profile));
p->uid = 10;
p->email = email;
p->role = "user";
char *res = serialize_profile(p);
free(p);
return res;
}
static int encrypt_profile(char *profile_str, unsigned char *out) {
EVP_CIPHER_CTX *ctx = evp_init();
int encrypted_len = evp_ecb_encrypt(ctx, (unsigned char *)profile_str,
strlen(profile_str), c_key, out);
evp_cleanup(ctx);
return encrypted_len;
}
static int decrypt_profile(unsigned char *in, int in_len, unsigned char *out) {
EVP_CIPHER_CTX *ctx = evp_init();
int decrypted_len = evp_ecb_decrypt(ctx, in, in_len, c_key, out);
evp_cleanup(ctx);
return decrypted_len;
}
static struct profile *make_admin_profile() {
/*
* first create a profile for specially crafted username (10 bytes + "admin"
* + padding to full block) to have encrypted version of "admin" end of the
* string: 0123456789abcdef | 0123456789abcdef | 0123456789abcdef |
* 0123456789abcdef email=aaaaaaaaaa | admin........... | &uid=10&role=use |
* r.............. cut this block ^
*/
char *fake = "aaaaaaaaaa"
"admin"
"\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b";
// 11 chars to pad, so we use '0x0b * 11'
unsigned char encrypted_fake[64];
encrypt_profile(profile_for(fake), encrypted_fake);
/*
* now replace 'user' part with block we cut before:
* 0123456789abcdef | 0123456789abcdef | 0123456789abcdef | 0123456789abcdef
* email=jack@test. | xyz&uid=10&role= | user |
* paste block here ^
*/
unsigned char encrypted[48];
encrypt_profile(profile_for("[email protected]"), encrypted);
// THE cut and paste
memcpy(&encrypted[32], &encrypted_fake[16], 16);
// decrypt, NULL-terminate, & parse
unsigned char decrypted[48];
int decrypted_len = decrypt_profile(encrypted, 48, decrypted);
decrypted[decrypted_len] = '\0';
return parse_profile((char *)decrypted);
}
int main() {
init_with_random_bytes(c_key, 16);
struct profile *admin = make_admin_profile();
printf("%s\n", serialize_profile(admin));
return 0;
}
|