去你的DDoS——个人博客如何防御DD/CC攻击?ep.3

芜湖!我是Aiden,我又来水文章了(不是

喜闻乐见,我又被打了,攻击者下了狠手,流量高达150G(但是这并没有什么吊用)

0x01 攻击过程

能用图解决的不说话

攻击全过程(看不清楚的右键新窗口打开哈,见谅)

这个是CF的日志分析

我默默为攻击者感到不值得,150G流量想来也不便宜吧?但是有一说一,最高负载100%(就五分钟),攻击全程平均负载也就30%

反正我也是看傻了

1×01 对症下药

1×02 隐藏你的WordPress 特征

下载插件 Hide My WP

启用插件,输入邮箱以激活免费版。

选择LiteMode,然后跟着配置。

配置完成之后,建议截一张长图备份你的配置(免得以后忘了)

然后大概率会要求你加一条配置文件到对应网站的Nginx或者Apache的配置文件,非常重要,一定要加,不然可能会出现进不了后台什么的 。

重启Nginx/Apache,完事。

现在你的登陆和管理页面都不会是原本的地址了(当然你主题提供了登陆的话插件会自动重定向),并且插件已将你的WP的全部特征信息都抹去,有效避免被使用针对WP漏洞的攻击。

1×03 开启CloudFlare Workers 缓存

Workers 的缓存比一般的CF缓存加页面规则更有针对性,所以我们启用它。

找到你的CF管理域名的ZoneID和你的全局API Token,保存备用

然后我们登录到CF后台,找到Workers

点进去,随便起个名字新建一个Worker,然后进入编辑页面

复制以下代码到你的Worker 编辑页面

// IMPORTANT: Either A Key/Value Namespace must be bound to this worker script
// using the variable name EDGE_CACHE. or the API parameters below should be
// configured. KV is recommended if possible since it can purge just the HTML
// instead of the full cache.

// API settings if KV isn't being used
const CLOUDFLARE_API = {
  email: "你的邮箱", // From https://dash.cloudflare.com/profile
  key: "全局 API Token",   // Global API Key from https://dash.cloudflare.com/profile
  zone: "对应域名的Zone ID"   // "Zone ID" from the API section of the dashboard overview page https://dash.cloudflare.com/
};

// Default cookie prefixes for bypass
// 注意!!!!这里填写你的对应路径,若你没有修改过就保持这样,修改过了WP的评论等路径的在此更改对应的路径成为你的路径
const DEFAULT_BYPASS_COOKIES = [
  "wp-",
  "wordpress",
  "comment_",
  "woocommerce_",
  "comments_"
];

//后面的不动就行了。

/**
 * Main worker entry point. 
 */
addEventListener("fetch", event => {
  const request = event.request;
  let upstreamCache = request.headers.get('x-HTML-Edge-Cache');

  // Only process requests if KV store is set up and there is no
  // HTML edge cache in front of this worker (only the outermost cache
  // should handle HTML caching in case there are varying levels of support).
  let configured = false;
  if (typeof EDGE_CACHE !== 'undefined') {
    configured = true;
  } else if (CLOUDFLARE_API.email.length && CLOUDFLARE_API.key.length && CLOUDFLARE_API.zone.length) {
    configured = true;
  }

  // Bypass processing of image requests (for everything except Firefox which doesn't use image/*)
  const accept = request.headers.get('Accept');
  let isImage = false;
  if (accept && (accept.indexOf('image/*') !== -1)) {
    isImage = true;
  }

  if (configured && !isImage && upstreamCache === null) {
    event.passThroughOnException();
    event.respondWith(processRequest(request, event));
  }
});

/**
 * Process every request coming through to add the edge-cache header,
 * watch for purge responses and possibly cache HTML GET requests.
 * 
 * @param {Request} originalRequest - Original request
 * @param {Event} event - Original event (for additional async waiting)
 */
async function processRequest(originalRequest, event) {
  let cfCacheStatus = null;
  const accept = originalRequest.headers.get('Accept');
  const isHTML = (accept && accept.indexOf('text/html') >= 0);
  let {response, cacheVer, status, bypassCache} = await getCachedResponse(originalRequest);

  if (response === null) {
    // Clone the request, add the edge-cache header and send it through.
    let request = new Request(originalRequest);
    request.headers.set('x-HTML-Edge-Cache', 'supports=cache|purgeall|bypass-cookies');
    response = await fetch(request);

    if (response) {
      const options = getResponseOptions(response);
      if (options && options.purge) {
        await purgeCache(cacheVer, event);
        status += ', Purged';
      }
      bypassCache = bypassCache || shouldBypassEdgeCache(request, response);
      if ((!options || options.cache) && isHTML &&
          originalRequest.method === 'GET' && response.status === 200 &&
          !bypassCache) {
        status += await cacheResponse(cacheVer, originalRequest, response, event);
      }
    }
  } else {
    // If the origin didn't send the control header we will send the cached response but update
    // the cached copy asynchronously (stale-while-revalidate). This commonly happens with
    // a server-side disk cache that serves the HTML directly from disk.
    cfCacheStatus = 'HIT';
    if (originalRequest.method === 'GET' && response.status === 200 && isHTML) {
      bypassCache = bypassCache || shouldBypassEdgeCache(originalRequest, response);
      if (!bypassCache) {
        const options = getResponseOptions(response);
        if (!options) {
          status += ', Refreshed';
          event.waitUntil(updateCache(originalRequest, cacheVer, event));
        }
      }
    }
  }

  if (response && status !== null && originalRequest.method === 'GET' && response.status === 200 && isHTML) {
    response = new Response(response.body, response);
    response.headers.set('x-HTML-Edge-Cache-Status', status);
    if (cacheVer !== null) {
      response.headers.set('x-HTML-Edge-Cache-Version', cacheVer.toString());
    }
    if (cfCacheStatus) {
      response.headers.set('CF-Cache-Status', cfCacheStatus);
    }
  }

  return response;
}

/**
 * Determine if the cache should be bypassed for the given request/response pair.
 * Specifically, if the request includes a cookie that the response flags for bypass.
 * Can be used on cache lookups to determine if the request needs to go to the origin and
 * origin responses to determine if they should be written to cache.
 * @param {Request} request - Request
 * @param {Response} response - Response
 * @returns {bool} true if the cache should be bypassed
 */
function shouldBypassEdgeCache(request, response) {
  let bypassCache = false;

  if (request && response) {
    const options = getResponseOptions(response);
    const cookieHeader = request.headers.get('cookie');
    let bypassCookies = DEFAULT_BYPASS_COOKIES;
    if (options) {
      bypassCookies = options.bypassCookies;
    }
    if (cookieHeader && cookieHeader.length && bypassCookies.length) {
      const cookies = cookieHeader.split(';');
      for (let cookie of cookies) {
        // See if the cookie starts with any of the logged-in user prefixes
        for (let prefix of bypassCookies) {
          if (cookie.trim().startsWith(prefix)) {
            bypassCache = true;
            break;
          }
        }
        if (bypassCache) {
          break;
        }
      }
    }
  }

  return bypassCache;
}

const CACHE_HEADERS = ['Cache-Control', 'Expires', 'Pragma'];

/**
 * Check for cached HTML GET requests.
 * 
 * @param {Request} request - Original request
 */
async function getCachedResponse(request) {
  let response = null;
  let cacheVer = null;
  let bypassCache = false;
  let status = 'Miss';

  // Only check for HTML GET requests (saves on reading from KV unnecessarily)
  // and not when there are cache-control headers on the request (refresh)
  const accept = request.headers.get('Accept');
  const cacheControl = request.headers.get('Cache-Control');
  let noCache = false;
  if (cacheControl && cacheControl.indexOf('no-cache') !== -1) {
    noCache = true;
    status = 'Bypass for Reload';
  }
  if (!noCache && request.method === 'GET' && accept && accept.indexOf('text/html') >= 0) {
    // Build the versioned URL for checking the cache
    cacheVer = await GetCurrentCacheVersion(cacheVer);
    const cacheKeyRequest = GenerateCacheRequest(request, cacheVer);

    // See if there is a request match in the cache
    try {
      let cache = caches.default;
      let cachedResponse = await cache.match(cacheKeyRequest);
      if (cachedResponse) {
        // Copy Response object so that we can edit headers.
        cachedResponse = new Response(cachedResponse.body, cachedResponse);

        // Check to see if the response needs to be bypassed because of a cookie
        bypassCache = shouldBypassEdgeCache(request, cachedResponse);
      
        // Copy the original cache headers back and clean up any control headers
        if (bypassCache) {
          status = 'Bypass Cookie';
        } else {
          status = 'Hit';
          cachedResponse.headers.delete('Cache-Control');
          cachedResponse.headers.delete('x-HTML-Edge-Cache-Status');
          for (header of CACHE_HEADERS) {
            let value = cachedResponse.headers.get('x-HTML-Edge-Cache-Header-' + header);
            if (value) {
              cachedResponse.headers.delete('x-HTML-Edge-Cache-Header-' + header);
              cachedResponse.headers.set(header, value);
            }
          }
          response = cachedResponse;
        }
      } else {
        status = 'Miss';
      }
    } catch (err) {
      // Send the exception back in the response header for debugging
      status = "Cache Read Exception: " + err.message;
    }
  }

  return {response, cacheVer, status, bypassCache};
}

/**
 * Asynchronously purge the HTML cache.
 * @param {Int} cacheVer - Current cache version (if retrieved)
 * @param {Event} event - Original event
 */
async function purgeCache(cacheVer, event) {
  if (typeof EDGE_CACHE !== 'undefined') {
    // Purge the KV cache by bumping the version number
    cacheVer = await GetCurrentCacheVersion(cacheVer);
    cacheVer++;
    event.waitUntil(EDGE_CACHE.put('html_cache_version', cacheVer.toString()));
  } else {
    // Purge everything using the API
    const url = "https://api.cloudflare.com/client/v4/zones/" + CLOUDFLARE_API.zone + "/purge_cache";
    event.waitUntil(fetch(url,{
      method: 'POST',
      headers: {'X-Auth-Email': CLOUDFLARE_API.email,
                'X-Auth-Key': CLOUDFLARE_API.key,
                'Content-Type': 'application/json'},
      body: JSON.stringify({purge_everything: true})
    }));
  }
}

/**
 * Update the cached copy of the given page
 * @param {Request} originalRequest - Original Request
 * @param {String} cacheVer - Cache Version
 * @param {EVent} event - Original event
 */
async function updateCache(originalRequest, cacheVer, event) {
  // Clone the request, add the edge-cache header and send it through.
  let request = new Request(originalRequest);
  request.headers.set('x-HTML-Edge-Cache', 'supports=cache|purgeall|bypass-cookies');
  response = await fetch(request);

  if (response) {
    status = ': Fetched';
    const options = getResponseOptions(response);
    if (options && options.purge) {
      await purgeCache(cacheVer, event);
    }
    let bypassCache = shouldBypassEdgeCache(request, response);
    if ((!options || options.cache) && !bypassCache) {
      await cacheResponse(cacheVer, originalRequest, response, event);
    }
  }
}

/**
 * Cache the returned content (but only if it was a successful GET request)
 * 
 * @param {Int} cacheVer - Current cache version (if already retrieved)
 * @param {Request} request - Original Request
 * @param {Response} originalResponse - Response to (maybe) cache
 * @param {Event} event - Original event
 * @returns {bool} true if the response was cached
 */
async function cacheResponse(cacheVer, request, originalResponse, event) {
  let status = "";
  const accept = request.headers.get('Accept');
  if (request.method === 'GET' && originalResponse.status === 200 && accept && accept.indexOf('text/html') >= 0) {
    cacheVer = await GetCurrentCacheVersion(cacheVer);
    const cacheKeyRequest = GenerateCacheRequest(request, cacheVer);

    try {
      // Move the cache headers out of the way so the response can actually be cached.
      // First clone the response so there is a parallel body stream and then
      // create a new response object based on the clone that we can edit.
      let cache = caches.default;
      let clonedResponse = originalResponse.clone();
      let response = new Response(clonedResponse.body, clonedResponse);
      for (header of CACHE_HEADERS) {
        let value = response.headers.get(header);
        if (value) {
          response.headers.delete(header);
          response.headers.set('x-HTML-Edge-Cache-Header-' + header, value);
        }
      }
      response.headers.delete('Set-Cookie');
      response.headers.set('Cache-Control', 'public; max-age=315360000');
      event.waitUntil(cache.put(cacheKeyRequest, response));
      status = ", Cached";
    } catch (err) {
      // status = ", Cache Write Exception: " + err.message;
    }
  }
  return status;
}

/******************************************************************************
 * Utility Functions
 *****************************************************************************/

/**
 * Parse the commands from the x-HTML-Edge-Cache response header.
 * @param {Response} response - HTTP response from the origin.
 * @returns {*} Parsed commands
 */
function getResponseOptions(response) {
  let options = null;
  let header = response.headers.get('x-HTML-Edge-Cache');
  if (header) {
    options = {
      purge: false,
      cache: false,
      bypassCookies: []
    };
    let commands = header.split(',');
    for (let command of commands) {
      if (command.trim() === 'purgeall') {
        options.purge = true;
      } else if (command.trim() === 'cache') {
        options.cache = true;
      } else if (command.trim().startsWith('bypass-cookies')) {
        let separator = command.indexOf('=');
        if (separator >= 0) {
          let cookies = command.substr(separator + 1).split('|');
          for (let cookie of cookies) {
            cookie = cookie.trim();
            if (cookie.length) {
              options.bypassCookies.push(cookie);
            }
          }
        }
      }
    }
  }

  return options;
}

/**
 * Retrieve the current cache version from KV
 * @param {Int} cacheVer - Current cache version value if set.
 * @returns {Int} The current cache version.
 */
async function GetCurrentCacheVersion(cacheVer) {
  if (cacheVer === null) {
    if (typeof EDGE_CACHE !== 'undefined') {
      cacheVer = await EDGE_CACHE.get('html_cache_version');
      if (cacheVer === null) {
        // Uninitialized - first time through, initialize KV with a value
        // Blocking but should only happen immediately after worker activation.
        cacheVer = 0;
        await EDGE_CACHE.put('html_cache_version', cacheVer.toString());
      } else {
        cacheVer = parseInt(cacheVer);
      }
    } else {
      cacheVer = -1;
    }
  }
  return cacheVer;
}

/**
 * Generate the versioned Request object to use for cache operations.
 * @param {Request} request - Base request
 * @param {Int} cacheVer - Current Cache version (must be set)
 * @returns {Request} Versioned request object
 */
function GenerateCacheRequest(request, cacheVer) {
  let cacheUrl = request.url;
  if (cacheUrl.indexOf('?') >= 0) {
    cacheUrl += '&';
  } else {
    cacheUrl += '?';
  }
  cacheUrl += 'cf_edge_cache_ver=' + cacheVer;
  return new Request(cacheUrl);
}

然后进入你的CF对应域名的Workers管理,新加一条路径,按照这个来配置

自己配置一下,Route填写域名/*,Worker选择你刚刚新加的。

最后进入你的WP 后台,下载并且启用此插件 Cloudflare Page Cache

完事

1×04 添加防火墙规则 (防DDoS)

进入CF–你的域名–Firewall–Firewall Rules–Create a Firewall Rule

按这个配置来,名字不要学我。

1×05 添加Rate Limit 规则,防止CC

进入CF–你的域名–Firewall–Tools–Rate Limit,然后添加一条规则(一条足矣)

按这个配置,名字不要学我。

如果你不能添加(这个似乎需要CF Pro),加钱吧。

购买 CF Pro,单域名三十一年,支持全功能的WAF。

当然呢,你不怕跑路的话,在采用Plesk的面板下随便买一个空间(一块钱你也舍不得???) 绑定域名就完事了。点我购买

1×06 防止针对性攻击

你的对手诚心想搞死你,发现了你的网站还是WordPress,WHMCS,Phone CMS,etc…. 然后针对性进行攻击….然后你的VPS被打死了,网站后台被破了,用户流失,读者唾弃……

放心,这些都不会发生

只要你有了CF Pro,在CF–你的域名–Firewall–Managed Rules 启动对应的防火墙规则即可,只需动动鼠标即可挡住渗透攻击。

示例

0x03 设想

就在我写文章的时候,本博客还正在被攻击

这次他们学聪明了,专打我的登陆路径,IP也是很正规的DO和中国IP以避开防火墙规则,但是好在Rate Limit 挡住了大部分攻击。

虽然这次攻击对我来说屁事都没有(我都没感觉到,Nodequery都没给我发警告邮件),但是心里还是不舒服

所以我正在想办法静态化登录页,但是目前的操作经常导致无法登录

哎,还是姿势水平不够啊,需要一点人生经验。

以后再说吧

本系列,完结。

(除非那群闸总又想出了新花样来打我)

评论

  1. 365cent
    Windows Chrome

    大佬牛逼(破音

    4月前
    2020-4-14 5:20:07

发送评论 编辑评论


上一篇
下一篇