在Java中,是否可以更改或修改枚举本身,从而破坏枚举单例? [英] In Java is it possible to change or modify an enum itself and thus to corrupt an enum singleton?

查看:105
本文介绍了在Java中,是否可以更改或修改枚举本身,从而破坏枚举单例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否可以在运行时更改枚举本身?例如。使用反射。问题不是要更改枚举常量的状态。它将更改一个枚举的常量集或删除任何常量。


关于以下枚举,是否可以添加颜色 WHITE 或删除颜色 RED 或更改其顺序?

 公共枚举Color { 

红色,绿色,蓝色;

}

为什么问?



  • 首先我想知道它是否可行。

  • 但是,如果可行,是否会对常见的
  • >

我知道这个问题有点恶意。但是,即使是约书亚·布洛赫(Joshua Bloch)也提到了精打细算的攻击。在讨论(1)实现单例时,建议使用枚举单例模式。如果我们可以修改一个枚举,那么对这种模式的攻击可行吗?


我试图解决它并对其进行部分管理。我将发布结果作为答案-在此之后建议




(1)参见什么是在Java中实现单例模式的有效方法吗?后面有链接到 effective_java_reloaded.pdf ,第31页。

解决方案

我开始使用 javap -c 分解枚举 Color 的分析。以下是摘录:

  static {}; 
代码:
0:新#1 // //游乐场/彩色
3:dup
4:ldc#14 //字符串红色
6:iconst_0
7:invokespecial#15 //方法< init> :( Ljava / lang / String; I)V
10:putstatic#19 //字段RED:Lplayground / Color;
13:新#1 //游乐场/彩色
16:dup
17:ldc#21 //字符串GREEN
19:iconst_1
20:invokespecial# 15 //方法< init>::( Ljava / lang / String; I)V
23:putstatic#22 //字段GREEN:Lplayground / Color;
26:新#1 //游乐场/彩色
29:dup
30:ldc#24 //字符串BLUE
32:iconst_2
33:invokespecial# 15 //方法< init>::( Ljava / lang / String; I)V
36:putstatic#25 //字段BLUE:Lplayground / Color;
39:iconst_3
40:anewarray#1 //游乐场/彩色
43:dup
44:iconsst_0
45:getstatic#19 //字段RED:游乐场/颜色;
48:aastore
49:dup
50:iconst_1
51:getstatic#22 //字段GREEN:Lplayground / Color;
54:aastore
55:dup
56:iconst_2
57:getstatic#25 //字段BLUE:Lplayground / Color;
60:aastore
61:putstatic#27 //字段ENUM $ VALUES:[Lplayground / Color;
64:返回

在索引61处,我们看到将三个枚举常量分配给静态名称为 ENUM $ VALUES 的数组。



通过反射列出所有静态字段...

  Field []声明字段= Color.class.getDeclaredFields(); 
代表(字段字段:clarifiedFields){
if(Modifier.isStatic(field.getModifiers())){
System.out.println(field.getName()+: + field.getType());
}
}

显示枚举常量并显示数组:

 红色:类运动场.ReflectEnum $ Color 
