commit
b7072a0b49
@ -0,0 +1 @@
|
|||||||
|
IndentWidth: 4
|
@ -0,0 +1,10 @@
|
|||||||
|
*.pdf
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
|
||||||
|
client
|
||||||
|
server
|
||||||
|
|
||||||
|
correction.py
|
@ -0,0 +1,17 @@
|
|||||||
|
CC := gcc
|
||||||
|
CFLAGS := -std=gnu11 -Wall -Wextra -pedantic -g
|
||||||
|
LDLIBS := -lssl -lcrypto -lz
|
||||||
|
|
||||||
|
all: server client
|
||||||
|
|
||||||
|
server: server.c
|
||||||
|
$(CC) $(CFLAGS) $^ $(LDLIBS) -o $@
|
||||||
|
|
||||||
|
client: client.c
|
||||||
|
$(CC) $(CFLAGS) $^ $(LDLIBS) -o $@
|
||||||
|
|
||||||
|
format:
|
||||||
|
clang-format -i *.c
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm client server
|
@ -0,0 +1,103 @@
|
|||||||
|
#include <netdb.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
|
#define BUF_LEN 1024
|
||||||
|
|
||||||
|
void open_connection(const char *hostname, const char *port, const char *msg) {
|
||||||
|
int sfd, s;
|
||||||
|
struct addrinfo hints;
|
||||||
|
struct addrinfo *result, *rp;
|
||||||
|
memset(&hints, 0, sizeof(hints));
|
||||||
|
hints.ai_family = AF_UNSPEC;
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
hints.ai_flags = 0;
|
||||||
|
hints.ai_protocol = 0;
|
||||||
|
|
||||||
|
s = getaddrinfo(hostname, port, &hints, &result);
|
||||||
|
if (s != 0) {
|
||||||
|
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (rp = result; rp != NULL; rp = rp->ai_next) {
|
||||||
|
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||||
|
if (sfd == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) {
|
||||||
|
break; // Success
|
||||||
|
}
|
||||||
|
|
||||||
|
close(sfd);
|
||||||
|
}
|
||||||
|
freeaddrinfo(result);
|
||||||
|
|
||||||
|
if (rp == NULL) {
|
||||||
|
fprintf(stderr, "Could not connect\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_library_init();
|
||||||
|
OpenSSL_add_all_algorithms();
|
||||||
|
SSL_CTX *ctx = SSL_CTX_new(TLS_method());
|
||||||
|
SSL_CTX_set_security_level(ctx, 1);
|
||||||
|
SSL_CTX_clear_options(ctx, SSL_OP_NO_COMPRESSION);
|
||||||
|
SSL *ssl = SSL_new(ctx);
|
||||||
|
SSL_set_fd(ssl, sfd);
|
||||||
|
|
||||||
|
SSL_connect(ssl);
|
||||||
|
|
||||||
|
STACK_OF(SSL_COMP) *st = SSL_COMP_get_compression_methods();
|
||||||
|
int has_compression = sk_SSL_COMP_num(st);
|
||||||
|
if (st == NULL) {
|
||||||
|
fprintf(stderr, "Cannot list compression methods\n");
|
||||||
|
} else if (sk_SSL_COMP_num(st) == 0) {
|
||||||
|
fprintf(stderr, "No compression methods enabled\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[BUF_LEN] = {0};
|
||||||
|
strcat(buf, "GET /");
|
||||||
|
strncat(buf, msg, BUF_LEN / 2);
|
||||||
|
strcat(buf, " HTTP/1.1\r\nHost: iut.local\r\n");
|
||||||
|
strcat(buf, "Connection: close\r\n");
|
||||||
|
strcat(buf, "Cookie: flag=bAr5\r\n");
|
||||||
|
strcat(buf, "\r\n");
|
||||||
|
SSL_write(ssl, buf, strlen(buf));
|
||||||
|
|
||||||
|
if (has_compression) {
|
||||||
|
uLong ucompSize = strlen(buf);
|
||||||
|
// Use a compression level of 9
|
||||||
|
uLong compSize = compressBound(ucompSize);
|
||||||
|
char *comp = malloc(compSize);
|
||||||
|
compress(comp, &compSize, buf, ucompSize);
|
||||||
|
free(comp);
|
||||||
|
printf("Sent %lu data bytes\n", compSize);
|
||||||
|
} else {
|
||||||
|
printf("Sent %lu data bytes\n", strlen(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t nb_read = read(sfd, buf, BUF_LEN - 1);
|
||||||
|
buf[nb_read] = '\0';
|
||||||
|
printf("%s\n", buf);
|
||||||
|
|
||||||
|
SSL_free(ssl);
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
if (argc != 4) {
|
||||||
|
fprintf(stderr, "Usage: %s <hostname> <port> <message>\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
open_connection(argv[1], argv[2], argv[3]);
|
||||||
|
return 0;
|
||||||
|
}
|
@ -0,0 +1,195 @@
|
|||||||
|
#include "openssl/err.h"
|
||||||
|
#include "openssl/ssl.h"
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <resolv.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define BACKLOG 3
|
||||||
|
#define BUF_LEN 1024
|
||||||
|
|
||||||
|
int open_server(int port) {
|
||||||
|
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (serverSocket == -1) {
|
||||||
|
perror("Cannot create socket");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
struct sockaddr_in address;
|
||||||
|
memset(&address, 0, sizeof(address));
|
||||||
|
address.sin_family = AF_INET;
|
||||||
|
address.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
address.sin_port = htons(port);
|
||||||
|
if (bind(serverSocket, (struct sockaddr *)&address, sizeof(address)) ==
|
||||||
|
-1) {
|
||||||
|
perror("Bind failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listen(serverSocket, BACKLOG) == -1) {
|
||||||
|
perror("In listen");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return serverSocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_CTX *init_ssl_ctx(void) {
|
||||||
|
const SSL_METHOD *method;
|
||||||
|
SSL_CTX *ctx;
|
||||||
|
|
||||||
|
OpenSSL_add_all_algorithms();
|
||||||
|
SSL_load_error_strings();
|
||||||
|
STACK_OF(SSL_COMP) *s = SSL_COMP_get_compression_methods();
|
||||||
|
if (s == NULL) {
|
||||||
|
fprintf(stderr, "Cannot list compression methods\n");
|
||||||
|
} else if (sk_SSL_COMP_num(s) == 0) {
|
||||||
|
fprintf(stderr, "This version of OpenSSL does not have any compression "
|
||||||
|
"methods available, cannot enable TLS compression.\n");
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < sk_SSL_COMP_num(s); i++) {
|
||||||
|
const char *name = SSL_COMP_get_name(sk_SSL_COMP_value(s, i));
|
||||||
|
printf("Found compression method: %s\n", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
method = TLS_server_method();
|
||||||
|
ctx = SSL_CTX_new(method);
|
||||||
|
SSL_CTX_set_security_level(ctx, 1);
|
||||||
|
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_3);
|
||||||
|
SSL_CTX_clear_options(ctx, SSL_OP_NO_COMPRESSION);
|
||||||
|
|
||||||
|
if (ctx == NULL) {
|
||||||
|
ERR_print_errors_fp(stderr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_CTX_set_cipher_list(ctx, "ALL:eNULL");
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
int load_certificates(SSL_CTX *ctx, const char *cert_file,
|
||||||
|
const char *key_file) {
|
||||||
|
if (SSL_CTX_load_verify_locations(ctx, cert_file, key_file) != 1) {
|
||||||
|
ERR_print_errors_fp(stderr);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SSL_CTX_set_default_verify_paths(ctx) != 1) {
|
||||||
|
ERR_print_errors_fp(stderr);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SSL_CTX_use_certificate_file(ctx, cert_file, SSL_FILETYPE_PEM) <= 0) {
|
||||||
|
ERR_print_errors_fp(stderr);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (SSL_CTX_use_PrivateKey_file(ctx, key_file, SSL_FILETYPE_PEM) <= 0) {
|
||||||
|
ERR_print_errors_fp(stderr);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!SSL_CTX_check_private_key(ctx)) {
|
||||||
|
fprintf(stderr, "Private key does not match the public certificate\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int handle_request(SSL *ssl, int fd) {
|
||||||
|
const COMP_METHOD *comp = SSL_get_current_compression(ssl);
|
||||||
|
if (comp == NULL) {
|
||||||
|
printf("No compression\n");
|
||||||
|
} else {
|
||||||
|
const char *name = COMP_get_name(comp);
|
||||||
|
if (name == NULL) {
|
||||||
|
fprintf(stderr, "Cannot get compression name!\n");
|
||||||
|
} else {
|
||||||
|
printf("Compression: %s\n", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[BUF_LEN];
|
||||||
|
int sd, bytes;
|
||||||
|
|
||||||
|
char output[] = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\nContent-Type: "
|
||||||
|
"text/plain\r\n\r\nHello World!\n";
|
||||||
|
|
||||||
|
if (SSL_accept(ssl) == -1) {
|
||||||
|
const char *cipher = SSL_get_cipher(ssl);
|
||||||
|
if (cipher == NULL || strcmp(cipher, "NONE")) {
|
||||||
|
char error_message[] =
|
||||||
|
"HTTP/1.1 400 Bad Request\r\nContent-Type: "
|
||||||
|
"text/plain\r\nContent-Length: 18\r\n\r\nInvalid protocol.\n";
|
||||||
|
write(fd, error_message, strlen(error_message));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
ERR_print_errors_fp(stderr);
|
||||||
|
} else {
|
||||||
|
bytes = SSL_read(ssl, buf, sizeof(buf));
|
||||||
|
if (bytes > 0) {
|
||||||
|
buf[bytes] = 0;
|
||||||
|
printf("Client msg: \"%s", buf);
|
||||||
|
SSL_write(ssl, output, strlen(output));
|
||||||
|
} else {
|
||||||
|
ERR_print_errors_fp(stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sd = SSL_get_fd(ssl);
|
||||||
|
SSL_free(ssl);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
SSL_CTX *ctx;
|
||||||
|
int server;
|
||||||
|
int port;
|
||||||
|
if (argc < 2) {
|
||||||
|
fprintf(stderr, "Usage: %s <port>\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
port = atoi(argv[1]);
|
||||||
|
}
|
||||||
|
if (port < 1 || port > 65535) {
|
||||||
|
fprintf(stderr, "Invalid port number: %s\n", argv[1]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char cert_file[] = "cert.pem";
|
||||||
|
const char key_file[] = "key.pem";
|
||||||
|
|
||||||
|
SSL_library_init();
|
||||||
|
|
||||||
|
ctx = init_ssl_ctx();
|
||||||
|
if (ctx == NULL) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (load_certificates(ctx, cert_file, key_file) == -1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
server = open_server(port);
|
||||||
|
if (server == -1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("Listening on https://localhost:%d/\n", port);
|
||||||
|
while (1) {
|
||||||
|
struct sockaddr_in addr;
|
||||||
|
socklen_t len = sizeof(addr);
|
||||||
|
SSL *ssl;
|
||||||
|
|
||||||
|
int client = accept(server, (struct sockaddr *)&addr,
|
||||||
|
&len);
|
||||||
|
printf("Connection: %s:%d\n", inet_ntoa(addr.sin_addr),
|
||||||
|
ntohs(addr.sin_port));
|
||||||
|
ssl = SSL_new(ctx);
|
||||||
|
SSL_set_fd(ssl, client);
|
||||||
|
handle_request(ssl, client);
|
||||||
|
close(client);
|
||||||
|
}
|
||||||
|
close(server);
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
return 0;
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
_Ce sujet a été testé à l'IUT (OpenSSL 1) et sur une machine personnelle (OpenSSL 3). Il ne fonctionne que sous GNU/Linux._
|
||||||
|
|
||||||
|
Paquets nécessaires sous Debian : `build-essentials zlib1g-dev libssl-dev`
|
||||||
|
|
||||||
|
= Préparation
|
||||||
|
|
||||||
|
Cloner le dépôt Git à l'IUT ou sur votre machine.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://codefirst.iut.uca.fr/git/clement.freville2/http-crime
|
||||||
|
cd crime-http
|
||||||
|
```
|
||||||
|
|
||||||
|
Compiler le projet avec Make, et générez un certificat auto-signé à l'aide d'OpenSSL.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make
|
||||||
|
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365 -nodes
|
||||||
|
```
|
||||||
|
|
||||||
|
= Observation
|
||||||
|
|
||||||
|
- Exécuter le serveur HTTPS avec `./server 8080`. Dans un autre terminal, afficher le contenu de la page d'accueil avec `curl -k https://localhost:8080`.
|
||||||
|
|
||||||
|
- Tester si la compression TLS est active avec OpenSSL. Pourquoi est-elle désactivée ?
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl s_client -connect 127.0.0.1:8080 -comp < /dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
- Nous allons recompiler la bibliothèque #link("https://www.openssl.org/")[OpenSSL] avec le support de la bibliothèque zlib. Comparer avec comment la plupart des #link("https://git.alpinelinux.org/aports/tree/main/openssl/APKBUILD?h=3.18-stable#n132")[distributions GNU/Linux] fournissent OpenSSL. Donner une raison pour laquelle l'option `no-zlib` est précisée.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /tmp
|
||||||
|
curl https://www.openssl.org/source/openssl-3.1.3.tar.gz | tar xzf -
|
||||||
|
cd openssl-3.1.3
|
||||||
|
./config zlib
|
||||||
|
make -j$(nproc)
|
||||||
|
```
|
||||||
|
|
||||||
|
- La variable d'environnement `LD_PRELOAD`#footnote[`man 8 ld.so`] permet de charger une bibliothèque compilée avant toutes les autres. Cela nous permet dans notre cas de remplacer la version d'OpenSSL du système par notre version. Appeler l'exécutable `server` et `client` avec les fichiers `.so` issus de la compilation d'OpenSSL.
|
||||||
|
|
||||||
|
- Observer le comportment du client. Quels arguments prend-t-il en compte ? Que connaît-on sur le résultat de la requête HTTPS ?
|
||||||
|
|
||||||
|
= Attaque
|
||||||
|
|
||||||
|
Profiter des vulnérabilités de CRIME pour récupérer le _flag_ en jeu. Il s'agit d'un cookie nommé `flag` envoyé par le client vers le serveur.
|
||||||
|
|
||||||
|
Pour cela, le code Python suivant peut vous être utile. On rappelle le principe de l'attaque CRIME : lorsque qu'un attaquant peut contrôler une partie des données, il peut progressivement récupérer le cookie en modifiant les parties et en observant comment la taille totale de la requête change pendant la compression.
|
||||||
|
|
||||||
|
Une séquence de caractères répétée sera compressée. Par conséquence, si une partie de l'URL correspond à une partie du cookie, alors la taille de la requête sera plus petite. Il suffit alors de tester toutes les possibilités pour chaque caractère du cookie tant qu'on peut ajouter un caractère qui réduit ou ne modifie la taille.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from string import ascii_lowercase, ascii_uppercase, digits
|
||||||
|
from subprocess import check_output
|
||||||
|
|
||||||
|
alphabet = ascii_lowercase + ascii_uppercase + digits
|
||||||
|
|
||||||
|
def request(url: str) -> str:
|
||||||
|
"""Executes the client binary with the following URL, and checks its output."""
|
||||||
|
# TODO: extraire l'information utile pour l'attaque
|
||||||
|
return check_output(["./client", "127.0.0.1", "8080", url]).decode('utf-8')
|
||||||
|
```
|
Loading…
Reference in new issue