通行证│用户名: 密码: 验证码: 验证码,看不清楚?请点击刷新验证码 电信网通铁通移动   在线
文章搜索:
热门搜索:红客 黑鹰 红客技术 安全动画 红客培训
首页 文章 软件 动画 资源 励志 论坛 邮箱 会员 军事 科技 博客 爱心红客 最近更新 800g资源
 业内新闻 漏洞公告 病毒公告 电脑知识 网络知识 菜鸟入门 攻防教程 黑客攻防 安全编程 工具使用 综合安全 个人安全 安全相关 Q Q安全 原创精华 红客人物 站内事件
您现在的位置: 爱国者安全网 >> 文章类 >> 安全防御 >> 安全相关 >> 文章正文
漠漠孤云尽成雨----浅谈CGI脚本安全
责任编辑:stef   更新日期:2005-7-6
 
漠漠孤云尽成雨
...........----浅谈CGI脚本安全
[I.T.S]Jambalaya

很多朋友告诉我一定要去不断总结自己学的东西,并且不要吝啬拿出来与人分享,这样才有一个很好的沉淀和交流,还有彼此的促进。于是自己把自己一部分学习笔记整理了一下。还有一些自己平时总结的一些学习经验,希望对大家有所帮助。

CGI脚本是在网络服务器上解释执行的,然后将执行的结果返回给客户端(就是我们的浏览器)。因为perl的强大功能、他的灵活性和对所有流行的操作系统的支持,当然还有它的“价格”(纯免费)。使其受到了很多CGI编程者的青睐。我们知道任何语言本身是无安全性可言的,安全只是存在于使用他的人的身上。所以如果在编写脚本时,没有安全的意识,那么编写出的代码也就没有安全的"功能"了。

