Go Fuzz Yourself

Avec sa nouvelle mouture du Langage Go, Google nous propose une fonctionnalité intéressante qui espérons le ne passera pas inaperçu. En effet, avec l’arrivée de la version 1.18, Go nous offre un mécanisme de Fuzzing qui s’intègre dans les tests unitaires.

Introduction

Le fuzzing est une technique souvent utilisée par les chercheurs en sécurité pour identifier des vulnérabilités dans un applicatif. Le principe est d’injecter de la donnée aléatoire pour valider qu’aucun comportement inattendu n’apparait lors de l’exécution du programme. Ces éléments aléatoires peuvent être injectés dans des champs textes, des arguments, des fichiers, des périphériques ou n’importe quels éléments contrôlable par l’utilisateur ou extérieur au programme. A l’aide de cette technique il est par exemple possible de détecter la présence de dépassement de mémoire, de race condition ou bien de fuites de mémoire.

A l’aide du fuzzing, les vulnérabilités suivantes ont d’ailleurs été identifiées :

  • CVE-2019-16411 : Déni de service ou fuite d’information sur le système de détection d’intrusion Suricata
  • CVE-2017-3732 : Fuite d’information sur la bibliothèque OpenSSL
  • CVE-2016-6978 : Déni de service ou exécution de code arbitraire via Adobe Reader
  • CVE-2015-0061 : Fuite d’information sur Windows et Windows server

Le fuzzing est une technique très proche du Monkey Testing, et se différencie uniquement par la méthode employée. Le fuzzing va envoyer de la donnée aléatoire dans l’ensemble de l’application, là où le monkey testing se concentrera sur l’exécution d’actions aléatoire.

Habituellement, ce type de test peut être effectué avec des fuzzers tels que wfuzz, AFL, ou Burpsuite. Ils se basent souvent sur des dictionnaires ou des mutations et cherchent à identifier des comportements ou résultats inattendu. L’intérêt en dehors d’un aspect uniquement “cybersécurité”, est d’obtenir un résultat non pris en compte dans les tests unitaires classiques et de pouvoir gérer le cas par la suite.

https://blog.hboeck.de/uploads/american-fuzzy-lop.png

Avec son approche Golang 1.18 permet d’intégrer la recherche de ces bugs directement dans les tests unitaires et donc d’anticiper l’apparition de régression de sécurité entre chaque évolution du code. Quand on a l’habitude de travailler avec de l’intégration continue (CI) comme c’est le cas chez Attineos, ce type de fonctionnalité devient tout de suite une aubaine.

Détecter une injection SQL

Prenons un exemple simple. Notre application est une API qui permet de récupérer une liste de produit depuis une base de données MariaDB/MySQL. Le code de la fonction est le suivant :

func GetProduct(db *sql.DB, id string) *sql.Rows {
	query := fmt.Sprintf("SELECT * FROM products WHERE id='%s'", id)
	res, err := db.Query(query)
     if err != nil {
		log.Fatal(err)
	}
	return res
}

Une analyse rapide du code, nous permet d’identifier une injection SQL juste avant l’exécution de la query. Néanmoins, il est tout à fait possible qu’un développeur passe à côté et ne remarque pas cette vulnérabilité. Sur un test unitaire classique, nous aurions ce type de code :

func TestGetProduct(t *testing.T) {
	// We open the database
	db := OpenDB()
	defer db.Close()

	// We prepare our testcases
	testcases := []struct {
		id string
		name string
		price float32
	}{
		{"1", "Pain au chocolat", 0.10},
		{"2", "Croissant", 0.80},
		{"3", "Pain aux raisins", 0.90},
	}

	// We try each testcase
	for _, tc := range testcases {
		// Retrieve the product
		row := GetProduct(db, tc.id)
		defer row.Close()

		// Check that the data retrieved is equals to our testcase
		for row.Next() {
			var id int
			var name string
			var price float32
			row.Scan(&id, &name, &price)

			if name != tc.name {
				t.Log("error should be " + tc.name + ", but got " + name)
				t.Fail()
			}

			if price != tc.price {
				t.Logf("error should be %f, but got %f", tc.price, price)
				t.Fail()
			}
		}
	}
}

