h2 数据库从 1.x 版本升级到 2.x 版本排坑记载

打印 上一主题 下一主题

主题 1867|帖子 1867|积分 5601

配景

又是一次被弊端追着升级的记载,排了一天升级的坑,照例是要记载一番的。漏扫到「H2 控制台 JNDI 远程代码执行弊端(CVE-2021-42392)」,弊端描述:
   H2 是一个用 Java 编写的关系数据库管理系统。
H2 存在远程代码执行弊端,该弊端是由于 H2 控制台可以通过 JNDI 从远程服务器加载自界说类,攻击者可利用该弊端在未授权的环境下,构造恶意请求,触发远程代码执行弊端。
  须要对 h2 进行升级,本文记载升级过程中的标题。
h2 的 2.x 版本和 1.x 版本相差有点大,主要有几个标题:

  • h2 的版本选择,须要考虑 JDK 版本。
  • v1.x 的数据库文件格式和 v2.x 的格式不兼容,必须走数据库文件的升级。
  • v2.x 新增了很多关键字,旧版本能正常识别的字符可能是新版本的保存关键字。
  • 包含 BLOB 范例的表,导入导出会出现数据超长标题,须要先舍弃再重新创建。
版本选择

本来想直接升到最新版本 2.3.232 ,启动发现 JDK 版本不兼容,这个版本是用 JDK11+ 编译的,而咱们的运行环境的 JDK 版本过低,所以保守选择弊端范围外的近来版本 h2-2.0.206 。
梳理出来的升级方案:

  • 用旧版本的 jar 执行旧版本数据库文件导出命令。
  • 用新版本的 jar 执行新版本数据库导入命令,创建新版本的数据库文件。
  • 停止旧版本的数据库历程。
  • 用新版本启动数据库历程。
关键字清除

数据库导入/导出命令:
  1. java -cp h2-1.4.197.jar org.h2.tools.Script -url "jdbc:h2:file:./my-data" -user $username -password $password -script backup.sql
  2. java -cp h2-2.0.206.jar org.h2.tools.RunScript -url "jdbc:h2:file:./my-data-new" -user $username -password $password -script backup.sql
复制代码
导入操作报了很多 expected "identifier";,根源是 H2 的 2.x 版本要求对保存的关键字使用双引号包围,还可以通过 jdbc url 配置 ;NON_KEYWORDS=KEYWORD1,KEYWORD2 对指定保存关键字进行清除。
对着 h2 1.x 版本导出的 SQL 文件,把所有可疑的关键字都摘出来后,调整导入语句如下:
  1. java -cp h2-2.0.206.jar org.h2.tools.RunScript -url "jdbc:h2:file:./my-data-new;MODE=LEGACY;NON_KEYWORDS=USER,KEY,PATH,COMMAND,INTERVAL,VALUE,USERNAME,PASSWORD,UNIT,DAYTIME,HOUR" -user $username -password $password -script backup.sql
复制代码
BLOB 表标题

清除关键字后,另有一个比力麻烦的标题。由于数据库中有一个表存储了应用上传的文件,导出 SQL 语句对它处理时创建了一个新表并为该表创建索引,这个语句导入会报错:
  1. Exception in thread "main" org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement " \000aCREATE PRIMARY[*] KEY SYSTEM_LOB_STREAM_PRIMARY_KEY ON SYSTEM_LOB_STREAM(ID, PART)"; expected "OR, FORCE, VIEW, ALIAS, SEQUENCE, USER, TRIGGER, ROLE, SCHEMA, CONSTANT, DOMAIN, TYPE, DATATYPE, AGGREGATE, LINKED, MEMORY, CACHED, LOCAL, GLOBAL, TEMP, TEMPORARY, TABLE, SYNONYM, UNIQUE, HASH, SPATIAL, INDEX"; SQL statement:
  2. CREATE PRIMARY KEY SYSTEM_LOB_STREAM_PRIMARY_KEY ON SYSTEM_LOB_STREAM(ID, PART) [42001-206]
复制代码
怀疑是设置了 KEY 为非关键字导致的,所以修改 SQL 中 KEY 关键字加双引号后,导入还是报这个错误。
然后比对主键创建语句发现其他表的主键创建不是用 CREATE PRIMARY KEY 而是用 ALTER TABLE 添加的,改成一样的语法:
  1. ALTER TABLE PUBLIC.SYSTEM_LOB_STREAM ADD CONSTRAINT PUBLIC.CONSTRAINT_6C0124 PRIMARY KEY(ID, PART);
复制代码
结果导入还是报错 BDATA BINARY 列 Value too long:

导出命令只能针对整个数据库文件,没有单独清除某个表的方法。估计是这个表没法处理了,只能舍弃掉该表了。
升级文件准备

由于 BLOB 字段标题,放弃该表,采取迂回处理。

  • 导出 1.x 版本的数据之前,先执行一个删除包含 BLOB 字段的表的操作。
  • 导入 2.x 版本后再补充执行创建 BLOB 表的操作。
