《如果 Java 没有 final,世界会怎样?一个 String 被修改的恐怖故事》
·
String类的分析:
在IDEA程序中我们在运用String类时都会比较好奇,String类中到底是什么样的,博主就看了一部分,并了解其中用法。
在这代码中final:不可被继承,是String中的重要一部分,它主要保证string具有不可变性,如果没有final,String可能会被恶意删改。
常量池和缓存的基础:
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s2); // true,指向常量池同一对象
System.out.println(s1 == s3); // false,新建对象
// 如果 String 可被继承,常量池无法保证对象行为一致
哈希值的缓存:
public final class String {
private int hash; // 缓存哈希值
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
// 计算并缓存
for (int i = 0; i < value.length; i++) {
h = 31 * h + value[i];
}
hash = h;
}
return h;
}
}
因为final保证了不可变性,才有哈希值的安全缓存;
让我用一个具体的例子来说明,如果没有 final修饰,子类如何破坏 String的行为:
假设String类没有final。
// 定义一个恶意的 String 子类
class MaliciousString extends String {
private boolean tampered = false;
public MaliciousString(String original) {
super(original);
}
// 重写 equals 方法,制造安全漏洞
@Override
public boolean equals(Object obj) {
// 总是返回 true,无论比较什么!
tampered = true;
return true;
}
// 重写 charAt 方法,返回错误的值
@Override
public char charAt(int index) {
// 总是返回 'X'
return 'X';
}
// 重写 hashCode 方法
@Override
public int hashCode() {
// 返回固定值,破坏 HashMap 的性能
return 1;
}
}
实际攻击示例:
- 破坏密码验证:
public class PasswordValidator {
// 验证密码的方法
public boolean validatePassword(String inputPassword) {
String realPassword = "secret123"; // 真实密码
// 正常情况下应该比较字符串内容
return realPassword.equals(inputPassword);
}
public static void main(String[] args) {
PasswordValidator validator = new PasswordValidator();
// 攻击者使用恶意的 String 子类
MaliciousString fakePassword = new MaliciousString("wrong_password");
// 由于 equals 被重写为总是返回 true
// 验证会通过,即使密码错误!
boolean result = validator.validatePassword(fakePassword);
System.out.println("验证结果: " + result); // 输出:true(被黑了!)
}
}
- 破坏 HashMap 的完整性:
import java.util.HashMap;
import java.util.Map;
public class HashMapAttack {
public static void main(String[] args) {
// 使用 String 作为键的 HashMap
Map<String, String> userDatabase = new HashMap<>();
userDatabase.put("admin", "管理员权限");
userDatabase.put("user1", "普通用户权限");
// 插入恶意键
MaliciousString maliciousKey = new MaliciousString("admin");
userDatabase.put(maliciousKey, "攻击者权限");
// 问题1:查找行为异常
System.out.println("查找 admin: " + userDatabase.get("admin"));
// 可能返回 "攻击者权限" 而不是 "管理员权限"
// 问题2:HashMap 内部结构被破坏
System.out.println("HashMap 大小: " + userDatabase.size());
// 可能显示 3,但实际上键 "admin" 被覆盖了
// 问题3:性能被破坏
for (int i = 0; i < 10000; i++) {
userDatabase.put(new MaliciousString("key" + i), "value");
}
// 所有键的 hashCode 都是 1,HashMap 退化为链表,O(1) → O(n)
}
}
- 破坏字符串操作:
public class StringOperationBreaker {
public static void main(String[] args) {
String str = "Hello World";
MaliciousString malicious = new MaliciousString("Hello World");
// 正常 String
System.out.println("正常字符串:");
for (int i = 0; i < str.length(); i++) {
System.out.print(str.charAt(i));
}
// 输出: Hello World
System.out.println("\n恶意字符串:");
for (int i = 0; i < malicious.length(); i++) {
System.out.print(malicious.charAt(i));
}
// 输出: XXXXXXXXXXX (全部是 X!)
// 更严重的问题:方法调用链
String processed = str.toUpperCase().substring(2, 5);
System.out.println("\n正常处理: " + processed); // 输出: LLO
String processed2 = malicious.toUpperCase().substring(2, 5);
System.out.println("恶意处理: " + processed2); // 输出: XXX
}
}
为什么final可以防止这些问题:
// String 的实际定义
public final class String { // ← 这个 final 是关键!
// 类体...
}
// 尝试继承会编译错误
class MaliciousString extends String { // ❌ 编译错误:无法从 final String 继承
// ...
}
以上就是今天内容,如有错误或者想法都可以提出来,大家一起讨论。
更多推荐
所有评论(0)