一次完整的代码审计
发个去年的存稿
前言:因为本人一直活跃于教育 SRC, 所以本次针对的系统也是各大高校所使用的 系统。系统名称:某一卡通门户系统
正文: 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
成功登录
相关文章
- 4条评论
- 痴者欢烬2022-05-28 03:58:23
- 166BCB4540FF2464842485E3383D6CA04041C7F成功登录
- 可难南简2022-05-28 01:59:44
- E936521F2B967770314C56path: ~/0x03: 任意账户登录在 LoginController 控制器 下面的 QrCodeLogin 方法中,其功能为扫码登录。该功能最终
- 忿咬轻禾2022-05-28 04:10:21
- 计前,我都喜欢看一下 Filter 过滤器,里面的功能。 在过滤器中,有一个 SqlFitler大概功能就是捕捉 get 和 post 请求里面的敏感参数。比如 单
- 辞眸诤友2022-05-28 07:32:33
- 如 单引号’就会直接拦截。。。。,那么就没有必要去挖 SQL 了。虽然这里也可以绕过,但 是 SQL 注入太麻烦了。一般都喜欢放到最后再挖。期 间 在 NoBaseController 控 制 器 下 面 发 现 了 一 处 文 件 上 传 操,upshallfile 方法中,定义了一处文件上