जावा एप्लिकेशन में दुर्भावनापूर्ण कोड के खिलाफ सैंडबॉक्स


91

एक सिमुलेशन सर्वर वातावरण में जहां उपयोगकर्ताओं को सर्वर द्वारा चलाने के लिए अपना कोड सबमिट करने की अनुमति होती है, यह स्पष्ट रूप से किसी भी उपयोगकर्ता द्वारा प्रस्तुत कोड को सैंडबॉक्स में चलाने के लिए फायदेमंद होगा, न कि Applets एक ब्राउज़र के विपरीत। मैं इन प्रस्तुत घटकों को अलग करने के लिए एक और वीएम परत जोड़ने के बजाय स्वयं जेवीएम का लाभ उठाने में सक्षम होना चाहता था।

मौजूदा जावा सैंडबॉक्स मॉडल का उपयोग करके इस तरह की सीमा संभव प्रतीत होती है, लेकिन क्या यह सक्षम करने के लिए एक गतिशील तरीका है जो चल रहे एप्लिकेशन के उपयोगकर्ता-प्रस्तुत भागों के लिए है?

जवाबों:


109
  1. अविश्वसनीय कोड को अपने स्वयं के धागे में चलाएं। उदाहरण के लिए यह अनंत छोरों और इस तरह की समस्याओं को रोकता है, और भविष्य के कदमों को आसान बनाता है। थ्रेड समाप्त होने के लिए मुख्य थ्रेड का इंतजार करें, और यदि बहुत लंबा समय लगता है, तो इसे थ्रेड के साथ मारें। Thread.stop को हटा दिया गया है, लेकिन चूंकि अविश्वास कोड की किसी भी संसाधन तक पहुंच नहीं होनी चाहिए, इसलिए इसे मारना सुरक्षित होगा।

  2. उस थ्रेड पर एक SecurityManager सेट करें । सुरक्षा प्रबंधक का एक उपवर्ग बनाएँ, जो चेकपेरमिशन (अनुमति की अनुमति) को ओवरराइड करता है, केवल कुछ चुनिंदा को छोड़कर सभी अनुमतियों के लिए SecurityException को फेंकने के लिए। विधियों की एक सूची और अनुमतियाँ हैं जिनकी उन्हें यहां आवश्यकता है: जावा टीएम 6 एसडीके में अनुमतियाँ

  3. असत्य कोड को लोड करने के लिए एक कस्टम ClassLoader का उपयोग करें। आपके क्लास लोडर को उन सभी वर्गों के लिए बुलाया जाएगा जो अविश्वास कोड का उपयोग करते हैं, इसलिए आप अलग-अलग JDK वर्गों तक पहुंच को अक्षम करने जैसे काम कर सकते हैं। करने की बात यह है कि अनुमत जेडीके कक्षाओं की एक सफेद सूची है।

  4. आप एक अलग JVM में अविश्वसनीय कोड चलाना चाहते हो सकता है। जबकि पिछले चरण कोड को सुरक्षित बनाएंगे, एक कष्टप्रद बात है कि अलग-थलग कोड अभी भी कर सकता है: जितना हो सके उतना मेमोरी आवंटित करें, जो मुख्य एप्लिकेशन के दृश्यमान पदचिह्न को बढ़ने का कारण बनता है।

JSR 121: एप्लिकेशन अलगाव एपीआई विनिर्देशन को हल करने के लिए डिज़ाइन किया गया था, लेकिन दुर्भाग्य से इसका कार्यान्वयन अभी तक नहीं हुआ है।

यह एक बहुत विस्तृत विषय है, और मैं ज्यादातर यह सब मेरे सिर के ऊपर से लिख रहा हूं।

लेकिन वैसे भी, कुछ अपूर्ण, उपयोग-पर-खुद का जोखिम, शायद छोटी गाड़ी (छद्म) कोड:

classloader

