浅谈Asp.net代码审计之会议室预定系统Book3.0SQL注入(二)

 

0x1 前言 (ForeWord)

继上篇浅谈Mac上手 Asp and Asp.net 代码审计(一)文章,我本来打算接着研究下去的,然而自己做其他事情搁置了,还好最近放假了,能抽出点时间来进行学习下了。我们可以去找asp.net的cms来进行漏洞分析来提高自己的代码审计能力,所以今天我打算以启明星会议室预定系统Book v3.0这个系统作为审计对象。

 

0x2 补充一些基础概念

经典ASP(Active Server Pages 动态服务器页面)

asp是一种使得网页中的脚本在因特网服务器上被执行的技术。

asp页面的文件扩展名是.asp 通常使用VBScript语言来编写。

后面关于Asp的更多内容,我会在下篇文章通过分析新云网站系统程序来做更详细的理解

ASP.NET

ASP.NET 是新一代ASP。它与经典ASP是不兼容的,但ASP.NET可能包括经典ASP

ASP.NET 页面经过编译的,所以运行速度比经典ASP快

ASP.NET 具有更好的语言支持

ASP.NET 页面的扩展名是.aspx,通常是用VB(visual Basic) 或者 C#(C sharp)编写(主要是C#)

关于使用的语言还有一种就是Jscript.net,我们平时用的aspx一句话就是基于此的,因为其他中间语言已经没有了

eval函数

%@ Page Language=”Jscript”%>
<%eval(Request.Item[“chopper”],”unsafe”);%>
<!– 设置unsafe参数能允许程序在不安全的上下文执行 –>

ASP.NET 中的控件可以用不同的语言(包括C++ 和 Java)编写。

当浏览器请求ASP.NET 文件时,ASP.NET引擎读取文件,编译和执行脚本文件,并将结果以普通的HTML页面返回给浏览器。

ASP.NET文件扩展名

  • 经典 ASP 文件的文件扩展名为 .asp
  • ASP.NET 文件的文件扩展名为 .aspx
  • Razor C# 语法的 ASP.NET 文件的文件扩展名为 .cshtml
  • Razor VB 语法的 ASP.NET 文件的文件扩展名为 .vbhtml

ASP.NET Razor

Razor 是一种将服务器代码嵌入到 ASP.NET 网页中的新的、简单的标记语法,很像经典 ASP 。

Razor 具有传统的 ASP.NET 的功能,但更容易使用并且更容易学习。

这个了解下就好了,平时很难遇到。

 

0x3 Asp.net的编译及其发布 (Principle of compilation and release)

我们平时用 vc++开发asp.net的流程一般是:

建立C# web程序 -> 选择模版 -> 对源码进行开发 -> 编译 -> 发布

关于Core web and Framework 两种框架在.NET生态

THE .NET Framework:

是过去经常使用的,用这个可以创建windows应用程序还有web applications,现在你可以用他创建winform,uwp,wpf等等相关的应用程序,web方式就是 Asp.net MVC

.NET Core :

是微软最新推出的开源的、跨平台的框架

其实两者最大的区别和选择: 程序是否需要跨平台性,其他的话就是一些开发特点(问题不大)

补充(zsx师傅说framework是一种遗留产物,Core的话是全新的框架)

关于asp.net web开发,通常也有两种开发方式: webSite and webApplication( 也就是 网站与web应用程序的区别。)

但是开发的话通常会选择采取web应用程序去开发,我们可以简单看下两者的区别。

web应用程序模型:

1.网站编译速度快

2.可以指定网站项目生成单一的程序集

3.方便管理各个项目

4.每次修改都需要重新编译,把所有处理程序编译成一个程序集(项目名.dll)

web 网站模型:

1.动态编译该页面,不用编译就可以看到效果,适合小网站的开发(有点类似php即视感)

2.每一个处理程序文件都是单独的,比如1.aspx报错不会影响到2.aspx的执行,web应用程序则相反。

3.每次运行都会即时编译一个程序集dll文件,可以用

context.Response.Write(System.Reflection.Assembly.GetExecutingAssembly().Location)

获取到dll文件的位置。

怎么区别呢?

项目就是一个应用程序,在vs中查看的时候,项目中建立的一般处理程序,有两个文件,比如First.ashx还有First.ashx.cs而网站只有一个First.ashx,但是他们写代码是一样的。

还有就是项目中的一般处理程序有命名空间,而网站没有命名空间。

因为命名空间作用是为了区分类同名的情况,而网站一个文件就是一个独立的程序。

例子就是:

