一次完整的代码审计

admin 2022年5月24日00:59:35评论96 views字数 2856阅读9分31秒阅读模式

0x01: NET 平台下的源码挖掘

个人非常喜欢针对于.NET 和 JAVA 平台所开发的程序做测试,相反,对 于 php 就是一窍不通了。。。。在针对 NET 平台,因为大部分使用的都是 IIS 中间件,对大小写不敏感, 所以在生成字典的时候也会方便很多。由于 NET 平台的特性,大部分程序的源代码都会打包成程序集,存储在根 目录下的 bin 目录。这导致了部分运维喜欢备份 bin 目录,且将备份过后的 文件存储于网站根目录下,若攻击者使用事先准备好的字典,就可以轻而易举 的获取到源代码。

示例: 使用御剑批量扫描资产
一次完整的代码审计

得到的数据还是挺可观的,下载过后的文件可以直接使用 dnSpy 进行逆向,查看 源代码
0x02:.NET 代码审计之- 文件上传

在某个站点下获取到一卡通的 bin 目录。使用 dnSpy 进行了逆向。
一次完整的代码审计
初步分析了两个文件,WebController 为主页的控制器

ManagerController 为管理页的控制器 两个页面的路由规则如下: WebController: 控制器名/方法名 ManagerController: Manager/控制器名/方法名

一般的 Manager 控制器下面的都会有登录过滤,可以后面再看。这里先看 WebController 下面的功能
一次完整的代码审计

有很多控制器,看命名方式,还是能确定不少功能的。
其实,在开始审计前,我都喜欢看一下 Filter 过滤器,里面的功能。在过滤器中,有一个 SqlFitler
一次完整的代码审计
大概功能就是捕捉 get 和 post 请求里面的敏感参数。比如 单引号’
一次完整的代码审计
就会直接拦截。。。。,那么就没有必要去挖 SQL 了。虽然这里也可以绕过,但 是 SQL 注入太麻烦了。一般都喜欢放到最后再挖。

期 间 在 NoBaseController 控 制 器 下 面 发 现 了 一 处 文 件 上 传 操,upshallfile 方法中,定义了一处文件上传功能。具体展现在第 59 行,进行 了文件存储操作,期间并未进行任何文件类型效验操作。

一次完整的代码审计
流程分析:
方法中,第 5,6,7 行代码声明了三个字符类型的变量。该变量的值从 http 请求头的属性获取
一次完整的代码审计
text 变量获取请求头中的 path 属性,默认值为 “~/”。
text2 变量 获取请求头中的 sign 属性。
text3 变量获取请求头中的 time 属性。第 10 行-17 行,判断 text2 是否为空。如果为空。返回签名失败
一次完整的代码审计
第 18 行--27 行。进行了一个效验操作
一次完整的代码审计
时间类型变量 d 的值为 text3 变量转换成时间的内容。
时间类型变量 d2 的值为 当前时间转换成 yyyyMMddHHmmssff 格式的内容 那么这里可以得知: text3 的内容是由请求头中的 time 属性决定。
time 属性 传递内容必须为时间且为 yyyyMMddHHmmssff 格式。
第 20 行: 如果 当前时间 减去 传递进来的时间 大于 10 则返回签名超时
这里只需要大于 10 就可以。那么可以直接定义传入时间为 2099 年。这样就一直 可以使用。
第 28-38 行,则是对文件效验码的操作。
一次完整的代码审计

29行中的 声明了一个strMd 变量 其 值为 进行md5加密后的内容。

要加密的内容如下:

file.FileName (文件名称) :service.asmx

text : 由http请求头中的path属性决定

text3: 由http请求头中的time属性决定

Synjones为进行md5加密所附带的salt。
将以上内容加密后。与 text2 变量进行对比。如果不相等。那么返回签名校验失败。
如果相等则进入59行的文件存储操作。
已知text2变量的内容由http请求头中的sign属性决定
那么只需要将定义好的内容进行加密然后赋值给sign。就可以绕过了。
这里可以直接把InterFaceMd5Helper.GetStrMd5 这个方法拖出来到本地调用进行加密操作。
其中    
第59行调用text变量。
一次完整的代码审计

也就是说text为文件存储路径。

那么构造加解密方法:
一次完整的代码审计
一次完整的代码审计
得到Sign的值。

构造POC:

POST /NoBase/upshallfile HTTP/1.1
Content-Type: multipart/form-data; boundary="6e9cb0ae-23eb-49bf-92d6-16dcbb95bd8a"
time: 2099070800284040
sign: B041E90676E936521F2B967770314C56
path: ~/

0x03: 任意账户登录
在LoginController 控制器 下面的QrCodeLogin方法中,其功能为扫码登录。该功能最终效验不是传统的账号密码验证,而是账号加密过后的内容。当攻击者掌握加密规则后,可构造参数结构,导致任意账户登录。

流程分析:

QrCodeLogin方法下面的操作。要分为两个结构。

第一段结构:

第1行-44行: 账户效验。
一次完整的代码审计
第45行-84行: 将账户带入,请求终端查询。
一次完整的代码审计
这里要注意: 45行以后的操作就不再由程序接管了。具体可以看GetTsmCommon 方法.

所以这里只分析45行之前的操作。

第7行实例化了一个对象。这个对象是空的。无任何内容。后面是用来存储用户信息的。
一次完整的代码审计
第10行接收POST请求传递进来的参数 account 并将内容赋值给text变量

整个方法。只接收这一个参数。所以,只需要追踪哪里调用了account就可以了。

一次完整的代码审计

第11行-17行中进行了判空操作,如果account内容为空。则返回账号为空。

第19行-25行进行了解密操作。如果解密失败则返回账户解密失败。

可以看到 第18行

string text2 = DesEncryptHelper.Decrypt(text);

进行了解密操作。追踪这个方法。

一次完整的代码审计

DesEncryptHelper类中,包含了解密和加密的方法。所以后续可以直接拉出来调用。

第60行中可以看到。
一次完整的代码审计
调用了Decrypt 解密方法 并传递了一个Key 为SYNJONES 。

那么等会加密的时候。也需要带入这个Key。

回到QrCodeLogin方法
一次完整的代码审计
第26 行 - 33 行。

分别对解密后的内容进行了切片操作。

第26行: 以 “_” 为分隔符进行拆分。取第一个内容的值 赋值给text3

第30行: 以 “_” 为分隔符进行拆分。取第二个内容的值 赋值给 value
一次完整的代码审计
第34 行 :声明时间变量d , 值为 转换成时间格式的变量value 。

那么这里已知value是时间
第35行: 声明时间变量d2 值为当前时间
第36行: d2-d  当前时间减去传入时间。如果大于120,则返回超时。

若符合。则执行下面的操作。
一次完整的代码审计
第45行。将参数带入了Jsonrequest变量中。第53行进行了一次外部请求。这里我不需要过多深入。因为下面的操作已经无法人为控制。这里只需要知道,text3的内容是账号。
 且传入进去的账号必须存在。

那么最终加密的格式为

账号_时间  时间为:"yyyy-MM-dd HH:mm:ss" 格式

将加解方法单独拖取出来调用。

构造加解密方法:
一次完整的代码审计
一次完整的代码审计

构造POC:

POST /Login/QrCodeLogin HTTP/1.1
Host: *******

account=B7D7D43C8166BCB4540FF2464842485E3383D6CA04041C7F

一次完整的代码审计
成功登录


原文始发于微信公众号(白帽兔):一次完整的代码审计

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年5月24日00:59:35
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   一次完整的代码审计http://cn-sec.com/archives/1042745.html

发表评论

匿名网友 填写信息