# php代码审计


标签(空格分隔): php 代码审计


---


#0 前言


#1 环境要求


##1.1,测试资源


(1)待测试设备的ip地址,web入口,包括常用用户的登录名和密码


(2)后台登陆方式,包括端口,协议,用户名和密码,代码是否加密,如果加密了的话需要解密;登陆是否需要密钥,密钥密码等


(3)web服务重启方式,web日志位置,


(4)数据库位置,数据库启动和登陆方式


(5)可访问性,确认加上了相关路由


##1.2,测试内容


(1)新增功能模块和重点关注模块及其代码位置

(2)设备上的一些常用配置,新增功能,尽量配置好,这样在测试的时候就免去了再学习如何使用的成本。


##1.3,测试规划



这些要求要直接告诉相应产品的测试人员,基本上都是必须品,有助于对产品的更深一步的测试。


#2 端口开放情况


[root@home2]# netstat -anp|grep -i listen

tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN 2768/mysqld-max

tcp 0 0 127.0.0.1:6666 0.0.0.0:* LISTEN 2720/deamond

tcp 0 0 127.0.0.1:2222 0.0.0.0:* LISTEN 2712/avsvr

tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 2738/sshd

tcp 0 0 0.0.0.0:25 0.0.0.0:* LISTEN 2960/smtpd

tcp 0 0 127.0.0.1:6010 0.0.0.0:* LISTEN 23075/sshd

tcp 0 0 127.0.0.1:6011 0.0.0.0:* LISTEN 30617/sshd

tcp 0 0 :::80 :::* LISTEN 2800/httpd

tcp 0 0 :::22 :::* LISTEN 2738/sshd

tcp 0 0 ::1:6010 :::* LISTEN 23075/sshd

tcp 0 0 ::1:6011 :::* LISTEN 30617/sshd

tcp 0 0 :::443 :::* LISTEN 2800/httpd

unix 2 [ ACC ] STREAM LISTENING 5865 2768/mysqld-max /var/lib/mysql/mysql.sock

unix 2 [ ACC ] STREAM LISTENING 5956 2804/httpd /home2/eqhttpd/logs/cgis


对于上述事例中除22,443端口之外的端口一定要深入追究,为什么开放,开放的必要性。特别是那些监听在任意地址的端口,如25,80,3306(MySQL),5432(PostgreSQL)6379(redis),27017(mongodb)等端口。


#3 漏洞扫描器&web扫描器


(1)系统扫描 可以使用Nessus进行系统漏洞扫描,对于高危漏洞一般都需要修复,中危漏洞要谨慎评估其影响。


(2)web扫描可以使用AWVS和burpsuit,AWVS可以用在代码审查前,一边审查代码,一边扫描,查看扫描报告就能得到目标系统的大概架构。burpsuit也可以用来进行web扫描,但是在代码审查阶段更多是使用重放功能,就是说构造poc。


#4 常用安全配置


首先得清晰这些常用配置的作用


##4.1 数据库


一般是独立的运行用户,如无必要一般监听在本地。


##4.2 php.ini


一定得设置:


1. display_errors = Off

关闭错误信息显示

2. register_globals = Off

防止出现变量覆盖

3. allow_url_fopen = Off

禁止php引用远程文件

4. expose_php = Off

禁止显示php版本信息,默认 On



建议设置:


1. open_basedir

限制脚本访问文件的路径

2. disable_functions=phpinfo,

禁用有风险的php 函数


其他建议设置magic_quotes_gpc开关,这个php 5.4版本中废除,safe_mode等


##4.3 apache


尽量少暴露系统信息,这样apache版本和系统版本都不会被暴露


SeverSignature off

ServerTokens ProductOnly

User nobody

Group nobody #独立运行用户

options - Indexes #关闭索引目录

DocumentRoot /应为web首页目录



#5 关键函数


##5.1 登入,登出,密码修改


确认所有的信息都是在登录后才能查看或者说只能有了相应的权限才能查看相应的配置,咱们产品的一般的登录过程:


