एग्ज़ीक्यूटर को एक असली sandbox बनाना
ट्यूटोरियल का एग्ज़ीक्यूटर आपकी मशीन पर आपके क्रेडेंशियल्स के साथ हमलावर द्वारा प्रभावित किया जा सकने वाला कोड चलाता है। इससे पहले कि यह आपकी किसी ज़रूरी चीज़ को छुए, यहाँ बताया गया है कि क्या बदलना होगा।
60 सेकंड में
एग्ज़ीक्यूटर को एक असली sandbox बनाना
ट्यूटोरियल का एग्ज़ीक्यूटर आपकी मशीन पर आपके क्रेडेंशियल्स के साथ हमलावर द्वारा प्रभावित किया जा सकने वाला कोड चलाता है। इससे पहले कि यह आपकी किसी ज़रूरी चीज़ को छुए, यहाँ बताया…
run_python_code एक काम की चीज़ करता है — यह क्रैश होने पर पैरेंट को बंद होने से रोकता है। लेकिन यह वह काम नहीं करता जिसका इसका नाम वादा करता है। इसे एक असली sandbox की ज़रूरतों के सामने रखकर देखें:| प्रॉपर्टी | subprocess | अगर मौजूद न हो तो इसका क्या मतलब है |
|---|---|---|
| क्रैश होने पर भी पैरेंट बचा रहता है | ✓ | — |
| समय सीमा | ✓ (timeout=10) | एक अनियंत्रित लूप एजेंट को अटका (hang) देता है |
| फ़ाइलसिस्टम आइसोलेशन | ✗ | यह ~/.ssh, ~/.aws, आपका सोर्स कोड पढ़ सकता है |
| नेटवर्क आइसोलेशन | ✗ | यह आपकी फ़ाइलों को कहीं भी POST कर सकता है |
| क्रेडेंशियल आइसोलेशन | ✗ | यह आपके सभी एनवायरनमेंट वेरिएबल्स को इनहेरिट कर लेता है |
| मेमोरी / CPU सीमाएँ | ✗ | कोड की एक लाइन पूरी मशीन को थका (exhaust) सकती है |
| कोई पर्सिस्टेंस नहीं | ✗ | यह पीछे एक cron एंट्री छोड़ सकता है |
न्यूनतम व्यावहारिक (minimum viable) असली sandbox
import subprocess
import tempfile
import os
IMAGE = "python:3.12-slim"
def run_python_code(code_string, timeout=10):
"""Run untrusted code in a disposable container with no network."""
with tempfile.TemporaryDirectory() as workdir:
path = os.path.join(workdir, "main.py")
with open(path, "w") as f:
f.write(code_string)
try:
result = subprocess.run(
[
"docker", "run",
"--rm", # destroyed when it exits
"--network", "none", # no exfiltration path at all
"--memory", "256m",
"--cpus", "0.5",
"--pids-limit", "64", # blocks fork bombs
"--read-only", # immutable root filesystem
"--tmpfs", "/tmp:size=16m",
"--cap-drop", "ALL",
"--security-opt", "no-new-privileges",
"--user", "65534:65534", # nobody
"-v", f"{workdir}:/work:ro", # only this file, read-only
"-w", "/work",
IMAGE,
"timeout", str(timeout), "python", "main.py",
],
capture_output=True,
text=True,
timeout=timeout + 5,
env={"PATH": os.environ["PATH"]}, # NOT os.environ
)
except subprocess.TimeoutExpired:
return "EXECUTION ERROR:\nTimed out"
if result.returncode != 0:
return f"EXECUTION ERROR:\n{result.stderr[:4000]}"
return f"SUCCESS OUTPUT:\n{result.stdout[:4000]}"हर फ़्लैग क्यों मौजूद है
- 1
--network none
फ़ाइल की सबसे मूल्यवान एकल लाइन। बिना नेटवर्क इंटरफ़ेस के, जेनरेट किया गया कोड कुछ भी बाहर नहीं भेज (exfiltrate) सकता, चाहे उसे कुछ भी करने को कहा गया हो। Module 12 के हमलों का पूरा परिवार यहीं खत्म हो जाता है। - 2
--rm और साथ में एक टेम्प डायरेक्टरी
अल्पकालिक (Ephemeral)। कोड जो कुछ भी लिखता है, इंस्टॉल करता है या शेड्यूल करता है, वह रन के बाद नहीं बचता, इसलिए कोई हमला अगले यूज़र के सेशन में नहीं टिक सकता (Module 18)। - 3
--read-only और --cap-drop ALL
यह इमेज में बदलाव नहीं कर सकता और न ही प्रिविलेज हासिल कर सकता है।nobodyके रूप में चलाने के साथ मिलकर, कंटेनर से बाहर निकलने (escape) के लिए कॉन्फ़िगरेशन की गलती नहीं बल्कि कर्नेल बग की ज़रूरत होगी। - 4
env={"PATH": ...}
शांत पर असरदार।subprocess.runडिफ़ॉल्ट रूप से आपके पूरे एनवायरनमेंट को इनहेरिट कर लेता है — आपके द्वारा एक्सपोर्ट की गई हर API key को। एक स्पष्ट न्यूनतम env पास करना एक लाइन का बदलाव है जो लीक की पूरी श्रेणी को खत्म कर देता है। - 5
आउटपुट ट्रंकेशन (छंटाई)
एक मॉडल जो गीगाबाइट में प्रिंट करता है, वह आपकी कॉन्टेक्स्ट विंडो और आपके बिल दोनों को भर देता है। इस पर कैप (सीमा) लगाएँ।
जब कंटेनर भी काफ़ी न हों
- gVisor या Kata Containers — वर्कलोड और होस्ट कर्नेल के बीच एक syscall सीमा।
- Firecracker microVMs — असली वर्चुअलाइज़ेशन, ~125ms में बूट होता है। होस्टेड कोड-एग्ज़ीक्यूशन सेवाएँ इसी का उपयोग करती हैं।
- WebAssembly (Pyodide, Wasmtime) — बनावट से ही क्षमता-आधारित (capability-based): जब तक आप न दें, कोई फ़ाइलसिस्टम या नेटवर्क नहीं मिलता। बेहतरीन तब है जब कोड को सिर्फ़ गणना (compute) करनी हो।
- एक मैनेज्ड sandbox सर्विस — किसी और की समस्या, जो अक्सर सही इंजीनियरिंग समाधान होता है।
बाकी सुरक्षा उपाय
- इटरेशन, क्लॉक-टाइम और मॉडल के खर्च पर एक सख्त सीमा, जो लूप के बाहर लागू हो।
- रन करने से पहले हर जेनरेट किए गए कोड ब्लॉक का लॉग होना — यह आपका एकमात्र फोरेंसिक रिकॉर्ड है।
- एजेंट द्वारा sandbox के बाहर किसी भी चीज़ को छूने से पहले इंसानी मंज़ूरी।
- इन्फ़्रास्ट्रक्चर लेयर पर एक किल स्विच, न कि कोई ऐसा फ़्लैग जिसे एजेंट देख सके (Module 33)।
और देखें और पढ़ें
लैब
एक sandbox एस्केप जिसका आपने प्रयास किया और असफल रहे।
समस्या
ATTACKS = {
"read host": "print(open('/etc/passwd').read())",
"network": "import urllib.request; urllib.request.urlopen('http://example.com')",
"memory": "x = bytearray(10**9)",
"fork bomb": "import os\nwhile True: os.fork()",
"persist": "open('/work/persisted.txt','w').write('still here')",
}आपका काम तब पूरा होगा जब
कठिन सवाल
उत्तर देखने से पहले ख़ुद जवाब देने की कोशिश करें। अगर आप इनके जवाब दे सकते हैं, तो आप पाठ समझ गए हैं।
Q1आप --network none को हटाते हैं लेकिन बाकी सब रखते हैं। अब एक हमलावर क्या कर सकता है, उसे सबसे बुरे से शुरू करते हुए रैंक करें।दिखाएं
अक्सर पूछे जाने वाले सवाल
क्या मेरे इंटरनल टूल के लिए Docker काफ़ी है?
एक इंटरनल टूल के लिए जहाँ लक्ष्य आपकी अपनी टीम से आते हैं, हाँ, ऊपर बताए अनुसार कॉन्फ़िगर किया गया हो। यहाँ ख़तरा दुर्घटना और गलती का है, कर्नेल का फ़ायदा उठाने वाले किसी इरादतन हमलावर का नहीं।
क्या मैं खतरनाक कोड को चलाने से पहले सिर्फ़ फ़िल्टर नहीं कर सकता?
आप कोशिश कर सकते हैं, और यह एक स्पीड ब्रेकर जैसा है (Module 20)। open("/etc/passwd") को लिखने के अनगिनत तरीके हैं। आइसोलेशन इसलिए काम करता है क्योंकि यह हमले को पहचानने पर निर्भर नहीं करता।
sandbox के अंदर pip install के बारे में क्या?
इसके लिए नेटवर्क की ज़रूरत होती है, जिसे आपने अभी-अभी हटाया है। पैकेज को अपनी इमेज में पहले से बेक (pre-bake) कर लें। यह एक फ़ीचर है: आपका एजेंट आज PyPI द्वारा दी जा रही किसी भी चीज़ के बजाय एक ज्ञात डिपेंडेंसी सेट पर चलता है।
यह लूप को कितना धीमा कर देता है?
कंटेनर शुरू होने में लगभग 200-500ms लगते हैं। कई सेकंड की मॉडल कॉल के मुकाबले, यह नगण्य है। अगर आपको कभी फ़र्क पड़े तो एक वॉर्म पूल तैयार रखें।
पाठ का टेस्ट
5 सवाल। पास होने और इस पाठ को पूरा करने के लिए 3 सही होने चाहिए (60%)।