0%

「Web安全」篇

「Web安全」篇

扫盲Web安全领域的主流漏洞,深入了解漏洞的产生原理以及对应的防御方法。

浏览器

浏览器机制

同源策略

  • 不同域的客户端脚本在没明确授权的情况下,不能读写对方的资源。

  • 同一个域的定义

    • 协议相同
    • 域名相同
    • 端口相同
  • 如果在未授权的情况下发起一个跨源请求,将会被浏览器拦截,并报错:CORS头缺少'Access-Control-Allow-Origin',也就是缺少必要的HTTP头。浏览器实际上是拦截了跨源请求的响应。

  • 虽然有同源策略在,但是是可以协商的。只要被请求的一方用某种方式允许了你的访问,就可以突破同源策略的限制。

  • 业务上常见的允许跨源的例子

    • jsonp
      jsonp的本质是跨源的对方给你一段JavaScript脚本让你执行,而script标签的src属性是允许跨源的,所以达到了跨源的目的。
    • CORS
      CORS的原理是被请求的服务端获取到发出请求方的请求后,给响应头加上AccessControl-Allow-Origin相关信息,浏览器就会对响应放行,让对方拿到数据。
    • WebSocket
      WebSocket是一种通信协议,使用ws://(非加密) 和wss://(加密) 作为协议前缀。该协议不实行同源策略,只要服务器支持,就可以通过它进行跨源通信。

浏览器渲染机制

  • HTML、CSS、JS的解析顺序
    HTML > CSS > JS
  • 浏览器的解码顺序
    HTML > URL > JS

浏览器渲染页面到屏幕上的主流程

浏览器安全策略

CSP内容安全策略(Content Security Policy)

  • 什么是CSP?
    为了缓解很大一部分潜在的跨站脚本问题,浏览器的拓展程序引入了CSP的概念。这将引入一些非常严格的安全策略,会使拓展程序在默认情况下更加安全。开发者可以创建并强制应用一些规则,管理网站加载的内容。简单来说,我们的网站只接受我们指定的资源。
  • CSP的意义
    等同防XSS等攻击的利器。CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。CSP 大大增强了网页的安全性。攻击者即使发现了漏洞,也没法注入脚本,除非还控制了一台列入了白名单的可信主机。
  • CSP的使用方法
    • 在HTTP Header上使用(首选)
      • Content-Security-Policy:"策略"
      • Content-Security-Policy-Report-Only:"策略"
    • 在HTML上使用
      • <meta http-equiv="content-security-policy" content="策略">
      • <meta http-equiv="content-security-policy-report-only" content="策略">

虽然CSP可以控制的策略非常多,但在实际应用中很少有企业能把它设置正确。

Cookie安全

  • 主要是domain、path、secure和HttpOnly的组合

iframe沙盒

  • iframe沙盒框架是对常规<iframe>表现行为的拓展
  • 它能让顶级页面对其嵌入的子页面及这些子页面的子资源设置一些额外的限制
  • 通过设置<iframe>的sandbox属性实现限制

flash安全沙箱

  • Flash的安全沙箱其实和同源策略有关,主要是针对跨源请求的,需要在被请求的站点下的crossdomain.xml路径下用xml的格式输出允许跨源请求的站点。
  • 现在flash逐渐被淘汰,对于这个策略,了解即可。

OWASP

  • 什么是OWASP?

OWASP(Open Web Application Security Project)即「开放式Web应用程序安全项目」。是一个开源的、非盈利的全球性安全组织,主要致力于应用软件的安全研究。组织使命是使应用软件更加安全,使企业和组织能够对应用安全风险作出更清晰的决策。

主要成果是共同推动了安全标准、安全测试工具、安全指导手册等应用安全技术的发展。OWASP被视为Web应用安全领域的权威。

OWASP TOP 10

OWASP TOP 10被用来分类网络安全漏洞的严重程度,目前被许多漏洞奖励平台和企业安全团队引用。

这个列表总结了Web应用程序最可能、最常见、最危险的十大漏洞,IT公司和开发团队可以利用这个列表来规范应用程序开发流程和测试流程,提高Web产品的安全性。

基本所有的Web漏洞扫描都会被要求满足OWASP TOP10的检测,同时相关企业在招聘Web安全人才的时候也要求掌握OWASP TOP 10

OWASP TOP 10是隔几年发一次,最近的两次OWASP TOP 10分别是「2017版」和「2013版」。

