- 辅助工具:nodepad++
seay源代码审计系统 - 编辑器:ultraedit
- 代码审计工具:RIPS(能根据漏洞自动生成payload)
前期准备:本地搭建网站,一边审计一边调试
审计基本流程
1.查看网站结构,根据网站功能查看大概存在的漏洞 2.看index.php,再根据index.php涉及的代码进行审计,看出每个文件有什么大致作用,找出哪个文件是共享的安全函数文件 3.寻找配置文件,保存一些数据库相关信息。首先查看数据库编码。如果是gbk则可能存在宽字节注入。如果变量的值用双引号则可能存在双引号解析代码执行的问题 4.过滤功能,查看各个过滤入口,过滤方式是替换还是正则,有无GPC,有没有addslasher()处理
但是上述方法很麻烦,把人都整麻,一般都采用敏感函数参数回溯的方式来逆向追踪参数的传递过程。因为大多数漏洞都是是由于函数的使用不当造成的。
seay源代码审计软件就主要利用了正则匹配一些高危函数,关键函数以及敏感关键字。然后就可以分析判断敏感函数的上下文,追踪参数源头。但是使用此法挖不到逻辑漏洞
关键函数
安全相关
- htmlspecialchars(html实体化编码)看到就可以基本确定没有XSS了
- mysql_real_escape_string()过滤,转义单引号双引号括号等,主要针对sql注入
- file_get_content()可能存在任意文件读取
file_put_content()任意写文件 fwrite - 配置选项:php.ini: display_error:On开启显示错误信息,增大注入风险
xss | sql injection | code exec | command exec | File | Other |
---|
echo | mysql_query | assert | passthru | fread | extract | print* | mysqli_query | eval | system | copy | parse_str | … | mysqli:query | preg_replace | popen | fuputs | unserialize | … | PDO:query | include* | shell_exec | unlink | … | | | require* | | | |
unlink是删除文件(任意文件删除漏洞)
除OWASP TOP10外的常见漏洞
覆盖安装漏洞
一般php程序都有一个初始安装的功能。这个安装的功能可能会有以下漏洞:
- 无验证功能,任意重装覆盖
- $_GET[‘step’]跳过限制步骤
- 变量覆盖导致重装
- 判断lock后跳转无exit
- 解析install.php.bak漏洞
无验证功能导致任意重装覆盖 因为安装网站时,会相应生成install.lock,后期在安装页面时会判断install.lock是否存在,不存在就可覆盖重装,也就是说此漏洞要搭配任意文件删除漏洞使用 安装后删除install.lock再次访问首页会跳转至install.php 判断lock后跳转无exit 审计代码: 在index.php内会include(./sys/config.php),而在config.php内有以下一段代码
if(!file_exists($_SERVER["DOCUMENT_ROOT"].'/sys/install.lock'))
{
header("Location:/install/install.php");
exit;
}
install.php源码:
if(file_exists($_SERVER["DOCUMENT_ROOT"].'/sys/install.lock'))
{
header("Location:../index.php");
}
function check_writeable($file){
....}
...
可以看到install.php内如果验到有install.lock会跳转至index.php,虽然跳转了但是后续安装代码依然会执行,原因就是没有exit
安装有以下一段代码:
$dbhost=$_POST["dbhost"];
$dbuser=$_POST["dbuser"];
$dbpass=$_POST["dbpass"];
$dbname=$_POST["dbname"];
$con = mysql_connect($dbhost,$dbuser,$dbpass);
if(!$con){
die('数据库链接出错,请检查账号密码及地址是否正确'.mysql_error());
}
$result=mysql_query('show database;') or die (mysql_error());
while($row=mysql_fetch_assoc($result)){
$data[]=$row['database'];
}
unset($result,$row);
if(in_array(strtolower($dbname),$data)){
mysql_close();
echo "<script>if(!alert('数据库已存在')){window.history.back(-1);)</script>";
exit();
}
mysql_query("create database $dbname",$con) or die (mysql_error());
$str_tmp="host=\"dbhost\";\r\n\$username=\"dbuser\";\r\n\$password=\"dbpass\";\r\n\$database=\"dbname\"\r\n"
$fp=fopen("../sys/config.php","w");
fwrite($fp,$str_tmp);
fclose($fp);
在上面代码中,创建数据库对dbname没有任何过滤,对写入文件内容没有任何过滤,所以四个参数都可能存在代码注入,但是在创建数据库之前需要先登录数据库,所以dbhost,dbuser,dbpass三个参数一定要是正确的参数,这三个参数就不能注入。所以但对dbname就可能存在sql注入和写马,但在写马的时候要注意,写马成功的条件一定是sql语句先要执行成功,所以这个地方应该只存在sql
OWASP TOP 100
命令注入
相关函数分类:
- 第一类(最常见):system(),exec(),shell_exec(),passthru()–可以直接传入命令执行并返回结果
- 第二类:popen(),proc_open()–使用这类函数传入命令时,命令会执行,但不会返回执行结果
- 第三类:pcntl_exec()–需要开启pcntl扩展
高危漏洞推荐用敏感函数回溯法,管道符&,|,&&,||,;分别在windows和linux下的功能不再赘述
ping.php的代码:
<?php
if( isset( $_POST[ 'submit' ]))
$target = $_POST['target'];
if (stristr(php_uname('s’ ),‘Windows NT’))
{
//判断是不是windows系统
$cmd = 'ping ' . $target; //是windows系统,进行ping操作
}
else {
$cmd = 'ping -c 3 '. $target,
$res = shell_exec($cmd);} ?>
可惜看到对post过来的数据没有任何过滤
修复方式:
1.使用正则表达式来对用户输入的POST值进行过滤验证
if(preg_match('/^(?=^.{3,255]$)[a-zA-Z0-9][-a-zA-Z0-9][0,62]L.[a-zA-Z0-9][-a-zA-Z0-9][0,62))+$/^(25[0-5][2[0-4]d|[01]?\d\d?)($|(?!\.$L))(4}$/',$target)){
2.判断输入过来的值是否为ip
$octet = explode( ".", $target );
if ( is_numeric( $octet[0] )) && ( is_numeric( $octet[1]))&& ( is_numeric( $octet[2] )) &8 ( is_numeric( $octet[3] ))8& ( sizeof( $octet )== 4))
{ $target = $octet[0] .'.'. $octet[1].'.'. $octet[2] . '.'. $octet[3];}
XSS
XSS的关键就是寻找参数未过滤的输出函数。常见的输出函数有:ehco,printf,print,print_r,sprintf,die,var_dump,var_export等 mysql_real_escape_string()不对xss进行过滤,htmlspecialchars才对尖括号等内容进行html实体化编码 如果在输入的时候不过滤而在输出的时候实体化编码也是不存在xss的
- 一般http头的数据是可控的,而如remote_addr是系统生成的,是不可控内容。$_SERVER是全局变量,所以代码公用
function get_client_ip(){
if($_SERVER["HTTP_CLIENT_IP"]&&strcasecmp($_SERVER["HTTP_CLIENT_IP"],"unknown")){
$ip=$_SERVER["HTTP_CLIENT_IP"];
}else if($_SERVER["X-FORWARDED_FOR"]&&strcasecmp($_SERVER["X-FORWARDED_FOR"],"unknown")){
$ip=$_SERVER["X-FORWARDED_FOR"];
}else if($_SERVER["REMOTE_ADDR"]&&strcasecmp($_SERVER["REMOTE_ADDR"],"unknown")){
$ip=$_SERVER["REMOTE_ADDR"];
}else if(isset($_SERVER["REMOTE_ADDR"])&&$_SERVER["REMOTE_ADDR"]&&strcasecmp($_SERVER["REMOTE_ADDR"],"unknown")){
$ip=$_SERVER["REMOTE_ADDR"];
}else $ip="unknown";
return($ip);
}
上面两个可控内容如果输出(比如管理界面要显示已登录用户的ip)没有过滤很明显也有xss
修复建议:可控输出基本应该用htmlspecialchars过滤
sql注入
sql的重点就是没有过滤可控参数进入数据库执行 审计sql建议正向审计,在拿到源码时先找到它的过滤注入函数,判断:
- 如果没有过滤可以直接注入
- 如果调用了addslashes()函数,还可能存在数字型注入
- 如果设置了set character_set_client=gbk开启了gbk编码,就会存在宽字节注入
str_ireplace(“select”,“sqlwaf”,$str);忽略大小写把select替换为sqlwaf 现有如下场景: comment表有四列,过滤了_,admin表有三列(id,admin_user,admin_pass),现在要查询admin表的admin_user
select * from comment where id =1 union select *,4 from admin;
联合查询,用*直接代替被过滤的字段,这种应用必须要求后面查询表的字段数小于原查询表的字段数
修复建议:使用PDO::PARAH_INT
文件包含
漏洞原理:服务器开启allow_url_include,利用url进行动态包含文件,且对要包含的来源文件没有审查 相关函数:include(),include_once(),require(),require_once() 取后缀名白名单:
$extend=explode(".",$file_name);
$va=count($extend)-1;
if($extend[$va]=='jpg'||$extend[$va]=='jpeg'||$extend[$va]=='png'||$extend[$va]=='gif'{
return 1;
else return 0;
php伪协议学习参考 修复:不要动态包含文件
二次注入
利用mysql数据库出库值反转义功能,多次与数据库交互造成的注入问题
- 在数据第一次进入数据库时,对其转移后存储,数据未经过滤、转义被取出后再次放到sql语句中
- 开发者对函数不理解导致,比如is_numeric
比如向数据库中插入’hack\’,入库变成hack’,拼接到sql语句就单引号闭合前面字符造成注入 二次注入关键在于要先insert\update,再select调用,关键在于select是否直接来自数据库而不是动态传参进来,且对该数据没过滤
任意文件读取
file_get_content()动态读取 现在有如下代码:
$avater = $uploaddir.'/u_'.time().'_'.$_FILES['upfile']['name'];
if(move_upload_file($_FILE['upfile']['tmp_name'],$avatar)){
$query = "update users set user_avatar = '$avatar' where user_id = '($_SESSION['user_id'])'";
mysql_close($conn);
$_SESSION['avatar'] = $avatar;
}
file_get_content($_SESSION['avatar']);
上述代码在进行存储时将文件名与时间戳和字符串拼接,要通过修改文件名进行任意文件读取几乎是不可能的,但是在进入字符串的时候没有进行任何过滤,不仅存在sql注入。还可以使文件名name=’/u_20220123_1,user_avater=“config.php” '; 后面的变量覆盖前面的造成任意文件读取 $_FILE[‘upfile’][‘name’]有个问题,比如a/b/c/1.jpg,该变量获取的文件名就只是1.jpg,不会获取整个,但是mysql可以读1十六进制,要读其他文件就十六进制编码一下
功能代码
- move_upload_file()把上传的临时文件拷贝到网站根目录,上传的文件会先保存为临时文件,一般在/tmp目录下
- stripslashes()去掉\
|