Bootstrap

WEB安全测试基础

目录

一、SQL注入

二、命令执行漏洞

三、文件包含漏洞

四、文件上传漏洞

五、反序列化漏洞


一、SQL注入

1、漏洞形成原因与危害

SQL injection(SQL注入)就是通过把SQL命令插入到WEB表单、页面请求的查询字符串中提交给服务器,最终达到在服务器执行SQL命令的方法。具体来说,其就是利用WEB应用程序对用户输入过滤不严格的缺陷,将SQL命令注入后台数据库引擎执行,而不是按照设计者的意图去执行SQL语句。

如:SELECT first_name, last_name FROM users WHERE user_id = $_GET[‘id’]

当用户输入是当用户输入:1 or 1=1#时,上述语句变成:

SELECT first_name, last_name FROM users WHERE user_id = 1 or 1=1 #会暴出所有记录。

SQL注入漏洞是WEB应用系统最高危的漏洞之一,其危害主要表现在:

(1)可以绕过登录验证,非法登录应用程序,如万能密码;

(2)可以非法访问数据库中的数据,盗取用户的隐私以及个人信息,造成用户的信息泄露;

(3)可以对数据库的数据进行增加、修改、删除操作,导致对某些网页内容进行篡改、嵌入网马链接、私自添加或删除管理员账号等后果。

(4)可以经过提权等步骤,获取操作系统的最高权限,从而远程控制服务器。

2、SQL注入的分类

根据自数据库查询的数据类型的不同,SQL注入漏洞可分为数字型注入和字符型注入两类。即如果查询的值在数据库中的字段是数字类型,则可能会出现数字型注入,相应的,如果查询的值在数字库的字段是字符类型,则可能会出现字符型注入。这两类漏洞的探测方式有所不同,数字型注入漏洞探测时不涉及单引号闭合,而字符型注入一般要使用单引号闭合。

3、SQL注入的防范

1、首先判断是数字型注入还是字符型注入。针对不同的注入采用不同的防范方式。

2、如果是数字型注入,用is_numeric()函数判断输入是否是数字,如果不是数字就不再处理,数字型注入比较容易解决。(或者首先判断是否为数字,如果是数字正常处理,如果不是数字,则可能存在字符型注入,按照字符型注入漏洞进行防范)

3、如果是字符型注入,可以采用mysqli_real_escape_string()来对输入的特殊字符,如\n,\r,\,',"等进行转义,基本可以解决注入的问题。

4、防范SQL注入的比较好的解决方案是采用预编译语句。预编译语句的作用是:编译一次,可以多次执行。其SQL语句是固定的,无法通过参数值进行重构,因此可完美解决SQL注入问题。下面以PHP语言为例说明预编译过程。

PHP执行预处理语句的过程如下:

(1)在SQL语句中添加占位符。PDO(数据库访问接口层)支持问号“?”和命名参数两种占位符。分别示例如下:

$sql = “select first_name,last_name from users where user_id= ?”;

$sql = “select first_name,last_name from users where user_id= :id;

(2)使用prepare()方法准备预处理语句。该方法将返回一个PDOStatement类对象。如:

$stmt = $pdo->prepare($sql);   //$pdo为PDO对象

(3)执行查询并将参数绑定到占位符上。如针对?占位符。

$stmt-> execute(array($id));

由于SQL语句被预编译,无法再进行重构,也就无法进行SQL注入了。

二、命令执行漏洞

1、命令执行漏洞的概念与危害

当WEB应用程序调用一些外部程序去处理任务时会用到一些执行系统命令的函数。如PHP中的system,exec,shell_exec等,当用户可以控制命令执行函数中的参数时,可注入恶意系统命令到正常命令中,造成命令执行攻击。

命令执行漏洞的危害非常大,shell_exec()等函数的作用就是可以在PHP中去执行操作系统命令,因而如果不对用户输入的命令进行过滤,那么理论上就可以执行任意系统命令,也就相当于直接获得了系统级的Shell,因而命令执行漏洞的威力相比SQL注入要大多了。