First.ashx对应代码如下

<%@ webHandler Language="C#" CodeBehind="First.ashx.cs" class="webApplication1.First" %>

然后是一个 First.ashx.cs 文件,会有个空间声明

 

namespace webApplication1{

 public void First : IHttpHandler

 {

 ………………..

 }

}

这就是网站应用程序的特点

而asp.net 网站的特点就是没有命名空间

<% webHandler Language="C#" class="First" %>

 public void First : IHttpHandler

 {

 ………………..

 }

 

0x4 浅探Asp.net 反编译原理

我们先了解下程序集的概念:

程序集,是基于.net平台,*.dll,*.exe为后缀的文件。

分类: 私有程序集 和 共享程序集

程序集包括:

1.windows文件首部 作用就是告诉window操作系统去区别asp.net 、 winform 和 控制台应用程序

2.CLR 首部信息: .NET 程序的标志

3.元数据:MeteData 描述数据的数据

4.程序集清单: 记录程序包含的dll程序以及相关版本信息

5.可选的内嵌资源

单文件程序集:

我们新建一个类库工程,然后编译就会得到一个*.dll的文件。

多文件程序集:

由多个模块构成的程序集,就是把另外的程序当作一个模块写进了另外一个dll。

接着我们可以了解下程序集的在asp.net工作流程:

1.页面的编译

页面的编译

特定.aspx资源的程序集的生成分为两个步骤进行。首先,该资源文件的源代码会被解析,根据得到的信息,从Page类(或Page的派生类)派生出相应的类。然后,动态生成的类会被编译为程序集,该程序集之后会被缓存到ASP.Net专用的临时目录下。

只要链接的aspx源文件没有被更改,且整个应用程序没有重启,已编译的页面就一直存在。对已链接aspx文件的任何更改,将使相关程序集变为无效,并在该页面下一次被请求时,强制HTTP运行库创建新的程序集。

编辑web.config和global.asax之类的文件会导致整个应用程序重启。在这种情况下,在某个页面被请求时,所有页面会被重新编译。如果bin文件夹中程序集被改动(新建或替换),所有页面也会被重新编译。

由于iis版本不同,对应的处理过程也有所差异,如果想全面了解推荐博客:asp.net

这里我针对iis6.0来简单介绍下asp.net环境下程序集的作用:

用户页面请求->iis6.0(驱动程序http.sys管理输入请求)->请求aspx资源->解析标识,映射到Page的派生类,确定处理该请求的类名->如果当前加载到AppDomain的程序集不包含这个名称的类,http运行库将发出该类的创建和编译命令,如果程序集存在这个类,则会被直接加载到内存用来处理该请求

关于asp.net 的详细http页面请求处理流程可以参考下图(主要看PageHandlerFactory即可):

ASP.NET 请求处理流程结合这篇文章或许能更好的帮助理解,但是也有一些差异。

这里可以归纳下程序集的作用(自我理解,可能不准确):

编译生成的程序集(.dll)是一种中间语言(IL)代码,集合了源代码中的所有.aspx.cs文件,能够被iis进程来调用,从而减少编译时间。

这里可以推荐下一个反编译的工具:

dnSpy 下载地址(这个是源码得用vs编译一下,我下面的内容是用1.5版本演示的,可能有点不太一样,不过网上有这个相关的教程,如果看不懂可以自己去百度学习下)

这个反编译工具可以反编译出源码然后修改内容再重新编译(平时我们写一些dll劫持的东西也可以这样弄,下次有机会写篇文章介绍下dll的相关利用)。

这里我可以演示下用c#写的hello world exe程序进行反编译

直接新建个.net framwork的控制台程序项目,代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            string str = "Hello World";
            Console.WriteLine(str);
            Console.ReadKey();
        }
    }
}

然后直接打开dnspy,把exe程序直接拖进去

修改下字符串->edit method->然后重新编译即可。(dnspy分64和32位版本,debug的需要32位,自己选择)

 

0x5 系统的安装过程 (System Installation)

源码下载地址(Source code Dowload URL): 启明星会议室预定系统Book v3.0

然后解压压缩包,可以看到有个readme.htm的安装指南。

当前环境(current environment):

我的系统(my system): mac os

审计环境系统(audit system): win10

服务器(server): iis

数据库(database): sqlserver 2012 (这个是在写这篇文章的时候装的,上面那两个环境是基于第一篇文章的)

0x5.1 快速安装 (Quick Step)