class MyClassLoader extends ClassLoader {
  @Override
  public Class<?> loadClass(String name) throws ClassNotFoundException {
    if (name is white-listed JDK class) return super.loadClass(name);
    return findClass(name);
  }
  @Override
  public Class findClass(String name) {
    byte[] b = loadClassData(name);
    return defineClass(name, b, 0, b.length);
  }
  private byte[] loadClassData(String name) {
    // load the untrusted class data here
  }
}

सुरक्षा प्रबंधक

class MySecurityManager extends SecurityManager {
  private Object secret;
  public MySecurityManager(Object pass) { secret = pass; }
  private void disable(Object pass) {
    if (pass == secret) secret = null;
  }
  // ... override checkXXX method(s) here.
  // Always allow them to succeed when secret==null
}

धागा

class MyIsolatedThread extends Thread {
  private Object pass = new Object();
  private MyClassLoader loader = new MyClassLoader();
  private MySecurityManager sm = new MySecurityManager(pass);
  public void run() {
    SecurityManager old = System.getSecurityManager();
    System.setSecurityManager(sm);
    runUntrustedCode();
    sm.disable(pass);
    System.setSecurityManager(old);
  }
  private void runUntrustedCode() {
    try {
      // run the custom class's main method for example:
      loader.loadClass("customclassname")
        .getMethod("main", String[].class)
        .invoke(null, new Object[]{...});
    } catch (Throwable t) {}
  }
}

4
उस कोड को कुछ काम की आवश्यकता हो सकती है। आप वास्तव में JVM उपलब्धता के खिलाफ पहरा नहीं दे सकते। प्रक्रिया को मारने के लिए तैयार रहें (शायद स्वचालित रूप से)। कोड अन्य थ्रेड्स पर मिलता है - उदाहरण के लिए अंतिम धागा। Thread.stopजावा लाइब्रेरी कोड में समस्याएं पैदा करेगा। इसी तरह, जावा लाइब्रेरी कोड को अनुमति की आवश्यकता होगी। SecurityManagerउपयोग करने की अनुमति देने के लिए बहुत बेहतर है java.security.AccessController। क्लास लोडर को संभवतः उपयोगकर्ता कोड की अपनी कक्षाओं तक पहुंच की अनुमति भी देनी चाहिए।
टॉम हॉल्टिन -

3
यह देखते हुए कि यह इतना जटिल विषय है, क्या जावा "प्लगइन्स" को सुरक्षित तरीके से संभालने के लिए मौजूदा समाधान नहीं हैं?
निक स्पेसक

9
इस दृष्टिकोण की समस्या है जब आप SecurityManager को System पर सेट करते हैं, यह न केवल रनिंग थ्रेड को प्रभावित करता है, बल्कि अन्य थ्रेड को भी प्रभावित करता है!
गेलिन लुओ

2
क्षमा करें, लेकिन थ्रेडस्टॉप () को फेंकने योग्य के साथ पकड़ा जा सकता है। आप कर सकते हैं जबकि (thread.isAlive) Thread.stop (), लेकिन फिर मैं अपवाद को पकड़ने वाले फ़ंक्शन को पुन: कॉल कर सकता हूं। मेरे पीसी पर परीक्षण किया गया, पुनरावर्ती फ़ंक्शन स्टॉप () पर जीतता है। अब आपके पास एक कूड़ा धागा है, जो सीपीयू और संसाधनों को चुरा रहा है
Lesto

8
इस तथ्य के अलावा कि System.setSecurityManager(…)पूरे JVM को प्रभावित करेगा, न केवल उस पद्धति को लागू करने वाले थ्रेड, थ्रेड के आधार पर सुरक्षा निर्णय लेने के विचार को छोड़ दिया गया है जब जावा 1.0 से 1.1 तक स्विच किया गया था। इस समय यह माना जाता था कि अविश्वासित कोड विश्वसनीय कोड और इसके विपरीत आह्वान कर सकता है, भले ही कोई भी कोड निष्पादित करता हो। किसी भी डेवलपर को गलती नहीं दोहरानी चाहिए।
होल्गर

18