OWASP TOP 10 2017版

  1. 注入(injection)
    将不受信任的数据作为命令或查询的一部分发送到解析器时,会产生诸如SQL注入、NoSQL注入、OS注入和LDAP注入的注入缺陷。攻击者的恶意数据可以诱使解析器在没有适当授权的情况下执行非预期命令或访问数据。

  2. 失效的身份认证(Broken Authentication)
    通常,通过错误使用应用程序的身份认证和会话管理功能,攻击者能够破译密码、密钥或会话令牌, 或者利用其它开发缺陷来暂时性或永久性冒充其他用户的身份。
    主要是指比如密码破解、会话劫持等身份认证相关漏洞。

  3. 敏感数据泄露(Sensitive Data Exposure)
    许多Web应用程序和API都无法正确保护敏感数据,例如:财务数据、医疗数据和PII数据。攻击者可以通过窃取或修改未加密的数据来实施信用卡诈骗、身份盗窃或其他犯罪行为。未加密的敏感数据容易受到破坏,因此,我们需要对敏感数据加密,这些数据包括:传输过程中的数据、存储的数据 以及浏览器的交互数据。

  4. XML外部实体(XML External Entities)
    许多较早的或配置错误的XML处理器评估了XML文件中的外部实体引用。攻击者可以利用外部实体窃 取使用URI文件处理器的内部文件和共享文件、监听内部扫描端口、执行远程代码和实施拒绝服务攻击。
    就是指XXE这种漏洞类型。

  5. 失效的访问控制(Broken Access Control)
    未对通过身份验证的用户实施恰当的访问控制。攻击者可以利用这些缺陷访问未经授权的功能或数据,例如:访问其他用户的帐户、查看敏感文件、修改其他用户的数据、更改访问权限等。
    比如说未授权访问、越权漏洞等都属于这一类型。

  6. 安全配置错误(Security Misconfiguration)
    安全配置错误是最常见的安全问题,这通常是由于不安全的默认配置、不完整的临时配置、开源云 存储、错误的 HTTP 标头配置以及包含敏感信息的详细错误信息所造成的。因此,我们不仅需要对所有的操作系统、框架、库和应用程序进行安全配置,而且必须及时修补和升级它们。

  7. 跨站脚本(Cross-Site Scripting)
    当应用程序的新网页中包含不受信任的、未经恰当验证或转义的数据时,或者使用可以创建 HTML或 JavaScript 的浏览器 API 更新现有的网页时,就会出现 XSS 缺陷。XSS 让攻击者能够在受害者的浏览器 中执行脚本,并劫持用户会话、破坏网站或将用户重定向到恶意站点。
    特指XSS这个漏洞类型。

  8. 不安全的反序列化(Insecure Deserialazation)
    不安全的反序列化会导致远程代码执行。即使反序列化缺陷不会导致远程代码执行,攻击者也可以 利用它们来执行攻击,包括:重播攻击、注入攻击和特权升级攻击。
    比如JAVA反序列等。

  9. 使用含有已知漏洞的组件( Useing Components with Known Vulnerabilities)
    组件(例如:库、框架和其他软件模块)拥有和应用程序相同的权限。如果应用程序中含有已知漏 洞的组件被攻击者利用,可能会造成严重的数据丢失或服务器接管。同时,使用含有已知漏洞的组 件的应用程序和API可能会破坏应用程序防御、造成各种攻击并产生严重影响。
    这类型也比较好理解,比如Linux里使用存在破壳漏洞的bash版本,就属于这个类型,通用组件漏洞。

  10. 不足的日志记录和监控(Insufficient Logging&Monitoring)
    不足的日志记录和监控,以及事件响应缺失或无效的集成,使攻击者能够进一步攻击系统、保持持续性或转向更多系统,以及篡改、提取或销毁数据。大多数缺陷研究显示,缺陷被检测出的时间超 过200天,且通常通过外部检测方检测,而不是通过内部流程或监控检测。
    比如由于缺乏相关的日志记录和监控,导致相关入侵无法被及时发现,或者被发现后无法进行相关的溯源。

常见Web漏洞

跨站脚本攻击(XSS)

攻击者往Web页面里插入恶意的Script代码,当用户访问该页面时,嵌入其中的恶意代码将会被执行,从而达到恶意攻击用户的目的。XSS还经常和CSRF漏洞结合利用,以造成更大的危害。

主流XSS类型

  • DOM型XSS
  • 反射型XSS
  • 存储型XSS

其他XSS类型

  • mXSS 突变型XSS
    由于编码等问题导致Payload变异而产生的XSS

  • UXSS 通用型XSS
    利用浏览器或者浏览器拓展漏洞来制造产生XSS的条件并执行代码的一种攻击类型
    一般导致的后果就是Bypass同源策略
    严格来说UXSS应该算不上传统的XSS

  • FlashXSS
    由Flash导致的XSS
    问题主要出现在Flash的实现代码上

  • 只存在于低版本IE浏览器的XSS(少见)

    • UTF-7 XSS
      特定IE版本下以UTF-7编码处理网页内容,导致XSS。
      UTF-7编码场景下的XSS,对应攻击Payload也要转换为UTF-7编码的结果。

    • MHTML XSS
      IE浏览器下的一种特殊类型的XSS,因为是源于仅限低版本IE浏览器支持的一个MHTML协议进行攻击的。

    • CSS XSS
      由于CSS支持的特性导致只在低版本IE下可能导致的XSS。

    • VBScript XSS
      仅在低版本IE下支持的由VBScript语句构建的XSS。

