账号:
密码:
最新动态
产业快讯
CTIMES / 文章 /
安全系统的设计与实作相关课题
 

【作者: 梁泰華,黃士殷】2000年02月01日 星期二

浏览人次:【4379】

前言

1988年11月,一个具有透过网路进行自我复制能力的程式被释放至网际网路中的某一台主机上;而在网际网路的协助之下,它没有花费太多的时间便成功地渗透了网路上数以千计的主机,迫使许多网站不得不停机等待修补漏洞。该程式主要由一名大学生Robert T. Morris 所撰写,因为它在网站间传递与复制的特性,也被人称之为「The Internet Worm」(网虫)。


后人分析(注一)发现:这只网虫之所以能轻易地攻进许多主机,除了靠网际网路的推波助澜之外,被攻击主机上一些服务程式有Bugs,才是主要的关键。


开发过软体的人应该都会认同一句话:「天下没有零Bug的程式。」特别是当程式愈写愈大时,这句话简直是不变的铁则。即使你/妳只是一个单纯的应用软体使用者,想必也一定有软体使用到一半便莫名其妙当掉的经验,先别急着责怪自己使用不当,这通常是应用程式设计或撰写上的疏失所导致。


Bug所造成的影响

一个因为程式写作上的错误而产生的Bug,究竟会造成多大的影响呢?实际上,根据该程式应用地点与错误程度的不同,造成的伤害也不一样:轻者导致当机,重者甚至可以伤人性命!以下所列举的,都是因为软体上的疏失而导致人员伤亡的真实案例:


1.1985至1987年间,一种用以对癌症病人实施放射治疗的医疗用机器Therac-25,因为控制软体(以PDP组合语言写成)中没有对负责剂量的变数值作溢位(Overflow)检查,使得在某些条件下,该变数可能溢位而造成照射剂量超过安全上限(注二);造成至少两名患者因照射剂量过多而死亡。


2.1991年波斯湾战争期间,一枚飞毛腿飞弹穿过爱国者反飞弹的防护网,击中了位于Saudi Arabia, Dhahran附近的一处军营,造成28名美国人死亡、98人受伤。原因是当时爱国者飞弹的控制软体中,潜藏了一个时序(Timing)上的错误,制造商提出的暂时解决方法是要求操作人员每隔数小时之后就必须重置(Reset)系统时钟,但由于战事紧张,Dhahran的爱国者飞弹在完全没有定时重置时钟的状况下连续运作了一百多个小时,使得该时序错误足以累积至影响飞弹拦截的正确时机。虽然以色列军方在取得爱国者飞弹八小时之后发现了这个软体上的错误,并立刻回报美国的制造商,美方也以最快的速度修正了这个Bug,但很不幸地,更新版的软体一直到事发后隔天才送达(注三)。


网站上的应用程式有Bug,虽然不至于造成人员的伤亡,但其所带来的影响,却也不可小觑;相信许多人对前一阵子海峡两岸的许多政府、非政府单位网站首页纷纷被非法修改的事件应该还记忆犹新。根据事后调查(注四),入侵者应该是利用rpc.cmsd这个服务程式的Bug,透过Buffer Overflow的攻击方式取得权限,进而修改该网站的首页内容。


事实上,根据被攻击程式所执行的权限之不同,入侵者所能造成的破坏也不一样,有时候虽然不至于整个网站的档案都被砍掉,但仅仅是一个首页被更改就够令人头痛了。因此,为了避免不安全的程式危害整个系统的安全,对一个程式设计者而言,了解并避免一些可能的程式设计错误是绝对必要的。以下我们将说明一些可能危害程式完整性(Integrity)与系统安全的错误,并提出一些解决方案。


常见的程式写作错误

不当的环境变数(Environment Variables):