2、命令执行漏洞原理与防范

操作系统命令可以连接执行是造成命令执行漏洞存在的前提条件。无论是在Windows操作系统还是Linux操作系统之中都可以通过管道符支持连续执行命令。下表是Windows与Linux操作系统的管道符:

Windows管道符

Linux管道符

作  用

|

|

前面命令输出结果作为后面命令的输入

||

||

前面命令执行失败时才执行后面的命令

&

&或;

前面命令执行后接着执行后面的命令

&&

&&

前面命令执行成功了才执行后面的命令

清楚了命令执行漏洞存在的原因,对其防范就比较简单了,主要措施包括:

(1)尽量不使用执行命令的函数。

(2)在使用执行命令的函数/方法的时候,对参数进行过滤,对管道符等敏感字符进行转义。

(3)在后台对应用的权限进行控制,即使有漏洞,也不能执行高权限命令。

(4)对PHP语言来说,不能完全控制的危险函数最好不要使用。

三、文件包含漏洞

1、文件包含漏洞的概念

文件包含就是开发人员为提高代码的重用性,把可重复使用的函数或代码写到单独一个文件中,在需要用到这些函数或代码时,直接调用此文件,而无需重复编写。文件包含相当于将被包含的文件内容复制到了包含处。

在PHP中提供了四个文件包含的函数,如下表所示:

函数名称

描 述

include()

当使用该函数包含文件时,只有代码执行到 include() 函数时才将文件包含进来,发生错误时只给出一个警告,继续向下执行。

include_once()

include_once() 语句和 include() 语句类似,唯一区别是如果该文件已经被包含过,则不会再次包含,即只会包含一次。

require()

除了处理失败的方式不同之外,require() 和 include() 几乎完全一样。require() 在出错时产生 (E_COMPILE_ERROR) 级别的错误,换句话说将导致脚本中止。而 include() 只产生警告 (E_WARNING),脚本会继续运行。

require_once()

require_once() 语句和 require() 语句完全相同,唯一区别是 PHP 会检查该文件是否已经被包含过,如果是则不会再次包含。

为了使代码更加灵活,有时会将被包含的文件设置为变量,用来进行动态调用。由于被包含的文件设置为变量,从而可以被控制,如果对客户输入参数过滤不严,客户端可以调用一个恶意文件,达到恶意执行代码的目的,这就是文件包含漏洞。从这里可以看到文件包含漏洞的产生原因是在通过 PHP 的函数引入文件时,由于传入的文件名没有经过合理的校验,从而操作了预想之外的文件,导致意外的文件泄露甚至恶意的代码注入。从定义当中也可以看到,文件包含漏洞存在并被利用的条件是:WEB应用程序用include()等文件包含函数通过动态变量的形式引入需要包含的文件,用户能够控制该动态变量。

文件包含漏洞可分为两类,本地文件包含(Local File Inclusion,LFI)与远程文件包含(Remote File Inclusion,RFI)。它们的原理是相同的,不同点就是前者只能包含服务器内存在的文件,后者则可包含远程服务器内的文件。两类包含基本没有区别,无论是哪种扩展名,只要遵循PHP语法规范,PHP解析器就会对其解析。

文件包含漏洞主要危害包括:

(1)读取敏感文件的内容,如Linux系统中的/etc/passwd文件。

(2)执行符合PHP语法规范的文件的恶意内容。

(3)利用远程包含植入webshell,在远程包含的文件中有如下代码:<?php fwirte(fopen("indes.php","w"),"<?php eval($_POST['cmd'])?> ");?>

(4)本地包含配合文件上传植入webshell,如在可上传的图片文件中包含代码:<?php fwrite(fopen("indes.php","w"),"<?php eval($_POST['cmd'])?>");?>

