Life has its own fate, and meeting may not be accidental.

0%

Java 导出内存查找敏感信息

如果重要的数据(保存在内存中)在使用后没有及时清理,有可能会导致信息泄漏。开发人员通常都回用String 保存敏感数据(密码,卡号等)。因为String 对象是不可变的,只有 JVM 的垃圾回收器才能从内存中清除String的值。而只有内存不足的时候虚拟机才会执行垃圾回收,所以我们不能保证垃圾回收什么时候进行。当系统崩溃后,memory dump 可能会泄漏敏感数据。

1、获取JVMHeapDump

内存转储执行的过程中为了保证dump的信息是可靠的,所以会暂停应用线上系统慎用
JVM会将整个heap的信息dump写入到一个文件,heap如果比较大的话,就会导致这个过程比较耗时,如果要转储千兆字节大小的堆,则暂停时间以分钟而不是为单位。

1
2
3
#jmap,jcmd 都是JDK自带工具,指定JVM pid 直接dump
jcmd 5760[PID] GC.heap_dump /tmp/dumpHeap.hprof
此转储过程比使用jmap进行转储要快得多!转储文件要小得多,但足以让您知道泄漏的位置。
1
2
3
4
>jmap -dump:live,format=b,file=/tmp/dumpHeap.hprof 5760  #live Object

>jmap -dump:format=b,file=/tmp/dumpHeap.hprof 19770 # all Object 不建议
如果使用"-dump",则将转储整个堆,包括不可达的对象。如果使用"-dump:live",则只会转储可访问的对象,但这(至少)需要标记堆以找出可访问的对象。

image.png

2、工具

JavaPassDump

工具:https://github.com/corener/JavaPassDump

所有在Java层的数据库连接,最终都会维持一个DB Driver Object,其中会保留数据库的连接配置。
思路一:基于经验的,匹配所有DB Driver,从内存中找到Driver Object,获取username/password字段 ;
思路二:绝大部分的配置信息,在内存中,都是String类型的field字段,直接从内存中提取password字段的field值。两种思路的前提都是能够分析内存,尝试直接分析运行时内存,从中提取配置信息,无奈坑点太多,遂转入dump JVMHeap,直接通过OQL语言进行分析。
实战中,DumpJVNHeap大小从几M~几G左右不等,拉到本地分析不太现实,直接在线上分析拿到分析结果就行。这样,在流量层面,效率方面,更优化。为了更简单通用,采用思路二,直接提取password字段的值。

根据线上JDK环境,适配对应的jar,

  • OQLQuery7-.jar 适配JDK<=7
  • OQLQuery8+.jar 适配JDK>=8

除了线上环境,OQLQuery.jar也可以用在本地分析JvmHeapDump.hprof文件,例如:Spring的/heapdump接口,产生的dump文件,利用对应的OQL语句,也可以提取出密码参数 。

1
2
3
4
5
## 放置于webServer的web路径下,访问时,dump当前JVM的heap到/tmp目录,windows系统,需要修改路径
>heapDump.jsp

## 分析JVMHeap文件 结尾为oql语句 base64编码
java -jar ./OQLQuery.jar /tmp/dumpHeap.hprof dmFyIGZpbHRlciA9IHt9OwptYXAoaGVhcC5jbGFzc2VzKCksIGZ1bmN0aW9uIChjbHMpIHsKICAgcmV0dXJuIG1hcChjbHMuZmllbGRzLCBmdW5jdGlvbiAoZmllbGQpIHsgCiAgICAgIGlmKCBmaWVsZC5uYW1lLnRvU3RyaW5nKCkuY29udGFpbnMoInBhc3MiKSB8fCBmaWVsZC5uYW1lLnRvU3RyaW5nKCkuY29udGFpbnMoInVzZXJuYW1lIikgfHxmaWVsZC5uYW1lLnRvU3RyaW5nKCkuY29udGFpbnMoIlBBU1MiKSl7CiAgICAgICAgcmV0dXJuIG1hcChoZWFwLm9iamVjdHMoY2xzKSwgZnVuY3Rpb24gKG9icykgewogICAgICAgICAgdmFyIHRhZyA9IGNscy5uYW1lKyJ8IitmaWVsZC5uYW1lIDsKICAgICAgICAgIHZhciByZXMgPSAgImNsYXNzIDogIitjbHMubmFtZSsiXG4gRmllbGQgWyAiK2ZpZWxkLm5hbWUudG9TdHJpbmcoKSsiIDogIjsKICAgICAgICAgIGlmKCBvYnNbZmllbGQubmFtZS50b1N0cmluZygpXSAhPSBudWxsICl7CiAgICAgICAgICAgIHJlcyA9IHJlcyArIG9ic1tmaWVsZC5uYW1lLnRvU3RyaW5nKCldLnRvU3RyaW5nKCkrIiBdXG4iOwogICAgICAgICAgfWVsc2V7CiAgICAgICAgICAgIHJlcyA9IHJlcyArICJudWxsIF1cbiI7CiAgICAgICAgICB9CiAgICAgICAgICBpZiAoZmlsdGVyW3RhZ10gPT0gbnVsbCkgewogICAgICAgICAgICBmaWx0ZXJbdGFnXSA9IHJlczsKICAgICAgICAgICAgcHJpbnQocmVzKTsKICAgICAgICAgIH0KICAgICAgICAgIHJldHVybiBudWxsOwogICAgICAgIH0pOwogICAgICB9CiAgICAgIHJldHVybiBudWxsOwogIH0pOwp9KTs=

