Web安全漏洞SSRF介绍及解决方案
Web安全,我们的接口可能接触的比较多的是XSS和CSRF。由于工作原因,我在负责的内部服务中遇到了SSRF问题。我在这里记录一下学习过程和解决方案。 SSRF(Server-Side Request Forgery),即服务器端请求伪造,是攻击者构造的用于伪造服务器发起的请求的安全漏洞。通常,SSRF 攻击的目标是外部网络无法访问的内部系统。
SSRF 大多是由于服务器提供了从其他服务器应用程序检索数据的功能,并且没有对目标地址进行过滤或限制而造成的。例如,从指定URL获取网页上的文本内容、从指定地址加载图片、下载等。根据应用程序流程,攻击者可以利用应用程序所在的服务器发出攻击者请求的HTTP请求。想要做。该漏洞可用于发现生产网络中的服务。攻击者可以直接代理到内网,从而使攻击者绕过网络访问。检查一下,您可以下载未经授权的文件、直接访问内网甚至获取服务器凭据。
作者负责的内部Web应用程序有一个下载文件的接口/download。它接受一个 url 参数,该参数指向需要下载的文件的地址。应用程序向该地址发起请求,并将文件下载到应用程序位置。服务器,然后进行后续处理。出现问题。应用所在的服务器就成为了跳板。攻击者利用该接口获取内网权限,可以执行多种恶意操作。
SSRF造成的损害有:
- 可以对外网、服务器所在内网、本地进行端口扫描,获取部分服务的banner信息;
- 攻击内网或本地运行的应用程序(如溢出;struts2、sqli等);
- 使用文件协议读取本地文件等。
常见的解决方案有:
- 过滤返回信息。验证远程服务器对请求的响应是更简单的方法。如果Web应用程序要获取特定类型的文件,则在向用户显示返回结果之前,必须检查返回的信息是否符合标准;
- 发送错误信息,防止用户根据错误信息判断远程服务器。端口状态;
- 将请求的端口限制为常用的http端口,例如80、443、8080、8090;
- 内网IP白名单。防止应用程序被利用获取内网数据并对内网进行攻击;
- 禁用不必要的协议。仅允许 http 和 https 请求。可以防止file:///、gopher://、ftp://等引起的问题。
由于作者应用/download接口请求的文件地址比较固定,所以白名单采用IP方式。当然,笔者也了解了更全面的解决方案。下面是安全部门同事的想法:
- 协议限制(默认允许的协议是HTTP和HTTPS)、30x跳数(标准不允许30x跳数)、统一错误信息(默认不统一、统一错误信息)避免恶意攻击并根据错误信息进行判断)
- IP地址评估:
- 无法访问0.0.0.0/8、169.254.0.0/16、127.0.0.0/8和240.0。 0.0/4等保留网段
- 如果IP为10.0.0.0/8、172.16.0.0/12、192.168.0.0/16私网段,请求IP地址并确定响应 内容
是不是application/json
- 解决URL getter和URL parser不一致的方法是:解析完URL后,去掉user并提交RFC3986并组装根据实现的URLDen Node.js以上想法。处理SSRF漏洞的主函数版本号:
const dns = require('dns') const parse = require('url-parse') const ip = require('ip') const isReservedIp = require('martian-cidr').default const protocolAndDomainRE = /^(?:https?:)?\/\/(\S+)$/ const localhostDomainRE = /^localhost[\:?\d]*(?:[^\:?\d]\S*)?$/ const nonLocalhostDomainRE = /^[^\s\.]+\.\S{2,}$/ /** * 检查链接是否合法 * 仅支持 http/https 协议 * @param {string} string * @returns {boolean} */ function isValidLink (string) { if (typeof string !== 'string') { return false } var match = string.match(protocolAndDomainRE) if (!match) { return false } var everythingAfterProtocol = match[1] if (!everythingAfterProtocol) { return false } if (localhostDomainRE.test(everythingAfterProtocol) || nonLocalhostDomainRE.test(everythingAfterProtocol)) { return true } return false } /** * @param {string} uri * @return string * host 解析为 ip 地址 * 处理 SSRF 绕过:URL 解析器和 URL 获取器之间的不一致性 * */ async function filterIp(uri) { try { if (isValidLink(uri)) { const renwerurl = renewUrl(uri) const parseurl = parse(renwerurl) const host = await getHostByName(parseurl.host) const validataResult = isValidataIp(host) if(!validataResult) { return false } else { return renwerurl } } else { return false } } catch (e) { console.log(e) } } /** * 根据域名获取 IP 地址 * @param {string} domain */ function getHostByName (domain) { return new Promise((resolve, reject) => { dns.lookup(domain, (err, address, family) => { if(err) { reject(err) } resolve(address) }) }) } /** * @param {string} host * @return {array} 包含 host、状态码 * * 验证 host ip 是否合法 * 返回值 array(host, value) * 禁止访问 0.0.0.0/8,169.254.0.0/16,127.0.0.0/8,240.0.0.0/4 保留网段 * 若访问 10.0.0.0/8,172.16.0.0/12,192,168.0.0/16 私有网段,标记为 PrivIp 并返回 */ function isValidataIp (host) { if ((ip.isV4Format(host) || ip.isV6Format(host)) && !isReservedIp(host)) { if (ip.isPrivate(host)) { return [host, 'PrivIp'] } else { return [host, 'WebIp'] } } else { return false } } /** * @param {string} uri * @return {string} validateuri * 解析并重新组合 url,其中禁止'user' 'pass'组合 */ function renewUrl(uri) { const uriObj = parse(uri) let validateuri = `${uriObj.protocol}//${uriObj.host}` if (uriObj.port) { validateuri += `:${uriObj.port}` } if (uriObj.pathname) { validateuri += `${uriObj.pathname}` } if (uriObj.query) { validateuri += `?${uriObj.query}` } if (uriObj.hash) { validateuri += `#${uriObj.hash}` } return validateuri } 复制代码对于可能导致漏洞的主界面管理函数,由于逻辑不同,这里不再给出具体实现。但只要遵循上面建议的避免SSRF漏洞的原则,结合上面的特性,就可以大致完成。
最后一句话总结:永远不要相信用户输入!
作者:倪奎
链接:https://juejin.im/post/5c466988f265da615f778eda
来源:掘金
版权归作者所有。商业转载请联系作者获取授权。非商业转载请注明来源。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网
