Come limitare setAccessible solo a usi “legittimi”?


100

Più ho imparato sul potere di java.lang.reflect.AccessibleObject.setAccessible, più sono stupito da ciò che può fare. Questo è adattato dalla mia risposta alla domanda ( Utilizzo della riflessione per modificare File.separatorChar finale statico per i test unitari ).

import java.lang.reflect.*;

public class EverythingIsTrue {
   static void setFinalStatic(Field field, Object newValue) throws Exception {
      field.setAccessible(true);

      Field modifiersField = Field.class.getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

      field.set(null, newValue);
   }
   public static void main(String args[]) throws Exception {      
      setFinalStatic(Boolean.class.getField("FALSE"), true);

      System.out.format("Everything is %s", false); // "Everything is true"
   }
}

Puoi fare cose davvero oltraggiose:

public class UltimateAnswerToEverything {
   static Integer[] ultimateAnswer() {
      Integer[] ret = new Integer[256];
      java.util.Arrays.fill(ret, 42);
      return ret;
   }   
   public static void main(String args[]) throws Exception {
      EverythingIsTrue.setFinalStatic(
         Class.forName("java.lang.Integer$IntegerCache")
            .getDeclaredField("cache"),
         ultimateAnswer()
      );
      System.out.format("6 * 9 = %d", 6 * 9); // "6 * 9 = 42"
   }
}

Presumibilmente i progettisti API si rendono conto di quanto setAccessiblepossa essere abusabile , ma devono aver ammesso che ha usi legittimi per fornirlo. Quindi le mie domande sono:

  • A cosa servono gli usi veramente legittimi setAccessible?
    • Java è stato progettato in modo da NON avere questa esigenza in primo luogo?
    • Quali sarebbero le conseguenze negative (se ce ne sarebbero) di tale progetto?
  • Puoi limitare setAccessiblesolo gli usi legittimi?
    • È solo attraverso SecurityManager?
      • Come funziona? Whitelist / blacklist, granularità, ecc.?
      • È comune doverlo configurare nelle proprie applicazioni?
    • Posso scrivere le mie classi in modo che siano a setAccessibleprova di errore indipendentemente dalla SecurityManagerconfigurazione?
      • Oppure sono in balia di chi gestisce la configurazione?

Immagino che un'altra domanda importante sia: DEVO PREOCCUPARMI PER QUESTO ???

Nessuna delle mie classi ha alcuna parvenza di privacy applicabile come mai. Il modello singleton (mettendo da parte i dubbi sui suoi meriti) è ora impossibile da applicare. Come mostrano i miei frammenti di cui sopra, anche alcuni presupposti di base su come funzionano i fondamentali di Java non sono nemmeno vicini all'essere garantiti.

QUESTI PROBLEMI NON SONO REALI ???


Ok, ho appena confermato: grazie a setAccessible, le stringhe Java NON sono immutabili.

import java.lang.reflect.*;

public class MutableStrings {
   static void mutate(String s) throws Exception {
      Field value = String.class.getDeclaredField("value");
      value.setAccessible(true);
      value.set(s, s.toUpperCase().toCharArray());
   }   
   public static void main(String args[]) throws Exception {
      final String s = "Hello world!";
      System.out.println(s); // "Hello world!"
      mutate(s);
      System.out.println(s); // "HELLO WORLD!"
   }
}

Sono l'unico che pensa che questa sia una preoccupazione ENORME?


