Питання 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 для незмінності]]