blog-frontend
blog-frontend copied to clipboard
X-Forwarded Headers
参考
这两篇文章, 无敌的
HTTP Headers and Classic Load Balancers
补上一篇以前并没有真的看懂的
科普文: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: true
的cookie
, 如何实现?
参考: 代理背后的 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);
});