30
Dovresti vedere cosa possono fare in C ++! (Oh, e probabilmente C #.)
Tom Hawtin - tackline

Risposte:


105

DEVO PREOCCUPARMI PER QUESTO ???

Dipende interamente dai tipi di programmi che stai scrivendo e dal tipo di architettura.

Se stai distribuendo un componente software chiamato foo.jar alle persone del mondo, sei comunque completamente alla loro mercé. Potrebbero modificare le definizioni di classe all'interno del tuo .jar (tramite reverse engineering o manipolazione diretta del bytecode). Potrebbero eseguire il tuo codice nella loro JVM, ecc. In questo caso preoccuparti non ti farà bene.

Se stai scrivendo un'applicazione web che si interfaccia solo con persone e sistemi tramite HTTP e controlli il server delle applicazioni, non è nemmeno un problema. Sicuramente gli altri programmatori della tua azienda potrebbero creare codice che interrompe il tuo schema singleton, ma solo se lo vogliono davvero.

Se il tuo lavoro futuro è scrivere codice presso Sun Microsystems / Oracle e hai il compito di scrivere codice per il core Java o altri componenti affidabili, è qualcosa di cui dovresti essere a conoscenza. Preoccuparti, tuttavia, ti farà solo perdere i capelli. In ogni caso, probabilmente ti faranno leggere le Linee guida per la codifica sicura insieme alla documentazione interna.

Se stai scrivendo applet Java, il framework di sicurezza è qualcosa di cui dovresti essere a conoscenza. Scoprirai che le applet non firmate che tentano di chiamare setAccessible risulteranno solo in una SecurityException.

setAccessible non è l'unica cosa che aggira i controlli di integrità convenzionali. Esiste una classe Java non API, denominata sun.misc.Unsafe che può fare praticamente qualsiasi cosa, incluso l'accesso diretto alla memoria. Anche il codice nativo (JNI) può aggirare questo tipo di controllo.

In un ambiente sandbox (ad esempio Java Applet, JavaFX), ogni classe dispone di una serie di autorizzazioni e l'accesso alle implementazioni native Unsafe, setAccessible e di definizione è controllato da SecurityManager.

"I modificatori di accesso Java non intendono essere un meccanismo di sicurezza."

Dipende molto da dove viene eseguito il codice Java. Le classi Java principali utilizzano i modificatori di accesso come meccanismo di sicurezza per applicare la sandbox.

Quali sono gli usi veramente legittimi di setAccessible?

Le classi core Java lo usano come un modo semplice per accedere a cose che devono rimanere private per motivi di sicurezza. Ad esempio, il framework di serializzazione Java lo utilizza per richiamare i costruttori di oggetti privati ​​durante la deserializzazione degli oggetti. Qualcuno ha menzionato System.setErr, e sarebbe un buon esempio, ma curiosamente i metodi della classe System setOut / setErr / setIn usano tutti codice nativo per impostare il valore del campo finale.

Un altro ovvio uso legittimo sono i framework (persistenza, framework web, injection) che devono sbirciare all'interno degli oggetti.

I debugger, a mio parere, non rientrano in questa categoria, poiché normalmente non vengono eseguiti nello stesso processo JVM, ma invece l'interfaccia con la JVM utilizzando altri mezzi (JPDA).

Java è stato progettato in modo da NON avere questa esigenza in primo luogo?

Questa è una domanda piuttosto profonda a cui rispondere bene. Immagino di sì, ma dovresti aggiungere altri meccanismi che potrebbero non essere del tutto preferibili.

Puoi limitare setAccessible solo agli usi legittimi?

La restrizione OOTB più semplice che puoi applicare è avere un SecurityManager e consentire setAccessible solo al codice proveniente da determinate fonti. Questo è ciò che fa già Java: le classi Java standard che provengono da JAVA_HOME possono eseguire setAccessible, mentre le classi di applet non firmate da foo.com non possono eseguire setAccessible. Come è stato detto prima, questo permesso è binario, nel senso che uno lo ha o no. Non esiste un modo ovvio per consentire a setAccessible di modificare determinati campi / metodi mentre non ne consente altri. Utilizzando il SecurityManager è possibile, tuttavia, impedire alle classi di fare riferimento completamente a determinati pacchetti, con o senza riflessione.

Posso scrivere le mie classi in modo che siano a prova di accessibilità indipendentemente dalla configurazione di SecurityManager? ... Oppure sono in balia di chi gestisce la configurazione?

Non puoi e sicuramente lo sei.


"Se stai distribuendo un componente software chiamato foo.jar alle persone del mondo, sei comunque completamente alla loro mercé. Potrebbero modificare le definizioni di classe all'interno del tuo .jar (attraverso il reverse engineering o la manipolazione diretta del bytecode). potrebbe eseguire il codice nella propria JVM, ecc. In questo caso preoccuparsi non serve a nulla. " È ancora così? Qualche consiglio su come rendere più difficile la manomissione del software (si spera in modo significativo)? È difficile lanciare le applicazioni desktop Java fuori dalla finestra a causa di ciò.
Sero

@naaz dovrei dire che non è cambiato nulla. È solo che l'architettura sta lavorando contro di te. Posso modificare qualsiasi software in esecuzione sulla mia macchina su cui ho il pieno controllo. Il deterrente più forte potrebbe essere legale, ma non immagino che funzionerà per tutti gli scenari. Penso che i binari Java siano tra i più facili da invertire, ma le persone con esperienza manometteranno qualsiasi cosa. L'offuscamento aiuta rendendo difficile apportare grandi modifiche, ma qualsiasi cosa booleana (come un controllo del copyright) è ancora banale da bypassare. Potresti invece provare a mettere le parti chiave su un server.
Sami Koivu

Che ne dici di denuvo?
Sero

15
  • A cosa servono gli usi veramente legittimi setAccessible?

Test unitari, interni della JVM (es. Implementazione System.setError(...)) e così via.

  • Java è stato progettato in modo da NON avere questa esigenza in primo luogo?
  • Quali sarebbero le conseguenze negative (se ce ne sarebbero) di tale progetto?

Molte cose sarebbero inattuabili. Ad esempio, varie iniezioni di persistenza, serializzazione e dipendenza Java dipendono dalla riflessione. E praticamente tutto ciò che si basa sulle convenzioni JavaBeans in fase di esecuzione.

  • Puoi limitare setAccessiblesolo gli usi legittimi?
  • È solo attraverso SecurityManager?

Sì.

  • Come funziona? Whitelist / blacklist, granularità, ecc.?

Dipende dal permesso, ma credo che il permesso da usare setAccessiblesia binario. Se si desidera la granularità, è necessario utilizzare un programma di caricamento classi diverso con un gestore della sicurezza diverso per le classi che si desidera limitare. Immagino che potresti implementare un gestore della sicurezza personalizzato che implementa una logica più fine.

  • È comune doverlo configurare nelle proprie applicazioni?

No.

  • Posso scrivere le mie classi in modo che siano a setAccessibleprova di errore indipendentemente dalla SecurityManagerconfigurazione?
    • Oppure sono in balia di chi gestisce la configurazione?

No non puoi, e sì lo sei.

L'altra alternativa è "imporlo" tramite strumenti di analisi del codice sorgente; ad esempio personalizzato pmdo findbugsregole. O revisione selettiva del codice del codice identificato da (diciamo) grep setAccessible ....

In risposta al follow-up

Nessuna delle mie classi ha alcuna parvenza di privacy applicabile come mai. Il modello singleton (mettendo da parte i dubbi sui suoi meriti) è ora impossibile da applicare.

Se questo ti preoccupa, suppongo che tu debba preoccuparti. Ma in realtà non dovresti cercare di costringere altri programmatori a rispettare le tue decisioni di progettazione. Se le persone sono così stupide da usare la riflessione per creare gratuitamente più istanze dei tuoi single (per esempio), possono convivere con le conseguenze.

D'altra parte, se intendi "privacy" per comprendere il significato di proteggere le informazioni sensibili dalla divulgazione, stai abbaiando all'albero sbagliato. Il modo per proteggere i dati sensibili in un'applicazione Java è di non consentire a codice non attendibile di entrare nella sandbox di sicurezza che tratta i dati sensibili. I modificatori di accesso Java non sono intesi come meccanismi di sicurezza.

<Esempio di stringa> - Sono l'unico a pensare che questa sia una preoccupazione ENORME?

Probabilmente non l' unico :-). Ma IMO, questa non è una preoccupazione. È accettato il fatto che il codice non attendibile debba essere eseguito in una sandbox. Se hai un codice fidato / un programmatore fidato che fa cose come questa, i tuoi problemi sono peggiori delle stringhe mutabili inaspettatamente. (Pensa a bombe logiche, esfiltrazione di dati tramite canali nascosti , ecc.)

Ci sono modi per affrontare (o mitigare) il problema di un "cattivo attore" nel tuo team di sviluppo o operativo. Ma sono costosi e restrittivi ... e eccessivi per la maggior parte dei casi d'uso.


1
Utile anche per cose come le implementazioni di persistenza. Le riflessioni non sono realmente utili per i debugger (anche se originariamente era previsto). Non puoi modificare utilmente (sottoclasse) il SecurityManagerper questo, perché tutto ciò che ottieni è il controllo dei permessi - tutto ciò che puoi fare è guardare l'acc e forse controllare lo stack nel thread corrente). grep java.lang.reflect.
Tom Hawtin - tackline

@Stephen C: già +1, ma apprezzerei anche il tuo contributo su un'altra domanda che ho appena aggiunto. Grazie in anticipo.
lubrificanti poligenici il

Nota che grep è un'idea terribile. Esistono così tanti modi per eseguire in modo dinamico il codice in Java che quasi sicuramente ne perderai uno. Il codice nella blacklist in generale è una ricetta per il fallimento.
Antimonio

"I modificatori di accesso Java non intendono essere un meccanismo di sicurezza." - in realtà, sì, lo sono. Java è progettato per consentire la coesistenza di codice affidabile e non attendibile nella stessa macchina virtuale: questo faceva parte dei suoi requisiti fondamentali sin dall'inizio. Ma solo quando il sistema è in esecuzione con un SecurityManager bloccato. La capacità di eseguire il sandbox del codice dipende interamente dall'applicazione degli specificatori di accesso.
Jules

@ Jules - La maggior parte degli esperti Java non sarebbe d'accordo con questo; es. stackoverflow.com/questions/9201603/…
Stephen C

11

La riflessione è in effetti ortogonale alla sicurezza in questa prospettiva.

Come possiamo limitare la riflessione?

Java ha il gestore della sicurezza e ClassLoaderle basi per il suo modello di sicurezza. Nel tuo caso, immagino che tu debba guardare java.lang.reflect.ReflectPermission.

Ma questo non risolve completamente il problema della riflessione. Le capacità riflessive disponibili dovrebbero essere soggette a un sistema di autorizzazione a grana fine, cosa che ora non avviene. Ad esempio, per consentire a determinati framework di utilizzare la riflessione (ad esempio Hibernate), ma non il resto del codice. O per consentire a un programma di riflettere solo in un modo di sola lettura, a scopo di debug.

Un approccio che potrebbe diventare mainstream in futuro è l'uso dei cosiddetti specchi per separare le capacità riflessive dalle classi. Vedere Specchi: principi di progettazione per strutture a livello meta . Esistono tuttavia varie altre ricerche che affrontano questo problema. Ma sono d'accordo che il problema è più grave per il linguaggio dinamico rispetto ai linguaggi statici.

Dovremmo essere preoccupati del superpotere che questa riflessione ci dà? Sì e no.

Sì, nel senso che la piattaforma Java dovrebbe essere protetta con Classloadere gestore della sicurezza. La capacità di pasticciare con la riflessione può essere vista come una violazione.

No, nel senso che la maggior parte dei sistemi non sono comunque del tutto sicuri. Molte classi possono spesso essere sottoclasse e potresti già abusare del sistema proprio con questo. Ovviamente le classi possono essere create finalo sigillate in modo che non possano essere sottoclassate in altri jar. Ma solo poche classi sono protette correttamente (ad esempio String) in base a questo.

Vedi questa risposta sulla lezione finale per una bella spiegazione. Vedi anche il blog di Sami Koivu per ulteriori informazioni sull'hacking di Java sulla sicurezza.

Il modello di sicurezza di Java può essere considerato in qualche modo insufficiente. Alcuni linguaggi come NewSpeak adottano un approccio ancora più radicale alla modularità, in cui hai accesso solo a ciò che ti viene esplicitamente fornito dall'inversione delle dipendenze (per impostazione predefinita nulla).

È anche importante notare che la sicurezza è comunque relativa . A livello di linguaggio, ad esempio, non è possibile impedire a un modulo di consumare il 100% della CPU o di consumare tutta la memoria fino a un file OutOfMemoryException. Tali preoccupazioni devono essere affrontate con altri mezzi. Forse vedremo in futuro Java esteso con quote di utilizzo delle risorse, ma non è per domani :)

Potrei espandere di più sull'argomento, ma penso di aver fatto il mio punto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.