所谓的环境变数,通常是命令解译器(Shell Interpreter,比方说︰DOS下的command.com或是UNIX下的sh等)于执行时所需要参考的一些资讯(例如︰PATH),但有时候这些变数设定上的疏失,却可能带来一些意想不到的后果。以PATH这个环境变数来说,命令解译器在接受使用者的命令之后,若该命令并非内建指令(Builtin Command,如echo),则会依据PATH 所指的路径搜寻同名称的可执行档。如果某一位使用者的PATH优先搜寻顺序设定为:.、/bin、/sbin、/usr/bin、/usr/sbin、/usr/local/bin、/usr/local/sbin (其中的. 符号表示搜寻目前路径),并且在其目前的工作目录(Working Directory)中包含了下列名为「ls」的命令稿(shell script):



[文字框标示起始]
#!/bin/sh
echo "hello world!"
[文字框标示结束]



则该使用者一旦执行了「ls」这个常见的命令,由于PATH搜寻顺序的影响,使得该目录下的ls命令稿最先被执行,因此结果将出现「hello world!」而非原来预期的档案列表。


除了PATH之外,另外还有一个环境变数「IFS」(Internal Field Separator)也经常出现在与命令稿相​​关的程式设计中。它主要被用来指定一个区隔输入字串的字元;比方说,如果当IFS为 “/” 时,”/bin/ls”这个字串就会被分割为 “bin ls”。配合前面的PATH变数,若使用者的目录下有一个名为「bin」的执行档,那原本在命令稿中欲以绝对路径执行的/bin/ls,在IFS等于“/” 的影响下,反而是「bin」这个程式被执行了,而如果bin这个程式是用来删除所有使用者档案的话...。


很明显地,在某些不当的环境变数交互影响之下,使得一些其他的攻击方式(如特洛依木马)得以奏效,也因此在「橙皮书」(注五)中有定义到一项称为「可信路径」(Trusted Path)的安全规范。


要注意的一点:这个问题主要发生在使用命令解译器时,这代表着即使是非命令稿的程式也可能被环境变数所影响;比方说在C程式中使用类似下列的呼叫:



[文字框标示起始]
.
.
.
system("/bin/ls -la");
.
.
.
[文字框标示结束]



UNIX系统下的档案保护位元(Umask)与符号链结(Symbolic Link):

档案保护位元(Umask)是UNIX系统下新建档案时的预设存取权限,预设值通常为022(表示仅允许该档案拥有者写入;其他使用者只能读取);但是,这对一些有特殊需求的应用程式而言是不够的。比方说有一个专门负责修改使用者密码的应用程式,它的作法乃是先将系统密码档(/etc/shadow)复制一份到暂存档(/etc/shadow~)中,待使用者改好暂存档中的密码后,再将暂存档改名为原来的密码档。但是,如果使用预设的档案存取权限,将使得原来被保护的密码档(仅系统管理者可读写)被存成没有被保护(其他人皆可读取)的密码暂存档;也就是说,在这段时间攻击者就有机可乘了。因此,针对一些安全层级要求较为严格的应用程式而言,程式设计人员必须注意档案保护位元是否适当地被设定。


符号链结(Symbolic Link)是UNIX档案系统中的一种特殊档案,以(图一)来说,/usr/sbin/sendmail这个程式即是一个符号链结档,连到/home/qmail/bin/ sendmail(注意最前面lrwxr-xr-x中那个「l」的属性),也就是说,使用者在执行/usr/sbin/sendmail时,事实上执行的是/home/qmail/ bin/sendmail这个程式;同样地,假设使用者写入资料到/usr/sbin/sendmail,事实上该笔资料是被写入至/home/qmail/bin/sendmail这个档案中。



《图一 》
《图一 》

以下举一个例子说明应用程式中不当的档案保护位元再配合上不完善的符号链结检查所造成的攻击方式:


