Jak ograniczyć setAccessible tylko do „legalnych” zastosowań?

100

Im więcej poznawałem moc java.lang.reflect.AccessibleObject.setAccessible, tym bardziej jestem zdumiony tym, co on potrafi. Jest to zaadaptowane z mojej odpowiedzi na pytanie ( Używanie odbicia do zmiany statycznego pliku końcowego File.separatorChar do testowania jednostkowego ).

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

Możesz robić naprawdę oburzające rzeczy:

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

Przypuszczalnie projektanci API zdają sobie sprawę z tego, jak nadużycie setAccessiblemoże być, ale musieli przyznać, że ma to uzasadnione zastosowania, aby je zapewnić. Więc moje pytania to:

  • Do czego służą prawdziwie uzasadnione zastosowania setAccessible?
    • Czy Java została zaprojektowana tak, aby w ogóle NIE miała takiej potrzeby?
    • Jakie byłyby negatywne konsekwencje (jeśli w ogóle) takiego projektu?
  • Czy możesz ograniczyć setAccessiblesię tylko do legalnych zastosowań?
    • Czy to tylko przez SecurityManager?
      • Jak to działa? Biała / czarna lista, szczegółowość itp.?
      • Czy często trzeba go konfigurować w swoich aplikacjach?
    • Czy mogę napisać moje klasy tak, aby były setAccessibleodporne na działanie niezależnie od SecurityManagerkonfiguracji?
      • Czy jestem na łasce tego, kto zarządza konfiguracją?

Wydaje mi się, że jeszcze jedno ważne pytanie brzmi: CZY MUSZĘ SIĘ O TYM MARTWIEĆ ???

Żadne z moich zajęć nie przypomina egzekwowalnej prywatności. Wzór singletona (odkładając na bok wątpliwości co do jego zalet) jest teraz niemożliwy do wyegzekwowania. Jak pokazują powyższe fragmenty, nawet niektóre podstawowe założenia dotyczące działania języka Java nie są nawet bliskie zagwarantowania.

CZY TE PROBLEMY NIE SĄ PRAWDZIWE ???


OK, właśnie potwierdziłem: dzięki setAccessible, napisy Java NIE są niezmienne.

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

Czy tylko ja uważam, że to OGROMNY problem?

