php代码审计入门

代码审计目的

  代码审计指的是对源代码进行检查,寻找代码中的bug以及安全缺陷(漏洞)。代码审计这是一个需要多方面技能的技术,也是需要一定的知识储备。我们需要掌握多种编程语言,安全工具的使用、漏洞原理、漏洞的修复方式、函数的缺陷等等,如果再高级一些,我们需要学习不同的设计模式,编程思想、MVC框架以及常见的框架,开发项目的思维。那么对于像我这样的小白应该是需要一个路线,一个流程。

代码审计基础

代码审计入门基础:html/js基础语法、PHP基础语法 ,面向对象思想,PHP小项目开发(Blog、注册登录、表单、文件上传、留言板等),Web漏洞挖掘及利用,Web安全工具基本使用(burpsuite、sqlmap等),代码审计工具(seay审计系统、zend studio+xdebug等)

代码审计两种基本方式:
通读所有源码:通读所有代码作为一种最麻烦的方法也是最全面的审计方法。特别是针对大型程序,源码成千上万行。当然了解整个Web应用的业务逻辑,才能挖掘到更多更有价值的漏洞。
功能点审计:根据漏洞对应发生函数进行功能行审计,常会用到逆向溯源数据流方法进行审计。
代码审计两种基本方法:

正向追踪数据流:跟踪用户输入参数 -> 来到代码逻辑 -> 最后审计代码逻辑缺陷 -> 尝试构造payload
逆向溯源数据流:字符串搜索指定操作函数 -> 跟踪函数可控参数 -> 审计代码逻辑缺陷 -> 尝试构造payload
现cms可分大体两类:
单入口cms:不管访问哪个模块都使用同一个入口文件,常见的MVC框架采用这种模式。
多入口cms:每个模块都有一个入口文件(可以前端设置一个入口文件 index.php,后端创建一个入口文件admin.php,前后端的入口文件是独立的)。

代码审计思路

从个人角度出发,如果环境允许的话,可以先选择做一个”程序员“再来做代码审计。

因为从开发者的位置去思考问题,可以快速定位问题。学习面向对象编程以及面向过程编程,编写一些项目提升对代码的理解能力,再是对各种漏洞可以独立挖掘利用并能理解漏洞的危害,这里我们主要针对PHP源码做审计。

接下来我们从三个层次开始我们的源码审计思路
1.确定要审计的源码是什么语言
2.确定该源码是单入口还是多入口
3.确定该语言的各种漏洞诞生的函数

PHP核心配置

一个漏洞在不同环境造成的结果也是不一样的。由于关于php.ini配置的内容过于多,这里推荐浏览官方文档https://www.php.net/manual/zh/ini.php,我们在这里主要列下php.ini 主要使用的安全配置。

safe_mode=off

PHP的safe_mode 功能是一个过时的安全特性,在早期的 PHP 版本中(比如 PHP 4 和早期的 PHP 5)默认启用。它旨在限制脚本能访问的文件路径、系统资源等,以防恶意攻击。然而,从 PHP 5.3 开始,safe_mode 已经不再推荐使用,并在 PHP 7 中完全移除。所以,safe_mode 主要影响的是 PHP 4.x 和早期的 PHP 5.x 版本。safe_mode是提供一个基本安全的共享环境。在一个多用户共享的phpweb服务器上,当这台服务器开启了safe_mode模式,有以下函数将会受到影响。首先,以下尝试访问文件系统的函数将会被限制,运行服务器的用户id,如果想要尝试操作某个文件,必须要用户对该文件的读取或者写入的访问权限。因此,在safe_mode打开的情况下,下列函数将会收到限制:
ckdir,move_uploaded_file,chgrp,parse_ini_file,chown,rmdir,copy,rename,fopen,require,highligh

safe_mode_allowed_env_vars=string

限制环境变量的存取,指定php程序可以改变的环境变量的前缀,当这个选项的值为空时,那么php可以改变任何环境变量

safe mode_exec dir ="xxx"

外部程序执行目录   
当安全模式被激活,safe_mode_exec_dir参数限制通过exec()函数执行的可执行文件到指定的目录。举例来说,如果你想限制在/usr/local/bin目录执行功能,你可以使用这个指令:safe_mode_exec_dir = "/usr/local/bin"

