如果你不能投球,那就没什么乐趣了。但扩展Throwable也使它实现了可序列化,这就是真正有趣的开始。使用序列化,我们可以创建一个球,该球应该被捕获的次数与序列化数据声明的次数相同。
这场比赛似乎破坏了乐趣。你不能抛出它;但更重要的是你不能序列化它。如果您试图直接将球序列化,它也将尝试序列化它所附加到的游戏,从而导致NotSerializableException。
请注意,从技术上讲,从球到其比赛的参考只是一个类似于 this$0的字段。我说的“附加”是指指定给那个字段。因此,这个问题相当于序列化一个具有不可序列化的正常字段的对象。
问题在于我们根本不需要序列化游戏和球。我们只需要将球反序列化,并将其附加到游戏中。该Game实例不需要直接来自序列化流。我们可以在它的位置放置替代品,并覆盖readResolve,在它附着到球之前用一个game替换它。
在实践中作弊
有多种方法可以创建包含附加到替代游戏的球的原始数据(字节数组)。我们创建了一个类似于ball(ba)的类。我们给它和ball一样的serialversionuid和我们期望的caught值。它附加到我们的替代实现readResolve(Player)上。我们将其序列化,并将ba类的名称替换为ball类。将byte[]转换为一个String并返回,允许我们使用string.replace。
选择ba这个名称是为了让play.player$ba的长度与game.game$ball的长度相同。否则,直接用一个替换另一个会损坏流。
package play; |
这就是发生的情况:
- 在调用时readObject(),首先Ball获取反序列化,然后caught设置为-1。
- Ball.this$0反序列化的值是一个实例Player。
- 在Player分配给该字段之前(因为它的类型错误,它将失败),readResolve调用其方法,创建一个新Game的score0
- 这Game被分配给Ball.this$0,readObject()返回Ball。
- ball.catched()是用catched=-1和这个$0.score==0来调用的,你作弊就被抓住了!
结论
创建具有对不可序列化对象的引用的可序列化对象是一个坏主意,因为您无法对它们进行序列化。但是,你仍然可以反序列化它们。
Java序列化充满了令人讨厌的意外可能性。关于这一点你可以做一系列的谜题。但是,如果你真的陷入此种境地,那么你需要做的就是查看过去几年的JDK安全漏洞。