blog-frontend icon indicating copy to clipboard operation
blog-frontend copied to clipboard

X-Forwarded Headers

Open Caaalabash opened this issue 4 years ago • 0 comments

参考

这两篇文章, 无敌的

HTTP Headers and Classic Load Balancers

获取用户IP的正确姿势

补上一篇以前并没有真的看懂的

科普文:Node.js 安全攻防 - 如何伪造和获取用户真实 IP ?

X-Forwarded-For 和 REMOTE_ADDR 和 X-Real-IP

  • REMOTE_ADDR: 代表客户端的IP, 这个值是不可伪造的, 如果没有代理的话, 这个值就是用户实际的IP值, 如果用户打开了ShadowSocks, 那么这个值可能就是代理的IP地址

  • X-Forwarded-For: 非正式协议, 一般来说, 在请求转发到代理的时候代理会添加一个X-Forward-For头(这并不是默认做法), 第一个值可以用来表示真实ip, 但会被伪造

以下图中的二层反向代理为例:

当正常请求时, 应用服务器收到的请求头为:

{
  x-forwarded-for: "171.221.144.94, 172.17.0.1",
  x-real-ip: "171.221.144.94"
}

当使用Postman伪造一个请求时

// 伪造请求header
{
  x-forwarded-for: "1.1.1.1"
  x-real-ip: "1.1.1.1"
}

// 响应头
{
  x-forwarded-for: "1.1.1.1, 171.221.144.94, 172.17.0.1",
  x-real-ip: "171.221.144.94"
}

可以得出结论: X-Forwarded-For不应该被轻易被信任, X-Real-IP更为真实, 同时, 叫什么名字并不重要, 即便叫X-XXX-XXXX我想也没什么关系

不过得出这一条结论有如下的前提:

  • 统一接入层(可以理解为第一层代理)能够受开发者控制, 为你定制一个请求头, 下方的代码一样可以解决问题
proxy_set_header X-Forwarded-For $remote_addr;

更好的解决方案

在统一接入层无法为你定制服务时, 那就使用既有的约定: 在转发请求给下游服务时, 把请求代理的IP写入到X-Forwarded-For头中, 形成一个IP地址列

X-Forwarded-For: client, proxy1, proxy2

并且, 在应用服务器中, 明确在应用服务器前的代理服务器的数量, 例如, 在上面的例子中, 存在2个代理服务器, 分别会设置如下的X-Forwarded-For

fake ---- 用户伪造
client ---- 第一层代理服务器设置的真实IP
proxy1 ----- 第二层代理服务器设置的局域IP

只需要从后向前查找, 用户就无法通过伪造X-Forwarded-For来影响真实IP的获取了

X-Forwarded-Proto 和 cookie secure

还是这张图: 客户端和统一接入层之间采用https链接, 后面和应用服务器使用http链接, 那么, 如果应用服务器需要设置一个secure: truecookie, 如何实现?

参考: 代理背后的 Express

// 信任来自正面代理服务器的第n个中继段, 以此作为客户机
app.set('trust proxy', 2)

并且在统一接入层配置:

proxy_set_header X-Forwarded-Proto https;

X-Forwarded-Proto 含义

The X-Forwarded-Proto request header helps you identify the protocol (HTTP or HTTPS) that a client used to connect to your load balancer. Your server access logs contain only the protocol used between the server and the load balancer; they contain no information about the protocol used between the client and the load balancer. To determine the protocol used between the client and the load balancer, use the X-Forwarded-Proto request header. Elastic Load Balancing stores the protocol used between the client and the load balancer in the X-Forwarded-Proto request header and passes the header along to your server.

帮助你识别客户端和负载均衡服务器(第一层Nginx)之间的协议, 这样就可以正确设置Cookie了, 参考express-session源码

function issecure(req, trustProxy) {
  // socket is https server
  if (req.connection && req.connection.encrypted) {
    return true;
  }

  // do not trust proxy
  if (trustProxy === false) {
    return false;
  }

  // no explicit trust; try req.secure from express
  if (trustProxy !== true) {
    return req.secure === true
  }

  // read the proto from x-forwarded-proto header
  var header = req.headers['x-forwarded-proto'] || '';
  var index = header.indexOf(',');
  var proto = index !== -1
    ? header.substr(0, index).toLowerCase().trim()
    : header.toLowerCase().trim()

  return proto === 'https';
}

如果没有从X-Forwarded-Proto中找到https, 那么express-session不会设置cookie

    // set-cookie
    onHeaders(res, function(){
      if (!req.session) {
        debug('no session');
        return;
      }

      if (!shouldSetCookie(req)) {
        return;
      }

      // only send secure cookies via https
      if (req.session.cookie.secure && !issecure(req, trustProxy)) {
        debug('not secured');
        return;
      }

      if (!touched) {
        // touch session
        req.session.touch()
        touched = true
      }

      // set cookie
      setcookie(res, name, req.sessionID, secrets[0], req.session.cookie.data);
    });

Caaalabash avatar May 02 '20 08:05 Caaalabash