1.快速安装(推荐,用于快速测试系统功能)  1.1 下载 安装微软 LocalSqlDb.msi LocalSqlDb.msi 并双击它安装。  1.2 从启明星官网下载您所需要的程序,解压后,双击 QMX.exe,即可运行。

这个,我个人不是很推荐,感觉没办法进行debug,而且有可能和真实环境有出入,我比较喜欢把审计环境贴近实际环境。

0x5.2 正式安装

我演示下自己的安装过程(个人觉得asp.net审计,环境搭建过程是真的有点繁琐,可能是我很少用window 系统)。

首先我们用 Microsoft SQL Server  Management Studio,然后在安全性的登陆名找到sa,设置密码,并且允许登陆。

之后我们在

新建一个名字为 book的数据库(名字一定要是book,因为book.sql数据文件中写死了 USE [book])

然后按下 ctrl + o 快捷键 或者直接在菜单栏点击打开文件,然后打开在 app_data 下的 book.sql脚本

然后点击执行

这样子,我们的表就自动建立了,然后修改根目录下的web.config文件的数据库链接

修改为如下:

   <connectionStrings>
    <add name="connectionString" connectionString="server=XQ176FEE\SSA; database=book; uid=sa; pwd=sa123; "/>
    </connectionStrings>

配置完成之后,我们还需要在iis上配置发布我们的网站。

由于文件夹权限问题,iis默认权限(network service) 没办法访问。

window 我个人感觉是真的烦。 文件夹我们为了方便我们直接设置所有者为Everyone 权限。

然后配置下iis(如果不对,试着调整下DefaultAppPool得net版本):

最后,我们就可以直接打开网站了。

默认密码是:

admin
123456

 

0x6 系统结构

第一篇文章已经写了我的环境,我是在Mac的PD下安装的WIN10,Mac可以直接共享window的文件夹。

直接执行tree命令列举出目录:

简单介绍下tree的命令使用:

tree –help 获取tree的帮助信息

tree -L 3 Descend only level directories deep 代表遍历level=3 层目录

tree -C Turn colorization on always 着色

tree -d List directories only 只列举目录

tree -L 2 -C:

