无论在CTF还是实际渗透过程中,代码审计都是重要的一环,如果开发者没有对数据进行过滤或者在不恰当的位置使用了某些函数,往往会造成意想不到的漏洞。下面总结一下易造成危害的函数,提高我们在实际开发或者代码审计方面的敏感性,做到更加安全的开发。
代码执行类
eval
这个函数大家应该很熟悉,函数的意思是将字符串的内容当做为PHP代码执行,比如我们常用到的一句话木马,在杀毒软件中,大多数已经把eval列入黑名单了,因此需要更加隐蔽的后门,如下:
1 | <?php |
creat_function
这个函数会创建一个匿名的函数,并返回独一无二的函数名。第一个参数是参数,第二个参数是内容。结构类似于:
1 | create_function('$a,$b','return 111') |
如果传入的如下内容,便可造成代码执行:
1 | create_function('$a,$b','return 111;}phpinfo();//') |
php7.2以后弃用了create_function
函数,继续使用将会发出警告。
preg_replace
这个函数的作用是执行正则表达式,这个函数的原型为mixed preg_replace ( mixed pattern, mixed replacement, mixed subject [, int limit])
,如果在构造正则表达式的时候,使用了/e
修正符,这时,preg_replace
会将replacement
参数当做php代码执行。
如下所示:
1 | preg_replace("/test/e","phpinfo();","jutst test") |
php5.5后/e
修饰符已被弃用,php7以后不在支持/e
修饰符。
assert
断言,这是一个调试函数,它会检测一个断言是否为False。它与eval的用法类似,可以将字符串当成PHP代码来执行,但是断言这个功能应该只被用来调试。
php7以后assert
默认不再可以执行代码,菜刀在实现文件管理器的时候用的恰好也是assert函数,这也导致菜刀没办法在PHP7上正常运行。
命令执行类
以下函数在参数可控的情况下,往往可以通过截断,管道等方法执行任意命令,需要注意的一点是php7以后exec
, system
和passthru
将会有空字节保护 。
exec
exec执行command命令,但是不会输出全部结果,而是返回结果的最后一行,如果想得到全部的结果,可以使用第二个参数,让其输出到一个数组。
1 | string exec ( string $command [, array &$output [, int &$return_var ]] ) |
passthru
类似于exec,但passthru直接将结果输出,不返回结果,不用使用echo查看结果。
system
与passthru的基本相同,但是system返回结果并且输出。
shell_exec
通过shell环境执行命令,并且将完整的输出以字符串方式返回。
1 | 类似于:<?php echo `pwd` ?> |
proc_open
执行一个命令,并且打开用来输入的文件指针
popen
使用 command 参数打开进程文件指针,如果出错,该函数返回 FALSE。
文件操作类
在需要传filename的地方,基本可以使用流协议,往往可以绕过特殊字符的过滤写入文件或读取文件,造成文件泄露,下面将会展示此类用法。
file_get_contents
把整个文件读入一个字符串中,类似于file函数,不过file函数是将文件读入一个数组中,变量名可控下往往会造成任意文件读取。
file_put_contents
将一个字符串写入文件,文件名可控下可以通过php伪协议来传入字符,如下:
fopen
打开文件或者 URL。如果打开失败,返回false。
move_uploaded_file
将上传的文件移动到新位置
readfile
输出一个文件,该函数读入一个文件并写入到输出缓冲,若成功,则返回从文件中读入的字节数。若失败,则返回false。
unlink & delete
删除文件
rmdir
删除目录
文件包含类
包含函数一共有四个,他们分别为:require
、include
、require_once
、include_once
,主要作用为包含并运行指定文件中的代码。
在不同的配置环境下,可以包含不同的文件,因此又分为远程文件包含和本地文件包含,建议将远程包含开关allow_url_include
设置为off,如果开始是打开,它可以包含网上任意文件,这样一来网站十分危险。
require & include
- include 和 require语句用于在执行流中插入写在其他文件中的有用的代码。
- include 和 require除了处理错误的方式不同之外,在其他方面都是相同的:
1 | require 生成一个致命错误(E_COMPILE_ERROR),在错误发生后脚本会停止执行。 |
require_once & include_once
- include_once (require_once)语句在脚本执行期间包含并运行指定文件。此行为和 include (require)语句类似,区别是如果该文件中的代码已经被包含了,则不会再次包含,只会包含一次。include_once(require_once)需要查询一遍已加载的文件列表, 确认是否存在, 然后再加载。
特殊函数
is_numeric
判读是否为数字,如果提交的参数是数字或者数字字符串就正常,也就是TRUE,否则返回FALSE。仅用is_numeric判断而不用intval函数转换,就有可能插入16进制的字符串到数据库,进而可能导致sql二次注入。
php7以后十六进制字符串不再被认为是数字。
glob
可以筛选出当前目录下的文件,可以进行条件控制。
1 | <?php print_r(glob("*.php)); ?> |
strcmp
字符串对比,可以使用数组绕过,类似的含有sha
函数, sha
函数无法处理数组,同时返回flase,判断相同。
1 | <?php |
strlen
返回字符串的长度
1 | 如果strlen()限制了长度小于8并且大小必须大于9999999,可以构造1e8=100000000 > 9999999 |
parse_url($url, PHP_URL_HOST)
- 如果代码规定
parse_url($url, PHP_URL_HOST) !== "www.baidu.com"
, 可以利用http的解析规则绕过host的限制:http://@127.0.0.1:[email protected]/admin.php
弱类型
- 在php中,使用两个等号来判断时,只需要两个变量的值相同即可返回true,否则返回false。而使用三个等号,则需要两个变量不仅要值相同,并且两个变量的类型也必须相同时才会返回true,否则返回false。
- 构造
md5('QNKCDZO') == md5('s878926199a')
便可绕过md5的检测,因此敏感的地方建议使用===
或者!==
.
小结
余弦大佬曾经说过,web安全重点就三个词——“输入”,“输出”,“数据流”。前面提到的函数都有着自己特定的功能,是开发过程中需要用到的。所谓有输入便有风险,攻击者往往不会按照开发者想要的情况来进行操作,因此在防护安全方面,永远不要相信用户输入的内容。