上一篇文章讲到了如何在本机环境和docker中去安装umami,这一篇讲一下如何防止umami脚本被屏蔽。
默认的代码如下:

<script async defer data-website-id="6b43b895-17fb-42d5-aacc-ef0841ca34c5" src="http://ip:3000/umami.js"></script>

有两种方法,但是都需要修改umami的脚本名。第一种是通过反代、worker、修改配置实现,第二种是重新混淆js实现,殊途同归。

通过umami配置文件修改脚本名

该方法需要重启umami来使其生效

在上一篇中创建的.env文件中增加一行TRACKER_SCRIPT_NAME=新脚本名称

如果是docker的话,需要通过docker ps定位到容器,然后执行docker attach 容器ID进入并找到.env文件进行修改(实际可能并不能进入到终端),或者是创建的时候使用-v参数将volume映射一份到本地目录。

227.png

修改完成后重启:pm2 restart umami

重启后效果如下:
229.png

但是dashboard中代码仍然显示的umami.js,我们换成自己定义的名称.js即可。

原因

在其umami/pages/_middleware.js代码中通过

process.env.TRACKER_SCRIPT_NAME获取变量从而决定track脚本的名称:

228.png

function customScriptName(req) {
  const scriptName = process.env.TRACKER_SCRIPT_NAME;

  if (scriptName) {
    const url = req.nextUrl.clone();
    const { pathname } = url;
    const names = scriptName.split(',').map(name => (name + '.js').trim());

    if (names.find(name => pathname.endsWith(name))) {
      url.pathname = '/umami.js';
      return NextResponse.rewrite(url);
    }
  }
}

通过反向代理重命名脚本名称

我们可以直接添加以下内容到伪静态

location /myjs {
    proxy_pass http://127.0.0.1:3000/umami.js;
}

然后访问/myjs即可,同理上步。

通过cloudflare worker进行http代理

此处就不以cloudflare为例了,我们使用百度云加速:

worker代码:

const ScriptName = '/umami.js';     //自定义脚本名称
const Endpoint = '/foo/bar';
const UmamiUrl = 'https://uaxk.com';  //UMAMI服务域名地址。
const corsHeaders = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET,HEAD,POST,OPTIONS',
    'Access-Control-Max-Age': '86400',
};
const ScriptWithoutExtension = ScriptName.replace('.js', '')
addEventListener('fetch', event => {
    event.passThroughOnException();
    event.respondWith(handleRequest(event));
})
async function handleRequest(event) {
    const pathname = new URL(event.request.url).pathname
    const [baseUri, ...extensions] = pathname.split('.')
    const clientIP = event.request.headers.get("CF-Connecting-IP")
    if (baseUri.endsWith(ScriptWithoutExtension)) {
        return getScript(event, extensions)
    } else if (pathname.endsWith(Endpoint)) {
        return postData(event)
    }
    return new Response(null, {status: 404})
}
async function getScript(event, extensions) {
    let response = await caches.default.match(event.request);
    if (!response) {
        response = await fetch(UmamiUrl +"/umami.js");
        var js = await response.text();
        js = js.replace("/api/collect", Endpoint);
        response = new Response(js, {
            headers: {
                ...response.headers,
                ...corsHeaders,
                'Access-Control-Allow-Headers': response.headers.get('Access-Control-Request-Headers'),
            },
        })
        event.waitUntil(caches.default.put(event.request, response.clone()));
    }
    return response;
}
async function postData(event) {
    const request = new Request(event.request);
    request.headers.delete('cookie');
    response = await fetch(UmamiUrl +"/api/collect", request);
    var js = await response.text();
    response = new Response(js, {
        headers: {
            ...response.headers,
            ...corsHeaders,
            'Access-Control-Allow-Headers': request.headers.get('Access-Control-Request-Headers'),
        },
    });
    return response;
}

231.png

232.png

由于百度云加速的限制,暂时无法验证是否可以使用,但是可以参考另外一篇博文,里面做了cloudflare的worker演示。

230.png

混淆js实现隐私防护屏蔽umami等统计

对于国内的一些隐私防护插件可以根据umami等统计脚本的代码特征进行识别从而屏蔽。

我们可以修改umami脚本后进行混淆,达到不被拦截的效果,当然,前提也是不要用很明显的二级、一级域名告诉别人这是统计用的。

原来的统计代码:

!function(){"use strict";var t=function(t,e,n){var a=t[e];return function(){for(var e=[],i=arguments.length;i--;)e[i]=arguments[i];return n.apply(null,e),a.apply(t,e)}},e=function(){var t=window.doNotTrack,e=window.navigator,n=window.external,a="msTrackingProtectionEnabled",i=t||e.doNotTrack||e.msDoNotTrack||n&&a in n&&n[a]();return"1"==i||"yes"===i};!function(n){var a=n.screen,i=a.width,r=a.height,o=n.navigator.language,c=n.location,s=c.hostname,u=c.pathname,l=c.search,d=n.localStorage,f=n.document,v=n.history,p=f.querySelector("script[data-website-id]");if(p){var m,g,h=p.getAttribute.bind(p),y=h("data-website-id"),w=h("data-host-url"),b="false"!==h("data-auto-track"),S=h("data-do-not-track"),k="false"!==h("data-css-events"),E=h("data-domains")||"",N=E.split(",").map((function(t){return t.trim()})),T=/^umami--([a-z]+)--([\w]+[\w-]*)$/,q="[class*='umami--']",A=function(){return d&&d.getItem("umami.disabled")||S&&e()||E&&!N.includes(s)},O=w?(m=w)&&m.length>1&&m.endsWith("/")?m.slice(0,-1):m:p.src.split("/").slice(0,-1).join("/"),j=i+"x"+r,L={},_=""+u+l,x=f.referrer,H=function(){return{website:y,hostname:s,screen:j,language:o,url:_}},R=function(t,e){return Object.keys(e).forEach((function(n){t[n]=e[n]})),t},J=function(t,e){A()||function(t,e,n){var a=new XMLHttpRequest;a.open("POST",t,!0),a.setRequestHeader("Content-Type","application/json"),g&&a.setRequestHeader("x-umami-cache",g),a.onreadystatechange=function(){4===a.readyState&&n(a.response)},a.send(JSON.stringify(e))}(O+"/api/collect",{type:t,payload:e},(function(t){return g=t}))},M=function(t,e,n){void 0===t&&(t=_),void 0===e&&(e=x),void 0===n&&(n=y),J("pageview",R(H(),{website:n,url:t,referrer:e}))},P=function(t,e,n,a){void 0===e&&(e="custom"),void 0===n&&(n=_),void 0===a&&(a=y),J("event",R(H(),{website:a,url:n,event_type:e,event_value:t}))},z=function(t){var e=t.querySelectorAll(q);Array.prototype.forEach.call(e,B)},B=function(t){(t.getAttribute("class")||"").split(" ").forEach((function(e){if(T.test(e)){var n=e.split("--"),a=n[1],i=n[2],r=L[e]?L[e]:L[e]=function(){"A"===t.tagName?function(t,e){var n=H();n.event_type=e,n.event_value=t;var a=JSON.stringify({type:"event",payload:n});navigator.sendBeacon(O+"/api/collect",a)}(i,a):P(i,a)};t.addEventListener(a,r,!0)}}))},C=function(t,e,n){if(n){x=_;var a=n.toString();(_="http"===a.substring(0,4)?"/"+a.split("/").splice(3).join("/"):a)!==x&&M()}};if(!n.umami){var D=function(t){return P(t)};D.trackView=M,D.trackEvent=P,n.umami=D}if(b&&!A()){v.pushState=t(v,"pushState",C),v.replaceState=t(v,"replaceState",C);var I=function(){"complete"===f.readyState&&(M(),k&&(z(f),new MutationObserver((function(t){t.forEach((function(t){var e=t.target;B(e),z(e)}))})).observe(f,{childList:!0,subtree:!0})))};f.addEventListener("readystatechange",I,!0),I()}}}(window)}();

整理一下:

!(function () {
  "use strict";
  var t = function (t, e, n) {
      var a = t[e];
      return function () {
        for (var e = [], i = arguments.length; i--; ) e[i] = arguments[i];
        return n.apply(null, e), a.apply(t, e);
      };
    },
    e = function () {
      var t = window.doNotTrack,
        e = window.navigator,
        n = window.external,
        a = "msTrackingProtectionEnabled",
        i = t || e.doNotTrack || e.msDoNotTrack || (n && a in n && n[a]());
      return "1" == i || "yes" === i;
    };
  !(function (n) {
    var a = n.screen,
      i = a.width,
      r = a.height,
      o = n.navigator.language,
      c = n.location,
      s = c.hostname,
      u = c.pathname,
      l = c.search,
      d = n.localStorage,
      f = n.document,
      v = n.history,
      p = f.querySelector("script[data-website-id]");
    if (p) {
      var m,
        g,
        h = p.getAttribute.bind(p),
        y = h("data-website-id"),
        w = h("data-host-url"),
        b = "false" !== h("data-auto-track"),
        S = h("data-do-not-track"),
        k = "false" !== h("data-css-events"),
        E = h("data-domains") || "",
        N = E.split(",").map(function (t) {
          return t.trim();
        }),
        T = /^umami--([a-z]+)--([\w]+[\w-]*)$/,
        q = "[class*='umami--']",
        A = function () {
          return (
            (d && d.getItem("umami.disabled")) ||
            (S && e()) ||
            (E && !N.includes(s))
          );
        },
        O = w
          ? (m = w) && m.length > 1 && m.endsWith("/")
            ? m.slice(0, -1)
            : m
          : p.src.split("/").slice(0, -1).join("/"),
        j = i + "x" + r,
        L = {},
        _ = "" + u + l,
        x = f.referrer,
        H = function () {
          return { website: y, hostname: s, screen: j, language: o, url: _ };
        },
        R = function (t, e) {
          return (
            Object.keys(e).forEach(function (n) {
              t[n] = e[n];
            }),
            t
          );
        },
        J = function (t, e) {
          A() ||
            (function (t, e, n) {
              var a = new XMLHttpRequest();
              a.open("POST", t, !0),
                a.setRequestHeader("Content-Type", "application/json"),
                g && a.setRequestHeader("x-umami-cache", g),
                (a.onreadystatechange = function () {
                  4 === a.readyState && n(a.response);
                }),
                a.send(JSON.stringify(e));
            })(O + "/api/collect", { type: t, payload: e }, function (t) {
              return (g = t);
            });
        },
        M = function (t, e, n) {
          void 0 === t && (t = _),
            void 0 === e && (e = x),
            void 0 === n && (n = y),
            J("pageview", R(H(), { website: n, url: t, referrer: e }));
        },
        P = function (t, e, n, a) {
          void 0 === e && (e = "custom"),
            void 0 === n && (n = _),
            void 0 === a && (a = y),
            J(
              "event",
              R(H(), { website: a, url: n, event_type: e, event_value: t })
            );
        },
        z = function (t) {
          var e = t.querySelectorAll(q);
          Array.prototype.forEach.call(e, B);
        },
        B = function (t) {
          (t.getAttribute("class") || "").split(" ").forEach(function (e) {
            if (T.test(e)) {
              var n = e.split("--"),
                a = n[1],
                i = n[2],
                r = L[e]
                  ? L[e]
                  : (L[e] = function () {
                      "A" === t.tagName
                        ? (function (t, e) {
                            var n = H();
                            (n.event_type = e), (n.event_value = t);
                            var a = JSON.stringify({
                              type: "event",
                              payload: n
                            });
                            navigator.sendBeacon(O + "/api/collect", a);
                          })(i, a)
                        : P(i, a);
                    });
              t.addEventListener(a, r, !0);
            }
          });
        },
        C = function (t, e, n) {
          if (n) {
            x = _;
            var a = n.toString();
            (_ =
              "http" === a.substring(0, 4)
                ? "/" + a.split("/").splice(3).join("/")
                : a) !== x && M();
          }
        };
      if (!n.umami) {
        var D = function (t) {
          return P(t);
        };
        (D.trackView = M), (D.trackEvent = P), (n.umami = D);
      }
      if (b && !A()) {
        (v.pushState = t(v, "pushState", C)),
          (v.replaceState = t(v, "replaceState", C));
        var I = function () {
          "complete" === f.readyState &&
            (M(),
            k &&
              (z(f),
              new MutationObserver(function (t) {
                t.forEach(function (t) {
                  var e = t.target;
                  B(e), z(e);
                });
              }).observe(f, { childList: !0, subtree: !0 })));
        };
        f.addEventListener("readystatechange", I, !0), I();
      }
    }
  })(window);
})();

我们可以看到,每次请求前都是O + '/api'进行的URL拼接,所以我们找到变量O的位置进行修改即可。

O = w
          ? (m = w) && m.length > 1 && m.endsWith("/")
            ? m.slice(0, -1)
            : m
          : p.src.split("/").slice(0, -1).join("/"),

我们将这里的O改成我们自己的umami域名,成了这样:

!(function () {
  "use strict";
  var t = function (t, e, n) {
      var a = t[e];
      return function () {
        for (var e = [], i = arguments.length; i--; ) e[i] = arguments[i];
        return n.apply(null, e), a.apply(t, e);
      };
    },
    e = function () {
      var t = window.doNotTrack,
        e = window.navigator,
        n = window.external,
        a = "msTrackingProtectionEnabled",
        i = t || e.doNotTrack || e.msDoNotTrack || (n && a in n && n[a]());
      return "1" == i || "yes" === i;
    };
  !(function (n) {
    var a = n.screen,
      i = a.width,
      r = a.height,
      o = n.navigator.language,
      c = n.location,
      s = c.hostname,
      u = c.pathname,
      l = c.search,
      d = n.localStorage,
      f = n.document,
      v = n.history,
      p = f.querySelector("script[data-website-id]");
    if (p) {
      var m,
        g,
        h = p.getAttribute.bind(p),
        y = h("data-website-id"),
        w = h("data-host-url"),
        b = "false" !== h("data-auto-track"),
        S = h("data-do-not-track"),
        k = "false" !== h("data-css-events"),
        E = h("data-domains") || "",
        N = E.split(",").map(function (t) {
          return t.trim();
        }),
        T = /^umami--([a-z]+)--([\w]+[\w-]*)$/,
        q = "[class*='umami--']",
        A = function () {
          return (
            (d && d.getItem("umami.disabled")) ||
            (S && e()) ||
            (E && !N.includes(s))
          );
        },
        O = 'https://www.dwt.life',
        j = i + "x" + r,
        L = {},
        _ = "" + u + l,
        x = f.referrer,
        H = function () {
          return { website: y, hostname: s, screen: j, language: o, url: _ };
        },
        R = function (t, e) {
          return (
            Object.keys(e).forEach(function (n) {
              t[n] = e[n];
            }),
            t
          );
        },
        J = function (t, e) {
          A() ||
            (function (t, e, n) {
              var a = new XMLHttpRequest();
              a.open("POST", t, !0),
                a.setRequestHeader("Content-Type", "application/json"),
                g && a.setRequestHeader("x-umami-cache", g),
                (a.onreadystatechange = function () {
                  4 === a.readyState && n(a.response);
                }),
                a.send(JSON.stringify(e));
            })(O + "/api/collect", { type: t, payload: e }, function (t) {
              return (g = t);
            });
        },
        M = function (t, e, n) {
          void 0 === t && (t = _),
            void 0 === e && (e = x),
            void 0 === n && (n = y),
            J("pageview", R(H(), { website: n, url: t, referrer: e }));
        },
        P = function (t, e, n, a) {
          void 0 === e && (e = "custom"),
            void 0 === n && (n = _),
            void 0 === a && (a = y),
            J(
              "event",
              R(H(), { website: a, url: n, event_type: e, event_value: t })
            );
        },
        z = function (t) {
          var e = t.querySelectorAll(q);
          Array.prototype.forEach.call(e, B);
        },
        B = function (t) {
          (t.getAttribute("class") || "").split(" ").forEach(function (e) {
            if (T.test(e)) {
              var n = e.split("--"),
                a = n[1],
                i = n[2],
                r = L[e]
                  ? L[e]
                  : (L[e] = function () {
                      "A" === t.tagName
                        ? (function (t, e) {
                            var n = H();
                            (n.event_type = e), (n.event_value = t);
                            var a = JSON.stringify({
                              type: "event",
                              payload: n
                            });
                            navigator.sendBeacon(O + "/api/collect", a);
                          })(i, a)
                        : P(i, a);
                    });
              t.addEventListener(a, r, !0);
            }
          });
        },
        C = function (t, e, n) {
          if (n) {
            x = _;
            var a = n.toString();
            (_ =
              "http" === a.substring(0, 4)
                ? "/" + a.split("/").splice(3).join("/")
                : a) !== x && M();
          }
        };
      if (!n.umami) {
        var D = function (t) {
          return P(t);
        };
        (D.trackView = M), (D.trackEvent = P), (n.umami = D);
      }
      if (b && !A()) {
        (v.pushState = t(v, "pushState", C)),
          (v.replaceState = t(v, "replaceState", C));
        var I = function () {
          "complete" === f.readyState &&
            (M(),
            k &&
              (z(f),
              new MutationObserver(function (t) {
                t.forEach(function (t) {
                  var e = t.target;
                  B(e), z(e);
                });
              }).observe(f, { childList: !0, subtree: !0 })));
        };
        f.addEventListener("readystatechange", I, !0), I();
      }
    }
  })(window);
})();

最后使用在线工具混淆一下:

  • https://www.sojson.com/
  • https://tool.lu/js

我们需要调试,所以选择tool.lu,

234.png

新建一个u.js,保存加密的js

235.png

再创建一个页面引用js:

236.png

最后效果:

233.png

因为我用的博客域名,实际换成umami即可。

237.png