── Admin //后台目录
│   ├── About_Intro.aspx
│   ├── Admin.Master
│   ├── Book_Category_Area.aspx
│   ├── Book_Client_Caller.aspx
│   ├── Book_Client_Info.aspx
│   ├── Book_Room_Config.aspx
│   ├── Book_Service_Config.aspx
│   ├── Config_Email.aspx
│   ├── Config_Email_EWS.aspx
│   ├── DB_Tool.aspx
│   ├── Default.aspx
│   ├── PItem.aspx
│   ├── PItemsControl.ascx
│   ├── Refresh.aspx
│   ├── ResetPwd.aspx
│   ├── Setting_Tool.aspx
│   ├── Upload.aspx
│   ├── Upload2.aspx
│   ├── User_MoveDept.aspx
│   ├── Users_Dept.aspx
│   ├── Users_Dept_Reset.aspx
│   ├── Users_EditUser.aspx
│   ├── Users_Export_Data.aspx
│   ├── Users_Import_Data.aspx
│   ├── Users_Imports_AD_Dept.aspx
│   ├── Users_Imports_From_AD.aspx
│   ├── Users_Imports_From_AD2.aspx
│   ├── Users_Lists.aspx
│   ├── Users_Query.aspx
│   ├── Users_Role.aspx
│   ├── Users_SetRight.aspx
│   ├── Users_Xfpinyin.aspx
│   ├── avtor.jpg
│   ├── images
│   └── img
├── App_Data // 应用数据
│   ├── MSSQL.BAT
│   ├── MSSQLEXPRESS.BAT
│   ├── Other1.xml
│   ├── Other2.xml
│   ├── RoomProperty.xml
│   ├── ad.xml
│   ├── allow_ext.xml
│   ├── book.mdf
│   ├── book.sql //安装的默认数据
│   ├── email.xml
│   ├── keep_db.txt
│   └── tool.xml
├── Book //
│   ├── AddBook.aspx
│   ├── Book.master
│   ├── ChangeArea.aspx
│   ├── CurrentMeeting.aspx
│   ├── DailyView.aspx
│   ├── Default.aspx
│   ├── Delete.aspx
│   ├── Details.aspx
│   ├── Editbook.aspx
│   ├── MyBook.aspx
│   ├── Pad.aspx
│   ├── Pop_Pc.aspx
│   ├── Pop_Projector.aspx
│   ├── Pop_Room.aspx
│   ├── Pop_Users.aspx
│   ├── Search.aspx
│   ├── WeeklyView.aspx
│   ├── images
│   ├── json_dept.aspx
│   ├── json_users.aspx
│   ├── quickbook.aspx
│   └── style
├── Default.aspx
├── GetPassword.aspx
├── Login.aspx
├── Logout.aspx
├── MyProfile.aspx
├── Register.aspx
├── Report
│   ├── DataAnalyze.aspx
│   ├── Export.aspx
│   ├── Export_Data.aspx
│   ├── Month_Times.aspx
│   ├── Rating.aspx
│   ├── Report.Master
│   ├── Timing.aspx
│   ├── Trending.aspx
│   ├── UsingRating.aspx
│   ├── ajax
│   └── default.aspx
├── SimlpeUploadFile.aspx
├── SystemConfig.exe
├── Template //模版文件
│   ├── cancel_temlate.htm
│   ├── memberTemplate.xls
│   └── new_temlate.htm
├── UserControls //
│   ├── Area.ascx //模版控件 嵌入aspx里面使用
│   ├── BookList.ascx
│   └── Rpt.ascx
├── Wap //wap版文件
│   ├── GetAjaxBookList.aspx
│   ├── GetAjaxCurrentBook.aspx
│   ├── GetAllRoom.aspx
│   ├── GetRoom.aspx
│   ├── Room1.aspx
│   ├── Room2.aspx
│   ├── Room3.aspx
│   ├── clock
│   ├── date.js
│   ├── date2.js
│   └── images
├── Web.config
├── bin // 程序集
│   ├── Book.dll //核心代码程序集
│   ├── Book.dll.config
│   ├── DNHelper.dll //数据库组件 主要是封装了一些数据库操作
│   ├── DayPilot.dll //Daypilot js 控件 主要是画图用的
│   ├── DayPilot.xml
│   ├── EeekSoft.Web.PopupWin.dll //弹出窗口的控件类
│   ├── ICSharpCode.SharpZipLib.dll //压缩文件的控件
│   ├── Interop.jmail.dll //收发邮件的组件
│   ├── MoverControl.dll // 用来操作前端的组件类
│   ├── NPOI.OOXML.dll //NPOI.Util. 基础类库,用于其他读写文件格式项目的开发
│   ├── NPOI.OpenXml4Net.dll //同上属于 NPOI
│   ├── NPOI.OpenXmlFormats.dll //同上属于 NPOI
│   ├── NPOI.dll //同上属于 NPOI
│   └── xListBox.dll
├── bootstrap //静态框架
├── chagepwd.aspx
├── dbback
├── deny.aspx
├── images //静态图片
├── install //安装目录
│   ├── Tool.aspx
│   ├── default.aspx
│   ├── lock.txt
│   ├── reset.txt
│   └── test_right.txt
├── javascript //静态文件
├── m //手机版文件
│   ├── AddBook.aspx
│   ├── Default.aspx
│   ├── Details.aspx
│   ├── Login.aspx
│   ├── MyMeeting.aspx
│   ├── MyProfile.aspx
│   ├── Mybook.aspx
│   ├── Search.aspx
│   ├── SearchStatus.aspx
│   ├── ajax
│   ├── ajax_get_available_room.aspx
│   ├── css
│   ├── del.aspx
│   ├── fonts
│   ├── images
│   ├── js
│   └── m.Master
├── readme.txt //说明文档
├── style //静态文件
├── temp
├── uploads //上传目录
│   ├── 2014-07
│   ├── 2014-08
│   └── Thumbs.db
└── webserver.exe //快速安装提供iis环境

其实上面有很多和我们代码审计没有很大关系,可以重点看我注释部分,明白重点是book.dll

 

0x7 程序的组成

通过查看其中的一个aspx文件,ex C:\Users\xq17\Desktop\book30.0\book\m\Search.aspx

<%@ Page Title="" Language="C#" MasterPageFile="~/m/m.Master" AutoEventWireup="true" CodeBehind="Search.aspx.cs" Inherits="Book.m.Search" %>

我们可以得到这个cms是一个web应用程序而不是一个web网站

并且CodeBehind(后端代码)源文件是Search.aspx.cs,继承于Book.m.Search类

直接拖进dnsSpy可以看到程序的一些设置信息(版本、名称等)

 

0x8 小试身手之演示挖掘注入过程

把核心的book.dll丢进dnSpy里面

可以看到反编译出了很多类,for example:

首先前面我们已经知道命名空间的作用就是防止类冲突,所以这里反编译对应的结果就是

代表在Book.m命名空间中存在着

这么多种操作类。

