
مثال Seal Security — تطبيق npm ضعيف (EJS CVE-2022-29078) تمت معالجته إلى إصدارات محكمة؛ تكامل GitHub Actions و Jenkins
تطبيق Node.js/Express بسيط وهشّ عمدًا، يُستخدم للتوضيح، من البداية إلى النهاية، كيف يعالج Seal Security ثغرة CVE معروفة عن طريق استبدال تبعية هشّة بنسخة مختومة (مُعاد نقل التصحيح إليها وجاهزة كبديل مباشر) — دون أي تغيير في نطاقات الإصدارات المُعلنة أو في كودك.
وهو مصمم ليكون اختبار دخان شاملًا لواجهة Seal CLI في بيئات CI/CD: شغّل التطبيق، وأطلق استغلالًا حقيقيًا، ثم شغّل Seal وشاهد الاستغلال نفسه يُحظَر.
| النظام البيئي | JavaScript / npm |
| الحزمة الهشّة | [email protected] (تُحل إلى 2.7.4) |
| CVE | CVE‑2022‑29078 — حقن قوالب من جانب الخادم في EJS → تنفيذ كود عن بُعد (CVSS 9.8) |
| النسخة المختومة (المُصحَّحة) | ejs 2.7.4-sp1 من سجل npm التابع لـ Seal |
| التكامل | Seal CLI كخطوة بناء واحدة — موضّح لكلٍّ من GitHub Actions وJenkins |
يأتي التطبيق أيضًا مع تبعيات هشّة أخرى معروفة (lodash 4.17.5 وjson5 0.5.1 وgot 6.7.1)، وكل واحدة منها يعالجها Seal أيضًا إلى بناء مختوم.
ينشر التطبيق سلسلة استعلام URL بأكملها مباشرةً في استدعاء عرض EJS:
const data = { name: 'World', ...req.query };
res.render('page', data, ...)
تقبل EJS كائن settings['view options'] الذي تُكتب قيمة outputFunctionName فيه — دون تعقيم — داخل جسم دالة القالب المُجمَّعة. لذلك يمكن للمهاجم حقن كود JavaScript عشوائي يعمل على الخادم بصلاحيات عملية Node.js.
طلب عادي
/?name=alice
يعرض Hello alice!.
طلب استغلال
/?name=Hacker&settings[view%20options][outputFunctionName]=x;setTimeout(function()%7Bprocess.exit(1)%7D,3000);s
ينفّذ الخادم setTimeout(function(){ process.exit(1) }, 3000). تُحمَّل الصفحة أولًا وتذكر بوضوح أن RCE نجح؛ أعد التحميل بعد بضع ثوانٍ وستحصل على ERR_CONNECTION_REFUSED — فالكود المُحقن قتل الخادم، مما يثبت أنه تم تنفيذ كود عشوائي.
التأخير البالغ 3 ثوانٍ مقصود: فهو يتيح للاستجابة الوصول إلى المتصفح قبل خروج العملية، بحيث ترى صفحة “نجح الاستغلال” ثم انهيارًا نظيفًا بدلًا من تبويب متجمد.
.
├── index.js # the vulnerable Express app
├── views/ # EJS templates
├── package.json / package-lock.json
├── Jenkinsfile # example Jenkins (Groovy) pipeline with the Seal stage
└── .github/workflows/
├── build-and-run.yml # build + expose the app for browser testing
└── seal-security.yml # run Seal remediation, then start the app
Seal هو SaaS مُستضاف لدى Seal — لا يُثبَّت أي شيء داخل بيئتك، وكل الاتصالات صادرة عبر HTTPS على TCP 443 فقط. لتشغيل المعالجة، تحتاج إلى:
| السر / بيانات الاعتماد | يُستخدم لـ |
|---|
قم بتهيئتها في Settings → Secrets and variables → Actions (على GitHub) أو Manage Jenkins → Credentials (على Jenkins). لا تقم أبدًا بإيداع الرموز في المستودع.
أضِف مضيفات Seal التالية إلى قائمة السماح للاتصالات الصادرة عبر المنفذ 443:
app.sealsecurity.io وauthorization.sealsecurity.io وcli.sealsecurity.io، وكذلك — بالنسبة إلى حزم npm المختومة — npm.sealsecurity.io. يتم تنزيل ملف CLI الثنائي من github.com / objects.githubusercontent.com.
npm install
npm start # → http://localhost:3001
افتح http://localhost:3001/?name=alice (يعمل)، ثم رابط الاستغلال أعلاه (يُسقط الخادم).
تعمل واجهة Seal CLI كخطوة إضافية واحدة، بعد npm install وقبل التغليف. وهي تفحص التبعيات المُحلَّلة وتعيد كتابة التبعيات الهشّة إلى نسخها المختومة، باستخدام وضع الإصلاح البعيد (تُدار السياسة مركزيًا في واجهة Seal UI).
يستخدم seal-community/cli-action:
- uses: seal-community/cli-action@latest
with:
mode: fix
fix_mode: remote
token: ${{ secrets.SEAL_TOKEN }}
target: package-lock.json # the lock file for this ecosystem
شغّلها عبر Actions → “Seal Security Remediation” → Run workflow. راجع ملف .github/workflows/seal-security.yml.
مرحلة واحدة تُضاف، بعد التثبيت وقبل التغليف. راجع ملف Jenkinsfile:
stage('Seal') {
steps {
sh '''
curl -fsSL https://github.com/seal-community/cli/releases/download/latest/seal-linux-amd64-latest -o seal
chmod +x seal
./seal fix --mode remote "$SEAL_MANIFEST" # SEAL_MANIFEST=package-lock.json
'''
}
}
يأتي SEAL_TOKEN من بيانات اعتماد Jenkins باسم seal-token؛ واضبط SEAL_PROJECT على معرّف مشروع Seal لديك.
بعد تنفيذ seal fix، تُحل التبعيات الهشّة إلى نسخ مختومة من سجل Seal — وتبقى نطاقات الإصدارات في package.json كما هي:
النسخة المختومة هي نفس الحزمة مع إعادة نقل تصحيح الأمان إليها، لذا فهي بديل مباشر جاهز — دون أي تغييرات في الكود ودون ترقية إلى إصدار رئيسي جديد.
أعد تشغيل رابط الاستغلال ضد التطبيق المُعالَج. لم يعد الحقن يُنفَّذ: فحزمة ejs المختومة ترفض outputFunctionName الخبيث، ويستجيب التطبيق بـ**“Invalid parameter”** بدلًا من تنفيذ الحمولة. ويبقى الخادم قيد التشغيل.
seal fix إلى ملف البيان/القفل المحدد — package-lock.json بالنسبة إلى npm. وبالنسبة إلى مستودع يحتوي على ملفات بيان متعددة، شغّل seal fix واحدًا لكل ملف بيان.هذا هو التكامل بأكمله — مرحلة واحدة، اتصالات صادرة فقط، دون أي تغييرات في كود التطبيق.
| أين يوضع |
|---|
| رمز Seal | مصادقة واجهة Seal CLI | سر GitHub Actions SEAL_TOKEN / بيانات اعتماد Jenkins من نوع "Secret text" باسم seal-token |
| رمز ngrok (اختياري) | كشف التطبيق الجاري أمام المتصفح للاختبار | سر GitHub Actions NGROK_TOKEN |
| التبعية | قبل | بعد (مختومة) |
|---|
| ejs | 2.7.4 | 2.7.4‑sp1 |
| lodash | 4.17.5 | 4.17.5‑sp1 |
| json5 | 0.5.1 | 0.5.1‑sp1 |
| got | 6.7.1 | 6.7.1‑sp1 |