写这篇文章的缘由其实还挺魔幻的,起因是在一次实战渗透时通过弱口令拿下一个低权限用户成功进入后台,在后台寻找功能点通过抓包分析,定位到目标系统后台存在SQL注入,通过os shell拿下内网之后闲着无聊就谷歌了下,发现这个系统的开发商是某某公司,同时cnvd也没有收录该产品,于是想着能不能捡漏搞个cnvd证书。
碍于信息检索能力太差,只收集到屈指可数的几个url,而且这几个系统都没有弱口令可以进入后台,因为进不了后台,就猜测后台的功能也都无法使用,漏洞无法复现,于是在知道肯定过不了的情况下还是硬着头皮只交了几个url上去(没记错通用漏洞需要至少3个以上验证成功漏洞案例),结果果不其然,三审的时候给我驳回了。
不甘心,案例找不出来,我把代码审计一遍还不行吗?于是就通过webshell打包了一份代码(因为是.net的站,就只打包了bin包下来),于是便有了这篇文章。
还是先简单聊聊sql注入如何拿下内网的吧(以前的一次实战,没有截图,脑补一下,见谅)。
漏洞点抓包。
尝试直接sqlmap拿下—os-shell,苦于没有绝对路径,就尝试百度看看还有没有其他漏洞,然而网上资料几乎没有,就在要放弃的时候从百度文库中找到一线希望,找到了该系统的使用说明,从说明书中得知系统的mysql数据库安装路径和web路径在同一目录下,于是通过—sql-shell使用。
select @@datadir
获取到mysql的安装目录,同时也就获得了web目录,最后就可以直接—os-shell了。
拿到os-shell之后先tasklist,没有杀软,不需要免杀;ping了下发现服务器出网,基础操作certutil下载msf马上线,先用msf上传一个web shell到网站目录(感觉拿到web shell后要放心点)。看了下权限,system,不用提权了;看了下systeminfo,08的机器,load kiwi模块,读取明文密码;netstat看下3389端口,没开启,用注册表开启。msf起socks代理,mstsc远程连接之。net view看了下没有域,上传fscan进行内网资产扫描,同时在桌面看到WinScp软件,而且其中还保存着好几台内网服务器,都是root权限,试了试都能连接上。这里我下了个星号密码查看器传上去,想获取它们的明文密码,结果失败了。谷歌了下,发现WinScp配置默认加密保存在注册表中,可以修改保存方式为ini文件并用工具破解其密码,于是修改之后dump到本地通过工具get到密码。
另一边fscan扫到了两台服务器的弱密码,还有几台有redis未授权漏洞,都可以写私钥登录。
此外,从sql备份文件中又找到另外平台的账号密码。
从webshell的文件管理处定位到漏洞文件Default.ashx,可以看到调用了UserInfo.Default这个类。
在bin包中找到对应dll文件,使用dnSpy反编译得到源码,开始审计。
结构如下:
首先全局搜索一下session关键字,没有发现。再在所有外部引用中搜索session关键字,还是没有发现,是个好兆头,说明系统可能没有对session进行验证。
代码第20行,定义ProcessRequest方法并将http请求体作为该方法的参数传入,并在第22行定义httpCookie变量存储当前cookie中键名为”WCMS.User”的数据,可以看到在代码第23行,程序只进行了三种判断,cookie不为空,cookie中UserID不为空且RoleID也不为空。
只要满足上述三个条件,程序就会继续处理请求,否则才返回204代码报错。
这里由于身份校验不严,导致攻击者可以在没有后台管理员权限的情况下也能执行相应操作。审计到这里我兴奋起来了,因为之前担心系统会对session进行判断就没有对另外几个站点进行复现,导致cnvd提交被驳回,然而现在完全不需要担心了,因为系统根本就没有对session进行验证,只需要修改http请求中的host参数就可以实现漏洞的批量利用。
但是审计到这里还没有结束,我们继续对sql注入漏洞的成因进行分析。
在代码第32行,对action参数进行判断,我们根据payload中的Read值,跟进到GetData()函数。
从代码第190行,不难看出该函数并未对参数进行过滤,只进行了是否为空的判断。
在代码第197行程序还进行了RoleInfoID的校验,担心这里可能会要求提供服务器中存在的id导致身份鉴权失败,我们着重分析下这里。
定义一个text变量接收结果,如果在http form表单中不存在RoleInfoID,就调用Lib.CommonFunction类中的GetRoleID()方法进行获取,我们跟进后发现程序仍然只判断了cookie是否存在,只有当cookie不存在时才会返回为空,导致代码第198行判断为假进而导致api返回为空。
如果cookie存在,会调用DESDec()函数将cookie中RoleID的值进行DES解密,并将结果保存以待后用。
这里我根据源码用c#重写了下解密过程,并将payload中的RoleID用于解密,结果最后是乱码(可能是我代码没写对的锅…)。
然后我用在线的DES工具解密出来又是正确的…(蚌埠住了,我写的代码就是屎呜呜呜)。
然后我们再回到GetData()函数,代码第186行通过Lib.Factory类中的CreateUserInfo()函数创建了DBHelper对象,并在第205行调用了GetItems()函数。
通过分析发现IDBHelper接口由Mysql.DBHelper类实现,跟进到DBHelper类的GetItems()函数进行分析。
代码第302行,使用for循环遍历之前text变量中的值,构造sql语句,使用where in语法对查询结果进行限定。同时,我们注意到,搜索关键字keyWords到目前为止仍未进行任何的过滤,而且后续通过分析,发现开发者也未在全局使用预处理和参数化,导致keyWords参数易于受到攻击。
综上,虽然RoleID会用于获取子账号ID,然而如果数据库中不存在该RoleID的用户也没有关系,因为我们的攻击方式是基于时间的盲注,即使数据库查询返回为空,也不妨碍我们通过时间比较进行攻击。因此可以下定结论,该漏洞在未经授权就可被利用。
整个流程如下图所示:
细心细心细心!