摘要:笔者在处理业务线问题时遇到接口返回的内容和实际内容不一致的现象。

本文分享自华为云社区《Java反射机制清空字符串导致业务异常分析》,作者:毕昇小助手。

编者按:笔者在处理业务线问题时遇到接口返回的内容和实际内容不一致的现象。根因是业务方通过Java反射机制将String类型敏感数据引用的value数组元素全部设置为’0’,从而实现清空用户敏感数据的功能。这种清空用户敏感数据的方法会将字符串常量池相应地址的内容修改,进而导致所有指向该地址的引用的内容和实际值不一致的现象。

背景知识

JVM为了提高性能和减少内存开销,在实例化字符串常量时进行了优化。JVM在Java堆上开辟了一个字符串常量池空间(StringTable),JVM通过ldc指令加载字符串常量时会调用 StringTable::intern 函数将字符串加入到字符串常量池中。

StringTable::intern函数代码

oop StringTable::intern(Handle string_or_null, jchar* name,
int len, TRAPS) {
unsigned int hashValue = hash_string(name, len);
int index = the_table()->hash_to_index(hashValue);
oop found_string = the_table()->lookup(index, name, len, hashValue);
// Found
if (found_string != NULL) {
ensure_string_alive(found_string);
return found_string;
}
debug_only(StableMemoryChecker smc(name, len * sizeof(name[0])));
assert(!Universe::heap()->is_in_reserved(name),
"proposed name of symbol must be stable");
Handle string;
// try to reuse the string if possible
if (!string_or_null.is_null()) {
string = string_or_null;
} else {
string = java_lang_String::create_from_unicode(name, len, CHECK_NULL);
}
#if INCLUDE_ALL_GCS
if (G1StringDedup::is_enabled()) {
// Deduplicate the string before it is interned. Note that we should never
// deduplicate a string after it has been interned. Doing so will counteract
// compiler optimizations done on e.g. interned string literals.
G1StringDedup::deduplicate(string());
}
#endif
// Grab the StringTable_lock before getting the_table() because it could
// change at safepoint.
oop added_or_found;
{
MutexLocker ml(StringTable_lock, THREAD);
// Otherwise, add to symbol to table
added_or_found = the_table()->basic_add(index, string, name, len,
hashValue, CHECK_NULL);
}
ensure_string_alive(added_or_found);
return added_or_found;
}

StringTable::intern 函数处理流程

字符串的创建方式

根据StringTable::intern函数处理流程,我们可以简单描绘如下6种常见的字符串的创建方式以及引用关系。

现象

某业务线使用fastjson实现Java对象序列化功能,低概率出现接口返回的JSON数据的某个属性值和实际值不一致的现象。正确的属性值应该为"null",实际属性值却为"0000"。

原因分析

为了排除fastjson自身的嫌疑,我们将其替换jackson后,依然会低概率出现同样的现象。由于两个不同三方件同时存在这个问题的可能性不大,为此我们暂时排除fastjson引入该问题的可能性。为了找到该问题的根因,我们在环境中开启远程调试功能。待问题复现,调试代码时我们发现只要是指向"null"的引用,显示的内容全部变成"0000",由此我们初步怀疑字符串常量池中的"null"被修改成"0000"。

一般导致常量池被修改有两种可能性:

  1. 第三方动态库引入的bug导致字符串常量池内容被修改;
  2. 在业务代码中通过Java反射机制主动修改字符串常量池内容;

业务方排查项目中使用到的第三方动态库,未发现可疑的动态库,排除第一种可能性。排查业务代码中使用到Java反射的功能,发现清空密码功能会使用到Java反射机制,并且将String类型密码的value数组元素全部设置为’0’。

业务出现的现象可以简单通过代码模拟:

  1. 在TestString对象类中定义一个nullStr属性,初始值为"null";
  2. 定义一个带有password属性的User类;
  3. 在main方法中创建一个密码为"null"的User对象,使用Java反射机制将密码字符串的所有字符全部修改为’0’,分别在密码修改前后打印TestString对象nullStr属性值;

复现代码

import java.lang.reflect.Field;
import java.util.Arrays;
public class TestString {
private String nullStr = "null";
public String getNullStr() {
return nullStr;
}
static class User {
private final String password;
User(String password) {
this.password = password;
}
public String getPassword() {
return password;
}
}
private static void clearPassword(User user) throws Exception {
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] chars = (char[]) field.get(user.getPassword());
Arrays.fill(chars, '0');
}
public static void main(String[] args) throws Exception {
User user = new User("null");
TestString testString = new TestString();
System.out.println("before clear password >>>>");
System.out.println(" User.password:" + user.getPassword());
System.out.println("TestString.nullStr:" + testString.getNullStr());
System.out.println("--------------------------------");
clearPassword(user);
System.out.println("after clear password >>>>");
System.out.println(" User.password:" + user.getPassword());
System.out.println("TestString.nullStr:" + testString.getNullStr());
}
}

复现代码字符串引用关系如下图所示。

User对象的password属性和TestString的nullStr属性引用都同时指向常量池中的"null"字符串,"null"字符串的value指向 {‘n’,‘u’,‘l’,‘l’} char数组。使用Java反射机制将User对象的password属性引用的value数组全部设置为’0’,导致 TestString的nullStr属性值也变成了 “0000”。

输出结果如下:

  before clear password >>>>
User.password:null
TestString.nullStr:null
--------------------------------
after clear password >>>>
User.password:0000
TestString.nullStr:0000

