老炮踩坑录 · 翻车现场系列
基于真实企业内网系统的配置实读,连接串与口令逐条附行号
关键词:properties 注释 · 明文口令 · 密文复用 · FTP 账号 · 配置复制
👋 欢迎阅读
🏠个人主页:知守观
📘我的专栏: 老炮踩坑录
💻当前内容:配置里的连接串与口令
引言
上一篇写这个仓库的两代 Web 框架,翻 pom 翻出来的。这篇翻的是配置。
现在做密钥扫描的平台,默认盯着 application.yml、application-prod.yml,盯着 .env,盯着 Java 里的常量字符串。这套系统的技术栈太老,配置不用 yml,用的是 properties;连接信息不用框架的约定键名,用的是 jdbc.url、defaultDataSource.jdbcUrl 这种自定义键。再加上注释行,扫描规则一条都套不上。
我在这个仓库的 resources/config 目录下,翻出四种不同性质的口令暴露方式。注释里的明文 Oracle 账号、可以跨库复用的密文、FTP 地址里的账号,还有同一份配置在四个模块里各存一份。
第一个文件:注释里的 Oracle 账号,是真实在用的那种
portal-web/resources/config/datasource.properties 开头六行:
#oracle version database settings
#jdbc.driver=oracle.jdbc.driver.OracleDriver
#jdbc.url=jdbc:oracle:thin:@10.20.30.41:1521:proddb
#jdbc.username=appuser
#jdbc.password=pwd01
Oracle 全套连接信息,driver、url、username、password 四行齐整,全部以 # 开头。注释掉了,当前不生效。
紧跟着第 8 到 12 行是实际生效的:
#jdbc.driver=org.apache.derby.jdbc.ClientDriver
jdbc.driver=com.p6spy.engine.spy.P6SpyDriver
jdbc.url=jdbc:derby://10.20.30.52:1527:appdb
jdbc.username=app
jdbc.password=app
这一段能读出三件事。
切换数据源的动作是改注释。Oracle 那套留着不删,Derby 那套注释掉一行再启用,这就是这个项目的"多环境配置"。项目里没有 profile 概念,没有 -Dspring.profiles.active,运维要换库就上去改 properties 再重启。
生效的驱动写的是 p6spy。com.p6spy.engine.spy.P6SpyDriver 是个 SQL 监听驱动,挂在这个位置意味着所有 SQL 都过它一遍。p6spy 的具体输出位置要看 spy.properties,这个文件我在配置目录里没找到,输出到哪里我没核实。生产环境挂着 SQL 监听驱动这件事本身,就够单独说一遍。
生效的账号是 app/app。用户名和密码一样,这个习惯在后面还有更夸张的例子。
连接池参数也在同一个文件里,第 14 到 19 行:
c3p0.minPoolSize=1
c3p0.maxPoolSize=2
c3p0.initialPoolSize=2
c3p0.maxIdleTime=30000
c3p0.checkoutTimeout=10000
最大连接数 2。这类后台系统并发不高,这个数字可能是照着实际压测调出来的,也可能是没人动过。拿不到当时的容量数据,我不猜原因,只把数字摆出来——一个跑在生产上的服务,连接池上限是 2。
第二个文件:RAC 的完整描述符,也在注释里
client.properties 第 41 到 53 行:
#Default data source configuration for the client
defaultDataSource.driverClass = oracle.jdbc.driver.OracleDriver
########### For HA DB ############
#defaultDataSource.jdbcUrl = jdbc:oracle:thin:@10.20.30.41:1521:proddb
########### For RAC DB ############
# defaultDataSource.jdbcUrl = jdbc:oracle:oci:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=10.20.30.61)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=10.20.30.62)(PORT=1521))(load_balance=no)(failover=yes))(CONNECT_DATA=(SERVICE_NAME=svc1)(failover_mode=(type=select)(method=basic)(retries=5)(delay=100))))
#defaultDataSource.user = Bx4nVc9jTa=
#defaultDataSource.password = Qm7tRk2pLw=
defaultDataSource.minPoolSize = 2
defaultDataSource.maxPoolSize = 5
defaultDataSource.initialPoolSize = 2
defaultDataSource.maxIdleTime = 3600
defaultDataSource.checkoutTimeout = 10000
两种数据库部署形态写在一起:单机 HA 一套,RAC 一套,靠注释切换。RAC 那条是完整的 TNS 描述符,两个节点 IP、服务名、failover 参数(retries=5, delay=100)全在里面。
这份配置比上一份危险的地方在于,它把数据库的拓扑结构也一起交出去了。拿到这段的人,知道这是双节点 RAC、服务名是什么、故障切换重试 5 次。做渗透的时候,这类信息比单个 IP 值钱。
账号密码这里不是明文,是 Bx4nVc9jTa= 和 Qm7tRk2pLw= 这种形式。Base64 编码的 8 字节块,对应的是一套对称加密。同一目录下的第 58 到 59 行给了线索:
# Default password encypt policy 0: MD5 1: SHA256
servparam.defaultencrypttype = 1
加密策略默认值写死成 SHA256。源码里 encypt 这个词也拼错了,跟后面的 dom't 一个路数。
同一个密文,出现在五个地方
Qm7tRk2pLw= 这个串,我在四个模块的配置文件里都见到了,而且都在各自文件的第 48 行:
alarm-web/resources/config/client.properties:48
portal-web/resources/config/client.properties:48
sms-web/resources/config/client.properties:48
number-web/resources/config/client.properties:48
同一份文件复制四份这件事后面单说。这里先看另一处更值得看的地方,sms-web/resources/config/alarmclient.properties 第 26 到 29 行:
# database information of Kpi
kpidb.url=jdbc:oracle:thin:@10.20.40.11:1521:orcl
kpidb.username=Ht3wPz6yEd=
kpidb.password=Qm7tRk2pLw=
同样的 Qm7tRk2pLw=,这次是不带注释、真正生效的数据库口令。也就是说,一个监控库和另一套库里被注释掉的那条连接,用的是同一个口令。
再看第 41 到 44 行:
# "|" is reserved for one key word in our system, please dom't use "|" in any account and password
alm.db.url=jdbc:oracle:thin:@10.20.40.11:1521:orcl|jdbc:oracle:thin:@10.20.40.11:1521:orcl
alm.db.username=Ht3wPz6yEd=|Bx4nVc9jTa=
alm.db.password=Qm7tRk2pLw=|Qm7tRk2pLw=
这里用 | 分隔了两套账号,一主一备。两边列的密码密文一模一样,两个不同账号共用同一个口令。
这段密文的加密方式从行为上能反推:同一个明文密码,在四个模块、三套账号、多份配置里,加密出来是同一个字符串。说明加密过程没有随机量参与,同样的输入永远得同样的输出。这类加密的弱点很明确——密文可以被当作口令来比对和复用。拿到 Qm7tRk2pLw= 的人不需要知道原文,只要目标系统的验证逻辑接受这个密文,就能进;如果验证逻辑拿密文去比密文,连解密都不用。
第 41 行那句注释是另一层信息。"|" is reserved for one key word in our system, please dom't use "|" in any account and password——系统内部用竖线做账号分隔符,所以口令里不许出现竖线。这句注释把口令的字符集限制也交代了。
一个 FTP 地址,账号和密码都在里面
alarmclient.properties 第 18 行:
filepath=ftp://ftpsvc:ftpsvc@10.20.30.66/share/proxy/test.txt|ftp://jack:jack@10.20.30.66/share/proxy/test.txt
两个 FTP 账号,ftpsvc/ftpsvc 和 jack/jack,用户名和密码完全一样,写在 URL 里。这行是文件服务器健康检查用的,配置里叫"监测文件服务器"。
FTP 的账号密码写进 URL,好处是简单,java.net.URL 直接就能连。代价是这行配置在四份复制品里各存一遍,日志、备份、工单附件里都可能有它。用户名等于密码这种事,用字典猜一遍就中。
同一个目录下的 grclient.properties 和 gr.properties,第 5 到 7 行:
# datastation server user
datastation.server.user = admin
# datastation server password
datastation.server.password = 3f9a1c7d24b8e05a6f13c9d7e20b4a8c56d1e9fb
账号 admin,密码是一串 40 位十六进制。40 位正好是 SHA-1 摘要的十六进制长度,我没有找到这个值的使用代码,无法确认它到底是 SHA-1 摘要还是某种自定义编码,不下结论。能确定的是它没有加盐的痕迹,同一个值在任何一份配置里都一致。
同一份配置,四个模块各存一份
datasource.properties 和 client.properties 这两个文件名,在下面四个模块里各出现一次:
alarm-web/resources/config/
portal-web/resources/config/
sms-web/resources/config/
number-web/resources/config/
我逐行比对过 datasource.properties,四份内容一致,包括注释掉的 Oracle 连接串和生效的 app/app。client.properties 第 48 行那个密文,四份也一样。
这意味着改一次数据库密码,要动四个模块的配置文件,重新打四个包。漏掉一个,那个模块连不上库,排查的时候还得先想明白"是网络问题还是有人漏改配置"。反过来,多存一份就多一个泄露点,仓库里任何一处被翻出去,口令就出去了。
配置本身没毛病,扫描工具够不着它才是毛病
这段我特意说清楚:这些配置文件的写法,在 2010 年那个语境下不算错。properties 是当年 Web 应用的标准配置方式,注释切换多环境是当时的常见做法,对称加密存口令在当时也是"做了安全处理"的正面动作。
问题在于十年之后,安全扫描的默认规则已经收敛到几个固定模式:yml 文件里的 password:、Java 里的 "secret"、.env。这套系统的配置落在规则之外。注释行更是在所有扫描规则的盲区里——扫描器通常只匹配未注释的有效配置,注释掉的行按噪音处理。
所以这套仓库交给依赖扫描平台跑,app/app 和 appuser/pwd01 这两组口令大概率一条告警都不会出。
对照表
有两处我没确认:一是 p6spy 的
spy.properties没找到,SQL 日志最终落在磁盘还是别处,我说不清;二是那串 40 位十六进制口令的使用代码没追到,SHA-1 只是按长度推测,不能当结论。用过这套系统的同行,评论区聊聊。
自查打卡:
跑一句:
git grep -nE "^\s*#" -- "*.properties" | grep -iE "password|passwd|pwd|jdbc:"这条命令专门找注释掉的连接信息。打在评论区,看看谁的配置文件里全是“被盗的密码”。。
老炮点评
这套配置里最该被重视的,是 Qm7tRk2pLw= 在五个地方重复出现。
明文口令的问题是显眼的,团队里谁都看得出 app/app 不妥。加密之后,从心理上这个问题就被结掉了——"已经加密了"。实际上这套加密出来的结果可复用、可跨库、可跨模块,拿到一个等于拿到全部。加密只改变了口令的存储形式,没有改变口令的复用结构。
只用一种做法就够用:把口令从仓库里全部拿出去,配置里只保留键名和占位符,值由部署时的环境变量或者配置中心注入。这件事在任何一代技术栈上都做得到,${DB_PASSWORD} 这种写法主流框架早十年前就支持,properties 文件也能用 System Property 覆盖。
对这套系统的现实建议,我按代价排了个顺序:FTP 那两行和 app/app 排第一,这四个值是直接可用的凭据,改配置加改密码,一天能完;四个模块的 config 目录合并成一份共用配置排第二,主要目的是减少重复;密文那部分要换加密方案就得连带改验证逻辑,量级按周算,老系统动它的收益和风险都得重新算一遍,放在后面做。
最后问一句
你维护的仓库里,有多少口令是"加密过的"?评论区说说你见过的加密方式,我这边见过最离谱的是 Base64 之后又反转一次字符串。
如果本文对你有帮助,欢迎:
👍 点赞 | ⭐ 收藏 | 👤 关注 | 💬 留言
欢迎留言探讨。你的每一次互动,都是我继续更新的动力🚀
下期预告:《iBATIS 2.3.4 考古:MyBatis 改名前的持久层长什么样》
还是这个仓库。两个老模块的数据访问层用的是 ibatis-sqlmap 2.3.4.726,没有 Mapper 接口,没有注解,SQL 全在 xml 的 sqlmap 里,代码里靠 SqlMapClient 点名调用。这套写法读起来什么样,跟今天的 MyBatis 差在哪,下一篇把 sqlmap 配置和调用代码拆开看。
我是老炮,18 年 Java 老兵,仍在一线。关注「Java老炮踩坑录」,真实项目复盘,让你少走弯路。