在移动应用安全场景中,攻击者常借助 HTTP 代理(如 Burp、Charles) 或 VPN 隧道(如 Reqable、Clh、V2y) 实现抓包、重放、篡改流量。
因此,检测设备当前是否处于代理或 VPN 环境,是 APP 进行流量完整性防护的关键步骤。
代理(Proxy)
代理服务器充当中间人角色,在用户与目标网站之间中转请求与响应。
它可以隐藏真实 IP、绕过访问限制或加速网络请求。
但其安全性有限,数据可能未加密或被服务商窃取。
VPN(Virtual Private Network)
VPN 通过加密隧道将设备的所有网络流量封装传输至远程服务器,实现更强的隐私与安全保护。
VPN 会接管设备的网络路由,所有数据在离开设备前即被加密,可有效防止流量嗅探与中间人攻击。
/**
* 代理检测
* 可通过系统属性读取是否存在手动代理配置
* (如OkHttp默认配置)会遵循系统代理设置。
*/
fun detectProxyBySystemProperties(): Triple<Boolean, String?, Int?> {
val proxyHost = System.getProperty("http.proxyHost")
val proxyPort = System.getProperty("http.proxyPort")
val httpsProxyHost = System.getProperty("https.proxyHost")
val httpsProxyPort = System.getProperty("https.proxyPort")
val host = proxyHost ?: httpsProxyHost
val port = proxyPort ?: httpsProxyPort
return if (!host.isNullOrBlank() && !port.isNullOrBlank()) {
val portInt = port.toIntOrNull()
val isEnabled = portInt != null
Triple(isEnabled, host, portInt)
} else {
Triple(false, null, null)
}
}
/**
* 代理检测
* 更精确的方式是遍历当前所有网络接口,读取LinkProperties的代理信息:
* 典型特征:Wi‑Fi 手动代理通常能在此命中;蜂窝网络较少使用 HTTP 代理。
*/
fun detectLinkProxiesStrict(
connectionManager: ConnectivityManager,
networks: List<Network>
): List<String> {
val proxyList = mutableListOf<String>()
for (network in networks) {
val linkProperties = connectionManager.getLinkProperties(network)
linkProperties?.let {
val proxyInfo = it.httpProxy
proxyInfo?.let { proxy ->
val host = proxy.host
val port = proxy.port
proxyList.add("$host:$port")
}
}
}
return proxyList.distinct()
}
/**
* VPN检测
* Android 系统会在 VPN 活动时为网络标记TRANSPORT_VPN, 系统级检测,准确率高。
* 某些第三方 VPN 应用可能未正确上报传输能力。
*/
private fun isVpnActiveByCapabilities(context: Context): Boolean {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networks = cm.allNetworks?.toList().orEmpty()
if (networks.isNotEmpty()) {
for (nw in networks) {
val caps = cm.getNetworkCapabilities(nw) ?: continue
if (caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) return true
}
return false
} else {
val caps = cm.getNetworkCapabilities(cm.activeNetwork)
return caps?.hasTransport(NetworkCapabilities.TRANSPORT_VPN) == true
}
}
/**
* VPN检测
* 通过枚举网卡接口名(tun*、ppp*)判断是否存在虚拟隧道。
* 不依赖系统信号,可发现部分伪装型 VPN。
* 可能被自定义内核或 Root 工具隐藏。
*/
private fun isVpnActiveByInterfaces(): Pair<Boolean, List<String>> {
val suspicious = mutableListOf<String>()
try {
val en = NetworkInterface.getNetworkInterfaces()
while (en.hasMoreElements()) {
val ni = en.nextElement()
val name = ni?.name ?: continue
val lower = name.lowercase(Locale.getDefault())
if (lower.startsWith("tun") || lower.startsWith("ppp")) suspicious.add(name)
}
} catch (_: Throwable) {
}
return Pair(suspicious.isNotEmpty(), suspicious)
}