0

I'm developing a Security Scanner and therefore need to send HTTP requests which don't honor RFC specifications. However, golang is very strict to comply with these.

  1. Issue

I want to send a HTTP request which contains prohibited special characters such as "". For example: "Ill\egal": "header value" However, golang always throws the error: 'net/http: invalid header field name "Ill\egal"'.

This error is thrown on line 523 at https://go.dev/src/net/http/transport.go

  1. Issue

I want to send a single HTTP request which contains either two content-length, two transfer-encoding or one content-length & one transfer-encoding header (for HTTP request smuggling). Those need sometimes to have wrong values.

However, it isn't possible to set those headers oneself, they are generated automatically. So it's only possible to use one of these headers with a correct value.

I've bypassed this by using a Raw TCP Stream, however this solution isn't satisfying, as I can't use a proxy this way: Use Dialer with Proxy. Route TCP stream through Proxy

  1. Issue

I want to send a HTTP request where the header name is mixed upper and lowercase. E.g. "HeAdErNaMe": "header value". This is possible for HTTP 1 requests by writing directly to the header map (req.Header["HeAdErNaMe"] = []string{"header value"})

However for HTTP 2 requests the headers will still be capitalized to meet the RFC specifications.

LowkeyFlex
  • 21
  • 1
  • 6
    The only way is to not use net/http. `I've bypassed this by using a Raw TCP Stream, however this solution isn't satisfying, as I can't use a proxy this way` You surely can use proxies if you write code for that. – tkausl Jan 12 '22 at 07:59
  • 3
    You can't use a package designed to make well formed HTTP requests to make badly formed HTTP requests. You will need to write your own HTTP library to do this. – Bracken Jan 12 '22 at 12:14

1 Answers1

0

You can dump request into a buffer, modify the buffer (with regexp or replace), and send modified buffer to the host using net.Dial.

Example:

package main

import (
    "bufio"
    "crypto/tls"
    "fmt"
    "log"
    "net/http"
    "net/http/httputil"
    "strings"
)

func main() {

    // create and dump request

    req, err := http.NewRequest(http.MethodGet, "https://golang.org", nil)
    if err != nil {
        log.Fatal(err)
    }

    req.Header.Add("User-Agent", "aaaaa")

    buf, err := httputil.DumpRequest(req, true)
    if err != nil {
        log.Fatal(err)
    }

    // Corrupt request

    str := string(buf)
    str = strings.Replace(str, "User-Agent: aaaaa", "UsEr-AgEnT: aaa\"aaa", 1)
    println(str)

    // Dial and send raw request text

    conn, err := tls.Dial("tcp", "golang.org:443", nil)
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    fmt.Fprintf(conn, str)

    // Read response

    br := bufio.NewReader(conn)
    resp, err := http.ReadResponse(br, nil)
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("%+v", resp)
}
serge-v
  • 750
  • 2
  • 8