By Design- 就是这么设计的,无效的
Bug Duplicate - 这个问题别人已经发现了,重复的Bug
External - 是个外部因素(比如浏览器、
操作系统、其他第3方软件)造成的问题
Fixed - 问题被修理掉了。Tester要尽可能找到这种Bug
Not Repro - 无法复现你这个问题,无效的Bug
Postponed - 是个问题,但是目前不必修理了,推迟到以后再解
Won't Fix - 是个问题,但是不值得修理了,不管它吧
1.对response的处理:(其中Test Request是request的名称)
def groovyUtils = new com.eviware.soapui.support.GroovyUtils( context ) def holder = groovyUtils.getXmlHolder( "Test Request#Response" ) log.info(holder.getNodeValue("//sessionid")); def sessionid = holder.getNodeValue("//sessionid") return sessionid <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sam="http://www.soapui.org/sample/"> <soapenv:Header/> <soapenv:Body> <sam:logout> <sessionid>${SessionId#result}</sessionid> </sam:logout> </soapenv:Body> </soapenv:Envelope> |
2.对断言的处理:
def groovyUtils = new com.eviware.soapui.support.GroovyUtils( context ) def holder = groovyUtils.getXmlHolder( "Test Request - logout#Response" ) def sessioninfo = holder.getNodeValue("//sessioninfo") log.info(sessioninfo) assert sessioninfo.equals("OK") |
1.尽快报告软件缺陷
软件缺陷发现的越早,在进度中留下的修复时间就越多。
软件缺陷发现的越晚,越不可能被修复,特别是小缺陷。
2.有效描述软件缺陷
有效的表现如下:
a.简短:只解释事实和演示、描述软件缺陷必需的细节。
b.单一:每一个报告只针对一个软件缺陷,应该分别报告缺陷,不能堆在一起。
c.明显和通用:用使用者容易看懂的、展示通用性的、简单易行的步骤描述软件缺陷,得到的修复机会更大。
d.再现:要想得到重现,软件缺陷报告必须展示其再现的能力,按照预定步骤可以使软件达到缺陷再次出现的相同情况。
e.在报告软件缺陷时不做评价:报告缺陷时,要不带倾向性、个人观点和煽动性。
f.补充完善软件缺陷报告。
一、简介
现在的服务器端程序很多都是基于
Java开发,针对于Java开发的Socket程序,这样的服务器端上线后出现问题需要手动重启,万一大半夜的挂了,还是特别麻烦的。
大多数的解决方法是使用其他进程来守护服务器程序,如果服务器程序挂了,通过守护进程来启动服务器程序。
万一守护进程挂了呢?使用双守护来提高稳定性,守护A负责监控服务器程序与守护B,守护B负责监控守护A,任何一方出现问题,都能快速的启动程序,提高服务器程序的稳定性。
Java的运行环境不同于C等语言开发的程序,Java程序跑在JVM上面。不同于C语言可以直接创建进程,Java创建一个进程等同于使用java -jar xxx.jar启动一个程序。
Java启动程序并没有C#类似的单实例限制,你可以启动多个,但是你不能启动多个,不能让多个守护A去守护服务器程序,万一启动了多个服务器程序怎么办?
二、技术讲解
这里的技术讲解比较粗略,具体请
百度一下,这里只讲解作用。
1、jps命令。
JDK自带的命令工具,使用jps -l可以列出正在运行的Java程序,显示Java程序的pid与Name。只对Java程序有效,其实查看的是运行的JVM
2、java.nio.channels.FileLock类的使用
这个是Java new IO中的类,使用他可以维持在读取文件的给文件加上锁,判断文件时候有锁可以判断该文件是否被其他的程序使用
3、ProcessBuilder与Process
这两个原理差不多,都是调用系统的命令运行,然后返回信息。但是硬编码会导致你的Java程序失去可移植性,可以将命令独立到配置文件中。
三、设计原理
Server:服务器程序
A:守护进程A
B:守护进程B
A.lock:守护进程A的文件锁
B.lock:守护进程B的文件锁
----------------------------------------------------------------------------------
Step 1:首先不考虑Server,只考虑A与B之间的守护
1.A判断B是否存活,没有就启动B
2.B判断A是否存活,没有就启动A
3.在运行过程中A与B互相去拿对方的文件锁,如果拿到了,证明对面挂了,则启动对方。
4.A启动的时候,获取A.lock文件的锁,如果拿到了证明没有A启动,则A运行;如果没有拿到锁,证明A已经启动了,或者是B判断的时候拿到了锁,如果是A已经启动了,不需要再次启动A,如果是B判断的时候拿到了锁,没关紧 要,反正B会再次启动A。
5.B启动的时候原理与A一致。
6.运行中如果A挂了,B判断到A已经挂了,则启动A。B同理。
Step 2:加入Server
1.A用于守护B和Server,B用于守护A。
2.原理与Step 1 一致,只是A多个一个守护Serer的任务。
3.当A运行的时候,使用进程pid检测到Server已经挂了,就启动Server
4.如果Server与A都挂了,B会启动A,然后A启动Server
5.如果Server与B挂了,A启动Server与B
6.如果A与B都挂了,守护结束
Step 3:使用Shutdown结束守护,不然结束Server后会自动启动
四、实现
1、GuardA的实现
1 public class GuardA { 2 // GuardA用于维持自己的锁 3 private File fileGuardA; 4 private FileOutputStream fileOutputStreamGuardA; 5 private FileChannel fileChannelGuardA; 6 private FileLock fileLockGuardA; 7 // GuardB用于检测B的锁 8 private File fileGuardB; 9 private FileOutputStream fileOutputStreamGuardB; 10 private FileChannel fileChannelGuardB; 11 private FileLock fileLockGuardB; 12 13 public GuardA() throws Exception { 14 fileGuardA = new File(Configure.GUARD_A_LOCK); 15 if (!fileGuardA.exists()) { 16 fileGuardA.createNewFile(); 17 } 18 //获取文件锁,拿不到证明GuardA已启动则退出 19 fileOutputStreamGuardA = new FileOutputStream(fileGuardA); 20 fileChannelGuardA = fileOutputStreamGuardA.getChannel(); 21 fileLockGuardA = fileChannelGuardA.tryLock(); 22 if (fileLockGuardA == null) { 23 System.exit(0); 24 } 25 26 fileGuardB = new File(Configure.GUARD_B_LOCK); 27 if (!fileGuardB.exists()) { 28 fileGuardB.createNewFile(); 29 } 30 fileOutputStreamGuardB = new FileOutputStream(fileGuardB); 31 fileChannelGuardB = fileOutputStreamGuardB.getChannel(); 32 } 33 34 /** 35 * 检测B是否存在 36 * 37 * @return true B已经存在 38 */ 39 public boolean checkGuardB() { 40 try { 41 fileLockGuardB = fileChannelGuardB.tryLock(); 42 if (fileLockGuardB == null) { 43 return true; 44 } else { 45 fileLockGuardB.release(); 46 return false; 47 } 48 } catch (IOException e) { 49 System.exit(0); 50 // never touch 51 return true; 52 } 53 } 54 } |
2、GuardServer的实现
1 public class GuardServer { 2 private String servername; 3 4 public GuardServer(String servername) { 5 this.servername = servername; 6 } 7 8 public void startServer(String cmd) throws Exception { 9 System.out.println("Start Server : " + cmd); 10 //将命令分开 11 // String[] cmds = cmd.split(" "); 12 // ProcessBuilder builder = new ProcessBuilder(cmds); 13 14 // 15 ProcessBuilder builder=new ProcessBuilder(new String[]{"/bin/sh","-c",cmd}); 16 //将服务器程序的输出定位到/dev/tty 17 builder.redirectOutput(new File("/dev/tty")); 18 builder.redirectError(new File("/dev/tty")); 19 builder.start(); // throws IOException 20 Thread.sleep(10000); 21 } 22 23 /** 24 * 检测服务是否存在 25 * 26 * @return 返回配置的java程序的pid 27 * @return pid >0 返回的是 pid <=0 代表指定java程序未运行 28 * **/ 29 public int checkServer() throws Exception { 30 int pid = -1; 31 Process process = null; 32 BufferedReader reader = null; 33 process = Runtime.getRuntime().exec("jps -l"); 34 reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 35 String line; 36 while ((line = reader.readLine()) != null) { 37 String[] strings = line.split("\\s{1,}"); 38 if (strings.length < 2) 39 continue; 40 if (strings[1].contains(servername)) { 41 pid = Integer.parseInt(strings[0]); 42 break; 43 } 44 } 45 reader.close(); 46 process.destroy(); 47 return pid; 48 } 49 } |
3、GuardAMain实现
1 public class GuardAMain { 2 public static void main(String[] args) throws Exception { 3 GuardA guardA = new GuardA(); 4 Configure configure = new Configure(); 5 GuardServer server = new GuardServer(configure.getServername()); 6 while (true) { 7 // 如果GuardB未运行 运行GuardB 8 if (!guardA.checkGuardB()) { 9 System.out.println("Start GuardB....."); 10 Runtime.getRuntime().exec(configure.getStartguardb()); 11 } 12 // 检测服务器存活 13 if (server.checkServer() <= 0) { 14 boolean isServerDown = true; 15 // trip check 16 for (int i = 0; i < 3; i++) { 17 // 如果服务是存活着 18 if (server.checkServer() > 0) { 19 isServerDown = false; 20 break; 21 } 22 } 23 if (isServerDown) 24 server.startServer(configure.getStartserver()); 25 } 26 Thread.sleep(configure.getInterval()); 27 } 28 } 29 } |
4、Shutdown实现
1 public class ShutDown { 2 public static void main(String[] args) throws Exception { 3 Configure configure = new Configure(); 4 System.out.println("Shutdown Guards.."); 5 for (int i = 0; i < 3; i++) { 6 Process p = Runtime.getRuntime().exec("jps -l"); 7 BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); 8 String line; 9 while ((line = reader.readLine()) != null) { 10 if (line.toLowerCase().contains("Guard".toLowerCase())) { 11 String[] strings = line.split("\\s{1,}"); 12 int pid = Integer.parseInt(strings[0]); 13 Runtime.getRuntime().exec(configure.getKillcmd() + " " + pid); 14 } 15 } 16 p.waitFor(); 17 reader.close(); 18 p.destroy(); 19 Thread.sleep(2000); 20 } 21 System.out.println("Guards is shutdown"); 22 } 23 } |
5、GuardB与GuardA类似
五、下载与使用
项目文件夹:guard_demo
下载地址:http://pan.baidu.com/s/1bn1Y6BX
背景:项目上一SharePoint网站分为SharePoint前端与
数据库,前端放在实体机上,数据库放在VM9虚拟机中。
环境:
Windows Server 2008 r2+sp1, SharePoint Server 2013 Enterprise,
SQL Server 2008 r2+sp1, VM9虚拟机
存在的问题:虚拟机中实际上只有70G大小的文件,但是虚拟机在磁盘上的空间有700多G,严重占用资源,在网上尝试用vmware-vdiskmanager.exe –k命令、VMwareToolboxCmd.exe disk shrinkonly命令基本上没有作用(其中第二条命令压缩一个几十G的虚拟机差不多要十几个小时,结果才压缩了几G)
解决思路:新建一个虚拟机,安装SQL Server,备份SiteCollection,还原SiteCollection到新数据库,删除原有虚拟机。
以下是我所在项目的迁移过程:
1、备份原网站上的
web.config,CKFinder/CKEditer文件
2、备份SiteCollection,命令如下:
backup-spsite -identity http://spserver -Path c: \a.bak -force
3、新建虚拟机,并打上补丁,设置虚拟机允许域账户访问,设置IP
4、用域账户通过IP登录新虚拟机,安装SQL Server 2008 r2,并打上补丁
5、运行SharePoint配置向导,关闭原场连接,新建场连接到新数据库(一般第一次会失败,失败后再运行一次即可)
6、还原SiteCollection,命令如下:
restore-spsite -identity http://spserver -Path c:\a.bak -databaseserver spserver -databaseName WSS_Content -force
7、将代码中的dll放到IIS的bin文件夹下面
8、重新部署wsp包,命令如下:
"C:\Program Files\Common Files\Microsoft Shared\web server extensions\15\BIN\stsadm.exe" -o deletesolution -name Jurassic.NRC.wsp -override
"C:\Program Files\Common Files\Microsoft Shared\web server extensions\15\BIN\stsadm.exe" -o addsolution -filename C:\Users\Administrator\Desktop\Jurassic.NRC.wsp
"C:\Program Files\Common Files\Microsoft Shared\web server extensions\15\BIN\stsadm.exe" -o deploysolution -name Jurassic.NRC.wsp -url http://sp2014 -allowgacdeployment -immediate –force
9、设置网站最大上传文件大小
10、设置列表阀值
11、重新配置Office Web Apps
以上步骤是我迁移我所在项目的全过程,欢迎相互交流!
1、查找当前目录下所有以.tar结尾的文件然后
移动到指定目录:
find . -name “*.tar” -exec mv {} ./backup/ ;
查找当前目录30天以前大于100M的LOG文件并删除。
find . -name "*.log" –mtime +30 –type f –size +100M |xargs rm –rf {} ;
写一个脚本查找最后创建时间是3天前,后缀是*.log的文件并删除。
find . -mtime +3 -name "*.log" |xargs rm -rf {} ;
写一个脚本将某目录下大于100k的文件移动至/tmp下。
find . -size +100k -exec mv {} /tmp ;
2、批量解压当前目录下以.zip结尾的所有文件到指定目录:
for i in `find . –name “*.zip” –type f `
do
unzip –d $i /data/www/img/
done
如何去掉行首的.字符: sed -i 's/^.//g' test.txt
在行首添加一个a字符: sed 's/^/a/g' test.txt
在行尾添加一个a字符: sed 's/$/a/' tets.txt
在特定行后添加一个c字符: sed '/wuguangke/ac' test.txt
在行前加入一个c字符: sed '/wuguangke/ic' test.txt
4、如何判断某个目录是否存在,不存在则新建,存在则打印信息。
if
[ ! –d /data/backup/ ];then
Mkdir –p /data/backup/
else
echo "The Directory already exists,please exit"
fi
注解:if …;then …else ..fi:为if条件语句,!叹号表示反义“不存在“,-d代表目录。
5、监控
linux磁盘根分区,如果根分区空间大于等于90%,发送邮件给Linux SA
(1)、打印根分区大小
df -h |sed -n '//$/p'|awk '{print $5}'|awk –F ”%” '{print $1}'
(2)、if条件判断该大小是否大于90,如果大于90则发送邮件报警
while sleep 5m
do
for i in `df -h |sed -n '//$/p' |awk '{print $5}' |sed 's/%//g'`
do
echo $i
if [ $i -ge 90 ];then
echo “More than 90% Linux of disk space ,Please Linux SA Check Linux Disk !” |mail -s “Warn Linux / Parts is $i%”
wugk@map.com
fi
done
done
6、统计Nginx访问日志,访问量排在前20 的 ip地址:
cat access.log |awk '{print $1}'|sort|uniq -c |sort -nr |head -20
7、sed另外一个用法找到当前行,然后在修改该行后面的参数:
sed -i '/SELINUX/s/enforcing/disabled/' /etc/selinux/config
Sed冒号方式 sed -i ‘s:/tmp:/tmp/abc/:g’test.txt意思是将/tmp改成/tmp/abc/。
11、打印出一个文件里面最大和最小值:
cat a.txt |sort -nr|awk ‘{}END{print} NR==1′
cat a.txt |sort -nr |awk ‘END{print} NR==1′
这个才是真正的打印最大最小值:sed ‘s/ / /g’ a.txt |sort -nr|sed -n ’1p;$p’
13、修改文本中以jk结尾的替换成yz:
sed -e ‘s/jk$/yz/g’ b.txt
14、网络抓包:tcpdump
tcpdump -nn host 192.168.56.7 and port 80 抓取56.7通过80请求的数据包。
tcpdump -nn host 192.168.56.7 or ! host 192.168.0.22 and port 80 排除0.22 80端口!
16、显示最常用的20条命令:
cat .bash_history |grep -v ^# |awk ‘{print $1}’ |sort |uniq -c |sort -nr |head -20
19、写一个防火墙配置脚本,只允许远程主机访问本机的80端口。
iptables -F
iptables -X
iptables -A INPUT -p tcp --dport 80 -j accept
iptables -A INPUT -p tcp -j REJECT
或者
iptables -A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
20、写一个脚本进行nginx日志统计,得到访问ip最多的前10个(nginx日志路径:/home/logs/nginx/default/access.log)。
cd /home/logs.nginx/default
sort -m -k 4 -o access.logok access.1 access.2 access.3 .....
cat access.logok |awk '{print $1}'|sort -n|uniq -c|sort -nr |head -10
21.写出下列命令的含义
(1)MaxKeepAliveRequests 100 连接的最大请求数
(2)Options FollowSymLinks 允许192.168.1.1可以列目录
Order Deny Allow
Deny from all
Allow from 192.168.1.1
22.替换文件中的目录
sed 's:/user/local:/tmp:g' test.txt
或者
sed -i 's//usr/local//tmp/g' test.txt
最近
工作上的活就是研究一下如何为一个历史代码工程添加
单元测试,已经做完了,就想抛砖引玉和大家分享一下结果,听听大家的反馈。
该工程目前还是VS2010下的C#代码,虽然大量使用了继承,封装和多态,但对接口的应用非常少,所以基本上没办法用常见的Mock框架(如Moq, Rhino Mock,等)来写单元测试。
考虑下来,解决方案无非两种:一是重构现有代码,通过接口(interface)来实现依赖注入(Dependency Injection, DI);二是寻找无需通过接口直接支持虚拟实体类的Mock框架,用来拦截对依赖对象的访问,返回预定的结果。采用方案一的话,优化了代码架构,从长远来说更好一些,但是工作量较大,还需要做大量的测试以保证不会改变现有代码功能。采用方案二的话,则不需要对现有代码做任何改动,只需要直接创建测试代码就好了。因此方案二还是很有吸引力的,我就按这个方向做了一些研究。
我发现市面上暂时还没有开源的Mock框架(.NET单元测试相关)支持虚拟实体类,而收费的我也只找到Microsoft Fakes, TypeMock Isolator和Telerik JustMock这三个。三者虽然用法上有些差异,但是主要功能比较接近,差别主要体现在以下几个方面:
1.从价格上来看:
Microsoft Fakes,没有免费版,只有在VS2012/VS2013的Premium和Ultimate版中才能用,而我们的开发环境用的主要是Professional版的,升级到相应版本的话成本很高(VS2013的话
Windows Store差价$1770,MSDN Subscription差价$4920)
Telerik JustMock满足需求的版本价格是每个开发者$399,包含一年免费升级+支持,(参见Buy JustMock)
TypeMock Isolator满足需求的版本价格是$799每个license,$150每年升级+支持,(参见Isolator Pricing)
由于Fakes价格太高,而且还需要升级现有代码,我们直接就不考虑了。当然如果已经在用相应版本的话,还是可以考虑的,毕竟
微软自己的东西还是用起来最方便的。
2.从功能上看,根据我们的一个测试用例分别用TypeMock和JustMock试写了一下,发现JustMock更强大一些。 我们的这个测试用例是要测某个时间值由于特殊原因不包含年份,因此从客户端传到位于不同时区的服务端时,在跨年时会被解析到错误的年份。比如:客户端位于GMT+8在2014/01/01 07:00:00时传了01/01 07:00:00过来,而服务端位于GMT,当时是2013/12/31 23:00:00,这个时间就会被解析成2013/01/01 07:00:00。解析调用的是DateTime.ParseExact()方法。TypeMock对MSCorLib中类的虚拟只提供了有限的支持,不支持这个方法的虚拟。而JustMock在这方面则做得好一些,能够支持该方法。
所以,我的最终结论是要么重构代码,使用接口来实现依赖注入,那么基本上各种Mock框架都能用;要么就买JustMock,不用动现有代码,直接加单元测试就好了。
内存泄漏的问题,在
百度是遇到最多的,阿里相对少点。与内存泄漏斗争了很久,我总结下常用的一些有效
测试方法吧。
1、valgrind,这是非常好用的工具,虽然参数很多,输出结果较多,但是只要认真看下,就很容易发现问题,报告是很详细的,不要被吓倒。valgrind检测的内存泄漏是非常准的,可以精确定位到代码行甚至是变量。valgrind基于valginrd core框架,这是个非常有强大的框架,他的作用不仅仅在于检测内存泄漏的,强烈建议测试新手通读下全部的文档。valgind自己也会有误报和漏报,所有具体场景需要具体分析。报告中一旦出现definitely lost的标记,就表示一定会有内存泄漏,泄漏的字节数也会报告出来,可以根据泄漏的内存大小和请求次数计算出到底是那个变量没有释放。
2、利用pmap+gdb,pmap可以列出特定进程的所有内存分配的地址和大小,通过gdb就可以直接看这些地址属于什么变量,通过统计这些内存地址的大小,就可以很容易的发现问题。利用自动化的gdb调试工具也可以很方便的定位。
3、其他的还包括memprof、商业工具Purify
IBM出品,官方宣传说的不错,但是这种不开放的技术,在业界得不到认可,国内大公司一般那都不用,只有人傻钱多的公司在用。
4、利用一些trace工具,比如ptrace,strace之类的工具,这些trace工具会追踪特定的api,只需要统计malloc和free的调用次数就可以简单的发现是否有泄漏,但是无法定位代码行。另外还有一个更高深的工具,SystemTap,这个在国内应用还不多,但是非常厉害,可以方便hook程序的关键逻辑并插入探针。从而可以方便的检测内存泄漏。Systemtap目前还不通用,而且安装复杂,暂时不推荐使用,可以关注下,过几年可能会大规模应用。
valgrind是首选,因为他的设计就是为了解决所有的c++的内存问题。一些valgrind不能简单发现的,我一般会review代码,然后通过gdb自动调试技术来发现问题。通过valgrind+gdb,可以解决所有的内存泄漏。
另外,内存的泄漏也并不完全是没有及时的free,还有可能是其他的原因,比如设计问题等。需要靠一定的开发经验判断。
要尽量把静态测试和动态测试尽早的加入到持续集成中,以尽早的发现问题,不然一旦代码复杂,追查的成本就会增大。
Web 应用的基础概念
在讨论 Web 应用安全之前,先简单介绍一下 Web 应用基础概念,这样便于理解为什么 Web 应用是脆弱的,容易受到攻击。
1、 什么是 Web 应用
Web 应用是由动态脚本、编译过的代码等组合而成。它通常架设在 Web 服务器上,用户在 Web 浏览器上发送请求,这些请求使用 HTTP 协议,经过因特网和企业的 Web 应用交互,由 Web 应用和企业后台的
数据库及其他动态内容通信。
2、 Web 应用的架构
尽管不同的企业会有不同的 Web 环境搭建方式,一个典型的 Web 应用通常是标准的三层架构模型,如图 1 所示。
图 1: Web 应用通常是标准的三层架构模型
Web
在这种最常见的模型中,客户端是第一层;使用动态 Web 内容技术的部分属于中间层;数据库是第三层。用户通过 Web 浏览器发送请求(request)给中间层,由中间层将用户的请求转换为对后台数据的查询或是更新,并将最终的结果在浏览器上展示给用户。
Web 应用安全全景
当讨论起 Web 应用安全,我们经常会听到这样的回答:
“我们使用了防火墙”、“我们使用了网络脆弱扫描工具”、“我们使用了 SSL 技术”、“我们每个季度都会进行渗透
测试”……所以,“我们的应用是安全的”。现实真是如此吗?让我们一起来看一下 Web 应用安全的全景图。
图 2: 信息安全全景
信息安全全景
在企业 Web 应用的各个层面,都会使用不同的技术来确保安全性。为了保护客户端机器的安全,用户会安装防病毒软件;为了保证用户数据传输到企业 Web 服务器的传输安全,通信层通常会使用 SSL(安全套接层)技术加密数据;企业会使用防火墙和 IDS(入侵诊断系统)/IPS(入侵防御系统)来保证仅允许特定的访问,不必要暴露的端口和非法的访问,在这里都会被阻止;即使有防火墙,企业依然会使用身份认证机制授权用户访问 Web 应用。
但是,即便有防病毒保护、防火墙和 IDS/IPS,企业仍然不得不允许一部分的通讯经过防火墙,毕竟 Web 应用的目的是为用户提供服务,保护措施可以关闭不必要暴露的端口,但是 Web 应用必须的 80 和 443 端口,是一定要开放的。可以顺利通过的这部分通讯,可能是善意的,也可能是恶意的,很难辨别。这里需要注意的是,Web 应用是由软件构成的,那么,它一定会包含缺陷(bugs),这些 bug 就可以被恶意的用户利用,他们通过执行各种恶意的操作,或者偷窃、或者操控、或者破坏 Web 应用中的重要信息。
因此可以看出,企业的回答,并不能真正保证企业的应用安全:
网络脆弱性扫描工具,由于它仅仅用来分析网络层面的漏洞,不了解应用本身,所以不能彻底提高 Web 应用安全性;
防火墙可以阻止对重要端口的访问,但是 80 和 443 端口始终要开放,我们无法判断这两个端口中通讯数据是善意的访问还是恶意的攻击;
SSL 可以加密数据,但是它仅仅保护了在传输过程中数据的安全性,并没有保护 Web 应用本身;
每个季度的渗透测试,无法满足处于不断变更之中的应用。
只要访问可以顺利通过企业的防火墙,Web 应用就毫无保留的呈现在用户面前。只有加强 Web 应用自身的安全,才是真正的 Web 应用安全解决之道。
常见的 Web 应用攻击
两个重要的国际应用安全组织
在讨论常见的 Web 应用攻击之前,我们需要先了解两个组织:WASC 和 OWASP。这两个组织在呼吁企业加强应用安全意识和指导企业开发安全的 Web 应用方面,起到了重要的作用。
Web Application Security Consortium(WASC),是一个由安全专家、行业顾问和诸多组织的代表组成的国际团体。他们负责为 WWW 制定被广为接受的应用安全标准。WASC 组织的关键项目之一是“Web 安全威胁分类”,也就是将 Web 应用所受到的威胁、攻击进行说明并归纳成具有共同特征的分类。该项目的目的是针对 Web 应用的安全隐患,制定和推广行业标准术语。WASC 将 Web 应用安全威胁分为如下六类:
Authentication(验证)
用来确认某用户、服务或是应用身份的攻击手段。
Authorization(授权)
用来决定是否某用户、服务或是应用具有执行请求动作必要权限的攻击手段。
Client-Side Attacks(客户侧攻击)
用来扰乱或是探测 Web 站点用户的攻击手段。
Command Execution(命令执行)
在 Web 站点上执行远程命令的攻击手段。
Information Disclosure(信息暴露)
用来获取 Web 站点具体系统信息的攻击手段。
Logical Attacks(逻辑性攻击)
用来扰乱或是探测 Web 应用逻辑流程的攻击手段。
可以通过如下的网址访问该组织网站,获得更多详细信息:[url]www.webappsec.org[/url]。也可以通过参考资料中链接,具体了解“Web 安全威胁分类”项目。
Open Web Application Security Project(OWASP),该组织致力于发现和解决不安全 Web 应用的根本原因。它们最重要的项目之一是“Web 应用的十大安全隐患”,总结了目前 Web 应用最常受到的十种攻击手段,并且按照攻击发生的概率进行了排序。这个项目的目的是统一业界最关键的 Web 应用安全隐患,并且加强企业对 Web 应用安全的意识。
图 3: Web 应用十大安全隐患
Web
可以通过如下的网址访问该组织。
IBM Rational,是上述两个组织的成员。
常见的 Web 应用攻击示例
在 OWASP 组织列举的十大 Web 应用安全隐患中,有两个概率最高的攻击手段,它们分别是“跨站点脚本攻击”(Cross-Site Scripting)和“注入缺陷”(Injection Flaws)。下面将通过举例来说明这两种攻击是如何实施的。
1、 跨站点脚本攻击
首先来看一下跨站点脚本的利用过程,如图 4。
图 4: 跨站点脚本攻击的过程
跨站点脚本攻击的过程
在上图中,恶意攻击者(这里使用 Evil.org 表示)通过 E-mail 或 HTTP 将某银行的网址链接发给用户(银行用 bank.com 表示),该链接中附加了恶意的脚本(上图步骤一);用户访问发来的链接,进入银行网站,同时,嵌在链接中的脚本被用户的浏览器执行(上图步骤二、三);用户在银行网站的所有操作,包括用户的 cookie 和 session 信息,都被脚本收集到,并且在用户毫不知情的情况下发送给恶意攻击者(上图步骤四);恶意攻击者使用偷来的 session 信息,伪装成该用户,进入银行网站,进行非法活动(上图步骤五)。
因此,只要 Web 应用中,有可被恶意攻击者利用执行脚本的地方,都存在极大的安全隐患。黑客们如果可以让用户执行他们提供的脚本,就可以从用户正在浏览的域中偷到他的个人信息、可以完全修改用户看到的页面内容、跟踪用户在浏览器中的每一个动作,甚至利用用户浏览器的缺陷完全控制用户的机器。
目前,跨站点脚本攻击是最大的安全风险。
2、 注入缺陷
目前的 Web 应用中,绝大多数都会向用户提供一个接口,用来进行权限验证、搜索、查询信息等功能。比如一个在线银行应用,首先会有对注册客户进行身份验证的登录界面,在正确登录后,会提供更多交互功能,如根据客户的银行卡号信息,查询客户的最近交易、转账细节等。这些都是注入缺陷的最佳利用场景。所谓注入缺陷,就是在上述场景中,用户输入的数据被当做命令和查询的一部分,送到后端的解释器中解释执行。如果用户的输入是正常合法的,Web 应用自然会返回正常合理的结果,但是,如果恶意攻击者,利用输入数据可被后台执行的原理,偷梁换柱,使用非法的输入,脆弱的 Web 应用会怎样呢?
下面我们举一个例子来说明注入缺陷是如何进行的。在一个交易网站中,用户必须输入产品 ID 号才可以查看该产品的详细信息。为了实现这个需求,通常会用 SQL 语句查询数据库来实现。开发人员在编写应用程序时,可能会使用如下的 SQL 语句来实现上述目的(这里仅为示例):
1) Select * from products where product_id = ` + 用户输入的 ID + `
这里的 products 是数据库中用来存放产品信息的表,+号表示 SQL 语句需要和用户输入的真实 ID 进行拼接。如果用户输入 325,则该语句在执行时变为:
Select * from products where product_id = ` 325 `
数据库会将 ID 为 325 的产品信息返回给用户。
2) 在界面上,需要用户输入产品 ID 的地方,黑客会输入如下数据:
` or `1`= `1
可以看到,黑客并没有输入正常合法的产品编号。
3) 通过黑客的非法输入,需要执行的 SQL 语句变为:
Select * from products where product_id = ` ` or `1`=`1`
可以看出,SQL 语句的意义就完全改变了,当产品 ID 为空或者 1=1 时,返回产品所有信息,而 1=1 是永远成立的条件,因此,黑客并没有输入任何产品编号,就可以返回数据库中所有产品的详细信息。
通过这个例子,我们可以看出,注入缺陷是风险非常高的安全漏洞,一旦 Web 应用中给用户提供了需要其输入数据的接口,就有可能遭到攻击,将后台的数据完全暴露在用户的面前。
上述说明的“跨站点脚本攻击”和“注入缺陷攻击”,是目前 Web 应用中比例最高的两种攻击手段,按照 OWASP 的项目排序,还有如下八种风险性较高的攻击方法:
Malicious File Execution(恶意文件执行);
Insecure Direct Object Reference(不安全的直接对象引用);
Cross-Site Request Forgery(跨站点的请求伪造);
Information Leakage and Improper Error Handling(信息泄漏和不正确的错误处理);
Broken Authentication & Session Management(损坏的认证和 Session 管理);
Insecure Cryptographic Storage(不安全的密码存储);
Insecure Communications(不安全的通信);
Failure to Restrict URL Access(未能限制 URL 访问)
在这里,我们就不过多的讨论这几种安全隐患,可以使用 3.1 节中提供的链接得到更多的描述信息。
构筑安全的 Web 应用
功能和性能,往往是我们衡量应用是否满足需求的指标,但是,对于载体为 Internet 的特殊应用-Web 应用而言,安全性也是必要的考量标准,皮之不存,毛将焉附?如果失去了安全性,即使功能再完备、性能再可靠的 Web 应用,一旦遭到黑客的攻击和破坏,一切都失去了意义。因此企业,尤其是提供 Web 应用的企业,一定要加强对应用安全的重视程度。
针对目前 Web 应用安全性不高的现状,IBM Rational 提出了构筑安全 Web 应用的解决方案。
加强全员应用安全性意识
一个根本、底层的战略手段就是加强企业全员的应用安全意识。正如前面所阐述过的,对于应用而言,无论是开发人员、测试人员、质量管理人员还是项目经理、企业高层,都会对其功能和性能做更多的关注,这也是由于早期应用多为 C/S 架构的应用,安全问题并不突出。但是在当今的环境,就不得不将安全作为应用质量的基础。
图 5 中功能、易用性、可靠性、性能、可支持性,是由 Rational Unified Process(RUP)定义的 FURPS 质量模型,它告诉我们应用的质量需要从这几个方面着手衡量,对于 Web 应用,就必须将安全性作为质量模型的基础条件。
图 5: 适于 Web 应用的质量模型
在Eclipse中配置Junit的方法有两种方式:
第一种方法:
1、下载junit的jar包,目前它的版本是junit3.8.1,可以从www.junit.org上下载。
2、在要使用Junit的project名上,点击properties--java build path-libraries, 点击Add External JARs,把Junit包点上就行了。如图所示,点击Add External JARs,选择相应的Junit包
第二种方法:
1、在要使用Junit的project名上,点击properties--java build path-libraries, 点击Add library,选择JunitT即可。如图1所示,在图1中点击Add library,在弹出的对话框中选择Jnuit,如图3所示,接下来选择junit版本即可。
(1)Eclpise中新建工程junittest
参考附件中的junittest-1.rar。其中定义了需要
单元测试的类:com.cvicse.test.junit. JunitTest
简单的步骤:
1、建一个project工程,工程名字为junittest。
2、建立一个包,名字为com.cvicse.test.junit。
3、建立一个类JunitTest,其内容如下
package com.cvicse.test.junit; public class JunitTest { public boolean method1(int comp){ //do something if(comp>5){ //do something return false; }else{ //do something return true; } }
(2)建立类junittest的junit类
选择要单元测试的类junittest,点击右健,选择”new”---“other”---“
java”—“junit”—“junit
test case”
1、选择第一个对话框中的属性,一般需要选择setup复选框和teardown
Setup函数用于测试的初始化,而teardown用于测试的销毁,前者相当于c++中的构造函数和析构函数。
2、进入第二个对话框,选择需要测试的类方法,一般只是选择被测试类本身的方法,
上图中的junittest中的method1就是被测试类的实现方法。
3、点击finish,自动生成该类的测试类JunitTestTest
4、生成的代码如下所示。
package com.cvicse.test.junit; import junit.framework.TestCase; public class JunitTestTest extends TestCase { protected void setUp() throws Exception { } protected void tearDown() throws Exception { } public void testMethod1() { fail("Not yet implemented" ); } }
第二种工程(建立与源码分离的工程)
建立与源码工程分离的test工程(测试代码与源码分离)
一般情况,按照“建立类的junit类”的方法建立的测试单元,一般与源码工程在同样的包中,混杂在一起,需要将其分离。
分离步骤:
1、 在工程名下新建立“source folder”,命名为test,拷贝/src/*.*到/test下,并删除非testcase类
2、 在源码/src中建立testcase类,然后
移动到/test对应的目录下,
3、 执行如果出现错误:”test type does not exist”,则需要将/test目录添加工程的source属性中:工程右健—“properties”—“java build path”—“source”—“add folder”—选择/test
4、 如果需要的配置文件在web-inf下,则需要拷贝到src目录下。
建立测试后的工程
建立单元测试的工程如:junitTest-2.rar
覆盖率测试和报告
目前覆盖率测试一般用jcoverage或者cobertura工具。
使用cobertura测试的配置文件为:cobertura/build.xml,为ant脚本。
工程参考:junitTest-3.rar;