一、移动端WebView安全问题的三大核心来源

很多做混合开发的同学,都会用原生套WebView的方式开发App,比如把H5页面嵌入App里,既能快速开发,又能随时更新内容。但你可能没注意到,WebView藏着三个隐形的安全陷阱,一不小心就会导致用户数据泄露、App被恶意控制的问题。

1.1 JavaScript Bridge接口的风险

简单说,JS桥就是让H5页面能调用原生代码的通道,比如H5要获取用户的登录信息,就通过JS桥调用原生的接口。如果这个桥随便暴露,任何人都能调用,那恶意页面(比如你不小心加载了钓鱼链接里的WebView)就能直接获取你的token、密码这些敏感数据,后果不堪设想。比如之前有App因为JS桥没做校验,导致用户的银行卡信息被泄露的案例。

1.2 文件读写的风险

WebView允许H5读写本地文件,比如H5要缓存商品数据,就会用到文件读写。但如果不限制读写路径,恶意页面就能通过路径跳转(比如用../的方式),读取App里的私有文件,比如存储账号密码的配置文件,甚至读取系统的敏感数据,这相当于把你家的抽屉随便打开,任何人都能翻东西。

1.3 跨域问题的风险

WebView里的跨域,是指不同来源的页面互相访问的限制。比如H5页面要调用第三方的接口,或者和原生端通信,如果没做好跨域校验,恶意页面就能伪造合法页面的请求,也就是CSRF攻击,让用户在不知情的情况下提交订单、转账,或者窃取用户的操作数据。

二、三大问题对应的防范措施

针对上面的三个风险,我们分别做针对性的加固,每一步都有具体的代码示例,大家可以直接拿去用。

2.1 JavaScript Bridge接口的安全处理

核心思路是:只允许合法的来源调用桥接口,不允许任何其他页面的调用。原来的桥接口是直接把方法挂在window上,没有任何校验,现在我们加上来源校验,只有来自你预设的合法域名的请求,才允许调用。 这里统一用JavaScript作为技术栈(因为WebView的JS桥通常用JS实现):

// 安全的JS Bridge实现,核心增加来源校验逻辑
window.SafeNativeBridge = {
  // 1. 先定义合法的调用来源白名单,只有这些域名的页面才能调用桥接口
  allowedCallOrigins: [
    'https://your-app.com',
    'https://m.your-app.com'
  ],

  // 2. 暴露获取用户token的接口,增加校验
  getUserToken: function(callOrigin) {
    // 第一步:校验调用者的来源是否在白名单里
    if (!this.allowedCallOrigins.includes(callOrigin)) {
      console.warn('非法调用来源,拒绝获取用户token');
      // 非法来源返回空,不泄露数据
      return null;
    }
    // 来源合法,返回原生提供的token
    return window._nativeUserToken;
  },

  // 3. 其他需要暴露的接口,只暴露必要的,不要暴露所有原生方法
  submitOrder: function(orderData, callOrigin) {
    // 同样先校验来源,再处理业务
    if (!this.allowedCallOrigins.includes(callOrigin)) {
      console.warn('非法来源,拒绝提交订单');
      return false;
    }
    // 后续提交订单的逻辑
    return true;
  }
};

这里要注意,callOrigin是WebView在调用桥接口的时候自动传入的,用来标识发起调用的页面来源,不需要H5自己传,原生端会自动补充,这样就避免了H5伪造来源的问题。

2.2 文件读写的路径限制

核心思路是:只允许读写App的私有目录,禁止访问其他路径,防止路径遍历攻击。比如原来的代码可以读写任意路径,现在我们限制只能在指定的私有目录里读写,任何超出这个目录的路径都拒绝。 还是用JavaScript示例:

// 安全的本地文件读取函数,限制只能访问App私有目录
async function readAppFile(relativeFilePath) {
  // 1. 定义允许访问的根目录,这里是App的私有files目录(原生端指定的绝对路径)
  const ALLOWED_ROOT_DIR = '/data/user/0/com.your-app/files/';
  // 2. 拼接用户传入的相对路径,得到完整路径
  const fullPath = `${ALLOWED_ROOT_DIR}${relativeFilePath}`;
  // 3. 校验完整路径是否在允许的根目录内,防止路径遍历(比如../攻击)
  // 这里用startsWith判断,确保路径不会跳出允许的根目录
  if (!fullPath.startsWith(ALLOWED_ROOT_DIR)) {
    console.warn('非法文件路径,拒绝访问');
    return null;
  }
  try {
    // 4. 只有路径合法,才执行读取操作
    const fileResponse = await fetch(`file://${fullPath}`);
    if (!fileResponse.ok) {
      throw new Error('文件读取失败');
    }
    return await fileResponse.text();
  } catch (error) {
    console.error('读取文件出错:', error.message);
    return null;
  }
}

