Java uygulamasındaki kötü amaçlı koda karşı korumalı alan


92

Kullanıcıların sunucu tarafından çalıştırılmak üzere kendi kodlarını göndermelerine izin verilen bir simülasyon sunucusu ortamında, Applet'lerin bir tarayıcı içinde olmasından farklı olarak, kullanıcı tarafından gönderilen herhangi bir kodun bir sanal alanda çalıştırılması açıkça avantajlı olacaktır. Gönderilen bu bileşenleri izole etmek için başka bir VM katmanı eklemek yerine JVM'nin kendisinden yararlanabilmek istedim.

Bu tür bir sınırlama, mevcut Java sanal alan modeli kullanılarak mümkün görünmektedir, ancak bunu çalışan bir uygulamanın yalnızca kullanıcı tarafından gönderilen bölümleri için etkinleştirmenin dinamik bir yolu var mı?

Yanıtlar:


111
  1. Güvenilmeyen kodu kendi iş parçacığında çalıştırın. Bu, örneğin sonsuz döngü ve benzeri sorunları önler ve gelecekteki adımları daha kolay hale getirir. Ana iş parçacığının iş parçacığının bitmesini beklemesini sağlayın ve çok uzun sürerse Thread.stop ile öldürün. Thread.stop kullanımdan kaldırıldı, ancak güvenilmeyen kodun herhangi bir kaynağa erişimi olmaması gerektiğinden, onu öldürmek güvenli olacaktır.

  2. Bu Konu üzerinde bir SecurityManager ayarlayın . Seçilmiş birkaçı dışındaki tüm izinler için bir SecurityException oluşturmak için checkPermission'ı (İzin izni) geçersiz kılan bir SecurityManager alt sınıfı oluşturun . Burada gereksinim duydukları yöntemlerin ve izinlerin bir listesi vardır: Java TM 6 SDK'daki izinler .

  3. Güvenilmeyen kodu yüklemek için özel bir ClassLoader kullanın. Sınıf yükleyiciniz, güvenilmeyen kodun kullandığı tüm sınıflar için çağrılır, böylece bireysel JDK sınıflarına erişimi devre dışı bırakmak gibi şeyler yapabilirsiniz. Yapılacak şey, izin verilen JDK sınıflarının bir beyaz listesine sahip olmaktır.

  4. Güvenilmeyen kodu ayrı bir JVM'de çalıştırmak isteyebilirsiniz. Önceki adımlar kodu güvenli hale getirirken, izole edilmiş kodun hala yapabileceği can sıkıcı bir şey var: yapabildiği kadar çok bellek ayırın, bu da ana uygulamanın görünür ayak izinin büyümesine neden olur.

JSR 121: Uygulama İzolasyon API Spesifikasyonu bunu çözmek için tasarlandı, ancak maalesef henüz bir uygulaması yok.

Bu oldukça ayrıntılı bir konu ve çoğunlukla bunları aklıma takmadan yazıyorum.

Ama her neyse, bazı kusurlu, riski göze alarak kullan, muhtemelen hatalı (sözde) kod:

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
  }
}

SecurityManager

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
}

Konu

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
Bu kodun biraz çalışılması gerekebilir. JVM kullanılabilirliğine karşı gerçekten koruma sağlayamazsınız. Süreci sonlandırmaya hazır olun (muhtemelen otomatik olarak). Kod diğer iş parçacıklarına - örneğin, sonlandırıcı iş parçacığı. Thread.stopJava kitaplık kodunda sorunlara neden olur. Benzer şekilde, Java kitaplık kodu da izinler gerektirecektir. Çok daha iyi sağlamak için SecurityManagerkullanmak java.security.AccessController. Sınıf yükleyici muhtemelen kullanıcı kodunun kendi sınıflarına da erişime izin vermelidir.
Tom Hawtin - tackline

4
Bunun çok karmaşık bir konu olduğu düşünüldüğünde, Java "eklentilerini" güvenli bir şekilde ele almak için mevcut çözümler yok mu?
Nick Spacek

10
Bu yaklaşımın sorunu, SecurityManager'ı System olarak ayarladığınızda, sadece çalışan iş parçacığını etkilemekle kalmaz, aynı zamanda diğer iş parçacığını da etkiler!
Gelin Luo

2
Maalesef thread.stop () atılabilir ile yakalanabilir. (Thread.isAlive) Thread.stop () sırasında yapabilirsiniz, ancak sonra istisnayı yakalayan bir işlevi özyinelemeli olarak çağırabilirim. Bilgisayarımda test edilen özyinelemeli işlev stop () üzerinden kazanır. Artık işlemci ve kaynakları çalan bir çöp iş parçacığınız var
Lesto

9
System.setSecurityManager(…)Tüm JVM'yi etkileyeceği gerçeğinin yanı sıra, yalnızca bu yöntemi çağıran iş parçacığı değil, iş parçacığına dayalı güvenlik kararları verme fikri, Java 1.0'dan 1.1'e geçtiğinde terk edilmiştir. Bu sırada, güvenilir olmayan kodun, kodu çalıştıran iş parçacığı ne olursa olsun, güvenilir kodu çağırabileceği ve bunun tersi de fark edildi. Hiçbir geliştirici bu hatayı tekrar etmemelidir.
Holger

18

Açıkçası böyle bir plan her türlü güvenlik endişesini gündeme getiriyor. Java'nın sıkı bir güvenlik çerçevesi vardır, ancak önemsiz değildir. Bunu bozma ve ayrıcalıksız bir kullanıcının hayati sistem bileşenlerine erişmesine izin verme olasılığı göz ardı edilmemelidir.