绿色:类运动场.ReflectEnum $ Color
蓝色:类运动场.ReflectEnum $ Color
ENUM $ VALUES:类[Lplayground.ReflectEnum $ Color;

我定义了以下方法来获取枚举数组:

 受保护的静态< E扩展了Enum< E>> E [] getEnumsArray(Class< E> ec)引发异常{
Field field = ec.getDeclaredField( ENUM $ VALUES);
field.setAccessible(true);
return(E [])field.get(ec);
}

使用它可以更改枚举常量的顺序:

  Color [] colors = getEnumsArray(Color.class); 
colors [0] = Color.GREEN;
colors [1] = Color.RED;
colors [2] = Color.BLUE;

列出枚举常量

 用于(颜色color:Color.values()){
System.out.println(action +: + color.ordinal());
}

显示:

 绿色:1 
红色:0
蓝色:2

显然顺序已更改。



由于可以将值分配给数组,因此分配也有效null

  Color [] colors = getEnumsArray(Color.class); 
colors [0] = Color.GREEN;
colors [1] = Color.RED;
colors [2] = null;

列出枚举常量显示:

  GREEN:1 
RED:0
线程 main中的异常java.lang.NullPointerException
在Playground.ReflectEnum.main(ReflectEnum.java:57 )

如果我们尝试按其名称查找枚举常量

  System.out.println(Color.valueOf( GREEN)); 
System.out.println(Color.valueOf( RED));
System.out.println(Color.valueOf( BLUE));

我们看到最后一次更改的影响更大:

 线程主中的异常java.lang.NullPointerException 
在java.lang.Class.enumConstantDirectory(Class.java:3236)
在Java中。 lang.Enum.valueOf(Enum.java:232)
在操场上.Color.valueOf(Color.java:1)
在操场上.ReflectEnum.main(ReflectEnum.java:48)

ReflectEnum.java:48 行包含上面的打印语句 Color.valueOf( GREEN)的值。此枚举常量未设置为 null 。因此,它完全破坏了 Color valueOf 方法。



但是 Enum.valueOf(Color.class, BLUE)仍会解析枚举常量 Color.BLUE 。 / p>

由于枚举数组被声明为 static final ,所以我跟随了使用Java反射更改私有静态最终字段以创建和设置新的枚举数组。

 受保护的静态< E扩展了Enum< E>> void setEnumsArray(Class E> ec,E ... e)引发异常{
Field field = ec.getDeclaredField( ENUM $ VALUES);
字段修饰符字段= Field.class.getDeclaredField( modifiers);
field.setAccessible(true);
修饰符Field.setAccessible(true);
修饰符Field.setInt(field,field.getModifiers()&〜Modifier.FINAL);
field.set(ec,e);
}

但是执行 setEnumsArray(Color.class,Color。 (BLUE,Color.GREEN,Color.RED,Color.BLUE)失败,

 线程异常 main java.lang.IllegalAccessException:无法设置静态最终值[Lplayground.Color;字段Playground.Color.ENUM $ VALUES到[Lplayground.Color;在sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)处的
在sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)中的
(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
at java.lang.reflect.Field.set(Field.java:758)
at park.ReflectEnum.setEnumsArray(ReflectEnum.java:76)
at Playground.ReflectEnum.main(ReflectEnum.java:37)






编辑1(在 Radiodef 的评论之后):



在我添加了以下两个方法之后……

 受保护的静态字段getEnumsArrayField(Class<?> ec)抛出异常{
字段字段= ec.getDeclaredField( ENUM $ VALUES);
field.setAccessible(true);
返回字段;
}

受保护的静态无效clearFieldAccessors(Field field)引发ReflectiveOperationException {
Field fa = Field.class.getDeclaredField( fieldAccessor);
fa.setAccessible(true);
fa.set(field,null);

Field ofa = Field.class.getDeclaredField( overrideFieldAccessor);
ofa.setAccessible(true);
ofa.set(field,null);

Field rf = Field.class.getDeclaredField( root);
rf.setAccessible(true);
字段根=(字段)rf.get(字段);
if(root!= null){
clearFieldAccessors(root);
}

我尝试过此操作:

  System.out.println(Arrays.toString((Object [])getEnumsArrayField(Color.class).get(null)));; 
clearFieldAccessors(getEnumsArrayField(Color.class));
setEnumsArray(Color.class,Color.BLUE,Color.GREEN,Color.RED,Color.BLUE);
System.out.println(Arrays.toString(Color.values()));

这表明:

  [红色,绿色,蓝色] 
[蓝色,绿色,红色,蓝色]

枚举数组已被另一个替换。






编辑2(在 GotoFinal

根据答案,位于如何在Java中使用反射创建枚举实例?可以在运行时创建更多枚举实例。然后应该可以用另一个实例代替一个枚举实例。我有以下枚举单例:

 公共枚举Singleton {

INSTANCE( );

私有字符串描述;

private Singleton(String description){
this.description = description;
}

@Override
public String toString(){
返回描述;
}

}

重复使用代码GotoFinal显示了我定义以下方法:

 受保护的静态单例createEnumValue(字符串名称,整数,字符串描述)引发异常{
类< Singleton> monsterClass = Singleton.class;
构造函数<?>构造函数= monsterClass.getDeclaredConstructors()[0];
constructor.setAccessible(true);

字段ConstructorAccessorField = Constructor.class.getDeclaredField( constructorAccessor);
ConstructorAccessorField.setAccessible(true);
sun.reflect.ConstructorAccessor ca =(sun.reflect.ConstructorAccessor)ConstructorAccessorField.get(constructor);
if(ca == null){
方法acquisitionConstructorAccessorMethod = Constructor.class.getDeclaredMethod( acquireConstructorAccessor);
acquisitionConstructorAccessorMethod.setAccessible(true);
ca =(sun.reflect.ConstructorAccessor)acquisitionConstructorAccessorMethod.invoke(constructor);
}
单例enumValue =(Singleton)ca.newInstance(new Object [] {name,ordinal,description});
返回枚举值;
}

受保护的静态< E扩展了Enum< E>> void setFinalField(Class ec,Field field,E e)引发NoSuchFieldException,IllegalAccessException {
Field ModifysField = Field.class.getDeclaredField( modifiers);
field.setAccessible(true);
修饰符Field.setAccessible(true);
修饰符Field.setInt(field,field.getModifiers()&〜Modifier.FINAL);
field.set(ec,e);
}

正在执行

  System.out.println(Singleton.INSTANCE.toString()); 
//设置INSTANCE = theNewOne
Singleton theNewOne = createEnumValue(Singleton.INSTANCE.name(),Singleton.INSTANCE.ordinal(),新的!);
setFinalField(Singleton.class,Singleton.class.getDeclaredField(Singleton.INSTANCE.name()),theNewOne);
System.out.println(Singleton.INSTANCE.toString());
//设置枚举数组= [theNewOne]
clearFieldAccessors(getEnumsArrayField(Singleton.class));
setEnumsArray(Singleton.class,theNewOne);
System.out.println(Arrays.toString(Singleton.values()));

显示:

 唯一的
新的!
[新的!]

总结:




  • 可以在运行时修改枚举,并用另一个替换枚举数组。但是至少将枚举常量设置为 null 会破坏VM中已定义枚举的一致性。虽然可以根据GotoFinal的答案解决此问题。


  • 使用 enum 实现单例时,可以用另一个枚举实例替换唯一的一个实例-至少对于某些知道其实现的JDK / JRE而言。如果枚举构造函数具有参数,则新创建的枚举实例可以利用它们来植入恶​​意行为。



Is it possible to change an enum itself at run-time somehow? E.g. using reflection. The question is not about to change the state of an enum constant. It's about to change the set of constants of an enum or to remove any constants.

Regarding the following enum is it possible to add a color WHITE or remove color RED or to change their order?

public enum Color {

  RED, GREEN, BLUE;

}

Why I ask?

I know this question is kind of malicious. But even Joshua Bloch mentioned a "cleverly crafted attack" when talking (1) about implementing a singleton and recommended the enum singleton pattern. If we can modify an enum is then an attack on this pattern feasible?

I tried to work it out and managed it partially. I will post my results as an answer - following this advice.


(1) See What is an efficient way to implement a singleton pattern in Java? There follow the link to effective_java_reloaded.pdf, page 31.

解决方案

I started my analysis disassembling the enum Color using javap -c. Here is an excerpt from:

 static {};
    Code:
       0: new           #1                  // class playground/Color
       3: dup           
       4: ldc           #14                 // String RED
       6: iconst_0      
       7: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V
      10: putstatic     #19                 // Field RED:Lplayground/Color;
      13: new           #1                  // class playground/Color
      16: dup           
      17: ldc           #21                 // String GREEN
      19: iconst_1      
      20: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V
      23: putstatic     #22                 // Field GREEN:Lplayground/Color;
      26: new           #1                  // class playground/Color
      29: dup           
      30: ldc           #24                 // String BLUE
      32: iconst_2      
      33: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V
      36: putstatic     #25                 // Field BLUE:Lplayground/Color;
      39: iconst_3      
      40: anewarray     #1                  // class playground/Color
      43: dup           
      44: iconst_0      
      45: getstatic     #19                 // Field RED:Lplayground/Color;
      48: aastore       
      49: dup           
      50: iconst_1      
      51: getstatic     #22                 // Field GREEN:Lplayground/Color;
      54: aastore       
      55: dup           
      56: iconst_2      
      57: getstatic     #25                 // Field BLUE:Lplayground/Color;
      60: aastore       
      61: putstatic     #27                 // Field ENUM$VALUES:[Lplayground/Color;
      64: return        

At index 61 we see the three enum constants being assigned to a static array with name ENUM$VALUES.

Listing all static fields by reflection ...

Field[] declaredFields = Color.class.getDeclaredFields();
for (Field field : declaredFields) {
  if (Modifier.isStatic(field.getModifiers())) {
    System.out.println(field.getName() + ": " + field.getType());
  }
}

shows the enum constants and reveals the array:

RED: class playground.ReflectEnum$Color
GREEN: class playground.ReflectEnum$Color
BLUE: class playground.ReflectEnum$Color
ENUM$VALUES: class [Lplayground.ReflectEnum$Color;

I defined the following method to get the enum array:

  protected static <E extends Enum<E>> E[] getEnumsArray(Class<E> ec) throws Exception {
    Field field = ec.getDeclaredField("ENUM$VALUES");
    field.setAccessible(true);
    return (E[]) field.get(ec);
  }

Using it it's possible to change the order of the enum constants:

Color[] colors = getEnumsArray(Color.class);
colors[0] = Color.GREEN;
colors[1] = Color.RED;
colors[2] = Color.BLUE;

Listing the enum constants

for (Color color : Color.values()) {
  System.out.println(action + ":" + color.ordinal());
}

shows:

GREEN:1
RED:0
BLUE:2

Obviously the order has been changed.

Since it is possible to assign values to the array it is also valid to assign null.

Color[] colors = getEnumsArray(Color.class);
colors[0] = Color.GREEN;
colors[1] = Color.RED;
colors[2] = null;

Listing the enum constants shows:

GREEN:1
RED:0
Exception in thread "main" java.lang.NullPointerException
    at playground.ReflectEnum.main(ReflectEnum.java:57)

And if we try look up the enum constants by their names

System.out.println(Color.valueOf("GREEN"));
System.out.println(Color.valueOf("RED"));
System.out.println(Color.valueOf("BLUE"));

we see that the last change broke even more:

Exception in thread "main" java.lang.NullPointerException
    at java.lang.Class.enumConstantDirectory(Class.java:3236)
    at java.lang.Enum.valueOf(Enum.java:232)
    at playground.Color.valueOf(Color.java:1)
    at playground.ReflectEnum.main(ReflectEnum.java:48)

The line ReflectEnum.java:48 contains the above print statement of Color.valueOf("GREEN"). This enum constant was not set to null. So it broke the valueOf method of Color completely.

But Enum.valueOf(Color.class, "BLUE") still resolves the enum constant Color.BLUE.

Since the enum array is declared as static final I followed Change private static final field using Java reflection to create and set a new enum array.

  protected static <E extends Enum<E>> void setEnumsArray(Class<E> ec, E... e) throws Exception {
    Field field = ec.getDeclaredField("ENUM$VALUES");
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    field.setAccessible(true);
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
    field.set(ec, e);
  }

But executing setEnumsArray(Color.class, Color.BLUE, Color.GREEN, Color.RED, Color.BLUE) fails with

Exception in thread "main" java.lang.IllegalAccessException: Can not set static final [Lplayground.Color; field playground.Color.ENUM$VALUES to [Lplayground.Color;
    at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
    at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
    at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
    at java.lang.reflect.Field.set(Field.java:758)
    at playground.ReflectEnum.setEnumsArray(ReflectEnum.java:76)
    at playground.ReflectEnum.main(ReflectEnum.java:37)


EDIT 1 (after comments of Radiodef):

After I added the following two methods ...

  protected static Field getEnumsArrayField(Class<?> ec) throws Exception {
    Field field = ec.getDeclaredField("ENUM$VALUES");
    field.setAccessible(true);
    return field;
  }

  protected static void clearFieldAccessors(Field field) throws ReflectiveOperationException {
    Field fa = Field.class.getDeclaredField("fieldAccessor");
    fa.setAccessible(true);
    fa.set(field, null);

    Field ofa = Field.class.getDeclaredField("overrideFieldAccessor");
    ofa.setAccessible(true);
    ofa.set(field, null);

    Field rf = Field.class.getDeclaredField("root");
    rf.setAccessible(true);
    Field root = (Field) rf.get(field);
    if (root != null) {
      clearFieldAccessors(root);
    }

i tried this:

System.out.println(Arrays.toString((Object[]) getEnumsArrayField(Color.class).get(null)));
clearFieldAccessors(getEnumsArrayField(Color.class));
setEnumsArray(Color.class, Color.BLUE, Color.GREEN, Color.RED, Color.BLUE);
System.out.println(Arrays.toString(Color.values()));

This shows:

[RED, GREEN, BLUE]
[BLUE, GREEN, RED, BLUE]

The enum array has been replaced by another one.


EDIT 2 (after comments of GotoFinal)
According to the answer of GotoFinal on How to create an instance of enum using reflection in java? it's possible to create further enum instances at run-time. Then it should be possible to replace an enum singleton instance by another one. I have the following enum singleton:

  public enum Singleton {

    INSTANCE("The one and only");

    private String description;

    private Singleton(String description) {
      this.description = description;
    }

    @Override
    public String toString() {
      return description;
    }

  }

Reusing the code GotoFinal has shown I defined the following method:

  protected static Singleton createEnumValue(String name, int ordinal, String description) throws Exception {
    Class<Singleton> monsterClass = Singleton.class;
    Constructor<?> constructor = monsterClass.getDeclaredConstructors()[0];
    constructor.setAccessible(true);

    Field constructorAccessorField = Constructor.class.getDeclaredField("constructorAccessor");
    constructorAccessorField.setAccessible(true);
    sun.reflect.ConstructorAccessor ca = (sun.reflect.ConstructorAccessor) constructorAccessorField.get(constructor);
    if (ca == null) {
      Method acquireConstructorAccessorMethod = Constructor.class.getDeclaredMethod("acquireConstructorAccessor");
      acquireConstructorAccessorMethod.setAccessible(true);
      ca = (sun.reflect.ConstructorAccessor) acquireConstructorAccessorMethod.invoke(constructor);
    }
    Singleton enumValue = (Singleton) ca.newInstance(new Object[] { name, ordinal, description });
    return enumValue;
  }

 protected static <E extends Enum<E>> void setFinalField(Class<E> ec, Field field, E e) throws NoSuchFieldException, IllegalAccessException {
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    field.setAccessible(true);
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
    field.set(ec, e);
  }

Now executing

System.out.println(Singleton.INSTANCE.toString());
// setting INSTANCE = theNewOne
Singleton theNewOne = createEnumValue(Singleton.INSTANCE.name(), Singleton.INSTANCE.ordinal(), "The new one!");
setFinalField(Singleton.class, Singleton.class.getDeclaredField(Singleton.INSTANCE.name()), theNewOne);
System.out.println(Singleton.INSTANCE.toString());
// setting enum array = [theNewOne]
clearFieldAccessors(getEnumsArrayField(Singleton.class));
setEnumsArray(Singleton.class, theNewOne);
System.out.println(Arrays.toString(Singleton.values()));

shows:

The one and only
The new one!
[The new one!]

Summarizing:

  • It's possible to modify an enum at run-time and to replace the enum array by another one. But at least setting an enum constant to null breaks the consistency of defined enums within the VM. Though this can be fixed according to GotoFinal's answer.

  • When implementing a singleton using enum it's possible to replace the only one instance by another enum instance - at least for some JDKs/JREs knowing their implementations. If the enum constructor has arguments they can be exploited by the new created enum instance to plant malicious behaviour.

这篇关于在Java中,是否可以更改或修改枚举本身,从而破坏枚举单例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