比如恶意页面想读取../shared_prefs/下的配置文件,拼接后的路径会跳出ALLOWED_ROOT_DIR,startsWith判断会失败,直接拒绝访问,这样就拦截了路径遍历攻击。

2.3 跨域通信的安全配置

核心思路是:明确指定通信的目标源,不使用通配符*,同时校验接收消息的来源,防止伪造消息。这里以WebView里常用的postMessage通信为例:

// 安全的跨域postMessage通信示例
// 1. 定义合法的消息来源白名单,用来接收消息时校验
const ALLOWED_MESSAGE_ORIGINS = [
  'https://your-app.com',
  'android-webview' // 原生WebView的来源标识
];
// 2. 定义允许发送消息的目标源,不能用*,只能是具体的域名
const ALLOWED_TARGET_ORIGIN = 'android-webview';

// 发送消息的方法,只发给指定的目标源
function sendToNative(messageData) {
  // 用postMessage发送消息,指定明确的目标源
  window.android.postMessage(messageData, ALLOWED_TARGET_ORIGIN);
}

// 接收消息的逻辑,必须校验来源
window.addEventListener('message', (event) => {
  // 第一步:校验消息的来源是否在白名单里
  if (!ALLOWED_MESSAGE_ORIGINS.includes(event.origin)) {
    console.warn('非法消息来源,拒绝处理');
    return;
  }
  // 来源合法,再处理具体的消息
  if (event.data.action === 'getUserInfo') {
    // 向来源页面返回数据,同样指定目标源
    event.source.postMessage({
      userId: 123,
      userName: '张三'
    }, event.origin);
  }
});

这里的关键是不用作为目标源,因为会允许任何页面接收你的消息,用具体的目标源就能确保只有合法的接收方能收到,同时接收方校验来源,确保消息是来自你信任的页面。

三、应用场景与技术选型细节

现在WebView的应用场景非常广,比如电商App的商品详情、内容App的资讯页面、教育App的课程页面,都是用WebView开发的混合应用。这种方案的优点是:H5开发成本低,更新快,不需要每次发版本,能快速迭代功能;缺点是:WebView的内核安全依赖系统的WebView版本,老旧版本会有漏洞,而且JS桥的暴露面如果没做好,很容易被攻击。 技术选型的时候,尽量用最新的WebView内核,比如安卓的Chrome WebView,iOS的WKWebView(比旧的UIWebView更安全),因为新内核修复了很多安全漏洞。另外,尽量减少JS桥的接口暴露,只暴露业务必须的接口,比如不要暴露原生的所有方法,只暴露几个核心的,比如获取用户信息、提交订单,其他的都隐藏,这样缩小攻击面。

四、注意事项与避坑指南

很多开发者容易踩的坑,这里列几个重点: 第一,不要用WebView加载未知的URL,比如用户分享的链接、第三方的外链,如果不确定是不是自己的域名,就不要在这个WebView里暴露JS桥,或者直接不加载这类URL。 第二,不要让WebView有写外部存储的权限,比如SD卡,恶意页面可以写入恶意脚本,或者窃取你的数据,只允许写App的私有目录,用完就删,不要留缓存。 第三,JS桥的接口要做签名校验,比如给每一个调用的请求加一个随机的token,原生端验证token是否合法,防止H5伪造调用,比如每次调用桥接口都带一个随机的nonce,原生端记录nonce,防止重放攻击。 第四,不要使用通配符*作为跨域的目标源,哪怕是测试环境,也要用具体的域名,避免线上出现漏洞。

五、总结

WebView的安全问题,本质是“暴露面”的控制,你暴露的接口越多,能被攻击的点就越多。针对JS桥,我们做来源校验,缩小调用范围;针对文件读写,限制路径,防止越权访问;针对跨域通信,明确目标源和来源校验,防止伪造。只要把这三个核心点做好,就能解决WebView90%以上的安全问题。另外,还要定期更新WebView内核,减少老旧版本的漏洞,尽量减少暴露的接口,只给需要的功能开权限,这样才能保障用户的数据安全,让你的App更稳定。