代码审计基础

1 审计方法与步骤

A 审计前的准备

1 获得源码-安装网站(在本地搭建网站,一边审计一边测试,实时跟踪各种动态变化)

2 把我大局

  • 网站结构(浏览源码文件夹,了解程序的大致目录)
  • 入口文件(index.php,admin.php文件一般是整个文件的入口,详细读一下index.php文件可知道程序的架构,运行流程等)
  • 配置文件(一般类似config.php等文件,保存一些数据库相关信息程序的一些信息。先看看数据库编码,如果是gbk则可能存在宽字节注入。如果变量的值是双引号,则可能存在双引号解析代码执行问题)
  • (重点)过滤功能-通过详读公共函数文件和安全过滤文件,清晰掌握用户输入的数据,哪些被过滤,哪些无过滤;过滤方式是替换还是正则?有没有GPC?有没有使用addslasher()处理

B 审计方法

  • 通读源码(一般是企业对自身产品的审计,对于小型应用也可读一读)—-方法:把握大局,然后根据入口文件进行各个模块的审计
  • 敏感函数参数回朔法(shell_exec)—利用Seay法师审计系统,然后可以分析判断敏感函数的上下文,追踪参数源头
  • (重要)定向功能分析法–主要根据程序的业务逻辑来审计,首先用浏览器逐个访问浏览,看看这套程序有哪些功能。根据相关功能,大概存在哪些漏洞。

    常见功能漏洞:(包括但不限于)
    初始化安装
    站点信息泄露
    文件上传,管理
    登录认证,权限管理
    数据库备份恢复
    找回密码
    验证码
    总结:

  • 首先,把握大局,不管什么程序,都要把握大局

  • 其次,根据定向功能针对每一项功能进行审计;
  • 最后,就是明娜函数参数回溯

    2 常见的INI配置

    A 配置文件

  • php.ini: 在PHP启动时被读取。对于服务器模块版本的PHP,仅在web服务器启动时读取一次
  • .user.ini文件:自PHP5.3.0起,php支持基于每个目录的.htaccess风格的INI文件
  • 还可以在httpd.conf中覆盖php.ini的值,以进行更灵活的配置

    B 配置文件语法

    C 变量相关的配置

    php.ini
    -
    变量相关:
    启用全局变量 register_globals = off 作用是关闭自动注册的全局变量,在设置为on的时候,php会将$_POST,$_GET,$_COOKIE,$_ENV,$_SESSION数组总的$key->$value直接注册为变量,比如$_POST[“username”]就会被注册为$username

    虽然方便了调用,但是有三个问题:

    • 不知道变量是哪里来的$_POST的还是$_SESSION来的,非常不方便阅读代码
    • 变量之间互相覆盖,引起不必要的麻烦(重点)
    • 安全问题,所以要设置为off
      短标签:
      short_open_tag = on
      这个设置决定是否使用PHP代码开始标志的缩写形式(<??>),若禁用,开始标签必须是完整形式(<?php ?>)
      同时会影响到缩写形式<?=,它和<?echo等价,从php5.4.0起,<?=总是可用的
      主要在文件上传使用到,若开启我们可以在上传一句话等使用变形

      D 安全模式的配置

      safe_mode = off(默认)

      能够控制一些php中的函数,比如system(),同时把很多文件操作函数进行了权限控制,也不允许对某些关键文件的文件,比如/etc/passwd,但是默认的php.ini是没有打开安全模式的
      本特性已经在PHP5.3.0起飞起并将PHP5.40起移除

      禁用类/函数
      disable_classes=,disable_functions=,disable_function=opendir,readir,scandir,fopen,unlink
      禁用某些类,禁用某些函数。接受函数分隔的函数名列表作为参数。只能设置在php.ini中

      E 上传文件及目录权限的配置

      设置上传及最大上传文件大小
      file_uploads = on
      upload_max_filesze = 8M
      文件上传的临时目录
      upload_tmp_dir=
      上传临时文件保存的目录,需要可写,如果不设置,则采用系统临时目录(/tmp,C:\Windows\temp)
      用户访问目录限制
      open_basedir = .:/tmp/ linux下:代表不同目录的分隔;windows下;代表不同目录分割
      能够避免PHP脚本访问不应该访问的文件,一定程度上限制了phpshell的危害。我们一般可以设置为只能访问网站目录,表示允许访问当前目录(即PHP脚本文件所在目录)和/tmp/目录,有效防止php木马跨站运行

      F 错误信息的配置

      错误信息控制:
      display_error = On
      站点发布后应关闭此功能,以免暴漏信息,调试的时候为了输出错误信息,故打开
      设置错误报告级别:
      error_reporting = E_ALL
      这个设置的作用是将错误级别设置为最高,可以显示所有的问题,方便查错,也有利于写出高质量的代码。推荐使用E_ALL|E_STRICT,即所有级别。
      错误日志:
      error_log=
      错误日志的位置,必须对web用户可写入,如果不定义则默认写入到web服务器的错误日志中
      log_errors = on
      如下所言,建议将错误日志输出到文件,而不知直接输出到前端

      G 魔术引号及远程文件的配置

      魔术引号(本特性已自PHP5.3.0起废弃并将自PHP5.4.0起移除
      magic_quotes_gpc = On
      gagic_quotes_runtime = Off
      为GPC(Get/Post/Cookie)操作设置magic_quotes状态,当magic_quotes为on,所有单引号,双引号,反斜杠和NULL(%00)被一个反斜杠自动转义
      是否允许打开远程文件
      allow_url_fopen = on
      是否允许包含远程文件(include/require)
      allow_url_include = false

      3 常见危险函数及特殊函数

      代码执行函数

      eval$assert(调试函数,和eval同样有把字符长当做php执行的功能)&preg_replace

    • mixed eval(string $code)
      把字符串$code作为php代码执行
      很多的webshell都是用的eval执行具体的操作<?php @eval($_POST[“0”]);?>
    • bool assert(mixed $assertion[string $description])
      检查一个断言是否为FALSE
      因为大多数杀软把eval列入黑名单,故用assert来替代eval来执行具体的操作
    • preg_replace($pattern, $replacement, $string)
      搜索$string中符合正则规则$pattern的部分,以$replacement替换,返回替换后的内容。
      /e修正符使preg_replace()将replacement参数当做php代码

      包含函数

      require,include,require_once,include_once
      包含函数也能读取任意文件内容,这需要用到[支持的协议和封装协议]和[过滤器]
      例如:利用php流filter读取任意文件
      include($_GET[“file”]);
      ?file=php://filter/convert.base64.encode/resource=index.php

      命令执行函数

  • exec() -执行一个外部程 *
  • passthru() - 执行外部程序并显示原始输出 *
  • proc_open() - 执行一个命令并打开文件指针用于读取以及写入
  • shell_exec() - 通过 Shell 执行命令,并将执行结果作为字符串返回 *
  • system() - 允许执行一个外部程序并回显输出,类似于 passthru()。 *
  • popen() - 通过popen()参数传递一条命令,并对popen()所打开的文件进行执行

只要命令的参数可控就能执行系统命令
例如:

system($cmd);或者system('ping -c 3'.$target);
当$cmd可以控就能执行任意命令
当$target可控的话,可以使用管道符等特殊函数截断从而执行任意命令
$target = 'a | whoami':

大体的思路就是,先把握大局->针对漏洞有目的的搜索危险函数->定位危险函数所在文件->回溯危险源->找到执行的函数->过滤防护

文件操作函数

  • copy -拷贝函数
  • file_get_contents - 把整个文件读入为一个字符串
  • file_put_contents -将一个字符串写入文件
  • file -把整个文件读入一个数组中
  • fopen - 打开文件或者URL
  • move_uploaded_file -将上传的文件移动到新的位置
  • readfile - 输出文件
  • rename -重命名一个文件或目录
  • rmdir -删除目录
  • unlink&delete - 删除文件

任意文件读取写入删除往往是上面几个函数收到了控制

特殊函数

  • string getenv(string $varname)
    获取一个环境变量
  • bool putenv(string $setting)
    添加setting到服务器环境变量,环境变量仅存活与当前请求期间,在请求结束时环境就会自动恢复到初始状态

配置相关

  • string ini_get(string $varname) 成功时返回配置选项的值
  • string ini_set(string $varname,string $newvalue)
  • string ini_alter(string $varname,string $newvalue)

设置指定配置选项的值,这个选项会在脚本运行时保持新的值,并在脚本结束时恢复。

<?php
splay_errors = ".(ini_get('display_errors')?'On':'Off');
ini_set("display_errors",0);
echo "\r\n<br/>display_errors = ".(ini_get('display_errors')?'On':'Off');
?>

数字判断

  • bool is_numeric(mixed $var)
    如果var是数字和数字字符串则返回TURE,否则返回FALSE,如果仅用is_numeric判断而不用inval转换就有可能插入16进制的字符创到数据库中,进而导致sql的二次注入

数组相关

  • bool in_array(mixed $needle,array $haystack[,bool $strict = FALSE])

在haystack中搜索needle,若没有设置strict则使用宽松的比较。
该函数有一个特性,比较之前会进行自动类型转换

 $a = '1abc'
in_array($a,array(1,2,3)的返回值会是真

变量覆盖

- void parse_str(string $str[,arrary &$arr])

若str是URL传递的查询字符串(query string),则将它解析为变量并设置到当前域。

<?php
//parse_str
$str = "first=value&arr[]=foobar&arr[]=baz";
echo "<pre>";
parse_str($str,$array);
print_r($array);

var_dump(isset($first));//已经复制到数组中,不在当前域

parse_str($str);
var_dump(isset($first));
echo "\$first = $first";
echo "r\n<br />";
echo "\$arr[0]=$arr[0]";
echo "r\n<br />";
echo "\$arr[1]=$arr[1]";
?>
<pre>Array
(
    [first] => value
    [arr] => Array
        (
            [0] => foobar
            [1] => baz
        )

)
bool(false)
bool(true)
$first = valuer
<br />$arr[0]=foobarr
<br />$arr[1]=baz[Finished in 0.1s]

列目录

glob()函数依照libc glob()函数使用的规则寻找所有与pattern匹配的文件路径;
<?php
//glob
echo "<pre>";
print_r(glob("t*.php"));//匹配t开头的
?>
------*** end*** ------