Function checkExtName(strExtName)
strExtName = lCase(strExtName) '转换为小写
strExtName = Replace(strExtName,"asp","") '替换asp为空
strExtName = Replace(strExtName,"asa","") '替换asa为空
checkExtName = strExtName
End Function
使用这种方式,程序员本意是将用户提交的文件的扩展名中的“危险字符”替换为空,从而达到安
全保存文件的目的。粗一看,按照这种方式,用户提交的asp文件因为其扩展名asp被替换为空,因
而无法保存,但是仔细想想,这种方法并不是完全安全的。
突破的方法很简单,只要我将原来的webshell的asp扩展名改为aaspsp就可以了,此扩展名经过
checkExtName函数处理后,将变为asp,即a和sp中间的asp三个字符被替换掉了,但是最终的扩展
名仍然是asp。
因此这种方法是不安全的。如何改进呢,请接着往下看。
第三、不足的黑名单过滤。
知道了上面的替换漏洞,你可能已经知道如何更进一步了,对了,那就是直接比对扩展名是否为
asp或者asa,这时你可能采用了下面的程序: 代码:Function checkExtName(strExtName)
strExtName = lCase(strExtName) '转换为小写
If strExtName = "asp" Then
checkExtName = False
Exit Function
ElseIf strExtName = "asa" Then
checkExtName = False
Exit Function
End If
checkExtName = True
End Function
你使用了这个程序来保证asp或者asa文件在检测时是非法的,这也称为黑名单过滤法,那么,这种
方法有什么缺点呢。
黑名单过滤法是一种被动防御方法,你只可以将你知道的危险的扩展名加以过滤,而实施上,你可
能不知道有某些类型的文件是危险的,就拿上面这段程序来说吧,你认为asp或者asa类型的文件可
以在服务器端被当作动态脚本执行,事实上,在windows2000版本的IIS中,默认也对cer文件开启
了动态脚本执行的处理,而如果此时你不知道,那么将会出现问题。
实际上,不只是被当作动态网页执行的文件类型有危险,被当作SSI处理的文件类型也有危险,例
如shtml、stm等,这种类型的文件可以通过在其代码中加入<!--#include file="conn.asp"-->语
句的方式,将你的数据库链接文件引入到当前的文件中,而此时通过浏览器访问这样的文件并查看
源代码,你的conn.asp文件源代码就泄露了,入侵者可以通过这个文件的内容找到你的数据库存放
路径或者数据库服务器的链接密码等信息,这也是非常危险的。
那么,如果你真的要把上面我所提到的文件都加入黑名单,就安全了吗,也不一定。现在很多服务
器都开启了对asp和php的双支持,那么,我是不是可以上传php版的webshell呢,所以说,黑名单
这种被动防御是不太好的,因此我建议你使用白名单的方法,改进上面的函数,例如你要上传图片
,那么就检测扩展名是否是bmp、jpg、jpeg、gif、png之一,如果不在这个白名单内,都算作非法
的扩展名,这样会安全很多。
第四、表单中传递文件保存目录。
上面的这些操作可以保证文件扩展名这里是绝对安全的,但是有很多程序,譬如早期的动网论坛,
将文件的保存路径以隐藏域的方式放在上传文件的表单当中(譬如用户头像上传到UserFace文件夹
中,那么就有一个名为filepath的隐藏域,值为userface),并且在上传时通过链接字符串的形式
生成文件的保存路径,这种方法也引发了漏洞。 代码:FormPath=Upload.form("filepath")
For Each formName in Upload.file ''列出所有上传了的文件
Set File=Upload.file(formName) ''生成一个文件对象
If file.filesize<10 Then
Response.Write "请先选择你要上传的图片 [ <a href=# onclick=history.go(-1)>重
新上传</a> ]"
Response.Write "</body></html>"
Response.End
End If
FileExt=LCase(file.FileExt)
If CheckFileExt(FileExt)=false then
Response.Write "文件格式不正确 [ <a href=# onclick=history.go(-1)>重新上传
</a> ]"
Response.Write "</body></html>"
Response.End
End If
Randomize
ranNum=Int(90000*rnd)+10000
FileName=FormPath&year(now)&month(now)&day(now)&hour(now)&minute(now)&second(now)
&ranNum&"."&FileExt
大家可以看出这段代码,首先获得表单中filepath的值,在最后将其拼接到文件的保存路径
FileName中。
在这里就会出现一个问题。
问题的成因是一个特殊的字符:chr(0),我们知道,二进制为0的字符实际上是字符串的终结标记
,那么,如果我们构造一个filepath,让其值为filename.asp■(这里是空字符,即终结标记),这
个时候会出现什么状况呢,FileName的值就变成了filename.asp,再进入下面的保存部分,所上传
的文件就以filename.asp文件名保存了,而无论其本身的扩展名是什么。
黑客通常通过修改数据包的方式来修改filepath,将其加入这个空字符,从而绕过了前面所有的限
制来上传可被执行的网页,这也是我们一般所指上传漏洞的原理。
那么,如何防护这个漏洞呢,很简单,尽量不在客户端指定文件的保存路径,如果一定要指定,那
么需要对这个变量进行过滤,如: 代码:FormPath = Replace(FormPath,chr(0),"")
第五、保存路径处理不当。
经过以上的层层改进,从表面上来说,我们的上传程序已经很安全了,事实上也是这样的,从2004
年的动网上传漏洞被指出后,其他程序纷纷改进上传模块,因此上传漏洞也消失了一段时间,但是
今年春节的时候,另种上传漏洞被黑客发掘了出来,即结合IIS6的文件名处理缺陷而导致的一个上
传漏洞。这里简单说一句,这个漏洞最早被发现与*易CMS系统。
在该系统中,用户上传的文件将被保存到其以用户名为名的文件夹中,上传部分做好了充分的过滤
,只可以上传图片类型的文件,那么,为什么还会出现漏洞呢。
IIS6在处理文件夹名称的时候有一个小问题,就是,如果文件夹名中包含.asp,那么该文件夹下的
所有文件都会被当作动态网页,经过ASP.dll的解析,那么此时,在*易系统中,我们首先注册一个
名为test.asp的用户名,而后上传一个webshell,在上传时将webshell的扩展名改为图片文件的扩
展名,如jpg,而后文件上传后将会保存为test.asp/20070101.jpg这样的文件,此时使用firefox
浏览器访问该文件(IE会将被解析的网页文件当作突破处理,因为其扩展名为图片),此时会发现我
们上传的“图片”又变成了webshell。
这个漏洞其实是十分有趣的,他不只是简单的asp漏洞,而是结合了IIS的一个缺陷,的确非常的隐
蔽。
当然,防御这个漏洞也是很简单的,如果没有必要,那么不要将突破按照用户名分目录保存,如果
一定要这样,那么需要检测用户名中是否有非法字符,例如.等。
详解两种主要漏洞
一、FilePath
说到FilePath,有些朋友可能会感到陌生;但要提到动网6.0的上传漏洞,大家一定都很熟悉吧?上传
漏洞本质上是由于FILEPath过滤而不严引起的。虽然现在动网已不存在此漏洞,但采用此上传源码
的程序还是非常多的。
<%
dim upload,file,formName,formPath,iCount,filename,fileExt '//定义上传变量
set upload=new upload_5xSoft '//建立上传对象
formPath=upload.form("filepath") '//第一步、获取文件路径,此处是关键。
if right(formPath,1)<>"/" then formPath=formPath&"/"
for each formName in upload.file '//用For读取上传文件
set file=upload.file(formName) '//生成一个文件对象
…………………… '//省略部分代码
fileExt=lcase(right(file.filename,4)) '//从文件名中截取后4位,并转换为小写字符。
if fileEXT<>".gif" and fileEXT<>".jpg" and fileEXT<>".zip" and fileEXT<>".rar" and
fileEXT<>".swf"then '//文件扩展名判断
response.write "<font size=2>文件格式不正确 [ <a href=# onclick=history.go(-1)>重新上
传</a> ]</font>"
response.end
end if
randomize
ranNum=int(90000*rnd)+10000
filename=formPath&year(now)&month(now)&day(now)&hour(now)&minute(now)&second(now)
&ranNum&fileExt '//第二步、filename由提交的文件路径+年月日的随机文件名+转换后的扩展名
组成
if file.FileSize>0 then
file.SaveAs Server.mappath(FileName) '//保存文件
end if
set file=nothing
next
%>
在这段源码中,最关键的是这两句:
1、formPath=upload.form("filepath")
2、filename=formPath&year(now)&month(now)&day(now)&hour(now)&minute(now)&second(now)
&ranNum&fileExt
小知识:变量与常量:所谓变量指在程序的运行过程中随时可以发生变化的值;而常量则恰恰相反,指
的是在程序的运行过程中始终保持不变的值。
下面让我们来看一下漏洞是如何形成的:
1. 从变量filepath中获取文件的保存路径。
2. 用路径变量formPath加随机生成的数字及经过判断的扩展名合成一个新的变量,变量Filename就
是上传文件保存的路径及名称。
这里举例说明:我们选择“111.jpg”上传,在上传过程中,随文件一起上传的还有FilePath变量,假
设其值为“image”,当这些值传到upfile.asp中,filename就变成了:
“image/200512190321944973.jpg”。上传成功后,该111.jpg被保存到image文件夹内,文件名也被
改成了:“200512190321944973.jpg”。这段流程看起来无懈可击,但还是被牛人研究出了突破方法
。什么方法呢?很简单,突破点就在变量身上。如果将其FilePath值改为“image/aa.asp□”,其中
“□”表示二进制的00(空的意思),这样,该变量提交给upfile.asp后,Filename值变成了
“image/aa.asp□/200512190321944974.jpg”。服务器在读取变量时,因为“□”是二进制的00,
所以它认为该变量语句已经结束,“□”后的字符自然也就被忽略了。这样一来Filename就成了:“
image/aa.asp”。瞧,漏洞出现了。
关于此漏洞的利用,我们可以使用桂林老兵的上传工具,或者用WinSock抓包,之后记事本保存提交数
据并增加、修改相关内容。用WinHex修改空格为二进制,最后用NC提交。具体操作请参阅相关文章
。
二、FileName
介绍过FilePath(上传路径)过滤不严的漏洞,再来看一看FileName(上传文件名)过滤不严造成的漏
洞,上传文件名过滤不严的形式是多种多样的,这里介绍两种:
1、动易文章
我们来看一下它的上传文件“Upfile_Article.asp”中的部分源码:
<%
Const UpFileType="rar|gif|jpg|bmp|swf|mid|mp3" '//允许的上传文件类型
Const SaveUpFilesPath="../../UploadFiles" '//存放上传文件的目录,注:以上两个常量均在
config.asp文件内定义
dim upload,oFile,formName,SavePath,filename,fileExt //变量定义
……………………
FoundErr=false '//此为是否允许上传的变量,初始化为假,表示可以上传。
EnableUpload=false '//此为上传文件扩展名是否合法的变量,初始化为假,表示的是不合法。
SavePath = SaveUpFilesPath '//存放上传文件的目录
……………………
sub upload_0() '//
set upload=new upfile_class '//建立上传对象
……………………
for each formName in upload.file '//用For循环读取上传的文件。
set ofile=upload.file(formName) '//生成一个文件对象
……………………
fileExt=lcase(ofile.FileExt) '//将扩展名转换为小写字符
arrUpFileType=split(UpFileType,"|") '//读取后台定义的允许的上传扩展名
for i=0 to ubound(arrUpFileType) '//第一关,用FOR循环读取arrUpFileType数组。
if fileEXT=trim(arrUpFileType(i)) then '//如果fileEXT是允许上传的扩展名
EnableUpload=true '//EnableUpload为真,表示该文件合法。
exit for
end if
next
if fileEXT="asp" or fileEXT="asa" or fileEXT="aspx" then '// 第二关,验证fileEXT是否为
asp、asa、aspx扩展名。
EnableUpload=false '//如果属于这三项之一,那么EnableUpload就定义为假,上传文件扩展名不合
法。
end if
if EnableUpload=false then '// 第三关,验证关。如果传递到此的EnableUpload变量为假,则说
明上传文件扩展名不合法。
msg="这种文件类型不允许上传!\n\n只允许上传这几种文件类型:" & UpFileType
FoundErr=true '//注意:因为文件名不合法,就更改了FoundErr值,由初始的false改为true。
end if
strJS="<SCRIPT language=javascript>" & vbcrlf
if FoundErr<>true then '//第四关,上传关。如果FoundErr不等于true才可以上传。
randomize
ranNum=int(900*rnd)+100
filename=SavePath&year(now)&month(now)&day(now)&hour(now)&minute(now)&second(now)
&ranNum&"."&fileExt '//定义filename,其值为固定的路径名+年月日及随机值生成的名称+传递过
来的fileExt扩展名。
ofile.SaveToFile Server.mappath(FileName) '//保存文件
msg="上传文件成功!"
……………………
next
set upload=nothing
end sub
%>
在这段源码中,用到了两个FOR循环、两个逻辑变量。“for each formName in upload.file”用于
取得所有上传的文件名;“for i=0 to ubound(arrUpFileType) ”用于检测文件扩展名。而两个逻
辑变量是EnableUpload和FoundErr。EnableUpload用于表示文件扩展名的合法性,True表示合法;而
FoundErr则用于表示文件是否可以上传,False表示可以上传。如果我们上传的是一个文件,那此段
代码是无懈可击的。但要上传两个呢?下面让我们看一下上传多个文件的流程:
首先,构造一个有两个上传框的本地Htm文件,Htm代码如下:
<form action="http://www.***.com/admin/Article/Upfile_AdPic.asp" method="post" >
<input name="FileName1" type="FILE" class="tx1" size="40">
<input name="FileName" type="FILE" class="tx1" size="40">
<input type="submit" name="Submit" value="上传" >
</form>
运行此Htm,在第一个框内选择一个jpg图片,文件名为“111.jpg”,在第二个框内选择一个Cer文件,
文件名为“222.cer”,点“上传”把这两个文件同时提交给程序。接着到Upfile_AdPic.asp中观察
这两个文件的上传流程(注意其中逻辑变量的变化)。
1)在进入第一个FOR(读取文件名)之前,程序先将变量FoundErr定义为false、EnableUpload定义为
false,然后读取文件名。先验证第一个文件111.jpg,在验证的第一关,jpg属于允许上传的类型,变
量EnableUpload=true。
2)接着到第二关,检验是否属于三种禁传类型,因为不属于,变量EnableUpload仍为true。
3)再到第三关卡,如果EnableUpload=false,那么FoundErr=true,而前面传递来的
EnableUpload=true,那FoundErr仍为进入第一个FOR循环之前的false。
4)最后进入第四关,此关的验证是:如果FoundErr<>true就可以通过,看一下从第三关传递过来的
FoundErr的值,是false那么就可以上传。这里请注意,在111.jpg上传后,EnableUpload的值保持为
true,FoundErr的值是false。
5)接着程序读取第二个文件222.cer,进入第一关验证是否为允许上传类型,如果cer属于此范围就将
EnableUpload定义为true。而cer不属于,所以就保持原值。EnableUpload的原值是什么?看一下
111.jpg上传后的变量值:“EnableUpload的值保持为true”,那么此时cer文件的EnableUpload值就
是true了。
6)再到第二关,cer同样不属于此限制范围,又跳过IF语句。再看EnableUpload的值,仍保持为true。
7)又到第三关了,因为EnableUpload=true,跳过了此关验证。直接进入第四关,这时回头看一下
FoundErr的值,自cer进行上传验证开始,一直未出现FoundErr,FoundErr的值到现在还是false。接
下来是第四关的验证。这里只要FoundErr不是true就可以上传,所以cer文件就这样通过了层层关卡
,进入到服务器。
除了cer格式,还可以上传asp□、asp.的文件,方法很简单:将上传框中的asp名称加入空格或小数点
。而上传到服务器中的asp□或asp.的扩展名,因为Windows文件命名规则,会自动去除后面的空格和
小数点,保存的就是asp格式了。
2、动感商务2005
接下来介绍动感商务2005的上传漏洞。动易是由于上传多个文件引起的漏洞,而动感则是因为文件
名过滤不严出现上传漏洞。下面是动感2005上传upfile.asp中的部分源码:
<%
Private Sub SaveFile_0() '//无组件上传
……………………
Set File = UploadObj.File(FormName) '//取得上传文件名
FileExt = FixName(File.FileExt) '//第一步、用FixName函数过滤上传文件的扩展名
If CheckFileExt(FileExt) = False then '//第二步、用CheckFileExt检查过滤后的文件扩展名
ErrCodes = 5
EXIT SUB '//退出上传
End If
FileName = FormatName(FileExt) '//符合条件的话,就用FormatName函数按日期生成文件名
……………………
If File.FileSize>0 Then
File.SaveToFile Server.Mappath(FilePath & FileName) '//保存的文件路径及名称是
Filepath+FileName
……………………
End Sub
%>
我们看一下上传所涉及到的一些参数。
A、FixName()函数:
Private Function FixName(Byval UpFileExt) '//第一步的过滤函数,过滤特殊扩展名。
If IsEmpty(UpFileExt) Then Exit Function '//如扩展名为空就退出交互
FixName = Lcase(UpFileExt) '//将扩展名转换为小写字符。
FixName = Replace(FixName,Chr(0),"") '//将二进制的00空字符过滤为空
FixName = Replace(FixName,".","") '//将单引号过滤为空,下同。
FixName = Replace(FixName,"'","")
FixName = Replace(FixName,"asp","")
FixName = Replace(FixName,"asa","")
FixName = Replace(FixName,"aspx","")
FixName = Replace(FixName,"cer","")
FixName = Replace(FixName,"cdx","")
FixName = Replace(FixName,"htr","")
FixName = Replace(FixName,"sHtml","")
End Function
从中我们可以看出,应用程序Asp.dll映射的类型全部过滤了。除此之外,小数点、单引号也被过滤,
甚至连Chr(0)都过滤了。
小知识:Chr(0)是什么?它就是16进制的0x00,表示为二进制是00000000,也就是前面在FilePath上传
漏洞中大显神通的空字符。
B、CheckFileExt()函数:
Private Function CheckFileExt(FileExt) '//第二步的判断函数,判断文件类型是否合乎要求
Dim Forumupload,i
CheckFileExt=False '//定义CheckFileExt的初始值为假,
If FileExt="" or IsEmpty(FileExt) Then '//第一次、为空则退出
CheckFileExt = False
Exit Function
End If
If FileExt="asp" or FileExt="asa" or FileExt="aspx" or FileExt="sHtml" Then '//第二次
、如果属于这四种类型也退出交互
CheckFileExt = False
Exit Function
End If
Forumupload = Split(InceptFile,",") '//第三次、从InceptFile中提取后台的上传扩展名
For i = 0 To ubound(Forumupload) '//用For循环检验
If FileExt = Trim(Forumupload(i)) Then '//如果和后台中的任一上传扩展名相符,则
CheckFileExt = True。
CheckFileExt = True
Exit Function
Else
CheckFileExt = False
End If
Next
End Function
此函数对经过FixName()过滤后的扩展名再次判断,其中有三次检查:第一次判断传递过来的扩展名
是否为空,为空则退出上传。第二次判断扩展名是否属于asp、asa等四种限传类型,属于也退出上传
。第三次用该扩展名同后台内自定义的上传扩展名进行对比,符合则允许上传。
C、FilePath值:
其所用到的filepath在upload.asp中,其值如下:
if info_name="bbs" then
FilePath = "/bbs/upload/"
else
FilePath = "/uploadpic/"
end if
FilePath是一个常量,从这条路找漏洞是行不通的。
这里上传一个文件来看其验证流程,我上传的是111.cer。在用“FileExt = FixName
(File.FileExt)”过滤扩展名时,因为cer属于fixName()函数的过滤范围,所以扩展名cer就成了空
。当把这个空的扩展名传递给CheckFileExt(),在其进行到“If FileExt="" or IsEmpty(FileExt)
”语句时,会因为FileExt为空而退出交互,返回格式不正确并拒绝上传。
如何突破呢?其关键点就在FixName()函数中。上面我们已经看到,在上传时cer会被过滤为空,但如
果我们将上传文件扩展名改为ccerer,同时在后台的自定义上传类型中增加“ccerer”、“cer”。
这样,扩展名为ccerer的文件在经过第一步FixName()的过滤后,就变成了cer(中间的cer字符被过滤
为空)。传递此值到CheckFileExt()函数中,通过其第一次不为空的关卡,再通过第二次限制类型的
关卡,最后到对比后台上传类型关卡。因为在前面我们已添加了“ccerer”、“cer”两种类型,所
以也就通过了第三次的判断。CheckFileExt = True。这样就把扩展名为ccerer的文件上传到服务
器上了,并且上传后的扩展名是cer。
有的朋友可能会问,如果上传扩展名为aaspsp□或aaspsp.格式的文件,经过FixName()函数的过滤后
不就变成了asp□或asp.么?而这两种格式也不在限制范围内,只要在后台中加上这几种类型,不是可
以把文件保存为Asp文件么?其实当初我也有这个想法,但经过仔细的研究分析,发现此路不通。为什
么?先说小数点,在FixName()中有这么一句:“FixName = Replace(FixName,".","")“,作用是将小
数点过滤为空。再来看空格,虽然FixName()中没有过滤空格,但在CheckFileExt()读取后台上传类
型时有这么一句:“If FileExt = Trim(Forumupload(i)) Then ”,其中有个Trim()。Trim的作用
是删除字符串开始和尾部的空格。虽然在后台能写入asp□类型,但在读取时,却会被Trim()过滤成
asp。而aaspsp□通过层层关卡到了此处,已经变成了asp□。asp□<>asp,类型不符,所以禁止上传
。
总而言之,上传漏洞是比较吸引眼球的。我用上面的例子对上传漏洞管中窥豹了一番,希望对各位朋
友有所帮助
|