php代码审计基础
挺久前看了半本代码审计,但是现在翻来都忘得差不多了。因为最近想开始一些cms的代码审计实战,就过了一遍安全牛的php代码审计视频,下面是笔记啦
一、思路和流程
- 通读原文:公共函数文件、安全过滤文件、配置文件、index文件
- 敏感关键字回溯参数
- 查找可控变量
- 功能点定向审计:程序安装、文件上传、文件管理、登录验证、备份恢复、找回密码(推荐,用思维导图方式记录)
二、核心配置
- 大小写敏感
- 运算符: |、&、~、!
- 空值表达方式foo=;foo=none;
- 安全模式 safe_mode=off 用来限制文档的存取、限制环境变量的存取、控制外部程序的执行(已在php5.4.0被移除)
- disable_functions 禁止敏感函数时,dl()函数也要加到禁止列表,否则攻击者可以利用dl()函数加载自定义的php扩展突破disable_functions,配置禁用函数时可以使用逗号分隔函数名
- php设置在安全模式下仍允许攻击者使用com()函数来创建系统组件执行任意命令,默认com.allow_dcom=false,可以改成true防止漏洞,使用com()函数需要再配置extension=php_com_dotnet.dll(如果php版本小于5.4.5则不需要)
- 全局变量注册开关register_globals=off,服务端获取数据的时候用$_GET['name']来获取数据
- register_globals=on,服务端使用POST或者GET提交的变量,都将自动使用全局变量的值来接受值
- 魔术引号自动过滤默认magic_quotes_gpc=off,打开后将自动把用户提交的sql查询语句进行转换,单引号、双引号、反斜杠、空字符加上反斜杠进行转义(已在php5.4.0被移除)
- 是否允许包含远程文件allow_url_include=off该配置为on的情况下,可以直接包含远程文件,若包含的变量为可控的情况下,可以直接控制变量来执行php代码
- 是否允许打开远程文件allow_url_open=on允许本地php文件通过调用url重写来打开和关闭写权限,默认的封装协议提供的ftp和http协议来访问文件
- HTTP头部版本信息expose_php=off防止了通过http头部泄露的php版本信息
- 文件上传临时目录upload_tmp_dir=上传文件临时保存的目录,如果不设置的话,则采用系统的临时目录
- 用户可访问目录open_basedir=E:\Local Test\WWW能够控制php脚本只能访问指定的目录,这样能够避免php脚本访问,不应该访问的文件,一定程度上限制了phpshell的危害
- 内部错误选项display_errors=on表明显示php脚本的内部错误,网站发布后建议关闭php的错误回显,在调试的时候通常把php错误显示打开
- 错误报告级别error_reporting=E_ALL&~E_NOTICE这个设置的作用是将错误级别调到最高,显示所有问题,方便排错
三、超全局变量
全局变量:在函数外面定义的变量,不能再函数中直接使用。它的作用域不会到函数内部,所以再函数内部使用的时候常常看到类似global $a;。
超全局变量:在所有脚本都有效,在函数可直接使用。除$_GET、$_POST、$_SERVER、$_COOKIE等之外的超全局变量保存在$_COOKIE等之外的超全局变量保存在$_GLOBALS数组中。
- $_REQUEST可以获取以POST方法和GET方法提交的数据,比较慢,尽量不使用
- $_SERVER保存关于报头、路径和脚本位置的信息等
- $_FILES['file']['name'] 显示客户端文件的原名称
- $_FILES['file']['type'] 文件的MIME类型,例如"image/gif"
- $_FILES[file']['size'] 已上传文件的大小,单位为字节
- $_FILES['file']['tmp_ name'] 储存的临时文件名,一般是系统默认
- $_FILES['file']['error'] 该文件上传相关的错误代码
- $_COOKIE通过 HTTP Cookies方式传递给当前脚本的变量的数组
- $HTTP_COOKIE_VARS包含相同的信息,但它不是一个超全局变量
- $_SESSION当前脚本可用 SESSION 变量的数组
- $HTTP_COOKIE_VARS包含相同的信息,但它不是一个超全局变量(4.1.0已废弃)
- $_ENV包含服务器端环境变量的数组,可在php程序的任何地方直接访问,只是被动的接受服务端的环境变量转换为数组元素
四、SQL注入
- 条件:
- 可控制用户数据的输入
- 原程序要执行的代码,拼接了用户输入的数据
- 利用方式:
- 查询数据
- 读写文件(一句话木马16进制转一下)http://127.0.0.1/sql.php?id=1 union select 1,2,3, <?php @eval($_POST['a']);?> into outfile "E:/LocalTest/WWW/test.php"
- 执行命令
- 修复方案:
- 使用预编译语句
- 使用存储过程
- 检查数据类型
- 使用安全函数
五、代码执行
代码执行漏洞是指应用程序本身过滤不严,用户可以通过请求将代码注入到应用中执行。当应用在调用一些能将字符串转化成代码的函数(如php中的eval)时,没有考虑到用户是否能控制这个字符串,将造成代码注入漏洞。狭义的代码注入通常指将可执行代码注入到当前页面中,如php的eval函数,可以将字符串代表的代码作为php代码执行,当前用户能够控制这段字符串时,将产生代码注入漏洞。
- 挖掘思路:
- 用户能够控制函数输入
- 存在可执行代码的危险函数
- 常见危险函数:
- eval和assert函数(将输入的字符串参数作为php程序代码来执行)
- 回调函数
- preg_replace函数
- 动态函数执行(定义一个函数、将函数名(字符串)赋值给一个变量、使用变量名代替函数名动态调用函数)
- 修复方案:
- 尽量不要执行外部的应用程序或命令
- 使用自定义函数或函数库来替代外部应用程序或命令的功能
- 使用escappeshellarg函数来处理命令的参数
- 使用safe_mode_exec_dir来指定可执行的文件路径
- 将执行函数的参数做白名单限制,在代码或配置文件中限制某些参数
六、命令执行
命令注入是一种攻击,其目标是通过易受攻击的应用程序在主机操作系统上执行任意命令。当应用程序将不安全的用户提供的数据(表单,Cookie,HTTP头等)传递给系统shell时,命令注入攻击是可能的。在这种攻击中,攻击者提供的操作系统命令通常以易受攻击的应用程序的特权执行。命令注入攻击可能很大程度上是由于输入验证不足。
- 挖掘思路:
- 用户能够控制函数输入
- 存在可执行代码的危险函数
- 常见危险函数:
- system函数
- exec函数
- shell_exec函数
- passthru函数
- ``(反引号)
- 修复方案:
- 尽量少用执行命令的函数或者直接禁用参数值尽量使用引号包括
- 在使用动态函数之前,确保使用的函数是指定的函数之一
- 在进入执行命令的函数/方法之前,对参数进行过滤,对敏感字符进行转义
- 尽量少用执行命令的函数
- 对于可控点是程序参数的情况下,使用escapeshellcmd函数进行过滤;对于可控点是程序参数值的情况下,使用escapeshellarg函数进行过滤
- 参数的值尽量使用引号包裹,并在拼接前调用addslashes进行转义
- 而针对由特定第三方组件引发的漏洞,及时打补丁,修改安装时的默认配置
- 命令执行和代码执行的区别:
- 代码执行:执行的效果完全受限于语言本身
- 命令执行:执行的效果不受限于语言语法本身,不受命令本身限制
- 命令执行的类型:代码层过滤不严、系统的漏洞造成命令注入、调用的第三方组件存在代码执行漏洞
七、XSS
- 挖掘思路:
没有过滤的参数,传入到输出函数中(搜索内容、发表文章、留言、评论回复、资料设置等)。
- 漏洞类型:
1.反射型:
非持久型,也叫反射型XSS。通过GET和POST方法,向服务器端输入数据。用户输入的数据通常被放置在URL的query string中,或者是form数据中。如果服务器端对输入的数据不进行过滤,验证或编码,就直接将用户输入的信息直接呈现给客户,则可能会造成反射型XSS。反射型XSS是比较普遍的XSS,其危害程度通常被认为较小。但是某些反射型XSS造成的后果会很严重,如在输入框的name中输入<meta http-equiv= "refresh" content="5" />,服务器不加处理,将name的值直接送到浏览器,则浏览器会每5秒自动刷新1次。严重者会导致服务器崩溃
常见场景:
将前端获取的内容,直接输出到浏览器页面
将前端获取的内容,直接输出到HTML标签
将前端获取的内容,直接输出到<script>标签
2.存储型:
持久型,也叫存储型XSS。通常是因为服务器端将用户输入的恶意脚本没有通过验证就直接存储在数据库,并且每次通过调用数据库的方式,将数据呈现在浏览器上。则该XSS跨站脚本攻击将一直存在。若其他用户访问该页面,则恶意脚本就会被触发,用于盗取其他用户的私人信息
3.DOM型:
全称Document Object Model,是一个平台和语言都中立的接口,可以使程序和脚本能够动态访问和更新文档的内容、结构以及样式。DOM型XSS其实是一种特殊类型的XSS,它是基于DOM文档对象模型的一种漏洞
- 数据流向的区别:
- 反射型:输入——输出
- 存储型:输入——进入数据库——取出数据库——输出
- DOM型:URL——浏览器
- 修复方案:
- 对所有输入中的script、iframe等字样进行严格的检查
- 验证数据的类型及其格式、长度、范围和内容
- 客户端做数据的验证与过滤,关键的过滤步骤在服务端进行
- 检查输出的数据
八、CSRF
- 漏洞本质:
攻击者获取到重要的参数,成功构造一个伪造请求。
- 攻击流程:
- 发送带有“跨站请求伪造”的HTTP链接给目标用户
- 目标登录网站
- 用户登录时,隐藏跨站请求伪造的HTTP链接
- 网站执行HTTP链接,因而引发跨站请求伪造
- 挖掘思路:
- 后台功能模块:管理后台、会员中心、添加用户等
- 被弓|用的核心文件里面有没有验证token和referer相关的代码
- 没带token:可以直接请求这个页面
- 没带referer:返回相同的数据
- 修复方案:
- 验证码
- 添加referer验证
- 添加token验证
九、文件操作
9.1文件上传
- 漏洞条件:
- 文件可上传
- 知道文件上传的路径
- 上传文件可以被访问
- 上传文件可以被执行
- 可控点:
- Content-Length,即上传内容大小
- MAX_FILE SIZE,即上传内容的最大长度
- filename,即上传文件名
- Content-Type,即上传文件类型
- 请求包中的乱码字段,即所上传文件的内容
- 挖掘思路:
- 上传点都调用同一个上传类,直接全局搜索上传函数
- 黑盒寻找上传点,代码定位
- 修复方案:
- 检测文件上传内容:黑白名单验证,检测文件扩展名是否合法;MIME验证,检测文件的MIME类型
- 限制文件大小
- 更改临时文件夹的路径
- 读取上传文件的绝对路径与文件名称
- 隐藏文件路径
9.2目录穿越
- 修复方案:
- 在URL内不要使用文件名称作为参数
- 检查使用者输入的文件名是否有“..”的目录阶层字符
- 在php.ini文件中设置open_ basedir来指定文件的目录
- 使用realpath函数来展开文件路径中的“./”、“../”等字符,然后返回绝对路径名称
- 使用basename函数来返回不包含路径的文件名称
9.3文件包含
- 分类:
- 远程文件包含:RFI允许攻击者包含远程文件,远程文件包含需要设置allow_ url_ include = On,四个文件都支持HTTP、FTP等协议,相对本地文件包含更容易利用,出现的频率没有本地包含多
- 本地文件包含:LFI允许攻击者通过浏览器包含本机上的文件。当一个WEB应用程序在没有正确过滤输入数据的情况下,就有可能存在这个漏洞,该漏洞允许攻击者操纵输入数据、注入路径遍历字符、包含web服务器的其他文件
- 挖掘思路:
- 模块加载、cache调用,传入的参数拼接包含路径
- include():使用此函数,只有代码执行到此函数时才将文件包含进来,发生错误时只警告并继续执行
- inclue_ once():功能和前者一样,区别在于当重复调用同一文件时,程序只调用一次
- require():使用此函数,只要程序执行,立即调用此函数包含文件,发生错误时会输出错误信息并立即终止程序
- require once():功能和前者一样,区别在于当重复调用同一文件时,程序只调用一次
- 修复方案:
- 关闭远程包含参数开关,彻底切断这个业务
- 设置类似白名单的方法,筛选固定文件名
- 常见目录穿越字符进行过滤,如(./ ../ ..\等)
9.4文件读取与删除
- 文件读取挖掘思路:
allow_url_fopen选项激活了URL形式的fopen封装协议使得可以访问URL对象例如文件。默认的封装协议提供用ftp和http协议来访问远程文件,一些扩展库例如zlib可能会注册更多的封装协议,全局搜索下列函数fopen()、file_get_contents()、fread、fgets、fgetss、file、fpassthru、parse_ini_file、readfile。
- 文件删除挖掘思路:
搜索unlink()函数。
- 修复方案:
- 正则严格判断用户输入参数的格式
- 检查使用者输入的文件名是否有“..”的目录阶层字符(配合目录穿越)
- 在php.ini文件中设置open_ basedir来限定文件访问的范围
十、变量覆盖
- 危害:
通常结合程序的其它漏洞实现完整的攻击,比如文件上传页面,覆盖掉原来白名单的列表,导致任意文件上传;用户注册页面控制没覆盖的未初始化变量导致SQL。
- 挖掘思路:
1.$$:遍历初始化变量(常见的遍历方式释放代码,可能导致变量覆盖漏洞,前提register_globals=off)
2.extract()函数第二个参数(较常见):
- EXTR_OVERWRITE:默认,如果有冲突,则覆盖已有的变量
- EXTR_PREFIX_SAME:如果有冲突,在变量名前加上前缀prefix。自PHP4.0.5起,这也包括了对数字索引的处理
- EXTR_IF_EXISTS:仅在当前符号表中已有同名变量时,覆盖它们的值。其它的都不处理。可以用在已经定义了一组合法的变量,然后要从一个数组例如$_REQUEST中提取值覆盖这些变量的场合,本标记是PHP4.2.0新加的
3.import_request_variable()函数(4.1.0<=PHP<5.4.0)
4.parse_str()函数
- 修复方案:
- 在php.ini文件中设置register_ globals=OFF
- 使用原始变量数组,如$_POST,$_GET等数组变量进行操作
- 不使用foreach语句来遍历$_GET变量,而改用[(index)]来指定
- 验证变量是否存在,注册变量前先判断变量是否存在
十一、反序列化
序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
- 漏洞成因:
反序列化对象中存在魔术方法,而且魔术方法中的代码可以被控制,漏洞根据不同的代码可以导致各种攻击,如代码注入、SQL注入、目录遍历等等。
- 漏洞本质:
- unserialize函数的变量可控
- php文件中存在可利用的类,类中有魔术方法
- 魔术方法:
__construct(),__destruct()
__call(),__callStatic()
__get(),__set()
__isset(),__unset()
__sleep(),__wakeup()
__toString()
__invoke()
__set_state()
__clone()
__debugInfo()
十二、弱类型
- 变量类型:
- 标准类型:布尔boolen、整型integer、浮点float、字符string
- 复杂类型:数组array、对象object
- 特殊类型:资源resource
- 操作之间比较:
1.字符串和数字
var_dump(0=="admin");//true var_dump("1admin"==1);//true var_dump("admin1"==1);//false var_dump("admin1"==0);//true
2.数字和数组
$arr=array(); var_dump(0==$arr);//false
3.字符串和数组
$arr=array(); var_dump('0'==$arr);//true
4."合法数字+e+合法数字"类型的字符串
var_dump("0e12345"=="0e45678");//true var_dump("1e1"=="10");//true
5.==和===
- 相关函数:
- empty()与isset():变量为: 0,"0",null,' ',false,array()时,使用empty函数返回的都为true;变量未定义或者为null时,isset函数返回的为false,其他都为true
- md5()传入不同数组md5值相同
- strcmp()比较字符串,相同返回0,比较字符串和数组也返回0
- 如果switch是数字类型的case的判断时,switch会将参数转换为int类型
$a="1asd"; switch($a){ case 1: echo "success"; //输出success break; case 2: echo "fail"; break; }
十三、伪协议
- file://协议——用于访问本地系统文件
allow_url_fopen:off/on
allow_url_include:off/on
- php://filter——读取源代码并进行base64编码输出
allow_url_fopen:off/on
allow_url_include:off/on
- php://filter——可以访问请求的原始数据的只读流
allow_url_fopen:off/on
allow_url_include:on
- data://协议——数据
allow_url_fopen:on
allow_url_include:on
十四、会话认证
14.1session劫持攻击
黑客劫持目标用户的session id来获取网站服务器上未经许可的存取信息,特别是窃取目标用户等的cookie数据,来取得网站的认可。
- 挖掘思路:
遇到的比较多的是出现在cookie验证上面,通常是没有使用session来认证,直接将用户信息保存在cookie中。
- 攻击流程:
- 目标用户登录网站
- 网站给予目标用户一个session id
- 获取目标用户的session id
- 黑客修改目标用户的session变量
- 修复方案:
- 使用随机而且长度够大的数字或字符串来当做session id
- 将网页之间传递的数据使用某种形式进行封装,特别是session id
- 更改session名称
- 注销后即销毁session的所有数据
14.2session固定攻击
黑客固定住目标用户的session id,因此目标用户所使用的session可由攻击者指定。
- 攻击流程:
- 登录网站服务器,返回session id
- 发送超链接,让目标用户点击
- 点击黑客的超链接
- 输入账号密码来登录
- 存取目标用户的session数据
- 修复方案:
- 不要从GET/POST变量中接受session id
- 调用session_start函数后,立即产生新的session id,并删除旧的session
- 将session id存放在cookie内
- 注销后即销毁session的所有数据
- 使用时间戳来记录session的使用时间,如果两次session的相差时间太长,就销毁session的所有数据
- 检查用户的IP地址,如果IP地址改变就产生一个新的session id,并且删除旧的session