Middleware Path not blocking multiple forward slashes in url

Hello Traefik Community!
I've been using Traefik v2 for some time now for some small personal sites hosting wordpress.

I have a middleware rule that IP restricts /wp-admin /wp-login.php and /xmlrpc.php

Recently I discovered if someone requests //xmlrpc.php or //////xmlrpc.php it works and completely ignores my middleware. I removed the xmlrpc.php file completely from my wordpress installation and that fixed that but now they are hitting /////wp-login.php. Traefik doesnt seem to care how many foward slashes /// are in the url. If it is not exactly /wp-login.php, it lets it through.

Not sure if this is a bug, but is there a way to fix it?

My wordpress installation is screaming with bots and all sorts of stuff trying to get past my login page.

Sincerely,

alexv305

Dare to share your Traefik static and dynamic config?

I use file based configuration.

Here is my sample config for one of the wordpress sites and the middleware used.

I changed the white listed IPs from what I actually use and my domain names have been removed.

filename: mywebsite.yml 

http:
  routers:
    mywebsite-http:
      rule: "Host(`mywebsite.com`,`www.mywebsite.com`)"
      entryPoints:
      - "web"
      service: mywebsite

    mywebsite-https:
      rule: "Host(`mywebsite.com`,`www.mywebsite.com`)"
      entryPoints:
      - "websecure"
      tls:
        certresolver: "production"
        domains:
          - main: "mywebsite.com"
          - main: "www.mywebsite.com"
      service: mywebsite

    mywebsite-http-whitelist:
      rule: "Host(`mywebsite.com`,`www.mywebsite.com`) && Path(`/wp-admin/`,`/wp-login.php`,`/xmlrpc.php`)"
      entryPoints:
      - "web"
      middlewares:
      - test-ipwhitelist
      service: mywebsite

    mywebsite-https-whitelist:
      rule: "Host(`mywebsite.com`,`www.mywebsite.com`) && Path(`/wp-admin/`,`/wp-login.php`,`/xmlrpc.php`)"
      entryPoints:
      - "websecure"
      middlewares:
      - test-ipwhitelist
      tls:
        certresolver: "production"
        domains:
          - main: "mywebsite.com"
          - main: "www.mywebsite.com"
      service: mywebsite

  services:
    mywebsite:
      loadBalancer:
        servers:
        - url: http://192.168.0.100


filename: middlewares.yml

http:
  middlewares:
    test-ipwhitelist:
      ipWhiteList:
        sourceRange:
          - "192.168.0.50/32"
          - "192.168.0.51/32"

Why you have routers for web/http? Usually that is only redirected to websecure/https, see simple Traefik example.

You don’t need explicit domains when you use Host(). It’s only needed for wildcards.

Path() is the wrong selector, better use PathPrefix().

If your target service responds to multiple slashes (which I think is a security issue in general), then you might want to use RegEx (doc):

HostRegexp , PathPrefix , and Path accept an expression with zero or more groups enclosed by curly braces, which are called named regexps.

Final note: Traefik v3 will not allow multiple names in Host() in the future, use logical operators:

( Host() || Host() ) && ( PathPrefix() || PathPrefix() )

I'm having the exact same issue - were you able to find a resolution to this?

Share your full Traefik static and dynamic config, and docker-compose.yml if used.

Use PathPrefix() because Path() only works for exact matches, and for sure there are sub-paths (URLs) to the admin site.

I think that it doesn’t matter if some ////wp-admin request gets through, as long as Traefik and Wordpress are configured correctly.

Of course the Wordpress web server should not respond with the regular login page, but a 404 "Not found". Check your access log.

If it does respond with the login page, then the web server needs to be configured correctly, but that’s not something Traefik can fix.

Of course you can just use a Traefik path regex as workaround, but that would not fix the mis-configuration in the Wordpress web server. And it might have side effects, for example when some media has the string in its name.

After writing my "me too" I spent a bunch of time on this yesterday and I think I finally got it figured out. There were a couple of things going on:

First, my NGINX configuration was copy/pasted from a Wordpress Multisite (site I'm currently working on is a single site) and had the following code that I think was responsible for redirecting from //xmlrpc to /xmlrpc, which in this case I removed:

    map $uri $blogname{
        ~^(?P<blogpath>/[^/]+/)wp-content/(.*)       $blogpath ;
    }

    map $blogname $blogid{
        default -999;
    }

But that wasn't quite what I wanted, I wanted Traefik to not even pass anything through to those routes so I can apply this to similar Wordpress Multisite sites.

I was using a PathPrefix rule, which looked like:

- "traefik.http.routers.${SERVICE}_admin.rule=Host(`${DOMAIN}`) && PathPrefix(`/wp-admin`,`/admin`,`/wp-login.php`,`/xmlrpc`)"

This rule would indeed catch something with $DOMAIN/wp-admin, but only with exactly those routes, letting anything with a more complicated path through. Solution ended up being a REGEX path - REGEX is most definitely NOT my strong area, so had to do a lot of futzing around to get something that worked. But for reference and anyone else that might be struggling with a similar issue, here's what seems to be working for me:

- "traefik.http.routers.${SERVICE}_admin.rule=Host(`${DOMAIN}`) && PathPrefix(`/{path:(.*)(xmlrpc.php|wp-admin|wp-login|wp-login.php)}`)"

I now get "Forbidden" when I try to access xmlrpc.php, wp-login.php or wp-admin from none whitelisted IPs. And this morning when I check my logs there are no global hackers trying to connect. Will continue to monitor to see if they find another way around these rules.

Note that PathPrefix() with regex will be different in Traefik v3 (doc).

Alternatively, you could add something like && !PathPrefix(`//`).