Correct way of getting Client's IP Addresses from http.Request - http

What's the correct way to get all client's IP Addresses from http.Request? In PHP there are a lot of variables that I should check. Is it the same on Go?
One that I found is:
req.RemoteAddr
And is the request case sensitive? for example x-forwarded-for is the same as X-Forwarded-For and X-FORWARDED-FOR? (from req.Header.Get("X-FORWARDED-FOR"))

Looking at http.Request you can find the following member variables:
// HTTP defines that header names are case-insensitive.
// The request parser implements this by canonicalizing the
// name, making the first character and any characters
// following a hyphen uppercase and the rest lowercase.
//
// For client requests certain headers are automatically
// added and may override values in Header.
//
// See the documentation for the Request.Write method.
Header Header
// RemoteAddr allows HTTP servers and other software to record
// the network address that sent the request, usually for
// logging. This field is not filled in by ReadRequest and
// has no defined format. The HTTP server in this package
// sets RemoteAddr to an "IP:port" address before invoking a
// handler.
// This field is ignored by the HTTP client.
RemoteAddr string
You can use RemoteAddr to get the remote client's IP address and port (the format is "IP:port"), which is the address of the original requestor or the last proxy (for example a load balancer which lives in front of your server).
This is all you have for sure.
Then you can investigate the headers, which are case-insensitive (per documentation above), meaning all of your examples will work and yield the same result:
req.Header.Get("X-Forwarded-For") // capitalisation
req.Header.Get("x-forwarded-for") // doesn't
req.Header.Get("X-FORWARDED-FOR") // matter
This is because internally http.Header.Get will normalise the key for you. (If you want to access header map directly, and not through Get, you would need to use http.CanonicalHeaderKey first.)
Finally, "X-Forwarded-For" is probably the field you want to take a look at in order to grab more information about client's IP. This greatly depends on the HTTP software used on the remote side though, as client can put anything in there if it wishes to. Also, note the expected format of this field is the comma+space separated list of IP addresses. You will need to parse it a little bit to get a single IP of your choice (probably the first one in the list), for example:
// Assuming format is as expected
ips := strings.Split("10.0.0.1, 10.0.0.2, 10.0.0.3", ", ")
for _, ip := range ips {
fmt.Println(ip)
}
will produce:
10.0.0.1
10.0.0.2
10.0.0.3

Here a completely working example
package main
import (
// Standard library packages
"fmt"
"strconv"
"log"
"net"
"net/http"
// Third party packages
"github.com/julienschmidt/httprouter"
"github.com/skratchdot/open-golang/open"
)
// https://blog.golang.org/context/userip/userip.go
func getIP(w http.ResponseWriter, req *http.Request, _ httprouter.Params){
fmt.Fprintf(w, "<h1>static file server</h1><p><a href='./static'>folder</p></a>")
ip, port, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
//return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
fmt.Fprintf(w, "userip: %q is not IP:port", req.RemoteAddr)
}
userIP := net.ParseIP(ip)
if userIP == nil {
//return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
fmt.Fprintf(w, "userip: %q is not IP:port", req.RemoteAddr)
return
}
// This will only be defined when site is accessed via non-anonymous proxy
// and takes precedence over RemoteAddr
// Header.Get is case-insensitive
forward := req.Header.Get("X-Forwarded-For")
fmt.Fprintf(w, "<p>IP: %s</p>", ip)
fmt.Fprintf(w, "<p>Port: %s</p>", port)
fmt.Fprintf(w, "<p>Forwarded for: %s</p>", forward)
}
func main() {
myport := strconv.Itoa(10002);
// Instantiate a new router
r := httprouter.New()
r.GET("/ip", getIP)
// Add a handler on /test
r.GET("/test", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// Simply write some test data for now
fmt.Fprint(w, "Welcome!\n")
})
l, err := net.Listen("tcp", "localhost:" + myport)
if err != nil {
log.Fatal(err)
}
// The browser can connect now because the listening socket is open.
//err = open.Start("http://localhost:"+ myport + "/test")
err = open.Start("http://localhost:"+ myport + "/ip")
if err != nil {
log.Println(err)
}
// Start the blocking server loop.
log.Fatal(http.Serve(l, r))
}