Si nous exécutons les tests unitaires, tout se déroule normalement comme attendu :

Unit test

Il s’agit là d’unes des limites des tests unitaires, car il est compliqué d’écrire un testcase sur un scénario que l’on a pas identifié. Dans le cas présent il s’agit d’une injection SQL qui n’est pas détectée par les testcases que nous avons définis.

C’est dans ce genre de cas que le fuzzing devient intéressant. En créant un nouveau test, nous allons intégrer une dose d’aléatoire dans nos vérifications afin, que cette fois-ci, l’injection SQL soit détectée. Cette fois-ci notre testcase ne prend pas en argument testing.T mais testing.F. Notre test va donc commencer comme suit :

func FuzzGetProduct(f *testing.F) {
	db:= OpenDB()
	defer db.Close()

	// Our testcase
}

Avant de demander à notre test de lancer son fuzzing, il aura besoin de quelques valeurs valides pour la fonction GetProduct afin d’identifier le type de donnée attendu par la fonction. Nous allons donc lui en fournir quelques-uns :

	testcases := []string{"1", "2", "42", "15863060418"}
	for _, tc := range testcases {
		f.Add(tc)
	}

Une fois ces données fournies, nous pouvons entrer dans le vif du sujet avec la routine de fuzzing

	f.Fuzz(func(t *testing.T, orig string) {
		GetProduct(db, orig)
	})

Cette dernière partie est celle qui va venir fuzzer notre fonction GetProduct(). Notre test complet contient le code suivant :

func FuzzGetProduct(f *testing.F) {
	db := OpenDB()
	defer db.Close()

	testcases := []string{"1", "2", "15863060418"}
	for _, tc := range testcases {
		f.Add(tc)
	}
	f.Fuzz(func(t *testing.T, orig string) {
		GetProduct(db, orig)
	})
}

Si nous exécutons notre test à nouveau nous avons le même résultat :

# go test
PASS
ok      y0no/fuzz_example       0.004s

Sans arguments supplémentaires golang ne lance pas le fuzzing. Pour cela il est nécessaire d’ajouter l’option -fuzz avec le nom de notre test. Dans notre cas la commande sera go test -fuzz=GetProduct, ce qui nous donne le résultat suivant :

go test -fuzz=GetProduct
fuzz: elapsed: 0s, gathering baseline coverage: 0/3 completed
fuzz: elapsed: 0s, gathering baseline coverage: 3/3 completed, now fuzzing with 4 workers
fuzz: elapsed: 0s, execs: 14 (236/sec), new interesting: 1 (total: 4)
--- FAIL: FuzzGetProduct (0.06s)
    fuzzing process hung or terminated unexpectedly: exit status 1
    Failing input written to testdata/fuzz/FuzzGetProduct/7bd1b1c4c3d126e40a4f332964108261a47ec776a24b194fa56bdde7f649a336
    To re-run:
    go test -run=FuzzGetProduct/7bd1b1c4c3d126e40a4f332964108261a47ec776a24b194fa56bdde7f649a336
FAIL
exit status 1
FAIL    y0no/fuzz_example       0.066s

Comme nous pouvons le voir cette fois-ci, le test est à l’état FAIL suite à un argument invalide passé à GetProduct. Pour avoir le détail de ce test invalide, il nous suffit d’aller voir le fichier invalide généré :

cat testdata/fuzz/FuzzGetProduct/7bd1b1c4c3d126e40a4f332964108261a47ec776a24b194fa56bdde7f649a336
go test fuzz v1
string("'")

Dans le cas présent, nous voyons que c’est le caractère ' qui pose problème est fait planter le programme. Si nous injectons cette valeur dans la requête SQL, voici ce que nous obtenons :

query := "SELECT * FROM products WHERE id='''"

On voit très rapidement que notre caractère a été interprété comme une fermeture de chaine de caractère et que la fin de notre requête n’est plus valide. Notre injection SQL a bien été détectée cette fois-ci.

Pour corriger le code, nous pouvons passer par une requête préparée avec le code suivant :