oql语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var filter = {};
map(heap.classes(), function (cls) {
return map(cls.fields, function (field) {
if( field.name.toString().contains("pass") || field.name.toString().contains("username") ||field.name.toString().contains("PASS")||field.name.toString().contains("key")||field.name.toString().contains("KEY")||field.name.toString().contains("key")){
return map(heap.objects(cls), function (obs) {
var tag = cls.name+"|"+field.name ;
var res = "class : "+cls.name+"\n Field [ "+field.name.toString()+" : ";
if( obs[field.name.toString()] != null ){
res = res + obs[field.name.toString()].toString()+" ]\n";
}else{
res = res + "null ]\n";
}
if (filter[tag] == null) {
filter[tag] = res;
print(res);
}
return null;
});
}
return null;
});
});


dmFyIGZpbHRlciA9IHt9OwptYXAoaGVhcC5jbGFzc2VzKCksIGZ1bmN0aW9uIChjbHMpIHsKICAgcmV0dXJuIG1hcChjbHMuZmllbGRzLCBmdW5jdGlvbiAoZmllbGQpIHsgCiAgICAgIGlmKCBmaWVsZC5uYW1lLnRvU3RyaW5nKCkuY29udGFpbnMoInBhc3MiKSB8fCBmaWVsZC5uYW1lLnRvU3RyaW5nKCkuY29udGFpbnMoInVzZXJuYW1lIikgfHxmaWVsZC5uYW1lLnRvU3RyaW5nKCkuY29udGFpbnMoIlBBU1MiKXx8ZmllbGQubmFtZS50b1N0cmluZygpLmNvbnRhaW5zKCJrZXkiKXx8ZmllbGQubmFtZS50b1N0cmluZygpLmNvbnRhaW5zKCJLRVkiKXx8ZmllbGQubmFtZS50b1N0cmluZygpLmNvbnRhaW5zKCJrZXkiKSl7CiAgICAgICAgcmV0dXJuIG1hcChoZWFwLm9iamVjdHMoY2xzKSwgZnVuY3Rpb24gKG9icykgewogICAgICAgICAgdmFyIHRhZyA9IGNscy5uYW1lKyJ8IitmaWVsZC5uYW1lIDsKICAgICAgICAgIHZhciByZXMgPSAgImNsYXNzIDogIitjbHMubmFtZSsiXG4gRmllbGQgWyAiK2ZpZWxkLm5hbWUudG9TdHJpbmcoKSsiIDogIjsKICAgICAgICAgIGlmKCBvYnNbZmllbGQubmFtZS50b1N0cmluZygpXSAhPSBudWxsICl7CiAgICAgICAgICAgIHJlcyA9IHJlcyArIG9ic1tmaWVsZC5uYW1lLnRvU3RyaW5nKCldLnRvU3RyaW5nKCkrIiBdXG4iOwogICAgICAgICAgfWVsc2V7CiAgICAgICAgICAgIHJlcyA9IHJlcyArICJudWxsIF1cbiI7CiAgICAgICAgICB9CiAgICAgICAgICBpZiAoZmlsdGVyW3RhZ10gPT0gbnVsbCkgewogICAgICAgICAgICBmaWx0ZXJbdGFnXSA9IHJlczsKICAgICAgICAgICAgcHJpbnQocmVzKTsKICAgICAgICAgIH0KICAgICAgICAgIHJldHVybiBudWxsOwogICAgICAgIH0pOwogICAgICB9CiAgICAgIHJldHVybiBudWxsOwogIH0pOwp9KTs=

image.png

heapdump_tool

该工具是基于jhat,通过jhat解析heapdump文件,所以需要安装jdk和配置好环境变量,例如win \Java\jdk8\bin\jhat.exe, 在控制台输入jhat检查是否安装正确。

https://toolaffix.oss-cn-beijing.aliyuncs.com/wyzxxz/20220720/heapdump_tool.jar

1
2
3
4
5
6
7
8
9
10
usage:> java -jar heapdump_tool.jar  heapdump
查询方式:
1. 关键词 例如 password
2. 字符长度 len=10 获取长度为10的所有key或者value值
3. 按顺序获取 num=1-100 获取顺序1-100的字符
获取url,file,ip
geturl 获取所有字符串中的url
getfile 获取所有字符串中的文件路径文件名
getip 获取所有字符串中的ip
默认不输出查询结果非key-value格式的数据,需要获取所有值,输入all=true,all=false取消显示所有值。

JDumpSpider

本工具需要使用 Oracle JDK 1.8 版本(更高版本将导致异常)。

1
2
3
4
5
6
7
$ java -jar .\target\JDumpSpider-1.0-SNAPSHOT-full.jar                  
Missing required parameter: '<heapfile>'
Usage: JDumpSpider [-hV] <heapfile>
Extract sensitive information from heapdump file.
<heapfile> Heap file path.
-h, --help Show this help message and exit.
-V, --version Print version information and exit.