(5)使用封装协议植入webshell

(6)通过包含Apache日志文件,植入webshell

2、文件包含漏洞的防御

通过前述,我们容易理解引起文件包含漏洞的原因:

  1. 被包含的文件可以被攻击者所控制
  2. 攻击者可以随心所欲的包含某个文件

根据引起文件包含漏洞的原因,防范文件包含漏洞的方法包括:

(1)严格判断包含中的参数是否外部可控,因为文件包含漏洞利用成功与否的关键点就在于被包含的文件是否可被外部控制。

(2)路径限制。限制被包含的文件只能在某一文件夹内,一定要禁止目录跳转字符,如:“../”

(3)包含文件验证:验证被包含的文件是否是白名单中的一员

(4)尽量不要使用动态包含,即将需要包含的页面固定写好,如:include(“head.php”)。

四、文件上传漏洞

1、文件上传漏洞的概念

文件上传漏洞是指由于WEB容器解析漏洞或程序员未对上传的文件进行严格的验证和过滤,而导致的用户向服务器上传可执行的脚本文件,通过此脚本文件获得了执行服务器端命令的权限。

2、服务器端的文件上传控制机制

服务端文件内容主要用来检测内容是否合法或含有恶意代码等。在判断文件类型时,可以结合使用MIME Type、后缀检查、甚至检测文件内容类型等方式。在文件类型检查中,白名单方式比黑名单的方式更加可靠。对于图片文件,可以使用压缩函数或者resize函数进行处理,可以破坏图片中可能包含的HTML代码。

(1)黑名单及白名单过滤扩展名机制与绕过

黑名单过滤是定义了一系列不安全的扩展名,服务器端在接收到文件后,将其扩展名与黑名单进行匹配,如果发现匹配成功,则认为文件不合法。这种过滤方式不够安全,因为有些危险的扩展名可能被忽略,导致危险。白名单过滤是定义了允许上传的扩展名,即扩展名不在白名单内的文件将不允许上传,其拥有比黑名单更好的防御机制,但也可能结合WEB容器的解析漏洞绕过白名单限制。

a)黑名单代码

<?php

    header("Content-type: text/html; charset=utf-8");

    $blacklist = array("php","php5","jsp","asp","asa","aspx");//黑名单

    if(isset($_POST['submit'])){

       //将字符串编码由utf-8转到gb2312,解决中文文件不能上传问题

       $name = iconv('utf-8','gb2312',$_FILES['file']['name']);

       $extension = substr(strrchr($name,"."),1);//得到扩展名

       $flag = true;

       //迭代判断扩展名是否在黑名单中

       foreach($blacklist as $key => $value){

           if($value == $extension){

              $flag = false;

              break;

           }

       }

       if($flag){

           $size = $_FILES['file']['size']; //接收文件大小

           $tmp = $_FILES['file']['tmp_name']; //临时路径

           //指定上传文件到uploadFile目录

       move_uploaded_file($tmp,"./uploadFile/".$name);

           echo "文件上传成功!        

       }else{

           echo "上传文件不合法";

       }

    }

?>

b)白名单代码

<?php

header("Content-type: text/html; charset=utf-8");

$whilelist = array("jpg","jpeg","png","bmp","gif");//白名单

    if(isset($_POST['submit'])){

       $name = iconv('utf-8','gb2312',$_FILES['file']['name']);

       $extension = substr(strrchr($name,"."),1);//得到扩展名

       $flag = false;

       //迭代判断扩展名是否在白名单中

       foreach($whilelist as $key => $value){

           if($value == $extension){

              $flag = true;

              break;

           }

       }

       if($flag){

           $size = $_FILES['file']['size']; //接收文件大小

           $tmp = $_FILES['file']['tmp_name']; //临时路径

           //指定上传文件到uploadFile目录

       move_uploaded_file($tmp,"./uploadFile/".$name);

           echo "文件上传成功!";         

       }else{

           echo "上传文件不合法";

       }

    }

