add ip logging to jail pingback spammers

This commit is contained in:
Wouter Groeneveld 2021-04-13 18:01:43 +02:00
parent e6bd0ef669
commit 54018ecc42
5 changed files with 30 additions and 19 deletions

View File

@ -6,7 +6,6 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"golang.org/x/time/rate" "golang.org/x/time/rate"
"net/http" "net/http"
"strings"
"sync" "sync"
"time" "time"
) )
@ -77,7 +76,7 @@ func (rl *RateLimiter) cleanupVisitors() {
// with the help of https://www.alexedwards.net/blog/how-to-rate-limit-http-requests, TY! // with the help of https://www.alexedwards.net/blog/how-to-rate-limit-http-requests, TY!
func (rl *RateLimiter) limiterMiddleware(next http.Handler) http.Handler { func (rl *RateLimiter) limiterMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := rl.guessIp(r) ip := ipFrom(r)
limiter := rl.getVisitor(ip) limiter := rl.getVisitor(ip)
if limiter.Allow() == false { if limiter.Allow() == false {
@ -89,15 +88,3 @@ func (rl *RateLimiter) limiterMiddleware(next http.Handler) http.Handler {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
}) })
} }
func (rl *RateLimiter) guessIp(r *http.Request) string {
realIp := r.Header.Get("X-Real-IP")
forwardedFor := r.Header.Get("X-Forwarded-For")
if realIp != "" { // in case of proxy. is IP itself
return realIp
}
if forwardedFor != "" { // in case of proxy. Could be: clientip, proxy1, proxy2, ...
return strings.Split(forwardedFor, ",")[0]
}
return r.RemoteAddr // also contains port, but don't care
}

View File

@ -17,12 +17,14 @@ func (lrw *loggingResponseWriter) WriteHeader(code int) {
lrw.ResponseWriter.WriteHeader(code) lrw.ResponseWriter.WriteHeader(code)
} }
// Logs ip (for those pingback bad boys to put in jail), request url, method, and response status code.
func LoggingMiddleware(next http.Handler) http.Handler { func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logWriter := &loggingResponseWriter{w, http.StatusOK} logWriter := &loggingResponseWriter{w, http.StatusOK}
next.ServeHTTP(logWriter, r) next.ServeHTTP(logWriter, r)
log.Info(). log.Info().
Str("url", r.RequestURI). Str("url", r.RequestURI).
Str("ip", ipFrom(r)).
Str("method", r.Method). Str("method", r.Method).
Int("status", logWriter.statusCode). Int("status", logWriter.statusCode).
Msg("handled") Msg("handled")

View File

@ -6,6 +6,7 @@ import (
"brainbaking.com/go-jamming/common" "brainbaking.com/go-jamming/common"
"brainbaking.com/go-jamming/rest" "brainbaking.com/go-jamming/rest"
"encoding/xml" "encoding/xml"
"fmt"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -15,18 +16,18 @@ func HandlePost(conf *common.Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
pingbackError(w, "Unable to read request body", []byte("<not-parsaeble>")) pingbackError(w, fmt.Errorf("pingback POST: Unable to read body: %v", err))
return return
} }
rpc := &XmlRPCMethodCall{} rpc := &XmlRPCMethodCall{}
err = xml.Unmarshal(body, rpc) err = xml.Unmarshal(body, rpc)
if err != nil { if err != nil {
pingbackError(w, "Unable to unmarshal XMLRPC request body", body) pingbackError(w, fmt.Errorf("pingback POST: Unable to unmarshal XMLRPC %s: %v", body, err))
return return
} }
if !validate(rpc, conf) { if !validate(rpc, conf) {
pingbackError(w, "malformed pingback request", body) pingbackError(w, fmt.Errorf("pingback POST: malformed pingback request: %s", body))
return return
} }
@ -61,7 +62,7 @@ func pingbackSuccess(w http.ResponseWriter) {
} }
// according to the XML-RPC spec, always return a 200, but encode it into the XML. // according to the XML-RPC spec, always return a 200, but encode it into the XML.
func pingbackError(w http.ResponseWriter, msg string, body []byte) { func pingbackError(w http.ResponseWriter, err error) {
xml := `<?xml version="1.0" encoding="UTF-8"?> xml := `<?xml version="1.0" encoding="UTF-8"?>
<methodResponse> <methodResponse>
<fault> <fault>
@ -91,7 +92,7 @@ func pingbackError(w http.ResponseWriter, msg string, body []byte) {
</value> </value>
</fault> </fault>
</methodResponse>` </methodResponse>`
log.Error().Str("msg", msg).Str("xml", string(body)).Msg("Pingback receive went wrong") log.Error().Err(err).Msg("Pingback receive went wrong")
w.WriteHeader(200) w.WriteHeader(200)
w.Write([]byte(xml)) w.Write([]byte(xml))
} }

View File

@ -6,6 +6,14 @@ import (
"testing" "testing"
) )
func TestMarshallXmlSpamFromProductionWithMissingDecoderCharset(t *testing.T) {
xmlString := `<?xml version="1.0" encoding="utf-16" standalone="yes"?><methodCall><methodName>pingback.ping</methodName><params><param><value><string>https://teramassage.com/gwangju/</string></value></param><param><value><string>https://brainbaking.com/projects/</string></value></param></params></methodCall>`
var rpc XmlRPCMethodCall
err := xml.Unmarshal([]byte(xmlString), &rpc)
assert.EqualError(t, err, `xml: encoding "utf-16" declared but Decoder.CharsetReader is nil`)
}
// See https://www.hixie.ch/specs/pingback/pingback#refsXMLRPC // See https://www.hixie.ch/specs/pingback/pingback#refsXMLRPC
func TestMarshallValidXMLRPC(t *testing.T) { func TestMarshallValidXMLRPC(t *testing.T) {
xmlString := `<?xml version="1.0" encoding="UTF-8"?> xmlString := `<?xml version="1.0" encoding="UTF-8"?>

View File

@ -5,6 +5,7 @@ import (
"github.com/MagnusFrater/helmet" "github.com/MagnusFrater/helmet"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"brainbaking.com/go-jamming/common" "brainbaking.com/go-jamming/common"
@ -28,6 +29,18 @@ func (s *server) authorizedOnly(h http.HandlerFunc) http.HandlerFunc {
} }
} }
func ipFrom(r *http.Request) string {
realIp := r.Header.Get("X-Real-IP")
forwardedFor := r.Header.Get("X-Forwarded-For")
if realIp != "" { // in case of proxy. is IP itself
return realIp
}
if forwardedFor != "" { // in case of proxy. Could be: clientip, proxy1, proxy2, ...
return strings.Split(forwardedFor, ",")[0]
}
return r.RemoteAddr // also contains port, but don't care
}
func Start() { func Start() {
r := mux.NewRouter() r := mux.NewRouter()
config := common.Configure() config := common.Configure()