一、注意对变量的处理
1、用户的输入是不可信任的,当谈到安全编程的时候,比尔盖茨先生对他的员工说了一句非常经典的话:All input is invalid.(所有的用户输入都是有害的,具体是不是他第一个说的,有待考察)
一切用户输入的地方都是我们应当注意的地方。大家知道美国最著名的电子商务网站e***.com吧,他在1999年就被黑客用以下的方法入侵了。我们的新浪网站也被用同一种方法攻了进去,并且还被人截了图。。。。
来看一段代码吧:
-----------Code Start------------
#unsafecodz.cgi
01 $filepath="f://myhome//bbs//"
......
......
13 $filename=$query -> param('page');
14 if ($filename eq "")
15 { $filename="error.html";
16 die("对不起,文件名不能为空!");
17 }
18 else{
19 $filename="$filepath/".$filename;
20 open(FILE,$filename);
21 while(<FILE>)
22 {
23 print $_;
24 }
25 close(FILE)
......
-----------Code Ends-------------

这段代码基本上没有作太多改动,因为我演示是在自己的机子上,所以把路径改了一下,我们在这里回放一下曾经的过程。

这段代码是网站用来浏览的其他网页的,如:http://127.0.0.1/myhome/bbs/unsafecodz.cgi?page=something.html,就会浏览something.html这个文件,这里的$filename用param提取page中用户输入的内容,$filename其实是用户间接输入的内容,而代码对$filename并没有做严格的审查只是检查是否为空,如果恶意用户直接在浏览器中指定其他文件,如cgi,asp或者任何文件,则返回的是文件的源代码,如:http://127.0.0.1/myhome/bbs/unsafecodz.cgi?page=unsafecodz.cgi。
看到unsafecodz.cgi的源代码了,通过简单的浏览其他的页面,我们可以基本上可以得到所有文件的源代码。我们也可以通过"../"来切换到其他的目录下。
这还不是最糟糕的,如果你的系统是Linux或者UNIX,那么更过分的在这里呢!http://127.0.0.1/myhome/bbs/cgi-bin/unsafecodz.cgi?page=/../../../../../../ect/passwd。结果显而易见,可以看到了主机的用户名和密码文档。
这也不是最遭的,如果利用open函数加管道符执行任意命令的后果会怎么样?利用open函数执行命令的技术很老了,我就不废笔墨了。
后来有人对代码进行加固,将第19行改成 $filename="$filepath/".$filename.".html" 他限定后面4位为html,但是这样的加固似乎起不到什么作用,因为它忽略了NULL字符。提交如下请求依然可以绕过他的限定:
/myhome/bbs/unsafecodz.cgi?page=unsafecodz.cgi%00.html
还是这个问题,还有人这么加固代码,它在将19行改了之后,又将第14行改成:
if (($filename eq "") || (-e $filename))
他在这里检查文件是否存在,如果不存在就不去进行后面的操作,我们这么依然提交:
/myhome/bbs/unsafecodz.cgi?page=unsafecodz.cgi%00.html
你会发现我们依然可以成功。
他还是忽略了什么,他忽略了什么呢?他忽略了null字符是可以绕开-e的检查,也就是说(-e $filename)将会认为文件是存在的,因为%00后面的东西在-e中会被忽略。

ok,这里我们停一下,我们应该能注意到刚才是什么改变了程序本来的流程。就是那个null字符(),想想还有什么我们可以用来改成程序的流程呢?\t,\r,\x0B,\n,空格。这些字符都很有用,大家记住它并要学会如何自由运用这些东西。大家是否还记得前不久的dvbbs论坛漏洞,就是由于上传中的null字符所引起的。很多时候你会发现技术突破不过就是你的技术积累沉淀后的爆发。


##关键词:用户输入的变量
##检查:是否做过有效过滤


2、隐式输入的危害

用户的输入分为两种,显示输入和隐式输入。上面的例子没有注意到用户的输入导致问题的出现。因为那是用户直接输入的,算是显示输入吧。现在很多程序员都会下意识的去保护自己程序的安全性或者他们本身就了解一些安全的重要性,都会加一些有用或者没用的限制,用户直接输入可利用的部分越来越少,于是隐式输入就开始受到关注。这里说的并不仅仅是perl cgi,包括现在一大帮人玩的asp注入攻击,还有本来是n年前的技术现在才浮出水面的php注入还有一些其他的攻击手段。仔细回顾一下你就会发现很多都是隐式输入所引起。隐式输入都是不被程序员注意或者容易被忽略的地方。

cookie应该算是隐式输入中比较典型的例子,我们就用cookie来说事儿吧......

-----------Code Start------------
#unsafecodz2.cgi
......
18 $filename=$query->cookie("namecookie");#**********#
19 $filename="$filepath/".$filename;
20 open(FILE,$filename);
21 while(<FILE>)
22 {
23 print $_;
24 }
25 close(FILE)
-----------Code Ends------------

注意打星号的那一行,这只是提取cookie而已,这的确不是用户的直接输入,但这却是用户可以间接控制的。如果恶意用户通过nc提交如下东东,后果是什么呢?
GET /myhome/unsafecodz2.cgi HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)
Host: 127.0.0.1
Connection: Keep-Alive
Cookie: namecookie=/../../../../../ect/passwd
我们看到这行中
Cookie: namecookie=/../../../../../ect/passwd

一般隐式输入都不容易被注意。
这里攻击者可以通过构造cookie来控制网站,结果就是和上面的一样。说到这里其实都是一句话:All input is invalid。作为代码的编写者要注意所有用户可以直接或间接控制的地方。

这里你可能觉得你似乎明白了,但我敢说其实你并没有真的明白,cookie的控制是里面最简单的一个例子。隐式输入很多时候,不要说写代码的人了,就连很多WEB安全的高手都不见得能够非常容易的找到,他们更多的是凭自己的经验和直觉。你可能觉得我言过其实了,其实并没有,简单的连傻瓜都能看出来的隐式输入,当然容易找到。但是许多的危害更大的,并不那么容易发现,所以这个时候多是凭借自己的经验和对漏洞"味道"灵敏的嗅觉。

##关键词:没有
##检查:是否做过有效过滤

