说明
该文章来源于徒弟lu2ker转载至此处,更多文章可参考:https://github.com/lu2ker/
文章目录
- 说明
- PHP中有一些内置类
- PHP反序列化问题
- 绕过姿势:
- 魔术方法(反序列化如何利用)
- CMS可能存在的部分逻辑问题
- $_REQUEST相关安全问题
- $_SERVER['PHP_SELF']问题
- array_merge 和 + 号 的区别
- 关于PDO的一些事
- 判断文件是否存在
- 函数调用时的命令空间问题
- 代码执行的一个小tip 用“.”拼接来return时执行
- 关于PDO的一些事
- 总结
- 关于过滤__php标签这件事
- 0x00 前言:
- 0x01 问题
- 0x02 思路一
- 0x03 思路二
- 0x04 思路三
PHP中有一些内置类
SimpleXMLElement
其中当$data
为XML数据,$options=2
时易造成XXE注入。这里
final public SimpleXMLElement::__construct ( string $data [, int $options = 0 [, bool $data_is_url = FALSE [, string $ns = "" [, bool $is_prefix = FALSE ]]]] )
GlobIterator
其中$pattern
为要搜索的文件名,$flags
may be a bitmask of the FilesystemIterator constants。
public GlobIterator::__construct ( string $pattern [, int $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO ] )
PHP反序列化问题
主要是总结传入序列化字符串如何绕过过滤 以及 反序列化为一个对象时魔术方法的调用
绕过姿势:
-
序列化字符串中 代表这是一个对象的序列化字符串的
O:
字符后面添加一个+
可以绕过形如/O:\d:/
这样的正则,因为源码var_unserializer.c
中有代码能够解析O:+数字
这样的对象序列化。 -
对于
protected
和private
类型的变量在序列化的时候有点复杂:对象的私有成员具有加入成员名称的类名称;
受保护的成员在成员名前面加上**’*’**。
这些前缀值在任一侧都有空字节。
这里有两种办法使字符串完整:①在空字节位置添加
%00
;②使用S:5:"\00*\00op"
,因为如果类型为S
,PHP的源码会将\00
按十六进制处理,也就是空字节。
还有一种情况就是php7.1+版本对属性类型不敏感,使用public就可以正常反序列化。 -
序列化字符串的逃逸。
魔术方法(反序列化如何利用)
-
看这篇文章挺好的。
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发
一道质量很高的PHP反序列化CTF题------提取码:54dt
CMS可能存在的部分逻辑问题
- install.php在安装完成后应该删除或者做访问控制,当已安装时再访问,应当在输出警告信息后退出程序,否则程序继续执行的话,就会导致重装漏洞。审计的时候注意
die()
和exit()
$_REQUEST相关安全问题
- $_REQUEST获取参数的顺序:这个主要看php.ini是如何配置的,一般情况都是GPCS的顺序,即GET、POST、COOKIE、SERVER,且从左往右优先级增大。
$_REQUEST
是直接从GET,POST 和 COOKIE中取值,不是他们的引用。即使后续GET,POST 和 COOKIE
发生了变化,也不会影响$_REQUEST
的结果。
- **PHP特性:**①php自身在解析请求的时候,如果参数名字中包含空格、
.
、[
这几个字符,会将他们转换成_
。②通过$_SERVER['REQUEST_URI']
方式获得的参数并不会进行转换。
另外一个特性是:当我们使用HPP(HTTP参数污染)传入多个相同参数给服务器时,PHP只会接收到后者的值。(这一特性和中间件有关系)
可以利用这些特点绕过某些过滤WAF。审计的时候要注意WAF处理的参数是怎么获取的,业务处理的参数是怎么获取的。
$_SERVER[‘PHP_SELF’]问题
‘PHP_SELF’
当前执行脚本的文件名,与 document root 有关。例如,在地址为 http://example.com/foo/bar.php 的脚本中使用 $_SERVER[‘PHP_SELF’] 将得到 /foo/bar.php。
__FILE__
常量包含当前(例如包含)文件的完整路径和文件名。如果 PHP 以命令行模式运行,这个变量将包含脚本名。‘PATH_INFO’
包含由客户端提供的、跟在真实脚本名称之后并且在查询语句(query string)之前的路径信息,如果存在的话。例如,如果当前脚本是通过 URL http://www.example.com/php/path_info.php/some/stuff?foo=bar 被访问,那么 $_SERVER[‘PATH_INFO’] 将包含
/some/stuff
。
所以,当URL是PATH_INFO这种形式时,$_SERVER[‘PHP_SELF’]的内容对于攻击者来讲是可控的,
P神绕360webscan的分析
array_merge 和 + 号 的区别
搬自https://www.cnblogs.com/corvus/p/14383327.html
array_merge()
array_merge() 将一个或多个数组合并起来,一个数组中的值附加在前一个数组的后面。返回作为结果数组
1.字符串键 后面的值会覆盖前面的一个值。
2.数字键,后面的值将不会覆盖原来的值而是附加到后面(数字键会重新分配,总是变成重零开始)
3.如果只给了一个数组并该数组是数字索引,则键名会以连续重新索引
+号合并数组
数组中有相同的键名(不管是不是数字),则会把最先出现的值作为最终的返回,而把后面的数组拥有相同键名的那些值 抛弃掉,数字索引不会重新排序
关于PDO的一些事
我这里参考师傅们的文章只整理了一份小总结,没有案例:关于PDO的一些事
具体的去看phith0n和haby0两位师傅的文章吧。
判断文件是否存在
测试发现Win下用../
进行跨盘符访问时,is_file()和file_exists()都返回了false,貌似是不支持win下形如D:/a/b/c/../../../../C:/Windows/win.ini
的路径,应该是无法访问到盘符的上级(盘符的上级是啥还真没想过),而linux下通过多个../
最终也就是限制在根目录(这么说应该也叫盘符的根目录),而没有跨到盘符的上级,然并卵,但记。
函数调用时的命令空间问题
PHP默认的命名空间为\
,直接用函数名调用函数相当于用命名空间的相对路径来调用,而使用如:\user()
就相当于是在用绝对路径来调用user函数了。
如果在当前命名空间调用系统类,就必须用绝对路径的写法。
by https://www.t00ls.com/viewthread.php?tid=67133&extra=page%3D2%26amp%3Borderby%3Dlastpost%26amp%3Bfilter%3D86400
代码执行的一个小tip 用“.”拼接来return时执行
return xxxxx.phpinfo()//
关于PDO的一些事
PDO主要是这个选项
PDO::ATTR_EMULATE_PREPARES => false
这个选项涉及到PDO的“预处理”机制:因为不是所有数据库驱动都支持SQL预编译,所以PDO存在“模拟预处理机制”。如果说开启了模拟预处理,那么PDO内部会模拟参数绑定的过程,SQL语句是在最后execute()
的时候才发送给数据库执行;如果我这里设置了PDO::ATTR_EMULATE_PREPARES => false
,那么PDO不会模拟预处理,参数化绑定的整个过程都是和Mysql交互进行的。
非模拟预处理的情况下,参数化绑定过程分两步:第一步是prepare阶段,发送带有占位符的sql语句到mysql服务器(parsing->resolution),第二步是通过execute()函数多次发送占位符参数给mysql服务器进行执行(多次执行optimization->execution)。
这时,假设在第一步执行prepare($SQL)
的时候我的SQL语句就出现错误了,那么就会直接由mysql那边抛出异常,不会再执行第二步。(From phith0n)
PDO默认支持多语句查询,如果php版本小于5.5.21或者创建PDO实例时未设置PDO::MYSQL_ATTR_MULTI_STATEMENTS
为false时可能会造成堆叠注入(From haby0)
翻译过来就是当它设置为False时PDO::prepare() 和 PDO::query() 就会禁用多查询,
PDO的配置放在实例化时的第四个参数位置,类型为数组。
new PDO($dsn, $user, $pass, array( PDO::MYSQL_ATTR_MULTI_STATEMENTS => false))
PS:有个小问题,非模拟预处理下PDO::prepare()才会和数据库交互,而非模拟预处理是不存在堆叠注入可能性的,官方手册里却还写上了这是为啥?感觉这里应该写成PDO::execute()才对。
总结
- 并不是说使用了PDO就是安全的
- pdo的模拟预处理和非模拟预处理
- 模拟预处理:在PDO内部模拟参数绑定的过程。
PDO::ATTR_EMULATE_PREPARES => true
- 非模拟预处理:参数化绑定的整个过程都与数据库交互,就是交给数据库去预编译,
PDO::ATTR_EMULATE_PREPARES => false
- 认为可以理解为模拟预处理和预处理…
- 模拟预处理:在PDO内部模拟参数绑定的过程。
- 可控变量直接拼接的前提下:
- 模拟预处理下,execute阶段会造成注入,可以使用堆叠注入
- 非模拟预处理下,不存在堆叠注入,会在prepare阶段报错,但还可以用其他注入类型(毕竟是直接拼接了)。
- 比如既然会在prepare阶段报错,那么理所应当可以使用报错注入,但是要回显错误信息的话还需要设置
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
- 综上再copy一下haby0给的安全建议:
- 使用PDO时尽量使用非模拟预处理
- PDO最好配置
PDO::MYSQL_ATTR_MULTI_STATEMENTS => false
,禁止多语句查询(默认是true) - 不要直接拼接可控变量,占位符多好啊
案例的话,去看提到的两位师傅的文章吧。
关于过滤__php标签这件事
涉及到0day,暂不说明细节,只记录思路。
0x00 前言:
- 最近在审一套系统的时候,找到个sqli点
- 该系统搭建安装的时候默认mysql账号为root
- 对于into outfile写文件的条件也全部满足,遂想到用SQL注入写文件的方式来GetShell
0x01 问题
因为是用sqli写webshell,payload难免会需要绕过一些针对请求参数的过滤,这里就过滤了<、>、"
等特殊字符,导致没有办法用类似
select '<?php phpinfo()?>' into outfile '[webpath]'
的方式写入webshell,其中的尖括号会被html实体化编码,但是怎么能轻言放弃呢?
0x02 思路一
在该系统的web目录下有很多php空文件,但是留下了<?php
,比如根目录下的phpinfo.php内容如下:
<?php
这也是我当时立马相到的思路:通过读取phpinfo.php的内容,得到<?php
,使用select load_file(),这个方法需要如下条件:
- SQL注入点需要查询结果是两列的,因为
xxx union select load_file('phpinfo.php'),'phpinfo();' into outfile '[webpath]'
- 需要支持联合查询
- 如果删除了这些毫无意义的空文件就立马shell不了了
随之分享给师傅们,集思广益
0x03 思路二
hex编码进行绕过
关于SQL注入中使用hex编码来绕过一些过滤的操作应该ctf会比较常见,当时也是没想起来,姿势用时方恨忘啊,遂决定写下此文。
使用方法如下:select 0x3c3f70687020706870696e666f28293b3f3e into outfile '[webpath]'
,这样就不存在什么限制了。
0x04 思路三
师傅发来先知一篇文章,其中John 师傅在评论区详细阐述了一种也很棒的方法:UTF-7编码绕过。
总结就是一句话:php的多字节编码并启用UTF-7。例如将<?php phpinfo();?>编码为utf-7,+ADw?php phpinfo()+ADs?+AD4-就能绕过<?检测。
可以通过上传
.htaccess
php_flag zend.multibyte 1
php_value zend.script_encoding UTF-7
或者
.user.ini
zend.multibyte=1
zend.script_encoding=utf-7
来开启这个配置。另外该师傅人真好,还讲了一个小tips,原文如下:
如果遇到图片文件头检测,`.user.ini`可以不加注释直接打上`GIF89a`然后换行,不会报错。`.htaccess`会报500错误,用#注释掉又无法绕过,
不过`0x00`也是`.htaccess`的注释符,所以可以用`0x00`开头的图片文件头,例如ico `00 00 01 00 01 00 20 20`,这样既能绕过文件头检测,也不会报错。
师傅好强,学习。