smary wielogenowe
źródło
30
Powinieneś zobaczyć, co potrafią w C ++! (Och, i prawdopodobnie C #.)
Tom Hawtin - tackline

Odpowiedzi:

105

CZY MUSZĘ SIĘ MARTWIĆ ???

Zależy to całkowicie od typów programów, które piszesz i dla jakiej architektury.

Jeśli rozpowszechniasz komponent oprogramowania o nazwie foo.jar wśród ludzi na całym świecie, i tak jesteś całkowicie na ich łasce. Mogą modyfikować definicje klas w twoim .jar (poprzez inżynierię wsteczną lub bezpośrednią manipulację kodem bajtowym). Mogą uruchomić Twój kod we własnej JVM, itp. W takim przypadku martwienie się nie pomoże.

Jeśli piszesz aplikację internetową, która łączy się tylko z ludźmi i systemami za pośrednictwem protokołu HTTP i kontrolujesz serwer aplikacji, również to nie jest problemem. Z pewnością inni programiści w Twojej firmie mogą tworzyć kod, który łamie Twój wzorzec singletona, ale tylko wtedy, gdy naprawdę tego chcą.

Jeśli Twoim przyszłym zadaniem jest pisanie kodu w Sun Microsystems / Oracle, a Twoim zadaniem jest pisanie kodu dla rdzenia Java lub innych zaufanych komponentów, powinieneś o tym wiedzieć. Martwienie się jednak sprawi, że stracisz włosy. W każdym razie prawdopodobnie sprawią, że przeczytasz Wytyczne dotyczące bezpiecznego kodowania wraz z wewnętrzną dokumentacją.

Jeśli zamierzasz pisać aplety Java, struktura bezpieczeństwa jest czymś, o czym powinieneś wiedzieć. Przekonasz się, że niepodpisane aplety próbujące wywołać setAccessible spowodują tylko wyjątek SecurityException.

setAccessible to nie jedyna rzecz, która omija konwencjonalne kontrole integralności. Istnieje bez API, podstawowa klasa Java o nazwie sun.misc.Unsafe, która może zrobić wszystko, co tylko zechce, w tym bezpośredni dostęp do pamięci. Kod natywny (JNI) może również obejść ten rodzaj kontroli.

W środowisku piaskownicy (na przykład aplety Java, JavaFX) każda klasa ma zestaw uprawnień, a dostęp do opcji Niebezpieczne, setAccessible i definiowanie natywnych implementacji jest kontrolowany przez SecurityManager.

„Modyfikatory dostępu Java nie mają być mechanizmem bezpieczeństwa”.

To zależy w dużej mierze od tego, gdzie jest uruchamiany kod Java. Podstawowe klasy Java wykorzystują modyfikatory dostępu jako mechanizm bezpieczeństwa wymuszający piaskownicę.

Jakie są naprawdę uzasadnione zastosowania setAccessible?

Podstawowe klasy Javy używają go jako łatwego sposobu na dostęp do rzeczy, które muszą pozostać prywatne ze względów bezpieczeństwa. Na przykład struktura serializacji Java używa go do wywoływania prywatnych konstruktorów obiektów podczas deserializacji obiektów. Ktoś wspomniał o System.setErr i byłby to dobry przykład, ale co ciekawe, metody klasy System setOut / setErr / setIn wszystkie używają kodu natywnego do ustawiania wartości końcowego pola.

Innym oczywistym uzasadnionym zastosowaniem są frameworki (trwałość, struktury internetowe, wstrzykiwanie), które muszą zaglądać do wnętrza obiektów.

Moim zdaniem debuggery nie należą do tej kategorii, ponieważ zwykle nie działają w tym samym procesie JVM, ale zamiast tego interfejs z JVM za pomocą innych środków (JPDA).

Czy Java została zaprojektowana tak, aby w ogóle NIE miała takiej potrzeby?

To dość głębokie pytanie, na które trzeba dobrze odpowiedzieć. Wyobrażam sobie, że tak, ale musiałbyś dodać inny mechanizm (y), które mogą nie być aż tak korzystne.

Czy możesz ograniczyć setAccessible tylko do legalnych zastosowań?

Najprostszym ograniczeniem OOTB, które możesz zastosować, jest posiadanie SecurityManagera i zezwolenie setAccessible tylko na kod pochodzący z określonych źródeł. To właśnie robi Java - standardowe klasy Java, które pochodzą z JAVA_HOME, mogą wykonywać setAccessible, podczas gdy niepodpisane klasy apletów z foo.com nie mogą wykonywać setAccessible. Jak powiedziano wcześniej, to zezwolenie jest binarne, w tym sensie, że albo je ma się, albo nie. Nie ma oczywistego sposobu, aby zezwolić setAccessible na modyfikowanie pewnych pól / metod, jednocześnie uniemożliwiając innym. Używając SecurityManager można jednak zabronić klasom odwoływania się do pewnych pakietów w całości, z odbiciem lub bez.

Czy mogę napisać moje klasy, które mają być ustawione jako dostępne bez względu na konfigurację SecurityManager? ... A może jestem na łasce tego, kto zarządza konfiguracją?

Nie możesz i na pewno jesteś.

Sami Koivu
źródło
„Jeśli dystrybuujesz komponent oprogramowania o nazwie foo.jar wśród ludzi na całym świecie, i tak jesteś całkowicie na ich łasce. Mogą modyfikować definicje klas w Twoim .jar (poprzez inżynierię wsteczną lub bezpośrednią manipulację kodem bajtowym). może uruchomić Twój kod we własnej JVM itp. W takim przypadku martwienie się nie pomoże ”. Czy nadal tak jest? Jakieś rady, jak utrudnić manipulowanie oprogramowaniem (miejmy nadzieję, że znacząco)? Z tego powodu trudno jest po prostu wyrzucić aplikacje desktopowe Java przez okno.
Sero
@naaz Muszę powiedzieć, że nic się nie zmieniło. Po prostu architektura działa przeciwko tobie. Każde oprogramowanie działające na moim komputerze, nad którym mam pełną kontrolę, mogę zmienić. Najsilniejszy środek odstraszający może być środkiem prawnym, ale nie sądzę, żeby zadziałał we wszystkich scenariuszach. Myślę, że pliki binarne Java są jednymi z najłatwiejszych do odwrócenia, ale osoby z doświadczeniem mogą wszystko zmienić. Maskowanie pomaga, utrudniając wprowadzanie dużych zmian, ale cokolwiek logicznego (jak sprawdzenie praw autorskich) jest nadal trywialne do ominięcia. Zamiast tego możesz spróbować umieścić kluczowe części na serwerze.
Sami Koivu
A co z denuvo?
Sero
15
  • Do czego służą prawdziwie uzasadnione zastosowania setAccessible?

Testowanie jednostkowe, elementy wewnętrzne JVM (np. Wdrażanie System.setError(...)) i tak dalej.

  • Czy Java została zaprojektowana tak, aby w ogóle NIE miała takiej potrzeby?
  • Jakie byłyby negatywne konsekwencje (jeśli w ogóle) takiego projektu?

Wiele rzeczy byłoby niemożliwych do wdrożenia. Na przykład różne iniekcje trwałości, serializacji i zależności Java są zależne od odbicia. I prawie wszystko, co opiera się na konwencjach JavaBeans w czasie wykonywania.

  • Czy możesz ograniczyć setAccessiblesię tylko do legalnych zastosowań?
  • Czy to tylko przez SecurityManager?

Tak.

  • Jak to działa? Biała / czarna lista, szczegółowość itp.?

To zależy od pozwolenia, ale uważam, że pozwolenie na używanie setAccessiblejest binarne. Jeśli chcesz uzyskać szczegółowość, musisz albo użyć innego programu ładującego klasy z innym menedżerem zabezpieczeń dla klas, które chcesz ograniczyć. Myślę, że można by zaimplementować niestandardowego menedżera bezpieczeństwa, który implementuje bardziej szczegółową logikę.

  • Czy często trzeba go konfigurować w swoich aplikacjach?

Nie.

  • Czy mogę napisać moje klasy tak, aby były setAccessibleodporne na działanie niezależnie od SecurityManagerkonfiguracji?
    • Czy jestem na łasce tego, kto zarządza konfiguracją?

Nie, nie możesz i tak, jesteś.

Inną alternatywą jest „wymuszenie” tego za pomocą narzędzi do analizy kodu źródłowego; np. zwyczaj pmdlub findbugszasady. Lub selektywne przeglądanie kodu identyfikowanego przez (powiedzmy) grep setAccessible ....

W odpowiedzi na dalsze działania

Żadne z moich zajęć nie przypomina egzekwowalnej prywatności. Wzór singletona (odkładając na bok wątpliwości co do jego zalet) jest teraz niemożliwy do wyegzekwowania.

Jeśli to cię martwi, to myślę, że musisz się martwić. Ale tak naprawdę nie powinieneś próbować zmuszać innych programistów do szanowania twoich decyzji projektowych. Jeśli ludzie są na tyle głupi, by użyć refleksji do darmowego tworzenia wielu instancji swoich singletonów (na przykład), mogą żyć z konsekwencjami.

Z drugiej strony, jeśli masz na myśli „prywatność”, obejmującą znaczenie ochrony poufnych informacji przed ujawnieniem, to szczekasz niewłaściwe drzewo. Sposobem ochrony wrażliwych danych w aplikacji Java jest niedopuszczenie niezaufanego kodu do bezpiecznej piaskownicy, która obsługuje poufne dane. Modyfikatory dostępu Java nie mają być mechanizmem bezpieczeństwa.

<Przykład ze smyczkami> - Czy tylko ja uważam, że jest to OGROMNY problem?

Chyba nie jedyny :-). Ale IMO, to nie jest problem. Przyjmuje się, że niezaufany kod powinien być wykonywany w piaskownicy. Jeśli masz zaufany kod / zaufanego programistę robiącego takie rzeczy, twoje problemy są gorsze niż nieoczekiwanie zmienne ciągi znaków. (Pomyśl o bombach logicznych, eksfiltracji danych przez ukryte kanały itp.)