जाहिर है कि इस तरह की योजना से सभी तरह की सुरक्षा चिंताएं पैदा होती हैं। जावा में एक कठोर सुरक्षा ढांचा है, लेकिन यह तुच्छ नहीं है। इसे खराब करने की संभावना है और एक अप्रभावी उपयोगकर्ता को महत्वपूर्ण सिस्टम घटकों की अनुमति देने की अनदेखी नहीं करनी चाहिए।

यह चेतावनी एक तरफ, यदि आप उपयोगकर्ता इनपुट को स्रोत कोड के रूप में ले रहे हैं, तो सबसे पहले आपको जो करना है, उसे जावा बाइटकोड में संकलित करें। AFIAK, यह मूल रूप से नहीं किया जा सकता है, इसलिए आपको javac पर एक सिस्टम कॉल करने की आवश्यकता होगी, और डिस्क पर स्रोत कोड को बायटेकोड पर संकलित करना होगा। यहाँ एक ट्यूटोरियल है जिसे इसके लिए एक शुरुआती बिंदु के रूप में इस्तेमाल किया जा सकता है। संपादित करें : जैसा कि मैंने टिप्पणियों में सीखा है, आप वास्तव में जावा कोड को मूल रूप से javax.tools.JavaCombiler का उपयोग करके संकलित कर सकते हैं

एक बार जब आपके पास जेवीएम बायटेकोड होता है, तो आप इसे क्लासवार्डर के डिफेंसक्लास फ़ंक्शन का उपयोग करके जेवीएम में लोड कर सकते हैं । इस लोड किए गए वर्ग के लिए सुरक्षा संदर्भ सेट करने के लिए आपको एक प्रोटेक्शनडोमेन निर्दिष्ट करना होगा । एक के लिए कम से कम निर्माता ProtectionDomain दोनों एक CodeSource और एक की आवश्यकता है PermissionCollection । PermissionCollection यहां आपके लिए प्राथमिक उपयोग की वस्तु है- आप इसका उपयोग लोड किए गए वर्ग की सटीक अनुमतियों को निर्दिष्ट करने के लिए कर सकते हैं। इन अनुमतियों को अंततः JVM के AccessController द्वारा लागू किया जाना चाहिए ।

यहां त्रुटि के बहुत सारे संभावित बिंदु हैं, और कुछ भी लागू करने से पहले आपको पूरी तरह से समझने के लिए बेहद सावधानी बरतनी चाहिए।


2
JDK 6 के javax.tools API का उपयोग करके जावा संकलन बहुत आसान है।
एलन ईद्भूजर

10

जावा-सैंडबॉक्स अनुमतियों की एक सीमित सेट के साथ जावा कोड को क्रियान्वित करने के लिए एक पुस्तकालय है। इसका उपयोग केवल सफेद-सूचीबद्ध वर्गों और संसाधनों के एक सेट तक पहुंच की अनुमति देने के लिए किया जा सकता है। यह व्यक्तिगत तरीकों तक पहुंच को प्रतिबंधित करने में सक्षम नहीं लगता है। यह इसे प्राप्त करने के लिए एक कस्टम क्लास लोडर और सुरक्षा प्रबंधक के साथ एक प्रणाली का उपयोग करता है।

मैंने इसका उपयोग नहीं किया है लेकिन यह अच्छी तरह से डिज़ाइन किया गया है और उचित रूप से अच्छी तरह से प्रलेखित है।

@wqas ने एक बहुत ही दिलचस्प जवाब दिया है जिसमें बताया गया है कि कैसे खुद को लागू करना संभव है। लेकिन विशेषज्ञों के लिए इस तरह के सुरक्षा महत्वपूर्ण और जटिल कोड को छोड़ना ज्यादा सुरक्षित है।

ध्यान दें कि 2013 के बाद से परियोजना को अपडेट नहीं किया गया है और निर्माता इसे "प्रयोगात्मक" बताते हैं। इसका होम पेज गायब हो गया है लेकिन सोर्स फोर्ज एंट्री बनी हुई है।

उदाहरण कोड परियोजना वेब साइट से अनुकूलित:

SandboxService sandboxService = SandboxServiceImpl.getInstance();

// Configure context 
SandboxContext context = new SandboxContext();
context.addClassForApplicationLoader(getClass().getName());
context.addClassPermission(AccessType.PERMIT, "java.lang.System");

// Whithout this line we get a SandboxException when touching System.out
context.addClassPermission(AccessType.PERMIT, "java.io.PrintStream");

String someValue = "Input value";

class TestEnvironment implements SandboxedEnvironment<String> {
    @Override
    public String execute() throws Exception {
        // This is untrusted code
        System.out.println(someValue);
        return "Output value";
    }
};

// Run code in sandbox. Pass arguments to generated constructor in TestEnvironment.
SandboxedCallResult<String> result = sandboxService.runSandboxed(TestEnvironment.class, 
    context, this, someValue);

System.out.println(result.get());

4

समस्या को स्वीकार किए गए उत्तर में समस्या का समाधान SecurityManagerकरने के लिए, जिसमें जेवीएम में कस्टम सभी थ्रेड्स पर लागू होगा, बजाय प्रति-थ्रेड आधार पर, आप एक कस्टम बना सकते हैं जो SecurityManagerविशिष्ट थ्रेड्स के लिए सक्षम / अक्षम किया जा सकता है:

import java.security.Permission;

public class SelectiveSecurityManager extends SecurityManager {

  private static final ToggleSecurityManagerPermission TOGGLE_PERMISSION = new ToggleSecurityManagerPermission();

  ThreadLocal<Boolean> enabledFlag = null;

  public SelectiveSecurityManager(final boolean enabledByDefault) {

    enabledFlag = new ThreadLocal<Boolean>() {

      @Override
      protected Boolean initialValue() {
        return enabledByDefault;
      }

      @Override
      public void set(Boolean value) {
        SecurityManager securityManager = System.getSecurityManager();
        if (securityManager != null) {
          securityManager.checkPermission(TOGGLE_PERMISSION);
        }
        super.set(value);
      }
    };
  }

  @Override
  public void checkPermission(Permission permission) {
    if (shouldCheck(permission)) {
      super.checkPermission(permission);
    }
  }

  @Override
  public void checkPermission(Permission permission, Object context) {
    if (shouldCheck(permission)) {
      super.checkPermission(permission, context);
    }
  }

  private boolean shouldCheck(Permission permission) {
    return isEnabled() || permission instanceof ToggleSecurityManagerPermission;
  }

  public void enable() {
    enabledFlag.set(true);
  }

  public void disable() {
    enabledFlag.set(false);
  }

  public boolean isEnabled() {
    return enabledFlag.get();
  }

}

ToggleSecurirtyManagerPermissionयह java.security.Permissionसुनिश्चित करने के लिए केवल एक सरल कार्यान्वयन है कि केवल अधिकृत कोड ही सुरक्षा प्रबंधक को सक्षम / अक्षम कर सकता है। यह इस तरह दिख रहा है:

import java.security.Permission;

public class ToggleSecurityManagerPermission extends Permission {

  private static final long serialVersionUID = 4812713037565136922L;
  private static final String NAME = "ToggleSecurityManagerPermission";

  public ToggleSecurityManagerPermission() {
    super(NAME);
  }

  @Override
  public boolean implies(Permission permission) {
    return this.equals(permission);
  }

  @Override
  public boolean equals(Object obj) {
    if (obj instanceof ToggleSecurityManagerPermission) {
      return true;
    }
    return false;
  }

  @Override
  public int hashCode() {
    return NAME.hashCode();
  }

  @Override
  public String getActions() {
    return "";
  }

}

3
अपने (स्वयं के) स्रोतों का हवाला देते हुए: Alphaloop.blogspot.com/2014/08/… और github.com/alphaloop/selective-security-manager
ज़ेसेमर