This is how I come up with the IP
func ReadUserIP(r *http.Request) string {
IPAddress := r.Header.Get("X-Real-Ip")
if IPAddress == "" {
IPAddress = r.Header.Get("X-Forwarded-For")
}
if IPAddress == "" {
IPAddress = r.RemoteAddr
}
return IPAddress
}
X-Real-Ip - fetches first true IP (if the requests sits behind multiple NAT sources/load balancer)
X-Forwarded-For - if for some reason X-Real-Ip is blank and does not return response, get from X-Forwarded-For
Remote Address - last resort (usually won't be reliable as this might be the last ip or if it is a naked http request to server ie no load balancer)

In PHP there are a lot of variables that I should check. Is it the same on Go?
This has nothing to do with Go (or PHP for that matter). It just depends on what the client, proxy, load-balancer, or server is sending. Get the one you need depending on your environment.
http.Request.RemoteAddr contains the remote IP address. It may or may not be your actual client.
And is the request case sensitive? for example x-forwarded-for is the same as X-Forwarded-For and X-FORWARDED-FOR? (from req.Header.Get("X-FORWARDED-FOR"))
No, why not try it yourself? http://play.golang.org/p/YMf_UBvDsH

According to Mozilla MDN: "The X-Forwarded-For (XFF) header is a de-facto standard header for identifying the originating IP address of a client."
They publish clear information in their X-Forwarded-For article.

I think I have a better way than the current method posted.
package main
import (
"fmt"
"log"
"net"
"net/http"
"strings"
)
func main() {
http.HandleFunc("/", getUserIP)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal(err)
}
}
// Get the IP address of the server's connected user.
func getUserIP(httpWriter http.ResponseWriter, httpServer *http.Request) {
var userIP string
if len(httpServer.Header.Get("CF-Connecting-IP")) > 1 {
userIP = httpServer.Header.Get("CF-Connecting-IP")
fmt.Println(net.ParseIP(userIP))
} else if len(httpServer.Header.Get("X-Forwarded-For")) > 1 {
userIP = httpServer.Header.Get("X-Forwarded-For")
fmt.Println(net.ParseIP(userIP))
} else if len(httpServer.Header.Get("X-Real-IP")) > 1 {
userIP = httpServer.Header.Get("X-Real-IP")
fmt.Println(net.ParseIP(userIP))
} else {
userIP = httpServer.RemoteAddr
if strings.Contains(userIP, ":") {
fmt.Println(net.ParseIP(strings.Split(userIP, ":")[0]))
} else {
fmt.Println(net.ParseIP(userIP))
}
}
}

The client can set the X-Forwarded-For header to any arbitrary value it wants.
Usage X-Forwarded-For without check trusted proxies may lead to ip spoofing.
Header example: X-Forwarded-For: <client>, <proxy1>, <proxy2>
For example someone may call:
curl -H "X-Forwarded-For: 136.226.254.1" -H "X-Real-Ip: 136.226.254.2" "http://super.com"
If your L7 balancer don't check and cleanup this headers you will get ip spoofing in your code (136.226.254.1). If you have some logic based on client IP addresses, it won't work correctly. Throttling based on ip for example.
For example nginx module http://nginx.org/ru/docs/http/ngx_http_realip_module.html used logic based on getting last untrusted ip address in chain X-Forwarded-For addresses. I didn't find right middleware for go with same logic and write it: https://github.com/thrownew/go-middlewares/tree/main/clientip

When using Cloudfront the client IP address is in the x-original-forwarded-for. Below is a Javascript example.
function getIp(request) {
const { headers, connection, socket } = request
const connectionSocket = connection && connection.socket
return (
(headers && headers['x-original-forwarded-for']) ||
(connection && connection.remoteAddress) ||
(socket && socket.remoteAddress) ||
(connectionSocket && connectionSocket.remoteAddress) ||
null
)
}

Related

Go HTTP RoundTripper: Preventing Connection Reuse Based on Response

I have a use case where I want to use an HTTP client in Go with pooled connections (connection re-use), but with the special case where a connection is intentionally closed (not allowed for re-use) if a request on that connection returns a specific HTTP status code.
I've implemented a custom http.RoundTripper, which wraps an http.Transport, and can inspect the response status code. However, I can't seem to find a way to prevent the http.Transport from re-using that connection, without also preventing it from re-using any other connection.
Is this possible using the net/http package? If not, any suggested workaround for accomplishing this?
My current code looks something like this:
type MyTransport struct {
transport *http.Transport
}
func (mt *MyTransport) RoundTrip(req *http.Request) (*http.Response, error) {
resp, err := tt.transport.RoundTrip(req)
if err != nil {
return resp, err
}
if resp.StatusCode == 567 {
// HERE:
// Do something to prevent re-use of this connection
}
return resp, err
}

