php代码审计学习笔记1

php代码审计学习笔记1

课程是某牛的,笔记只供个人学习

感觉就是从代码层面复习了一遍漏洞原理

环境配置及审计工具介绍

环境就用phpstudy

看代码的话我用的是notepad++

审计工具可以用seay源代码审计系统(基于正则

Fortify SCA也是一款不错的商业化代码审计工具(靠内置的规则空间匹配

rips审计工具也不错,可以看到用户输的一些点

以及一款数据库监控工具 vMysqlMonitoring.exe

审计靶场:VAuditDemo ZVulDrill

代码审计的思路及流程

MVC架构

MVC是一种设计创建Web程序的一种模式,其同时提供了HTML,CSS和JAVASCRIPT的完全控制。

image-20220824105406833

MVC关系

image-20220824105546784

常见的php框架

image-20220824105649145

审计思路

MVC框架常见的一个处理流程
获取请求 全剧过滤 模块文件 c函数内容 m函数内容 v显示
常见网站目录架构
主目录

模块目录
插件目录
上传目录
模板目录
数据目录
配置目录

配置文件

公共函数的文件
安全过滤的文件

数据库结构

入口文件
通读全文
函数集文件,配置文件,安全过滤文件,index文件
优点:了解程序的架构及业务逻辑,挖掘更高质的漏洞
缺点:耗费时间多,工程较大
敏感关键字回溯参数
优点:快速挖掘想要的漏洞,最常用的方法。
缺点:覆盖不到逻辑漏洞的挖掘,不能了解程序的基本架构
查找可控变量
可以用rips审计工具的查找用户输入功能
功能点定向审计
程序安装,文件上传,文件管理,登录验证,备份恢复,找回密码

PHP核心配置详解

PHP.INI配置文件也就是所谓的核心配置

下面是一些常亮

image-20220824183645573

基本配置-语法

大小写敏感
directive = value(指令 = 值)
foo=bar ≠ FOO=bar
运算符
|、&、~、!

image-20220824185643247

空值的表达方式
foo =     ;
foo = none;
foo = "none";这个就表示这么一个字符串了,而不是空值

基本配置-安全模式

安全模式
safe_mode = off
安全模式,用来限制文档的存取、限制环境变量的存取,限制外部程序的执行。
本特性已在PHP5.4.0被移除
限制环境变量存取
safe_mode_allowed_env_vars = string
指定php程序可以改变的环境变量的前缀,当这个选项的值为空时,那么php可以改变任何环境变量。
外部程序执行目录
safe_mode_exec_dir = "E:\Local Test\WWW"
禁用函数
disable_function
为了更安全的运行php,可以用此指令来禁止一些敏感函数的使用,当你想用本指令禁止一些危险函数时,切忌把dl()函数也加到禁止的列表,攻击者可以利用dl()函数加载自定义的php扩展来突破disable_function。配置禁用函数时可以使用逗号分隔函数名。
同理
disable_classes是禁用类
com组件
com.allow_dcom = false
php设置在安全模式下,仍旧允许攻击者使用com()函数来创建系统组件来执行任意命令,我推荐关闭这个函数来防止出现此漏洞。
使用COM()函数需要在php.ini中配置extension=php_com_dotnet.dll,如果php VERSION<5.4.5则不需要

基本配置-控制变量

全局变量注册开关
register_globals = off
php.ini的register_globals选项的默认值预设为off,在4.2版本之前是默认开启的,当register_globals的设定为On时,程序可以接受来自服务器的各种环境变量,包括表单提交的变量,这是对服务器来讲是不安全的,所以我们不能让它注册为全局变量。
register_globals = off时,服务器端获取数据的时候用$_GET['name']来获取数据
register_globals = On时,服务器使用POST或者GET提交的变量,都将自动使用全局变量的值来接受值。
如下:
开启这个开关之后,我们只要传入一个_SESSION[username]=aa,就可以doeverthing
关掉之后,我们就不能获取权限了

image-20220824194314976

魔术引号过滤
magic_quotes_gpc = on  本特性已在PHP5.4.0已经移除
magic_quotes_gpc = Off 在php.ini里面是默认关闭的,如果他打开后将自动把用户提交对sql的查询语句进行转换,如果设置成ON的话,php会吧所有的单引号,双引号,反斜杠和空字符加上反斜杠进行转义,他会影响HTTP请求的数据有(GET,POST,Cookies),开启他会提高网站的安全性,当然,您也可以使用addslashes来转义提交的HTTP请求数据,或者用stripsashes来删除转义

基本配置-远程文件

是否允许远程包含文件
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
这个设置的作用是将错误级别跳到最高,显示所有问题,方便排错。

代码调试

代码调试小技巧

echo
最简单的输出调试方法,一般用来输出变量值或者不确定执行到哪个分支。

print_r(true为1,false为空),var_dump,debug_zval_dump
这个主要是输出变量的数据值,特别是数组和对象数据,一般我们在查看接口的返回值或者不确定的变量,都可以使用这个api,debug_zval_dump输出结果和var_dump类似,唯一增加的一个值是refcount,记录一个变量被引用了多少次

debug_print_backtrace
可以查看输出的调用栈信息

exit()
停止程序,无法运行后面代码

审计中涉及的超全局变量

全局变量:全局变量就是在函数外面定义的变量。不能在函数中直接使用。因为他的作用域不会到函数内部。所以在函数内部使用的时候常常看到类似global$a;
超全局变量:超全局变量作用域在所有脚本都有效。注意,除了$_GET,$_POST,$_SERVER,$_COOKIE等之外的超全局变量保存在$GLOBALS数组中。

$GLOBALOS

<?php
$name = 123;
function test(){
    $name = 456;
}

test();
echo $name;

以上代码输出的是123

<?php
$name = 123;
function test(){
    global $name;
    $name = 456;
}

test();
echo $name;

这样就是输出的456,其中global起的是一个参数传递的作用。

<?php
$name = 123;
function test(){
    $GLOBALS[name] = 4567;
}

test();
echo $name;

输出4567,变量的作用域设置的全局

$_POST$_GET

post是向服务器传送数据。将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址,用户看不到这个过程。

get是从服务器上获取数据,把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到

$_REQUEST

request可以接受get和post的数据,但是比较慢,所以我们尽量不要使用request

$_SERVER

这种超全局变量保存关于报头,路径和脚本位置的信息等。

我整理了其中的参数,也方便以后查阅

可以var_dump下来看一下

$_SERVER['PHP_SELF'] #当前正在执行脚本的文件名,与 document root相关。

$_SERVER['argv'] #传递给该脚本的参数。

$_SERVER['argc'] #包含传递给程序的命令行参数的个数(如果运行在命令行模式)。

$_SERVER['GATEWAY_INTERFACE'] #服务器使用的 CGI 规范的版本。例如,“CGI/1.1”。

$_SERVER['SERVER_NAME'] #当前运行脚本所在服务器主机的名称。

$_SERVER['SERVER_SOFTWARE'] #服务器标识的字串,在响应请求时的头部中给出。

$_SERVER['SERVER_PROTOCOL'] #请求页面时通信协议的名称和版本。例如,“HTTP/1.0”。

$_SERVER['REQUEST_METHOD'] #访问页面时的请求方法。例如:“GET”、“HEAD”,“POST”,“PUT”。

$_SERVER['QUERY_STRING'] #查询(query)的字符串。

$_SERVER['DOCUMENT_ROOT'] #当前运行脚本所在的文档根目录。在服务器配置文件中定义。

$_SERVER['HTTP_ACCEPT'] #当前请求的 Accept: 头部的内容。

$_SERVER['HTTP_ACCEPT_CHARSET'] #当前请求的 Accept-Charset: 头部的内容。例如:“iso-8859-1,*,utf-8”。

$_SERVER['HTTP_ACCEPT_ENCODING'] #当前请求的 Accept-Encoding: 头部的内容。例如:“gzip”。

$_SERVER['HTTP_ACCEPT_LANGUAGE']#当前请求的 Accept-Language: 头部的内容。例如:“en”。

$_SERVER['HTTP_CONNECTION'] #当前请求的 Connection: 头部的内容。例如:“Keep-Alive”。

$_SERVER['HTTP_HOST'] #当前请求的 Host: 头部的内容。

$_SERVER['HTTP_REFERER'] #链接到当前页面的前一页面的 URL 地址。

$_SERVER['HTTP_USER_AGENT'] #当前请求的 User_Agent: 头部的内容。

$_SERVER['HTTPS'] — 如果通过https访问,则被设为一个非空的值(on),否则返回off

$_SERVER['REMOTE_ADDR'] #正在浏览当前页面用户的 IP 地址。

$_SERVER['REMOTE_HOST'] #正在浏览当前页面用户的主机名。

$_SERVER['REMOTE_PORT'] #用户连接到服务器时所使用的端口。

$_SERVER['SCRIPT_FILENAME'] #当前执行脚本的绝对路径名。

$_SERVER['SERVER_ADMIN'] #管理员信息

$_SERVER['SERVER_PORT'] #服务器所使用的端口

$_SERVER['SERVER_SIGNATURE'] #包含服务器版本和虚拟主机名的字符串。

$_SERVER['PATH_TRANSLATED'] #当前脚本所在文件系统(不是文档根目录)的基本路径。

$_SERVER['SCRIPT_NAME'] #包含当前脚本的路径。这在页面需要指向自己时非常有用。

$_SERVER['REQUEST_URI'] #访问此页面所需的 URI。例如,“/index.html”。

$_SERVER['PHP_AUTH_USER'] #当 PHP 运行在 Apache 模块方式下,并且正在使用 HTTP 认证功能,这个变量便是用户输入的用户名。

$_SERVER['PHP_AUTH_PW'] #当 PHP 运行在 Apache 模块方式下,并且正在使用 HTTP 认证功能,这个变量便是用户输入的密码。

$_SERVER['AUTH_TYPE'] #当 PHP 运行在 Apache 模块方式下,并且正在使用 HTTP 认证功能,这个变量便是认证的类型。

$_FILE

$_FILES数组内容如下: 
$_FILES['myFile']['name'] 客户端文件的原名称。 
$_FILES['myFile']['type'] 文件的 MIME 类型,需要浏览器提供该信息的支持,例如"image/gif"。 
$_FILES['myFile']['size'] 已上传文件的大小,单位为字节。 
$_FILES['myFile']['tmp_name'] 文件被上传后在服务端储存的临时文件名,一般是系统默认。可以在php.ini的upload_tmp_dir 指定,但 用 putenv() 函数设置是不起作用的。 
$_FILES['myFile']['error'] 和该文件上传相关的错误代码。['error'] 是在 PHP 4.2.0 版本中增加的。下面是它的说明:(它们在PHP3.0以后成了常量) 
UPLOAD_ERR_OK 
值:0; 没有错误发生,文件上传成功。 
UPLOAD_ERR_INI_SIZE 
值:1; 上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值。 
UPLOAD_ERR_FORM_SIZE 
值:2; 上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值。 
UPLOAD_ERR_PARTIAL 
值:3; 文件只有部分被上传。 
UPLOAD_ERR_NO_FILE 
值:4; 没有文件被上传。 
值:5; 上传文件大小为0. 

通过HTTPCookies方式传递给当前脚本的变量的数组

$HTTP_COOKIE_VARS和$_COOKIE是不同的变量,PHP处理他们的方式不同

$HTTP_COOKIE_VARS包含相同的信息(4.1.0已废弃)但它不是一个超全局变量

$_SESSION

是一个数值,其中的value值可以我们设置,类似cookie

$_ENV

$_ENV包含服务器端环境变量的数组,可在PHP程序的任何地方直接访问

$_ENV只是被动的接收服务器端的环境碧昂量转换为数组元素。

SQL注入

基础

数字型的注入,当输入的参数是整型时,则可认为是数字型注入。如年龄,id,页码,都可以是,而且不需要单引号进行闭合。

首先我们自己新建一个数据库来进行一个测试

image-20220826202223548

这是一个名为test的数据库,以及一个user表

随后自己写一个查询页面

<?php
$id = $_GET['id'];
$conn = mysql_connect('127.0.0.1','root','root');#建立一个连接,返回一个连接标识
mysql_select_db('test',$conn);#选择数据库
$sql = "SELECT * FROM USER WHERE id =$id";
$result = mysql_query($sql) or die(mysql_error());#执行一次sql查询
while($row = mysql_fetch_array($result)#返回一个关联数组){
    echo 'ID'.$row['id'].'<br>';
    echo 'USERNAME'.$row['username'].'<br>';
    echo 'PASSWORD'.$row['password'].'<br>';
    echo 'PHONE'.$row['phone'].'<br>';
}
mysql_close($conn);
echo '<br>';
echo $sql;

image-20220826205026879

可以看到就像是一个基础的sql注入靶场

or1=1就可以看到注入成功

image-20220826205517820

注入利用的方式

查询数据,读写文件,执行命令

写入webshell
union select 1,2,3,十六进制编码的一句话 into outfile 'C:/Localhost/WWW/3.php'

登录处注入的例子

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
  <form action="login.php" method="post">
    账号:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    <input type="submit" value="点击登录" name="login">
  </form>>
</body>
</html>

login.php

```php
<?php
if (!isset($_POST[‘login’])){
echo ‘fail’;#首先看是否有数据从那边传过来
}else{
$username = $_POST[‘username’];
$password = $_POST[‘password’];
$conn = mysql_connect(“127.0.0.1”,”root”,”root”);#连接数据库
mysql_select_db(“test”,$conn);#选择数据库
$sql = “SELECT *FROM USER WHERE USERNAME=’”.$username.”‘“;#拼接sql语句
$result = mysql_query($sql) or die(‘执行失败’.mysql_errno());#执行sql语句
while($row = mysql_fetch_array($result)){
$db_username = $row[‘username’];
$db_password = $row[‘password’];
}#拿到数据库的结果整理
if($db_password == $password && $db_username = $username)


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。后续可能会有评论区,不过也可以在github联系我。