一. 防止SQL注入

1 危害

  1. 攻击者可以利用它读取、修改或者删除数据库内的数据,获取数据库中的用户名和密码等敏感信息
  2. 甚至可以 获得数据库管理员的权限。
  3. 如果能够再利用SQLServer扩展存储过程和自定义扩展存储过程来执行一些系统命令,攻击者还可以获得该系统的控制权。
  4. SQL Injection 也很难防范。网站管理员无法通过安装系统补丁或者进行简单的安全配置进行自我保护,一般的防火墙也无法拦截SQL Injection 攻击。

2 原理

结构化查询语言(SQL)是一种用来和数据库交互的文本语言。SQL Injection 就是利用某些数据库的外部接口把用户数据插入到实际的数据库操作语言(SQL)当中,从而达到入侵数据库乃至操作系统的目的。它的产生主要是由于程序对用户输入 的数据没有进行严格的过滤,导致非法数据库查询语句的执行。

3 注入方式举例

例如下面的用户登陆验证程序:

$sql = "SELECT * FROM user WHERE username='$username' AND password='$password'";
$result = mysql_db_query($dbname, $sql);
  1. or 注入
    如果我们提交如下url:
    http://127.0.0.1/injection/user.php?username=angel'or'1=1
    这个sql就变成:

    SELECT * FROM user WHERE username=’angel’ or ‘1=1’ AND password=’$password
    

    password部分就被这个or给怼没了,那么就可以不输入密码成功登陆系统

  2. 注释注入
    同样我们也可以利用sql的注释语句实现sql注入,如下面的例子:
    http://127.0.0.1/injection/user.php?username=angel'/*
    http://127.0.0.1/injection/user.php?username=angel'%23
    根据mysql的特性,mysql支持/*和#两种注释格式,所以我们提交的时候是把后面的代码注释掉,值得注意的是由于编码问题,在地址栏里提交#会变成空的,所以我们在地址栏提交的时候,应该提交%23,才会变成#,就成功注释了。

  3. SQL注入语句大全
    http://blog.csdn.net/zzq19860626/article/details/10220427

4 需要注意以下几个要点

  1. 永远不要信任用户的输入。对用户的输入进行校验,可以通过正则表达式,或限制长度;对单引号和 双”-“进行转换等。
  2. 永远不要使用动态拼装sql,可以使用参数化的sql或者直接使用存储过程进行数据查询存取。
  3. 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
  4. 不要把机密信息直接存放,加密或者hash掉密码和敏感的信息。
  5. 应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装

5 PHP中我们如何解决

1) PHP手册介绍的预防措施

PHP手册的SQL注入部分:http://php.net/manual/zh/security.database.sql-injection.php

  • 永远不要使用超级用户或所有者帐号去连接数据库。要用权限被严格限制的帐号。
  • 检查输入的数据是否具有所期望的数据格式。PHP 有很多可以用于检查输入的函数,从简单的变量函数和字符类型函数(比如 is_numeric(),ctype_digit())到复杂的 Perl 兼容正则表达式函数都可以完成这个工作。
  • 如果程序等待输入一个数字,可以考虑使用 is_numeric() 来检查,或者直接使用 settype() 来转换它的类型,也可以用 sprintf() 把它格式化为数字。
  • 使用数据库特定的敏感字符转义函数(比如 mysql_escape_string() 和 sql_escape_string())把用户提交上来的非数字数据进行转义。如果数据库没有专门的敏感字符转义功能的话 addslashes() 和 str_replace() 可以代替完成这个工作。
  • 要不择手段避免显示出任何有关数据库的信心,尤其是数据库结构。参见错误报告和错误处理函数。
  • 也可以选择使用数据库的存储过程和预定义指针等特性来抽象数库访问,使用户不能直接访问数据表和视图。但这个办法又有别的影响。
  • 在允许的情况下,使用代码或数据库系统保存查询日志也是一个好办法。显然,日志并不能防止任何攻击,但利用它可以跟踪到哪个程序曾经被尝试攻击过。日志本身没用,要查阅其中包含的信息才行。毕竟,更多的信息总比没有要好。

2)使用PDO防止SQL注入

PDO防注入原理分析以及使用PDO的注意事项:https://my.oschina.net/zxu/blog/148432

二. 验证码

对于识别验证码的程序来说,最难的部分是验证字符的切割和特征码的建立,而国内很多程序员只做验证码时,总是喜欢在验证码加很多干扰素,干扰线,影响效果不说,还达不到很好的效果;所以,要想使自己验证码难于本识别,只做下面两点就够了

  • 字符粘连,最好所有的字符都有粘连的部分;
  • 不要使用规格字符,验证码的各个部分使用不同比例的缩放或者旋转。

只要做到这两点,或者这两点的变形,识别程序就很难识别。

三. 刷新提交

场景

  • 点击提交按钮两次。
  • 点击刷新按钮。
  • 使用浏览器后退按钮重复之前的操作,导致重复提交表单。
  • 使用浏览器历史记录重复提交表单。
  • 浏览器重复的HTTP请求。

用户提交表单时可能因为网速的原因,或者网页被恶意刷新,致使同一条记录重复插入到数据库中,这是一个比较棘手的问题。我们可以从客户端和服务器端一起着手,设法避免同一表单的重复提交。

有效策略

  • js禁掉提交按钮。
    表单提交后使用Javascript使提交按钮disable。这种方法防止心急的用户多次点击按钮。但有个问题,如果客户端把Javascript给禁止掉,这种方法就无效了。
  • 使用Post/Redirect/Get模式。
    在提交后执行页面重定向,这就是所谓的Post-Redirect-Get (PRG)模式。简言之,当用户提交了表单后,你去执行一个客户端的重定向,转到提交成功信息页面。这能避免用户按F5导致的重复提交,而其也不会出现浏览器表单重复提交的警告,也能消除按浏览器前进和后退按导致的同样问题。
  • 在session中存放一个特殊标志。
    当表单页面被请求时,生成一个特殊的字符标志串,存在session中,同时放在表单的隐藏域里。接受处理表单数据时,检查标识字串是否存在,并立即从session中删除它,然后正常处理数据。
    如果发现表单提交里没有有效的标志串,这说明表单已经被提交过了,忽略这次提交。
  • 使用header函数转向
    当用户提交表单,服务器端处理后立即转向其他的页面,代码如下所示。
    if (isset($_POST['action']) && $_POST['action'] == 'submitted') {
       //处理数据,如插入数据后,立即转向到其他页面
       header('location:submits_success.php');
    }
    
    这样,即使用户使用刷新键,也不会导致表单的重复提交,因为已经转向新的页面,而这个页面脚本已经不理会任何提交的数据了。
  • 在数据库里添加约束。
    在数据库里添加唯一约束或创建唯一索引,防止出现重复数据。这是最有效的防止重复提交数据的方法。
  • 使用Cookie处理
    使用Cookie记录表单提交的状态,根据其状态可以检查是否已经提交表单。如果客户端禁止了Cookie,该方法将不起任何作用,这点请注意。

四. DOS攻击

防止 DDoS 攻击的方式:

  • 减少公开暴露
  • 利用扩展和冗余
  • 充足的网络带宽保证
  • 分布式服务拒绝 DDoS 攻击
  • 实时监控系统性能
  • 修改配置;这里提供一个简单的参考方法,修改php.ini文件
    • “disable_functions”改成gzinflate,默认是放空
    • ”allow_url_fopen“设为Off
    • php_sockets.dll 把这个模块打开
      重启使配置生效,一般可以抵御掉DDOS攻击。

参考资料:
51CTOblog的DDOS攻击与防御专题:http://blog.51cto.com/zt/282

五. 跨域攻击

注:以下内容摘自:https://blog.tonyseek.com/post/introduce-to-xss-and-csrf/

XSS

XSS全称“跨站脚本”,是注入攻击的一种。其特点是不对服务器端造成任何伤害,而是通过一些正常的站内交互途径,例如发布评论,提交含有 JavaScript 的内容文本。这时服务器端如果没有过滤或转义掉这些脚本,作为内容发布到了页面上,其他用户访问这个页面的时候就会运行这些脚本。

防止 XSS 的根本之道还是过滤用户输入。用户输入总是不可信任的,这点对于 Web 开发者应该是常识。

如果我们不需要用户输入 HTML 而只想让他们输入纯文本,那么把所有用户输入进行 HTML 转义输出是个不错的做法。似乎很多 Web 开发框架、模版引擎的开发者也发现了这一点,Django 内置模版和 Jinja2 模版总是默认转义输出变量的。如果没有使用它们,我们自己也可以这么做。PHP 可以用 htmlspecialchars 函数Python 可以导入 cgi 模块用其中的 cgi.escape 函数。如果使用了某款模版引擎,那么其必自带了方便快捷的转义方式。

对于复杂的情况,简单的方法就是白名单重新整理。“白名单”消毒 HTML 标签和属性(Sanitize HTML)的开源解决方案:

  • JavaScript: sanitize-html
  • PHP: htmlpurifier

CSRF

CSRF 和 XSS 根本是两个不同维度上的分类。XSS 是实现 CSRF 的诸多途径中的一条,但绝对不是唯一的一条。一般习惯上把通过 XSS 来实现的 CSRF 称为 XSRF。

CSRF 的全称是“跨站请求伪造”。CSRF 的全称是“跨站请求伪造”,而 XSS 的全称是“跨站脚本”。看起来有点相似,它们都是属于跨站攻击——不攻击服务器端而攻击正常访问网站的用户。CSRF 顾名思义,是伪造请求,冒充用户在站内的正常操作。我们知道,绝大多数网站是通过 cookie 等方式辨识用户身份(包括使用服务器端 Session 的网站,因为 Session ID 也是大多保存在 cookie 里面的),再予以授权的。所以要伪造用户的正常操作,最好的方法是通过 XSS 或链接欺骗等途径,让用户在本机(即拥有身份 cookie 的浏览器端)发起用户所不知道的请求。

CSRF 并不一定要有站内的输入,因为它并不属于注入攻击,而是请求伪造。被伪造的请求可以是任何来源,而非一定是站内。所以我们唯有一条路可行,就是过滤请求的处理者。

首先可以提高的一个门槛,就是改良站内 API 的设计。对于发布帖子这一类创建资源的操作,应该只接受 POST 请求,而 GET 请求应该只浏览而不改变服务器端资源。

接下来我们就可以用比较简单也比较有效的方法来防御 CSRF,这个方法就是“请求令牌”。实现方法非常简单,首先服务器端要以某种策略生成随机字符串,作为令牌(token),保存在 Session 里。然后在发出请求的页面,把该令牌以隐藏域一类的形式,与其他信息一并发出。在接收请求的页面,把接收到的信息中的令牌与 Session 中的令牌比较,只有一致的时候才处理请求,否则返回 HTTP 403 拒绝请求或者要求用户重新登录验证身份。

如下也列出一些据说能有效防范 CSRF,其实效果甚微的方式甚至无效的做法:
通过 referer 判定来源页面:referer 是在 HTTP Request Head 里面的,也就是由请求的发送者决定的。如果我喜欢,可以给 referer 任何值。当然这个做法并不是毫无作用,起码可以防小白。但我觉得性价比不如令牌。

过滤所有用户发布的链接:这个是最无效的做法,因为首先攻击者不一定要从站内发起请求(上面提到过了),而且就算从站内发起请求,途径也远远不止链接一条。比如 <img src=”./create_post.php” /> 就是个不错的选择,还不需要用户去点击,只要用户的浏览器会自动加载图片,就会自动发起请求。

在请求发起页面用 alert 弹窗提醒用户:这个方法看上去能干扰站外通过 iframe 发起的 CSRF,但攻击者也可以考虑用 window.alert = function(){}; 把 alert 弄哑,或者干脆脱离 iframe,使用 Flash 来达到目的。