前言
在前面我已经介绍了AKun Wallpaper的部署,分析了xss漏洞并且讲解了如何修复这些漏洞。如果还没有看过我前两篇文章的师傅可以先看一下前两篇文章。在这一篇文章中我将重点分析任意文件删除漏洞和sql注入漏洞,然后对漏洞进行复现,最后讲解如何修复这些漏洞。
AKun Wallpaper 代码审计实战分析1:https://blog.csdn.net/rpsate/article/details/122354690
AKun Wallpaper 代码审计实战分析2:https://blog.csdn.net/rpsate/article/details/122387998
漏洞分析1
审计过程
首先看到doImageAction.php
的第18行,此处存在一个删除文件函数 unlink()
。该函数的参数 $fileName
是通过GET方式获取,所以该处所删除的文件可控,所以该处存在任意文件删除漏洞。
漏洞复现
为了避免删除正常文件,我在网站根目录下创建了一个文件 a.php
来做测试。
然后访问 http://127.0.0.1/admin/doimageAction.php?action=delImage&fileName=../a.php
,其中action=delImage
代表要执行删除文件的操作,fileName=../a.php
表示要删除的文件就是上一级目录中的a.php
。但是访问后网页后弹出提示框提示删除失败,其实这时候已经成功删除文件了。再次查看文件,发现a.php
已经不存在了。
这里提示删除失败是因为没有传入id参数,导致数据库删除图片相应的记录失败。如下图所示,标红路径即本次程序运行路径。
修复方案
防止任意文件删除的方法有多种,但是所有方法基本有以下两个特点:
- 尽量避免从用户端获取文件名。
- 防止目录跳转。
所以此处给出两种解决办法:
- 禁止用户任意传入文件名,通过图片id从数据库中获取图片名称。
- 禁止目录跳转,只允许删除指定目录中的文件。
这里选择第二种方法做演示,在删除文件的代码前添加如下代码即可防止任意文件删除。其中 LIST_IMAGES
是图片存储的目录, basename()
函数是去除目录,只保留文件名。修改后如下图所示:
$fileName = LIST_IMAGES."/".basename($fileName);
注意:在
doImageAction.php
中37行是循环删除多个文件,在复现此处漏洞的时候传入的参数与前面有所不同,其参数格式为:action=delAllImage&info=id1:filename1,id2:filename2,id3:filename3
,但是其修复方式与前面讲述方法一样。
漏洞分析2
审计过程
看到 lib/downImage.php
文件,在该文件中执行流程是:
- 首先通过GET方式获取一个字符串。
- 然后将字符串中目录去除,只保留有效文件名部分。
- 最后下载该文件。
虽然该漏洞不能任意穿越目录,但是可以下载当前目录中任意文件。该文件存在 lib
目录中,该目录中存在一些敏感文件。如果这些文件能被用户下载,将会存在巨大的潜在风险。
修复方法可以根据具体业务需求设置白名单。此文件本来是移动端程序下载图片时使用,这里为了简化程序删除了移动端版本,但是
downImage.php
忘记删除了,所以此处删除该文件就可修复,不会影响正常业务。
漏洞分析3
审计过程
看到 lib/mysql.func.php
第24行,此处执行了一条sql语句,顺着这条语句找到数据源。
数据源是 core/core.php
的20行处获取,从数据源到sql语句执行的过程中未进行任何安全处理,所以该处大概率存在sql注入漏洞。
漏洞复现
上面分析的代码功能就是添加管理员,我们打开添加管理员页面,填写任意数据。
开启浏览器代理,通过burpsuite抓取数据包。
将数据包发送到 Repeater
模块,并构造恶意payload。发送数据包5s后才收到响应包,这说明函数sleep(5)
已经执行成功。
修复方案
sql注入的防范方法主要有以下几种:
- 通过
addslashes
、mysql_real_escape_string
等函数或开启magic_quotes_gpc
参数来过滤引号。这种方法在某些情况下可以通过宽字节注入或二次注入绕过限制,也不能有效防范int类型注入。 - 通过
intval
函数转换防止注入。该函数的作用是将变量转化为int型。这种方法无法对本身含有字符型数据的数据进行处理。 - 通过黑名单或白名单的方式进行过滤。绕过白名单或黑名单的方法多种多样,难以防范。
- 采用PDO预编译防止sql注入。这种方法防止sql注入的效果比较好,建议采用这种方法。
此处如果采用PDO预编译的方法修复漏洞,该程序所有sql语句都要重写,工作量巨大,所以建议开发初期就采用PDO预编译方式操作数据库。此处采用 mysql_real_escape_string
函数加上 mysql_set_charset
函数对特殊字符转义来防止sql注入。
Q:
mysql_set_charset
的作用是什么?A:
mysql_set_charset
设置了数据库的编码,同时也记录了当前设置的编码,在后面使用mysql_real_escape_string
函数时会自动获取这个编码,并通过此编码来转义特殊字符。这样统一了转义时用的编码和数据库编码,便可以防止宽字节注入。Q:
addslashes
与mysql_real_escape_string
的区别是什么?A:
mysql_real_escape_string
会获取数据库字符集并根据数据库字符集转义特殊字符,而addslashes
会直接使用默认字符集转义特殊字符。另外addslashes
只会对'
、"
、\
、null
转义,而mysql_real_escape_string
同时还会对\r
、\n
和\x1a
转义。
将 lib/mysql.func.php
第13行的 mysql_query("set names utf8");
替换成下面代码:
mysql_set_charset('utf8');
然后使用mysql_real_escape_string
函数对用户传过来的数据进行转义,lib/mysql.func.php
中20行的 insert
函数改成:
function insert($table,$data) {
$key = join(",",array_keys($data));
$key = mysql_real_escape_string($key);
$value = join(",", array_values($data));
$value = mysql_real_escape_string($value);
$value = explode(",", $value);
$value = "'".join("','", $value)."'";
$sql = "INSERT INTO {$table}({$key}) VALUES({$value})";
mysql_query($sql);
return mysql_insert_id();
}
其他sql注入漏洞原理类似,可以用同样的方法修复。