?>

(2) MIME验证与绕过

HTTP协议规定了上传资源的时候在HTTP Header中加上一项文件的MIME TYPE,来识别文件类型,这个动作是由浏览器完成的,服务端可以检查此类型。不过这也不能保证上传文件的安全,因为HTTP Header可以被中间人修改。

<?php

 if (isset($_POST['Upload'])) {

    $target_path = DVWA_WEB_PAGE_TO_ROOT."hackable/uploads/";

    $target_path = $target_path . basename($_FILES['uploaded']['name']);

    $uploaded_name = $_FILES['uploaded']['name'];

    $uploaded_type = $_FILES['uploaded']['type'];//获取MIME值

    $uploaded_size = $_FILES['uploaded']['size'];

    if (($uploaded_type == "image/jpeg") && ($uploaded_size < 100000)){

       if(!move_uploaded_file($_FILES['uploaded']['tmp_name'], target_path))

{

         echo '<pre>';echo 'Your image was not uploaded.'; echo '</pre>';

         } else {               

          echo'<pre>';echo $target_path.'succesfully uploaded!';echo '</pre>';        

               }

       } else{

          echo '<pre>Your image was not uploaded.</pre>';

            }

        }

?>

五、反序列化漏洞

1、反序列化的概念

从字面意思看,反序列化就是序列化反向操作的过程,那么什么是序列化呢?序列化说通俗点就是把一个对象变成可以传输的字符串。而json(JavaScript Object Notation)就是一种轻量级的数据交换格式,其可以将JavaScript中的对象转换为字符串,然后在函数、网络之间传递。而反序列化就是把那串可以传输的字符串再变回对象。在PHP中,序列化函数是serialize(),反序列化函数是unserialize()。

什么要有序列化与反序列化呢?假设我们写了一个class,这个class里面存有一些变量。当这个class被实例化了之后,在使用过程中里面的一些变量值发生了改变。如果以后在某些时候还会用到这个变量,而采取一直保存这个class不销毁,等着下一次再调用的话,会浪费系统资源。对于大项目,资源浪费问题会被放大,从而会产生很多麻烦。PHP就可以把这个对象序列化了,存成一个字符串,这样占用内存较小。如果再用的时候,再反序列化,重新生成对象,这样可有效节省了资源。另外,要进行数据传输,也需要生成字符串。常见的序列化格式包括:json字符串、xml字符串、字节数组、二进制格式。可以这样讲,序列化的目的就是方便存储和传输。

如下面例子:

class Student {

    public $name = "Shanshan";

    public $sex = "women";

    public $age = "18";

}

$example = new Student ();

$example->name = "John";

$example->sex = "man";

$example->age = "28";

$val = serialize($example);

echo $val;

定义了Student类,其中有$name、$sex、$age三个变量,$example是Student的对象,并且这个对象的变量值发生了变化,如果要保存$example对象中三个变量的值,可以采用serialize()函数将其序列化。通过echo命令,可看到序列后的字符串,如下图所示:

从图中可以看到对象序列化成John的格式,其是根据双引号(")、冒号(:)、逗号(,)及花括号({})区分各字符意义的,其中的字母或数字含义如图标识所示。这种格式便于保存和传输。如果要用该字符串还原出原先的对象值,用unserialize()函数反序列化即可。

2、反序列化漏洞产生的原因

为什么会产生反序列化漏洞呢?这和魔术方法有关。在PHP中有一些以双下划线开头的方法,如__construct()、__destruct(),他们不需要手动调用,它会在某一时刻自动执行,为程序的开发带来极大的便利。

常见的魔法函数如下表:

函数名

函数作用

__construct()

称之为构造函数,当创建对象时会自动调用。

__destruct()

称之为析构函数,当对象被销毁时会自动调用。对象的显式和隐式销毁两种,其中显试销毁是指当对象没有被引用时就会被销毁,如unset或为其赋值NULL;隐式销毁是指PHP是脚本语言,在代码执行完最后一行时,所有申请的内存都要释放掉。

__wakeup()

使用unserialize时触发

__sleep()

使用serialize时触发

__call()

在对象上下文中调用不可访问的方法时触发

__callStatic()

在静态上下文中调用不可访问的方法时触发

__get()

用于从不可访问的属性读取数据

__set()

用于将数据写入不可访问的属性

__isset()

在不可访问的属性上调用isset()或empty()触发

__unset()

在不可访问的属性上使用unset()时触发

__toString()

把类当作字符串使用时触发

__invoke()

当脚本尝试将对象调用为函数时触发

魔术函数用法如下例所示:

class MagicClass{

    public function __toString(){

        return "class to string.<br>";

    }

    public function __construct(){

        echo "call __construct function.<br>";

    }

    public function __destruct(){

        echo "call __destruct function.<br>";

    }

}

$im = new MagicClass();

echo $im;

执行此代码将会出现如下显示:

当创建MagicClass对象$im时,将会自动调用__construct()函数,echo $im语句将一个对象当作字符串使用,自动调用了__toString()函数,执行完语句对象$im隐式销毁,自动调用了__destruct()函数,因此显示如上图所示,而不是按照MagicClass类中函数定义的顺序执行。

此时,我们可以将echo $im语句更换了print_r($im),显示将如下图:

在这里,并没有把$im当作字符串使用,因此没有调用__toString()函数,因此显示有所不同。

如果服务器接收反序列化过的字符串、并且未经过滤就把其中的变量直接放进这些魔术方法里面的话,就容易造成很严重的漏洞。下面通过示例来说明:

在XAMPP\HTDOCS\WEBPEN文件夹下建立serialize.php文件,内容如下:

class Student{

    var $name = "shanshan";

    function  __destruct(){

            echo $this->name;

    }

}

$a = $_GET['input'];

$student1 = unserialize($a);

在此例中,服务器接收了用户输入的变量,未经过滤就把变量直接放进魔术方法__destruct()中。

在URL中输入:

http://127.0.0.1/webpen/serialize.php?input=O:7:"student":1:{s:4:"name";s:29:"<script>alert(/xss/)</script>";}

将显示弹出对话框,即可利用此漏洞进行跨站脚本攻击。

根据什么构造出input参数呢?input参数是根据序列化的结果来构造和。构建一个poc.php文件,内容如下:

class Student{

    var $name = "shanshan";

    function __destruct(){

            echo $this->name;

    }

}

$stu = new Student();

$stu->name = "<script>alert(/xss/)</script>";

$val = serialize($stu);

echo $val;

即把serialize.php中的Student类引进来,然后新建对象,把要输出的值赋给$name变量。然后序列化,并输出,即可得到如下字符串:

O:7:"Student":1:{s:4:"name";s:29:" <script>alert(/xss/)</script>";}

(注意:在输出过程中由于存在JavaScript代码,因此会执行此代码)。

通过示例,可以看到反序列漏洞利用的过程:

(1) 需要有一个漏洞触发点,即需要用到unserialize()函数,在例中$student1 = unserialize($a)。

(2) 需要有一个相关联的,有魔术方法(会被自动调用)的类。Student类中有__desctruct()魔术方法。

(3) 漏洞的效果取决于__destruct 这个魔术函数内的操作,这里是输出变量,因此可在客户端执行相关操作。

(4) 构建poc.php ,利用程序,先序列化,得到序列化值。

(5) 从可控输入$_GET['input']把序列化值输入进去。

3、反序列化漏洞的检测与防御

反序列化一般通过代码审计的方式发现。

和大多数漏洞一样,反序列化的问题也是用户参数的控制问题引起的,所以好的预防措施就是不要把用户的输入或者是用户可控的参数直接放进反序列化的操作中去。

;