这里我们读下Book/m 下search类 , code as

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using DBHelper;

namespace Book.m
{
    // Token: 0x0200003E RID: 62
    public class Search : Page
    {
       // Token: 0x060000F4 RID: 244 RVA: 0x0000CC18 File Offset: 0x0000AE18
       protected void Page_Load(object sender, EventArgs e)
       {
           if (!this.Page.IsPostBack)
           {
              this.Bind();
           }
       }

       // Token: 0x060000F5 RID: 245 RVA: 0x0000CC2D File Offset: 0x0000AE2D
       protected void Button3_Click(object sender, EventArgs e)
       {
           this.Bind();
       }

       // Token: 0x060000F6 RID: 246 RVA: 0x0000CC35 File Offset: 0x0000AE35
       protected void lv_lists_PagePropertiesChanging(object sender, PagePropertiesChangingEventArgs e)
       {
           this.DataPager1.SetPageProperties(e.StartRowIndex, e.MaximumRows, false);
           this.Bind();
       }

       // Token: 0x060000F7 RID: 247 RVA: 0x0000CC58 File Offset: 0x0000AE58
       public void Bind()
       {
           string text = "   select id, \r\n        begintime, \r\n        endtime, \r\n       roomname , \r\n       numbers, \r\n      contactname,\r\n       intro  from book_booklist   \r\n            \r\n            where    process in (1,3,5)  ";
           if (this.txt_contactname.Text != "")
           {
              if (this.txt_contactname.Text.Length > 20)
              {
                  this.txt_contactname.Text = this.txt_contactname.Text.Substring(0, 19);
              }
              text = text + " and contactanme like '" + this.txt_contactname.Text + "' ";
           }
           if (this.rbl_date.SelectedItem.Value == "today")
           {
              object obj = text;
              text = string.Concat(new object[]
              {
                  obj,
                  " and  begintime>='",
                  DateTime.Today,
                  "'"
              });
              object obj2 = text;
              text = string.Concat(new object[]
              {
                  obj2,
                  " and  begintime< '",
                  DateTime.Today.AddDays(1.0),
                  "'"
              });
           }
           if (this.rbl_date.SelectedItem.Value == "tomorrow")
           {
              object obj3 = text;
              text = string.Concat(new object[]
              {
                  obj3,
                  " and  begintime>='",
                  DateTime.Today.AddDays(1.0),
                  "'"
              });
              object obj4 = text;
              text = string.Concat(new object[]
              {
                  obj4,
                  " and  begintime< '",
                  DateTime.Today.AddDays(2.0),
                  "'"
              });
           }
           if (this.rbl_date.SelectedItem.Value == "week")
           {
              object obj5 = text;
              text = string.Concat(new object[]
              {
                  obj5,
                  " and  begintime>='",
                  DateTime.Today,
                  "'"
              });
              object obj6 = text;
              text = string.Concat(new object[]
              {
                  obj6,
                  " and  begintime< '",
                  DateTime.Today.AddDays(7.0),
                  "'"
              });
           }
           if (this.txt_key.Text != "")
           {
              if (this.txt_key.Text.Length > 20)
              {
                  this.txt_key.Text = this.txt_key.Text.Substring(0, 19);
              }
              text = text + " and roomname+intro like '%" + this.txt_key.Text + " %' ";
           }
           text += "    order by id desc ";
           text = text.ToLower().Replace("update", "").Replace("insert", "").Replace("delete", "").Replace("select", "");
           this.lv_lists.DataSource = Instance.ExeDataSet("select " + text);
           this.lv_lists.DataBind();
       }

       // Token: 0x04000181 RID: 385
       protected TextBox txt_contactname; //这里定义了几个私有属性

       // Token: 0x04000182 RID: 386
       protected RadioButtonList rbl_date;

       // Token: 0x04000183 RID: 387
       protected TextBox txt_key;

       // Token: 0x04000184 RID: 388
       protected Button Button3;

       // Token: 0x04000185 RID: 389
       protected ListView lv_lists;

       // Token: 0x04000186 RID: 390
       protected DataPager DataPager1;
    }
}

这里我们需要重点理解两个方法(Page_Load):

       protected void Page_Load(object sender, EventArgs e) //这个会在加载search.aspx的时候自动触发
       {
           if (!this.Page.IsPostBack) //第一次加载时触发 一般用来初始化些设置
           {
              this.Bind();
           }
       }
       protected void Button3_Click(object sender, EventArgs e) //当点击控件时就会触发
       {
           this.Bind();
       }