一般情况下,如果不需要执行什么程序,建议不要指定执行系统程序的目录。可以指定一个目录,然后把需要执行的程序拷贝到这个目录即可,例如:safe_mode_exec_dir = /temp/cmd
但是,更推荐不要执行任何程序。这种情况下,只需要将执行目录指向网页目录即可
例如  safe_mode_exec_dir= /usr/www
注意 : 执行目录的路径要以实际操作系统目录路径为准

disable_functions

这里是禁用函数的地方,一个黑名单,如果拿到shell却执行不了命令,多半就是这里出了问题。为了更安全的运行PHP,可以用此指令来禁止一些敏感函数的使用,当你想用本指令禁止一些危险函数时,切记把dl()函数也加到禁止列表,攻击者可以利用dl()函数加载自定义的php扩展突破disable_functions.配置禁止函数时可以使用逗号分隔函数名。注意同时还需要禁用LD_PRELOAD和putenv()这两个函数。

COM组件com.allow_dcom= false

PHP设置在安全模式下(safe_mode),仍允许攻击者使用COM()函数来创建系统组件来执行任意命令,推荐关闭这个函数。 使用COM()函数需要在PHP.ini中配置extension=php_com_dotnet.dll,如果PHPversion<5.4.5则不需要

register_globals=off

register_globals 全局变量注册开关
php.ini的register_globals选项的默认值为OFF,在4.2版本之前是默认开启的,当设定为On时,程序可以接收来自服务器的各种环境变量,包括表单提交的变量,这是对服务器分厂不安全的, register_globals = off时,服务器端获取数据的时候用$_GET['name']来获取数据。 register_globals = on时,服务端使用POST或GET提交的变量,都将自动使用全局变量的值来接受。

magic_quotes_gpc=on

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的情况下,同时allow_url_open = on时,可以直接包含远程文件,若包含的变量为可控的情况下,可以直接控制变量来执行PHP代码。即该选项设置:是否允许include()和require()函数包含URL(HTTP,HTTPS)作为文件处理。该设置对本地文件包含不会造成影响。

allow_url_open=on

允许本地PHP文件通过调用url重写来打开或者关闭写权限,默认的封装协议提供的ftp和http协议来访问文件。该设置对本地文件包含不会造成影响。

expose_php=off

在 PHP 中,expose_php 是一个 PHP 配置选项,用于控制是否在 HTTP 响应头中显示 PHP 版本信息。当expose_php 的值为 On 时,PHP 版本信息会显示在响应头中,否则不会显示。expose_php 主要是为了提供给攻击者更多的信息,以便他们利用已知的 PHP 漏洞。因此,建议在生产环境中关闭这个选项,以增加服务器的安全性。
底层原理是在 PHP 的源码中,有一个默认的 HTTP 响应头模板,当 expose_php 值为 On 时,PHP 会将当前 PHP 的版本信息填充到响应头模板中,然后发送给客户端。当 expose_php 值为 Off 时,PHP 不会填充版本信息到响应头模板中。

当expose_php = On时,会出现php版本信息,当expose_php = Off时,便不会出现php的版本信息

upload_tmp_dir

上传文件临时保存的目录,如果不设置的话,则采用系统的临时目录。

open_basedir

用户可访问目录  open_basedir = D:\WWW
能够控制PHP脚本只能访问指定的目录,这样能够避免PHP脚本访问不应该访问的文件,一定程度上限制了webshell的危害

display_errors

内部错误选项,表明是否显示PHP脚本的内部错误,网站发布后建议关闭PHP的错误回显

session_use_trans_sid

session_use_trans_sid=off
如果启用 session.use_trans_sid,会导致 PHP 通过 URL传递会话 ID,这样一来,攻击者就更容易劫持当前会话,或者欺骗用户使用已被攻击者控制的现有会话。

手动调试代码

在进行手动代码调试时,可以让程序输出一些中间变量或者是计算结果的值,这样方便做进一步的判断