Istnieją sposoby radzenia sobie (lub złagodzenia) problemu „złego aktora” w zespole programistycznym lub operacyjnym. Ale są kosztowne i restrykcyjne ... i przesadne w większości przypadków użycia.

Stephen C.
źródło
1
Przydatne również do takich rzeczy, jak implementacje trwałości. Refleksje nie są naprawdę przydatne dla debuggerów (chociaż pierwotnie miały być). Nie możesz pożytecznie zmienić (podklasy) SecurityManagerna to, ponieważ wszystko, co dostajesz, to sprawdzenie uprawnień - wszystko, co naprawdę możesz zrobić, to spojrzeć na acc i być może przejść stos, sprawdzić bieżący wątek). grep java.lang.reflect.
Tom Hawtin - tackline
@Stephen C: +1 już, ale byłbym również wdzięczny za wkład w jeszcze jedno pytanie, które właśnie dodałem. Z góry dziękuję.
polygenelubricants
Zauważ, że grep to okropny pomysł. Jest tak wiele sposobów dynamicznego wykonywania kodu w Javie, że prawie na pewno jeden z nich przegapisz. Ogólnie kod na czarnej liście jest receptą na niepowodzenie.
Antymon
„Modyfikatory dostępu Java nie mają być mechanizmem bezpieczeństwa”. - właściwie tak, są. Java została zaprojektowana tak, aby umożliwić koegzystencję zaufanego i niezaufanego kodu na tej samej maszynie wirtualnej - od samego początku było to częścią jej podstawowych wymagań. Ale tylko wtedy, gdy system działa z zablokowanym programem SecurityManager. Możliwość kodu piaskownicy zależy całkowicie od wymuszania specyfikacji dostępu.
Jules
@Jules - Większość ekspertów Java nie zgodziłaby się z tym; np. stackoverflow.com/questions/9201603/…
Stephen C
11

