上个月,我盯上了一家做云办公的科技公司。外网防护滴水不漏,主流SRC已经半年没人挖出高危。准备放弃时,我无意中在Google Play看到了他们唯一对外公开的APP——包名是com.cloudwork.app。
灵感来了。一个小时后,我的手机桌面上多了一个叫“CloudWork内部调试版”的APP,连上的是他们办公网的一台Jenkins服务器。全程没扫端口、没打exp,只靠反复试了30多个包名。
一、Android包名是一把钥匙
每个Android APP都有唯一的包名,采用反向域名格式,比如com.example.myapp。为了方便管理,开发团队往往会制定一套内部命名规范:
生产版:
com.company.app内部测试版:
com.company.app.internal或com.company.app.debug或com.company.app.test特定团队版:
com.company.app.dev、com.company.app.qa、com.company.app.staging渠道定制版:
com.company.app.vendor、com.company.app.partner
这些内部APP通常不会上架应用商店,但并不代表它们无法被下载。因为开发团队要把APP分发给测试人员,最便捷的方式就是挂在内部下载页、Firebase App Distribution、微软App Center,甚至是直接放在公网的静态资源服务器上。而这些分发渠道,往往只靠包名就能猜出下载链接。
二、从零开始:如何枚举内部APP
第一步:寻找命名规律
先在应用商店或公开渠道找到目标公司的一个APP,记下包名。比如这次的目标公司,公开APP是com.cloudwork.app。那么它内部测试版很可能就是com.cloudwork.app.internal、com.cloudwork.app.test、com.cloudwork.app.debug。
有了基础包名,我用一个小脚本批量生成几十个变体:
base = "com.cloudwork.app" suffixes = ["internal", "debug", "test", "dev", "qa", "staging", "beta", "alpha", "pre", "release", "nightly", "snapshot", "sandbox", "demo", "trial", "canary", "feature", "fix", "hotfix", "patch"] for s in suffixes: print(f"{base}.{s}") # 也考虑在app前加后缀 for s in ["internal", "dev", "test"]: print(f"com.cloudwork.{s}")第二步:探测下载源
有了候选包名,上哪下载?直接拼凑常见分发平台的链接:
Firebase App Distribution:
https://appdistribution.firebase.google.com/testerapps/1:1234567890:android:hash/download?appId={包名}微软App Center:
https://install.appcenter.ms/orgs/{org}/apps/{appname}/releases直接APK托管:
https://mobile.company.com/app/{包名}.apk或https://static.company.com/android/{包名}/latest.apk内部Nexus/Artifactory:
https://nexus.company.com/repository/releases/com/company/app/
但对这个目标,他们用的是自建的OTA分发系统,域名是ota.cloudwork.com,下载链接格式为https://ota.cloudwork.com/apps/{包名}/latest.apk。这个域名在公网能正常访问,而且没有做任何访问控制。
我写了个Python脚本,把枚举出来的包名逐个拼接请求,看哪个返回200:
import requests ota_base = "https://ota.cloudwork.com/apps/{}/latest.apk" for pkg in package_list: url = ota_base.format(pkg) r = requests.head(url, timeout=5) if r.status_code == 200: print(f"[+] Found: {pkg} -> {url}")不到两分钟,命中了三个:com.cloudwork.app.internal、com.cloudwork.app.debug、com.cloudwork.app.dev。下载安装,果然都是该公司内部团队使用的APP。
三、内部APP里藏着什么?
这三款APP都没有加壳加固,反编译后大量敏感信息浮出水面。
1. 硬编码内网地址与凭证
在com.cloudwork.app.internal的strings.xml和BuildConfig里,直接写死了后端API的Base URL:http://192.168.10.53:8080/api/,以及WebSocket地址ws://jenkins.internal.cloudwork.com/ws。甚至还带了一组测试账号的用户名和密码。
显然,这个APP是给内部测试人员用的,为了方便调试,把内网服务接口和测试账号全写进了代码。测试人员拿着APP,在任何能访问公司Wi-Fi的设备上,就能直接连通内网服务。而在没有Wi-Fi的情况下,APP还会尝试通过一个VPN配置文件连接内网——这个VPN配置文件也同样被打包在APK的资源目录里。
2. 直接连通内网服务
我安装了这个APP,虽然无法连接它们的内网Wi-Fi,但通过查看反编译代码,拿到了内网服务器的SSH登录跳板机地址和另一组测试凭证。随后,我利用社会工程学手段获取了该公司一个员工邮箱,通过VPN配置文件里的网关地址,在授权的渗透测试框架下,成功接入了内网。
进入内网后,发现192.168.10.53是一台Jenkins服务器,上面运行着所有项目的CI/CD流水线。流水线脚本中又泄露了生产环境数据库的只读账号和GitLab私有仓库的Deploy Key。
3. 更多连锁反应
com.cloudwork.app.debug里包含了一个内部调试工具,可以查看所有员工的在线状态和内部聊天记录。com.cloudwork.app.dev里集成了一款面向开发者的日志查看器,能拉取线上业务系统的运行日志,其中不乏用户手机号和身份证号。
这些APP完全没有做混淆和加密,密钥、证书、测试数据全裸奔。而它们的下载入口,仅仅是一个公网可访问的OTA服务器,没有任何鉴权。
四、为什么2026年这种漏洞依然存在?
开发者对“不公开”的误解:认为只要不把APP上传应用商店,就算“隐藏”了。但任何公网可访问的URL,迟早会被发现。
自动化分发缺乏安全审计:DevOps文化下,测试包通过CI/CD自动打包、上传到分发平台,安全团队根本不知道有这些URL存在。
包名规律过于简单:为了方便,团队往往用
-internal、-debug等后缀,很容易被枚举。内部APP安全标准低:测试APP常关闭SSL验证、打印详细日志、携带调试接口,攻击者一旦拿到就如获至宝。
五、如何防御?
对于企业安全团队:
隐藏下载入口:OTA分发平台务必加上身份认证,哪怕是简单的HTTP Basic Auth,或者使用临时令牌。
包名随机化:内部测试APP采用无规律的包名后缀,避免被轻易枚举。例如使用UUID:
com.company.app.a1b2c3d4。最小权限原则:内部APP不应携带高权限凭证,API地址应使用公网域名而非内网IP,避免泄露内网拓扑。
代码混淆与加固:即使是测试包,也要做基本的混淆,移除硬编码密钥和测试数据。
定期扫描公开资产:使用Google Dork、GitHub监控等方式,定期排查是否有内部包名或下载链接泄露。
对于安全测试人员(白帽子):
信息收集阶段,重视目标公司的APP资产,尤其留意包名规律。
利用Firebase/App Center等平台的API批量枚举私有APP。
下载到测试包后,重点逆向分析
strings.xml、BuildConfig、AndroidManifest.xml、资源文件和SO库。关注硬编码的URL、Token、测试账号,以及VPN配置文件,这些往往是内网突破的钥匙。
六、写在最后
移动互联网时代,企业把越来越多的业务逻辑放进了APP,内部测试包更是成了“会走的密钥库”。攻击者根本不需要破解外网防火墙,只需猜对一个包名,就能把你的测试APP装到自己手机上,然后借里面的配置直捣内网。下次做SRC或渗透测试时,记得去看看这家公司有没有公开的APP——花几分钟枚举一下包名,你也许就能像我一样,连上他们内网的Jenkins,看到那些本来不该被看到的流水线。
严正声明
本文所述技术仅用于合法授权的安全测试。所有案例均已脱敏处理,仅保留技术原理供学习参考。未授权访问他人计算机系统、下载内部APP均属违法行为,与本文作者无关。请在SRC平台授权范围内进行测试。