当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2015-093347

漏洞标题:我是如何层层突破中国联通某省营业系统后台的(成功的案例)

相关厂商:中国联通

漏洞作者: 黑暗游侠

提交时间:2015-01-22 17:02

修复时间:2015-03-08 17:04

公开时间:2015-03-08 17:04

漏洞类型:设计缺陷/逻辑错误

危害等级:高

自评Rank:20

漏洞状态:已交由第三方合作机构(cncert国家互联网应急中心)处理

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2015-01-22: 细节已通知厂商并且等待厂商处理中
2015-01-27: 厂商已经确认,细节仅向厂商公开
2015-02-06: 细节向核心白帽子及相关领域专家公开
2015-02-16: 细节向普通白帽子公开
2015-02-26: 细节向实习白帽子公开
2015-03-08: 细节向公众公开

简要描述:

我是如何层层突破中国联通某省营业系统后台的(成功的案例)

详细说明:

中国联通广东省的微店业务系统,涉及金融订单,1元购买6 plus, 店家数据,任意篡改等等等等
具体我搜了下wooyun,发现有一部分案例,可以先看一下:

http://wooyun.org/bugs/wooyun-2010-084466
http://wooyun.org/bugs/wooyun-2010-084515
http://wooyun.org/bugs/wooyun-2010-084519
http://wooyun.org/bugs/wooyun-2010-084524


但是这些案例都有缺陷,比如有些只给出了购买逻辑可以1元购买,但是后台是人工审核的呀,人家又不是傻子,给你1元买。
也有些是SQL注入,但是只给出了数据证明,数据是有用的呀,但是你可知道前台有2层认证的呀,登陆认证然后手机验证码双重认证的呀,所以你SQL注入这是没有用的呀!!(万万没想到看多了,说话情不自禁就这样了。。。。)

漏洞证明:

1、突破登陆层
先来看下后台登陆界面:

http://app.weixin.gzuni.com/admin/user/login/?path=/admin/


15.png


遗憾的是,不存在任何注入
于是找突破口,看到“忘记密码”功能
于是点进去,一步一步来,输入admin

16.png


找回密码需要的不是邮箱,是手机,又是发验证码,下一步

17.png


确认发送后并没有数据包,根据console发现是.send()方法,所以当然截获不到数据包
那么线索到这里就断了,不能更换手机不能截获验证码
于是抓取页面所有js文件来看看有没有其他新的线索
通过排除发现这么一个JS函数:

$(function(){
var _LoginLocal = function(){
var self = this;

self.user_id = ko.observable("");
self.password = ko.observable("");
self.checkcode = ko.observable("");
self.password_confirm = ko.observable("");
self.loginStatusText = ko.observable("下一步");
self.disableLoginBtn = ko.observable(false);
self.errMsg = ko.observable("登录状态:");
self.hiteMsg = ko.observable("验证状态");
self.shouldShowgoBackBtn = ko.observable(false);
self.shouldShowMessage = ko.observable(false);
self.shouldShowHiteMessage = ko.observable(false);
self.shouldShowStepOne = ko.observable(true);
self.shouldShowStepTwo = ko.observable(false);
self.shouldShowStepThree = ko.observable(false);
self.goback= function(){
self.shouldShowStepOne(true);
self.loginStatusText("下一步");
self.shouldShowgoBackBtn(false);
self.shouldShowHiteMessage(false);
}
self.login = function(){
self.shouldShowMessage(false);
self.shouldShowHiteMessage(false);
self.disableLoginBtn(true);
if(self.shouldShowStepOne()){
self.loginStatusText('正在验证...');
$.post('/admin/user/login/user_verify',{
user_id: self.user_id(),
},function(data,status){
if(!data.result){
self.disableLoginBtn(false);
self.loginStatusText("下一步");
self.errMsg(data.msg);
self.shouldShowMessage(true);
}else{
self.shouldShowStepOne(false);
self.disableLoginBtn(false);
self.loginStatusText("确定发送");
self.hiteMsg(data.msg);
self.shouldShowHiteMessage(true);
self.shouldShowgoBackBtn(true);
}
},'json')
}else if(self.shouldShowgoBackBtn()){
self.loginStatusText('正在发送...');
$.post('/admin/user/login/chk_code',{
user_id: self.user_id()
},function(data,status){
if(!data.result){
}else{
self.shouldShowStepThree(true);
self.shouldShowgoBackBtn(false);
self.loginStatusText('验证');
self.disableLoginBtn(false);
}
},'json')
}else if(self.shouldShowStepThree()){
self.loginStatusText('正在验证...');
$.post('/admin/user/login/verify_chkcode',{
user_id: self.user_id(),
checkcode: self.checkcode()
}, function(data, status){
if(!data.result){
self.errMsg(data.msg);
self.shouldShowMessage(true);
self.loginStatusText('验证');
self.disableLoginBtn(false);
}else{
self.shouldShowStepThree(false);
self.shouldShowStepTwo(true);
self.loginStatusText('提交');
self.disableLoginBtn(false);
}
},'json')
}else if(self.shouldShowStepTwo()){
self.loginStatusText('正在提交...');
$.post('/admin/user/login/confirm_password',{
user_id: self.user_id(),
password: self.password(),
password_confirm: self.password_confirm()
}, function(data, status){
if(!data.result){
self.disableLoginBtn(false);
self.loginStatusText("提交");
self.errMsg(data.msg);
self.shouldShowMessage(true);
}else{
self.shouldShowStepThree(false);
self.shouldShowStepTwo(true);
self.loginStatusText('提交成功,正在跳转');
window.setTimeout(window.location.href= data.redirect,800);
}
},'json')
}
}
};
var modLoginLocal = new _LoginLocal();
ko.applyBindings(modLoginLocal, document.getElementById('divLoginLocal'));
})