大约在1996 年,有一个在X Window上使用,称为WorkMan的CD播放程式被发现因为缺乏完善的档案检查而造成一些安全上的顾虑(注六),使得在某些情况下可以让攻击者修改系统上任意档案。这里简单介绍一下相关的背景:首先,WorkMan在启动后会在/tmp的目录下开启一个用以存放该行程行程代码(PID)的暂存档,但很不幸的,该暂存档的读写权限被设定为0666(也就是所有人都可以读取与写入这个档案)。无巧不巧,在某些系统上,为了让WorkMan具有读取CD-ROM的能力,通常会给予它系统管理者的权限,换句话说,WorkMan此时便能以系统管理者的权限建立或覆盖该暂存档。再加上WorkMan并不会检查暂存档是否为符号链结,使得有心人可以综合以上几项特性,有技巧地覆盖掉系统上的任意档案;想想看:如果被覆盖的档案是/etc/passwd或/etc/shadow之类的重要档案,后果将有多么地严重!


Race Conditions

指两个以上的行程,因为同时读写同一份没有被保护好的共用资源,造成该资源出现最终状态不稳定的情形;所谓的「最终状态不稳定」,乃是指随着最后对该资源修改动作的行程之不同,最终的资源内容也不一样。以下举一个多执行绪(Multi-threaded)的程式说明:



[文字框标示起始]
.
.
.
int	global = 0;

Thread1()
{
global = global + 3;
}
Thread2()
{
printf("global = %d\n", global);
}
Thread3()
{
global = global - 1;
}
.
.
.
[文字框标示结束]



若Thread1、Thread2与Thread3同时执行,由于无法得知这三者的执行先后次序,造成 Thread2可能印出四种不同的值:0、3、-1或2。这种不稳定的情形在一个程式中通常是不被允许的,因为可能导致时序上或其他方面的错误;最简单的解决方式是采用诸如Semaphore或Critical Section之类的资源锁定(Resource Locking)机制,由于行程间同步这个课题已经超过本篇范畴,在此不多加讨论。


或许有些人会觉得奇怪:我的程式又没有用到多执行绪,怎么还会有Race Condition的顾虑呢?别忘了,程式中不仅在存取共用变数时,即使存取到程式以外的系统资源(如档案),都有可能发生因为没有做好保护动作,而导致最后结果不定的情形发生。以前面档案保护位元所举的「密码修改程式」例子来说,如果改成:



[文字框标示起始]
.
.
.
开启暂存档"/etc/passwd.tmp";
将"/etc/passwd.tmp"的读写权限改为0600;
.
.
.
[文字框标示结束]



也就是开启暂存档之后,「立刻」把该档案设为「仅有拥有者可读写」,是不是就保证不会被第三者得知暂存档的内容了呢?很不幸地,在开启档案与修改档案权限的两个系统呼叫之间,该行程仍有可能被作业系统切换(Context Switch)掉,换另一个行程B执行,如果行程B正好是「开启档案/ etc/passwd.tmp」,那么行程B仍然有能力在该档案读写权力被修改后读取该档的内容(因为档案读写权限的检查是在它被开启的时候做的)。


Failed on Boundary Conditions

程式执行的过程中,不免有一些例外的状况出现,比方说磁碟空间不足、印表机没纸或记忆体不够了;也因此,以C语言来说,在许多的系统函式库提供的呼叫中,都包含了一个称为「传回值」(Return Value)的输出结果,通常表示该呼叫是否执行成功,比方说当使用malloc()配置记忆体时,若malloc()传回的指标为NULL,表示系统无法配置到所需的记忆体空间。一般程式一旦呼叫函数失败,应该立刻采取适当的处置,比方说终止程式的执行,或要求使用者提供协助。


但有许多不太细心的程式开发人员,却往往忽略了传回值,使得程式执行时一旦碰到突发状况,就产生无法预期的后果;更广泛地来说,UNIX系统中的信号处理(Signal Handling)也应该视为和回传值同等地重要,特别针对一些在背景执行的程式(如daemon),如果没有特别处理一些讯号,有几个讯号预设是产生coredump(也就是结束程式之前,先将该程式所使用的记忆体及当时的执行资讯写入一个称为「core」的档案中,以利后来侦错工作的进行),某些作业系统在写入core档时并不会检查目的档是否为符号链结,如此一来将造成攻击者越权覆盖系统上其他档案的一个机会。