echo
exit();
print_r
var_dump();
debug_zval_dump();
debug_print_backtrace();
echo "<script>alert($estr);</script>"";
die("<script>alert($estr);</script>");

PHP的弱类型

比较符号==与===

比较符号 == 与 ===
== 两个等号是先把等号两边的变量转化成相同的类型,如果转换类型后的结果是相等的,就认为相等。如果整型跟字符型比较字符或从左往右提取整型直到遇到字符结束,再比较
=== 三个等号是先判断两边变量的数据类型,如果数据类型相同,再去判断两边的值,如果值相等,那么为真
具体执行过程
1.判断全等于操作符两边的数据类型是否相同 如果不相同,则返回false
2.判断全等于操作符两边的值是否相等,如果不相等,则返回false

<?php 
    var_dump(0=="admin");   //true
    echo "<br>";
    var_dump(123==="123");  //false
    echo "<br>";
    var_dump(123=="123");   //true
    echo "<br>";
    var_dump(123=="123c");  //true
    echo "<br>";
    var_dump(123<"124c");   //true
    echo "<br>";
    var_dump(0=="hahaha");  //true
    echo "<br>";

    var_dump(1=="1admin");  //true
    echo '<br>';
    var_dump(1=="admin1");  //false
    echo '<br>';
    var_dump(0=="admin1");  //true
    echo '<br>';
    //数字和数组
    $arr = array();
    var_dump(0==$arr);  //false
    echo '<br>';
    //字符串和数组
    $arr = array();
    var_dump("0"==$arr);    //false
    echo '<br>';
    //"合法数字+e+合法数字"类型的字符串
    var_dump("0e123456"=="0e4456789");  //true
    echo '<br>';
    var_dump("1e1"=="10");  //true
    echo '<br>';
    var_dump("1e2"=="10");  //false
?>

array_search与is_array in_array

is_array:判断传入的是不是一个数组。
array_search(x,$数组):在数组中寻找与指定值(x)相等的值,array_search函数 类似于"==",会进行类型的转换。