func GetProduct(db *sql.DB, id string) *sql.Rows {
	res, err := db.Query("SELECT * FROM products WHERE id=?", id)
	if err != nil {
		log.Fatal(err)
	}
	return res
}

Si nous rééxecutons le test qui plantait précédemment, on voit qu’il ne nous pose plus de problème :

go test -run=FuzzGetProduct/7bd1b1c4c3d126e40a4f332964108261a47ec776a24b194fa56bdde7f649a336
PASS
ok      y0no/fuzz_example       0.003s

Grace à l’ajout du fuzzing dans nos tests nous avons :

  • identifié un scénario de test problématique
  • Identifié une vulnérabilité de type injection SQL
  • Corrigé “salement” l’injection SQL

Intégration continue

Par défaut, lorsque l’on lance les tests avec fuzzing, go envoi des données aléatoires de manière infinie. Dans le cas où l’on démarre ces tests dans un processus d’intégration continue, l’exécution infinie risque de nous poser problème. Pour faire face à cette contrainte, il est possible d’appliquer une option nommée -fuzztime permettant de définir un temps maximal d’exécution du fuzzing. Si le test dépasse ce temps, alors le programme s’arrête en considérant que le test est à l’état SUCCESS. Par exemple pour tester uniquement trente secondes de fuzzing :

go test -fuzz=GetProduct -fuzztime=10s

Il est donc très simple aujourd’hui d’ajouter une part d’aléatoire dans les tests unitaires d’un projet go avec cette version 1.18. Il serait donc dommage de s’en priver :)

Écoute du signal d'un Babyphone via RTL-SDR

Introduction

Depuis quelque semaines j’ai la chance d’être l’heureux “possesseur” d’un bébé. Pour surveiller le petit bonhomme, nous avons investi dans un babyphone tout ce qu’il y a de plus classique, c’est-à-dire avec un émetteur et un récepteur. Vu que je n’ai plus beaucoup l’occasion de dormir, qu’une RTL-SDR trainait sur le bureau, et que je ne connais pas très bien GNURadio. Je me suis fixé pour objectif de créer un circuit virtuel à l’aide de GNURadio Companion afin d’avoir un récepteur virtuel pour le babyphone avec les mêmes “fonctionnalités”. Néanmoins, avant de rentrer dans le vif du sujet, on va éclaircir quelques termes.

Une SDR est une radio définie par logiciel (Software Defined Radio). Il s’agit d’un appareil capable d’émettre et/ou de recevoir des signaux radio avec un circuit de traitement qui n’est pas géré par le hardware. Pour résumer, il est possible avec ces petites bêtes de “programmer” le traitement que l’on veut faire du signal à partir d’un ordinateur sans avoir à modifier les composants de la carte électronique. C’est le logiciel GNURadio Companion, qui dans le cadre de cet article permettra de “coder” le traitement des signaux envoyés par le babyphone. GNURadio Companion, abrégé GRC, est une interface graphique à GNURadio qui permet de facilement créer un circuit de traitement du signal en positionnant des blocs de traitement (filtrage, modulation, codage, etc.) et en les reliant les uns aux autres.

GNURadio Companion

Maintenant que nous avons défini les termes “complexes”, voici le plan d’action pour arriver à créer un récepteur logiciel pour le babyphone :

  • Identifier les fonctionnalités du récepteur
  • Trouver la fréquence et la modulation utilisées par l’émetteur
  • Créer le “circuit” de traitement GNURadio permettant d’émuler le récepteur

Étape 1 : Identifier les fonctionnalités du récepteur

Le récepteur du babyphone est plutôt simple, sans fonctionnalités exotiques. Au niveau des entrées, nous avons un réglage de volume permettant d’éteindre le récepteur lorsqu’il est sur 0. Un switch permettant de sélectionner le canal 1 ou 2 pour les communications. Et pour ce qui est des sorties, nous avons un hautparleur pour entendre ce qui se passe et une série de signaux lumineux pour avoir le niveau de “puissance” du signal.

Étape 2 : Trouver la fréquence et la modulation de l’émetteur

