目的:对源代码进行审计,寻找代码中的BUG和安全漏洞
一.代码审计的基础
1.基础:
html/js基础语法、PHP基础语法 ,面向对象思想,PHP小项目开发(Blog、注册登录、表单、文件上传、留言板等),Web漏洞挖掘及利用,Web安全工具基本使用(burpsuite、sqlmap等),代码审计工具(seay审计系统、zend studio+xdebug等)
2.代码审计两种基本方式:
- 通读全文源码:通读全文发作为一种最麻烦的方法也是最全面的审计方法。特别是针对大型程序,源码成千上万行。当然了解整个Web应用的业务逻辑,才能挖掘到更多更有价值的漏洞。
- 功能点审计:根据漏洞对应发生函数进行功能行审计,常会用到逆向溯源数据流方法进行审计。
3.代码审计两种基本方法:
- 正向追踪数据流:跟踪用户输入参数 -> 来到代码逻辑 -> 最后审计代码逻辑缺陷 -> 尝试构造payload
- 逆向溯源数据流:字符串搜索指定操作函数 -> 跟踪函数可控参数 -> 审计代码逻辑缺陷 -> 尝试构造payload
4.现cms可分大体两类:
- 单入口cms:不管访问哪个模块都使用同一个入口文件,常见的MVC框架采用这种模式。
- 多入口cms:每个模块都有一个入口文件(可以前端设置一个入口文件 index.php,后端创建一个入口文件admin.php,前后端的入口文件是独立的)。
二.代码审计的思路
1.确定要审计的源码是什么语言
2.确定该源码是单入口还是多入口
3.确定该语言的各种漏洞诞生的函数
三.php的弱类型
1.比较符号 == 与 ===
-
== 在进行比较的时候,会先将字符串类型转化成相同,如果整型跟字符型比较字符或从左往右提取整型直到遇到字符结束,再比较。
-
=== 在进行比较的时候,会先判断两种字符串的类型是否相等,当等号两边类型不同时,会先转换为相同的类型,再对转换后的值进行比较,如果比较一个数字和字符串或者涉及到数字内容的字符串,则字符串会被转换成数值并且比较按照常数值进行比较.。
2.array_search 与 is_array
-
is_array:判断传入的是不是一个数组。
-
array_search(x,$数组):在数组中寻找与指定值(x)相等的值,array_search函数 类似于"==",会进行类型的转换。
if(!is_array($_GET['test'])){
exit();
}
$test = $_GET['test'];
for($i = 0;$i<count($test) ;$i++ ){
if($test[$i] === "admin"){
echo "error";
exit();
}
$test[$i] = intval($test[$i]);
}
if(array_search("admin",$test) === 0){
echo "flag";
}else{
echo "false";
}
我们可以传入test[]=0来进行绕过,首先test是一个数组,符合is_array的判断,然后test=0;在array_search中0==admin为true,绕过了array_search。
3.in_array()函数
-
array_search()与in_array()也是一样的问题。
$array=[0,1,2,'3'];
var_dump(in_array('abc', $array)); //true
var_dump(in_array('1bc', $array)); //true
4.is_numeric()函数
-
检测变量是否为数字或数字字符串,如果var是数字和数字字符串则返回TRUE,否则返回FALSE
$temp = $_GET['password'];
is_numeric($temp) ? die("no numeric") : NULL;
if($temp>9999){
echo '我giao';
}
通过输入一个大于9999的数字后加上字符串构造
5.strcmp()函数
-
比较函数如果两者相等返回0,string1>string2返回>0 反之小于0。在5.3及以后的php版本中,当strcmp()括号内是一个数组与字符串比较时,也会返回0。
函数接受到了不符合的类型,发生了错误,但是还是判断其相等
6.switch()语句
-
如果switch是数字类型的case的判断时,switch会将参数转换为int类型
7.md5()函数
-
0e开头的全部相等(绕过==判断),两个字符串转换成MD5值时都是0e开头,0e 纯数字这种格式的字符串在判断相等的时候会被认为是科学计数法的数字,先做字符串到数字的转换。
-
md5()中的需要是一个string类型的参数。但是当你传递一个array时,md5()`不会报错,只是会无法正确地求出array的md5值,返回false,这样就会导致任意2个array的md5值都会相等。
var_dump(md5('240610708') == md5('QNKCDZO'));//true
$array1=[1,2,3];
$array2=[4,5,6];
var_dump(md5($array1)===md5($array2)) //true
8.sha1()函数
-
sha1函数和md5函数一样不能判断数组的值。
$array1=[1,2,3];
$array2=[4,5,6];
var_dump(sha1($array1)===sha1($array2)); //true
9.empty与isset
-
变量为:0,"0",null,'',false,array()时,使用empty函数,返回的都是true
-
变量未定义或者为null时,isset函数返回的为false,其他都为true
四. PHP核心配置
一个漏洞在不同环境造成的结果也是不一样的。
由于关于php.ini配置的内容过于多,这里推荐浏览官方文档 PHP: php.ini 配置 - Manual,我们在这里主要列下php.ini 主要使用的安全配置。
-
safe_mode = off
用来限制文档的存取,限制环境变量的存取,控制外部程序的执行.PHP5.4.0移除。
-
限制环境变量存取
safe_mode_allowed_env_vars = string
指定php程序可以改变的环境变量的前缀,当这个选项的值为空时,那么php可以改变任何环境变量,如果 如:safe_mode_allowed_env_vars = PHP_,当这个选项的值为空时,那么php可以改变任何环境变量。
-
外部程序执行目录
safe_mode_exec_dir = "/usr/local/bin"
当安全模式被激活,safe_mode_exec_dir参数限制通过exec()函数执行的可执行文件到指定的目录。举例来说,如果你想限制在/usr/local/bin目录执行功能,你可以使用这个指令:
safe_mode_exec_dir = "/usr/local/bin"
-
禁用函数
disable_functions
为了更安全的运行PHP,可以用此指令来禁止一些敏感函数的使用,当你想用本指令禁止一些危险函数时,切记把dl()函数也加到禁止列表,攻击者可以利用dl()函数加载自定义的php扩展突破disable_functions.配置禁止函数时可以使用逗号分隔函数名。
-
COM组件
com.allow_dcom = false
PHP设置在安全模式下(safe_mode),仍允许攻击者使用COM()函数来创建系统组件来还行任意命令,推荐关闭这个函数。 使用COM()函数需要在PHP.ini中配置
extension=php_com_dotnet.dll
,如果PHPversion<5.4.5则不需要。
-
全局变量注册开关
register_globals = off
php.ini的register_globals选项的默认值为OFF,在4.2版本之前是默认开启的,当设定为On时,程序可以接收来自服务器的各种环境变量,包括表单提交的变量,这是对服务器分厂不安全的, register_globals = off时,服务器端获取数据的时候用$_GET['name']来获取数据。 register_globals = on时,服务端使用POST或GET提交的变量,豆浆自动使用全局变量的值来接受。
-
魔术引号自动过滤
magic_quotes_gpc = on
PHP5.4.0被移除 magic_quotes_gpc = off 在php.ini中默认是关闭的,如果打开它,将自动把用户提交对sql的查询的语句进行转换,如果设置成ON,php会把所有的单引号,双引号,和反斜杠和空字符(NULL)加上反斜杠()进行转义 它会影响HTTP请求的数据(GET,POST.COOKIE),开启它会提高网站的安全性。
-
是否允许包含远程文件
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 = D:\WWW
能够控制PHP脚本只能访问指定的目录,这样能够避免PHP脚本访问不应该访问的文件,一定程度上限制了。webshell的危害
-
内部错误选项
display_errors = on
表明实现PHP脚本的内部错误,网站发布后建议关不PHP的错误回显。
-
错误报告级别
error_reporting(E_ALL & ~Enotice)
具体列表推荐:PHP error_reporting() 函数 | 菜鸟教程
这里设置的作用是将错误级别调到最高,显示所有问题,方便环境部署时候排错。
五. 学习漏洞函数
1.全局变量/超全局变量
全局变量:
-
定义在函数外部的就是全局变量,它的作用域从定义处一直到文件结尾。
-
函数内定义的变量就是局部变量,它的作用域为函数定义范围内。
-
函数之间存在作用域互不影响。
-
函数内访问全局变量需要 global关键字或者使用 $GLOBALS[index]数组。
超全局变量:
-
超全局变量 在 PHP 4.1.0 中引入,是在全部作用域中始终可用的内置变量。
- PHP 中的许多预定义变量都是“超全局的”,这意味着它们在一个脚本的全部作用域中都可用。在函数或方法中无需执行 global $variable; 就可以访问它们。
常用的超全局变量有9个:
-
$GLOBALS:引用全局作用域中可用的全部变量
-
$_SERVER:系统环境变量
-
$_REQUEST:可以接受get和post两种传参的值
-
$_POST:广泛用于HTML表单提交
-
$_GET:提交数据(HTML表单,URL中的数据)
-
$_FILES:上传文件使用
-
$_ENV:服务器端环境变量
-
$_COOKIE:用于会话控制
-
$_SESSION:用于会话控制
此处推荐文章:PHP 全局变量 - 9 个超全局变量详解与用法示例-CSDN博客
2.SQL注入
-
urldecode — 解码已编码的 URL 字符串
-
rawurldecode — 对已编码的 URL 字符串进行解码
3.代码执行
- eval:eval(string)会将string最为php代码执行
- assert:assert(“is_int($s)”) 判断里面的内容是否成立,字符串会被执行和eval类似
- preg_replace:执行正则表达式的替换
- create_function:create_function(字符串$args,字符串$code) args 声明变量部分,code代码执行部分,代码执行部分内部有执行eval函数,例如下面就是执行了system('whoami')
$newfunc = create_function('$v', 'return system($v);');
$newfunc('whoami');
-
array_map:为数组的每个数组进行调用函数array_map(function,array1,......) 返回的就是array1调用function函数返回的值,如果下面的get传参是phpinfo,能够显示php配置的所有信息
<?php
$arr=$_GET['arr'];
$array=array(1,2,3,4,5);
$new_array=array_map($arr,$array);
?>
-
call_user_func:只执行第一个数组调用函数,call_user_func(function,array1...) 返回值就是array1调用function函数的返回值
<?php
call_user_func("assert",$_GET['cmd']);
?>
-
call_user_func_array:调用回调函数,并把一个数组参数作为回调函数的参数
<?php
$cmd=$_GET['cmd'];
$array[0]=$cmd;
call_user_func_array("assert",$array);
?>
-
array_filter:array_filter(array,callback) 遍历array中的所有数据,并将值传给callback函数,如果过了callback返回true,否则返回false,返回过滤后的数组
4. 命令执行
- system: 执行外部程序,并显示输出,system(string $commmand,int $return) 执行command命令并将结果保存在return里
<?php
system("whoami");
?>
- exec : 执行外部程序,例如<?php echo exec("whoami");?>
- shell_exec : 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。
<?php echo shell_exec("whoami");?>
-
passthru:执行外部程序并且显示原始输出
5. XSS跨站脚本攻击
- print_r
- echo
- printf
- die :退出当前脚本
- var_dump:输出变量相关信息
- var_export:输出变量相关信息
6.文件上传漏洞
move_uploaded_file()
7.文件包含漏洞
include():会将指定的文件载入并执行里面的程序,会多次引用 include_once():与上面的差别是只会引用一次 require():与include()区别在于include和include_once按照代码执行的顺序执行,而require与require_once是在加载页面最开始执行 require_once():与上面的差别是只会引用一次
伪协议
file:// — 访问本地文件系统 http:// — 访问 HTTP(s) 网址 ftp:// — 访问 FTP(s) URLs php:// — 访问各个输入/输出流(I/O streams) zlib:// — 压缩流 data:// — 数据(RFC 2397) glob:// — 查找匹配的文件路径模式 phar:// — PHP 归档 ssh2:// — Secure Shell 2 rar:// — RAR ogg:// — 音频流 expect:// — 处理交互式的流
此处推荐文章:https://segmentfault.com/a/1190000018991087
8.任意文件下载
fopen() readfile() file_get_contents()
9.任意文件删除
unlink()
10.任意文件读取
file() fgets() fgetss() fopen() readfile() fpassthru() parse_ini_file() file_get_contents()
11.变量覆盖
extract() :函数在 PHP 中用于从数组中将变量导入到当前的符号表。换句话说,这个函数允许你将数组中的键值对转换成变量名和变量的值。这对于处理从表单、数据库或其他来源接收的数据时特别有用,因为它允许你轻松地将这些数据转换成 PHP 脚本中可以直接使用的变量。 parse_str() import_request_variables()//此函数只能用于PHP4.1 ~ PHP5.4
12.反序列化漏洞
unserialize()
魔术方法
-
__construct()//每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作
-
__destruct()//某个对象的所有引用都被删除或者当对象被显式销毁时执行
-
__call() //在对象上下文中调用不可访问的方法时触发
-
__callStatic() //在静态上下文中调用不可访问的方法时触发
-
__get() //用于从不可访问的属性读取数据
-
__set() //用于将数据写入不可访问的属性
-
__isset() //在不可访问的属性上调用isset()或empty()触发
-
__unset() //在不可访问的属性上使用unset()时触发
-
__sellp() //使用serialize时触发
-
__wakeup() //使用unserialize时触发
-
__toString() //把类当作字符串使用时触发
-
__invoke() //当脚本尝试将对象调用为函数时触发
-
__set_state()//当调用 var_export() 导出类时,此静态方法会被自动调用。
-
__clone()//当使用 clone 复制一个对象时自动调用
-
__debuginfo()//使用 var_dump() 打印对象信息时自动调用
六.防御函数
- addslashes():addslashes()通常用于防止sql注入,它可对通过get,post和cookie传递过来的参数的单引号和双引号已经null前加“\”进行转义
- htmlspecialchars():将特殊的字符转成HTML实体
- str_replace:以其他字符替换字符串中的一些字符(区分大小写)
- trim:用于删除字符串的头尾空白符,空白符包括:空格、制表符 tab、换行符等其他空白符
七.总结
审计路线:Demo->综合漏洞靶场->网上审计过的CMS->多入口CMS->单入口CMS->框架->函数缺陷