XSS攻击和防御

  • 挖掘XSS漏洞步骤

    1. 寻找注入点
    2. 查看提交的内容是否注入到了页面
    3. 根据注入点位置的DOM结构,构造Payload
  • XSS利用

    • JavaScript能做的,它都能做。
      • 内网端口扫描
      • 信息收集
      • 执行代码
      • 通过BeEF平台丰富的XSS工具进行利用
      • ……
  • XSS盲打
    不管提交什么内容,页面都只会返回特定相同的内容,而不会因为提交的内容不同而不同。
    这种情况下无法通过页面返回的内容来判断提交的XSS代码是否执行成功,这时候就需要用到「XSS盲打平台」。

  • XSS盲打平台
    将XSS盲打平台生成的XSS代码插入到漏洞点(怀疑有漏洞的位置),如果XSS代码被成功执行了,那么在XSS盲打平台的后台就能看到相应的信息,表明此处存在XSS漏洞。
    XSS盲打平台

  • XSS防御

    • 针对 script 等特殊标签做HTML转义,让Payload失效。
    • WAF
    • CSP(内容安全策略)
    • 语言开发框架

跨站请求伪造(CSRF)

可以在受害者毫不知情的情况下,以受害者的名义伪造请求,发送给受攻击的站点。从而在并未授权的情况下执行在权限保护之下的操作。

CSRF 攻击原理

攻击的前提条件:必须是在用户登录了网站的基础上,用同个浏览器去访问攻击者构造的钓鱼页面。(向网站发送请求时才会携带网站的用户Cookie,才能攻击成功。)

CSRF漏洞的利用

假设某网站,在修改用户密码的位置存在CSRF漏洞,用户在修改密码时会发送如下一个请求:
http://localhost/?passwd=1234&submit=submit
passwd字段是用户的新密码

在用户登录状态下访问这个URL,用户的密码就会被修改为passwd字段的值(1234)。

攻击者构造了如下一个钓鱼页面:

<html>
<body>
<img src="1.jpg" width="300px">
<iframe style="display:none;" src="http://localhost/?passwd=hacked&submit=submit">
</body>
</html>

假设用户当前是登录状态,当用户打这个钓鱼页面时,用户只会看到一个美女图片(1.jpg),实际上他的密码已经被修改为 hacked(通过一个隐藏的iframe标签向网站发送了一个修改密码的请求)。这个例子就是攻击者通过CSRF漏洞,以用户的身份修改了用户密码。
CSRF漏洞具体能做到什么,要看出现漏洞的具体功能点,如果银行转账功能出现CSRF漏洞,那么攻击者就能通过漏洞转走你账户的钱……

CSRF的防御手段

  • 构造随机内容的提交参数

    • 增加随机参数(验证码、Token、……)
  • 验证HTTP请求是否正常

    • 验证HTTP请求的Referer

具体功能增加防护措施的时候,可以根据功能场景进行改进。

目录穿越(目录遍历)与文件包含

目录穿越

由于Web服务器或Web应用程序对用户输入文件名称的安全性验证不足而导致的一种安全漏洞。
可绕过服务器的安全限制,访问任意受限的文件(可以是Web根目录以外的文件,比如:数据库配置文件、Linux的密码文件),甚至执行系统命令。

典型的目录穿越漏洞:
http://localhost/file.php?filename=../../../../../../etc/passwd
..代表的是上一层目录,这里的../../../../../../etc/passwd意思是往前跳多层目录,直到跳到根目录下,再加上要读取的文件路径:/etc/passwd,就实现了读取Web服务器系统的任意文件。

文件包含

在开发的时候会有一些共用的代码,在多个文件中会复用,开发人员往往会把这样的代码或者方法写在一个文件里,通过文件包含的方法直接包含到代码里进行使用。

##### 本地文件包含

假设网站有如下两个PHP文件:

file.php:

<?php include($_GET['filename']); ?>

phpinfo.php:

<?php echo phpinfo(); ?>

那么当访问http://localhost/file.php?filename=phpinfo.php时,phpinfo.php文件就会被包含到file.php中执行,最后就会打印出phpinfo信息。
file.php在包含文件的时候并没有对$_GET['filename']的值做相关的处理,「filename」的参数可以是任意的,这就意味着这里存在「本地文件包含漏洞」。

远程文件包含

