weiphp微信开发框架存在这样一个问题,当用户分享某个页面到好友、朋友圈时会附加上自身的openid(openid是微信公众号来识别用户的唯一ID),甚至当其他用户点击链接访问时,框架以为是前者的用户身份,而且没有校验。这点BUG无论对于业务还是安全性来说都影响非常大。
在此简单做一说明及修复方案,框架版本:2.0,3.0某些版本也存在。
首先是业务逻辑方面,例如官方附带的插件,投票、填表等。
首先通过关键词触发Vote插件 .../Vote/WeixinAddonModel.class.php
Vote插件返回给客户端一个图文链接,其中的地址包含了当前用户的OpenId。(这里如果当用户分享地址给其他人,则其他人也会以前者的身份登录
###文件地址:/Addons/Vote/Model/WeixinAddonModel.class.php###从代码可以看出,URL是这么来的。其中官方的备注是必须传输openid,否则无法辨认来源用户身份。###在这里说明下,此处个人建议写法是依然传输token,也就是公众号id。###该框架是针对多公众号的,否则无法指定所服务的公众号。###主要将openid屏蔽掉,不要进行传输。// 以下官方写法
//组装用户在微信里点击图文的时跳转URL
//其中token和openid这两个参数一定要传,否则程序不知道是哪个微信用户进入了系统
$param ['id'] = $info ['id'];
$param ['token'] = get_token ();
$param ['openid'] = get_openid ();
$url = addons_url ( 'Vote://Vote/show', $param );//以下修改后
//组装用户在微信里点击图文的时跳转URL
//其中token和openid这两个参数一定要传,否则程序不知道是哪个微信用户进入了系统
$param ['id'] = $info ['id'];
$param ['token'] = get_token (); //$param ['openid'] = get_openid ();
$url = addons_url ( 'Vote://Vote/show', $param );
以上做法也只是保住了一部分,毕竟框架功能基本已经完成了,其他地方也均要修改,也是一件比较麻烦的事情。
建议直接修改addons_url 函数,屏蔽掉构造参数中的openid。
/Application/Common/Common/function.php
###文件地址:/Application/Common/Common/function.php/**
* 插件显示内容里生成访问插件的url
* @param string $url url
* @param array $param 参数
* @author ....
*/function addons_url($url, $param = array()) {
... /* 解析URL带的参数 */
if (isset ( $url ['query'] )) {
parse_str ( $url ['query'], $query );
$param = array_merge ( $query, $param );
}
/* 基础参数 */
$params = array ( '_addons' => ucfirst ( $addons ), '_controller' => ucfirst ( $controller ), '_action' => $action
);
$params = array_merge ( $params, $param ); // 添加额外参数
//增加过滤openid
if(isset($param['openid'])) unset($param['openid']); return U ( 'Home/Addons/execute', $params );
}
自此之后,openid貌似已经过滤完了,但经测试发现,用户首次访问公众号网页的时候,会进行oauth2授权获取openid,此时链接地址上,会再次出现openid。
###文件地址:/Application/Common/Common/function.php###原因在此function OAuthWeixin($callback) {
$isWeixinBrowser = isWeixinBrowser ();
$info = get_token_appinfo (); if (! $isWeixinBrowser || $info ['type'] != 2 || empty ( $info ['appid'] )) {
redirect ( $callback . '&openid=-1' );
}
$param ['appid'] = $info ['appid'];
if (! isset ( $_GET ['getOpenId'] )) {
$param ['redirect_uri'] = $callback . '&getOpenId=1';
$param ['response_type'] = 'code';
$param ['scope'] = 'snsapi_base';
$param ['state'] = 123;
$url = 'https://open.weixin.qq.com/connect/oauth2/authorize?' . http_build_query ( $param ) . '#wechat_redirect';
redirect ( $url );
} elseif ($_GET ['state']) {
$param ['secret'] = $info ['secret'];
$param ['code'] = I ( 'code' );
$param ['grant_type'] = 'authorization_code';
$url = 'https://api.weixin.qq.com/sns/oauth2/access_token?' . http_build_query ( $param );
$content = file_get_contents ( $url );
$content = json_decode ( $content, true ); //获取到用户openid之后,openid又被附加到URL上,进行跳转了。
redirect ( $callback . '&openid=' . $content ['openid'] );
}
}
做一修改,将openid不再附加到URL上,而是直接写到session里面。
###文件地址:/Application/Common/Common/function.phpfunction OAuthWeixin($callback) {
$isWeixinBrowser = isWeixinBrowser ();
$info = get_token_appinfo (); if (! $isWeixinBrowser || $info ['type'] != 2 || empty ( $info ['appid'] )) {
redirect ( $callback . '&openid=-1' );
}
$param ['appid'] = $info ['appid'];
if (! isset ( $_GET ['getOpenId'] )) {
$param ['redirect_uri'] = $callback . '&getOpenId=1';
$param ['response_type'] = 'code';
$param ['scope'] = 'snsapi_base';
$param ['state'] = 123;
$url = 'https://open.weixin.qq.com/connect/oauth2/authorize?' . http_build_query ( $param ) . '#wechat_redirect';
redirect ( $url );
} elseif ($_GET ['state']) {
$param ['secret'] = $info ['secret'];
$param ['code'] = I ( 'code' );
$param ['grant_type'] = 'authorization_code';
$url = 'https://api.weixin.qq.com/sns/oauth2/access_token?' . http_build_query ( $param );
$content = file_get_contents ( $url );
$content = json_decode ( $content, true ); //此处增加将openid写入session
if($content['openid']!==NULL){
get_openid($content ['openid']);
} //过滤url中敏感参数
$callback = filterUrlParam($callback);
redirect ( $callback);
}
}
//过滤URL参数function filterUrlParam($url,$filter = array('getOpenId','code','state','openid')){
$parse_param = parse_url($url);
$query_array = explode('&',$parse_param['query']);
$query = ''; foreach($query_array as $val){
$tmp = explode('=', $val); if(count($tmp)>=1){
!in_array($tmp[0], $filter) && $query .= $val.'&';
}
}
$parse_param['query'] = $query = rtrim($query,'&');
$url = unparse_url($parse_param); return $url;
}//以上所需函数function unparse_url($parsed_url) {
$scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
$host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
$port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
$user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
$pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
$pass = ($user || $pass) ? "$pass@" : '';
$path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
$query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
$fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
return "$scheme$user$pass$host$port$path$query$fragment";
}
之后,还有进行处理的一处就是将URL参数中openid识别为真实用户的罪魁祸首
###文件地址:/Application/Common/Common/function.php// 获取当前用户的OpenIdfunction get_openid($openid = NULL) {
$token = get_token (); if ($openid !== NULL) {
session ( 'openid_' . $token, $openid );
}
//删除掉参数请求中的openid// elseif (! empty ( $_REQUEST ['openid'] )) {// session ( 'openid_' . $token, $_REQUEST ['openid'] );// }
$openid = session ( 'openid_' . $token );
$isWeixinBrowser = isWeixinBrowser (); if (empty ( $openid ) && $isWeixinBrowser) {
$callback = GetCurUrl ();
OAuthWeixin ( $callback );
}
if (empty ( $openid )) { return - 1;
}
return $openid;
}
好一番折腾,终于将openid过滤完了。
以上均为个人观点,如果有存在错误或者有疑问请在文末留言,谢谢!。