W tej perspektywie refleksja jest rzeczywiście ortogonalna do bezpieczeństwa / ochrony.

Jak możemy ograniczyć refleksję?

Java ma menedżera bezpieczeństwa i ClassLoaderpodstawę jej modelu bezpieczeństwa. W twoim przypadku myślę, że musisz się przyjrzeć java.lang.reflect.ReflectPermission.

Ale to nie rozwiązuje całkowicie problemu refleksji. Dostępne zdolności odblaskowe powinny podlegać szczegółowemu schematowi autoryzacji, co obecnie nie ma miejsca. Np. Aby zezwolić pewnym frameworkom na używanie odbicia (np. Hibernate), ale nie reszty kodu. Lub, aby umożliwić programowi odzwierciedlenie tylko w sposób tylko do odczytu, w celu debugowania.

Jednym z podejść, które może stać się głównym nurtem w przyszłości, jest użycie tak zwanych luster w celu oddzielenia zdolności odblaskowych od klas. Zobacz Mirrors: Zasady projektowania dla obiektów meta-poziomu . Istnieje jednak wiele innych badań, które dotyczą tego problemu. Ale zgadzam się, że problem jest poważniejszy w przypadku języka dynamicznego niż w przypadku języków statycznych.

Czy powinniśmy się martwić supermocarstwem, jakie daje nam refleksja? Tak i nie.

Tak, w tym sensie, że platforma Java powinna być zabezpieczona Classloadermenedżerem bezpieczeństwa. Możliwość zepsucia refleksji może być postrzegana jako naruszenie.

Nie w tym sensie, że większość systemów i tak nie jest całkowicie bezpieczna. Wiele klas może często być podklasami i już teraz możesz nadużywać systemu. Oczywiście zajęcia mogą być zrobione finallub zapieczętowane , aby nie można ich było podklasować w innym słoiku. Jednak tylko kilka klas jest odpowiednio zabezpieczonych (np. String) zgodnie z tym.

Zobacz tę odpowiedź na temat końcowych zajęć, aby uzyskać ładne wyjaśnienie. Zobacz także blog Sami Koivu, aby dowiedzieć się więcej o hakowaniu java na temat bezpieczeństwa.

Model bezpieczeństwa Javy może być pod pewnym względem postrzegany jako niewystarczający. Niektóre języki, takie jak NewSpeak, przyjmują jeszcze bardziej radykalne podejście do modularności, w której masz dostęp tylko do tego, co jest jawnie dane ci przez odwrócenie zależności (domyślnie nic).

Należy również pamiętać, że bezpieczeństwo i tak jest względne . Na poziomie języka nie można na przykład zapobiec zużyciu przez moduł 100% procesora lub całej pamięci do a OutOfMemoryException. Takie obawy należy rozwiązać w inny sposób. Być może zobaczymy w przyszłości rozszerzenie Java o limity wykorzystania zasobów, ale nie na jutro :)

Mógłbym szerzej rozwinąć ten temat, ale myślę, że przedstawiłem swój punkt widzenia.

ewernli
źródło