根据过去的经验,有一些边界状况是出在型态转换上,比方说C语言中的signed与 unsigned两种型态,举例来说:



[文字框标示起始]
void corrupt(int idx)
{
char	string[1024];

if (idx < 1024)
string[idx] ='\0';
.
.
.
}
[文字框标示结束]



上例中的idx,预设是整数型态,也就是包含负数,而corrupt()中用来做边界检查的if条件式却没有考虑到负整数的情况,造成下一行的程式码可能将\ 0\ 填入string[]以外的地方。这一类的错误也引出了我们下一个主题:Buffer Overflow。


Buffer overflow

这个在1988年Morris的网虫中所使用到的攻击技术之一,一直到现在都还被人拿来使用,据估计(注七),目前约有三分之二的系统安全漏洞都是采Buffer Overflow 的途径达成。简单来说,这个技术可以让一​​个程式去执行「自己程式码以外的程式码」。对一般应用程式而言,这并不会有很大的影响,但对于一些具有特权的程式,攻击者可以让特定的程式码被这些特权程式以超级使用者的身份执行,进而达到取得系统管理者权限或破坏系统的目的。听起来相当神奇,竟然可以执行原先编译进去的程式码以外的指令,这究竟是如何达到的?


一般说来,目前常见的电脑架构都是以堆叠(Stack)来存放副程式参数、区域变数(Local Variable)、传回值与返回位址等相关资讯,我们先看看一般程式语言在处理副程式呼叫时,堆叠长什么样子:


如(图二)所示,当sub()被呼叫时,参数a的位址会先被推入堆叠,然后是sub()的返回位址(Return Address),而sub()中的区域变数buf则待进入该函式之后再被配置于堆叠上方。


《图二 》
《图二 》

如(图三)所示,strcpy()执行之后,因为*a的内容(“this is a test”包含结束字元共15Bytes)已经超过了buf的大小(只有8Bytes),因此多出来的几个字元就会盖掉邻近buf的返回位址与参数位址。


《图三 》
《图三 》

事实上,一旦sub()结束,开始返回主程式main()时,程式的流程就已经被破坏了(在本例中是回到了一个可能当掉此程式的位址)。换句话说,只要攻击者能算出该回到哪一个位址,并将想执行的指令以机器语言的形式放入该位址(在本例中可以放入buf),便能执行任何想执行的指令。


结论︰安全程式设计守则

综合以上几点,我们归纳出几项设计或撰写安全关键程式时应该注意的事项:


1.环境变数

不要相信任何外在环境输入的资讯,不论是使用者输入的资讯、系统环境变数、抑或副程式彼此之间传递的参数,设计者都有责任对所有外在环境输入的资讯做严格地检查。


2.型态转换(Type Casting)

对于一些非使用强制型态(Strong Typing)的程式语言(如C),型态转换是相当危险的一个动作,在它便利的功能背后往往隐藏着许多潜在的问题,小小一个char转int的动作都可能因为指标(pointer)处理不慎而造成大灾难。


3.边界状态检查

千万别假设所有的系统函式永远会正确地被执行,使用任何函式前除了完全了解它的行为之外,对于它在错误下会产生何种结果,也必须一并考量,同时做出正确的处置。


为了避免Buffer Overflow的产生,程式中除了必须对所有用到阵列或类似型态的资料结构详加检查之外,在处理字串时,也应该避免使用一些「具有潜在危险」的函式,如strcpy()、sprintf()、strcat ()或gets()等等,因为它们提供的边界检查能力相当限(有些甚至完全没有边界检查),而最好以一些较为安全的函式如:strncpy( )、snprintf()、strncat()或fgets()取代之。


4.共用资源

