Onebound技术规范文档


后端安全规范

<p>[TOC]</p> <h3>基本准则:</h3> <ul> <li>所有的用户输入都是有害的,对所有从客户端传入的数据都不信任, 需要做判断和过滤(类型,长度,格式,范围),否则可能会受到SQL Injection、XSS等攻击。比如:$_GET, $_POST, $_COOKIE, $_FILES, $_REQUEST等。 直接使用将可能存在被注入的危险。</li> <li> <p>用户的相关输入涉及数据库操作时需要对输入做专门的转换。例如: 数据库操作中数字型的需要做intval转换,字符串类型的需要通过mysql_real_escape_string过滤。</p> </li> <li> <p>用户上传的文件的文件名必须随机重新命名,并限制其后缀,大小,类型,并以明确的后缀结束。</p> </li> <li> <p>最好别使用PHP调用shell,若实在要调用shell命令时,输入参数作为system,exec,passthru等任何命令执行函数的参数时,要使用escapeshellarg函数进行过滤。(escapeshellcmd在某些条件下会存在漏洞,因此强烈推荐使用escapeshellarg) </p> </li> <li> <p>输入参数作为php文件内容时,要使用var_export进行数据导出。字符串尽量用’ ‘而不是” “进行引用,一个是效率问题,一个是安全问题。</p> </li> <li> <p>输出到网页中的变量要处理转义 防止xss攻击。</p> </li> <li>若有输入变量带到网址跳转中去,必须校验域名地址,防止通过URL被篡改,从当前网站跳转到欺诈网站,产生钓鱼攻击。</li> <li>一些其它数据来源:比如导入excel数据,在进入库或者查询时。必须进行转义处理。</li> <li>对外提供API接口的,数据传输过程中建议使用rsa+aes 相结合的方式来做加密处理,防止数据传输过程中被嗅探和篡改.</li> <li>入库中存放密码不能明文,用户密码不能用纯md5加密 应该用md5+salt等</li> <li>在表单提交中,最好加入token机制,以防CSRF攻击。</li> <li>若文件包含了一个用户输入变量,请校验后缀,路径等信息,过滤一些跨目录符号。如:../ ..\ .. 等 防止文件遍历。</li> <li>接口禁止返回过多隐私数据,例如会员信息接口,不应将password,salt这两字段显示出来,如果被黑客抓包,极有可能导致用户信息泄露</li> </ul> <h3>关闭调试模式</h3> <p>在部署到生产环境的时候,确保你已经关闭了调试模式,可以通过修改环境变量的方式关闭调试模式。</p> <pre><code class="language-php">// application/config.php return [ 'app_debug' =&gt; Env::get('app.debug', false), ]</code></pre> <p>无论是本地开发还是生产环境部署,都不建议直接通过修改配置文件的方式开启/关闭调试模式,而应该使用环境变量(本地开发可以通过定义.env文件)。</p> <h3>请求变量过滤</h3> <p>永远不要相信用户的输入 ThinkPHP建议的获取请求变量的方法是Request类的param方法(如非必要不要再使用get或者post方法获取,更不要使用原生的$_GET/$_POST等方法获取)。</p> <p>例如在Onebuy后台手动充值时,充值金额输入框没有对输入数据进行校验处理,直接写库以及用户余额变更,在User::money()方法中,使用的bcadd方法去相加,导致后台手动充值时,用户余额未增加,但有增加记录</p> <pre><code class="language-php"> /** * 变更会员余额 * @param int $money 余额 * @param int $user_id 会员ID * @param string $type 消费类型 */ public static function money($money, $user_id, $type, $memo) { $user = self::get($user_id); if ($user &amp;&amp; $money != 0) { $before = $user-&gt;money; $after = function_exists('bcadd') ? bcadd($user-&gt;money, $money, 2) : $user-&gt;money + $money; //更新会员信息 $user-&gt;save(['money' =&gt; $after]); //写入日志 MoneyLog::create(['user_id' =&gt; $user_id, 'money' =&gt; $money, 'type' =&gt; $type, 'before' =&gt; $before, 'after' =&gt; $after, 'memo' =&gt; $memo]); } } $money = $this-&gt;request-&gt;request('money') // 当用户输入的数字带有特殊字符或字母时,bcadd()方法不会对两个数值相加,导致用户余额变更失败 User::money($money,1,'recharge','Admind Recharge')</code></pre> <h3>SQL注入</h3> <p>ThinkPHP的查询统一使用了PDO的prepare预查询和参数绑定机制,能有效的避免SQL注入的发生。但不代表绝对安全,如果缺乏良好的代码规范,仍然有可能被利用。一个最简单的原则就是不要让用户决定你的查询条件(或者字段排序)和控制你的查询数据。</p> <p>对于一些字符串的查询条件(包括原生查询)或者特殊的查询(包括ORDER部分),不要使用字符串拼接方式,而是需要手动进行参数绑定。</p> <pre><code class="language-php">// 错误示例 使用了字符串拼接方式 Db::query("select * from think_user where id=$id AND status=$statis"); // 正确示例 Db::query("select * from think_user where id=? AND status=?", [ $id, $status]); // 正确示例 Db::execute("update think_user set name=:name where status=:status", [ 'name' =&gt; 'thinkphp', 'status' =&gt; 1 ]);</code></pre> <h3>XSS攻击</h3> <p>跨站脚本攻击(cross-site scripting,简称 XSS),XSS是一种在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。</p> <p>在渲染输出的页面中,要对一些数据进行安全处理,防止被恶意利用造成XSS攻击,所有的输出应经过htmlentities转义输出,确保安全。</p> <p>例如某个论坛模块,用户可以自己发帖,假设用户在发帖时,写了以下代码:</p> <pre><code class="language-html">&lt;script&gt;$(()=&gt;{document.getElementTags('body').innerHtml = ''})&lt;/script&gt;</code></pre> <p>如果这段代码没有经过htmlentities转义输出,在页面加载完成后,会执行这段js脚本,将body下的document清空,导致用户正常使用时,突然页面一片空白</p> <h3>CSRF</h3> <p>CSRF是跨站请求伪造的缩写,也被称为XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS利用的是用户对指定网站的信任,CSRF利用的是网站对用户网页浏览器的信任。因为CSRF攻击利用的是冲着浏览器分不清发起请求是不是真正的用户本人。也就是说,简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。</p> <p>防范CSRF攻击常用手段</p> <h4>用户操作限制——验证码机制</h4> <p>添加验证码来识别是不是用户主动去发起这个请求,由于一定强度的验证码机器无法识别,因此危险网站不能伪造一个完整的请求。优点是简单粗暴,低成本,可靠,能防范99.99%的攻击者。缺点是对用户不友好。</p> <h4>在请求地址中添加token并验证</h4> <p>CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于cookie中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有token或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。这种方法要比检查 Referer 要安全一些,token 可以在用户登陆后产生并放于session之中,然后在每次请求时把token 从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把 token 以参数的形式加入请求。对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成</p> <h4>在HTTP 头中自定义属性并验证</h4> <p>这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去</p> <h3>使用验证器</h3> <p>对于大量的表单需要验证的情况,建议使用验证器功能统一进行数据的合规验证。验证器的验证操作应该在控制器或者路由阶段使用validate方法进行处理,模型的数据验证功能新版已经取消不再建议使用,模型和数据库操作的时候应该传入经过安全处理过的数据</p> <h3>业务逻辑安全</h3> <p>这个属于应用层面的安全,很多漏洞源于某个业务逻辑自身的安全隐患,包括没有做合理的数据验证和权限检查,尤其是涉及资金及财务层面的,一定要做更多的安全检查,并且开启事务。一个好的建议是更多的对应用进行分层设计,减少每层的复杂性,独立的分层设计便于提高安全性。</p> <h3>服务器安全</h3> <p>服务器安全主要可以从多个方面入手,例如运维人员权限、开发人员权限、测试环境生产环境隔离、数据库操作、数据备份等</p> <p>服务器上的网站、数据库、配置文件应定时备份,备份应异地备份,备份文件最好不要放在本机服务器,万一本机宕机,使用异地备份文件可快速恢复生产环境,保障业务正常进行。如有条件,可建立五重备份机制:常规备份、自动同步、LVM快照、Azure备份、S3备份。</p> <p>目前公司服务器权限管理较乱,生产环境FTP权限、生产环境MySQL权限基本每个开发人员都有,并且开发人员可以随意对FTP文件上传、覆盖、删除等操作,生产环境出现问题,无法对相关人员进行追责。MySQL生产环境也可以随意进入修改数据、修改结构,安全隐患非常大。后续应确定生产环境下,仅允许由运维人员去操作拉取最新代码,更新数据库结构、数据等</p> <p>在处理需求和故障时,执行风险命令(比如rm、restart、reboot等)需再三确认,执行命令前,检查所在服务器,所在服务器路径,再执行。在处理事故时,一定要考虑处理措施是否会引发连锁故障,重要操作三思而行。针对数据库的操作更要谨慎,避免在不清醒状态下,在服务器上错误执行了命令,导致数据丢失或故障。数据库执行的命令,添加字段、加索引等,必须是经过测试检查的命令,才能在正式环境运行。</p> <p>在对重要业务升级、迁移、扩容之前,列一下操作步骤,越详细越好,实际操作按步骤操作,操作完做好记录。在操作前应对数据、代码、配置进行异机备份,避免出现操作失误后数据、代码、配置丢失问题。</p> <h3>参考文章</h3> <p><a href="https://www.zhihu.com/question/487676341/answer/2133192911" title="关于csrf攻击怎么防范?">关于csrf攻击怎么防范?</a> <a href="https://blog.thinkphp.cn/789333" title="ThinkPHP安全规范指引">ThinkPHP安全规范指引</a> <a href="https://www.cnblogs.com/hellohell/p/6114551.html">PHP安全规范</a></p>

页面列表

ITEM_HTML