1)判断登录session,如果超时了直接退出


2)验证用户名的合法性'/^[a-z|A-Z][a-zA-Z0-9-_]{1,19}$/'


3)产生一个sessionid与登录前的不一样

session_regenerate_id ( true );


4)判断证书的状态


5)从数据库找出相应用户,匹配其密码


6)如果用户名密码一致了就登录成功一小段,不管成功与否记录登录时间,登录次数,登录ip,密码不对直接退出到登录。


7)判断密码的长度,复杂度,密码的过期时间,不符合要求提示用户修改密码


8)密码输错重新来过,超过次数和时间限制一段时间后才能再登录。


密码修改主要是防止低权限修改高权限用户的密码。


##5.2 常用的封装函数


在一些接口的封装中,一定得明白其工作原理才能有效的做好参数过滤。举个例子,某产品中重新封装了执行命令exec,将参数中的;,&,|这三个字符替换成了\,而在外层调用的时候再对参数escapseshellcmd,须不知escapseshellcmd只是在那些shell元字符之前加了反斜杠,当碰上echo 1\;id,经过两层处理后就变成了echo 1\\\\;id,反斜杠倒是把反斜杠给过滤了,;解放出来。


#6 注入问题


注入问题=危险函数+可控参数,危险函数就包括include,system,read,mysql_query,eval等等,可控参数就是完全没经过过滤或者过滤不严格,或者一些特性绕过等。在php中,这些全局变量```($_GET,$_POST,$_COOKIE,$FILES,$_SERVER,$_ENV ,$_REQUEST)```可被直接访问到。在这一步中,一般可以通过直接搜索关键字得到。在这一步中,我经常会用到“Seay源码审计系统”这个工具,它是依据危险函数加$来正则匹配的,比直接关键字搜索会好一点。


##6.1 包含漏洞


php的文件包含函数有include,include_once,require,require_once,它可以把文件当作代码来执行。


###6.1.1 本地文件包含LFI


本地文件包含的利用技巧:一般的系统都会限制结尾为.php,所以就会用到截断,截断的技巧有两个:


1)gpc=Off,php<5.3.4可用%00来截断,如果gpc开启的话%00会变成\0就无法利用。


2)长文件名截断,linux超过4096字节就会被php截断,没有实验成功(5.2.8),windows 259字节,没尝试,这里与GPC没有关系。 linux下使用../../../../../etc/passwd/./././././././ ,windows使用.....,楼下连接测试成功或者和linux一样。


3)转换字符集造成截断,iconv()截断


4) 通过伪协议绕过,比如说zip(或者phar)协议绕过,新建一个1.jpg,里面包含要执行的php代码,压缩成zip,改名为xx.jpg,然后上传去包含,包含的url:include.php?include=zip://var/tmp/1.jpg%231.php


截断完之后就是如何利用了,利用技巧有挺多,具体如下:


1)包含上传后的文件,比如说图片,文件等


2)data:// php://input等伪协议,需要allow_url_include=on


3)日志文件,access.log,通过nc来去掉header,防止空格被编码成%20.


root@kali:/var/log/apache2# nc 127.0.0.1 80

GET / HTTP/1.1

查看access.log有日志:

127.0.0.1 - - [09/Apr/2015:03:53:21 -0400] "root@kali:/var/log/apache2# nc -h" 400 582 "-" "-"

127.0.0.1 - - [09/Apr/2015:03:53:45 -0400] "GET / HTTP/1.1" 404 480 "-" "-"


然后直接加载该日志,如果日志很大的话,可以写一句话创建一个文件什么的,执行一次就能拿到shell

eg:


");fclose($fp);?>


4)/proc/self/environ中user-agent可改about:config 后加general.useragent.override ,value 填 即可,或者使用useragent switch

测试过程中,要么是没权限,要么是在这个文件中没有读到user-agent字段


5)session file


6)其他应用程序的日志,如ftp,ssh等


###6.1.2 远程文件包含RFI


