前言

  大家都知道由于PHP其简单、快速、方便、易于开发且专注于Web领域等特性,使得PHP在目前的Web后端依旧称霸。但是,因为其动态特性等一系列因素使得我们在编程时依旧犯下错误,最后导致bug的产生。
  而我最近在看stackoverflow时,无意中发现关于PHP安全编程的相关知识。因此,将其记录在博客中便于日后查找。

安全编程

PHP版本号泄露

  • 问题
image.png

如上图所示,一般默认我们在向服务器请求数据时,返回的响应头中 “X-Powered-By”显示了我们服务器使用的PHP版本。这其实是不安全的,因为PHP存在时间已经有20多年了每个版本都或多或少都有些bug。如果NB的黑客知道你的PHP的版本号,极有可能利用这些bug攻击我们的电脑。

  • 解决方案

① 在php.ini配置文件中,设置expose_php = off
② 在PHP代码中直接使用 header(“X-Powered-By: Magic”); 修改X-Powered-By内容
③ 在PHP代码中直接使用 header_remove(“X-Powered-By”);

XSS攻击

  • 问题

XSS攻击产生的原因在于服务器没有对客户端上传的数据进行校验,导致客户端上传一段js代码上来。最后,导致在我们渲染页面的时候,将该js代码也渲染到网页上从而产生意外。

如下面代码所示:

image.png

前端代码index.html,后台代码b.php。当我们从浏览器打开index.html后点击提交,b.php直接从POST请求获取了数据并直接输出到页面,结果就是页面直接输出js代码。想象一下黑客不是弹出hello world,而是无限循环或者直接将页面元素全部删除后果是咋样。

image.png
  • 解决方案
<?php

// 使用htmlspecialchars将字符串中html标签进行处理
echo '<div>' . htmlspecialchars($_GET['input']) . '</div>';
// 获取使用下面的方式 当然使用htmlspecialchars更加方便
echo '<div>' . filter_input(INPUT_GET, 'input', FILTER_SANITIZE_SPECIAL_CHARS) . '</div>';

// 而且对于动态生成的URL,也可以使用如下方式来保证安全
<?php
// 使用urlencode来编码URL
$input = urlencode($_GET['input']);
// 或者使用如下方式
$input = filter_input(INPUT_GET, 'input', FILTER_SANITIZE_URL);
echo '<a href="http://example.com/page?input="' . $input . '">Link</a>';

而且也开始使用第三方库htmlpurifier来过滤HTML代码

CSRF攻击

  • 问题

  CSRF攻击原理在于使用了用户浏览器上的cookie。我们在使用网银时会先登录,然后本地浏览器会产生cookie用来标识用户,之后我们的每一次向服务器请求都会携带该cookie而服务器也是根据该cookie判断用户是否有权限进行操作。比如说:银行有一个转账请求URL: https://www.example.com?account=xiaoming&money=2000&target=xiaofeng,正常情况下用户发出该请求后 服务器在校验cookie后通过该请求完成转账。
  但是,黑客也可能是该网银的用户也知道有这条转账的请求,因此,写了一个页面 里面有一个链接 <a href="https://www.example.com?account=xiaoming&money=20000&target=heike">,然后,黑客诱导用户进入该网页并点击了该链接,如果用户刚刚已经登录网银而cookie还在有效期,则在用户点击该链接后浏览器会携带有效cookie并向服务器进行转账操作,服务器看到这个cookie认为是用户本人在进行转账操作而将20000元转给heike。因此,在用户不知道的情况完成了一个转账给heike的合法操作。

  • 解决方案
<form method="get" action="/delete.php">
  <input type="text" name="accnt" placeholder="accnt number" />
  <input type="hidden" name="csrftoken" value="<randomToken>" />
  <input type="submit" />
</form>

  如上所示,每一次浏览器向服务器请求页面时服务器会动态生成csrftoken放入表单中,然后浏览器向服务器发送请求都携带csrftoken。这样服务器会验证csrftoken检查是否是有效请求。
  当然,你也可以将其使用ajax请求并将csrftoken放入header。获取你也可以同时检查HTTP_REFERER以及csrftoken来验证是否是合理请求

命令行注入

  • 问题

该注入产生的原因还是在于服务器没有对客户端传入的数据进行校验而直接利用数据完成操作,最后导致严重的后果。
例如:

<pre>
<?php system('ls ' . $_GET['path']); ?>
</pre>

如果传入的$_GET[‘path’]字符串是 “; rm -rf /”,那么就会执行 ls;rm -rf /命令。最后服务器就会将根目录的数据全部删除。

  • 解决方案
<pre>
// 使用escapeshellarg或者escapeshellcmd命令对数据进行过滤
<?php system('ls ' . escapeshellarg($_GET['path'])); ?>
</pre>

上面执行的代码为 ls '; rm -fr /'

SQL注入

  • 问题

SQL注入产生原因还是在于对客户端传入的数据没有进行过滤并直接用来拼接SQL语句,这也是以前PHP产生初期黑客攻击的常用方式。

  • 解决方案

1) 对客户端的数据进行检查,过滤一些字符
2) 使用PDO进行预编译来绑定参数,从而查询数据库。目前,如果使用PHP框架也很多针对SQL的解决方案,例如:Yii2框架中AR查询·

error_reporting

  • 问题

在我们编写PHP代码中经常会在浏览器出现 状态码500 (不要跟我说你从来没有遇到过),如果PHP配置中打开了展示错误提示的功能,那么一些服务器的错误信息会显示在浏览器上。这样会让人知道服务器的相关信息,那么怎样让这些错误信息不要显示在浏览器上而输出到服务器特定位置,而这就需要用到set_error_handler函数。

  • 解决方案

① 若没有打开PHP错误提示的功能,可写如下代码临时打开

ini_set("display_errors", "On");
error_reporting(E_ALL | E_STRICT);

② 根据自己的业务要求,将下面的代码修改后放入自己的PHP代码中,下面代码的意思是PHP发送错误时会调用该函数,然后我通过实现该函数就可以将错误信息重定向到特定位置。

set_error_handler(function($errno , $errstr, $errfile, $errline){
  try{
    $pdo = new PDO("mysql:host=hostname;dbname=databasename", 'dbuser', 'dbpwd', [
      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
    ]);

    if($stmt = $pdo->prepare("INSERT INTO `errors` (no,msg,file,line) VALUES (?,?,?,?)")){
      if(!$stmt->execute([$errno, $errstr, $errfile, $errline])){
        throw new Exception('Unable to execute query');
      }
    } else {
      throw new Exception('Unable to prepare query');
    }
  } catch (Exception $e){
    error_log('Exception: ' . $e->getMessage() . PHP_EOL . "$errfile:$errline:$errno | $errstr");
  }
});

其他的还有远程文件包含以及上传文件攻击,大家可以查看https://stackoverflow.com/documentation/php/2781/security#t=201708170948498596789了解,它们的攻击原因都在于对浏览器传入的数据么有进行过滤。

总结

黑客攻击手段多种多样,但黑客之所以能够攻击到服务器在于服务器对于客户端传入的数据没有进行校验。因此,要保证服务器的安全性必须要防御性编程思想,特别是那些涉及敏感数据要多多思考。