सिस्टम-स्कॉप्ड सुरक्षा प्रबंधक बनाने के लिए थ्रेडलोकल का बहुत स्मार्ट उपयोग प्रभावी रूप से थ्रेड-स्कोप किया गया (जो अधिकांश उपयोगकर्ता चाहते हैं)। इसके अलावा इनहेरिटेबल थ्रेडलोकल का उपयोग करने पर विचार करें कि असंबद्ध संपत्ति को अविभाजित कोड द्वारा थ्रेडेड थ्रेड्स में स्थानांतरित करें।
निक

4

वैसे किसी भी सुझाव या समाधान को देने के लिए बहुत देर हो चुकी है, लेकिन फिर भी मैं इसी तरह के मुद्दे का सामना कर रहा था, इस तरह के और अधिक शोध उन्मुख। मूल रूप से मैं ई-लर्निंग प्लेटफार्मों में जावा कोर्स के लिए प्रोग्रामिंग असाइनमेंट के लिए एक प्रावधान और स्वचालित मूल्यांकन प्रदान करने की कोशिश कर रहा था।

  1. एक तरीका हो सकता है, प्रत्येक छात्र के लिए न्यूनतम कॉन्फ़िगरेशन संभव ओएस के साथ एक अलग वर्चुअल मशीन (जेवीएम नहीं) लेकिन वास्तविक वर्चुअल मशीन बनाएं।
  2. जावा या पुस्तकालयों के लिए अपनी प्रोग्रामिंग भाषाओं के अनुसार, जो भी आप चाहते हैं कि छात्र इन मशीनों को संकलित और निष्पादित करें।

मुझे पता है कि यह काफी जटिल और बहुत सारे काम करता है, लेकिन Oracle वर्चुअल बॉक्स पहले से ही गतिशील रूप से वर्चुअल मशीन बनाने या क्लोन करने के लिए जावा एपीआई प्रदान करता है। https://www.virtualbox.org/sdkref/index.html (नोट, यहां तक ​​कि VMware भी ऐसा करने के लिए एपीआई प्रदान करता है)

और न्यूनतम आकार और विन्यास लिनक्स वितरण के लिए आप इसे यहां देख सकते हैं http://www.slitaz.org/en/ ,

इसलिए अब अगर छात्र इसे गड़बड़ करते हैं या करने की कोशिश करते हैं, तो यह मेमोरी या फाइल सिस्टम या नेटवर्किंग, सॉकेट के साथ हो सकता है, अधिकतम वह अपने स्वयं के वीएम को नुकसान पहुंचा सकता है।

आंतरिक रूप से इन वीएम में आप जावा के लिए सैंडबॉक्स (सुरक्षा प्रबंधक) जैसी अतिरिक्त सुरक्षा प्रदान कर सकते हैं या लिनक्स पर उपयोगकर्ता विशिष्ट खाते बना सकते हैं और इस प्रकार पहुंच को प्रतिबंधित कर सकते हैं।

उम्मीद है की यह मदद करेगा !!


3

यहाँ समस्या के लिए एक धागा-सुरक्षित समाधान है:

https://svn.code.sf.net/p/loggifier/code/trunk/de.unkrig.commons.lang/src/de/unkrig/commons/lang/security/Sandbox.java

package de.unkrig.commons.lang.security;

import java.security.AccessControlContext;
import java.security.Permission;
import java.security.Permissions;
import java.security.ProtectionDomain;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;

import de.unkrig.commons.nullanalysis.Nullable;