远程文件包含必须开启allow_url_include=On(默认关闭),allow_url_fopen=On(默认为On)才能利用,一般少见。


##6.2 命令执行


命令执行有system,passthru,pcntl_exec,shell_exec,exec,popen,proc_open,一般通过escapseshellcmd来过滤参数。由于历史原因或者开发者的爱好,都不爱使用系统自带的函数,而是转用执行命令来实现。如果参数没做好控制就会导致很多的问题。最近审查出来的严重问题有一半是命令执行。


##6.3 任意文件操作


(1) 任意文件读取,函数有file_get_contents,fopen,readfile,fgets,fread,parse_ini_file,highlight_file,fgetss,show_source

(2) 任意文件写入删除,函数有unlink,copy,fwrite,file_put_contents,bzopen,rename,这里面就会有任意文件删除,删除一些重要的key,配置文件等,重命名覆盖一些重要配置文件,或者重命名为一些含通配符的名字导致linux通配符的问题等。


##6.4 目录穿越


严格来说它应该是文件操作的一种,只是它出现的太频繁了,所以单独抽出来,一般通过../回到更上层目录来达到目录穿越的目的,可通过设置白名单来规避:`$w_reg = '/^[A-Za-z]{0,10}\.[A-Za-z]*$/';`。特别注意通过黑名单来过滤的时候只过滤../,这样***者设计....//就达到绕过目的了。


##6.5 文件上传


文件上传可以算是***工程师最喜欢的功能。一般通过此来上传webshell,所以危害极大。关键函数move_uploaded_files,一般代码中都是通过客户端和服务端做规避,客户端的验证很容易绕过,这里就不多涉及,主要是为了用户友好性和减轻服务器压力在客户端做一些限制。服务端的话一般通过对文件重命名,文件后缀白名单,文件大小限制来解决,如果能将上传目录设置为php不可执行必然是更好。

常见的bypass策略:


(1)客户端限制通过burpsuit等代理工具直接绕过


(2)后缀黑名单的利用点有大小写,名单列表,0x00截断,Apache,IIS,Nginx解析漏洞,。htaccess文件***


(3)后缀白名单的利用点是0x00截断绕过,LFI,IIS和Nginx解析漏洞


建议的代码规范要求:


(1)通过文件名检查或者文件重命名来规避路径穿越,重命名一般以特殊名字+日期+用户session某个字段作为名字因子,造成hacker不可猜测.


(2)后缀和扩展名尽量不用黑名单控制,黑名单的***方式比白名单的***方式多的多,稍微不小心就被绕过,建议白名单处理。


(3)限制文件大小



##6.6 sql注入


sql注入一般都是程序员自己拼接sql语句导致的,而且出现的频率非常高,造成的危害也挺大。从数据库的角度来看,select语句可通过union来注入;update语句在set位置就看哪个列被展示出来就利用哪个列,在where之后就采用盲注;insert就把要导出的数据插入到该列中;delete语句通过盲注来实现。简单检测就可以通过',\,"来测试。从php方面来看,常见的危险地方有:


1)解码编码处,一些参数先经过urlencode,base64encode,XML处理之后,不能被转义,decode之后,直接导致注入,无视GPC


2)SERVER处注入,由于GPC默认是不处理SERVER参数的,像这些参数QUERY_STRING,X_FORWARDED_FOR,CLIENT_IP,HTTP_HOST,ACCEPT_LANGUAGE,最常见的还是X_FORWARDED_FOR,一般用于处理ip有关的位置。


3)FILES处注入,FILES不受gpc影响,通常是在上传的名字上有问题,这里同样有其他注入问题。


4)变量覆盖,造成变量覆盖的函数有extract,parse_str,$$,如果直接从$_POST取出再导入数据查询就会引入问题。


5)未初始化,在register_globals=On的情况下导致直接在url参数中写入变量,php>=4.2已经默认关闭。


6)二次注入,入库的时候经过了全局转义,入库后消失,出库的时候就被带出来了。eg:


mysql> insert into users(first_name) values('lxx\'')

-> ;