对应的我们在页面

http://10.211.55.20:8083/m/search.aspx

点击的搜索的时候,就会触发Button3_Click方法,然后执行当前类下的Bind方法,我们跟进

       public void Bind()
       {
           string text = "   select id, \r\n        begintime, \r\n        endtime, \r\n       roomname , \r\n       numbers, \r\n      contactname,\r\n       intro  from book_booklist   \r\n            \r\n            where    process in (1,3,5)  ";
           if (this.txt_contactname.Text != "")
           {
              if (this.txt_contactname.Text.Length > 20)
              {
                  this.txt_contactname.Text = this.txt_contactname.Text.Substring(0, 19);
              }
              text = text + " and contactanme like '" + this.txt_contactname.Text + "' ";
           }
           if (this.rbl_date.SelectedItem.Value == "today")
           {
              object obj = text;
              text = string.Concat(new object[]
              {
                  obj,
                  " and  begintime>='",
                  DateTime.Today,
                  "'"
              });
              object obj2 = text;
              text = string.Concat(new object[]
              {
                  obj2,
                  " and  begintime< '",
                  DateTime.Today.AddDays(1.0),
                  "'"
              });
           }
           if (this.rbl_date.SelectedItem.Value == "tomorrow")
           {
              object obj3 = text;
              text = string.Concat(new object[]
              {
                  obj3,
                  " and  begintime>='",
                  DateTime.Today.AddDays(1.0),
                  "'"
              });
              object obj4 = text;
              text = string.Concat(new object[]
              {
                  obj4,
                  " and  begintime< '",
                  DateTime.Today.AddDays(2.0),
                  "'"
              });
           }
           if (this.rbl_date.SelectedItem.Value == "week")
           {
              object obj5 = text;
              text = string.Concat(new object[]
              {
                  obj5,
                  " and  begintime>='",
                  DateTime.Today,
                  "'"
              });
              object obj6 = text;
              text = string.Concat(new object[]
              {
                  obj6,
                  " and  begintime< '",
                  DateTime.Today.AddDays(7.0),
                  "'"
              });
           }
           if (this.txt_key.Text != "")
           {
              if (this.txt_key.Text.Length > 20)
              {
                  this.txt_key.Text = this.txt_key.Text.Substring(0, 19);
              }
              text = text + " and roomname+intro like '%" + this.txt_key.Text + " %' ";
           }
           text += "    order by id desc ";
           text = text.ToLower().Replace("update", "").Replace("insert", "").Replace("delete", "").Replace("select", "");
           this.lv_lists.DataSource = Instance.ExeDataSet("select " + text);
           this.lv_lists.DataBind();
       }

this.txt_contactname.Text 这个在类中有定义protected TextBox txt_contactname;

而TextBox类型在System.Web.UI.WebControls中被定义,跟进下代码或者读下official document

我们可以知道this.txt_contactname.Text是获取this.txt_contactname的值也就是对应控件

中生成的input中的value的值

可以看到中间没有进行了其他过滤(有时候要考虑全局过滤)

asp.net 的全局过滤一般有两种:

1.编写Global.asax

2.filter httpcontext上下文实例

通过阅读web.config这一全局配置可以知道这个cms没有全局过滤。

很显然,代码在

text = text + " and contactanme like '" + this.txt_contactname.Text + "' "; //拼接了可控变量
           if (this.txt_key.Text != "")
           {
              if (this.txt_key.Text.Length > 20)
              {
                  this.txt_key.Text = this.txt_key.Text.Substring(0, 19); //拼接了可控变量,但是做了长度截断处理
              }
              text = text + " and roomname+intro like '%" + this.txt_key.Text + " %' ";
           }

最后程序:

this.lv_lists.DataSource = Instance.ExeDataSet(“select ” + text); //进行数据库查询操作,结果集作为显示数据源
this.lv_lists.DataBind();//绑定显示控件,返回结果

我们可以尝试跟进下Instance.ExeDataSet这个方法

