内容简介:1. Protobuf 是什么Protobuf 是 google 开源的一种平台无关、语言无关、可扩展且轻便高效的序列化数据结构的协议,可以用于网络通信和数据存储。
本文将介绍 Protobuf 协议在安卓 APP 中的应用,以及如何开展相关接口的安全测试。 文中用到的 vuls 漏洞应用代码及应用可以在https://github.com/AndroidAppSec/vuls/releases/tag/v4.1 下载。
前言
在对安卓 APP 的网络请求进行安全测试的时候,经常会遇到不同的数据传输协议,比如基于 XML 的、基于 JSON 的、甚至是基于二进制流的。本文将介绍一种常用的数据传输协议-- Protobuf 的安全测试方法 。
1. Protobuf 是什么
Protobuf 是 google 开源的一种平台无关、语言无关、可扩展且轻便高效的序列化数据结构的协议,可以用于网络通信和数据存储。
其具有体积小、序列化和传输速度快、维护简单的特点,被越来越多的安卓 APP 使用。
其目前最新的版本为 proto3,相较于之前的版本支持更多的语言,语法也更为简洁。本文主要是以 proto3 为讨论对象。
2. Protobuf 语法
Protobuf 数据结构文件 一般是以 .proto 结尾的文本文件。 拿官方文档中的例子来简单介绍一下。 以下是一个定义搜索请求消息的案例:
syntax = "proto3"; message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; }
文件的第一行指定了协议版本,如果不定义则默认为 proto2。这必须是文件的第一个非空的非注释行。
message 定义了 SearchRequest 消息,并指定了三个字段(以分号分割)。每一个字段中指定了字段数据类型、字段名称和字段编号。
更多内容请查看官方文档(https://developers.google.com/protocol-buffers/docs/proto3)。
3. 在 Android Studio 中使用 Protobuf
3.1 搭建开发环境
-
在 Project/build.gradle 中加入 protobuf 插件
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.6'
-
在 app/build.gradle 中加入如下配置,头部加上:
apply plugin: 'com.google.protobuf'
-
android{ } 中加入 :
sourceSets { main { // 定义 java 文件目录 java { srcDir 'src/main/java' } // 定义proto文件目录 proto { srcDir 'src/main/proto' } } }
-
android{ } 同级中加入 :
protobuf { //配置protoc编译器 protoc { artifact = 'com.google.protobuf:protoc:3.5.1' } //这里配置生成目录,编译后会在build的目录下生成对应的java文件 generateProtoTasks { all().each { task -> task.builtins { remove java } task.builtins { java {} } } } }
-
在 dependencies 中加入 protobuf 相关依赖
implementation 'com.google.protobuf:protobuf-java:3.6.1' implementation 'com.google.protobuf:protoc:3.6.1'
-
安装 Protobuf Support 插件
插件安装完成后需重启 Android Studio。
3.2 编写 proto 文件
这里我们打算用 protobuf 来实现登录的功能。
在 src/main 下建立 proto 文件夹,建立 LoginRequest.proto 文件,内容如下:
syntax = "proto3";
import public "Message.proto";
//登录请求结构体
message LoginRequest {
string username = 1;
string password = 2;
}
//登录响应结构体
message LoginResponse {
int32 code = 1;
Message msg = 2;
}
Message.proto 文件,内容如下:
syntax = "proto3";
// 返回消息体
message Message {
int32 id = 2;
string content = 1;
}
3.3 生成对应的 Java 文件
Sync Project 或者 Build-->Clean Project,会在 \app\build\generated\source\proto 中生成对应的 Java 文件。
4. 编写测试应用
4.1 编写安卓客户端 登录功能代码
将生成的 Java 文件拷贝到 src 目录下:
登录功能的主要代码如下:
public void login(View view){ username = ((EditText) findViewById(R.id.et_ac_username)).getText().toString(); password = ((EditText) findViewById(R.id.et_ac_password)).getText().toString(); String url = "http://192.168.8.233/pblogin"; final LoginRequestOuterClass.LoginRequest loginRequest = LoginRequestOuterClass.LoginRequest .newBuilder() .setUsername(username) .setPassword(password) .build(); OkHttpClient okHttpClient = new OkHttpClient.Builder().build(); RequestBody requestBody = RequestBody.create( MediaType.parse("application/pb"), loginRequest.toByteArray()); Request request = new Request.Builder().url(url).post(requestBody).build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(ProtoActivity.this, "请求失败", Toast.LENGTH_LONG).show(); } }); } @Override public void onResponse(Call call, Response response) throws IOException { ResponseBody body = response.body(); LoginRequestOuterClass.LoginResponse loginResponse = LoginRequestOuterClass.LoginResponse.parseFrom(body.bytes()); final int code = loginResponse.getCode(); MessageOuterClass.Message responseMsg = loginResponse.getMsg(); final int id = responseMsg.getId(); final String content = responseMsg.getContent(); runOnUiThread(new Runnable() { @Override public void run() { if (id == 1){ Toast.makeText(ProtoActivity.this, content, Toast.LENGTH_LONG).show(); }else { Toast.makeText(ProtoActivity.this, content + code, Toast.LENGTH_LONG).show(); } } }); } }); }
4.2 编写服务端代码
服务端使用 Springboot 框架,在 pom 文件中加入 protobuf 依赖:
<dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>3.6.1</version> </dependency>
Controller 逻辑代码如下:
@RequestMapping("/pblogin") public void login(HttpServletRequest request, HttpServletResponse response) throws IOException { request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8"); LoginRequestOuterClass.LoginRequest loginRequest = LoginRequestOuterClass.LoginRequest .parseFrom(request.getInputStream()); String username = loginRequest.getUsername(); String password = loginRequest.getPassword(); LoginRequestOuterClass.LoginResponse.Builder builder = LoginRequestOuterClass.LoginResponse.newBuilder(); MessageOuterClass.Message.Builder messageBuilder = MessageOuterClass.Message.newBuilder(); if ("admin".equals(username) && "12345".equals(password)){ builder.setCode(200); messageBuilder.setId(1); messageBuilder.setContent("登录成功!"); builder.setMsg(messageBuilder.build()); }else { builder.setCode(200); messageBuilder.setId(-1); messageBuilder.setContent("用户名或密码错误!"); builder.setMsg(messageBuilder.build()); } builder.build().writeTo(response.getOutputStream()); }
原生应用是指开发的安卓应用,H5 是指通过 WebView 加载的 web 应用。很多情况下原
5. 抓包测试
5.1 启动服务端
执行命令 java -jar server-1.1.jar 。
5.2 配置手机代理为 burp
5.3 登录
5.4 burp 抓包请求如下
以 hex 方式查看一下,就知道 请求与普通的 http 请求还是不同的,是基于二进制序列化的。
将请求发送到 repeter,然后修改参数,会报错,提示协议解析错误。
6. BlackBox Protobuf Burp Extension
6.1 介绍
nccgroup 开源了一个 burp 插件专门用来做 protobuf 协议的安全测试,叫做 BlackBox,地址 https://github.com/nccgroup/blackboxprotobuf。
6.2 安装
-
安装 Jpython
-
依次运行命令
git clone https://github.com/nccgroup/blackboxprotobuf
cd blackboxprotobuf
git submodule update --init
-
在 Burp 的 Extender 标签中安装插件
-
安装成功后会出现 “Protobuf Type Editor” 标签页
6.3 使用
插件安装成功之后,burp 再抓取到 protobuf 协议数据时候,会自动添加 protobuf 标签,提供浏览和修改请求数据的功能 。
注:
-
BlackBox Protobuf 插件是通过 HTTP 请求头的 Content-Type 来判断数据是否为 protobuf 协议的。也就是说如果要插件去自动分析 protobuf 协议,需要将对应的 Content-Type 值 加入到 BlackBox Protobuf 插件的分析列表中。可以在 blackboxprotobuf/blackboxprotobuf/burp/editor.py 中进行添加。
-
BlackBox Protobuf 插件可以在 Proxy 和 Repeater 中使用,但是在 Intruder 中无效。
7. 逆向 Protobuf 协议格式
对于 Protobuf 协议来说,只要能获取到一个数据结构体中的字段信息,就可以去生成相应的数据。字段信息包括: 字段数据类型、字段名称和字段编号。
使用 Jadx 打开 vuls 应用,找到对应的代码如下:
上面的红框中,可以获取到字段 password(string类型)和 username(string类型)。
上面的红框中,可以获取到字段 password 编码为 2,username编码为 1。
这样就可以还原出 LoginRequest 的请求结构体
//登录请求结构体 message LoginRequest { string username = 1; string password = 2; }
之后,就可以按照前面讲的自行生成对应的 java 类,然后编写程序进行安全测试。
8. 参考
https://github.com/huangdali/Android_ProtoBuf_Demo
https://developers.google.com/protocol-buffers/docs/overview
https://www.jianshu.com/p/2265f56805fa
https://developers.google.com/protocol-buffers/docs/proto3
https://github.com/nccgroup/blackboxprotobuf
https://www.kaifaxueyuan.com/basic/protobuf3.html
https://blog.csdn.net/kpioneer123/article/details/51491739
- End -
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Flutter 学习之路 - 测试(单元测试,Widget 测试,集成测试)
- itest(爱测试)接口测试&敏捷测试管理 7.7.7 发布,接口测试重大升级
- 性能测试vs压力测试vs负载测试
- SpringBoot | 第十三章:测试相关(单元测试、性能测试)
- 敏捷测试VS传统测试对比,6招玩转敏捷测试!
- itest(爱测试)接口测试&敏捷测试管理平台 8.1.0 发布
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。