Вопрос 19 · Раздел 13

Можно ли изменить значение String через рефлексию?

Технически — да, через Reflection API можно изменить внутреннее содержимое строки, но это очень плохая практика.

Версии по языкам: English Russian Ukrainian

Junior Level

Технически — да, через Reflection API можно изменить внутреннее содержимое строки, но это очень плохая практика.

String s = "Hello";
// Получаем доступ к приватному полю value
// В Java 8 и раньше поле было char[] value. Начиная с Java 9 (Compact Strings) — byte[] value.
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
byte[] value = (byte[]) field.get(s);
value[0] = 'J'; // Меняем 'H' на 'J'
System.out.println(s); // Выведет "Jello"

Почему это плохо:

  • Изменится строка везде, где используется "Hello" (пул строк)
  • Сломается hashCode — строка потеряется в HashMap
  • Это нарушение безопасности JVM

⚠️ ВНИМАНИЕ: Этот приём НЕЛЬЗЯ использовать в production-коде. Это нарушение безопасности, ломающее JVM. Только для образовательных целей.


Middle Level

Последствия

  1. Поломка String Pool — строка изменится во всех местах, где используется этот литерал (логи, конфиги, имена классов)

  2. Поломка HashMapString кэширует hashCode. После изменения массива value, содержимое строки изменится, а хеш останется старым → объект невозможно найти в HashMap

  3. Security Bypass — проверки безопасности (доступ к файлам, URL) основаны на строках. Изменение строки “после проверки” — классическая уязвимость

Защита в современных JDK

В Java 17+ доступ к внутренним полям java.base через рефлексию запрещён модульной системой (Project Jigsaw). Вы получите InaccessibleObjectException.

Обойти можно флагами запуска (--add-opens java.base/java.lang=ALL-UNNAMED), но в production это невозможно.


Senior Level

Полный пример атаки

String s = "admin";
Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true);
byte[] value = (byte[]) valueField.get(s);
// Меняем "admin" на "hacker"
value[0] = 'h'; value[1] = 'a'; value[2] = 'c';
value[3] = 'k'; value[4] = 'e'; value[5] = 'r';

Глубинные последствия

  • ClassLoader pollution — имена классов, загрузчики, пути — всё может быть скомпрометировано
  • Security Manager — если используется, проверки могут быть обойдены
  • SerialVersionUID — зависит от имени класса, может быть нарушен

Эволюция защиты

  • Java 8 и раньше: setAccessible(true) работал без ограничений
  • Java 9: модульная система ограничила доступ к java.base
  • Java 16+: по умолчанию InaccessibleObjectException
  • Java 17+: --illegal-access=deny по умолчанию

Резюме для Senior

  • Reflection-атака на String — пример того, как низкоуровневые манипуляции разрушают высокоуровневые гарантии
  • Современные JDK (17+) блокируют это на уровне модулей
  • В production с корректными флагами это невозможно
  • Для Senior это иллюстрация важности инкапсуляции на всех уровнях

🎯 Шпаргалка для интервью

Обязательно знать:

  • Технически возможно через setAccessible(true) на поле value, но это нарушение безопасности
  • Последствия: поломка String Pool (изменится везде), сломается hashCode (потеря в HashMap), Security Bypass
  • Java 9+: модульная система ограничила доступ; Java 16+: InaccessibleObjectException по умолчанию
  • Java 17+: --illegal-access=deny — reflection к java.base заблокирован
  • Обойти можно флагами (--add-opens), но в production это невозможно
  • Это НЕЛЬЗЯ использовать в production — только для образовательных целей

Частые уточняющие вопросы:

  • Работает ли в Java 17+? — Нет, InaccessibleObjectException без --add-opens
  • Что сломается если изменить строку? — Все использования этого литерала, hashCode в HashMap, Security Manager
  • ClassLoader pollution — что это? — Имена классов, загрузчики, пути — всё может быть скомпрометировано
  • SerialVersionUID зависит от этого? — Да, зависит от имени класса, может быть нарушен

Красные флаги (НЕ говорить):

  • «Это нормальный приём для production» — это нарушение безопасности JVM
  • «Reflection работает в любой версии» — в Java 16+ заблокирован для java.base
  • «Изменится только эта строка» — изменятся ВСЕ экземпляры этого литерала в пуле
  • «Можно добавить флаг в production» — --add-opens java.base/java.lang=ALL-UNNAMED — security risk

Связанные темы:

  • [[4. Почему класс String является иммутабельным]]
  • [[5. Какие последствия иммутабельности String]]
  • [[18. Как работает String pool и как это связано с иммутабельностью]]
  • [[8. Достаточно ли сделать все поля final для иммутабельности]]