namespace DBHelper
{
    // Token: 0x02000016 RID: 22
    public class Instance
    {
       // Token: 0x06000115 RID: 277 RVA: 0x00007F80 File Offset: 0x00006180
       public static DataSet ExeDataSet(string strSql)
       {
         strSql = Instance.checkSql(strSql); //这里进行了check,默认是没有操作,可以重写来escape。
           SqlConnection connection = new SqlConnection(Instance._con);
           SqlCommand sqlCommand = new SqlCommand(strSql);
           sqlCommand.Connection = connection;
           DataSet result;
           using (SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(sqlCommand)) //这里执行了sql操作
           {
              DataSet dataSet = new DataSet();
              sqlDataAdapter.Fill(dataSet); //结果数据用来填充dataSet对象
              sqlCommand.Parameters.Clear();
              result = dataSet; 
           }
           return result;//返回了sql的结果集合
       }

所以说这里就是一个很明显的注入啦,但是限制了长度为20,没有办法随心所欲的注入。

但是可以获取下数据库名,这里用burp演示下。

 

0x9 开启0day之旅之前台SQL注入

上面那个search.aspx在旧版本应该没有限制长度,后面这个V3.0最新版应该修复了,不过还是可以获取到数据库等信息。虽然这个点不能够完美注入,但是因为这个系统没做cms全局过滤,除开那些做了参数化查询的点,很容易找到其他注入点,让我们一起来挖掘吧。

分析下前台登陆验证的流程:

由web.config中<authentication mode=”Forms”/>身份验证采取的是form认证。

if (FormsAuthentication._FormsName == null)
                  {
                     FormsAuthentication._FormsName = ".ASPXAUTH";
                  }

cookie字段默认就是.ASPXAUTH

    public static int Login(string username, string password)
    {
       string sql = " select uid  from users_users where username=@username and password=@password;   ";
       SqlParameter[] prams = new SqlParameter[]
       {
           new SqlParameter("@username", username),
           new SqlParameter("@password", Helper.Encrypt(password))//参数化查询
       };
       object obj = Instance.ExeScalar(sql, prams);
       if (obj == null || obj == DBNull.Value)
       {
           return -1;
       }
       int num = int.Parse(obj.ToString());
       if (num > 0)
       {
           UsersHelper.Login(num);
       }
       return num;
    }

所以登陆处没办法注入,登陆成功后设置cookie

public static void Login(int i)
    {
       string path = HttpContext.Current.Request.ApplicationPath;
       if (Helper.GetWebconfig("apppath") != "")
       {
           path = Helper.GetWebconfig("apppath");
       }
       DataTable userInfoById = UsersHelper.GetUserInfoById(i);
       string userRolesString = UsersHelper.GetUserRolesString(i);
       DataRow dataRow = userInfoById.Rows[0];
       HttpContext.Current.Response.Cookies["userinfo"]["userid"] = HttpContext.Current.Server.UrlEncode(i.ToString());
       HttpContext.Current.Response.Cookies["userinfo"]["username"] = HttpContext.Current.Server.UrlEncode(dataRow["username"].ToString());
      HttpContext.Current.Response.Cookies["userinfo"]["avtor"] = HttpContext.Current.Server.UrlEncode(dataRow["avtor"].ToString());
       HttpContext.Current.Response.Cookies["userinfo"]["deptname"] = HttpContext.Current.Server.UrlEncode(dataRow["Department"].ToString());
       HttpContext.Current.Response.Cookies["userinfo"]["displayname"] = HttpContext.Current.Server.UrlEncode(dataRow["displayname"].ToString());
      HttpContext.Current.Response.Cookies["userinfo"]["roles"] = HttpContext.Current.Server.UrlEncode(userRolesString);
       HttpContext.Current.Response.Cookies["userinfo"]["password"] = "";
       HttpContext.Current.Response.Cookies["userinfo"].Expires = DateTime.Now.AddDays((double)Helper.CookieDay);
       HttpContext.Current.Response.Cookies["userinfo"].Path = path;
       FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, i.ToString(), DateTime.Now, DateTime.Now.AddDays((double)Helper.CookieDay), true, userRolesString);
       string value = FormsAuthentication.Encrypt(ticket); 
       HttpCookie httpCookie = new HttpCookie(FormsAuthentication.FormsCookieName, value);//FormsAuthentication.FormsCookieName=.ASPXAUTH
       httpCookie.Path = path;
       httpCookie.Expires = DateTime.Now.AddDays((double)Helper.CookieDay);
       HttpContext.Current.Response.Cookies.Add(httpCookie); //设置cookie
    }

所以说关键的验证是:

FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, i.ToString(), DateTime.Now, DateTime.Now.AddDays((double)Helper.CookieDay), true, userRolesString);

加密过程依赖MachineKey,不同服务器这个值不同,所以没办法伪造cookie。

这个系统是分为两部分的,一个是用户、一个是管理员

在v1.7的时候book/Search.aspx不需要登陆就可以访问(web.config可以设置指定页面所有人可以访问),但是v3.0这个版本至少需要一个用户账号。

v3.0 的web.config就配置了