Bu uyarı bir yana, kaynak kodu biçiminde kullanıcı girişi alıyorsanız, yapmanız gereken ilk şey onu Java bayt koduna derlemektir. AFIAK, bu yerel olarak yapılamaz, bu nedenle javac'a bir sistem çağrısı yapmanız ve kaynak kodunu diskteki bayt koduna derlemeniz gerekir. İşte bunun için bir başlangıç ​​noktası olarak kullanılabilecek bir eğitim. Düzenleme : Yorumlarda öğrendiğim gibi, Java kodunu javax.tools.JavaCompiler kullanarak doğal olarak kaynaktan derleyebilirsiniz.

JVM bayt koduna sahip olduğunuzda, onu ClassLoader'ın defineClass işlevini kullanarak JVM'ye yükleyebilirsiniz . Bu yüklenen sınıf için bir güvenlik bağlamı ayarlamak için bir Koruma Alanı belirtmeniz gerekir . Bir için asgari kurucu ProtectionDomain bir CodeSource ve hem gerektiren PermissionCollection . PermissionCollection, sizin için birincil kullanım nesnesidir - burada, yüklenen sınıfın sahip olduğu tam izinleri belirtmek için kullanabilirsiniz. Bu izinler nihai olarak JVM'nin AccessController tarafından uygulanmalıdır .

Burada birçok olası hata noktası vardır ve herhangi bir şey uygulamadan önce her şeyi tamamen anlamak için son derece dikkatli olmalısınız.


2
Java derlemesi JDK 6'nın javax.tools API'sini kullanarak oldukça kolaydır.
Alan Krueger

10

Java Sandbox izinleri sınırlı bir set ile Java kodunu çalıştırmak için bir kütüphanedir. Yalnızca beyaz listedeki sınıflara ve kaynaklara erişime izin vermek için kullanılabilir. Bireysel yöntemlere erişimi kısıtlayacak gibi görünmüyor. Bunu başarmak için özel bir sınıf yükleyiciye ve güvenlik yöneticisine sahip bir sistem kullanır.

Kullanmadım ama iyi tasarlanmış ve oldukça iyi belgelenmiş görünüyor.

@waqas, bunun nasıl mümkün olduğunu açıklayan çok ilginç bir cevap verdi. Ancak bu tür güvenlik açısından kritik ve karmaşık kodu uzmanlara bırakmak çok daha güvenlidir.

Projenin 2013'ten beri güncellenmediğine ve yaratıcıların onu "deneysel" olarak tanımladığına dikkat edin. Ana sayfası kayboldu ancak Source Forge girişi kaldı.

Proje web sitesinden uyarlanan örnek kod:

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Özelin iş parçacığı başına değil, JVM'deki tüm iş parçacıklarına uygulanacağı kabul edilen yanıttaki sorunu çözmek SecurityManageriçin, aşağıdaki gibi belirli iş parçacıkları için etkinleştirilebilen / devre dışı bırakılabilen bir özel oluşturabilirsiniz :

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();
  }

}

ToggleSecurirtyManagerPermissionjava.security.Permissionsadece yetkili kodun güvenlik yöneticisini etkinleştirmesini / devre dışı bırakmasını sağlamak için basit bir uygulamadır . Şöyle görünüyor:

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 "";
  }

}


Sistem kapsamlı SecurityManager'ları etkili bir şekilde iş parçacığı kapsamına almak için ThreadLocal'ın çok akıllıca kullanılması (çoğu kullanıcının isteyeceği). Ayrıca, izin verilmeyen özelliği güvenilmeyen kod tarafından oluşturulan iş parçacıklarına otomatik olarak iletmek için InheritableThreadLocal kullanmayı da düşünün.
Nick

4

Pekala, herhangi bir öneri veya çözüm sunmak için çok geç, ama yine de benzer türden bir sorunla karşı karşıyaydım, biraz daha araştırma odaklı. Temel olarak e-öğrenme platformlarında Java dersi için programlama ödevleri için bir hazırlık ve otomatik değerlendirmeler sağlamaya çalışıyordum.

  1. Bunun bir yolu, ayrı bir sanal makineler (JVM değil), ancak her öğrenci için minimum yapılandırma olası işletim sistemine sahip gerçek sanal makineler oluşturmak olabilir.
  2. Java için JRE'yi veya kitaplıkları, öğrencilerin bu makinelerde derlemesini ve yürütmesini istediğiniz program dillerine göre yükleyin.

Bunun oldukça karmaşık ve çok sayıda görev olduğunu biliyorum, ancak Oracle Virtual Box, sanal makineleri dinamik olarak oluşturmak veya klonlamak için Java API sağlıyor. https://www.virtualbox.org/sdkref/index.html (Unutmayın, VMware bile aynısını yapmak için API sağlar)

Ve asgari büyüklük ve konfigürasyon Linux dağıtımı için burada bu bir başvurabilir http://www.slitaz.org/en/ ,

Öyleyse şimdi eğer öğrenciler karıştırırsa veya yapmaya çalışırsa, bellek veya dosya sistemi veya ağ iletişimi, soket, maksimum kendi VM'sine zarar verebilir.

Ayrıca bu sanal makinelere dahili olarak Java için Sandbox (güvenlik yöneticisi) gibi ek güvenlik sağlayabilir veya Linux'ta kullanıcıya özel hesaplar oluşturabilir ve böylece erişimi kısıtlayabilirsiniz.

Bu yardımcı olur umarım !!


3

İşte sorun için iş parçacığı açısından güvenli bir çözüm:

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.

}

Lütfen yorumlayın!

CU

Arno


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.