既然有「本地文件包含」,那自然也有「远程文件包含」啦,如果php.ini的配置选项allow_url_fopenallow_url_include都为 ON 的话,被包含的文件就可以是HTTP的URL形式或者是php://data://等特殊协议,意味着可以包含(执行)远程的文件。
被包含的远程PHP文件,后缀名应该改为txt,否则读取到的会是远程PHP文件被解析后的结果。

在利用「文件包含漏洞」的时候,经常会结合「目录穿越漏洞」来读取/etc/passwd等敏感文件。

文件包含和目录穿越的防御

  • 设置白名单
    将要包含的文件都写入到白名单,非白名单的包含文件都直接拒绝。
  • 设置php.ini相关配置选项
    • 关闭远程文件包含
      关闭allow_url_fopenallow_url_include选项
    • 设置open_basedir选项
      通过设置open_basedir选项的值,将允许访问(包含)的文件限定在某一特定目录内。

JSONP(JSON with Padding)安全与防御

什么是 JSONP

基于 JSON 格式,为解决跨域请求资源而产生的解决方案。
它的基本原理是利用script元素标签远程调用JSON文件来实现数据传递。

JSON劫持(JSON Hijacking)

典型的JSON劫持攻击是通过定义一个跟JSON接口中callback参数值相同的函数来接收JSON接口的数据,以此获取到目标用户敏感信息。

JSON劫持和CSRF非常相似,JSON劫持也可以理解为CSRF的一种应用场景,它们的攻击手法也基本相同,只不过CSRF的目的是发送请求,而JSON劫持是为了获取用户的敏感信息。攻击者首先诱导受害者进入钓鱼页面,钓鱼页面会向网站的JSON接口发送请求,用户如果当前是登录状态,发送请求时就会携带网站的用户Cookie,网站就会返回相关的用户信息,攻击者就成功获取到了用户的敏感信息。

JSONP 的安全问题

如果「callback参数」可以随意修改,是可控的话,还可能导致XSS漏洞。存在XSS的前提是接口返回内容的Content-Type设置不正确(或者没有设置),就有可能导致XSS漏洞的产生。

除了由于自定义callback函数名可以导致XSS以外,由于这个函数的自定义问题以及开发者在编码等方面的开发不严谨,也可能会导致其他一些不常见的安全问题,这里不再深入。

JSON劫持的防御

  • 验证调用方(referer、……)
  • 增加随机Token

XSS问题的防御

  • 对callback函数的长度进行限制
    XSS的Payload往往必须要有一定的长度,才能产生真实的危害。
  • 对内容(callback参数值、返回内容)进行特殊字符的过滤

HTML5 安全问题与防御

HTML5 是定义 HTML 标准的最新的版本,它是HTML的第五次重大修改,标准于14年10月29日完成。

作为 HTML 的升级版,HTML5 引入了更多的标签、属性、方法、功能、……安全问题都是伴随着功能出现的,HTML5 自然而然就产生了一些可以被利用的安全问题,或者说,扩大了原来的一些攻击方法的攻击面/攻击点。

XSS

HTML5 带来的新标签、新属性为XSS恶意代码「绕过WAF等防护设备的黑名单机制」提供了便利:可以尝试使用更多不同的标签、属性绕过防护设备的安全检测,执行恶意代码。这方面的防御主要看防护产品是否增加了对新标签、新属性的检测。

CORS(跨域资源共享)