getpassword.aspx  chagepwd.aspx m/login.aspx是所有用户都可以访问,其他的话就需要权限了

  <location path="m/login.aspx">
    <system.web>
      <authorization>
        <allow users="*"/> //这个就是所有用户都可以访问
      </authorization>
    </system.web>
  </location>
  .........
    <system.web>

    <authorization>
      <deny users="?"/> //禁止匿名用户
    </authorization>

这个系统默认安装会有(当然也可以爆破,没有验证码):

kevin 普通用户 123456
dream 普通用户 123456
admin 管理员 123456

其实这个系统应该挺多注入的,但是我的初心还是为了学习asp.net代码审计,所以这里只分析一个有趣的点。

url:http://10.211.55.20:8083/m/MyMeeting.aspx

我们跟进代码看下:

这里可以很明显看出:

string strSql = string.Concat(new object[]{"123",this.username,});//通过string的Concat方法拼接了this.username

然后在

           DataSet dataSet = Instance.ExeDataSet(strSql); //这里进行了sql查询
           this.lv_lists.DataSource = dataSet.Tables[0];
           this.lv_lists.DataBind();
           this.lv_lists2.DataSource = dataSet.Tables[1];
           this.lv_lists2.DataBind();

如果我们可以控制this.username那么就可以注入了

           this.uid = UserHelper.GetUserId;
           this.username = UserHelper.GetLoginName; //我们选择跟进GetLoginName

UserHelper.GetLoginName

public static string GetLoginName
    {
       get
       {
           if (Helper.IsUseAd && HttpContext.Current.Request.Cookies["userinfo"] == null)//Cookie可控可以绕过
           {
              UsersHelper.LoginAd(UserHelper.GetSamaccountName());
           }
           if (HttpContext.Current.Request.Cookies["userinfo"] != null) //进入这里
           {
              return HttpContext.Current.Server.UrlDecode(HttpContext.Current.Request.Cookies["userinfo"]["username"]); //这里返回了Cookie作为this.username
           }
           return "";
       }
    }

这里基本无限制注入,获取管理员密码

Cookie: userinfo=userid=1&username=admin' and (select top 1 password from users_users where username='admin')>0--+;

其实就是md5加密,只是做了分割而已,这个跟进代码看看就知道了。

这个注入有个专属名字叫做cookie注入,在防护还有绕过waf上,是非常经典的漏洞。

 

0x0A SQL注入修补方案

建议全部上参数化查询

 

0x0B 总结

感觉自己对于asp.net审计只是很浅的入门而已,还需要在开发和审计代码中不断提高自己,目前在着手写最近在护网过程中遇到一个asp系统叫做新云内容管理系统v3.0,虽然没有啥漏洞,但是可以记录下自己审计asp程序的思路和突破点(asp程序读起来相当简单,也很直观,只是代码比较乱)。后面几天,我会继续完善关于asp.net程序其他漏洞的审计思路,并且都通过实战挖掘0day的方式去审计,由于自己比较菜,欢迎师傅们多多指点。

 

0x0C 参考链接

Asp.Net网站的的编译与发布原理

ASP.NET–网站配置、发布与部署

某系统.NET代码审计

【ASP.NET代码审计】逐浪CMS(ZOOMLA!CMS)漏洞挖掘

漏洞银行-通过asp.net代码审计学习owasp

ASP.NET MVC 5 – 开始MVC5之旅

浅谈ASP.NET框架

深入剖析ASP.NET的编译原理

Asp.net 反编译及解密分析

Visual Studio 中 Web 应用程序和网站区别

20分钟读懂程序集

CLR查找和加载程序集的方式(一)

ASP.NET入门(1) – 建立和开发ASP.NET 5 项目

简析.NET Core 以及与 .NET Framework的关系

浅谈 .NET Framework 与 .NET Core 的区别与联系

探索基于.NET下实现一句话木马之ashx篇

ASP.NET Web应用程序和ASP.NET网站的区别

asp.net core mvc视频开发教程

C#Asp.net 基础入门到进阶实战培训视频教程

dnSpy对目标程序(EXE或DLL)进行反编译修改并编译运行

ASP.Net页面请求处理流程

ASP.NET 请求处理流程

dbhelper.org官方SqlHelper中文文档

ASP.NET/MasterPageFile

【实战学习c#】为程序设置版本和帮助信息

浅谈C#.NET防止SQL注入式攻击

一篇文章了解MSSQL之注入指北

经典FormsAuthenticationTicket 分析

细说ASP.NET Forms身份认证

(完)