1.对每条ab请求,可以采用下列步骤得到基准调校的结果:
1.配置并重启被测试的Apache httpd和/或Tomcat实例。
2.确信服务器日志没有启动错误。如果有,请按照前面描述的步骤修正错误。
3.在服务器重启后,运行ab命令请求,让服务器服务于首次请求。
4.作为基准调校的一部分,再次运行ab命令行。
5.在所有的请求都完成时,确信ab报告了零错误及non-2xx零响应。
6.在ab请求之间等待几秒钟,从而服务器能返回空闲状态。
7.注意在ab统计信息中每秒的请求数。
8.如果每秒的请求数发生了明显变化,请返回到第4步;否则,每秒的反复请求数就是基准调校的结果。如果该数字经过ab的10次反复请求仍继续发生明显变化,则放弃,记录最后的每秒请求数,作为基准调校的结果。
2.预编译的jsps.xml Ant构建文件
<project name="pre-compile-jsps" default="compile-jsp-servlets">
<!-- 私有属性 -->
<property name="webapp.dir" value="${basedir}/webapp-dir" />
<property name="tomcat.home" value="/opt/tomcat"/>
<property name="jspc.pkg.prefix" value="com.mycompany"/>
<property name="jspc.dir.prefix" value="com/mycompany"/>
<!-- 编译属性 -->
<property name="debug" value="on" />
<property name="debuglevel" value="lines,vars,source" />
<property name="deprecation" value="on" />
<property name="encoding" value="ISO-8859-1" />
<property name="optimize" value="off" />
<property name="build.compiler" value="modern"/>
<property name="source.version" value="1.5" />
<!-- 初始化路径 -->
<path id="jspc.classpath">
<fileset dir="${tomcat.home}/bin">
<include name="*.jar"/>
</fileset>
<fileset dir="${tomcat.home}/server/lib">
<include name="*.jar"/>
</fileset>
<fileset dir="${tomcat.home}/common/i18n">
<include name="*.jar"/>
</fileset>
<fileset dir="${tomcat.home}/common/lib">
<include name="*.jar"/>
</fileset>
<fileset dir="${webapp.dir}/WEB-INF">
<include name="lib/*.jar"/>
</fileset>
<pathelement location="${webapp.dir}/WEB-INF/classes"/>
<pathelement location="${ant.home}/lib/ant.jar"/>
<pathelement location="${java.home}/../lib/tools.jar"/>
</path>
<property name="jspc.classpath" refid="jspc.classpath"/>
<!-- ========================================== -->
<!-- 从JSP文件产生JAVA源码和web.xml文件 -->
<!-- ========================================== -->
<target name="generate-jsp-java-src">
<mkdir dir="${webapp.dir}/WEB-INF/jspc-src/${jspc.dir.prefix}"/>
<taskdef classname="org.apache.jasper.JspC" name="jasper2">
<classpath>
<path refid="jspc.classpath"/>
</classpath>
</taskdef>
<touch file="${webapp.dir}/WEB-INF/jspc-web.xml"/>
<jasper2 uriroot="${webapp.dir}"
package="${jspc.pkg.prefix}"
webXmlFragment="${webapp.dir}/WEB-INF/jspc-web.xml"
outputDir="${webapp.dir}/WEB-INF/jspc-src/${jspc.dir.prefix}"
verbose="1" />
</target>
<!-- ========================================= -->
<!-- 编译JSP servlet并由此产生Java类文件 -->
<!-- 由JspC任务产生的源码 -->
<!-- ========================================= -->
<target name="compile-jsp-servlets" depends="generate-jsp-java-src">
<mkdir dir="${webapp.dir}/WEB-INF/classes"/>
<javac srcdir="${webapp.dir}/WEB-INF/jspc-src"
destdir="${webapp.dir}/WEB-INF/classes"
includes="**/*.java"
debug="${debug}"
debuglevel="${debuglevel}"
deprecation="${deprecation}"
encoding="${encoding}"
optimize="${optimize}"
source="${source.version}">
<classpath>
<path refid="jspc.classpath"/>
</classpath>
</javac>
</target>
<!-- ========================================= -->
<!-- 清除所有预编译的JSP源码、类和jspc-web.xml -->
<!-- ========================================= -->
<target name="clean">
<delete dir="${webapp.dir}/WEB-INF/jspc-src"/>
<delete dir="${webapp.dir}/WEB-INF/classes/${jspc.dir.prefix}"/>
<delete file="${webapp.dir}/WEB-INF/jspc-web.xml"/>
</target>
</project>
3.记住虽然Tomcat是开源的,但它也是一个非常复杂的应用程序,因此在开始修改源码之前,一定要小心谨慎。如果决定要投入Tomcat源码开发,请使用Tomcat的邮件列表以分享您的想法,并与社区互动。
4.使用APR连接器,而不使用JIO或NIO的连接器,原因有以下几点:
1.HTTPS使用APR连接器可能会更快,因为APR连接器是叫做OpenSSL库文件的本地代码。而JIO和NIO连接器是纯粹的Java代码,并使用了纯粹的Java TLS/SSL编码,大家都知道,比OpenSSL要慢一些。但是,如果没有使用HTTPS,则不用关心这一点。
2.对于某些代码场合(主要是AJP),APR可能会更高效。对这种配置,具有最大吞吐量是比较重要的。
3.由于使用的sendfile(2)系统呼叫,所以APR被设计为处理大型静态文本文件(如提供媒体文件)时更高效。
4.任何连接器实现的所有底层网络编码都是固有的(native)(如JVM是在C/C++中编写的),因此,无论您是否更喜欢APR,取决于您是喜欢来自ASF还是JVM提供商提供的本地网络编码(native network code)。
5.APR一个被广泛接受的用于Web服务器的I/O实现,用C编程语言编写(供Apache httpd使用),而且工作正常。
6.在MS-Windows上,NIO连接器并没有真正起作用,因为NIO似乎在Windows上无法正常发挥作用,至少在使用Sun的Java VM时是这样。在其他操作系统上,这不成为问题。
7.APR使用了便携的、安全随机数产生器,从而Tomcat会话ID在Windows上默认是安全的。
8.APR还有一些其他功能是非常有用的,而核心Java平台并没有提供这些功能。关键是APR是不同于Java的实现,而且包含一组不同的功能。这些新功能可能会放在任何新版的APR中,而且可以修改APR连接器以充分利用这些功能。
5.如果想显示配置一个Connector以使用APR,则可按如下方式设置协议属性:
<Connector port="8080"
protocol="org.apache.coyote.http11.Http11AprProtocol"
disableUploadTimeout="false"
maxThreads="150" connectionTimeout="20000"
redirectPort="8443" />
6.若要让Apache httpd保护WEB-INF及META-INF目录,请在httpd.conf中追加下列内容:
<LocationMatch "/WEB-INF/">
AllowOverride None
deny from all
</LocationMatch>
<LocationMatch "/META-INF/">
AllowOverride None
deny from all
</LocationMatch>
把所有Web应用程中对.htaccess的请求都映射给叫做forbidden.jsp的JSP文件:
<servlet>
<servlet-name>htaccess</servlet-name>
<jsp-file>/forbidden.jsp</jsp-file>
</servlet>
<servlet-mapping>
<servlet-name>htaccess</servlet-name>
<url-pattern>*.htaccess</url-pattern>
</servlet-mapping>
7.一个简单的HttpServlet,它会在文件系统上创建一个文件,并输出文件成功写入的消息。
package com.oreilly.tomcat.servlets;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOTxception;
import java.io.PrintWriter;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* 该servlet尝试在Web应用程序的文档中写入文档根目录
*/
public class WriteFileServlet extends GenericServlet {
public void service(ServletRequest request, ServletResponse response)
throws IOException, ServletException
{
//尝试打开一个文件并写入。
String catalinaHome = "/opt/tomcat";
File testFile = new File(catalinaHome + "/webapps/ROOT", "test.txt");
FileOutputStream fileOutputStream = new FileOutputStream(testFile);
fileOutputStream.write(new String("testing...\n").getBytes());
fileOutputStream.close();
//如果执行到此,表示文件已成功地创建了。
PrintWriter out = response.getWriter();
out.println("File created successfully!");
}
}
为了便于在ROOT Web应用程序中编译、安装及测试,编写了下面这个servlet:
# mkdir $CATALINA_HOME/webapps/ROOT/WEB-INF/classes
# export CATALINA_HOME=/opt/tomcat# javac -classpath $CATALINA_HOME/lib/servlet-api.jar -d $CATALINA_HOME/webapps/ROOT/WEB-INF/classes WriteFileServlet.java
然后, 在ROOT Web应用程序的WEB-INF/web.xml部署描述符中加入servlet和servlet-mapping元素:
现在启用SecurityManager并重新启动Tomcat.
然后,访问URL http://localhost:8080/writefile。
8.编写tc-chroot init脚本,以便使用jbchroot而非chroot的绝对路径,并给jbchroot传入一个或多个开关参数来更改用户组和/或组:
#!/bin/sh
#
# chrooted Apache Tomcat servlet容器的linux init脚本。
#
# chkconfig: 2345 96 14
# 说明: Apache Tomcat servlet容器
# 进程名: tc-chroot
# config: /opt/chroot/tomcat/conf/tomcat-env.sh
#
APP_ENV="/opt/tomcat/conf/tomcat-env.sh"
# 如果存在,则寻找app config文件的源文件。
[ -r "$APP_ENV" ] && . "${APP_ENV}"
# 对应于Tomcat 启动/停止 脚本的路径。
TOMCAT_SCRIPT=$CATALINA_HOME/bin/catalina.sh
# 程序名
PROG="$0"
# 解决 links - $0 可以是一个软链接。
while [ -h "$PROG" ]; do
ls=`ls -ld "$PROG"`
link=`expr "$ls" : `.*-> \(.*\)$'`
if expr "$link" : '.*/.*' > /dev/null; then
PROG="$link"
else
PROG=`dirname "$PROG"`/"$link"
fi
done
PROG="`basename $PROG`"
case "$1" in
start)
echo -n "Starting $PROG: "
# Mount /proc.
mkdir -p /opt/chroot/proc
mount -t proc proc /opt/chroot/proc &>/dev/null
/usr/local/bin/jbchroot -U tomcat -- /opt/chroot \
/bin/bash -c "set -a; . $APP_ENV; \
$TOMCAT_SCRIPT start" &>/dev/null
let RETVAL=$?
if [ $RETVAL -eq 0 ]; then
echo "[ OK ]"
else
echo "[ FAILED ]"
fi
;;
stop)
echo -n "Stopping $PROG: "
/usr/local/bin/jbchroot -U tomcat -- /opt/chroot \
/bin/bash -c "set -a; . $APP_ENV; \
$TOMCAT_SCRIPT stop" &>/dev/null
let RETVAL=$?
if [ $RETVAL -eq 0 ]; then
# Give Tomcat some time to perperly stop all webapps.
sleep 3
# Unmount /proc.
umount /opt/chroot/proc &>/dev/null
echo "[ OK ]"
else
echo "[ FAILED ]"
fi
;;
*)
echo "Usage: tc-chroot {start|stop}"
exit 1
esac
9.存在SQL注入的一段代码:
//已经连接到数据库了,创建使用的Statement
Statement statement = connection.createStatement();
//创建用于用户登陆的包含SQL查询的常规字符串
// inserting the username and password into the String.
String queryString = "select * from USER_TABLE where USERNAME='" + username + "' and PASSWORD='" + password + "';";
// 用计划好的字符串执行该SQL查询
ResultSet resultSet = statement.executeQuery(queryString);
// 从数据库中返回结果行,表明用户已登录。
10.BadInputValve属性
className 此Valve实现的Java类名,必须设为com.oreilly.tomcat.valves.BadInputValve
escapeQuotes 在执行请求之前、决定此Valuve是否要转义请求参数中的任何引号(包括双引、单引号和反引号)。默认值为false
escapeAngleBrackets 在执行请求之前,决定此Valves是否要转移参数中的任何尖括号。默认值为false
escapeJavaScript 决定此Valve是否要转义请求参数中任何潜在威胁JavaScript函数与对象的引用。默认值为true
allow 以逗号分隔的一组常规表达式,使该Valve允许处理一个请求。可以不设定,表示指定none。如果没有设置allow而设置了一个或多个deny,则不允许处理请求
deny 以逗号分隔的一组常规表达式,使该Valve拒绝请求。如果在deny清单中,有一个常规表达式与参数名或值的一部分相匹配,则决绝请求。如果没有设定deny且没有设定allow,则表示允许所有请求并过滤其参数。如果没有设定deny,但设置了一个或多个allow,则只有一个或多个allow模式与参数名或值的一部分匹配时,该Valve才允许处理请求。
11.以下是请求所需并安装商业服务器X.509认证的简要步骤:
1.产生服务器端的密钥对,并存储在一个密钥库中;
2.根据密钥对产生认证签名请求(sertificate signing request, CSR).
3.要想从谁那里购买商业服务器授权证书(CA),就把CSR发送给他。
4.接收返回的CA认证及新签名的认证;
5.把该CA认证导入到所安装的Java的cacerts密钥库中。这将允许JVM把您的CA认证辨认为证书授权认证。
6.把签名服务器证书导入到已存储服务器密钥对的相同密钥库中。
12.$CATALINA_HOME/conf目录中的主要配置文件:
server.xml Tomcat主配置文件
web.xml servlet与其他适用于整个Web应用程序设置的配置文件,必须符合servlet规范的标准格式。
tomcat-users.xml Tomcat的UserDatabaseRealm用于认证的默认角色、用户以及密码清单。
catalina.policy Tomcat的Java安全防护策略文件。
context.xml 默认的context设置,应用于安装了Tomcat的所有主机的所有部署内容。
13.filter过滤器允许您用几个程序建立管道。过滤器可以在待定的URL模式交给目标的servlet之前,以及在执行servlet之后,用一段程序来处理这些URL模式。Filter元素有几个子元素:
元素 | 必要性 | 含义 |
icon | 可选的 | 用于在GUI工具中显示 |
filter-name | 必要的 | 用于filter-mapping的名称 |
display-name | 可选的 | 用于在GUI工具中显示 |
description | 可选的 | 用于在GUI工具中显示 |
filter-class | 必要的 | 过滤器的完整Java类名 |
init-param | 0或更多 | 此特定过滤器的初始化参数 |
14.servlet元素可以让您给servlet或JSP分配名称,以便用于servlet-mapping及指向一个servlet的其他元素中。
子元素:
子元素名 | 可允许的质量 | 含义 |
icon | 可选择 | 显示图形的图标 |
servlet-name | 必要 | 名称,如前所述 |
display-name | 可选择 | 表达GUI工具中的显示名称与描述信息 |
description | 可选择 | servlet的描述 |
servlet-class后jsp-file | 必要 | 一个被命名与描述的servlet或JSP名称 |
init-param | 0个或多个 | servlet专属的初始化参数 |
load-on-startup | 可选择 | 当启动Tomcat时加载servlet的顺序 |
run-as | 可选择 | 用来执行此servlet的用户角色名称 |
security-role-ref | 0个或多个 | 安全防护的角色(相关细节请参阅第2章) |
在Tomcat中,用来编译及执行所有Jsp的JspServlet,其url-pattern元素为:
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
</servlet-mapping>
15.捕捉给定Context(已改变URI)的任何请求,并将这些请求映射到JSP(以输出更新后的URI):
<web-app>
<servlet>
<servlet-name>Redirector</servlet-name>
<jsp-file>/redirector.jsp</jsp-file>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 将所有的请求都映射到Redirector servlet -->
<servlet-mapping>
<servlet-name>Redirector</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
16.session-config
<session-config>
<session-timeout>30</session-timeout>
</session-config>
17.如果想将符合*.foo的文件名映射到MIME类型的application/x-ian-test-file,则可以通过追加下列mime-mapping元素:
<mime-mapping>
<extension>foo</extension>
<mime-type>application/x-ian-test-file</mime-type>
</mime-mapping>
18.jdchroot.c源码:
/*
* jdchroot.c
* Linux 和 Solaris 上OpenBSD的chroot命令,由Jason Brittain提供
*/
#ifndef lint
static const char copyright[] =
"@(#) Copyright (c) 1988, 1993\n\
The Regents of the University of California. All rights reserved.\n";
#endif /* not lint */
#ifndef lint
#if 0
static const char sccsid[] = "@(#)chroot.c 8.1(Berkeley) 6/9/93";
#else
static const char rcsid[] = "$OpenBSD: chroot.c,v 1.7 2002/10/29 23:12:06 millert Exp $";
#endif
#endif /* not lint */
#include <ctype.h>
#include <errno.h>
#include <grp.h>
#include <limits.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int, char **);
void usage(char *);
static char *getToken(char **, const char *);
int main(int argc, char **argv)
{
struct group *gp;
struct passwd *pw;
const char *shell;
char *fulluser, *user, *group, *grouplist, *endp, *p;
gid_t gid, gidlist[NGROUPS_MAX];
uid_t uid;
int ch, gids;
unsigned long ul;
char *myname;
myname = argv[0];
gid = 0;
uid = 0;
gids = 0;
user = fulluser = group = grouplist = NULL;
while ((ch = getopt(argc, argv, "G:g:U:u:")) != -1) {
switch(ch) {
case 'U':
fulluser = optarg;
if (*fulluser == '\0')
usage(myname);
break;
case 'u':
user = optarg;
if (*user == '\0')
usage(myname);
break;
case 'g':
group = optarg;
if (*group == '\0')
usage(myname);
break;
case 'G':
grouplist = optarg;
if (*grouplist == '\0')
usage(myname);
break;
case '?':
default:
usage(myname);
}
}
argc -= optind;
argv += optind;
if (argc < 1)
usage(myname);
if (fulluser && (user || group || grouplist)) {
fprintf(stderr, "%s: The -U option may not be specified with any other option\n",
myname);
exit(-1);
}
if (group != NULL) {
if ((gp = getgrnam(group)) != NULL)
gid = gp->gr_gid;
else if (isdigit((unsigned char)*group)) {
errno = 0;
ul = strtoul(group, &endp, 10);
if (*endp != '\0' || (ul == ULONG_MAX && errno == ERANGE)) {
fprintf(stderr, "%s: Invalid group ID `$s'\n", myname, group);
exit(-1);
}
gid = (gid_t)ul;
}
else {
fprintf(stderr, "%s: No such group `%s'\n", myname, group);
exit(-1);
}
if (grouplist != NULL)
gidlist[gids++] = gid;
if (setgid(gid) != 0) {
fprintf(stderr, "%s: setgid", myname);
exit(-1);
}
}
while ((p = getToken(&grouplist, ",")) != NULL && gids < NGROUPS_MAX) {
if (*p == '\0')
continue;
if ((gp = getgrnam(p)) != NULL)
gidlist[gids] = gp->gr_gid;
else if (isdigit((unsigned char)*p)) {
errno = 0;
ul = strtoul(p, &endp, 10);
if (*endp != '\0' || (ul == ULONG_MAX && errno == ERANGE)) {
fprintf(stderr, "%s: INvalid group ID `%s'\n", myname, p);
exit(-1);
}
gidlist[gids] = (gid_t)ul;
}
else {
fprintf(stderr, "%s: No such group `%s'\n", myname, p);
exit(-1);
}
/*
* 如果指定,则忽视主群组,笔者在上面已经追加了一个主群组.
*/
if (group == NULL || gidlist[gids] != gid)
gids++;
}
if (p != NULL && gids == NGROUPS_MAX) {
fprintf(stderr, "%s: Too many supplementary groups provided\n", myname);
exit(-1);
}
if (gids && setgroups(gids, gidlist) != 0){
fprintf(stderr, "%s: setgroups", myname);
exit(-1);
}
if (user != NULL) {
if ((pw = getpwnam(user)) != NULL)
uid = pw->pw_uid;
else if (isdigit((unsigned char)*user)) {
errno = 0;
ul = strtoul(user, &endp, 10);
if (*endp != '\0' || (ul = ULONG_MAX && errno == ERANGE)) {
fprintf(stderr, "%s: Invalid user ID `%s'\n", myname, user);
exit(-1);
}
uid = (uid_t)ul;
}
else {
fprintf(stderr, "%s: No such user `%s'\n", myname, user);
exit(-1);
}
}
if (fulluser != NULL) {
if ((pw = getpwnam(fulluser)) == NULL) {
fprintf(stderr, "%s: No such user `%s'\n", myname, fulluser);
exit(-1);
}
uid = pw->pw_uid;
gid = pw->pw_gid;
if (setgid(gid) != 0) {
fprintf(stderr, "%s: setgid\n", myname);
exit(-1);
}
if (initgroups(fulluser, gid) == -1) {
fprintf(stderr, "%s: initgroups\n", myname);
exit(-1);
}
}
if (chroot(argv[0]) != 0 || chdir("/") != 0) {
fprintf(stderr, "%s: %s\n", myname, argv[0]);
exit(-1);
}
if ((user || fulluser) && setuid(uid) != 0) {
fprintf(stderr, "%s: setuid\n", myname);
exit(-1);
}
if (argv[1]) {
execvp(argv[1], &argv[1]);
fprintf(stderr, "%s: %s\n", myname, argv[1]);
exit(-1);
}
if ((shell = getenv("SHELL")) == NULL)
shell = "/bin/sh";
execlp(shell, shell, "-i", (char *)NULL);
fprintf(stderr, "%s, %s\n", myname, shell);
/* 没有走样 */
}
void usage(char *myname)
{
(void)fprintf(stderr, "usage: %s [-g group] [-G group,group,...] "
"[-u user] [-U user] newroot [command]\n", myname);
exit(1);
}
/* 这是Solaris上丢失的strsep的替代品. */
static char *getToken(char **str, const char *delims)
{
char *token;
if (*str == NULL) {
/* No more tokens */
return NULL;
}
token = *str;
while (**str != '\0') {
if (strchr(delims, **str) != NULL) {
**str = '\0';
(*str)++;
return token;
}
(*str)++;
}
/* There is no other token */
*str = NULL;
return token;
}
19.BadInputValve.java
package com.oreilly.tomcat.valve;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.util.ParameterMap;
import org.apache.catalina.valves.RequestFilterValve;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* 过滤恶意用户从HTT请求产生的输入,
* 避免遭受包括Cross Site Scripting(XSS)、SQL注入、
* 和HTML注入漏洞及其他黑客攻击。
*/
public class BadInputValve extends RequestFilterValve {
// -----------------------------------------静态变量
/**
* 记录日志的Log实例。
*/
private static Log log = LogFactory.getLog(BadInputValve.class);
/**
* 关于这一实现的描述信息
*/
protected static String info =
"com.oreilly.tomcat.valve.BadInputValve/2.0";
/**
* 空字符串数组,重用为toArray()的类型指示器。
*/
private static final String[] STRING_ARRAY = new String[0];
// -----------------------------------------实例变量
/**
* 该标志判断转义引号(escape quotes)是否为请求的一部分。
*/
protected boolean escapeQuotes = false;
/**
* 该标志判断转义尖括号(escape angle brackets)是否为请求的一部分。
*/
protected boolean escapeAngleBrackets = false;
/**
* 该标志判断转义JavaScript (escape JavaScript)函数和对象名是否为请求的一部分.
*/
protected boolean escapeJavaScript = false;
/**
* 替代映射 (映射、替代的常规表达式)。
* 用于替代单引号(') 和双引号(")的转义等同物。
* 使其无法用于恶意目的。
*/
protected HashMap<String, String> quotesHashMap =
new HashMap<String, String>();
/**
* 替代映射(映射、替代的常规表达式)。
* 用于替代尖括号(<>)的转义等同物。使其无法用于恶意目的。
*/
protected HashMap<String, String> angleBracketsHashMap =
new HashMap<String, String>();
/**
* 替代映射( 映射、替代的常规表达式)。
* 用于替代存在安全隐患的JavaScript函数调用转义等同物。
* 使其无法用于恶意目的。
*/
protected HashMap<String, String> javaScriptHashMap =
new HashMap<String, String>();
/**
* 用于过滤参数的常规表达式映射。
* 如果找到了检索的字符串,则关键在于检查的常规表达式字符串,
* 及用于修改参数的常规表达式字符串。
*/
protected HashMap<String, String> parameterEscapes =
new HashMap<String, String>();
// ------------------------------构造器
/**
* 构建这个类的新实例,包括默认属性值。
*/
public BadInputValve() {
super();
//输出regex转义映射。
quotesHashMap.put("\"", """);
quotesHashMap.put("\'", "'");
quotesHashMap.put("`", "`");
angleBracketsHashMap.put("<", "<");
angleBracketsHashMap.put(">", ">");
javaScriptHashMap.put(
"document(.*)\\.(.*)cookie", "document.cookie");
javaScriptHashMap.put("eval(\\s*)\\(", "eval(");
javaScriptHashMap.put("setTimeout(\\s*)\\(", "setTimeout$1(");
javaScriptHashMap.put("setInterval(\\s*)\\(", "setInterval$1(");
javaScriptHashMap.put("execScript(\\s*)\\(", "execScript$1(");
javaScriptHashMap.put("(?i)javascript(?-i):", "javascript:");
log.info("BadInputValve instantiated.");
}
// --------------------------------------------属性
/**
* 在执行请求之前,获取一个标志,决定该门限(Valve)是否将转义作为请求一部分的引号(双引号和单引号)。
*/
public boolean getEscapeQuotes() {
return escapeQuotes;
}
/**
* 在执行请求之前,获取一个标志,决定该门限(Valve)是否将转义作为请求一部分的引号(双引号和单引号)。
*/
/*
* @param escapeQuotes
*/
public void setEscapeQuotes(boolean escapeQuotes) {
this.escapeQuotes = escapeQuotes;
if (escapeQuotes) {
//转义所有引号。
parameterEscapes.putAll(quotesHashMap);
}
}
/**
* 在执行请求之前,获取一个标志,决定该门限(Valve)是否将转义作为请求一部分的尖括号(<>)。
*/
public boolean getEscapeAngleBrackets() {
return escapeAngleBrackets;
}
/**
*在执行请求之前,获取一个标志,决定该门限(Valve)是否将转义作为请求一部分的尖括号(<>)
*/
public boolean getEscapeAngleBrackets() {
return escapeAngleBrackets;
}
/**
* 在执行请求之前,获取一个标志,决定该门限(Valve)是否将转义作为请求一部分的尖括号(<>)。
* @param escapeAngleBrackets
*/
public void setEscapeAngleBrackets(boolean escapeAngleBrackets) {
this.escapeAngleBrackets = escapeAngleBrackets;
if (escapeAngleBrackets) {
// 转义所有尖括号.
parameterEscapes.putAll(angleBracketsHashMap);
}
}
/**
* 在执行请求之前,获取一个标志,决定该门限(Valve)是否将转义作为请求一部分
*的对JavaScript函数的潜在危险引用。
*/
public boolean getEscapeJavaScript() {
return escapeJavaScript;
}
/**
* 在执行请求之前,获取一个标志,决定该门限(Valve)是否将转义作为请求
* 一部分的对JavaScript函数和对象的潜在危险引用。
*
* @param escapeJavaScript
*/
public void setEscapeJavaScript(boolean escapeJavaScript) {
this.escapeJavaScript = escapeJavaScript;
if (escapeJavaScript) {
// Escape potentially dangerous JavaScript method calls.
parameterEscapes.putAll(javaScriptHashMap);
}
}
/**
* 返回关于该门限(Valve)实现的描述信息。
*/
public String getInfo() {
return info;
}
// ------------------------------------公共方法
/**
* 在恶意用户进入web应用程序之前,消除请求参数。
*
* @param request 要被处理的servlet请求
* @param response 要被创建的servlet响应
*
* @excaption IOException 如果发生输入/输出错误
* @exception ServletException 如果发生servlet错误
*/
@Override
public void invoke(Request request, Response response)
throws IOException, ServletException {
//跳过对non-HTTP请求与响应的过滤。
if (!(request instanceof HttpServletRequest) ||
!(response instanceof HttpServletResponse)) {
getNext().invoke(request, response);
return;
}
//基于allow和deny,只让请求通过。
if (processAllowAndDenies(request, response)) {
//过滤存在安全隐患的JavasScript代码,
//从而在Tomcat开始执行请求之前,
//清楚了黑客的请求。
filterparameters(request);
//执行该请求
getNext().invoke(request, response);
}
}
/**
* 使用(摘要)RequestFilterValve 的泛函性
* 以终止在参数名和参数值中包含禁用字符串模式的请求
*
*
* @param request 要被处理的servlet请求
* @param response 要被创建的servlet响应
*
* @exception IOException 如果发生输入/输出错误
* @exception ServletException 如果发生servlet错误
*
* @return false 如果请求禁止,否则为true。
*/
public boolean processAllowsAndDenies(Request request, Response response)
throws IOException, ServletException {
ParameterMap paramMap =
(ParameterMap) ((HttpServletRequest) request).getParameterMap();
//循环参数清单。
Iterator y = paramMap.keySet().iterator();
while (y.hasNext()) {
String name = (String) y.next();
String[] values = ((HttpServletRequest)
request).getParameterValues(name);
//查看名字中是否包含禁止模式。
if (!checkAllowsAndDenies(name, response)) {
return false;
}
// 检查该模式的参数值。
if (values != null) {
for (int i=0; i<values.length; i++) {
String value = values[i];
if (!checkAllowsAndDenies(value, response)) {
return false;
}
}
}
}
// 没有参数则导致拒绝,将继续响应请求。
return true;
}
/**
* 执行为该Valve配置的过滤,
* 映射指定的请求属性。如果允许处理请求,
* 则该方法返回true,否则,
* 该方法发送一条Forbidden错误响应网页,并返回false。
*
* <br><br>
*
* 该方法借鉴了RequestFilterValve.process()的大部分精华,
* 只有这一方法拥有boolean返回类型而不用调用
* getNext().invoke(request, response).
*
* @param property 要过滤的请求属性。
* @param response 要被处理的servlet响应。
*
* @exception IOException 如果发生一个输入/输出错误
* @exception ServletException 如果发生一个error错误
*
* @return true 如果仍然允许处理该请求。
*/
public boolean checkAllowsAndDenies(String property, Response response)
throws IOException, ServletException {
// 如果既没有deny也没有allow,则处理该请求。
if (denies.length == 0 && allows.length == 0) {
return true;
}
// 如果有的话,则检查deny模式。
for (int i=0; i<denies.length; i++) {
Matcher m = denies[i].matcher(property);
if (m.find()) {
ServletResponse sres = response.getResponse();
if (sres instanceof HttpServletResponse) {
HttpServletResponse hres = (HttpServletResponse) sres;
hres.sendError(HttpServletResponse.SC_FORBIDDEN);
return false;
}
}
}
//如果有的话,则检查allow模式
for (int i=0; i<allows.length; i++) {
Matcher m = allows[i].matcher(property);
if (m.find()) {
return true;
}
}
// Allow, 如果指定了denies而没有指定allows
if (denies.length > 0 && allows.length == 0) {
return true;
}
// 否则,拒绝响应请求。
ServletResponse sres = response.getResponse();
if (sres instanceof HttpServletResponse) {
HttpServletResponse hres = (HttpServletResponse) sres;
hres.sendError(HttpServletResponse.SC_FORBIDDEN);
}
return false;
}
/**
* 对具有安全隐患的内容,如果找到了转义字符,则过滤所有现存的参数。
*
* @param request 包含该参数的请求。
*/
public void filterParameters(Request request) {
ParameterMap paramMap =
(ParameterMap) ((HttpServletRequest) request).getParameterMap();
// 解除参数映射上的锁,从而可以修改该参数。
paramMap.setLocked(false);
// 循环每个替代模式。
Iterator escapesIterator = parameterEscapes.keySet().iterator();
while (escapesIterator.hasNext()) {
String patternString = (String) escapesIterator.next();
Pattern pattern = Pattern.compile(patternString);
// 循环参数列表。
@SuppressWarnings("unchecked")
String[] paramNames =
(String[]) paramMap.keySet().toArray(STRING_ARRAY);
for (int i=0; i<paramNames.length; i++) {
String name = paramNames[i];
String[] values = ((HttpServletRequest)request).getParameterValues(name);
//查看名字中是否包含该模式。
boolean nameMatch;
Matcher matcher = pattern.matcher(name);
nameMatch = matcher.find();
if (nameMatch) {
// 参数名与模式匹配,从而可以用修改名字,
// 追加参数后作为新名字、移除老名字
// 等方法修补之。
String newName = matcher.replaceAll(
(String) parameterEscapes.get(patternString));
request.addParameter(newName, values);
paramMap.remove(name);
log.warn("Parameter name " + name +
" matched pattern \"" + patternString +
"\". Remote addr: " +
((HttpServletRequest) request).getRemoteAddr( ));
}
// 检查该模式的参数值。
if (values != null) {
for (int j=0; j<values.length; j++) {
String value = values[j];
boolean valueMatch;
matcher = pattern.matcher(value);
valueMatch = matcher.find( );
if (valueMatch) {
// 匹配的值,所有修改它,然后赋回数组
String newValue;
newValue = matcher.replaceAll((String) parameterEscapes.get(patternString));
values[j] = newValue;
log.warn("Parameter \"" + name +
"\"'s value \"" + value +
"\" matched pattern \"" +
patternString + "\". Remote addr: " +
((HttpServletRequest)request).getRemoteAddr( ));
}
}
}
}
}
// 在完成以后,确保锁住了参数。
paramMap.setLocked(true);
}
/**
* 返回该对象的文本表现
*/
@Override
public String toString() {
return "BadInputValve";
}
}