php代码审计学习笔记1
课程是某牛的,笔记只供个人学习
感觉就是从代码层面复习了一遍漏洞原理
环境配置及审计工具介绍
环境就用phpstudy
看代码的话我用的是notepad++
审计工具可以用seay源代码审计系统(基于正则
Fortify SCA也是一款不错的商业化代码审计工具(靠内置的规则空间匹配
rips审计工具也不错,可以看到用户输的一些点
以及一款数据库监控工具 vMysqlMonitoring.exe
审计靶场:VAuditDemo ZVulDrill
代码审计的思路及流程
MVC架构
MVC是一种设计创建Web程序的一种模式,其同时提供了HTML,CSS和JAVASCRIPT的完全控制。
MVC关系
常见的php框架
审计思路
MVC框架常见的一个处理流程
获取请求 全剧过滤 模块文件 c函数内容 m函数内容 v显示
常见网站目录架构
主目录
模块目录
插件目录
上传目录
模板目录
数据目录
配置目录
配置文件
公共函数的文件
安全过滤的文件
数据库结构
入口文件
通读全文
函数集文件,配置文件,安全过滤文件,index文件
优点:了解程序的架构及业务逻辑,挖掘更高质的漏洞
缺点:耗费时间多,工程较大
敏感关键字回溯参数
优点:快速挖掘想要的漏洞,最常用的方法。
缺点:覆盖不到逻辑漏洞的挖掘,不能了解程序的基本架构
查找可控变量
可以用rips审计工具的查找用户输入功能
功能点定向审计
程序安装,文件上传,文件管理,登录验证,备份恢复,找回密码
PHP核心配置详解
PHP.INI配置文件也就是所谓的核心配置
下面是一些常亮
基本配置-语法
大小写敏感
directive = value(指令 = 值)
foo=bar ≠ FOO=bar
运算符
|、&、~、!
空值的表达方式
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
关掉之后,我们就不能获取权限了
魔术引号过滤
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.
$_COOKIE
通过HTTPCookies方式传递给当前脚本的变量的数组
$HTTP_COOKIE_VARS和$_COOKIE是不同的变量,PHP处理他们的方式不同
$HTTP_COOKIE_VARS包含相同的信息(4.1.0已废弃)但它不是一个超全局变量
$_SESSION
是一个数值,其中的value值可以我们设置,类似cookie
$_ENV
$_ENV包含服务器端环境变量的数组,可在PHP程序的任何地方直接访问
$_ENV只是被动的接收服务器端的环境碧昂量转换为数组元素。
SQL注入
基础
数字型的注入,当输入的参数是整型时,则可认为是数字型注入。如年龄,id,页码,都可以是,而且不需要单引号进行闭合。
首先我们自己新建一个数据库来进行一个测试
这是一个名为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;
可以看到就像是一个基础的sql注入靶场
or1=1就可以看到注入成功
注入利用的方式
查询数据,读写文件,执行命令
写入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联系我。