谨慎处理程式中每一项可能用到的资源,不论是记忆体、目录架构、周边设备、暂存档或档案描述子(File Descriptor),在没有做好锁定的动作前,永远不要相信它是安全的。以前面的密码暂存档一例而言,最好便是在开启档案之前,先以umask()设定保护权限;再不然,在open()时也可以设定暂存档的读写权限。


此外,在开启暂存档之类的档案时,多作一些额外检查以应付诸如符号链结等特殊档案是绝对有必要的(想想WorkMan的例子吧)。


5.资源与权限

对许多程式设计人员而言,他/她们都会希望自己的程式在最充足的系统资源与最大的权限之下执行。这其实是相当危险的想法,想想看,一旦一个应用程式攫取的系统中所有的资源与最高的权限,任何一个小小的程式错误,都可能伤害整个系统,所谓「爬得越高,摔得越重」,为了系统整体的安全,任何程式在执行时都必须限制资源的使用与遵循「最小权限原则」(Least Privilege Principle,注五)。


很多程式上的安全问题均是在原设计者未预期的状况下产生的,正如一开始我们所提到,几乎不大可能设计出一个完全没有缺陷的程式,因此,我们除了提供几项安全程式设计守则之外,也只能以一句话奉劝程式开发人员:「当你撰写软体的时候,千万得仔细考虑每一个环节,以免造成难以弥补的损失:小心、小心、小心!」


(作者梁泰华、黄士殷分别就学、任教于元智大学资工系)


备注

注一:Eichin, Mark W., and Rochlis, Jon A., “With Microscope and Tweezers:


An Analysis of the Inner -net Virus of November 1988,” in Proceedings,


1989 IEEE Computer Society Symposium on Security and Privacy page


326-343, 1-3 May 1989, Oakland, California.


注二:N. G. Leveson and C. S. Turner, “An Investigation of the Therac-25


Accidents,” IEEE Computer 26(July 1993), 偏偏. 18-41.


注三:P. Mellor, “CAD: Computer -Aided Disaster,”Technical Report, Centre


for Software Reliability, City University, London, UK, July 1994.


注四:TWCERT, http://www.cert. org.tw/chi/index.html


注五:”Trusted Computer System Evaluation Criteria,” Department of Defense


Standard, DoD 5200.28-STD, December 1985.


注六:”Vulnerability in Workman,” CERT Advisory CA-96.23, October 1996.


http://www.cert.org/advisories/CA-96.23.workman_ vul.html


注七:Crispin Cowan, Perry Wagle , Calton Pu, Steven Beattie , and Jonathan


Walpole, “Buffer Over-flows: Attacks and Defe-男色身佛软体和 Vulnerability


oft和 Decade,” Procee- 定时of DARPA Information Survivability


Conference and Expo, vol 2, pp. 119-130, 1999.


相关文章
大型可挠曲式电浆电视
零组件科技论坛─「电池能源管理与新一代电池技术」研讨会实录
浅谈奈米平面显示器
未来两年是OLED市场发展的关键期
数字电视时代的电浆显示器展望
comments powered by Disqus
相关讨论
  相关新闻
» 达梭系统携手CDR-Life 加速癌症治疗科学创新
» 宜鼎独创MIPI over Type-C解决方案突破技术局限,改写嵌入式相机模组市场样貌
» 鼎新电脑串连生态系夥伴 数智驱动智慧低碳未来制造
» 鼎新电脑携手和泰丰田解缺工 以数位劳动力开启储运新时代
» Fortinet SASE台湾网路连接点今年落成 全台巡??落实云地零信任资安


刊登廣告 新聞信箱 读者信箱 著作權聲明 隱私權聲明 本站介紹

Copyright ©1999-2024 远播信息股份有限公司版权所有 Powered by O3  v3.20.1.HK84T7YFIECSTACUKX
地址:台北数位产业园区(digiBlock Taipei) 103台北市大同区承德路三段287-2号A栋204室
电话 (02)2585-5526 #0 转接至总机 /  E-Mail: webmaster@ctimes.com.tw