/**
 * This class establishes a security manager that confines the permissions for code executed through specific classes,
 * which may be specified by class, class name and/or class loader.
 * <p>
 * To 'execute through a class' means that the execution stack includes the class. E.g., if a method of class {@code A}
 * invokes a method of class {@code B}, which then invokes a method of class {@code C}, and all three classes were
 * previously {@link #confine(Class, Permissions) confined}, then for all actions that are executed by class {@code C}
 * the <i>intersection</i> of the three {@link Permissions} apply.
 * <p>
 * Once the permissions for a class, class name or class loader are confined, they cannot be changed; this prevents any
 * attempts (e.g. of the confined class itself) to release the confinement.
 * <p>
 * Code example:
 * <pre>
 *  Runnable unprivileged = new Runnable() {
 *      public void run() {
 *          System.getProperty("user.dir");
 *      }
 *  };
 *
 *  // Run without confinement.
 *  unprivileged.run(); // Works fine.
 *
 *  // Set the most strict permissions.
 *  Sandbox.confine(unprivileged.getClass(), new Permissions());
 *  unprivileged.run(); // Throws a SecurityException.
 *
 *  // Attempt to change the permissions.
 *  {
 *      Permissions permissions = new Permissions();
 *      permissions.add(new AllPermission());
 *      Sandbox.confine(unprivileged.getClass(), permissions); // Throws a SecurityException.
 *  }
 *  unprivileged.run();
 * </pre>
 */
public final
class Sandbox {

    private Sandbox() {}

    private static final Map<Class<?>, AccessControlContext>
    CHECKED_CLASSES = Collections.synchronizedMap(new WeakHashMap<Class<?>, AccessControlContext>());

    private static final Map<String, AccessControlContext>
    CHECKED_CLASS_NAMES = Collections.synchronizedMap(new HashMap<String, AccessControlContext>());

    private static final Map<ClassLoader, AccessControlContext>
    CHECKED_CLASS_LOADERS = Collections.synchronizedMap(new WeakHashMap<ClassLoader, AccessControlContext>());

    static {

        // Install our custom security manager.
        if (System.getSecurityManager() != null) {
            throw new ExceptionInInitializerError("There's already a security manager set");
        }
        System.setSecurityManager(new SecurityManager() {

            @Override public void
            checkPermission(@Nullable Permission perm) {
                assert perm != null;

                for (Class<?> clasS : this.getClassContext()) {

                    // Check if an ACC was set for the class.
                    {
                        AccessControlContext acc = Sandbox.CHECKED_CLASSES.get(clasS);
                        if (acc != null) acc.checkPermission(perm);
                    }

                    // Check if an ACC was set for the class name.
                    {
                        AccessControlContext acc = Sandbox.CHECKED_CLASS_NAMES.get(clasS.getName());
                        if (acc != null) acc.checkPermission(perm);
                    }

                    // Check if an ACC was set for the class loader.
                    {
                        AccessControlContext acc = Sandbox.CHECKED_CLASS_LOADERS.get(clasS.getClassLoader());
                        if (acc != null) acc.checkPermission(perm);
                    }
                }
            }
        });
    }

    // --------------------------

    /**
     * All future actions that are executed through the given {@code clasS} will be checked against the given {@code
     * accessControlContext}.
     *
     * @throws SecurityException Permissions are already confined for the {@code clasS}
     */
    public static void
    confine(Class<?> clasS, AccessControlContext accessControlContext) {

        if (Sandbox.CHECKED_CLASSES.containsKey(clasS)) {
            throw new SecurityException("Attempt to change the access control context for '" + clasS + "'");
        }

        Sandbox.CHECKED_CLASSES.put(clasS, accessControlContext);
    }

    /**
     * All future actions that are executed through the given {@code clasS} will be checked against the given {@code
     * protectionDomain}.
     *
     * @throws SecurityException Permissions are already confined for the {@code clasS}
     */
    public static void
    confine(Class<?> clasS, ProtectionDomain protectionDomain) {
        Sandbox.confine(
            clasS,
            new AccessControlContext(new ProtectionDomain[] { protectionDomain })
        );
    }

    /**
     * All future actions that are executed through the given {@code clasS} will be checked against the given {@code
     * permissions}.
     *
     * @throws SecurityException Permissions are already confined for the {@code clasS}
     */
    public static void
    confine(Class<?> clasS, Permissions permissions) {
        Sandbox.confine(clasS, new ProtectionDomain(null, permissions));
    }

    // Code for 'CHECKED_CLASS_NAMES' and 'CHECKED_CLASS_LOADERS' omitted here.

}

कृपया टिप्पणी करें!

सीयू

आर्नो


हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.