前端加密分析之某路由器奇葩算法
2020-08-15 13:03:54 Author: forum.90sec.com(查看原文) 阅读量:635 收藏

声明:本文章敏感部分已做了相应的处理,不代表任何利益和立场,且仅限于安全研究与教学使用,读者使用本文章方法所造成的所有后果,由用户承担全部法律及连带责任!作者不承担任何法律及连带责任。

背景介绍

某次bar冲浪时,看到一款路由产品,习惯性登录,提示密码错误,bing搜一下找到默认的账号、密码,登陆之,直接进后台——嗯,后台功能挺丰富。

图片

萌生了爆破的念头,到fofa上搜一下,居然发现好几万台这样的设备,好的,这就写脚本。在分析登录请求包的时候,发现它前端的参数居然是加密的,眼看应该是不能够通过重放来登录了,但在js文件里又发现了玄机...

正常情况下的前端加密过程

正常情况下,有效的前端加密逻辑是:首先生成随机的密钥key,然后使用key对原文进行加密,再将密钥和密文都发给服务器,服务器要么利用key和密文解密得到原文(如aes加密),要么直接比较密文(如md5哈希),其中key可能是随机的,也可能是固定的。下面整理一种aes前端加密配合后端解密的常见流程


 【获取随机key】 ->  

 【用key进行加密 encrypted_text = encrypt(key, password)   】 ->  

 【将key和encrypted_text都发送给服务端】 ->

 【服务端解密、验证、返回验证结果】
图片

我将这个过程抽象,得出以下三个结论:

  1. 开发者之所以进行前端加密,是因为 不希望攻击者知晓原文 (例:防止MitM中间人攻击、burp明文传输)

  2. 原文 <==( 密钥+加密算法 )==>密文

  3. 加密算法 绝大部分 是写在js里的,而密钥 常常是可控 的,甚至有些被硬编码了,如图所示(图片参考地址ref-1)

当然了,你可能会说,RSA算法是例外呀,加密和解密的key可以不是同一个,这样即使加密的key泄露了也无法解密。

没错,但如果不考虑这种特殊情况,我们就可以说: 在密钥key已知的情况下,要想使攻击者猜不出原文,就要使加密算法足够复杂,增加攻击者分析算法的成本,进而提高攻击者自己加密的难度。 遗憾的是,加密、解密常常已被封装成了单个函数,而算法本身更是简单到离谱,这就使得前端js加密形同虚设。。。

好,说了这么多废话,咱们言归正传——分析分析这个前台登录界面的加密算法

登录处账号密码加密算 法的分析

当账号为默认账号 wwws 、密码为默认密码 admin 时,登录处的POST包如下

 POST /wenwang/login.cgi HTTP/1.1
 User-Agent: Mozilla/5.0 (Windows NT 10.0; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4086.0 Safari/537.36
 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
 Accept-Encoding: gzip, deflate
 Content-Type: application/x-www-form-urlencoded
 Content-Length: 98
 Connection: close
 Upgrade-Insecure-Requests: 1
 Pragma: no-cache
 Cache-Control: no-cache

 user=7WJCDjFKjXyuyq&password=6mwHEERAeYcbogp&ww_loginpage=1&csrfprotect=&Submit=%E7%99%BB%E5%BD%95

在F12 debugger里发现加密函数 formFun()


 <li>

  <label>账号:</label>

  <input name="user1" id="user1" type="text" class="loginuser" value="" />

 </li>

 <li>

  <label>密码:</label>

  <input name="password1" id="password1" type="password" class="loginpwd" value="" AUTOCOMPLETE="off"/>

 </li>

 <form name="login_form" action="login.cgi" method="post" onSubmit="return formFun()"><!--加密后的在这里-->

跟进加密函数,


 function formFun(){

  var user = document.getElementById("user1").value; //'wwws'
  var paswd = document.getElementById("password1").value; //'admin'
  ...
  var t_user = randomString(10);//10位的随机字符串
  var t_paswd = randomString(10);
  var i = 0;

  for(i=0;i<user.length;i++) //用户名加密 'wwws' -> 'yuyq'
  {
  if(i%2 == 0)
       t_user += String.fromCharCode(user.charCodeAt(i)+2);
  else
       t_user += String.fromCharCode(user.charCodeAt(i)-2);
  }

  for(i=0;i<paswd.length;i++) //密码加密 'admin' -> 'cbogp'
  {
  if(i%2 == 0)
        t_paswd += String.fromCharCode(paswd.charCodeAt(i)+2);
  else
        t_paswd += String.fromCharCode(paswd.charCodeAt(i)-2);
  }
  document.getElementById("user").value=t_user;
  document.getElementById("password").value=t_paswd;

  return true;
 }

里面用到的几个函数,用法都比较简单,我的理解如下:

  • randomString() ,可返回指定长度的随机字符串

  • charCodeAt() ,可返回指定位置的字符的 Unicode 编码。字符串中第一个字符的位置为 0, 第二个字符位置为 1,以此类推,如 'w'.charCodeAt()=119

  • String.fromCharCode() ,可根据Unicode值返回一个字符串。如 String.fromCharCode(119)="w"

间隔移位算法

其实,加密过程中的核心算法,就是下面的这段函数,我们来看看


  for(i=0;i<user.length;i++) //用户名加密 'wwws' -> 'yuyq'

  {

  if(i%2 == 0)

  t_user += String.fromCharCode(user.charCodeAt(i)+2);//奇数位上的字符串向上偏移2位,即unicode值+2

  else

  t_user += String.fromCharCode(user.charCodeAt(i)-2);//偶数位上的字符串向下偏移2位,即unicode值-2

  }

 }

