基础知识:
ASP.NET开发可以选用两种框架:ASP.NET Core
与ASP.NET Framework
ASP.NET开发也分为两种:
WebApplication:
WEB应用程序,改变代码后需要重启网页。具有namespace空间名称,项目中所有的程序代码文件,和独立的文件都被编译成为一个程序集,保存在bin文件夹中。
WebSite:
WEB网站,改变代码后不用重启网页。它没用到namespace空间名称,每个asp页面会转成一个dll。
ASP.NET比较关键的文件:
web.config:
1.web.config是基于XML的文件,可以保存到Web应用程序中的任何目录中,用来储存数据库连接字符、身份安全验证等。
2.加载方式:当前目录搜索 -> 上一级到根目录 -> %windir%/Microsoft.NET/Framework/v2.0.50727/CONFIG/web.config -> %windir%/Microsoft.NET/Framework/v2.0.50727/CONFIG/machine.config -> 都不存在返回null
Global.asax:
1. Global.asax提供全局可用的代码,从HttpApplication基类派生的类,响应的是应用程序级别会话级别事件,通常ASP.NET的全局过滤代码就是在这里面。
ASP.NET的常见拓展名:
在%windir%\Microsoft.NET\Framework\v2.0.50727\CONFIG\web.config
中有详细定义,这里提取部分简单介绍。
```
aspx:应用程序根目录或子目录,包含web控件与其他
cs:类文件
aspx.cs:web窗体后台程序代码文件
ascx:应用程序根目录或子目录,Web 用户控件文件。
asmx:应用程序根目录或子目录,该文件包含通过 SOAP 方式可用于其他 Web 应用程序的类和方法。
asax:应用程序根目录,通常是Global.asax
config:应用程序根目录或子目录,通常是web.config
ashx:应用程序根目录或子目录,该文件包含实现 IHttpHandler 接口以处理所有传入请求的代码。
soap:应用程序根目录或子目录。soap拓展文件
```
3.某系统
4.dnSpy反编译
程序的文件目录
├─Admin
├─App_Data //App_Data文件夹应该包含应用程序的本地数据存储
├─bin // 包含应用程序所需的任何预生成的程序集
├─bootstrap
├─css
├─images
├─img
├─install
├─javascript
├─m
├─purchase
├─style
├─temp
├─Template
├─uploads
└─UserControl
WEB应用程序会把我们写的代码编译为DLL文件存放在Bin文件夹中,在ASPX文中基本就是一些控件名,所以需要反编译他的DLL来进行审计。
Logout.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Logout.aspx.cs" Inherits="Book.Logout" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
.
.
.
</html>
在文件头中有这几个参数:
1.Language="C#" //脚本语言
2.AutoEventWireup="true" //是否自动关联某些特殊事件
3.CodeBehind="Logout.aspx.cs" //指定包含与页关联的类的已编译文件的名称
4.Inherits="Book.Logout" //定义供页继承的代码隐藏类
我们所关注的也就是Inherits
的值,如上所示他指向了Bin
目录下的purchase.dll
中Book
类的Logout
函数(注:purchase.dll是网站编译的项目名,一般与文件目录对应)
web.config
这个文件包含了目录权限控制、数据库密码等等
<location path="purchase/orderdetail.aspx">
<system.web>
<authorization>
<allow users="*"/>
</authorization>
</system.web>
</location>
<authentication mode="Forms" />
比如我们使用的这套程序中authorization定义了purchase/orderdetail.aspx
匿名可以访问,但是这套程序的本页面还写了一套验证
if (this.uid <= 0)
{
if (!(base.Request.QueryString["g"] == "p"))
{
base.Response.Redirect("../login.aspx");
return;
}
this.ph_pdf.Visible = false;
}
所以我们只需要访问purchase/orderdetail.aspx?g=p
即可绕过跳转
,其中<authentication mode="Forms" />
表示Form 表单认证。
在ASP.NET中全局过滤一般用到Global.asax
至于他为什么可以起到全局过滤的作用可以看看ASP.NET三剑客。当然这套程序并没有全局过滤,在提交多个漏洞后,官网公告说这套程序建议内网部署,官网让公网部署的用户设置了身份验证:
<system.web>
<authorization>
<deny users= "?"/>
</authorization>
</system.web>
但是这套程序安装会默认插入多条用户数据。
首先我们来看Login.aspx
,前面已经贴过代码,我们需要反编译purchase.dll
去找Book.Login
,这里使用dyspy
login.aspx
->Button1_Click
->LoginForm()
在login中控件名对应dll
public void LoginForm()
{
int num = UsersHelper.Login(this.txt_username.Text, this.txt_pwd.Text);
if (num > 0)
{
base.Response.Redirect(FormsAuthentication.GetRedirectUrl(num.ToString(), true));
}
else
{
Helper.Result(this, "用户名或者密码错误");
}
}
跟进UsersHelper.Login
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;
}
这里使用的是参数化查询,所以这里不存在注入。登陆后的注入很多这里选一个。
search.aspx
这里剔除了部分无用代码,可以看到没有经过任何过滤,控件的值就拼接到text
字符串,由Instance.ExeDataSet(text)
执行,跟进ExeDataSet
函数
没有过滤直接带入查询,如果你觉得从代码来看sql语句很麻烦,这里可以使用Sql Sever Profiler
监控SQL语句。
Payload:
1%' and 1=user--
前面说到purchase/orderdetail.aspx?g=p
可以绕过直接访问,具体原因可以移步《第二章:越权》,那么我们看看这个是否存在注入,如果存在那么将是一个前台注入。
看到69-88
行,要执行命令需要this.isview
为true
,在30-36
行赋值只需要t=view
即可
sid
没有经过任何过滤,同时ExeDataSet
函数也不存在过滤,即存在注入。
Payload:
purchase/orderdetail.aspx?g=p&t=view&sid=1%20and%201=user--
1.在web.config
中有四种验证模式:
方式 | 描述 |
---|---|
window | IIS验证,在内联网环境中非常有用 |
Passport | 微软集中式身份验证,一次登录便可访问所有成员站点,需要收费 |
Form | 窗体验证,验证帐号/密码,Web编程最佳最流行的验证方式 |
None | 表示ASP.NET自己根本不执行身份验证,完全依赖IIS身份验证 |
其中FORM
窗体验证的流程图:
开启form
窗体验证的同时还需要配置web.config
,不然就会出现问题,一般来说还需要配置最基本的页面访问权限,比如禁止匿名用户访问。
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
当然还可以设置一些管理页面允许某某用户访问等等,在这套程序中开启了form
然后在程序里面验证的cookies
,而且并没有设置所有页面的authorization
。
2.除去web.config
的配置通常还有两种写法来验证是否登陆。
第一种:在每个页面判断Session["UserName"]是否等于null
第二种:类似php的include
的继承,这也是本套程序使用的方法。
首先他定义了一个purchase.Master
母版页 在里面写上了权限验证的代码。
然后次母版页头文件会引入MasterPageFile="~/purchase/purchase.Master"
调用之前都会先调用母版页的Page_Load
函数来验证是否登陆。当然你也可能遇到没有使用母版页的程序,那么他可能是先定义一个onepage
类继承page
,然后其他页面继承onepage
类,与此相同。
例1:
比如没有任何验证的,也没有继承验证类的,无需登陆访问
例2:
这套程序验证权限的地方比较少,只是简单的判断了是否登陆,登陆后基本可以访问大多数管理页面这里。
例3:
在MyProfile.aspx
文件中 直接获取表单数据进行update
,并没有验证权限,导致低权限账号也可以update admin
但是这里是参数化查询,所以不存在注入。修改admin
的账号密码为1234567
。
例4:
前面说到这套程序里面验证的cookies
,而且并没有设置所有页面的authorization
权限,所以我们能不能伪造cookie呢。
在23-26
行判断this.uid
的值来进行跳转,在16
行定义了他的值,跟进UserHelper.GetUserId
public static int GetUserId
{
get
{
if (Helper.IsUseAd && HttpContext.Current.Request.Cookies["userinfo"] == null)
{
UsersHelper.LoginAd(UserHelper.GetSamaccountName());
}
if (HttpContext.Current.Request.Cookies["userinfo"] != null)
{
return int.Parse(HttpContext.Current.Request.Cookies["userinfo"]["userid"]);
}
return -1;
}
}
this.uid
等于cookies
中获取的userinfo
的值,这一步可以伪造,接着我们看到30-33
这里他设置了管理员的布尔值,跟进RoleHelper.IsAdmin
public static bool IsAdmin
{
get
{
string name = "IsAdmin";
string admin = RoleHelper.Admin;
bool? flag = HttpContext.Current.Session[name] as bool?;
if (flag == null)
{
flag = new bool?(UserHelper.IsInAnyRoles(admin));
HttpContext.Current.Session[name] = flag;
}
return flag.Value;
}
}
前面从session
中获取,如果flag
为null
则从UserHelper.IsInAnyRoles(admin)
获取。
跟进IsInAnyRoles
可以看到只要我们传入的cookies
中roles
的等于传入的数组值就返回true
其中 public static string Admin = "administrators";
,所以构造cookies
:userinfo=userid=1&roles=administrators;
项目地址:https://github.com/aleenzz/.NET_study/tree/master/asp.net_bug
有问题还请大佬们指出QAQ