准备 h2 升级文件,清单如下:

  • 新版本:h2-2.0.206.jar。
  • 旧版本:h2-1.4.197.jar。
  • 删除BLOB表的脚本:BLOB_TABLE_DROP.sql。
  • 创建BLOB表的脚本:BLOB_TABLE_CREATE.sql。
  • 旧版本的数据库文件。
将这些文件放在同一个目录下,直接用 jar 包中的导入、导出工具完成数据库升级。
升级脚本编写

  1. #!/bin/sh
  2. dir=$(dirname "$0")
  3. # Step1:Prepare for upgrade.
  4. appHome=`pwd`
  5. echo 'home path is '$appHome
  6. # Step2:Clear history db file and restart h2 with version 2.x.
  7. cd $appHome/h2/
  8. rm -rf app*
  9. sh stop.sh
  10. sh startH2ByV2.sh
  11. # Step3:Upgrade h2 dt.mv.dv from v1.4.197 to v2.0.206 as file format is related to h2 version.
  12. # 3.1 Get username and password ,need to trim return char ,otherwise will encounter「Wrong user name or password」error.
  13. username=$(cat $appHome/conf/db.properties |grep username=|awk -F "=" '{print $2}'|tr -d ' \r')
  14. password=$(cat $appHome/conf/db.properties |grep password=|awk -F "=" '{print $2}'|tr -d ' \r')
  15. # 3.2 Copy old version db file to h2  directory.
  16. cp $appHome/data/appDBData.mv.db .
  17. # 3.3 Drop table with BLOB field and export use v1.4.197.
  18. java -cp h2-1.4.197.jar org.h2.tools.RunScript -url "jdbc:h2:file:./appDBData" -user $username -password $password -script BLOB_TABLE_DROP.sql
  19. java -cp h2-1.4.197.jar org.h2.tools.Script -url "jdbc:h2:file:./appDBData" -user $username -password $password -script app_backup.sql
  20. # 3.4 Create dt-new use v2.0.206 and create table with BLOB field for new db.
  21. java -cp h2-2.0.206.jar org.h2.tools.RunScript -url "jdbc:h2:file:./appDBData-new;MODE=LEGACY;NON_KEYWORDS=USER,KEY,PATH,COMMAND,INTERVAL,VALUE,USERNAME,PASSWORD,UNIT,DAYTIME,HOUR" -user $username -password $password -script app_backup.sql
  22. java -cp h2-2.0.206.jar org.h2.tools.RunScript -url "jdbc:h2:file:./appDBData-new" -user $username -password $password -script BLOB_TABLE_CREATE.sql
  23. # Step4:Update app's db file and restart.
  24. # 4.1 Copy dt-new.mv.db to data directory.
  25. cp appDBData-new.mv.db $appHome/data/
  26. # 4.2 Update db.properties url with new db file.
  27. cp $appHome/conf/db.properties $appHome/conf/db-h2-v1.properties
  28. dtNew=$(cat $appHome/conf/db.properties|grep 'NON_KEYWORDS')
  29. if [ -z $dtNew ]; then
  30.     echo "Update db.properties use new database file."
  31.     sed -i -c "s/data\/appDBData/data\/appDBData-new;NON_KEYWORDS=USER,KEY,PATH,COMMAND,INTERVAL,VALUE,USERNAME,PASSWORD,UNIT,DAYTIME,HOUR/" $appHome/conf/db.properties
  32. else
  33.     echo "Db.properties is already updated with new database file."
  34. fi
  35. # 4.3 Restart app.
  36. cd $appHome/bin
  37. sh app.sh restart
复制代码
启示录

总体来看比力简单的,但时升级时截取应用配置的数据库帐密信息时遇到一个标题,就是通过脚本截取的信息一直报帐号或密码错误「Wrong user name or password」,而直接设置帐号和密码又能精确导入导出。
所以断定解析结果出了标题,果然是 awk -F 截取到的信息结尾有一个 CR 字符,该配置文件是 CRLF 的分隔符,awk 除了了 \n 还是保存了一个 CR 字符即 \r ,须要去掉才气得到精确的数据库信息。
修正获取认证信息的脚本,结尾加上 tr -d ' \r' 就可以了。
  1. username=$(cat $appHome/conf/db.properties |grep username=|awk -F "=" '{print $2}'|tr -d ' \r')
复制代码
另有一个启示就是,数据库建表时须要谨慎,起名的时间应该避开数据库的保存关键字。比方数据库有一个表的某个字段名称就是 KEY,这是明显的索引关键字呀。
别的,如果用原生 JDBC 操作数据库,应该用列序号来处理查询结果,避免直接用列名称,由于不同数据库列名称大小写有差异。比如 Oracle 默认大写,但是 Postgrel 默认小写,步伐想兼容不同数据库,最好用 getString(columnIndex) 这个方法来获取查询结果。
2024年有半年是在排这个老项目的坑,一全栈开辟硬是成了运维角色,一年了,没正经写过代码。甲辰年腊月二十六,我们腊月二十九才开始放假啊!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

勿忘初心做自己

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表