Il existe plusieurs méthodes permettant de trouver la fréquence de fonctionnement d’un émetteur. Il est tout d’abord possible de voir si une indication sur l’appareil affiche une fréquence de fonctionnement. Si ce n’est pas le cas, nous pouvons toujours nous retrancher sur la notice ou la documentation du constructeur. Dans notre cas, le site du constructeur indique un fonctionnement à 865MHz sans plus d’information. Pour un début c’est pas mal, malheureusement l’émetteur permet de sélectionner deux canaux. Il y a donc de forte chance que plusieurs fréquences soit utilisées.

Vu que le constructeur nous indique un fonctionnement à 865MHz, nous allons tenter d’effectuer un balayage fréquentiel à l’aide de l’outil rtl_power et de notre RTL-SDR. L’objectif ici, est de mesurer la puissance des signaux sur chaque fréquence, et donc de détecter les plages occupées.

Pour effectuer ce balayage, nous allons donc utiliser la commande rtl_power -f 840M:880M:100k res.csv. Celle-ci nous permet de scanner entre 860 et 880MHz avec un pas de 100kHz et d’enregistrer les mesures relevées dans un fichier nommé res.csv. Je laisse le script tourner pendant quelques minutes, tout en changeant le canal de l’émetteur de temps en temps.

A la fin de l’enregistrement, nous obtenons un fichier CSV de quelques kilo-octets pas forcément très lisible. Nous allons donc utiliser un script python permettant de rendre le résultat plus… graphique.

FFT

Nous voyons en jaune sur l’image les signaux reçus par la clé RTL-SDR. Nous apercevons très nettement deux bandes de fréquences en fonction du canal choisi. Nous pouvons donc valider que les fréquences utilisées par l’émetteur semblent être aux alentours de 864,3MHz pour le canal 1 et 863,2MHz pour le canal 2. Il nous manque donc maintenant la modulation pour pouvoir tenter de faire quelque chose avec GNURadio.

Pour la modulation, nous allons passer par l’outil GQRX, qui est une interface permettant de facilement manipuler une SDR. Nous allons donc pouvoir écouter les fréquences et voir si un signal audible sort avec les démodulations intégrées à l’outil.

En se mettant sur la fréquence 864.3MHz dans GQRX, on remarque très rapidement sur le spectre fréquentiel que l’on est “à côté” du signal.

Fréquence canal 1

En réalité, le premier canal semble être sur 864.44MHz et le second sur 863.25MHz. En sélectionnant le mode “WFM Mono”, on entend de suite ce qui est envoyé par l’émetteur.

WIN!

Maintenant que l’on a toutes les informations que l’on veut, nous allons pouvoir passer sur GNURadio Companion et tenter d’implémenter les fonctionnalités du récepteur dans un circuit virtuel.

Étape 3 : Création du circuit dans GNURadio

Pour notre circuit GNURadio, nous allons utiliser les blocs suivants :

  • Une source RTL-SDR pour capter le signal
  • Un bloc power squelch pour nettoyer le bruit
  • Un filtre passe bas
  • Un démodulateur WBFM
  • Une sortie audio pour entendre ce que l’on capte

Avec ces éléments, nous sommes en mesure de récupérer et traiter le signal venant de l’émetteur. Mais nous allons y ajouter quelques widgets, permettant de gérer graphiquement notre récepteur virtuel. Avec QT, nous pouvons ajouter un *QT GUI Chooser", pour que je puisse sélectionner le canal de communication, et un QT GUI Range afin que je puisse régler le volume à l’aide d’un slider.

Après avoir mis tout ce petit monde sur le plan et relier tout comme il faut, voici le circuit :

Circuit GNURadio

Une fois exécuté, nous sommes en mesure d’entendre les sons captés par l’émetteur et l’interface nous permet de gérer le volume ou bien de sélectionner le canal.

récepteur virtuel

Conclusion

Maintenant que l’on connait le fonctionnement du récepteur et celui du GNURadio Companion, pourquoi ne pas tenter de faire peur à la maman ? Et si la fois prochaine, nous tentions d’émettre un signal en se faisant passer pour l’émetteur du bébé ?

Ressources :