<?php
    if(!is_array($_GET['test'])){
    exit();
    }

    $test = $_GET['test'];
    echo count($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,得到flag

in_array

in_array函数与array_search也是一样的问题,利用类似于"=="进行判断,会进行类型的转换。作用是判断说查询数据是否在数组中。

<?php
    $array = [0,1,2,'3'];
    var_dump(in_array('abc',$array));  //true
    
    var_dump(in_array('2bc',$array)); //true
    
    var_dump(in_array('345',$array)); //false

is_numeric

检测变量是否为数字或者数字字符串,如果变量是数字和数字字符串则返回true,否则返回false


<?php
    $a = 1233;
    $b = '123';
    $c = "1234";
    $d = "123c";
    var_dump(is_numeric($a)); //true
    var_dump(is_numeric($b));//true
    var_dump(is_numeric($c));//true    
    var_dump(is_numeric($d));//false

strcmp

比较函数如果两者相等返回0,string1>string2返回值大于0 反之小于0。在5.3及以后的php版本中,当strcmp()括号内是一个数组与字符串比较时,也会返回0。该函数本质是字符的ASCII码的比较

<?php
$a = "123";
$b = 123;
var_dump(strcmp($a,$b));  //0
$a1 = "123c";
var_dump(strcmp($a1,$b));  //1
var_dump(strcmp($a1,$a)); //1

$b1 = 1234;
var_dump(strcmp($b,$b1)); //-1
$str = 'ABCD';
var_dump(strcmp($b1,$str));  //-1

switch

如果switch是数字类型的case的判断时,switch会将参数转换为int类型


<?php
$choice = "2xx";
switch($choice){
    case 1:
        echo "1";
        break;
    case 2:
        echo "2";
        break;
    case 0:
        echo "0";
        break;
}


//输出结果为   2

md5

科学计数法 0e绕过
md5() 遇到「公式」,会先「运算」,再对运算结果「计算」MD5。由于0和任何数相乘都等于0,所以0e开头的任何数,其MD5都是相同的。

一些md5值为0e开头的字符
QNKCDZO   => 0e830400451993494058024219903391
240610708 => 0e462097431906509019562988736854

md5() 不能处理数组,数组都返回null。同时会报一个Warning,不影响执行,不用管。

sha1

sha1函数和md5函数一样不能判断数组的值。

$array1=[1,2,3];
$array2=[4,5,6];
var_dump(sha1($array1)===sha1($array2)); //true

empty与isset

变量为:0,"0",null,'',false,array()时,使用empty函数,返回的都是true
变量未定义或者为null时,isset函数返回的为false,其他都为true

$a = null;
$b = 0;
$c = "";
$d = "0";
$e = false;
$f  = array();
var_dump(empty($a));//true
var_dump(empty($b));//true
var_dump(empty($c));//true
var_dump(empty($d));//true
var_dump(empty($e));//true
var_dump(empty($f));//true
var_dump(isset($a));//false
var_dump(isset($b));//true
var_dump(isset($c));//true
var_dump(isset($d));//true
var_dump(isset($e));//true
var_dump(isset($f));//true

学习"漏洞"函数

全局变量和超全局变量

全局变量/超全局变量
全局变量:定义在函数外部的就是全局变量,它的作用域从定义处一直到文件结尾。
函数内定义的变量就是局部变量,它的作用域为函数定义范围内,函数之间存在作用域,局部变量互不影响。

函数内访问全局变量需要 global关键字或者使用 $GLOBALS[index]数组。

超全局变量:
超全局变量 在 PHP 4.1.0 中引入,是在全部作用域中始终可用的内置变量。

PHP 中的许多预定义变量都是“超全局的”,这意味着它们在一个脚本的全部作用域中都可用。在函数或方法中无需执行 global $variable; 就可以访问它们。
参考  https://www.runoob.com/php/php-superglobals.html
常用的超全局变量
$GLOBALS
$_SERVER
$_REQUEST
$_POST
$_GET
$_FILES
$_ENV
$_COOKIE
$_SESSION

SQL注入

select    update     insert into     delete

代码执行

eval()
usort()
uasort()
assert()
array_map()
preg_replace()
array_filter()
call_user_func()
create_function()
call_user_func_array()
文件操作函数:
fputs(fopen('shell.php','w'),'<?php eval($_POST[cmd])?>'); 
动态函数:$_GET['a']($_GET['b'])

命令执行

system()
exec()
passthru()
shell_exec()

XSS跨站脚本

print
print_r
echo
printf
die
var_dump
var_export

文件上传漏洞

move_uploaded_file()

文件包含

include()  

include_once()   只包含一次

require()  

require_once()   只包含一次

伪协议

详细参考 https://segmentfault.com/a/1190000018991087
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:// — 处理交互式的流

任意文件下载

fopen()   readfile()    file_get_contents()

任意文件删除

unlink()

任意文件读取

file()
fgets()
fgetss()
fopen()
readfile()
fpassthru()
parse_ini_file()
file_get_contents()

变量覆盖

$

$a='hello';
$b='world';
$$a=$b;
echo($hello);  echo '<br>';
echo($a);

输出
world
hello

这里先是定义了a,b两个变量,分别赋值hello和world。接着,$$a表示将变量a的值变成一个变量即$hello,然后将变量b的值赋给它,最后输出world。
所以在源码中看到$$可以想到变量覆盖漏洞。

反序列化漏洞

unserialize()

魔法方法
__construct()//每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作

__destruct()//某个对象的所有引用都被删除或者当对象被显式销毁时执行

__call() //在对象上下文中调用不可访问的方法时触发

__callStatic() //在静态上下文中调用不可访问的方法时触发

__get() //用于从不可访问的属性读取数据

__set() //用于将数据写入不可访问的属性

__isset() //在不可访问的属性上调用isset()或empty()触发

__unset() //在不可访问的属性上使用unset()时触发

__sleep() //使用serialize时触发

__wakeup() //使用unserialize时触发

__toString() //把类当作字符串使用时触发

__invoke() //当脚本尝试将对象调用为函数时触发

__set_state()//当调用 var_export() 导出类时,此静态方法会被自动调用。

__clone()//当使用 clone 复制一个对象时自动调用

__debuginfo()//使用 var_dump() 打印对象信息时自动调用

  • 18
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值