脑海中闪电划过,我惊呆了
这里的验证是通过true和false来是否继续下一个页面
所以我们修改返回的数据包为true或者1都可以突破验证码页面来到更改密码页面
但是!!这并不是最优化方案!
来看最后一段:

if(self.shouldShowStepTwo()){
self.loginStatusText('正在提交...');
$.post('/admin/user/login/confirm_password',{
user_id: self.user_id(),
password: self.password(),
password_confirm: self.password_confirm()
}, function(data, status){
if(!data.result){
self.disableLoginBtn(false);
self.loginStatusText("提交");
self.errMsg(data.msg);
self.shouldShowMessage(true);
}else{
self.shouldShowStepThree(false);
self.shouldShowStepTwo(true);
self.loginStatusText('提交成功,正在跳转');
window.setTimeout(window.location.href= data.redirect,800);
}


直接构造post数据包,填好用户名、密码、再次确认密码然后直接发送就可以更改任意用户的密码。
地址提交到这里:/admin/user/login/confirm_password
3个参数:user_id、password、password_confirm
这里我将admin账户密码修改成为了wooyunorg

20.png


返回的数据包,成功,我们来登陆试试

21.png


登陆成功,下面开启第二层验证!
2、突破第二层手机验证:

22.png


登陆后又提示验证码输入,有了上一次的经验,继续爬行js,从js找出漏洞
然后看到这一段代码

var _LoginLocal = function(){
var self = this;

self.user_id = ko.observable("");
self.password = ko.observable("");
self.checkcode = ko.observable("");
self.loginStatusText = ko.observable("登录");
self.disableLoginBtn = ko.observable(false);
self.errMsg = ko.observable("登录状态:");
self.hiteMsg = ko.observable("验证状态");
self.shouldShowMessage = ko.observable(false);
self.shouldShowHiteMessage = ko.observable(false);
self.shouldShowStepOne = ko.observable(true);
self.shouldShowStepTwo = ko.observable(false);
self.login = function(){
self.shouldShowMessage(false);
self.shouldShowHiteMessage(false);
self.disableLoginBtn(true);
if(self.shouldShowStepOne()){
self.loginStatusText('正在验证...');
$.post('/admin/user/login/check',{
user_id: self.user_id(),
pwd: self.password()
},function(data,status){
if(!data.result){
self.disableLoginBtn(false);
self.loginStatusText("登录");
self.errMsg(data.msg);
self.shouldShowMessage(true);
}else{
self.shouldShowStepTwo(true);
self.shouldShowStepOne(false);
self.disableLoginBtn(false);
self.loginStatusText("验证");
self.hiteMsg(data.msg);
self.shouldShowHiteMessage(true);
}
},'json')
}else{
self.loginStatusText('正在登录...');
$.post('/admin/user/login/chkcode_verify',{
user_id: self.user_id(),
checkcode:self.checkcode()
},function(data,status){
if(!data.result){
self.loginStatusText("验证");
self.disableLoginBtn(false);
self.shouldShowMessage(true);
self.errMsg(data.msg);
}else{
self.loginStatusText("登录成功,正在跳转...");
window.setTimeout(2000, window.location.href = data.redirect);
}
},'json')
}
}
};


问题出在这里:

$.post('/admin/user/login/chkcode_verify',{
user_id: self.user_id(),
checkcode:self.checkcode()
},function(data,status){
if(!data.result){
self.loginStatusText("验证");
self.disableLoginBtn(false);
self.shouldShowMessage(true);
self.errMsg(data.msg);
}else{
self.loginStatusText("登录成功,正在跳转...");
window.setTimeout(2000, window.location.href = data.redirect);


window.setTimeout(2000, window.location.href = data.redirect)
这里设置了response,可以来跳转
那么构造以下response试试

23.png


24.png


25.png


但是结果并不尽人如意,跳转从admin转回了login,看来修改返回包还不能一步突破
继续看代码:
发现以下2处

'json')


// -*- coding:utf-8 -*-
(function(){
var _setCookie = function(key, val, path, maxAge){
path = typeof path=='string'? path: '/';
maxAge = typeof maxAge == 'number'? maxAge: 0;
var cookieStr = escape(key)+'='+escape(val)+'; path='+path;
if(maxAge){
cookieStr += '; max-age='+maxAge;
}
document.cookie = cookieStr;
};
var _parseCookies = function(){
var pt = /(\w+)\s*\=\s*([^\=\;]+)/gi;
var m = null;
var c = {};
do{
m = pt.exec(document.cookie);
if(m){
c[m[1]] = m[2];
}
}while(m);
return c;
};
var cookieUtils = {
setCookie: _setCookie,
parseCookies: _parseCookies
};
if(typeof window.cookieUtils == 'undefined'){
window.cookieUtils = cookieUtils;
}
})();


这里设置了一个状态undefined,json提交数据
那么伪造cookie是可以突破验证的
由于比较费时间,还要fuzzing正则,不深入这条路
那么就来最简单的,爆破验证码突破吧
4位数验证码,设置payload 1000-9999
爆破出这次验证码是1068,然后登陆确认
成功突破前台,来到了后台

11.png


有了后台,1元购买终于成了可能

10.png


旗下所有微店、订单、数据全部泄露,包括新闻发布

修复方案:

版权声明:转载请注明来源 黑暗游侠@乌云


漏洞回应

厂商回应:

危害等级:中

漏洞Rank:9

确认时间:2015-01-27 14:46

厂商回复:

CNVD确认所述情况,已经转由CNCERT下发给广东分中心,由其后续协调网站管理单位处置。

最新状态:

暂无