苦于代码审计能力偏弱,调研一番发现这本书或许有所帮助。
第一部分 代码审计前的准备
代码审计环境搭建
- wamp/wnmp: WAMP(Windows下的Apache+Mysql/MariaDB+Perl/PHP/Python),WNMP(Windows下的Nginx+Mysql+PHP)
- lamp/lnmp:将上述环境安装在Linux中。
PHP_INI_*常量的定义
- PHP_INI_USER:该配置选项可在用户的PHP脚本或Win注册表中设置。
- PHP_INI_PERDIR:该配置选项可在php.ini. .htaccess或httpd.conf中设置。
- PHP_INI_SYSTEM:该配置选项可在任何地方设置。
- PHP_INI_ALL:该配置选项可在任何地方设置。
- php.ini only:该配置选项可仅可在php.ini中配置。
会影响PHP脚本安全的配置列表及核心配置选项
- register_globals(全局变量注册开关):该选项在on的情况下,会将用户GET/POST等方式提交上来的参数注册成全局变量并初始化值为参数对应的值,使提交参数可以直接在脚本中使用。register_globals在PHP版本小于等于4.2.3时设置为PHP_INI_ALL,从PHP5.3.0起被废弃,在PHP5.4.0中被移除。
代码实例:(实验环境php5.2.17,在php.ini中添加register_globals = On)1
2
3
4<?php
if($user=='admin'){
echo 'true';
} - allow_url_include(是否允许包含远程文件):在该配置为on的情况下,可以直接包含远程文件,当存在include(
$var
)且$var可控的情况下,可以直接控制$var变量来执行PHP代码。allow_url_include在PHP5.2.0后默认设置为off,配置范围为PHP_INI_ALL。与之类似的配置有allow_url_fopen,配置是否允许打开远程文件,但安全隐患没有前者大。
代码实例:(实验环境php5.2.17,payload:a=http://127.0.0.1:80/test/info.txt ,info.txt内容为<?php phpinfo();?>
)1
2<?php
include $_GET['a']; - magic_quotes_gpc(魔术引号自动过滤):该参数在不存在编码或其他特殊绕过的情况下,可以使很多漏洞无法利用。当该参数被开启时(选项设置为on),会自动在GET、POST、COOKIE变量中的单引号(‘)、双引号(“)、反斜杠(\)及空字符(NULL)的前面加上反斜杠(\),但在PHP5中magic_quotes_gps并不会过滤$_SERVER变量,导致很多类似client-ip、referer一类的漏洞能够利用。PHP5.3之后不推荐使用该参数,PHP5.4之后被取消。在PHP版本小于4.2.3时,配置范围是PHP_INI_ALL;在PHP版本大于4.2.3时,是PHP_INI_PERDIR。
代码实例:(测试?a=1’)1
2<?php
echo $_GET['a']; - magic_quotes_runtime(魔术引号自动过滤):过滤方式同样为加反斜杠,但和magic_quotes_gpc的处理对象不一样。magic_quotes_runtime只对从数据库或文件中获取的数据进行过滤,magic_quotes_runtime在PHP5.4之后被取消,配置范围是PHP_INI_ALL。但该参数仅对部分函数有作用,某些情况下可以被绕过。
代码实例:1
2
3
4
5
6#1.txt
1'2"3\4
<?php
ini_set("magic_quotes_runtime","1");
echo file_get_contents("1.txt"); - magic_quotes_sybase(魔术引号自动过滤):用于自动过滤特殊字符,当设置为on时,会覆盖magic_quotes_gpc=on的配置(使gpc=on失效)。与gpc的共同点是处理对象一致(GET、POST、Cookie),但该参数仅转义空字符以及把单引号变成双引号,使用率比gpc低。配置范围为PHP_INI_ALL,在PHP5.4.0中移除。(代码实例与gpc相同)
- safe_mode(安全模式):是PHP内嵌的一种安全机制,配置范围为PHP_INI_SYSTEM,PHP5.4之后被取消(取消原因是,PHP开发者认为在PHP语言机制上试图解决安全问题是一件不合适的事情,虽然safe_mode在一定程度上对共享主机有效,但同时也带来了不少误报,与其在PHP上解决权限安全问题,不如使用linux默认的权限限制机制或其它层级的解决办法)。该参数效果为,所有文件操作函数都会受到限制,非文件所有者不能对该文件进行操作(如include()),如果有一些脚本文件放在非Web服务启动用户所有的目录下,需要利用include等函数进行加载,可以使用safe_mode_include_dir来配置可包含的路径。此外,通过函数popen()、system()以及exec()等函数执行命令或程序会提示错误,如果需要使用外部脚本,可以集中存放,然后用safe_node_exec_dir来指向存放目录。
代码实例:1
2# echo `whoami`; 执行命令失败的回显提示
Warning: shell_exec() [function, shell_exec]: Cannot execute using backquotes in Safe Mode ... - open_basedir(PHP可访问目录):用于限制PHP只能访问哪些目录,通常只需要设置Web文件目录即可,如果需要加载外部脚本,也需要把所在路径加入该指令中,多个目录以分号分割。需要注意,指定限制实际上是前缀而不是目录名,如配置open_basedir=/www/a,那么/www/a和/www/ab都可以访问,所以为了避免该现象发生,需要用斜线结束路径名,如/www/a/。当参数激活,执行脚本访问其它文件时都需要验证文件路径,所以会影响执行效率。该指令配置范围在PHP<5.2.3时是PHP_INI_SYSTEM,在PHP>=5.2.3时是PHP_INI_ALL。
- disable_functions(禁用函数):使用该指令来禁止敏感函数的使用,使用本指令时,需把dl()函数也添加进禁用列表,否则攻击者可以利用dl()函数价值自定义的PHP扩展突破该指令的限制。指令范围为php.ini,配置禁用函数时使用逗号分割函数名。
- display_errors和error_reporting错误显示:display_errors用于表明是否显示PHP脚本内部错误,生产环境中建议关闭,在开启时,可以通过设置error_reporting来设置错误显示的级别。配置范围均为PHP_INI_ALL。
审计辅助与漏洞验证工具
代码编辑器
- Notepad++
- UltraEdit(文件对比)
- Zend Studio(PHP集成开发环境)
代码审计工具
- Seay源代码审计系统
- RIPS
漏洞验证辅助
- Burp
- 浏览器扩展:Hackbar, Firebug, Live HTTP Headers, Modify
- 编码转换及加解密工具:Seay代码审计系统自带的编码功能,Burp自带的decoder,超级加解密转换工具
- 正则调试工具:Seay自带的正则调试功能,灵者正则调试
- SQL执行监控工具:Seay mysql监控
漏洞发现与防范
通用代码审计思路
敏感函数回溯参数过程
根据敏感函数来逆向追踪参数的传递过程,使用较多,因为大多数漏洞都是由于函数使用不当造成的。非函数使用不当的漏洞,如SQL注入,也有一些特征,如Select、Incert等,结合From和Where等关键字判断是否为一条SQL语句,通过对字符串的识别分析,就能判断该SQL语句参数有没有使用单引号过滤,或者根据经验判断。如HTTP头里面的HTTP_CLIENT_IP和HTTP_X_FORWORDFOR等获取到的IP地址常直接拼接到SQL语句中,且由于它们是存在于$_SERVER
变量中不受GPC的影响,那么就可以查找这两个参数关键字快速寻找漏洞。
该方法的优点是定向挖掘、高效、高质量,缺点是对整体框架了解不够深入,定位利用点会花费时间,另外无法覆盖逻辑漏洞。
通读全文代码
在企业中做自身产品代码审计时,我们需要了解整个应用的业务逻辑以获取更多漏洞。
通读全文代码时,首先要看程序的大体代码结构,如主目录有哪些文件,模块目录有哪些文件,插件目录有哪些文件,还要注意文件大小、创建时间。根据文件命名可大致了解该程序实现哪些功能,核心文件是哪些。
在看程序目录结构时,要特别注意以下几个文件:
函数集文件
,通常命名中包含functions或者common等关键字,这些文件内是一些公共函数,提供给其它文件统一调用,所以大多数文件会在文件头包含其它文件。寻找这些文件的一个技巧就是打开index.php或一些功能性文件,在头部一般都能找到。配置文件
,通常命名中包含config关键字,包括Web程序运行必须的功能性配置选项及数据库等配置信息,从该文件中可以了解程序的小部分功能,另外看这个文件时注意观察配置文件中参数值是用单引号还是双引号,如果是双引号,则很可能存在代码执行漏洞。(如利用PHP可变变量($$a)的特性执行代码,ref:https://www.cnblogs.com/Cl0ud/p/12336834.html)安全过滤文件
,该文件关系到挖掘到的可疑点能否利用,通常命名中有filter、safe、check等关键字。这类文件主要作用是针对参数进行过滤,比较常见的是针对SQL注入和XSS过滤,还有文件路径、执行的系统命令的参数,其它相对少见。而目前大多数应用会在程序入口循环对所有参数使用addslashes()进行过滤。index文件
,是一个程序的入口文件,通过阅读该文件可大致了解整个程序的架构、运行流程、包含的文件,以及核心文件有哪些。而不同的目录的index文件也有不同的实现方式,最好先将核心目录的index文件都简单读一遍。
学习代码审计前期建议先下载一些小应用来读,积累经验后,再去读开源框架。
根据功能点定向审计
先简单黑盒测试一下,再通过发现的容易出问题的功能去阅读该功能点的源码,提高审计速度。
- 文件上传功能:任意上传、SQL注入
- 文件管理功能:任意文件操作、XSS漏洞
- 登录认证功能:任意用户登录
- 找回密码功能:验证码爆破、验证凭证算法
漏洞挖掘与防范(基础篇)
SQL注入
挖掘经验
常出现在登录页面、获取HTTP头(user-agent/client-ip等)、订单处理等业务相对复杂的地方,登录页面注入大多出现在HTTP头的client-ip和x-forward-for,用于记录登录IP地址。另外在订单系统内,由于订单涉及购物车等多个交互,经常会发生二次注入,通读代码时可着重关注这几个地方。
- 普通注入:指最容易利用的SQL注入漏洞,有int型和string型,在string型注入中需要使用单或双引号闭合。数据库操作存在一些关键字,如select from、mysql_connect、mysql_query、mysql_fetch_row等,查询方式还有update、incert、delete,只需要在白盒审计中查找这些关键字即可定向挖掘SQL注入。
- 编码注入:程序在进行一些操作前经常会进行编码处理,而做编码处理的函数可能会存在问题。通过输入转码函数不兼容的特殊字符,即可导致输出字符变成有害数据,在SQL注入里,最常见的编码注入是MySQL宽字节以及urldecode/rawurldecode函数导致的。
- 宽字节注入:使用PHP连接MySQL的时候,当设置
set character_set_client=gbk
时会导致一个编码转换的注入问题,当存在该漏洞时,注入参数里带入%df%27
,即可把程序中过滤的\(%5c)
吃掉。而通常都不是直接设置set character_set_client=gbk
,而是设置SET NAMES ‘gbk’
,同样存在漏洞。官方建议是使用mysql_set_charset来设置编码,只要在后面合理的使用mysql_real_escape_string还是可以解决该漏洞的。对宽字节注入的挖掘方法比较简单,搜索SET NAMES
、character_set_client=gbk
、mysql_set_charset('gbk')
。该漏洞的解决方法如以下三种,比较推荐一和三:- 在执行查询前先执行
SET NAMES 'gbk', character_set_client=binary
- 使用mysql_set_charset(‘gbk’)设置编码,然后使用mysql_real_escape_string()过滤。
- 使用pdo方式,在PHP5.3.6及以下版本中需要设置
setAttribute(PDO::ATTR_EMULATE_PREPARES,false);
,来禁用prepared statements的仿真效果。
- 在执行查询前先执行
- 二次urlencode注入:只要字符被进行转换就有可能产生漏洞。现在的Web程序大多会进行参数过滤,通常使用addslashes()、mysql_real_escape_string()、mysql_escape_string()函数或者开启GPC的方式来防止注入,也就是给单引号、双引号、反斜杠(\)和NULL加上反斜杠转义。如果某处使用了urldecode或者rawurldecode函数,则会导致二次加码生成单引号而引发注入。该漏洞可以通过搜索urldecode和rawurldecode函数来挖掘。
- 宽字节注入:使用PHP连接MySQL的时候,当设置
漏洞防范
- gpc/runtime魔术引号:通常数据污染有两种方式,一种是应用被动接收参数,另一种是主动获取参数。利用magic_quotes_gpc和magic_quotes_runtime可以防止部分SQL注入(对int型注入没有太大作用)
- 过滤函数和类:有两种使用场景,一种是程序入口统一过滤,框架程序使用这种方式比较多,另一种是在程序进行SQL语句运行前使用,除了PHP内置的一些过滤单引号等函数外,还有一些开源类过滤union、select等关键字。
- addslashes函数:过滤单引号、双引号、反斜杠以及空字符NULL,大多被用在程序入口,判断如果没有开启GPC则使用该函数进行过滤。不过它的参数必须是string类,所以可能会存在通过数组绕过的漏洞。
- mysql_[real_]escape_string函数:这两个函数都是对字符串进行过滤,只存在于大于PHP4.03的版本,[
\x00
]、[\n
]、[\r
]、[\
]、['
]、["
]、[\xla
]会受到影响。两个函数唯一不一样的地方在于mysql_real_escape_string接受的是一个连接句柄并根据当前字符集转移字符串,推荐使用。 - intval等字符转换:上述方式在int类型注入时效果不会,比如可以通过报错或盲注等方式来绕过,这时候就要用到intval函数了。intval的作用是将变量转换成int类型,这里举例intval是要表达一种利用参数类型白名单的方式来防止漏洞,对应的还有很多如floatval等。
- PDO prepare预编译:通过预编译的方式来处理数据库查询。当PHP版本<5.3.6时,使用PHP本地模拟prepare再把完整的SQL语句发送给MySQL服务器,且使用set names ‘gbk’时,仍然存在宽字节SQL注入,因为PHP和MySQL编码不一致。正确的写法应该是使用ATTR_EMULATE_PREPARES来禁用PHP本地模拟prepare。
XSS漏洞
挖掘经验
挖掘XSS漏洞关键在于寻找没有被过滤的参数,且这些参数传入至输出函数。常用输出函数列表如下:print、print_r、echo、printf、sprintf、die、var_dump、var_export
,寻找带有变量的这些函数即可。另外在代码审计中,浏览器环境对XSS漏洞利用影响非常大。通读代码时可多关注各处设置资料、文章发表、留言等富文本区域,这种地方存在的XSS通常是存储型的。
反射型 XSS
直接通过外部输入在浏览器端输出触发,该种漏洞比较容易通过扫描器黑盒审计发现。白盒审计中,只需要寻找带有参数的输出参数,根据输出参数对输出内容回溯输入参数,观察有没有经过过滤。
存储型XSS
把利用代码保存在数据库或文件中,当Web程序读取利用代码并输出在页面上时执行利用代码。比反射型容易利用,较为隐蔽且不用考虑绕过浏览器过滤。挖掘时也是需要寻找未过滤的输入点和未过滤的输出函数(可能完全不在同一个业务流中),可以根据当前代码功能去猜,或追寻数据有在哪里被操作,使用表名、字段名去代码里搜索。
漏洞防范
- 特殊字符HTML实体转码。
- 标签事件属性黑白名单。
CSRF漏洞
挖掘经验
主要用于越权操作,所以漏洞会出现在有权限控制的地方。黑盒挖洞可以先搭建环境,打开几个有非静态操作的页面,抓包看看有没有token,没有token就不带referer直接请求该页面,返回数据一样的话,可能存在CSRF漏洞。白盒审计,通读代码时看看被大量引用的基础文件(核心文件)、你比较关心的功能点代码内有没有验证token和referer相关的代码,或者直接搜索token关键字。
漏洞防范
- 增加token/referer验证避免img标签请求的水坑攻击。
- 增加验证码。(比较麻烦,更适用于敏感操作页面)
漏洞挖掘与防范(进阶篇)
文件操作漏洞
文件包含漏洞
文件包涵函数有include()、include_once()(前两个在包含文件时即使遇到错误,下面的代码仍然执行)、require()、require_once()(这两个在包含文件时遇到错误会报错退出程序)。
挖掘经验
文件包含漏洞大多出现在模块加载、模板加载以及cache调用的地方。在挖掘漏洞时可以跟踪程序运行流程,看模块加载包含的文件是否可控等,另一个是直接搜索上文四个函数来回溯寻找可控变量。一般该类漏洞都是本地文件包含,大多需要截断。
本地文件包含
本地文件包含(local file include,LFI),大多出现在模块加载、模板加载和cache调用,有多种利用方式,如上传一个允许上传的文件格式的文件再包含以执行代码,包含PHP上传的临时文件,在请求URL或ua里面加入要执行的代码,WebServer记录到日志后再包含WebServer的日志,还有像Linux下可以包含/proc/self/environ文件。
远程文件包含
远程文件包含(remote file include, RFI),需要设置allow_url_include = On,相比于本地包含来说更容易利用,但出现频率不高。
文件包含截断
- 使用
%00
截断,最古老的方法,受限于GPC和addslashes等函数的过滤,另外PHP5.3之后的版本已经全面修复,不能使用该方法了。 - 使用多个英文句号
.
和反斜杠/
来阶段,不受GPC限制,但同样在PHP5.3之后被修复。 - 远程文件包含时利用问号
?
来伪截断,不受GPC和PHP版本限制,只要能返回代码给包含函数就能执行。在HTTP协议里,访问http://remotehost/i.txt和访问http://remotehost/i.txt?.php 返回的结果是一样的,因为WebServer把问号之后的内容当成请求参数,而txt不在WebServer里解析,参数对访问i.txt返回的内容不影响,实现伪截断。
文件读取(下载)漏洞
挖掘经验
文件读取漏洞比较容易寻找,一种方式是可以先黑盒看功能点对应的文件,再去读文件源码。另一种是搜索文件读取的函数(file_get_contents()、highlight_file()、fopen()、readfile()、fread()、fgetss()、fgets()、parse_ini_file()、show_source()、file()
),看有无可直接或间接控制的变量,除了正常读取文件的函数之外,另外一些其他功能的函数也可以用于读取文件,如include()等。
文件上传漏洞
挖掘经验
挖掘简单,上传点常调用同一个上传类,上传函数又只有move_uploaded_file()这一个,所以最快方法就是直接搜索该函数,再去看调用的代码存不存在未限制上传格式或者可以绕过,其中问题较多的是黑名单限制文件格式以及未更改文件名的方式,在未改名的情况下,在Apache利用其向前寻找解析格式和IIS6的分号解析bug都可以执行代码。
- 未过滤或本地过滤:共同点是都未在服务器端过滤。
- 黑名单扩展名过滤:出现较少,存在限制的扩展名不够全、验证扩展名的方式存在问题可直接绕过或截断。
- 文件头、content-type验证绕过:早期出现较多,上传文件时,如果直接上传一个非图片文件会被提示不是图片文件,但只要在文件头里加上
GIF89a
后上传,则验证通过。这是因为程序用了如getimagesize()函数等。content-type是在http request请求头内,所以可以被攻击者修改,而早期的一些程序只是单纯的验证了这个值。
文件删除漏洞
常出现在有文件管理功能的应用上,原理和文件读取差不多,只不过利用的函数不一样,一般因为删除的文件名可以用../
跳转,或者没有限制当前用户权限。
挖掘经验
。挖掘漏洞可以先去找相应的功能点,黑盒测试一下能不能删除某个文件,如果删除不了,再去从执行流程追踪提交的文件名参数的传递过程。如果纯白盒挖,也可以去搜索带有变量参数的unlink(),采取回溯变量的方式。
文件操作漏洞防范
通用文件操作防御
- 合理的权限管理。
- 以加密等方式替代直接将文件名作为下载参数的操作。
- 避免目录跳转,禁止参数中携带
..
、/
、\
来跳转目录。
文件上传漏洞防范
- 白名单过滤文件扩展名,使用in_array或
===
来对比扩展名。 - 保存上传文件时重命名文件,文件名采用时间戳的拼接随机数的MD5值保存方式
md5(time()+rand(1,10000))
代码执行漏洞
挖掘经验
eval()和assert()函数导致的代码执行漏洞大多是因为载入缓存或者模板以及对变量的处理不严格导致。
preg_replace()函数代码执行需要存在/e参数,这个函数原本是用来处理字符串的,因此漏洞出现最多的是在对字符串的处理,比如URL、HTML标签以及文章内容等过滤功能。
call_user_func()和call_user_func_array()函数的功能是调用函数,多用在框架里面动态调用函数,所以一般比较小的程序不常出现该类代码执行。array_map()函数的作用是调用函数并且除第一个参数外其它参数为数组,通常会写死第一个参数,即调用的参数,类似这三个函数功能的函数还有很多。
还有一类非常常见的是动态函数的代码执行,如$_GET($_POST["xx"])
。
代码执行函数
- eval和assert函数:用于动态执行函数,所以它们的参数就是PHP代码。
- preg_replace函数:对字符串进行正则处理。
- 调用函数过滤不严:数十个函数有调用其它函数的功能,如果传入的函数名可控,那么就可以调用意外的函数来执行需要的代码,即存在代码执行漏洞。这些函数有:
1
2
3
4
5
6
7
8
9
10
11
12
13
14call_user_func()、call_user_func_array()、array_map()、
usort()、uasort()、uksort()、array_filter()、
array_reduce()、array_diff_uassoc()、array_diff_ukey()、
array_udiff()、array_udiff_assoc()、array_udiff_uassoc()、
array_intersect_assoc()、array_intersect_uassoc、
array_uintersect()、array_uintersect_assoc()、
array_uintersect_uassoc()、array_walk()、array_walk_recursive()、
xml_set_character_data_handler()、xml_set_default_handler()、
xml_set_element_handler()、xml_set_end_namespace_decl_handler()、
xml_set_external_entity_ref_handler()、xml_set_notation_decl_handler()、
xml_set_processing_instruction_handler()、
xml_set_start_namespace_decl_handler()、
xml_set_unparsed_entity_decl_handler()、stream_filter_register()、
set_error_handler()、register_shutdown_function()、register_tick_function()
动态函数执行
由于PHP的特性,PHP函数可以直接由字符串拼接,加大了安全控制的难度。PHP动态函数写法为变量(参数)
,例如:
1 | <?php |
想要挖掘这种形式的代码执行漏洞,需要找可控的动态函数名。
漏洞防范
采用参数白名单过滤,这里的白名单并不是说完全固定为参数,可以结合正则表达式来进行白名单限制。
命令执行漏洞
代码执行漏洞指的是可以执行PHP脚本代码,而命令执行漏洞指的是可以执行系统或应用指令(如CMD命令或bash命令)的漏洞。PHP的命令执行漏洞主要基于一些函数的参数过滤不严导致,可以执行命令的函数有system()、exec()、shell_exec()、passthru()、pcntl_exec()、popen()、proc_open()这七个函数,另外反引号也可以执行命令,不过实际上这种方式也是调用的shell_exec()函数。PHP命令执行继承了WebServer用户权限,一般该权限都可以向Web目录写文件。
挖掘经验
该漏洞多出现在包含环境包的应用里,一般这类产品会有额外的脚本来协助处理日志及数据库等,web应用会有比较多的点之间使用system()、exec()、shell_exec()、passthru()、pcntl_exec()、popen()、proc_open()等函数执行系统命令来调用这些脚本,可以直接在代码中搜索这几个函数,收获应该会不少。除了这类应用,还有一些调用外部程序的功能也会出命令执行漏洞,由于特征明显,可以直接搜索函数名进行挖掘。
命令执行函数
上述的函数中,sustem()、exec()、shell_exec()、passthru()以及反引号是可以直接传入命令并返回执行结果。
popen()、proc_open()函数不会直接返回执行结果,而是返回一个文件指针。
反引号命令执行
反引号执行命令是调用的shell_exec()函数。
漏洞防范
- 使用PHP自带的命令防注入函数,包括escapeshellcmd()(过滤整条命令)和escapeshellarg()(保证传入命令执行函数的参数确实是以字符串参数形式存在,不能被注入)。
- 对命令执行函数的参数做白名单限制。(通用修复方法)
漏洞挖掘与防范(深入篇)
变量覆盖漏洞
变量覆盖指的是可以用我们自定义的参数值替换程序原有的变量值,通常需要结合程序的其它功能来实现完整攻击。
该类漏洞大多由函数使用不当导致,常引发漏洞的函数有:extract()函数和parse_str(),import_request_variables()函数则是用于未开启全局变量注册时,调用该函数相当于开启了全局变量注册,在PHP5.4后该函数已经被取消。另外部分应用利用$$的方式注册变量没验证已有变量导致覆盖,这些应用在使用外部传递进来的参数时不是用类似于$_GET['key']
这样原始的数组变量,而是把里面的key注册成一个变量$key,注册过程中没有验证该变量是否已经存在,所以会导致变量覆盖。
挖掘经验
由于变量覆盖漏洞通常要结合其他功能代码来实现完整攻击,所以挖掘可用的变量覆盖漏洞还要考虑究竟哪些变量可以被覆盖并且后面有被使用。
由函数导致的变量覆盖比较好挖掘,寻找参数带有变量的extract()、parse_str()函数,回溯变量是否可控。import_request_variables()则只需要找没有初始化且操作前没有赋值的变量,就可以大胆的提交该变量作为参数,另外只要写在该函数前的变量,不管是否已经初始化都可以覆盖,不过该函数只在PHP4-4.1.0以及5-5.4.0可用。
关于国内很多程序使用$$
符号注册变量会导致变量覆盖,可以直接搜索$$
去挖掘,不过建议挖掘前应通读核心文件。
函数使用不当
- extract()(最常见):将数组中的键值对注册成变量,函数结构如下: 该函数有3种可能会覆盖已有变量,第一种是第二个参数为EXTR_OVERWRITE,它表示如果有冲突,则覆盖已有变量;第二种是只传入第一个参数,默认为EXTR_OVERWRITE模式;第三种则是第二个参数为EXTR_IF_EXISTS,表示仅在当前符号表中已有同名变量时,覆盖它们的值,其它的都不注册新变量。
1
int extract (array &$var_array [, int $extract_type = EXTR_OVERWRITE [, string $prefix = NULL]])
- parse_str():解析字符串并注册成变量,在注册变量前不会验证当前变量是否已经存在,所以会直接覆盖掉已有变量。该函数有两个参数: 其中
1
void parse_str(string $str [, array &$arr])
$str
是必须的,代表要解析注册成变量的字符串,形式为a=1
,经过函数后会注册变量$a并赋值1。第二个参数$arr是一个数组,当第二个参数存在时,注册的变量会放在这个数组内,但如果该数组内原先就存在相同的键(key),则会覆盖原有键值。 - import_request_variables():作用是把GET、POST、COOKIE的参数注册成变量,用在register_globals被禁止的时候,需要PHP4.1-5.4之间的版本。不过建议不开globals的时候也不要使用该函数,容易造成变量覆盖。
$$变量覆盖
由于双$导致原变量被覆盖,在漏洞代码之前的变量都可以被覆盖。
漏洞防范
最常见漏洞点是做变量注册以及赋值给变量的时候没有验证变量是否存在,所以推荐使用原始的变量数组,如$_GET
、$_POST
,或者在注册变量前一定要验证变量是否存在。
使用原始变量
由于上述变量覆盖漏洞是在进行变量注册时导致,所以要解决变量覆盖的问题,最直接的方法就是不进行变量注册,直接使用原生的$_GET
、$_POST
等数组变量进行操作,如果考虑到程序可读性等原因,需要注册个别变量,可以直接在代码中定义变量,然后再把请求中的值赋值给它。
验证变量存在
如果一定要用前面几种方式注册变量,可以在注册变量前先判断变量是否存在,如使用extract()函数则可以配置第二个参数为EXTR_SKIP。使用parse_str()函数注册变量钱需要自行通过代码判断变量是否存在。不建议使用import_request_variables()注册全局变量,会导致变量不可控。最重要的,自行申明的变量一定要初始化,不然即便注册在执行代码前也能被覆盖。
逻辑处理漏洞
此次指程序在业务逻辑上的漏洞。
挖掘经验
漏洞大多存在于逻辑处理及业务流程中,没有特别明显的关键字用于快速定位,挖掘技巧通常是通读功能点源码,熟悉业务流程,可关注程序是否可重复安装、修改密码处是否可越权修改其它用户密码、找回密码验证码是否可暴力破解以及修改其它用户密码、cookie是否可预测或cookie验证是否可绕过等。
等于与存在判断绕过
判断函数存在漏洞时,可以逃逸判断函数绕过逻辑。常见存在漏洞的判断函数有:
- in_array():用于判断一个值是否在某个数组列表里,该函数存在一个问题,比较前会自动做类型转换,实现输入参数并不全等于数组任意值时,也可以实现绕过并注入。
- is_numeric():用于判断一个变量是否为数字,检查通过返回true,否则返回false。该函数存在一个问题,当传入参数为hex时则直接通过并返回true,而mysql是可以直接使用hex编码代替字符串名为的。所以这里虽然不能直接注入SQL语句,但存在二次注入和XSS等漏洞隐患,比如当我们提交
<script>alert(1)</script>
的hex编码时,效果相同。如果程序有其它地方调用该值并直接输出,则有可能执行代码触发XSS漏洞。 - 双等于和三等于:双等于在判断等于前会先做变量类型转换,三等于则不会,所以双等于存在安全风险。
账户体系中的越权漏洞
漏洞分为水平越权和垂直越权,但漏洞原理相同,都是账户体系在判断权限时不严格导致存在绕过漏洞。这一类绕过通常发生在cookie验证不严、简单判断用户提交的参数,归根结底,都是因为参数在客户端提交,服务端未严格校验。
未exit或return引发的安全问题
某些情况下,在经过if条件判断之后,要么继续执行if后面的代码,要么在if流程内退出当前操作,但该退出行为,有不少程序忘记写return、die()、或者exit(),导致程序继续执行。
常见支付漏洞
最常见支付漏洞有四种,第1、2、3种比较简单,分别是客户端可修改单价、总价和购买数量,服务器端未严格校验导致。部分商城程序是直接由单价和数量计算总价,但并没有验证这两个数字是否小于0。这种形式的支付漏洞,可以通过寻找支付代码并看代码过滤情况挖掘。
还有一种是以重复发包来利用时间差,少量钱多次购买,如使用手机给腾讯发送购买QQ业务的短信再快速取消。这类漏洞可从判断余额及扣费功能代码处寻找。
漏洞防范
- 深入熟悉业务逻辑。
- 多熟悉函数的功能和差异。
会话认证漏洞
挖掘经验
在cookie验证上出现几率较高,通常是没有使用session认证,而是将用户信息直接保存在cookie中,以备程序使用时直接调用。一般这个过程都会有一个统一的函数去调用数据,容易导致SQL注入和越权等漏洞。在挖掘登录认证漏洞时,可以先看程序的登录功能代码,看整个登录过程的业务逻辑有没有可以控制session值或直接绕过密码验证的漏洞;另外需要关注程序验证是否为登录的代码,通俗的说是验证cookie的代码,是不是直接取cookie的值,然后如何判断这个值来验证是否登录。
cookie认证安全
cookie可以保存任何字符串,各个浏览器保存cookie字节数大小不一样,一般不超过4096个字节,通常用于保存登录帐号的标识信息。cookie出现问题较多的是cookie的SQL注入等常见漏洞,以及web应用程序在服务端直接读取cookie值来操作当前用户数据,由于cookie可以伪造,从而导致伪造用户身份登录的漏洞。
漏洞防范
了解认证的业务逻辑,严格限制输入的异常字符以及避免直接使用客户端提交的内容进行操作。应该结合cookie和session,不能直接从cookie获取参数值进行操作,另外注意设置session时,需要保证客户端不能操作敏感session参数。特别注意敏感数据不要放在cookie中,cookie在浏览器端以及传输过程中都有被窃取的可能性。
二次漏洞审计
什么是二次漏洞
需要先构造好利用代码写入网站保存,在第二次或多次请求后调用攻击代码触发或修改配置触发的漏洞叫做二次漏洞。该漏洞的出现归根结底是开发者在可信数据的逻辑上考虑不全面。
二次漏洞审计技巧
虽然二次漏洞写入和触发payload很可能不在同一个地方,但还是可以通过找相关关键字去定位的,只是精准度会稍微降低。大多数二次漏洞的逻辑性比一般的漏洞强的多,所以最好还是把全部代码读一遍,更好的了解业务逻辑和全局配置。
业务逻辑越复杂的地方越容易出现二次漏洞,我们可以重点关注购物车、订单、引用数据、文章编辑、草稿等和数据库交互的地方,以及和文件系统交互的系统配置文件(一般需要管理员权限才能操作)。
在二次漏洞类型里,可以重点关注SQL注入、XSS。
代码审计小技巧
钻GPC等转义的空子
GPC会自动把提交内容的敏感字符转义导致攻击代码无法执行,但还是存在漏洞:
$_SERVER变量
在PHP5后,用$_SERVER
取到的header
字段不受GPC影响,且普通程序员很少会考虑到这些字段。header
注入里常见的是user-agent
、referer
以及client-ip/x-forward-for
,因为大多数Web应用都会记录访问者的IP以及referer
等信息,同样的$_FILES
变量也不受GPC保护。
编码转换问题
宽字节注入就是一种非常典型的编码转换问题导致绕过GPC的方式。不仅是PHP与MySQL交互过程中会发生编码转换导致问题,PHP自带的编码转换函数也会发生问题,比如mb_convert_encoding()、iconv(),也就是只要发生编码转换就有可能会出现问题。
神奇的字符串
字符处理函数报错信息泄漏
页面的报错信息通常能泄漏文件绝对路径、代码、变量及函数等信息,页面报错有很多情况,但不是所有情况页面都会出现错误信息,显示错误信息需要在PHP配置文件中打开并设置等级。
大多数错误提示会显示文件路径,可以获取Web路径。由于用户提交数据在后端大多是以字符串方式处理,所以利用字符串处理函数报错成了必不可少的方法,对于利用参数来报错的方式,给函数传入不同类型的变量是最实用的方式。
字符串截断
截断利用最多的是在文件操作上面,通常用来利用文件包含漏洞和文件上传漏洞,%00
即NULL会被GPC和addslashes()过滤掉,所以利用%00
截断需要GPC关闭以及不被addslashes()函数过滤,另外PHP5.3之后也不能用这种方式截断。
- iconv字符编码转换截断:如从UTF-8转换到GBK,部分代码不能被成功转换(chr(128)-chr(255)之间),在利用该函数转码时,遇到不能处理的字符串时后续字符串不会被处理。
php://输入输出流
PHP代码解析标签
- 最标准的
<?php?>
- 脚本标签:
<script language="php"></script>
,可以正常解析PHP代码 - 短标签:
<?...?>
,使用短标签需要在php.ini中设置short_open_tag=on,默认为on状态。 - asp标签
<%...%>
,在PHP3.0.4后可用,需要在php.ini中设置asp_tags=on,默认为off。
通常用于绕过<?php?>
标签过滤以留后门及绕过Web程序或waf写入webshell。
fuzz漏洞发现
不严谨的正则表达式
- 没有用
^
和$
限定匹配开始位置 - 特殊字符未转义,匹配特殊字符的原字符时需要使用反斜杠
\
来进行转义,不然.
则可以用来表示任何字符,存在安全隐患。
十余种MySQL报错注入
利用报错注入最快拿到注入的数据。
- floor():
id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)
- extractvalue():
id=1 and (extractvalue(1, concat(0x5c, (select user()))))
- updatexml():
id=1 AND (updatexml(1,concat(0x5e24,(select user()),0x5e24),1))
- GeometryCollection():
id=1 AND GeometryCollection((select * from(select * from(select user())a)b))
- polygon():
id=1 AND polygon((select * from(select * from(select user())a)b))
- multipoint():
id=1 AND multipoint((select * from(select * from(select user())a)b))
- multilinestring():
id=1 AND multilinestring((select * from(select * from(select user())a)b))
- multipolygon():
id=1 AND multipolygon((select * from(select * from(select user())a)b))
- linestring():
id=1 AND linestring((select * from(select * from(select user())a)b))
- exp():
id=1 and EXP(~(select * from(select user())a))
Windows FindFirstFile
目前大多数程序会对上传文件名加密,这样我们就无法直接得到上传webshell文件路径,但在windows下时,我们只需要知道文件所在目录,利用win特性就可以访问文件,因为win在搜索文件时使用了FindFirstFile这一个winapi函数去一个文件夹(包括子文件夹)去搜索指定文件。
利用方法很简单,只需要将文件名不可知部分之后的字符用<
或>
代替即可,不过要注意,只使用一个<
或>
则只能代表一个字符,如果文件名是12345或更长,请求1<
或1>
都访问不到文件,需要1<<
才能访问到,代表继续往下搜索,有点像win的短文件名。
目前所有PHP版本都可用,PHP并没有在语言层面禁止使用<>
这些特殊字符,从函数层面来讲,可以利用这个特性的函数有:
1 | include() include_once() require() require_once() |
PHP可变变量
部分PHP应用在写配置文件或使用preg_replace()函数第二个参数赋值变量时,会用到双引号来代表string类型给变量赋值,存在代码执行漏洞。
1 | <?php |
注意,上述代码中的@
是必须存在的,不然代码无法执行,但除了该符号外还有其它写法,只要不影响PHP规范就可以执行,举例:
- 花括号内第一个字符为空格:
$a = "${ phpinfo()}";
- 花括号内第一个字符为TAB:
$a = "${ phpinfo()}";
- 花括号内第一个字符为注释符:
$a = "${/**/phpinfo()}";
- 花括号内第一个字符为回车换行符:
$a = "${ phpinfo()}";
- 花括号内第一个字符为加号:
$a = "${+phpinfo()}";
- 花括号内第一个字符为减号:
$a = "${-phpinfo()}";
- 花括号内第一个字符为感叹号:
$a = "${!phpinfo()}";
除此之外还有一些如~
、\
等。
PHP安全编程规范
参数的安全过滤
第三方过滤函数与类
目前大多数程序都有一个统一的参数过滤入口,但对于特定场景和漏洞就不够好用。所以除了总入口,在具体功能点也需要进行过滤。
内置过滤函数
- SQL注入过滤函数:有addslashes()、mysql_real_escape_string()以及mysql_escape_string(),作用都是给字符串添加反斜杠
\
来转义掉单引号、双引号、反斜杠以及空字符NULL。addslashes()和mysql_escape_string()都是直接在敏感字符串前加反斜杠,可能会存在宽字节注入绕过的问题,而mysql_real_escape_string()会考虑当前连接数据库的字符集编码,更加安全。 - XSS过滤函数:有htmlspecialchars()和strip_tags(),功能不同,htmlspecialchars作用是将字符串中的特殊字符转换成HTML实体编码,能够干掉大多数的XSS攻击。strip_tags则是用来去掉HTML及PHP标记。
- 命令执行过滤函数:有escapeshellcmd()和escapeshellarg()两个函数,escapeshellcmd过滤的字符为下方代码框所示,win下过滤方式则是在这些字符前面加了
^
符号,linux下则是在这些字符前加了反斜杠。escapeshellarg函数过滤较简单,给所有参数加上一对双引号,强制为字符串。1
&,;,`,|,*,?,~,<,>,^,(,),[,],{,},$,\,\x0A,\xFF,% 以及单双引号
使用安全的加密算法
- 对称加密:算法安全性比较高,数据的实际安全性取决于密钥的管理。所以不建议使用对称加密对用户密码进行加密存储。
- 非对称加密:安全性比对称加密更好。
- 单向加密:不可逆算法,常见如MD系列和sha1,通常用于保存密码和做数字签名,但存在碰撞的问题。
业务功能安全设计
验证码
验证码绕过
- 不刷新直接绕过:后端接收一次请求后并没有主动刷新验证码,将验证码和session绑定在一起,为了保证验证码正常使用,会把验证码明文或加密后放在Cookie或POST数据包里,所以每次只要同一个数据包里的两个验证码对应即可绕过。(重复发包利用?)
- 暴力破解:验证码能够被爆破,主要是程序没有设置验证码错误次数和超时设定,导致能够不断尝试。
- 机器识别:利用机器识别验证码。
- 打码平台:人工打码绕过。
对策
- 设置验证码错误次数(最重要)。
- 不把验证码放在HTML页面或cookie中。
- 验证码设置只能请求一次,请求一次后不管错误与否都在后端程序强制刷新。
- 短信或邮件验证码必须要6位以上字母和数字混合,图片或语音验证码需要加强混淆干扰。(短信验证码这条似乎在当前不适用,多数厂商选择限制短时间同IP发包等,但不增加验证码复杂度)
- 验证码要动态生成,不能统一生成多次调用。
验证码资源滥用
利用大量网站短信验证码未限制获取验证码次数和时间间隔的接口,实现短信/邮箱轰炸。防护比较简单,限制单个手机号在一个时间段内请求接收短信的次数,或限制某一IP在一个时间段内请求接收短信的次数。
用户登录
撞库登录
指登录口没有做登录次数限制,导致可以使用不同的用户及密码不断进行登录尝试,遍历用户密码。撞库漏洞情况有:
- 用户名和密码错误次数都无限制。
- 单时间段内用户密码错误次数限制。(可以使用单密码和用户名列表撞库)
- 单时间段内IP登录错误次数限制。(存在误杀内网用户的可能)
比较好的解决方案是使用登录验证码和多因素认证。
API登录
免重新登录跳转处存在漏洞,如修改用户参数实现任意登录。对于这种漏洞注意以下安全点:
- 登录密钥(clientkey)需要不可预测且不固定,生成key的算法中加入随机字符。
- API接口禁止搜索引擎收录。
- 登录密钥当次绑定当前主机,换机器不可用,防止木马和嗅探。
用户注册
- 设计验证码。
- 采集用户机器唯一识别码,拦截短时间内多次注册。
- 根据帐号格式自学习识别垃圾帐号。
- 防止SQL注入漏洞与XSS漏洞(常见)。
密码找回
- 输入用户名/邮箱/手机阶段:抓包修改手机/邮箱参数。
- 填写验证码和新密码阶段:
- 验证凭证简单,可以被暴力破解。
- 验证凭证算法简单,凭证可以被预测。
- 验证凭证直接保存在源码里。
- 发送新密码阶段:
凭证未绑定用户:请求发送至邮箱的找回密码链接时,后端根据uid和key对应判断该链接有效,但将新密码提交到服务器时,服务器端没有判断当前key是否和uid或邮箱匹配,直接修改掉uid或邮箱指定的用户密码。这样只要拦截修改密码的请求包,篡改用户参数即可。所以安全风险点应该注意的有:- 接收验证码的邮箱和手机号不可由用户控制,应直接从数据库读取。
- 加强验证凭证复杂度,防止被暴力破解。
- 限制验证凭证错误次数,单用户在一定时间内验证码错误一定次数,强制等待一段时间。
- 验证凭证设置失效时间。
- 验证凭证不要保存在页面。
- 输入用户邮箱或ID、手机号取验证凭证的地方需要设置验证码防止短信炸弹和批量找回等。
- 验证凭证跟用户名、用户ID、用户邮箱绑定,找回密码时验证当前凭证是否是当前用户的。
资料查看和修改
这里主要介绍的是越权漏洞的利用。
- 未验证用户权限:直接修改当前资源ID即可访问该资源,没有验证当前资源是否属于当前用户。
- 未验证当前登录用户:虽然程序绑定了用户ID和资源ID,但该用户ID是访问资源时直接从cookie或post、get参数里获取,所以可以通过修改成另一用户ID,利用其权限操作资源。
上述漏洞较多出现在用户资料修改,及用户资料查看。
防御思路有:
- 用户资源ID(订单ID、地址ID等)绑定到用户,只允许有权限的用户查看。
- 当前用户信息存储到session,不放在request中,避免攻击者修改。
投票/积分/抽奖
共同点:单个用户次数存在限制
,该限制存在很多绕过方式。
通常有几种利用方法:
- cookie或POST请求正文绕过。修改cookie或post请求数据产生绕过。
- 基于IP验证。看程序获取IP的方式,如果是client-ip或x_forward_for获取IP,可直接伪造IP绕过。
- 基于用户认证。利用批量注册刷票,或在投票时随意修改POST包或cookie里的当前uid、用户名等查看是否能够绕过限制。
从上述利用手段可以看到主要三个点是IP、登录用户、cookie,可用性比较高的防御手段如下: - 机器识别码验证。
- 操作需要登录,当前用户信息从session读取。
充值支付
主要有四种情况:客户端可修改单价、总价和购买数量以及利用时间差多次购买。
主要应对手法是:
- 保证数据可信,商品单价和总价不可从客户端获取。
- 购买数量不能小于等于0。
- 账户支付锁定机制,当一个支付操作开始就应该立马锁定当前用户,不能同时两个后端请求对余额进行操作。
私信及反馈
除去特殊情况下可以滤去的SQL注入或命令执行等少见漏洞外,最常见的就是XSS漏洞以及越权漏洞。
远程地址访问
访问远程地址获取资源的功能可能会被利用(如填入内网地址)
利用限制填写来防御该类漏洞,但大部分厂商修复时不会考虑到短地址的问题,修复后仍然可以通过生成短链接的方式利用。
文件管理
本身就是一个高危功能,权限管理不当会导致被攻击者利用写入webshell。
为了保证安全,在满足业务需求的情况下,设计时应遵循以下几点:
- 禁止写入脚本可在服务器端执行的文件:如服务器可解析PHP,那么此次就需要限制不能操作PHP扩展名的文件和PHP标签的代码。
- 限制文件管理功能操作的目录:限制文件管理功能只能操作固定目录,目录不能从客户端提交,在代码中设置好即可,如果实在需要进行目录跳转的话,一定要禁止提交
../
以及\..
避免越权操作其它目录。 - 限制文件管理功能访问权限:虽然文件管理是正常功能,但存在一点后门的性质,所以对该功能的访问权限一定要严格控制。
- 禁止上传特殊字符文件名的文件:大多数应用会对上传文件进行展示,特别是网盘类应用,注意对上传文件名进行检查,禁止文件名中有尖括号、单双引号等特殊字符,避免攻击者用文件名进行XSS攻击。
数据库管理
跟文件管理一样,也是高位功能,如果启动数据库服务的系统用户以及数据库用户的权限都够大,完全可以利用该功能直接执行系统命令及操作服务器上的文件。
- 限制可以操作的数据库表,要么在代码内写死只能操作哪些表(如备份),如果是执行SQL语句的方式可以另建一个mysql用户,限制可操作的表和字段。
- 限制备份到服务器上的文件名,需要随机生成且长度不低于16位,扩展名不能自定义,防止攻击者利用该功能导出webshell或猜解文件名直接下载。
命令/代码执行
命令执行和代码执行功能通常都在系统后台,相比来说,命令执行的功能使用更多,代码执行功能在特殊应用上才会存在。设计该类功能时应该注意以下几点:
- 严格控制该功能访问权限,建议高权限才能访问。
- 在满足业务需求的情况下,可以设置命令白名单,可以使用escapeshellcmd()以及escapeshellarg()函数进行过滤,命令直接写死在代码中更好。
- 给命令及代码执行功能设置独立密码。
- 代码执行功能限制脚本可访问的路径。
- 在满足需求的情况下限制当前执行命令的系统权限。
文件/数据库备份
是非常常见且非常容易出现安全问题的功能。常见问题有:
- 未授权访问和越权访问:未授权访问体现在这个备份功能直接在不登录或登录验证存在漏洞的情况下可以直接使用,以及存在CSRF漏洞可以直接劫持管理员帐号进行备份。
- 备份文件名可预测:攻击者可以利用枚举的方式扫描备份包。
- 生成的文件可利用web中间件解析漏洞执行代码
如何设计备份功能: - 进行权限控制,只有高权限可以使用。
- 文件名随机生成,不可预测。
API
因为爬虫无法抓取APP中的API接口,所以接口的SQL注入等漏洞相对较多,目前最多的问题是未授权访问以及数据遍历漏洞。因此设计一个安全的API需要从以下几点考虑:
- 访问权限控制:必要时加入账户体系,严格控制数据调用权限,比如当前用户必须在登录情况下,接口参数中传入自己登录成功的凭证才能调用这个用户的数据。另外不需要账户体系时也要注意加入不可暴力破解的访问密钥进行权限验证。
- 防止敏感信息泄漏:没必要输出的信息应该注意禁止输出。
- SQL注入等常规漏洞:注意代码安全,防止SQL注入、代码执行等漏洞的产生。
应用安全体系建设
- 用户密码安全策略
- 前后台用户分表:同表的情况下可能存在越权修改管理员信息等情况。
- 后台地址隐藏
- 密码加密存储方式
- 登录限制
- API站库分离
- 慎用第三方服务
- 严格的权限控制
- 敏感操作多因素验证
- 应用自身的安全中心