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 混淆是基本防护措施

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