史上最快漏洞制造机?AI Coding 把安全推向极限
Escape 安全研究团队分析了超过 5600 个使用 vibe coding 平台构建的应用,其中 Android 应用占比最高,发现 2000+ 个漏洞、400+ 个暴露的密钥以及 175 个 PII 泄露实例。
Vibe Coding 在 Android 开发中的应用
Vibe coding 平台让非 Android 开发者通过自然语言描述即可生成完整的 Android 应用代码。Cursor、Replit、Lovable 等工具可以直接生成 Kotlin/Java 代码、Gradle 配置甚至完整的 Android Studio 项目。
核心问题:AI 生成的 Android 代码通常能正常运行,但缺少基本的安全防护,导致 APK 被反编译后暴露大量敏感信息。
研究发现
Android 应用特有的安全问题
APK 反编译后暴露敏感信息
-
• 2000+ 个安全漏洞
-
• 400+ 个暴露的密钥和敏感数据
-
• 175 个包含 PII 的测试数据(手机号、身份证号等)
典型漏洞分布
-
• 组件导出配置错误:72%
-
• 不安全的数据存储:45%
-
• 网络通信未加密:32%
-
• WebView 安全配置错误:28%
-
• Intent 注入漏洞:15%
典型漏洞场景
场景 1:组件导出漏洞
// ❌ 错误做法:未正确配置组件导出
classAdminActivity : AppCompatActivity() {
overridefunonCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 直接执行敏感操作,未校验调用来源
val userId = intent.getStringExtra("user_id")
deleteUser(userId) // 任何应用都可以调用并删除用户
}
privatefundeleteUser(userId: String?) {
// 删除用户数据
database.delete("users", "id = ?", arrayOf(userId))
}
}
<!-- AndroidManifest.xml -->
<!-- ❌ 错误配置:Activity 可被任意外部应用启动 -->
<activity
android:name=".AdminActivity"
android:exported="true" /><!-- Android 12+ 必须显式声明 -->
攻击方式:
# 1. 使用 adb 直接启动导出的 Activity
adb shell am start -n com.example.app/.AdminActivity \
--es user_id "victim_user_123"
# 2. 使用 Drozer 检测导出组件
drozer console connect
dz> run app.package.attacksurface com.example.app
dz> run app.activity.info -a com.example.app
# 3. 恶意应用调用
# 任何已安装的应用都可以启动这个 Activity
正确做法:
<!-- AndroidManifest.xml -->
<!-- ✅ 方案 1:不导出敏感组件 -->
<activity
android:name=".AdminActivity"
android:exported="false" /><!-- 仅应用内部可访问 -->
<!-- ✅ 方案 2:使用自定义权限保护 -->
<permission
android:name="com.example.app.permission.ADMIN_ACCESS"
android:protectionLevel="signature" /><!-- 只有同签名应用可获取 -->
<activity
android:name=".AdminActivity"
android:exported="true"
android:permission="com.example.app.permission.ADMIN_ACCESS" />
// ✅ 方案 3:在代码中校验调用来源
class SecureAdminActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 校验调用来源
if (!isCallerTrusted()) {
Toast.makeText(this, "Unauthorized access", Toast.LENGTH_SHORT).show()
finish()
return
}
// 校验权限
if (!hasAdminPermission()) {
finish()
return
}
val userId = intent.getStringExtra("user_id")
if (isValidUserId(userId)) {
deleteUser(userId)
}
}
private fun isCallerTrusted(): Boolean {
// 检查调用来源包名
val callingPackage = callingActivity?.packageName ?: referrer?.host
return callingPackage == packageName // 只允许自己调用
}
private fun hasAdminPermission(): Boolean {
return checkCallingOrSelfPermission(
"com.example.app.permission.ADMIN_ACCESS"
) == PackageManager.PERMISSION_GRANTED
}
private fun isValidUserId(userId: String?): Boolean {
// 校验 userId 格式和权限
return userId != null && userId.matches(Regex("^[a-zA-Z0-9_]+$"))
}
}
场景 2:不安全的数据存储
// ❌ 错误做法:使用 SharedPreferences 存储敏感信息
classUserManager(context: Context) {
privateval prefs = context.getSharedPreferences("user_data", Context.MODE_PRIVATE)
funsaveLoginInfo(username: String, password: String, token: String) {
prefs.edit()
.putString("username", username)
.putString("password", password) // 明文存储密码
.putString("token", token) // 明文存储 Token
.apply()
}
}
攻击方式:
# Root 设备或备份提取
adb backup -f backup.ab com.example.app
ddif=backup.ab bs=24 skip=1 | openssl zlib -d > backup.tar
tar -xvf backup.tar
# 查看 SharedPreferences
cat apps/com.example.app/sp/user_data.xml
正确做法:
// ✅ 使用 EncryptedSharedPreferences
classSecureUserManager(context: Context) {
privateval masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
privateval securePrefs = EncryptedSharedPreferences.create(
context,
"secure_user_data",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
funsaveToken(token: String) {
securePrefs.edit()
.putString("token", token) // 加密存储
.apply()
}
// 注意:永远不要存储密码,即使是加密的
}
场景 3:不安全的网络通信
// ❌ 错误做法:禁用证书校验
classInsecureHttpClient {
funcreateClient(): OkHttpClient {
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
overridefuncheckClientTrusted(chain: Array<X509Certificate>, authType: String) {}
overridefuncheckServerTrusted(chain: Array<X509Certificate>, authType: String) {}
overridefungetAcceptedIssuers(): Array<X509Certificate> = arrayOf()
})
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, trustAllCerts, SecureRandom())
return OkHttpClient.Builder()
.sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
.hostnameVerifier { _, _ -> true } // 接受所有主机名
.build()
}
}
正确做法:
// ✅ 使用标准的证书校验
classSecureHttpClient {
funcreateClient(): OkHttpClient {
return OkHttpClient.Builder()
.certificatePinner(
CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build()
)
.build()
}
}
<!-- res/xml/network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-configcleartextTrafficPermitted="false">
<trust-anchors>
<certificatessrc="system" />
</trust-anchors>
</base-config>
<domain-config>
<domainincludeSubdomains="true">api.example.com</domain>
<pin-set>
<pindigest="SHA-256">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</pin>
</pin-set>
</domain-config>
</network-security-config>
场景 4:WebView 安全问题
// ❌ 错误做法:启用危险的 WebView 设置
classUnsafeWebViewActivity : AppCompatActivity() {
overridefunonCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val webView = WebView(this)
webView.settings.apply {
javaScriptEnabled = true
allowFileAccess = true// 允许访问文件系统
allowContentAccess = true
allowFileAccessFromFileURLs = true// 允许跨域访问
allowUniversalAccessFromFileURLs = true
}
// 直接加载用户提供的 URL,未做校验
val url = intent.getStringExtra("url")
webView.loadUrl(url ?: "")
// 暴露 Android 接口给 JavaScript
webView.addJavascriptInterface(object {
@JavascriptInterface
fungetToken() = getSharedPreferences("auth", MODE_PRIVATE)
.getString("token", "") // JavaScript 可读取 Token
}, "Android")
}
}
正确做法:
// ✅ 安全的 WebView 配置
classSecureWebViewActivity : AppCompatActivity() {
privateval allowedDomains = listOf("example.com", "trusted.com")
overridefunonCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val webView = WebView(this)
webView.settings.apply {
javaScriptEnabled = true
allowFileAccess = false
allowContentAccess = false
allowFileAccessFromFileURLs = false
allowUniversalAccessFromFileURLs = false
// 禁用调试模式
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(false)
}
}
// URL 白名单校验
webView.webViewClient = object : WebViewClient() {
overridefunshouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
val host = request.url.host ?: returntrue
return !allowedDomains.any { host.endsWith(it) }
}
}
// 不暴露敏感接口给 JavaScript
val url = intent.getStringExtra("url") ?: return
if (isUrlAllowed(url)) {
webView.loadUrl(url)
}
}
privatefunisUrlAllowed(url: String): Boolean {
returntry {
val host = Uri.parse(url).host ?: returnfalse
allowedDomains.any { host.endsWith(it) }
} catch (e: Exception) {
false
}
}
}
场景 5:Intent 注入漏洞
// ❌ 错误做法:未校验 Intent 数据
classDeepLinkActivity : AppCompatActivity() {
overridefunonCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 直接从 Intent 获取 URL 并打开
val url = intent.getStringExtra("url")
val webView = WebView(this)
webView.loadUrl(url ?: "") // 可被注入恶意 URL
// 直接从 Intent 获取文件路径
val filePath = intent.getStringExtra("file")
val file = File(filePath ?: "")
file.delete() // 可删除任意文件
}
}
正确做法:
// ✅ 严格校验 Intent 数据
classSecureDeepLinkActivity : AppCompatActivity() {
overridefunonCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 校验 Intent 来源
if (!isIntentFromTrustedSource(intent)) {
finish()
return
}
// 校验 URL 格式和域名
val url = intent.getStringExtra("url")
if (url != null && isValidUrl(url)) {
val webView = WebView(this)
webView.loadUrl(url)
} else {
Toast.makeText(this, "Invalid URL", Toast.LENGTH_SHORT).show()
finish()
}
}
privatefunisValidUrl(url: String): Boolean {
returntry {
val uri = Uri.parse(url)
uri.scheme in listOf("https") && // 只允许 HTTPS
uri.host?.endsWith("example.com") == true// 域名白名单
} catch (e: Exception) {
false
}
}
privatefunisIntentFromTrustedSource(intent: Intent): Boolean {
// 检查 Intent 的来源包名
val callingPackage = callingActivity?.packageName
return callingPackage in listOf("com.example.trusted")
}
}
<!-- AndroidManifest.xml -->
<activity
android:name=".SecureDeepLinkActivity"
android:exported="true">
<intent-filter>
<actionandroid:name="android.intent.action.VIEW" />
<categoryandroid:name="android.intent.category.DEFAULT" />
<categoryandroid:name="android.intent.category.BROWSABLE" />
<!-- 限定 scheme 和 host -->
<data
android:scheme="https"
android:host="example.com"
android:pathPrefix="/app" />
</intent-filter>
</activity>
为什么 AI 生成的 Android 代码不安全
1. AI 从不安全的历史代码学习
// AI 经常生成这种"能用但不安全"的代码
// 因为 Stack Overflow 上大量此类示例
classQuickHttpClient {
// 为了"快速解决 SSL 问题"而禁用证书校验
// 这在生产环境是严重的安全漏洞
}
Veracode 研究:45% 的 AI 生成 Android 代码存在安全缺陷。
2. 缺少 Android 特有的安全考虑
AI 通常不会主动添加:
-
• 组件导出的安全校验(最常见的漏洞)
-
• ProGuard/R8 混淆配置
-
• Root 检测
-
• 完整性校验(防止重打包)
-
• 调试模式检测
-
• 模拟器检测
典型 AI 错误:
// AI 生成的代码经常自动添加 android:exported="true"
// 因为这样"更方便",但却带来安全隐患
3. 用户提示不明确
❌ 弱提示:"创建一个登录功能"
结果:明文存储密码,HTTP 传输
✅ 强提示:"创建一个安全的登录功能,要求:
- 密码使用 EncryptedSharedPreferences 加密存储
- Token 存储在 Android Keystore
- 网络请求使用 HTTPS + Certificate Pinning
- 添加 ProGuard 规则保护关键类"
Android 安全防护清单
组件导出检查
# 1. 使用 Androguard 分析 AndroidManifest.xml
pip install androguard
androguard axml AndroidManifest.xml | grep -A 5 "exported"
# 2. 使用 Drozer 进行渗透测试
drozer console connect
dz> run app.package.attacksurface com.example.app
dz> run app.activity.info -a com.example.app -u
dz> run app.service.info -a com.example.app -u
dz> run app.provider.info -a com.example.app -u
# 3. 检查所有导出组件
adb shell dumpsys package com.example.app | grep -A 3 "Activity\|Service\|Receiver\|Provider"
// 运行时权限检查工具类
classComponentSecurityChecker(privateval context: Context) {
funcheckExportedComponents(): List<String> {
val vulnerabilities = mutableListOf<String>()
val packageInfo = context.packageManager.getPackageInfo(
context.packageName,
PackageManager.GET_ACTIVITIES or
PackageManager.GET_SERVICES or
PackageManager.GET_RECEIVERS or
PackageManager.GET_PROVIDERS
)
// 检查 Activities
packageInfo.activities?.forEach { activity ->
if (activity.exported && activity.permission.isNullOrEmpty()) {
vulnerabilities.add("Exported Activity without permission: ${activity.name}")
}
}
// 检查 Services
packageInfo.services?.forEach { service ->
if (service.exported && service.permission.isNullOrEmpty()) {
vulnerabilities.add("Exported Service without permission: ${service.name}")
}
}
return vulnerabilities
}
}
ProGuard/R8 混淆配置
# proguard-rules.pro
# 启用混淆
-dontoptimize
-dontpreverify
# 混淆关键类
-keep class com.example.app.model.** { *; }
# 移除日志
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
public static *** i(...);
}
# 字符串加密
-obfuscatecode
Root 检测
classRootChecker {
funisDeviceRooted(): Boolean {
// 检查常见的 Root 文件
val paths = arrayOf(
"/system/app/Superuser.apk",
"/system/xbin/su",
"/system/bin/su"
)
return paths.any { File(it).exists() } ||
checkRootManagementApps() ||
checkSuBinary()
}
privatefuncheckRootManagementApps(): Boolean {
val packages = arrayOf("com.topjohnwu.magisk", "eu.chainfire.supersu")
return packages.any { isPackageInstalled(it) }
}
privatefuncheckSuBinary(): Boolean {
returntry {
Runtime.getRuntime().exec("su").waitFor() == 0
} catch (e: Exception) {
false
}
}
}
应用完整性校验
classIntegrityChecker(privateval context: Context) {
// 检查签名是否被篡改
funverifySignature(): Boolean {
returntry {
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
context.packageManager.getPackageInfo(
context.packageName,
PackageManager.GET_SIGNING_CERTIFICATES
)
} else {
@Suppress("DEPRECATION")
context.packageManager.getPackageInfo(
context.packageName,
PackageManager.GET_SIGNATURES
)
}
val signatures = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
packageInfo.signingInfo.apkContentsSigners
} else {
@Suppress("DEPRECATION")
packageInfo.signatures
}
// 与预期的签名哈希比对
val currentSignature = signatures[0].toCharsString()
currentSignature == EXPECTED_SIGNATURE_HASH
} catch (e: Exception) {
false
}
}
companionobject {
privateconstval EXPECTED_SIGNATURE_HASH = "SHA256:abcd1234..."
}
}
调试和模拟器检测
classDebugChecker(privateval context: Context) {
funisDebuggable(): Boolean {
return (context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
}
funisEmulator(): Boolean {
return (Build.FINGERPRINT.startsWith("generic") ||
Build.FINGERPRINT.startsWith("unknown") ||
Build.MODEL.contains("google_sdk") ||
Build.MODEL.contains("Emulator") ||
Build.MODEL.contains("Android SDK") ||
Build.MANUFACTURER.contains("Genymotion") ||
Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic") ||
"google_sdk" == Build.PRODUCT)
}
}
Network Security Config
<!-- res/xml/network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- 禁用明文流量 -->
<base-configcleartextTrafficPermitted="false">
<trust-anchors>
<certificatessrc="system" />
</trust-anchors>
</base-config>
<!-- 域名配置和证书 Pinning -->
<domain-config>
<domainincludeSubdomains="true">api.example.com</domain>
<pin-setexpiration="2026-01-01">
<pindigest="SHA-256">base64EncodedPin1==</pin>
<pindigest="SHA-256">base64EncodedPin2==</pin>
</pin-set>
</domain-config>
<!-- Debug 配置(仅在 debug 构建中生效) -->
<debug-overrides>
<trust-anchors>
<certificatessrc="user" />
</trust-anchors>
</debug-overrides>
</network-security-config>
<!-- AndroidManifest.xml -->
<application
android:networkSecurityConfig="@xml/network_security_config">
</application>
开发流程建议
1. 代码审查
# 使用静态分析工具
./gradlew lint
./gradlew detekt
# 使用 Android Studio 的安全扫描
# Analyze -> Inspect Code
2. 依赖检查
// build.gradle.kts
plugins {
id("org.owasp.dependencycheck") version "8.4.0"
}
dependencyCheck {
formats = listOf("HTML", "JSON")
failBuildOnCVSS = 7.0f
}
3. APK 安全测试
# 使用 MobSF 进行自动化安全扫描
docker pull opensecurity/mobile-security-framework-mobsf
docker run -it -p 8000:8000 opensecurity/mobile-security-framework-mobsf
# 反编译检查
apktool d app-release.apk
jadx app-release.apk
# 检查组件导出配置
grep -r "android:exported" app-release/AndroidManifest.xml
grep -r "android:permission" app-release/AndroidManifest.xml
# 检查硬编码密钥
grep -r "api" app-release/
grep -r "key" app-release/
grep -r "token" app-release/
grep -r "password" app-release/
# 使用 Drozer 测试导出组件
drozer console connect
dz> run app.package.attacksurface com.example.app
dz> run app.activity.start --component com.example.app com.example.app.AdminActivity
总结
Vibe coding 让 Android 开发变得简单,但 AI 生成的代码在安全性上存在严重问题。超过 2000 个漏洞的研究表明,未经审查的 AI 代码会带来巨大风险。
Android 开发者必须注意:
-
• 组件导出是 Android 最常见的安全漏洞,必须谨慎配置
-
• APK 可被轻易反编译,任何硬编码信息都会暴露
-
• SharedPreferences 不是安全存储,使用 EncryptedSharedPreferences
-
• 网络通信必须使用 HTTPS + Certificate Pinning
-
• WebView 需要严格的安全配置
-
• Intent 数据必须校验,防止注入攻击
-
• ProGuard/R8 混淆是基本防护措施
更多推荐


所有评论(0)