Golang ReverseProxy per host

I am trying to implement a Reverse Proxy in Go that proxies traffic to different hosts based on some tenant embedded in the URL. The implementation looks like this:
type Offloader struct {
tenantHostMap map[string]string // Map a tenant to its host:port
tenantProxyMap map[string](*httputil.ReverseProxy) // Map a tenant to its reverse proxy
}
func (o *Offloader) OnCreate() {
// Tenants Map
o.tenantHostMap = make(map[string]string)
o.tenantProxyMap = make(map[string]*httputil.ReverseProxy)
o.PopulateTenantHostMap()
// Rx
http.HandleFunc("/", o.ServeHTTP)
go http.ListenAndServe(":5555", nil)
}
// ServeHTTP is the callback that is called each time a Http Request is received.
func (o *Offloader) ServeHTTP(w http.ResponseWriter, req *http.Request) {
incomingUrl := req.URL.RequestURI()
tenant := o.GetTenantFromUrl(incomingUrl)
if proxy, ok := o.tenantProxyMap[tenant]; ok {
proxy.ServeHTTP(w, req)
}
if remoteHostAddr, ok := o.tenantHostMap[tenant]; ok {
remoteUrl, err := url.Parse(fmt.Sprintf("http://%s", remoteHostAddr))
if err != nil {
return
}
proxy := httputil.NewSingleHostReverseProxy(remoteUrl)
o.tenantProxyMap[tenant] = proxy
proxy.ServeHTTP(w, req) // non blocking
} else {
panic("Unknown Tenant")
}
}
When receiving a new HTTP request, I get the tenant from the URL. If this is the first time I am seeing this tenant I create a new ReverseProxy, otherwise I try to use the one I created before and stored in the tenantProxyMap.
When I test this, I get the following error:
2022/04/05 12:31:01 http: proxy error: readfrom tcp ****: http: invalid Read on closed Body
2022/04/05 12:31:01 http: superfluous response.WriteHeader call from net/http/httputil.(*ReverseProxy).defaultErrorHandler (reverseproxy.go:190)
If I create a new Reverse Proxy for each request rather than reusing the same proxy, the error doesn't happen.
I thought the proxy is per host and not per request (as the name suggests), so I am wondering why this error happens?
I know I need to protect the maps from concurrent reads/writes however that is irrelevant at the moment.
Thanks,
The problem is that in the scenario where a previous proxy already existed, you first pass the request on to that - and then still recreate the proxy, and again pass the request. In other words: you are making two proxied requests for each incoming request, when the tentantProxyMap is already populated for that tenant.
The ReverseProxy implementation closes the req.Body, so the second time you pass the request on to the proxy, it attempts reading from an already closed body. You're seeing the http: invalid Read on closed Body error as a result.
What you should try is to return after proxying the request, e.g. by adding a return:
if proxy, ok := o.tenantProxyMap[tenant]; ok {
proxy.ServeHTTP(w, req)
return
}

How to bypass golang's HTTP request (net/http) RFC compliance

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.
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
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
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.
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)
}

How to know proxy used by http client in given request