解决方法其实很简单,就是严格控制用户的输入。所谓严格控制并不只是过滤,因为过滤难免有漏网之鱼。限定要比过滤来得轻巧和严格。把用户的输入控制在你规定的范围内,可以用一个正则表达式来给用户划一个范围,指定用户可以输入的字符或者数字,如果用户输入与你的规定的不匹配,则不与通过。
至于如何做限定,用正则表达式会很简单。我用email的例子说明:
if (email !~/^[\w.-]+\@[w.-]+$))#如果不和里面的规定字符匹配则报错
{
&error"输入不正确,难道您就是传说中的黑客?"
}
else
{
#输入正确,继续操作。
......
email的一般格式是fakename@fakename.org。我们只希望用户输入字符、数字、@、"."、“-”、“_”这些东东。永远不要幻想用户会按照你所希望的输入,除非你给他们划定范围。在这里用以一个简单的正则表达式。在这个正则表达式中,要求用户只能输入英文大小写字符,数字和“@”,"-","_""."这几个字符,如果输入其他的,则报错。

二、注意几个危险函数在代码中的使用和特殊字符过滤。
1、有几个危险函数在程序中用得越少越安全(这么说好像有点不严格,呵呵)。因为很多都是黑客的突破口。这些函数是:system(),open(),exec()。
system()和exec都可以执行系统命令,如system("del f:\myhome\$filname"),如果$filename也是通过表单从用户那里得到的,如果我们在$filename处输入1.txt;del f:\myhome。我们就删除了整个目录。我们现在可以删除任意文件。如果你用管道操作符的话也可以。
其实system()函数可以执行系统命令,如果对其中变量缺少严格限制容易引起安全问题,程序员们或多或少的知道一些,但是由于快速开发或者项目给的时间紧,为了应付差事,往往不会理会。

##关键词:system()、exec()
##检查:函数中是否有可控制的变量,是否可利用

open()函数也是我们应当留意的地方,大家不要误会,open函数本身是没有什么的,而是里面的用户输入的数据导致的问题集中到了open()函数上。open()函数本来是用来打开一些文件,我们看到我们的第一个程序就是因为open函数引起的泄漏源代码。我们还可以通过“>”,"<"这样的符号来控制是否向文件写入或者读出。如果加上">"则可以向文件中写入,如果对那些向cgi文件中写入的字符不加以控制或者过滤不完全,那么写入任意的语句就很危险了。其实安全漏洞只不过是代码编写者没有考虑到而恶意用户却想到了的地方。一切我们还是让代码自己说吧:
------------Code Start---------------
......
my $file = "$lbdir" . "forum$inforum/$intopic.pl";
......
if (open(FILE, ">$file"))
{
flock(FILE, 2) if ($OS_USED eq "Unix");
print FILE "$intopic\t$topictitletemp\t$topicdescription\t$threadstate\t$threadposts\t$threadviews\t$startedby\t$startedpostdate\t$inmembername\t$currenttime\t$posticon\t$inposttemp\t";
close(FILE);
}
------------Code Ends------------------
呵呵,这段代码大家眼熟吧,不错,这就是LB5K论坛post.cgi中的一段代码,我觉得这段代码应该最能说明问题(其实是我手懒,懒的自己写了),大家注意这里的
open(FILE, ">$file");
打开文件准备向里面写入。后面的print就是向指定的文件中写入。论坛的本意是将用户的贴子标题简单的保存在一个.pl文件中作为保存,但是编写者没有注意到贴子的标题其实是用户可以任意控制的,如果恶意用户输入system函数执行任意指令,如果标题是system @ARGV那么所保存的.pl记录文件中的第一行也是system @ARGV,这样如果去调用那个.pl文件就形成了一个webshell。
别急,我还没说完。前面说过利用open()+管道符同样可以执行命令。
##关键字: open()和后面相关联的变量
##检查:open打开的文件是否可写,可写变量是否可以控制。变量是否可控制。

2、特殊字符
大部分情况下在用户输入的附近,程序员为了保护程序会过滤掉一大批特殊字符。然后用户输入的恶意语句无法执行。这个部分其实要说的东西很多,但是总觉得自己有点啰里啰唆的感觉,为了节省版面简言之吧。
看看下面的字符你是不是都过滤掉了:

反引号(``)
反引号我们平时使用的不多,但在perl应用中功能却很大,它象system一样可以执行系统命令,如:
System('dir c:\')
$a=`dir c:\`;print $a;
上下两条语句执行的结果是一样的,就算过滤掉system,用反引号可以起到相同的作用。

逗号
逗号可能是因为个头小的缘故,并没有受到太大的重视,不象他的兄弟分号长得那么高总受到人家关注,但其实逗号很多时候可以做许多事情,下面两句话意思是一样的:
$a=`dir C:\`;print $a;
$a=`dir C:\`,print $a#

分号
分号就不用说了吧,可以截断程序的流程,比如:
system("del ./path/$file");
假设$file可控,那么直接加入jam.txt;del ./path
然后整个path目录就消失了......

反斜杠
反斜杆的应用很巧妙,正常的过滤下如果用户输入/../会被过滤成/\.\./,但是如果用户输入/\.\./呢?则被过滤成了/\.\./,看到了么?巧妙的输入,巧妙地逃避了规则限制。

管道符
知道这句语句在ls后面加上管道符(|)会起到什么作用么?
open(FILE, "/bin/ls")
知道的话我就不废话了。

尖括号(<>)
跨站脚本是怎么实现的你不会忘记吧?^_*

美元符($)
这个放在perl中的字符串前面是什么意思?如果用户用这个字符构造语句输入,又会产生什么效果?你不会不知道吧:p

换行符等
\t,\r,\x0B,\n,还记得第一个例程我们是怎么改变程序的流程的么?

其实这只是一部分,限于篇幅不多说了。



三、验证的不完全
验证不完全和上下文逻辑错误是程序员最容易犯的错误。作WEB安全如果能有一个严谨的思维那就再好不过了,因为一个严谨的思维来去对付那些逻辑错误应该是能很容易发现(个人认为)。如果你不幸和我一样没有一个成熟而又严谨的思维,那么就多练习多总结来增长自己的经验值吧。勤能补捉阿!最容易忽略的往往危害是最大的。几个大的cgi论坛都曾经有过这个问题。其实所谓验证不完全就是一种逻辑思维能力的不完善。建议去学一下离散数学:-D。
举例说明:
------------Code Start---------------
#adminchecking.cgi
#这是一段管理员检验认证的入口程序
......
$membername=cookie("inputname"); #从cookie中得到名字和密码
$memberpassword = cookie("inputpassword");
$filetoopen="$filepath/"."$membername.cgi";#提取保存在$membername.cgi中的用户名和密码
if (-e "$filetoopen") #检查这个用户文件是否存在
{
open(FILE,"$filetoopen");
$line=<FILE>;
close(FILE);
($name,$password,$vip)=split(/\t/,$line);
}
if((lc($supername) eq lc($name)) && ($superpassword eq $password) && ($vip eq "super"))
#$vip值来自param提交
{ #如果管理员名字、密码和头衔(vip)都正确则可以进入。。。。
------------Code Ends------------------
这里暂不讨论将密码和管理员名称保存在$membername.cgi下是否安全,我这里只是简单的举个例子。程序首先从cookie中提取了用户名和密码,还有用户头衔。然后打开以这个用户为名的文件,提取保存在里面的admin的用户名,密码和头衔,分别给$name,$password,$code。然后对这三者和输入的进行比对,都一样才可进入管理页面。好像没什么错误,但不知道你是否发现其实那个if语句根本就是形同虚设,为什么这么说呢?如果cookie中用户名和密码都为空,那么if这段语句根本不会被执行,更不要说打开用户文件了。这里最主要的是那个头衔的值,如果用户名和密码都为空,这时因为文件不存在所以if不会执行,也就是不会去提取用户的用户名、密码、头衔着三个值,用户名和密码都为空,空肯定等于空,这个不用说了。那么就剩下一个头衔(vip)没有解决,因为头衔(vip)是用户提交的,所以我们可以在浏览器中直接指定vip=super,这样就成功了绕过了对管理员的检验认证。这就是我们所说的验证不完全,也是比较难避免的错误。
也许你会说这样的危害只能影响到那些开源的源代码,那你就错了,通过不同页面不同参数的比较,发现类似如这种校验参数的利用并不是什么难事。这就是WEB入侵中的小技巧。
这里一路说下来,我们应该注意到:思维的拓展性很多时候是来自于经验。如果你老是感叹为什么别人想得出来的方法自己想不出来,然后认为自己的思维太不开阔了,那我可以很负责告诉你,那不是你的思维不开阔,那绝对是你的经验不够,而经验是哪里来的?他来自于实践:)

##关键字:没有
##检查:对管理员的认证检验的入口,和相关函数。


四、未检验用户输入长度
对于攻击者很多时候并不都是象我这样的好心人,(呵呵,别用砖头丢我)有时候他们只是为了攻击而攻击,这个时候D.O.S(拒绝服务攻击)就成了他们的首选。对用户的输入长度的检验就尤为重要了,如$ENV。如果你没有对用户输入作一个限定,那么当用户输入一堆垃圾信息时,就会产生拒绝服务攻击。更有甚者,用垃圾文件添塞硬盘,直到把硬盘写满为止,可怕不?别认为别人不会用这么笨的方法,前不久就有人写程序模仿IE正常访问,去刷新不断调用数据库,导致数据库瘫痪。

五、服务器的权限设置
不要让你的WEB以管理员权限运行,IIS的默认设置是GUEST的,这种权限很低,就算攻击者得到了一个Webshell,也不会对你有太大的威胁,如果你的目录设置和服务器的配置好的话,攻击者很难有大的作为。如IIS默认配置是GUEST,Apeach的默认配置是uid=99;nobody的权限。如果都是这样的话,这条就是废话了,但是我在给很多网站做测试的时候发现不少服务器给WEB一个root权限,那基本上这个服务器如果被黑客攻击就算是OVER了。攻击者连提升权限都不用,就可以从容的控制整个服务器。到时候你想哭都找不到调儿。

六、服务器的目录设置
其实上面已经谈到过了目录设置,这里细说一下,不要把所有的目录均设置为有脚本执行权限的。
注意用户可以控制的目录区域,比如上传头像或者文件的目录
恶意用户向服务器写入的shell如果写入了没有脚本执行权的目录中,那个shell也就执行不了了。



这篇文章到这里就结束了,这只是自己总结的一部分,其实还有很多东西限于篇幅和其他原因没有写,比如命名规则之类的技巧。大家可能已经感觉到了,整篇文章并不是一篇完全的教学文章,我更希望能通过这篇文章来拓展你的思维。很多技术的拓展的根基还是你的基本功。学习东西的时候不妨多问问自己:我真的明白了么?
很多时候你觉得自己已经明白了,但其实你只了解到了皮毛而已。
当自己发现一个新的技术或者方法不妨和别人共享一下,很多时候你觉得是如获至宝,但其实人家早就发现了。
明确自己在应该学习什么?你是在学习技术本身还是在学习学习技术的思维?
"漠漠孤云尽成雨",沉淀和积累是促进你前进的动力
废话太多了,就容易被wtf当成骗稿费的了。(这个尤为重要,切记,切记)
行文仓促,技术有限,如有错误,欢迎到www.itaq.org指教。

  • 上一篇文章:
  • 下一篇文章:
  • 最近更新
    固顶文章 爱国者安全网2007年度优秀版主评选
    普通文章 瑞星公司01月11日发布 每日计算机病毒及木马播报
    普通文章 破解博彩神助(专注彩票) V2.8.01
    推荐文章 推荐:跨站脚本执行漏洞代码的六点思路
    普通文章 Windows系统下的远程堆栈溢出 实战篇
    普通文章 Windows系统下的远程堆栈溢出 原理篇
    普通文章 MsSQLServer是如何加密口令的
    普通文章 浅谈国内的渗透评估过程
    普通文章 Dvbbs8.1 0DAY(通杀Access和mssql版本)
    普通文章 微软:我们的代码比赛门铁克更安全
    热门文章
    普通文章REAL蛀虫利用播放器漏洞下载恶意程序
    普通文章李彦宏:中国要在互联网领域逐渐超越美国
    普通文章马云:阿里巴巴的成功是一个生态链的成功
    普通文章Ingres用户认证非授权访问漏洞
    普通文章TCPreen FD_SET()函数远程栈溢出漏洞
    普通文章Winace UUE文件解压堆溢出漏洞
    普通文章Pclxav木马猎手第一代特征码引擎源代码
    普通文章IE收藏夹管理小精灵算法分析
    普通文章Extra Drive Pro算法分析历程
    普通文章雨过天晴自我注册
    精彩专题