通过输出结果我们可以发现在通过Java反射机制修改某一个字符串内容后,所有指向原字符串的引用的内容全部变成修改后的内容。

总结

在保存业务敏感数据时避免使用String类型保存,建议使用byte[]或char[]数组保存,然后通过Java反射机制清空敏感数据。

点击关注,第一时间了解华为云新鲜技术~

Java反射机制清空字符串导致业务异常分析的更多相关文章

  1. 详解Java反射机制

    反射是程序在运行状态下,动态的获取某个类的内部信息的一种操作.例如:类名,包名,所有属性的集合,所有方法的集合,构造方法的集合等.该操作发生在程序的运行时状态,所以编译器管不着有关反射的一些代码,通常 ...

  2. 一文带你了解Java反射机制

    想要获取更多文章可以访问我的博客 - 代码无止境. 上周上班的时候解决一个需求,需要将一批数据导出到Excel.本来公司的中间件组已经封装好了使用POI生成Excel的工具方法,但是无奈产品的需求里面 ...

  3. day11 Java反射机制

    java反射机制 反射是java中的动态机制,它允许我们在程序运行期间再确定类的实例化,方法的调用,属性的调用等,而不是传统意义上的在编码期间确定. 因此,反射可以大大的提高代码的灵活度,但是随之而来 ...

  4. 长篇图解java反射机制及其应用场景

    一.什么是java反射? 在java的面向对象编程过程中,通常我们需要先知道一个Class类,然后new 类名()方式来获取该类的对象.也就是说我们需要在写代码的时候(编译期或者编译期之前)就知道我们 ...

  5. JAVA反射机制—学习总结

    最近收到很多关于Java反射机制的问题留言,其实Java反射机制技术方面没有太多难点,或许是大家在学习过程中遗漏了细小知识点,导致一些问题无法彻底理解,现在我们简单的总结一下,加深印象.什么是反射机制 ...

  6. 转!!java反射机制

    Java 反射机制 基本概念 在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法? 答案是肯定的. 这种动态获取类的信息以及动态调用对象 ...

  7. Java反射机制(创建Class对象的三种方式)

    1:SUN提供的反射机制的类: java.lang.Class<T> java.lang.reflect.Constructor<T> java.lang.reflect.Fi ...

  8. 【54】Java反射机制剖析

    java反射机制: 1.指的是可以于运行时加载,探知和使用编译期间完全未知的类. 2.程序在运行状态中, 可以动态加载一个只有名称的类, 对于任意一个已经加载的类,都能够知道这个类的所有属性和方法; ...

  9. 【java】-- java反射机制

    参考文章:https://blog.csdn.net/sinat_38259539/article/details/71799078    https://blog.csdn.net/wanderlu ...

  10. Java反射机制的使用(全)

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6566957.html  一:反射是什么 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有 ...

随机推荐

  1. Django+celery+eventlet+flower+redis异步任务创建及查询实现

    1.环境版本:Django 3.2.12celery 5.3.4eventlet 0.33.3flower 2.0.1redis 3.5.3项目名称:new_project 2.celery配置(se ...

  2. QMainWindow无法显示,使用show()不显示窗口(QT)

    当使用 MainWindow w: w.show(); 不显示窗口时 变更为: MainWindow *w=new MainWindow(); w->show();

  3. [Python急救站课程]日期和时间的输出

    日期和时间的输出 from datetime import datetime # 引用datetime 库 now = datetime.now() # 获得当前日期和时间信息 print(now) ...

  4. "拍牌神器"是怎样炼成的(二)--- 键鼠模拟之AutoIt

    不同于上一篇的WinAPI方法,这次让我们来看另一个更简单.有效的键鼠模拟方案,即通过COM组件AutoItX实现键鼠模拟. AutoIt AutoIt是一个免费软件,它使用一种类似BASIC的脚本语 ...

  5. Redis Functions 介绍之一

    Redis提供了编程接口(programming interface)可以让你在Redis服务器端执行客户的脚本. 一个重大的变化就是从Redis 7开始,你可以选择使用Redis Functions ...

  6. [WPF]浅析资源引用(pack URI)

    WPF中我们引用资源时常常提到一个概念:pack URI,这是WPF标识和引用资源最常见的方式,但不是唯一的方式.本文将介绍WPF中引用资源的几种方式,并回顾一下pack URI标识引用在不同位置的资 ...

  7. 题解 CF1264D2

    前言 建议大家看一下我对于 D1 的题解(传送门)后再看本题解,本题解是基于那篇题解的基础上书写的. 数学符号约定 \(\dbinom{n}{m}\):表示 \(n\) 选 \(m\) . 如非特殊说 ...

  8. 对象转url参数

    对象转url function getParams(params) { let paramStr = ''; Object.keys(params) .forEach((item) => { i ...

  9. LabVIEW基于机器视觉的实验室设备管理系统(3)

    目录 行动计划 创建用户信息数据库 后面板连线 初始化 确认修改 确认id 判断旧密码是否正确 判断两次输入的新密码是否相同 修改用户数据库中的密码 结尾 效果演示 上一期我们完成了欢迎登录和信息查询 ...

  10. 一个小型markdown读写编辑器推荐

    在作业完成时我和很多同学们发现并没有很好地利用博客园里面自带的markdown标记语法的形式完成作业 因此我在网上自己搜索发现了这款软件 下载地址 在帮助里可以学到一些简单的使用形式 可以方便编辑已有 ...