前言
在挖移动端的时候,通常会关注跨端方向的问题,因为我们能直接从webview容器去访问native的代码,从客户端角度,从前端能直接深入到客户端,所以跨端这块比较有研究意义。
本文详细介绍android前端到客户端跨端通信的基础知识以及相关漏洞挖掘思路。
基础知识
当我们要了解跨端(这里指前端和客户端跨端)的时候,通常需要掌握一些基础知识,如下:
1. 跨端
在移动端前端和客户端的跨端开发,通常是指使用一种技术或框架,同时在移动应用的前端和客户端开发中进行应用。这种跨端技术通常使用 Web 技术(如 HTML、CSS 和 JavaScript)来开发应用程序,然后通过 WebView 或类似的技术来集成到原生应用中。
在这种跨端开发中,前端开发者可以使用熟悉的 Web 技术来开发应用程序,而不需要学习和使用原生开发语言和工具。同时,客户端开发者可以使用原生开发语言和工具来集成前端开发的应用程序。
2. 常见的移动端前端和客户端的跨端技术
1. React Native:React Native 可以使用 Web 技术来开发应用程序,并通过 JavaScript Bridge 技术来与原生应用进行交互,从而实现跨端开发。
2. Flutter:Flutter 可以使用 Web 技术来开发应用程序,并通过 Flutter Engine 技术来与原生应用进行交互,从而实现跨端开发。
3. WebView:WebView 是一种原生控件,可以在原生应用中嵌入 Web 应用程序。前端开发者可以使用 Web 技术来开发应用程序,然后通过 WebView 技术来集成到原生应用中。
4. Hybrid:Hybrid 是一种结合了原生和 Web 技术的跨端开发模式,可以使用 Web 技术来开发应用程序,并使用原生技术来实现底层功能。
3.1 React Native - ReactContextBaseJavaModule
攻击者视角,我更关心前端如何调用客户端。
在 React Native 中,Native Modules 用于将原生功能封装成 JavaScript 可以调用的函数或方法。
在原生代码中,创建一个 Native Module,通过该模块可以调用客户端代码。例如,在 Android 平台上可以创建一个 Java 类,实现 ReactContextBaseJavaModule
接口,并在该类中定义一个可以被 JavaScript 调用的函数。示例代码如下
public class MyNativeModule extends ReactContextBaseJavaModule {
private Context mContext;
public MyNativeModule(ReactApplicationContext reactContext) {
super(reactContext);
mContext = reactContext;
}
@Override
public String getName() {
return "MyNativeModule";
}
@ReactMethod
public void showSessionID() {
Toast.makeText(mContext, getCookie(), Toast.LENGTH_SHORT).show();
}
}
3.2 React Native - ReactApplication
继承ReactApplication并重写getPackages函数就可以将我们3.1新建的module注册进来。
import com.example.MyNativeModule;
...
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.asList(
new MainReactPackage(),
new MyNativeModule()
);
}
};
}
3.3 React Native - 前端调用
前端就能直接通过事件触发nativemodule的showSessionID方法了。
import React from 'react';
import { TouchableOpacity, Text, NativeModules } from 'react-native';
const MyButton = () => {
const onPress = () => {
NativeModules.MyNativeModule.showSessionID();
};
return (
<TouchableOpacity onPress={onPress}>
<Text>Click me</Text>
</TouchableOpacity>
);
};
export default MyButton;
所以如果存在远程动态加载一些前端代码,或者存储类或者其他安全问题能够控制前端代码,那我们就能进一步调用注册进来的客户端代码,例如上述的sessionid。
4.1 webview - @JavascriptInterface
新建一个javascript接口
class MyNativeInterface {
@JavascriptInterface
public void showCookie() {
Toast.makeText(mContext, getcookie(), Toast.LENGTH_SHORT).show();
}
}
4.2 webview - addJavascriptInterface
把接口注册进webview容器
import android.webkit.WebView;
import android.webkit.WebViewClient;
...
public class MainActivity extends AppCompatActivity {
private WebView mWebView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = findViewById(R.id.webView);
mWebView.setWebViewClient(new MyWebViewClient());
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(new MyNativeInterface(), "MyNativeInterface");
}
}
4.3 webview - 前端调用
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebView Example</title>
</head>
<body>
<button onclick="window.MyNativeInterface.showcookie()">Click me</button>
</body>
</html>
webview的攻击比较丝滑,因为只要打开一个webview容器就可以有机会攻击注册进来的跨端接口。扫一扫、Deeplink、IM、搜索等等。
5. Hybrid
Hybrid 应用程序比普通的 WebView 应用程序更加复杂,需要在客户端代码中实现一些原生功能,例如调用系统 API、访问本地数据库等,一种开发模式,它结合了 Web 技术和原生技术,用于开发跨平台应用程序。但是实现原理就是webview。所以参考4.1~4.3即可。
6. Flutter
实现原理和React Native类似,继承MethodCallHandler定义channel并写入跨端方法->setMethodCallHandler注册channel到Flutter->前端调用跨端方法。
public class MyChannel implements MethodCallHandler {
private static final String CHANNEL_NAME = "com.example.my_channel";
private Context mContext;
private MyChannel(Context context) {
mContext = context;
}
public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME);
channel.setMethodCallHandler(new MyChannel(registrar.context()));
}
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
if (call.method.equals("showToast")) {
String message = call.argument("message");
Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
result.success(true);
} else {
result.notImplemented();
}
}
}
--------
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.plugin.common.MethodChannel;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL_NAME = "com.example.my_channel";
@Override
public void configureFlutterEngine(FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL_NAME)
.setMethodCallHandler(new MyChannel(this));
}
}
-----------
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
static const platform = const MethodChannel('com.example.my_channel');
void _showToast() async {
try {
await platform.invokeMethod('showToast', {'message': 'Hello, world!'});
} on PlatformException catch (e) {
print(e);
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Demo'),
),
body: Center(
child: RaisedButton(
child: Text('Click me'),
onPressed: _showToast,
),
),
),
);
}
}
实战演练
我们通过逆向APK,搜索webview容器是否注入了跨端方法,全局搜索@JavascriptInterface
然后找到的跨端方法分析出调用参数的结构,并筛选出所有敏感的方法,然后构造前端调用点,触发客户端代码即可。
例如 poc
<script>window.xxxx.yyyy(aaa,bbb);
后话
不同的应用厂商在跨端上面可能都会选择不同的解决方案,或者自己开发一些跨端框架,这时候可能需要逆向看代码逻辑才能往下走。另外跨端的调用往往也不是一帆风顺,开发者会增加各种限制,例如权限限制,访问域名限制等等,这个也需要进一步的绕过思路,这个还要慢慢发散思路和学习。