HTTP 认证

    HTTP 不像 SSH 具备完备的协议级别的认证及会话管理功能,常用的 HTTP 认证机制:

    1. BASIC 认证

    HTTP 1.0 定义的认证方式,认证步骤从略。BASIC 认证的问题主要有:

    • 明文传输,密码在非加密线路上极易被盗
    • 认证注销操作不够灵活

    Base64 不是加密手段,而是为将用户名 / 口令中不兼容的字符转换为 HTTP 协议兼容的字符集的编码方案。

    2. DIGEST 认证

    HTTP 1.1 提出了摘要认证,使用 质询 / 响应(Challenge / Response)方式:

    • 服务器返回 401 Authorization Required, 响应中首部字段包含 临时质询码(nonce 随机数)及 Request-URI 安全域字串(realm, 即 digestURI)
    • 客户端的认证响应计算公式 Response = MD5(HA1:nonce:HA2),其中 HA1=MD5(username:realm:password), HA2=MD5(method:digestURI)

    摘要认证的安全性说明:

    • 使用 MD5 加密是为了达成“不可逆的”,但如果没有加入随机数 nonce,容易遭到破解攻击(如彩虹表)
    • RFC 2617 引入了一些安全增强的选项,如质量保护(QoP),客户端随机数(clientNonce),以防止重放攻击
    • 可能遭受中间人攻击,即伪装服务器告知客户端使用基本认证或早期的 RFC 2069 摘要访问认证模式(安全级别弱);进一步而言,摘要访问认证没有提供帮助客户端验证服务器身份的机制

    3. 表单认证

    当前 Web 应用普遍采用基于表单的认证方式,将认证信息放到报文实体,POST 发送给服务器;服务器生成标识用户状态的 Session 信息,通过 Set-Cookie 告知客户端(指定 httponly 以避免 XSS),登陆会话绑定 IP 地址段 / UA 信息等用户特征,一定程度避免 Cookie 窃用。

    注:Session 也可以保存到 JS 中,但由于 JS 的执行上下文无法跨越浏览器 Tab,所以新建标签窗口后会丢失登陆状态。

    认证信息往往使用 username:nonce:password 的散列值结果进行传输,如 QQ 登陆加密模块(来源:https://segmentfault.com/q/1010000000926027

    function getEncryption(password, uin, vcode, isMd5) {
        var str1 = hexchar2bin(isMd5 ? password : md5(password));
        var str2 = md5(str1 + uin);
        var str3 = md5(str2 + vcode.toUpperCase());
        return str3;
    }
    

    md5(md5(md5(password) + uin) + vcode). 服务端保存密码的摘要以避免了明文保存;往往使用可逆的加密算法(如 AES)进行存储。认证时服务端进行同样的运算即可,验证码 vcode 的存在能够抵御重放攻击。

    注,上述解决的是 C/S 两端的安全性,链路的安全性也是一个严重问题,如 Session 窃取,链路劫持攻击(重置连接、页面替换、JS 注入、缓存投毒等)

    参考文章:

    一个衍生问题:如何在不安全链路上传输机密信息?

    // 原始信息 data=123
    http://www.test.com?data=123
    
    // 加密信息 data=xx 摘要结果 hash=yy
    http://www.test.com?data=xx&hash=yy
    

    简单思路(非完全可靠):首先将数据进行加密(防止窃听),对加密数据计算摘要(防止篡改)。这里要保护好散列函数,服务端负责计算确认 yy=digest(xx)

    Web 攻击

    按是否直接对目标 Web 应用发起攻击,可以分主动攻击、被动攻击。

    主动攻击SQL 注入OS 命令注入HTTP 首部注入攻击HTTP 响应截断攻击目录遍历等。以 OS 命令注入举例:对某创新路由的安全测试,引用文中部分内容:

    代码当中大量使用 fork_exec、os.execute、luci.sys.call、luci.util.execi 等来调用一些系统命令,这类函数在 PHP、JSP、.NET 代码审计中被列为危险函数,因为一旦过滤不严格,将用户输入的非法内容带入,将直接继承 WebServer 的权限来执行恶意系统指令(OpenWRT 中 WebServer 运行权限为 Root),攻击者可以借存在漏洞的接口实现提权。

    基于 XSS 或诱导用户点击,能够实现针对这类注入漏洞的远程攻击效果

    被动攻击:利用圈套策略(如欺诈邮件 / 链接)执行攻击代码,如 跨站脚本攻击(Cross-Site Scripting, XSS)及 跨站请求伪造(Cross-Site Request Forgeries, CSRF)

    1. 跨站脚本攻击(XSS)

    攻击者向 Web 页面中插入恶意 HTML 代码,当用户浏览该页时、嵌入的 HTML 代码即执行;漏洞通常源于缺乏对用户输入的有效编码过滤。XSS 本质上即一种 HTML 注入,和传统的缓冲区溢出(Buffer Overflow)是类似的思想——没有对数据和代码进行有效分离,导致控制流的劫持、即错误地把攻击者的数据当成代码指令执行。

    1.1 XSS 分类

    a. 反射型 XSS(Non-Persistent XSS)

    注入的脚本体现在一次独立的响应中,被浏览器执行。一般通过诱导 URL 点击(钓鱼攻击 Phishing),容易出现在错误页、搜索页等

    http://vulnerable.com?q=<script>alert(0)</script>
    

    现代浏览器会做一些主动防御,HTTP 头部的 X-XSS-Protection 即开启对反射型 XSS 特征的防御。

    参考例子:QQ邮箱反射型XSS漏洞一枚

    b. 存储型 XSS(Persistent XSS)

    攻击代码嵌入到网站数据库中,当其他用户访问时,攻击脚本动态执行。容易出现在评论留言、个人信息、文章发表。

    c. 基于 DOM 的 XSS(DOM Based XSS)

    基于用户输入的数据修改页面 DOM 节点导致的 XSS.

    详细阅读:

    1.2 XSS 防御

    输入(如 GET Query、表单提交、Cookie 数据、头部字段、数据库数据)进行验证及过滤,对输出(HTML、数据库写入)进行编码或转义,编码要针对输出语境(HTML / JavaScript / CSS)进行区分处理,如果处理不严密,可能会因为不同 Parser 执行的先后顺序、导致看似严密的编码形同虚设。

    关于 JavaScript 中的 escape / encodeURI / encodeURIComponent 区别:

    2. 跨站伪造攻击(CSRF)

    利用用户在已登录 Web 站点的登录态(信任凭据),执行恶意操作。

    比如路由器的 CSRF 漏洞,攻击者在恶意页面(或包含 XSS 漏洞的站点)加入一个 img 标签

    <img src="http://admin:admin@192.168.1.1/csrf.html?dns=8.8.8.8" />
    

    当页面被访问时,会发出 HTTP 请求至:http://admin:admin@192.168.1.1/csrf.html?dns=8.8.8.8(即修改路由器 DNS 或开启 DMZ 功能)

    CSRF 防御手段通常在请求中包含 Token / Referer 验证,避免攻击者构造出合法请求。