代码审计之某企业网站管理系统
1、部署源码
1.1 系统简介
某企业网站管理系统 Company Content Management System
某企业网站管理系统是一款基于php+mysql+samrty的免费开源建站系统。
1.2 部署
1)使用phpstudy进行网站部署。
2)网站界面如下:
3、漏洞审计之sql注入
1)index.html部分代码(已添加说明注释)
<?php
if(!file_exists('includes/install.lock')){ //判断是否存在install.lock文件
header("location:install/index.php");exit;
}
define('xsd', true);
define('root',dirname(__file__)); //定义常量root,通过dirname(_file_)获取当前文件所在目录的绝对路径,_file_表示当前文件
session_start();
require_once(root.'/includes/db_class.php');
require_once(root.'/includes/config.php');
require_once(root.'/includes/function.php');
require_once(root.'/includes/page_class.php');
require_once(root.'/includes/comm.php'); //检查文件是否被包含过,如果被包含过,则不会运行,报错显示详细信息
$bid = (isset($_GET["bid"]) && ereg("^[0-9]+$", $_GET["bid"])) ? $_GET["bid"]: 0;
$cid = (isset($_GET["cid"]) && ereg("^[0-9]+$", $_GET["cid"])) ? $_GET["cid"]: 0;
$did = (isset($_GET["did"]) && ereg("^[0-9]+$", $_GET["did"])) ? $_GET["did"]: 0;
$setpage = (isset($_GET["setpage"]) && ereg("^[0-9]+$", $_GET["setpage"])) ? $_GET["setpage"]: 0;
$setid = (isset($_GET["setid"]) && ereg("^[0-9]+$", $_GET["setid"])) ? $_GET["setid"]: 0;//正则匹配,只匹配0-9内的数字
if (isset($_GET["action"]))
{
if (in_array($_GET["action"], array("0","1","2","3","5","6",'7'))){
switch ($_GET["action"])
{
case 0:
//单页图文
include_once(root.'/includes/single_page.php');
break;
case 1:
//新闻列表
include_once(root.'/includes/news_page.php');
break;
case 2:
//产品列表
include_once(root.'/includes/products_page.php');
break;
case 3:
//客户反馈
include_once(root.'/includes/feedback_page.php');
break;
case 5:
//新闻详细
include_once(root.'/includes/news_page_show.php');
break;
case 6:
//产品详细
include_once(root.'/includes/products_page_show.php');
break;
case 7:
//搜索
include_once(root.'/includes/search.php');
break;
}
}else{
die("参数出错");
}
}
else{
if (@$_SESSION["l"]==1){
$showurl = "index_en.html";
}else{
$showurl = "index.html";
}
}
if(!file_exists(root."/templete/".templete_url."/".$showurl))die(templete_url."/".$showurl."此模版文件不存在"); //检查模版文件是否存在
$smarty->display($showurl); //显示模板
?>
分析index.php中参数有bid、cid、did,参数传入都已做过正则处理,不存在sql注入。可以看到引入的includes文件中包含文件comm.php。
2)查看comm.php中代码,如果打开了magic_quotes_gpc,那么传过来的值会自动addslashes()。
<?php
/*
* 通用函数库
* @Copyright (c) 2009.02 新生代软件
* @author feng QQ:13019489 <@link [url]http://www.37241.net[/url]>
* @name comm.php
*/
//php解码javascript的escape
function unescape($str)
{
$str = rawurldecode($str);
preg_match_all("/%u.{4}|&#x.{4};|&#d+;|.+/U",$str,$r);
$sar = $r[0];
foreach($sar as $k=>$v)
{
if(substr($v,0,2) == "%u")
$sar[$k] = iconv("UCS-2","GBK",pack("H4",substr($v,-4)));
elseif(substr($v,0,3) == "&#x")
$sar[$k] = iconv("UCS-2","GBK",pack("H4",substr($v,3,-1)));
elseif(substr($v,0,2) == "&#")
$sar[$k] = iconv("UCS-2","GBK",pack("n",substr($v,2,-1)));
}
return join("",$sar);
}
function safe($str)
{
if (get_magic_quotes_gpc()) {
return $str;
} else {
return addslashes($str);
}
}
if (!get_magic_quotes_gpc()) //如果打开了magic_quotes_gpc,那么传过来的值会自动addslashes()。
{
if (!empty($_GET))
{
$_GET = addslashes_deep($_GET);
}
if (!empty($_POST))
{
$_POST = addslashes_deep($_POST);
}
$_COOKIE = addslashes_deep($_COOKIE);
$_REQUEST = addslashes_deep($_REQUEST);
}
function addslashes_deep($value)
{
if (empty($value))
{
return $value;
}
else
{
return is_array($value) ? array_map('addslashes_deep', $value) : addslashes($value);
}
}
?>
说明:打开magic_quotes_gpc来防止SQL注入
SQL注入是非常危险的问题,小则网站后台被入侵,重则整个服务器沦陷,
所以一定要小心。php.ini中有一个设置:
magic_quotes_gpc = Off
这个默认是关闭的,如果它打开后将自动把用户提交对sql的查询进行转换,
比如把 ' 转为 '等,这对防止sql注射有重大作用。所以我们推荐设置为:
magic_quotes_gpc = On
虽然国内很多PHP程序员仍在依靠addslashes防止SQL注入,还是建议大家加强中文防止SQL注入的检查。addslashes的问题在于黑客可以用0xbf27来代替单引号,而addslashes只是将0xbf27修改为0xbf5c27,成为一个有效的多字节字符,其中的0xbf5c仍会被看作是单引号,所以addslashes无法成功拦截。
当然addslashes也不是毫无用处,它是用于单字节字符串的处理,多字节字符还是用mysql_real_escape_string吧。
另外对于php手册中get_magic_quotes_gpc的举例:
if (!get_magic_quotes_gpc()) {
$lastname = addslashes($_POST[‘lastname’]);
} else {
$lastname = $_POST[‘lastname’];
}
最好对magic_quotes_gpc已经开放的情况下,还是对$_POST[’lastname’]进行检查一下。
再说下mysql_real_escape_string和mysql_escape_string这2个函数的区别:
mysql_real_escape_string 必须在(PHP 4 >= 4.3.0, PHP 5)的情况下才能使用。否则只能用 mysql_escape_string ,两者的区别是:mysql_real_escape_string 考虑到连接的
当前字符集,而mysql_escape_string 不考虑。
总结一下:
- addslashes() 是强行加\;
- mysql_real_escape_string() 会判断字符集,但是对PHP版本有要求;
- mysql_escape_string不考虑连接的当前字符集。
示例:PHP addslashes() 函数
3)审计save.php页面(客户反馈功能)
审计save.php页面,发现在留言提交时,分析代码可以看到,变量abc的值是由text’.$rs[“id”].'的值拼接特殊符号|@;而来,由于使用post方法提交,且未做sql过滤,而且开头文件并未包含comm.php.故此处可以拼接sql语句导致sql注入。
<?php
require_once(dirname(__file__).'/db_class.php');
require_once(dirname(__file__).'/config.php');
function ar($str){
if (is_array($str)){
foreach($str as $key1=>$value1){
$a.=$str[$key1]." ";
}
}else{
$a=$str;
}
return $a;
}
$bid = intval($_GET["bid"]);
$title = $_POST["title"];
$riqi = date("Y-n-j G:i:s");
$title1 ='';
$text1 ='';
$text2 ='';
$abc = '';
$que=$db->query("select * from xsd_feedback_set where bid = '$bid' ORDER BY rootid");
while($rs=$db->fetch_array($que)){
$text1 ='$abc.=ar($_POST["text'.$rs["id"].'"])."|@";';//此处eval()中参数使用单引号,不存在代码执行漏洞
eval ($text1);
}
foreach($title as $key=>$value)
{
$title1 .= htmlspecialchars($title[$key])."|@";
}
$qu=$db->query("insert into xsd_feedback set title = '$title1',content = '$abc',bid = '$bid',riqi='$riqi'");//
//此处存在注入点$abc,由于使用post方法提交,且未做sql过滤,而且开头文件并未包含comm.php.故此处可以拼接sql语句导致sql注入。
if ($qu){
echo "<script language='javascript'>alert('提交成功')</script>";
echo"<script language='javascript'>history.back(-1)</script>";
}else{
echo "<script language='javascript'>alert('提交失败')</script>";
echo"<script language='javascript'>history.back(-1)</script>";
}
?>
$text1 ='text1=′$abc.=ar($_POST["text'.POST["text′.$rs["id"].'"])."|@";';
4)渗透测试复现sql注入:
4.1.访问页面
4.2.使用burp抓包,使用工具sqlmap。发现sql注入
4.3.数据库中数据表的存储结构(数据存储都放在content字段中):