起因[#]
某次机缘巧合,领导给了一套国内某主流.net技术的BPM系统源码,要求审计,于是便开始了这次审计之旅。。。
BPM(业务流程管理系统),这套系统采用前后端分离架构,前端ExtJS,后端.net的一般处理程序+windows服务开放socket端口1580,一般处理程序与服务器本地1580端口通信。数据库Oracle/SQLServer等。
环境搭建好后,使用VS打开站点,使用附加到进程,选择IIS进程,开启调试,配合Reflector查看DLL封装。
场景[#]
看了没多久发现一个任意文件下载的漏洞,SQL注入,文件上传任意代码执行也存在,但是只有任意文件文件下载是不需要验证的,其他漏洞都需要验证身份后才能利用成功,如何才能获取一个身份扩大利用面呢?仔细查看登录逻辑代码并不存在sql注入,系统支持中文,数字,字母格式的用户名,弱口令也是不好搞的,况且也未发现敏感信息泄露。等等,不是还有任意文件下载吗?下载web.config后数据库直连不可以吗,还真不行,由于业务流程一般涉及到企业的核心,基本都部署在内网,通过NAT方式映射到公网访问,或者纯内网方式访问不对外,这类情况基本没有对外映射数据库端口的(主要看运气,后来发现这套程序的所有密钥都是相同的..未随机化处理,牛逼....)。
仔细观察登录后的身份信息:
Cookie: ys-dLogin.PositionId=xxx; ys-dLogin.UserId=xxxxxx; ASP.NET_SessionId=atn2xhww13wfclf3ycs1045l; UserDisplayName=xxxxxxx; .ASPXAUTH=DCD91077D0C3FAF548406C2E885A9E202C7D16C832E991807B7C4E8718B3D2760C01F50BC3007CAA1FE6E48AB84822F8AA27E47B8FCF0D6216321B4410E034A24F0090406B283354FCFABF9AF
使用这个cookie通过PostMan调用后端接口发现其真正具有身份验证功能的只有.ASPXAUTH这个值。那么这个值是否可以伪造?
答案是肯定的。web.config里定义了这个:
<machineKey validationKey="ABAA84D7EC4BB56D75D237CECFFB9628809BDB8BF9999CD64568A145BE59719F" decryptionKey="ABAA84D7EC4BB56D75D237CECFFB9628809BDB8BF9999CD64568A145BE59719F" validation="SHA1" decryption="AES" />
看到这里老.net开发肯定就明白是怎么回事了,是的,这套系统使用了微软FormsAuthentication类作为身份认证使用的。该类的实现方法在System.Web.dll,通过该认证,可以把用户Name 和部分用户数据存储在Cookie中,通过基本的条件设置可以,很简单的实现基本的身份角色认证。
FormsAuthenticationTicket类用于创建一个对象,该对象表示 forms 身份验证用于标识已经过身份验证的用户的身份验证票证。 Forms 身份验证票证的属性和值与存储在 cookie 或 URL 中的加密字符串进行转换。
FormsAuthentication类提供了一 Encrypt 种方法,用于创建一个字符串值,该字符串值可以存储在 cookie 中,也可以存储在 URL 中 FormsAuthenticationTicket 。 FormsAuthentication类还提供了一 Decrypt 种方法,用于 FormsAuthenticationTicket 根据从 forms 身份验证 cookie 或 URL 检索到的加密的身份验证票证来创建对象。
FormsAuthenticationTicket可使用类的属性访问当前经过身份验证的用户的 Ticket FormsIdentity 。 可以 FormsIdentity 通过 Identity 将当前的属性转换 User 为类型来访问当前的对象 FormsIdentity 。
生成Cookie的代码,微软官方的Demo:
private void Login_Click(Object sender, EventArgs e)
{
// Create a custom FormsAuthenticationTicket containing
// application specific data for the user.
string username = UserNameTextBox.Text;
string password = UserPassTextBox.Text;
bool isPersistent = false;
if (Membership.ValidateUser(username, password))
{
string userData = "ApplicationSpecific data for this user.";
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
username,
DateTime.Now,
DateTime.Now.AddMinutes(30),
isPersistent,
userData,
FormsAuthentication.FormsCookiePath);
// Encrypt the ticket.
string encTicket = FormsAuthentication.Encrypt(ticket);
// Create the cookie.
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));
// Redirect back to original URL.
Response.Redirect(FormsAuthentication.GetRedirectUrl(username, isPersistent));
}
else
{
Msg.Text = "Login failed. Please check your user name and password and try again.";
}
}
那么知道了怎么生成的,于是定位到代码的Login方法,关键逻辑如下:
string privateKey = (string)XXTempStorageManager.CurrentStore.Load(keystore);
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(1024);
rsaProvider.FromXmlString(privateKey);
uid = System.Text.Encoding.UTF8.GetString(rsaProvider.Decrypt(Convert.FromBase64String(uid), false));
pwd = System.Text.Encoding.UTF8.GetString(rsaProvider.Decrypt(Convert.FromBase64String(pwd), false));
}
if (BPMConnection.Authenticate(XXAuthHelper.BPMServerName, XXAuthHelper.BPMServerPort, uid, pwd, out realAccount, out token))
{
XXAuthHelper.SetAuthCookie(realAccount, token);
XXAuthHelper.ClearLogoutFlag();
rv[XXJsonProperty.success] = true;
rv["errorMessage"] = Resources.XXStrings.Aspx_Login_Success;
}
首先从上下文获取请求中的key,此key为页面Load时由后端动态生成的,然后分别获取加密后的用户名和密码,使用key进行解密,调用:
BPMConnection.Authenticate(XXAuthHelper.BPMServerName, XXAuthHelper.BPMServerPort, uid, pwd, out realAccount, out token),
此函数调用了后端的windows服务,逻辑复杂,命名及其不规范(个人水平太菜...),总之没找到生成规则,推测是和web.config定义的SecurityKey有关,这个函数返回一个token,该token并不产生变化,所以可以写死。
继续跟入:
XXAuthHelper.SetAuthCookie(realAccount, token);
这里就出现了熟悉的画面了:
public static void SetAuthCookie(string account, string token)
{
DateTime cookieIssuedDate = DateTime.Now;
var ticket = new FormsAuthenticationTicket(100,
account,
cookieIssuedDate,
cookieIssuedDate.AddMinutes(FormsAuthentication.Timeout.TotalMinutes),
false,
token,
FormsAuthentication.FormsCookiePath);
string cookieValue = FormsAuthentication.Encrypt(ticket);
HttpContext.Current.Response.Cookies.Remove(FormsAuthentication.FormsCookieName);
SetCookie(HttpContext.Current.Response, FormsAuthentication.FormsCookieName, cookieValue);
}
所以winform简单写个工具,把machineKey放入app.config中:
接下来就是Burpsuit一把梭,拦截响应头信息,替换cookie值,放包,刷新页面,成功伪造默认管理员用户登陆。
.net程序在某些只有任意文件下载的场景下,配合web.config的machineKey或许有新突破。