Query OK, 1 row affected (0.00 sec)

mysql> select first_name from users limit 1;

+------------+

| first_name |

+------------+

| lxx' |

+------------+

1 row in set (0.00 sec)


但是这种一般有数据库字段长度的限制的条件。


7)宽字节注入,一种情况是php编码与数据库的编码方式不一致导致的,另外一种情况是iconv转换字符集的时候导致的。举个例子:

`錦'-->錦\'-->\xe5\\\\'`,首先是转义,然后再iconv utf8 to gbk 两个反斜杠一结合单引号就出来了。


>>> '錦'

'\xe9\x8c\xa6'

>>> '錦'.decode('utf8')

u'\u9326'

>>> '錦'.decode('utf8').encode('gbk')

'\xe5\\'

>>> '錦\\'.decode('utf8').encode('gbk')

'\xe5\\\\'

8)数组字符串混用,大体意思就是程序员取了个数组,而***者传入了字符串,那在取数组第一个字符的时候,实际上变成了转义后的字符串的第一个字符,也就是\,很容易就干掉了后续的单引号。


9)截取半段字符,比如说截断7个字符,刚好到xxxxxx\',那刚好就到反斜杠了。


10)replace单引号,各种莫名其妙的replace,比如说将',"置为空等。


sql注入的核心就是匹配单引号,再加一个可控参数,匹配单引号的方式有很多。

1)避免转义,避免转义的方式就有SERVER注入,FILES注入,编码解码,数字型注入。

2)吃掉反斜杠,有宽字节注入吃掉,入库吃掉

3)吃掉单引号,数组字符串混用,截取半段字符,replace单引号

后两种都是在加了转义的前提下一些不恰当的编码方式导致的,前一种则无视是否有转义。

sql注入的防护一般是通过参数话查询来实现,eg:

```php

$stmt = $mysqli -> prepare("SELECT priv FROM testUsers WHERE username=? AND password=?");

$stmt -> bind_param("ss", $user, $pass);

$stmt -> execute();

```


##6.7 xss


xss主要是在输出的时候做好控制,本质上还是在输入的时候引出的,可盗取用户的cookie等,在输出的时候过滤使用htmlspecialchars,它会转化&,",',<,> 这五个字符。


##6.8 任意代码执行


函数有```php

array_map,``,eval,assert,preg_replace../e,call_user_func```,一般的shell都会通过这些函数来实现。


##6.9 csrf问题


当web系统的一些关键性操作,如:重启系统、付款、留言、添加删除用户等功能,仅仅通过简单的get请求来实现时,那么就容易导致csrf***。***者可以突破cookie的限制,通过伪造合法用户的请求来打到***站点的目的。


测试时需要仔细通读核心操作的代码,判断对应功能是否加入了动态判断,以确认是否可以被csrf***的风险。即使是post操作,或者判断refer字段,也同样可以被csrf利用,一般通过随机token来规避。


#7 常见PHP安全函数工作原理


1)escapeshellcmd,防止命令注入,是对shell元字符添加反斜杠,具体有#&;`|*?~<>^()[]{}$\, \x0A 和 \xFF,类似的函数还有escapseshellarg


2)addslashes,为了数据库查询语句等的需要在某些字符前加上了反斜线。这些字符是单引号(')、双引号(")、反斜线(\)与 NUL(NULL 字符) 这个一般用于sql语句,但是现在也应该抛弃。


3)htmlspecialchars,主要就是转换```&,',",>,< to &,",',',<,>```,用于防止xss


4)mysql_real_escape_string — 转义 SQL 语句中使用的字符串中的特殊字符,并考虑到连接的当前字符集,主要转义\x00,\n,\r,\,',",\x1a,同理的还有pg_escape_string,建议用这些函数来替代addslashes。


只有理解了这些函数的工作原理,才能对其进行进一步封装。


#其他问题


1)xxe问题,libxml库版本<2.9,通过php.ini可以得知,可以导致信息泄漏,ddos,数据导出等问题


2)XPATH注入