CORS是一个W3C标准,全称是「跨域资源共享」(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

服务端在响应头中设置Access-Control-Allow-Origin字段来表明接受哪些网站的跨域请求(还有其他几个与CORS请求相关的字段,这里只讲最重要的这个),如果开发人员偷懒,为了方便,将Access-Control-Allow-Origin字段的值设置为「」的话,这表明接受任何网站的跨域请求,如果同时使用了Access-Control-Allow-Credentials:true将导致CORS的安全机制几乎完全失效,这就会导致安全问题。考虑到这种权限过于宽松,CORS又规定,`Access-Control-Allow-Origin:Access-Control-Allow-Credentials:true`不能同时使用。浏览器会对下面这种误配置报错:

Access-Control-Allow-Origin: *  
Access-Control-Allow-Credentials: true

CORS标准规定,Access-Control-Allow-Origin只能配置为单个origin,null或*,
如果开发者想要实现同时与多个域名共享域名的需求,则需要专门编写代码或者使用框架来协助动态生成访问控制策略。这种动态生成的做法增加了开发者配置难度,导致实际网络中出现各种不安全的误配置。

最简单地动态生成访问控制策略的方法,就是在Access-Control-Allow-Origin中反射请求的Origin值。例如,下面是一个错误 Nginx 配置示例:

add_header "Access-Control-Allow-Origin" $http_origin;
add_header “Access-Control-Allow-Credentials” “true”;

这种配置相当于信任任何网站,任意攻击者都可以直接跨域读取网站资源。

「Shell of the Future」工具就是利用CORS建立HTTP通道,将页面发送给控制端(攻击者),可以绕过一些反会话劫持的方法(Http-Only限制的Cookie、绑定IP地址的会话ID、……),实现XSS会话劫持。

如果某个内部站点没有对外网开放,但CORS被设置为接受所有跨域请求,身在外部的攻击者同样可能通过构造钓鱼页面,诱导内部工作人员访问,通过内部工作人员的网络就可以成功访问到内部站点,从而获取到外网访问不到的敏感信息。

防御:对于跨域请求,验证身份、验证数据有效性、……

Web Storage

「Web Storage」 实际上由两部分组成:

  • sessionStorage
    sessionStorage用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此sessionStorage不是一种持久化的本地存储,仅仅是会话级别的存储。
  • localStorage
    localStorage用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。

由于是浏览器本地存储,而其API都是通过JavaScript提供的,那么意味着一旦重要、敏感的数据采用这种存储方式,攻击者就可以通过XSS攻击窃取信息。

防御:避免存储敏感信息到WebStorage

WebSQL

HTML5 引入了一组使用 SQL 操作客户端数据库的 APIs。

既然是数据库操作,那么就会存在一些比如注入等问题。

防御:防护措施和SQL注入也是类似的,比如过滤关键词等。

API

HTML5 引入了一些新的API,可以实现新的攻击方法。

由于 HTML5 涉及的API比较多,就不一一展开了。给一个「通过History新的API来隐藏恶意URL」的例子,可以搭配短网址来隐藏反射型XSS的攻击代码,以下为一个反射型XSS的URL:

http://localhost/xss/?xsscode=<script>history.pushState({},'',location.href.split('?').shift());document.write(1)</script>

用户访问页面后,XSS攻击代码被成功执行,但用户看到的URL却是http://localhost/xss,达到了隐藏恶意URL的效果。

点击劫持(Click jacking)

点击劫持又称「UI覆盖攻击」,是一种视觉上的欺骗手段,它主要有两种方式,一是攻击者使用一个透明的iframe,覆盖在一个网页上,然后诱使用户在该页面上进行操作,此时用户将在不知情的情况下点击透明的iframe页面;二是攻击者使用一张图片覆盖在网页,遮挡网页原有位置的含义。

点击劫持说明图

点击劫持的攻击原理比较简单,如果了解HTML、CSS的话应该不难理解。

点击劫持的防御

  • 使用X-Frame-Options响应头

如果是通过JavaScript方法来禁止iFrame嵌套,存在被绕过的风险。

URL跳转(重定向)

借助未验证的URL跳转,将应用程序引导到不安全的第三方区域,从而导致安全问题。

URL跳转漏洞实现过程

攻击者构造恶意链接 -> 用户访问恶意链接 -> 经过Web应用程序和浏览器的解析 -> 跳转到恶意网站

如果这个网站http://localhost/?url=other.domain的url参数可以用来跳转到其他网页,而网站并没有对 url 参数进行验证、限制的话,那就意味着攻击者也可以通过 url 参数来让用户跳转到任意网站(钓鱼页面、……)。如果是比较知名的公司旗下的网站存在URL跳转漏洞,用户看到恶意链接的域名是属于这个公司的,很可能就会信任这个链接。

URL跳转漏洞的防御

  • 严格控制将要跳转的域名

SQL注入(SQL Injection)

是一种常见的Web安全漏洞,攻击者利用这个漏洞可以访问或修改数据,或者利用潜在的数据库漏洞进行攻击。

SQL

全称是「结构化查询语言(STRUCTURED QUERY LANGUAGE)」,SQL 是用于访问和处理数据库的标准的计算机语言。

常见的SQL数据库

  • MySQL
  • SQL Server
  • ……

SQL注入基本原理

以用户登录为例,程序在收到用户名和密码后,往往会先根据用户名和密码在数据库中查询用户名和密码(数据库中一般存储的是密码的Hash值,而不是密码的明文)同时匹配的数据库记录,如果在数据库中查询到对应的数据记录,则登录成功,否则登录失败。
它的SQL语句大概是这样的:

select * from users where user='用户名' and password='密码的Hash值'

用户名的值是用户提交的,如果网站没有做相关的过滤的话,就可以提交一些「特殊的用户名」,来达到「修改SQL语句结构,而改变查询」的目的。因为密码字段在数据库中通常是以Hash值存储的,用户提交时程序会将密码转换为对应的Hash值,这样密码的字段就相当于不可控了,就没法通过密码的字段改变SQL语句结构,不过能控制用户名就足够了。在早些年,密码还是以明文存储的,经典的「万能密码」就是通过密码字段的注入点,实现登录任意用户帐号。

如果将用户名字段写为' or 1=1;--+,那么网站的SQL语句就会变成这样:

select * from users where user='' or 1=1;-- ' and password='密码的Hash值'

可以看到,提交的用户名改变了SQL语句原本的结构,进而改变了它的查询结果,以上语句把验证密码的部分注释掉了,并且使判断语句永远为真,导致的结果就是「会返回数据库中所有的用户数据」(因为判断语句永远为真(or 1=1),所有数据都会符合判断条件,程序就返回了所有匹配的数据)。SQL注入的本质就是「数据和代码未分离,把用户输入当作代码执行」

SQL注入需要以下两个必备条件

  • 可以控制输入的数据
  • 服务器要执行的代码拼接了控制的数据

SQL注入点的两种类型

  • 整数型
    字段类型为 int ,不需要用引号闭合SQL语句。
  • 字符型
    字段类型为 varchar ,字符型注入点则需要进一步判断SQL语句是使用单引号闭合的还是双引号,然后把语句正确闭合。

SQL注入的挖掘

只要跟网站数据库有交互的地方都有可能存在SQL注入。(GET参数值、POST参数值、Cookie、……)

判断是否存在SQL注入

最常见的是使用and 1=1and 1=2'(引号)这几种判断方式。

假设要判断http://localhost/?id=1的 id 参数是否存在注入,先提交一个引号看看:http://localhost/?id=1%27(%27 是URL编码的单引号,因为无论是字符型还是整数型注入点,输入不成对的单/双引号,都会因为缺少另一半引号而导致SQL语句报错,如果网站报错了,说明 id 参数的值被带入SQL语句中执行了,意味着这里存在SQL注入。

判断注入点类型

之所以要判断注入点类型,是因为要知道SQL语句需不需要闭合、用双引号还是单引号闭合,知道这些才能构造正确的SQL语句。如果语句比较复杂,通常会使用注释符(#--/**/)来闭合语句末尾。

找到注入点后,通过提交SQL语句并观察网站变化,来判断注入点是整数型还是字符型(还要判断SQL语句用的是单引号还是双引号):

  • http://localhost/?id=1 and 1=1 && http://localhost/?id=1 and 1=2

判断是否为整数型注入

正常情况下的SQL语句:

select * from x where id=1

当提交 1=1(第一条测试语句)时的SQL语句:

select * from x where id=1 and 1=1

逻辑判断为真,所以返回正常。

当提交 1=2(第二条测试语句)时的SQL语句:

select * from x where id=1 and 1=2

逻辑判断为假,所以返回错误。

从外部来看,两次提交返回的结果不一致:1=1 时页面正常,1=2 时返回错误,表明这是整数型注入点。

当注入点是字符型时,用来判断整数型注入的语句因为没有闭合引号,被引号包裹了,无法改变SQL语句结构,所以不管是 1=1(select * from x where id='1 and 1=1')还是 1=2(select * from x where id='1 and 1=2')页面都没有变化。

当页面随着提交的SQL语句不同(1=1、1=2)而改变(页面正常/返回错误)时,就表明当前的测试语句符合注入点的类型(被当作语句执行,改变了逻辑判断的结果),重点就在于页面有没有随着提交的SQL语句的逻辑条件不同而产生变化。这就是使用and 1=1and 1=2'(引号)来判断注入点类型的原理。

  • http://localhost/?id=1' and '1'='1 && http://localhost/?id=1' and '1'='2

判断是否是字符型注入(单引号)

判断注入点类型的原理已经在上面说过了,就不重复说了,只写出各个情况下的SQL语句的变化,可以结合上面说的原理自己思考下。

正常情况下的SQL语句:

select * from x where id='1'

当提交 1=1(第一条测试语句) 时的SQL语句:

select * from x where id='1' and '1'='1'

当提交 1=2(第二条测试语句) 时的SQL语句:

select * from x where id='1' and '1'='2'
  • http://localhost/?id=1" and "1"="1 && http://localhost/?id=1" and "1"="2

双引号的判断方法和上面两位(整数型、单引号)一样,这里不再列举……

确定查询字段数

order by语句可以用来判断查询的字段数,方便后续构造SQL语句(如:使用 union 时后面的语句的字段数需要和前面语句的查询字段数相同)。http://localhost/?id=1 order by 5 --+中 5 就是我们输入的字段数,如果真实字段数小于我们猜解的字段数就会抛出异常,我们就知道字段数是小于 5 的,然后继续在 1-5 的范围内猜解,逐步缩小范围,直到确定字段数。

猜解字段数

通常会采用「二分查找」的方法来加快猜解的效率:
每次选择范围的中间值进行猜解

确定回显点

「回显点」就是各个查询字段显示的位置,SQL语句的返回结果将显示在对应的位置。使用union select语句根据查询字段数指定对应数量的数据(有多少个查询字段,就select 多少个数据,这些数据最好不要相同,更容易分辨)。

如果有三个查询字段,语句就是这样:http://localhost/?id=1 union select 1,2,3,1、2、3是随便写的,它们会分别显示在对应的回显点,通过看这三个数字在页面的位置,就知道各个查询字段的回显点的位置了。

union 语句实际上是把两条语句返回的结果合并成一条返回,有些网站程序可能只会读取其中第一条结果,那样后面那条SQL语句返回的结果就没法显示了,这时候就需要让union前面那条语句的返回结果为空,只需要让前面那条语句的条件不成立即可:http://localhost/?id=-1 union select 1,2,3,假设id=-1的数据是不存在的,那返回结果就会为空,而后面那条语句是有结果的,所以最后返回的会是后面那条语句的结果。

SQL注入的利用

获取数据库信息

常用的MySQL函数:

函数 作用
database() 当前所使用的数据库
version() 数据库版本
user() 当前用户
@@datadir 数据库的存储位置
@@version_compile_os 操作系统
concat()、concat_ws() 字符串拼接
ascii() 返回字符所对应的ASCII码
substring()、substr() 字符串截断

获取信息的常用语句

  • 获取所有数据库名

    select SCHEMA_NAME from information_schema.SCHEMATA
  • 获取指定数据库中的所有表名

    select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=database()
  • 获取指定表中所有的字段名

    select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME='USERS'
  • 获取指定表中,指定字段的所有数据

    select user,password from USERS
读取系统文件
select load_file('c:\\windows\\win.ini')

注:Windows路径需要用双反斜杠转义

写入WebShell
select "<?php @eval($_GET['cmd']); ?>" into outfile 'D:\\web\\WWW\\shell.php'

SQL盲注

之前讲的SQL注入实际上都是「SQL回显注入」——数据或执行结果会直接返回到页面上。
「SQL盲注」是无法通过SQL注入在页面返回内容中找到所需要的数据或用作注入成功判断的内容,都可以称为SQL盲注。

SQL盲注的几种方式
  • 布尔型
    通过条件判断的方式,观察页面变化来确认执行结果。
  • 时间型
    在语句中增加延时函数或者执行比较耗时的命令,通过观察页面加载时间来确认执行结果。
  • 错误型
    通过主动引发异常来让数据库将我们想要的信息暴露出来。
猜解数据库

由于SQL盲注是看不到页面返回结果的,需要通过一步步猜解,才能得到想要的信息。

  1. 确定当前数据库名长度
    这里通过「布尔型」的方法来判断数据库名的长度,语句:id=1 and length(database())>5(省略了前面的域名部分,重点关注 id 参数的值),这里判断数据库名长度是否大于 5,如果条件成立,页面就会正常返回,否则页面就不会正常返回——表明数据库名长度是小于等于 5 的,然后通过「二分查找」的方法接续在 5 的范围内猜解,直到确定数据库名的长度。(「二分查找」实际上更多就是用在进行SQL盲注时的猜解部分)

  2. 猜当前数据库名

分别猜解数据库名每一位的字符是什么,最终拼凑出完整的数据库名。
因为字符无法使用二分查找,猜解效率会非常低(尤其是手工注入的时候),那么可以通过将字符转换为「ASCII码」来解决:select * from users where user_id=1 and ascii(substr(database(),1,1))>100,这里判断当前数据库名的第一个字符的ASCII码是否大于100,不是的话则继续猜解50、25、12……依此类推,最终成功确定数据库名第一个字符的ASCII码,接着只需要查看ASCII码对应的字符是什么即可,然后接着猜下一位字符……最终拼凑出完整的数据库名。之后的「猜表名」、「猜字段名」等,方法都是一样的。

通过这种方法,可以逐步猜解出想要的信息,初学者应该尽量避免使用工具而使用手工操作,这样可以更好地理解SQL注入并熟练操作。当对SQL注入比较熟悉之后,就可以使用相关工具来解放双手,提高测试效率,开始学习使用「SQL注入神器」sqlmap

SQL注入的防御

  • 参数化查询
  • 强制数据类型
  • 对用户输入,使用黑/白名单进行验证,把用户输入都当作不可信的
  • 有一些语言自带安全验证的方法,可以直接调用
  • 使用外部的安全框架和API
  • 使用WAF产品(它对Web漏洞是通用的,并不只针对于SQL注入)

文件上传

现在很多网站都有上传功能(上传用户头像、发送文件、……),如果网站没有对用户上传的文件进行严格的验证、过滤的话,就容易产生「文件上传漏洞」,攻击者就可能通过上传恶意文件来将木马植入到网站服务器,从而获取网站权限。

一句话木马

一段脚本代码所造成的脚本木马,可以执行攻击者下发的指令,从而控制网站。

一些语言的一句话木马示例:

  • PHP
<?php @eval($_POST("cmd"));?>
  • ASP
<%eval request("cmd")%>
  • ASP.NET
<%@ PageLanguage="Jscript"%><%eval(Request.item["cmd"], "unsafe");%>

任意文件上传

文件上传功能的实现代码没有严格限制用户上传的文件后缀以及文件类型,导致攻击者可以上传任意文件(当然也包括脚本木马)。

绕过不同类型的限制,进行文件上传

大部分程序对于上传的文件都会进行一些限制和过滤的处理,如果程序验证不严谨,就可能被绕过。

一些绕过程序限制来上传文件的手段
  • MiME类型修改
    MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。
  • 文件内容绕过
    比较常见的是通过将一句话木马插入到图片数据中(图片马),从而绕过Web应用的「文件内容检测」,然后在上传时通过抓包将文件后缀名改为脚本文件的后缀名或者利用「Web容器解析漏洞」来使脚本内容能够成功执行。
  • JavaScript 前端绕过
    有些网站是通过前端的 JavaScript 来验证上传文件的,而 JavaScript 是运行在客户端的,用户是可控的,自然也能被轻易绕过,只需要修改或删除相关的 JavaScript 代码即可(也可以抓包修改文件后缀名)。

命令、代码执行

命令执行

当应用需要调用一些外部程序去处理内容的情况下,就会用到一些执行系统命令的函数(如 PHP 中的「system」、「exec」、「shell_exec」、……)。

当用户可以控制命令执行函数中的参数时,就可以注入恶意系统命令到正常的命令中,造成「命令执行漏洞」。

代码执行

当应用在调用一些字符串转化为代码的函数(「eval」、「assert」、「preg_replace」、……)时,没有考虑到用户是否能控制这个字符串内容,将导致「代码注入漏洞」。

命令、代码执行的防御

  • 尽量少用可以执行命令的的函数(通过在php.ini中的「disable_functions」选项,可以设置要禁用的函数。)

  • 使用「escapshellcmd」函数对参数进行过滤

  • 在调用命令执行函数之前,对参数进行过滤、对敏感字符进行转义、……

  • 避免使用 eval() 来读取 JSON

  • 对于必须使用 eval() 的地方,要严格处理用户提交的数据

逻辑漏洞

所有应用程序都是通过逻辑实现各种丰富多彩的功能的,要实现这些功能必须掌握大量的技巧并进行周密的安排i,但在很多情况下,这些功能的逻辑存在缺陷。逻辑缺陷的本质就是设计者或开发者在思考问题的过程中做出的特殊假设存在明显或隐晦的错误

传统的Web漏洞在找到漏洞点之后,可能需要绕过各种防护手段(安全的开发框架、安全防护软件、入侵检测系统、……)。而「逻辑漏洞」主要是功能逻辑存在缺陷,代码逻辑是人的逻辑思维,找到关键点后,往往不用构造恶意的请求即可完成攻击,很容易绕开防护手段。

逻辑漏洞常见的表现形式

  • 越权
    一般包括「水平越权」和「垂直越权」。

    • 水平越权
      相同级别(权限)的用户(角色)之间,可以越权访问、修改或删除的非法操作。

      如果出现漏洞,将可能会造成大批量数据泄漏,严重的甚至会造成用户信息被恶意篡改。
      测试方法:一般是在发送请求的数据包中,将请求的资源的唯一标识符(订单ID、用户ID、文章ID、……)修改成一个不属于你的数据,查看返回结果。

    • 垂直越权
      不同级别(或不同角色)之间的越权。

      • 向上越权
        低级别(权限)用户可以进行只有高级别用户(管理员)才能做的操作。
      • 向下越权
        高级别用户可以访问低级别用户的信息。
  • 无限制地重试
    比如在登录的时候,修改数据包中的密码字段重新发送,如果后端没有做次数限制,就会造成「密码爆破漏洞」;如果发送验证码的操作没有做时间限制,那么就会造成「短信轰炸漏洞」。

  • 后端信任前端数据

前端是运行在客户端环境下的,意味着数据可能会被伪造,如果后端没有验证前端回传的数据,直接信任了从前端发送来的数据,就可能会被篡改的数据包欺骗。(例如在购买商品时,修改商品的金额,后端没有做验证的话,攻击者就可以任意篡改商品金额,可以想象这个后果有多严重。)

  • 竞态条件考虑不周
    竞态条件(race condition)是指设备或系统出现不恰当的执行时序,而得到不正确的结果。
    一个操作应该只产生一次结果,但是利用多线程重复发送数据包请求,可能产生多条结果。
  • ……