有人的人喜欢在日站的时候直接手动搜集信息, 查找弱点, 但是对一个铁桶一般的, 已经被检测过无数次的网站来说, 这样做的效率略微低了一点. 有一款好的扫描器, 可以让整个检测和信息搜集的过程事半功倍. 不仅可以借助扫描器庞大的数据库, 发现手工检测无法发现的漏洞, 甚至可以主导整个入侵的思路. 总之, 对我等彩笔来说, Acunetix Web Vulnerability Scanner 是一款不错的工具, 非常适合对目标进行初步的信息搜集, 搞清资源之间的关系, 为进一步的渗透打下基础.
但是我们要意识到, 扫描器也有不少缺点. 首先由于使用的是破解版, 并没有实时更新, 因此降低了扫到漏洞的可能. 其次, 扫描器是机器, 自然就有机器的缺点. 比如不能正确识别各式各样的URL中的参数, 不能识别伪静态等等. 因此, 根据扫描器搜集的信息进行进一步的手工检测, 是非常必要的.
言归正传, 让我们将目光转移到今天的目标上来. 这台服务器是一台运行着IIS 7.5的windows服务器, 目标是拖库. 程序为ASP.NET. 经过检测没有注入点(后文会提到他防止注入的方式). 但是扫描器告诉了我们一个非常重要的信息, 这台服务器存在MS10-070安全漏洞. 关于这个漏洞, 英文好的同学可以看这里; 中文好的同学可以看这里; 两种语言都运用自如的同学呢, 可以两篇都看, 甚至可以看一下这里, 还有这里; 没有时间的同学, 请继续看下面.
作为一个异常傲娇的漏洞, MS10-070允许你以高昂的时间代价读取服务器网站目录下的一个文件, 不能在一台机器上同时试图读两个文件, 并且你永远也不知道这个文件是不存在还是你花的时间不够.
事实就是这么残酷, 但鲁老夫子教育我们, 真的猛士, 要敢於直面慘淡的人生, 正視淋漓的鮮血. 于是聪明的人们想出了一个能极大提高信息搜集效率的办法, 那就是读一个通常都存在的文件, web.config. 通过这个文件, 我们至少可以得到以下信息: 网站的数据库连接字符串, 以及网站的URL匹配策略.
比如今天的目标, 就非常淡腾地把网站的数据库连接字符串放到了另外一个文件中, 以至于还要再读另外一个文件, 这一读, 就是一个多小时.
<appSettings configSource="config/app.config" />
此外, 这个网站采取了正则表达式匹配的方式来进行URL匹配, 因此SQL注入什么完全不可能了.
<rule name="rule" stopProcessing="true"> <match url="^abcd_/([0-9]+).html" /> <action type="Rewrite" url="abcd.aspx?id={R:1}" /> </rule>
自然, 下一步就是读取这个config目录下的app.config文件. 部分内容如下.
<appSettings> <!--连接字符串是否加密 --> <add key="ConStringEncrypt" value="true"/> <add key="ConnectionString" value="8618B53C32BF8E0B8618B53C32BF8E0B8618B53C32BF8E0B8618B53C32BF8E0B8618B53C32BF8E0B8618B53C32BF8E0B8618B53C32BF8E0B8618B53C32BF8E0B8618B53C32BF8E0B8618B53C32BF8E0B"/>
终于进入正题了, 淡腾的加密. 所谓线索, 其实就是用来google的关键词. 经验告诉我们, 在这个情况下, ConStringEncrypt
无疑是最有效的关键词. 本人在这里犯了一个十分严重的错误, 即错误地将搜索“ConStringEncrypt”等同于搜索“asp.net 连接字符串 加密”, 导致搜索误入歧途, 结果都是微软自.net 2.0开始添加的加密方法, 以及一些支离破碎的介绍动软加密的文章, 信息互相冲突, 很长时间没有进展. 具体蛋碎流水账可以看这里.
经过习科论坛 fung 的提示, 当时认为有两条路, 一是下载他的dll文件, 但是由于没有做过asp.net开发, 不知道文件, 项目的命名规律, 可耻地失败了. 第二条路就是破解连接字符串.
经过进一步的信息搜集, 发现app.config的加密方式并不是微软官方的加密方式, 否则由于密钥文件名称的不确定性, 以及加密算法的坚固程度, 就不会有这个帖子了. 灵光一现的时刻, 发现搜索ConStringEncrypt的结果全都是中文, 这是很不寻常的. 通常情况下, 如果“ConStringEncrypt”是微软官方的加密方式的关键字, 至少应该有满篇的英文结果. 于是几下就找到了这个系统的名字: 动软代码生成器. 顺便提一句, 百度就是渣渣.
动软代码生成器连接字符串加密核心代码如下.
public static string Encrypt(string Text) { return Encrypt(Text,"litianping"); } /// <summary> /// 加密数据 /// </summary> /// <param name="Text"></param> /// <param name="sKey"></param> /// <returns></returns> public static string Encrypt(string Text,string sKey) { DESCryptoServiceProvider des = new DESCryptoServiceProvider(); byte[] inputByteArray; inputByteArray=Encoding.Default.GetBytes(Text); des.Key = ASCIIEncoding.ASCII.GetBytes(System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(sKey, "md5").Substring(0, 8)); des.IV = ASCIIEncoding.ASCII.GetBytes(System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(sKey, "md5").Substring(0, 8)); System.IO.MemoryStream ms=new System.IO.MemoryStream(); CryptoStream cs=new CryptoStream(ms,des.CreateEncryptor(),CryptoStreamMode.Write); cs.Write(inputByteArray,0,inputByteArray.Length); cs.FlushFinalBlock(); StringBuilder ret=new StringBuilder(); foreach( byte b in ms.ToArray()) { ret.AppendFormat("{0:X2}",b); } return ret.ToString(); }
看到DES三个字母的时候, 不才顿时眼睛一亮, 尖牙一露, 要知道DES是上个世纪7, 80年代的标准加密算法, 并且自从90年代以来就被广泛挑战. 用21世纪第13个年头的高端家用处理器来暴力破解一下听起来是相当可行的一个方案.
来分析一下加密流程. 首先, 程序对密钥取md5, 经试验得知, 结果是32位16进制字符串, 并取其前8位, 并将其作为DES加密的密钥(Key)以及初始化向量(IV, Initial Vector, 可以理解为第二个密钥). 之后对明文字符串加密, 并将其转换为16进制字符串, 返回.
这套系统有一个自带的编译好的加解密工具, 密钥是默认的“litianping”. 试一下, 程序异常了, 解密失败. 这说明敌人非常狡猾地用了一个自定义密钥. 这种情况下, 如果去试图破解作为密钥的sKey, 显然是非常效率低下的. 因为密钥长度不定, 字符集不定, 中间还要加一步md5运算, 基本上等于碰运气. 这种傻事是绝对不能做的.
普及一下DES的基本知识. DES有众多不同的加解密模式, 包括ecb, cbc 等等. DES是对称加密算法, 即一个确定的明文字符串经过一个确定的密钥加密后, 可以被同一个密钥解密为之前的明文. DES还是一个块加密算法, 即DES以8 Bytes每组, 对明文进行加密, 余下不足8 Bytes用默认填充Byte补足, 刚好8 Bytes的则在结尾附加8个默认填充Byte. 8 Bytes明文生成8 Bytes密文, 因此只要一个密钥能够解密密文的前8字节, 同样的密钥就能解密整个密文. DES的密钥是8 Bytes, 但是参与具体运算的是56 bits, 似乎是每个Byte被扣掉了一个bit, 这里不是很清楚, 请自行忽略. 因此, 暴力破解的话, 需要搜索的密钥空间就是8 Bytes, 即256^8种可能. 这个数字有多大呢, 根据wolframalpha的计算, 如果处理器每秒能计算3兆(3 * 1024^2)个密钥(这差不多是我i7处理器, 使用openssl的DES库时单线程的计算量), 那么需要 185, 825 格里高利年. 18.5万年之后, 人类将繁衍6600代, 海平面高度将下降12米, 而那时候的地球看起来 唔, 好像也没什么太大的变化.
但是, 凡事都有个但是, 嗯嗯. 经过仔细的分析发现, 上述加密流程中, 作为DES密钥的8个字节, 是MD5加密的结果. 而MD5加密的结果, 是可读的16进制字符串! 这也就意味着, 此时DES的密钥空间从256^8骤降到了16^8(每个字节有16种可能, 即0-F). 再来计算一下, 时间降到了1365秒, 也就是22分钟多一点.
花了一天时间, 用C语言写了一个程序来进行计算. 当时出于吃饱了撑的的考虑, 决定实现一个密钥分配器, 由每个线程向公共的密钥池请求接下来若干个密钥分块并进行计算. 完成计算后, 销毁上述密钥块, 并请求下一轮计算的密钥块. 因为在下实在是太懒了, 于是从文件/标准输入中读取密文这个功能就没有实现, 直接在代码里定义常量吧.
DES解密部分由openssl的DES加解密库实现, 多线程部分使用了已编译的windows版pthread. 开发环境是Dev-Cpp 5.4.2, 至于如何配置编译环境, 请看这篇文章:windows下的openssl安装以及DevCpp编译器下的openssl编程环境配置. 不要忘记加上pthread, 具体方法请自行搜索.
一个比较大的问题是, openssl的DES库似乎并不返回解密是否成功的信息. 而不论密钥是否正确, 解密函数总会返回一个字符串, 当然, 如果密钥不正确, 解密的结果也就没有任何意义, 俗称乱码. 而这直接导致了无法快速判断是否解密成功, 于是不才采用了一个笨办法. 由于已知连接字符串前7位为server=
, 于是就将解密所得字符串的前6位与“server”比较, 成功则打印. 如下图所示, 在i7-3770上使用6线程对动软代码生成器压缩包中加解密工具的默认连接字符串密文进行计算. 显然第一个密钥是正确的. 而“litianping”经过md5后的前8位也正是804BD9D4
. 实际使用中, 使用6线程5分58秒就找到了密钥.
目标站点有两个IP, 分别在 124.232.161.x 和 183.61.243.x, 无法判断是否是同一台机器, 但是ASP.NET的配置不同, 后者没有自定义错误. 但是两者的连接字串中数据库地址均为相同的10.x.x.x , 实在无法确认这是怎样的网络结构. 曾尝试旁站但是可耻地失败了