举个例子:原字符串为 wwws , 经过上面的算法处理过后,就成了 yuyq

我给它取名叫“间隔移位算法”,示意图如下,最左边一栏是Unicode的顺序,绿色是原文,蓝色是算法处理得到的“密文”;。

理清了核心算法,我们再回过去看看漏了些什么: t_usert_paswd 都是有初始值的—— 长度为10的随机字符串!

var t_user = randomString(10);//10位的随机字符串

var t_paswd = randomString(10);

所谓的间隔移位算法,将原文"加密"后,是拼接在初始值后面的。同样用原字符串为 wwws 来举例说明一下:

wwws -> yuyq -> AAAAAAAAAAyuyq , 这10个A就代表长度为10的随机字符串。

实际上,分析到这里,它登录处在后端解密的整个逻辑,就呼之欲出了。解密时:从第11位开始,截取字符串,得到间隔移位算法处理过的字符串,即上图中“密文”,再根据奇偶位移位的不同规则,恢复成原文即可 AAAAAAAAAAyuyq -> yuyq -> wwws

结论

对于每一次登陆时POST的值来说,前10位是没有实际作用的,我们可以随意填充。从第11位开始的值,才真正被服务器接收,移位解密后,用于验证账号和密码是否正确。

因此,对于默认口令的检测,我们只需要将移位加密后的账号、密码,拼接到10个任意字符串之后即可。(其实, 验证默认口令时,重放数据包就可以了 XD

无需POST包的登录

看到登录成功后它返回的Cookie值,我端起桌上的菊花茶,陷入了沉思。。。

 Set-Cookie: gw_userid=AAAAAAAAAAyuyq,gw_passwd=3DDE690BBFDBCF8E3E258CBC40C0B9BF;

不妨来分析一下这段cookie,先看 gw_userid=AAAAAAAAAAyuyq ,这不就是刚刚登陆时POST包里的 user 参数吗。那 gw_passwd 后面的32位字符串呢,怕不是密码哈希,尝试到各大平台上解密,无果。不过不打紧,只要登录密码是admin,即便是在不同的设备上,gw_passwd也是这个值(加密规则相同)。

因此,为了验证是否可以用默认密码登录,连POST包都不用发,直接带着这个cookie访问主页,观察会是否302跳转到登录界面即可。若跳转即证明cookie无效,使用的密码并非默认密码,否则密码就是默认密码。

至此,编写批量验证脚本的思路已经完全分析清楚了。

总结

由于该后台登录处采用固定方法“加密”,没有起到真正的加密效果,导致可用重放的方法来尝试登录,甚至在完全弄清楚算法后,可以自行加密、进行爆破。

reference


文章来源: https://forum.90sec.com/t/topic/1253/1
如有侵权请联系:admin#unsafe.sh