I'm doing some requests through some proxy servers. The function that defines which proxy url to use will choose randomly from a list of proxies. I would like to know for a given request, which proxy url is being used. As far as I know, when using a proxy server the http headers remain the same, but the tcp headers are the one that change.
Here's some code illustrating it (no error handling for simplicity):
func main() {
transport := &http.Transport{Proxy: chooseProxy}
client := http.Client{Transport: transport}
request, err := http.NewRequest(http.MethodGet, "https://www.google.com", nil)
checkErr(err)
// How to know here which proxy was used? Suppose the same client will perform several requests to different URL's.
response, err := client.Do(request)
checkErr(err)
dump, _ := httputil.DumpRequest(response.Request, false)
fmt.Println(dump)
}
func chooseProxy(request *http.Request) (*url.URL, error) {
proxies := []string{"proxy1", "proxy2", "proxy3"}
proxyToUse := proxies[rand.Intn(len(proxies))]
return url.Parse(proxyToUse)
}
I'm assuming that the Proxy function in the transport is called for each request even if the same client is used, as per the docs that say "Proxy specifies a function to return a proxy for a given Request". Am I right?
Some HTTP proxies add a Via header that tell who they are.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Via
You can modify your chooseProxy function so that it saves the proxy selected.
To do that, you can transform the chooseProxy func into a method of a type that will be used as storage for the information you want to keep:
type proxySelector string
func (sel *proxySelector) chooseProxy(request *http.Request) (*url.URL, error) {
proxies := []string{"proxy1", "proxy2", "proxy3"}
proxyToUse := proxies[rand.Intn(len(proxies))]
*sel = proxySelector(proxyToUse) // <-----
return url.Parse(proxyToUse)
}
func main() {
var proxy proxySelector
transport := &http.Transport{Proxy: proxy.chooseProxy} // <-----
client := http.Client{Transport: transport}
request, err := http.NewRequest(http.MethodGet, "https://www.google.com", nil)
checkErr(err)
// How to know here which proxy was used? Suppose the same client will perform several requests to different URL's.
response, err := client.Do(request)
checkErr(err)
dump, _ := httputil.DumpRequest(response.Request, false)
fmt.Println(dump)
fmt.Println("Proxy:", string(proxy)) // <-----
}
The request which contains the target URI is given as argument request to chooseProxy. So you can have the correct mapping already inside your chooseProxy function, all you need to to is check proxyToUse vs. request.URL there.
If you don't really trust the code that this mapping is actually done, then you need to look outside the code. For example you can look at the actual network traffic with Wireshark to see which proxy gets accessed.

How come the redis-benchmark command is not following the redis protocol?

I was reading in directly from a tcp connection after running the redis-benchmark command and as far as I can tell, redis-benchmark is NOT following the redis protocol.
The redis protocol is as stated in its website:
The way RESP is used in Redis as a request-response protocol is the
following:
Clients send commands to a Redis server as a RESP Array of Bulk Strings.
The server replies with one of the RESP types according to the command implementation.
Meaning that a correct client implementation must always send RESP arrays of bulk strings.
If that is true, then, anything that does not start with a * is considered a syntax error (since its not an RESP array).
Thus, if one were to send a ping command to a redis-server, then it must be sent as a resp array of length 1 with 1 bulk string containing the word ping. For example:
"*1\r\n$4\r\nPING\r\n"
However, whenever I listen directly to the redis-benchmark command and read its tcp connection I get instead:
"PING\r\n"
which does not follow the redis protocol. Is that a bug or is there something implied in the redis protocol that makes pings special? As far as I could tell I couldn't find anything that said that pings were special, nor that length 1 commands were special. Does someone know whats going on?
To see reproduce these results yourself you can copy my code to inspect it directly:
package main
import (
"fmt"
"log"
"net"
)
func main() {
RedisBenchmark()
}
func RedisBenchmark() {
url := "127.0.0.1:6379"
fmt.Println("listen: ", url)
ln, err := net.Listen("tcp", url) //announces on local network
if err != nil {
log.Fatal(err)
}
for {
conn, err := ln.Accept() //waits and returns the next connection to the listener
if err != nil {
log.Fatal(err)
}
tcpConn := conn.(*net.TCPConn)
go HandleConnection(tcpConn)
}
}
func HandleConnection(tcpConn *net.TCPConn) {
b := make([]byte, 256) //TODO how much should I read at a time?
n, err := tcpConn.Read(b)
if err != nil {
fmt.Println("n: ", n)
log.Fatal(err)
}
fmt.Printf("+++++> raw input string(b): %q\n", string(b))
msg := string(b[:n])
fmt.Printf("+++++> raw input msg: %q\n", msg)
}
and run it using go with:
go run main.go
followed on a different terminal (or tmux pane):
redis-benchmark
for all the test or if you only want to run ping with 1 client:
redis-benchmark -c 1 -t ping -n 1
you can see the details of how I am running it with the flags at: http://redis.io/topics/benchmarks
That is called an inline command. Check the Inline Commands section of the Redis Protocol article.
You can refer to the source code to find out the differences between inline command and RESP.
readQueryFromClient
|--> if command begins with * --> processInlineBuffer()process it as RESP
|
|--> if command not begins with * --> processMultibulkBuffer():process it as inline command
RESP is a more efficent way to parse the command for the Redis Server

Resources