文章目录

代码注入:SQL注入

问题

SQL注入是一种数据库攻击手段。攻击者通过向应用程序提交恶意代码来改变原SQL语句
的含义,进而执行任意SQL命令,达到入侵数据库乃至操作系统的目的。

解决

一般都是使用了Statement,用PreparedStatement代替即可

//Statement stmt = connection.createStatement();
//ResultSet rs = stmt.executeQuery(sqlString);
String sqlString = "select * from db_user where username=? and password=?";
PreparedStatement stmt = connection.prepareStatement(sqlString);
stmt.setString(1, username);
stmt.setString(2, pwd);
ResultSet rs = stmt.executeQuery();

输入验证:日志伪造

问题

在这里插入图片描述
允许日志记录未经验证的用户输入,会导致日志伪造攻击。

解决

在这里插入图片描述
在第33行调用 org.apache.commons.lang 包下 StringEscapeUtils 类的静态方法 escapeJavaScript(),该方法使用 JavaScript 字符串规则进行字符转义。

密码管理:不安全的随机数

问题

Java API中提供了java.util.Random类实现PRNG(),该PRNG是可移植和可重复的,如果两个
java.util.Random类的实例使用相同的种子,会在所有Java实现中生成相同的数值序列。

解决

使用java.security.SecureRandom来生成更安全的随机数。

//指定算法
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
//生成0~19的随机数
int number = random.nextInt(20);

资源管理:日期格式化缺陷

问题

SimpleDateFormat类内部有一个Calendar对象引用,它用来储存和
这个SimpleDateFormat相关的日期信息。这样就会导致一个问题,如果SimpleDateFormat是
static的, 那么多个thread之间就会共享SimpleDateFormat, 同时也是共享Calendar引用。在高
并发的情况下,容易出现幻读成员变量的现象。

解决方法1

DateTimeFormatter替换SimpleDateFormat
SimpleDateFormat的方法参数要使用Data
DateTimeFormatter就要使用LocalDateTime

	//private static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	//使用DateTimeFormatter代替SimpleDateFormat
	private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
	//当前时间
	LocalDateTime now = LocalDateTime.now();
	//formatter.format(now)转成字符串
	System.out.println("执行当前时间"+formatter.format(now));

解决方法2

不要给它定义为成员变量,定义成局部变量,每此使用前都新new一个,缺陷就是浪费内存,方便改,具体看情况选择吧
在这里插入图片描述

资源管理:单例成员变量

问题

变量username为成员变量,当LoginServlet
的对象处理多个请求时,变量username将被多个请求共享,这将导致线程间数据泄露。

String username;
protected void doPost(HttpServletRequest req,HttpServletResponse res) {
	username = req.getParameter(“username”);

	out.println( “ 欢迎您,“+username+!);
}

解决

使用局部变量,因为局部变量在每个线程中都有各自的实例,从而
保证线程安全

protected void doPost(HttpServletRequest req,HttpServletResponse res) {
	String username = req.getParameter(“username”);

	out.println( “ 欢迎您,“+username+!);
}

代码质量:硬编码文件分隔符

问题

路径分割符号问题,不同的操作系统不同。在程序中不要硬性编码与平台相关的任何常量,
比如行分隔符,文件分隔符,路径分隔符等等。例如文件分隔符,Windows系统使用"",
而UNIX系统则使用"/"。应用程序需要在不同的平台上运行时,使用硬编码文件分隔符会导
致应用程序逻辑执行错误,并有可能导致拒绝服务。
例如:以下代码使用硬编码文件分隔符来打开文件:
File file = new File(dirName + “\” + fileName);

解决

不应使用硬编码文件分隔符,而应使用语言库提供的独立于平台的API,如
java.io.File.separator,也可以通过使用java.lang.System.getProperty(“file.separator”)来获取。
例如:针对示例代码的修改方法是:
File file = new File(dirName + File.separator + fileName);
可以直接替换/
newPath = newPath.replace("/",File.separator);

代码质量:null引用

问题

程序间接引用了可能为null的变量,从而引发空指针异常。
例如:下面代码片段中,程序data设置为null,使用之前未判断是否为null。

...
Data data = null
...
data.setId(id);
...

解决

程序在间接引用可能为null的对象之前应对对象引用进行判断。
例如:下面代码片段中,程序data设置为null,使用之前进行判断是否为null。

....
Data data = null
...
if(data != null){
data.setId(id);
}
...

代码质量:比较Locale相关的数据未指定适当的Locale

问题

在比较数据时,如果可能与locale设置相关,则应指定相应的locale。
例1:下面代码示例中,使用了locale相关的String.toUpperCase()将字符转换为大写字符。在
英文locale中,会将"title"转换为"TITLE",在土耳其locale中,会将"title"转换为"T?TLE",其中
的?是拉丁字母的"I"。
同理:在英文locale中,会将"TITLE"转换为"title",在土耳其locale中,会将"TITLE"转换为
“tıtle”,其中的ı是拉丁字母的"i"。
“title”.toUpperCase();
“TITLE”.toLowerCase();

解决

在比较数据时,如果可能与locale设置相关,则应指定相应的locale。
例1:下面代码示例将locale设置为英文,从而避免产生意外的问题。

"title".toUpperCase(Locale.ENGLISH);
"TITLE".toLowerCase(Locale.ENGLISH);

例2:可以在对字符串处理前,将默认的locale设置为English。

Locale.setDefault(Locale.ENGLISH);
"title".toUpperCase();
"TITLE".toLowerCase();

资源管理:资源未释放:数据库

问题

程序创建或分配数据库资源后,不进行合理释放,将会降低系统性能。攻击者可能会通过
耗尽资源池的方式发起拒绝服务攻击。
例如:在下面Java代码片段中,创建数据库对象资源后未进行合理释放,在繁忙的程序环
境下,数据库连接池可能会耗尽,从而导致其他用户不能访问该数据库资源。
简单说:操作了数据库,没关闭

try {
Connection conn = DriverManager.getConnection(some_connection_string)
PreparedStatement stmt = conn.prepareStatement(sqlquery);

} catch (Exception e) {
log(e);
}

解决

程序不应依赖于finalize()回收数据库资源,应在finally代码块中手动释放数据库资源。
关闭相关的连接方法

扩展

打开顺序:
Connection
PreparedStatement
Result
关闭顺序:
Result
PreparedStatement
Connection
也有人说只要关闭con,其它都会关闭,但是最好跟着顺序来吧

资源管理:资源未释放:文件

问题

程序创建或分配文件句柄后,不进行合理释放,将会降低系统性能,攻击者还可能会通过
耗尽资源池的方式发起拒绝服务攻击。
例如:下面代码片段中,创建了一个文件句柄zFile,未进行合理释放。
简单说:就是ZipFile用了没关

public void getZipContents(String fileName){
ZipFile zFile = null;
try {
zFile = new ZipFile(fileName);
...
} catch (IOException e) {
e.printStackTrace();
}
}

解决

程序不应依赖于finalize()回收文件句柄资源,应在finally代码块中手动释放文件句柄资源。
例如:下面代码片段中,使用完之前创建的文件句柄zf后,在finally代码块中进行了释放。
简单说:close()就可以了

public void getZipContents(String fileName){
	ZipFile zFile = null;
	try {
		zFile = new ZipFile(fileName);
		...
	} catch (IOException e) {
		e.printStackTrace();
	}finally{
		if(zFile!=null){
			try {
				zFile.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

资源管理:资源未释放:流

问题

程序创建或分配流资源后,不进行合理释放,将会降低系统性能。攻击者可能会通过耗尽
资源池的方式发起拒绝服务攻击。
例如:在下面Java方法中,创建I/O流对象后未进行合理释放,程序依靠Java虚拟机的垃圾
回收机制释放I/O流资源,事实上,程序不能确定何时调用虚拟机的finalize()方法。在繁忙
的程序环境下,可能导致Java虚拟机不能有效的使用I/O对象。
简单说:使用了IO流,没有关闭

public void processFile(String filePath){
try {
FileInputStream fis = new FileInputStream(filePath);
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
String line="";
while((line=br.readLine())!=null){
processLine(line);
}
} catch (FileNotFoundException e) {
log(e);
} catch (IOException e){
log(e);
}
}

解决

程序不应依赖于finalize()回收流资源,应在finally代码块中手动释放流资源。
例如:下面代码片段中,在finally代码块中对流资源进行了合理的释放。
简单说,用完IO,close()就好

public void processFile(String filePath){
	FileInputStream fis = null;
	InputStreamReader isr = null;
	BufferedReader br = null;
	try {
		fis = new FileInputStream(filePath);
		isr = new InputStreamReader(fis);
		br = new BufferedReader(isr);
		String line="";
		while((line=br.readLine())!=null){
			//processLine(line);
			650
		}
	} catch (FileNotFoundException e) {
		//log(e);
	} catch (IOException e){
		//log(e);
	}finally{
		if(br!=null){
			try {
				br.close();
			} catch (IOException e) {
				//log(e);
			}
		}
		if(isr!=null){
			try {
				isr.close();
			} catch (IOException e) {
				//log(e);
			}
		}
		if(fis!=null){
			try {
				fis.close();
			} catch (IOException e) {
				//log(e);
			}
		}
	}
}

扩展

需要关闭的种类;

Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
w.close()

// 压缩流
ZipOutputStream toClient = new ZipOutputStream(outStream);
toClient.close();

ZipFile zip = new ZipFile(zipFile,Charset.forName("GBK"));//解决中文文件夹乱码
zip.close();

Workbook wb = excel.getWookBookByIn(is);
wb.close();

// 输出文件路径信息
FileOutputStream out = new FileOutputStream(outPath);
out.close();


InputStream in = zip.getInputStream(entry);
in.close();


BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(new File(fileName)));
bufferedInputStream.close();


PdfDocument firstSourcePdf = new PdfDocument(new PdfReader(sourceFile));
firstSourcePdf.close();

密码管理:空密码

问题

程序中使用了空的密码值,这样会大大降低程序的安全性。
在这里插入图片描述

解决

其实只是变量设置为空,但是代码检测要报错
在这里插入图片描述

代码质量:非静态内部类实现序列化接口

问题

非静态内部类(包括普通内部类、Local内部类和匿名内部类)的实例持有一个外部类实例
的隐式引用。非静态内部类实现序列化接口,当其被序列化时会出现运行时异常或泄露外
部类中的信息。
个人理解,就是内部类实现了Serializable接口
下面代码示例中,内部类间接实现序列化接口

public class SuperSer implements Serializable{
//...
}
public class OuterSer{
private int rank;
class InnerSer extends SuperSer{
protected String name;
}
}

例1:下面代码示例中,内部类实现或间接实现序列化接口,外部类没有实现序列化,当内
部类被序列化时,程序会报出NotSerializableException

public class OuterSer{
private int rank;
class InnerSer implements Serializable{
protected String name;
}
}

例2:下面代码示例中,内部类和外部类实现或间接实现序列化接口,当内部类被序列化时,
外部类的字段也会被序列化。

public class OuterSer implements Serializable {
private int rank;
class InnerSer implements Serializable{
protected String name;
}
}

解决

内部类不要实现或间接实现Serializable接口,或将内部类声明为静态的。
没验证,看表面意思就是内部类不要实现Serializable,或者让实现了Serializable的内部类变成static
例1:下面代码示例中,内部类没有实现序列化接口。

public class OuterSer implements Serializable {
private int rank;
class InnerSer{
protected String name;
}
}

例2:下面代码示例中,将内部类声明为静态内部类。

public class OuterSer implements Serializable {
private int rank;
static class InnerSer implements Serializable{
protected String name;
}
}

代码质量:不正确的块划分

问题

就是if的写法不规范,程序能分清,但是接手的人搞不清到底哪些是if要执行的内容
在这里插入图片描述

解决

因为分不清到底写代码的人是怎么想的,所以,只要程序不出问题,无解,还是不要改它了
在这里插入图片描述

代码质量:equals()参数为null

问题

equal中的参数是null的
在这里插入图片描述

解决

如果程序员想要判断对象是否为null,可以采用obj==null或obj!=null代替obj.equals(null)。
使用==或者!=代替

异常处理:空的catch代码块

问题

catch中没有代码
在这里插入图片描述

解决

抛出异常
在这里插入图片描述

代码质量:表达式永远为false

问题

这个if判断的条件一直是false
在这里插入图片描述

解决

看看if判断的这个值的来源,从哪来的,是不是一直都是false这个值,然后把不要的判断删掉就行
在这里插入图片描述

代码质量:表达式永远为true

问题

这个if判断的值一直都是true
在这里插入图片描述

解决

删掉没用的判断
在这里插入图片描述

代码质量:未使用的字段

问题

定义了,但是没有用到
在这里插入图片描述

解决

注释或者删掉
在这里插入图片描述

异常处理:泛化的抛出异常

问题

在方法上抛出异常的时候抛的是Excepiton,范围太广了
在这里插入图片描述

解决

在这里插入图片描述
抛出具体的异常类型
在这里插入图片描述

代码质量:系统信息泄露:Debug日志程序

问题

使用log输出了系统报错信息
==

解决

不输出
在这里插入图片描述

代码质量:系统信息泄露:标准错误流

问题

使用printStackTrace安全
在这里插入图片描述

解决

在这里插入图片描述
不输出

代码质量:系统信息泄露:内部

问题

输出了报错信息
在这里插入图片描述

解决

不输出
在这里插入图片描述

代码质量:使用浮点数进行精确计算

问题

使用了double
在这里插入图片描述

解决

使用BigDecimal代替Double
在这里插入图片描述

代码质量:使用==或!=比较字符串

问题

判断字符串用equals()
在这里插入图片描述

解决

在这里插入图片描述

代码质量:日志记录:使用系统输出流

问题

使用了System.out输出信息
在这里插入图片描述

问题

使用log代替
在这里插入图片描述

代码质量:日志记录:非static final的日志对象

问题

日志没有用static final修饰
应用程序中定义的日志记录对象未声明为static final的。
例如:下列代码中定义的日志记录对象未声明为final。
在这里插入图片描述

解决

声明为static final
建议应用程序中将日志记录对象声明为static final的

JavaEE配置错误:过多的Servlet映射

问题

多个URL模式映射到单个Servlet上,通常表明程序的结构较差。

解决

一个Servlet对用一个servlet-mapping
将执行多个功能的Servlet分解成多个单独的Servlet,每个Servlet都包含自己的URL映射和单
独的功能。

配置管理:JavaEE配置错误:不完善的错误配置信息

问题

Web应用程序的默认错误页面不应显示程序的敏感信息。Web应用程序应该为4xx(如404)
错误、5xx(如500)错误、java.lang.Throwable异常定义一个错误页面,防止攻击者挖掘应
用程序容器内置错误响应信息

解决

定义相应的错误页面,不展示错误信息
应用程序应该在web.xml中配置默认的错误页面。
例如:下面配置文件片段中,定义了相应的错误页面。
在这里插入图片描述

配置管理:JavaEE配置错误:缺少错误配置信息

问题

Web应用程序的默认错误页面不应显示程序的敏感信息。Web应用程序应该为4xx(如404)
错误、5xx(如503)错误、java.lang.Throwable异常定义一个错误页面,防止攻击者挖掘应
用程序容器内置错误响应信息。

解决

应用程序应该在web.xml中配置默认的错误页面。
例如:下面代码片段中,定义了相应的错误页面。
在这里插入图片描述

配置管理:JavaEE配置错误:缺少Servlet映射

问题

servlet,没有对应的servlet-mapping
web.xml中定义的没有对应的。缺少有效的Servlet映射会阻止用
户对未映射的Servlet的所有访问。
例如:以下代码片段中,缺少MyServlet的Servlet映射。
在这里插入图片描述

解决

删掉多余的servlet,或者把少的<servlet-mapping>补上
检查程序逻辑,增加对应的。

代码质量:JavaEE程序:遗留的调试代码

问题

应用程序中的测试代码会建立一些意想不到的入口,这些入口可能会被攻击者作为“后门”
进行利用
例如:JAVA EE程序中的main()方法
在这里插入图片描述

解决

删掉多余的代码
系统在发布之前应删除测试代码。

API误用:忽略返回值

问题

没有对方法返回值判断
程序员可以假设某些事件或条件永远不会发生或者不需要担心,例如内存条件低,由于限
制性权限而无法访问资源,某个函数调用永远不会失败,或者行为不当的客户端或组件。
但是,攻击者可能会故意触发这些异常情况,从而违反了程序员的假设,可能会引入不稳
定,不正确的行为或漏洞。例如,如果程序调用函数来删除权限但不检查返回代码以确保
成功删除权限,则程序将继续以更高权限运行。
例如:在下面的代码片段中,程序没有对read()返回值做判断
在这里插入图片描述

解决

对返回的值做判断
程序应检查方法的返回值,确保方法返回的是期望的数据,再进行下一步操作。

API误用:类没有实现equals()

问题

在没有重写equals()方法的类(或接口)上调用equals()方法,会导致调用继承自
java.lang.Object的equals()方法,尽管可以合法的使用Object.equals(),但是Object.equals()将
比较两个对象的内存地址,而不是比较对象的成员变量。
例如:下列代码中PersonTeam类未实现equals()方法,当使用equals()方法时是通过两个对象
的内存地址比较。
在这里插入图片描述

解决

使用equals()方法比较对象的成员信息,需要重写Object的equals()方法。

API误用:调用System.gc()

问题

使用了System.gc()
程序频繁调用System.gc()将会降低系统性能

解决

不使用System.gc()
程序不应频繁调用System.gc()进行垃圾回收,应让Java虚拟机进行内存管理。

输入验证:日志伪造:调试

问题

如果程序允许日志记录未经验证的用户输入,会导致日志伪造攻击甚至是注入恶意信息。
例如:下面代码片段中,在接收到非法用户请求时,未进行任何数据验证情况下,记录用
户的用户名。
在这里插入图片描述

解决

验证
防止日志伪造攻击可以采用白名单或黑名单的方式对不可信赖的数据进行校验。
例如:下面代码片段对username进行了验证,防止产生日志伪造攻击。
在这里插入图片描述

代码注入:有风险的SQL查询:Hibernate

问题

sql语句有问题,可能有有1=1这种
SQL注入是一种数据库攻击手段。攻击者通过向应用程序提交恶意代码来改变原SQL语句
的含义,进而执行任意SQL命令,达到入侵数据库乃至操作系统的目的。使用Hibernate执
行一个通过用户输入构建的动态SQL指令,会使攻击者篡改指令的含义或者执行任意的
SQL命令。系统中有一些方法存在着一定风险,当方法所需的数据来源于不可信赖的数据
源时,可能会导致SQL注入。
例如:下面代码片段中,动态构造并执行了一个SQL查询。
在这里插入图片描述
如果攻击者能够替代userName的任意字符串,他们可以使用下面的关于userName的字符串
进行SQL注入。
’ or 1=1 or ‘’=’
当其注入到命令时,命令就会变成:
from db_user where name like ‘%’ or 1=1 or ‘’=’%’

解决

使用:name绑定参数或者筛选下关键字

造成SQL注入攻击的根本原因在于攻击者可以改变SQL查询的上下文,使程序员原本要作
为数据解析的数值,被篡改为命令了。防止SQL注入的方法如下:
(1)正确使用参数化API进行SQL查询。
(2)如果构造SQL指令时需要动态加入约束条件,可以通过创建一份合法字符串列表,使
其对应于可能要加入到SQL指令中的不同元素,来避免SQL注入攻击。
例1:以下代码片段对参数名称进行绑定,来防止SQL注入。
...
Query query=session.createQuery(“from User user where user.name=:userName ”);
query.setString(“userName”,name);
...2:以下代码片段对参数位置进行邦定,来防止SQL注入。
....
Query query=session.createQuery(“from User user where user.name=?);
query.setString(0, userName);
...3:以下代码片段使用setParameter()来防止SQL注入。
...
682
String hql=”from User user where user.name=: userName”;
Query query=session.createQuery(hql);
query.setParameter(“userName”,name,Hibernate.STRING); ...
...4:以下代码片段使用setProperties()方法来防止SQL注入。
InputUser inputUser=new InputUser ();
inputUser.setName(“userName”);
Query query=session.createQuery(“from User u where u.name=:name”);
query.setProperties(inputUser);
setProperties()方法会自动将inputUser对象实例的属性值匹配到命名参数上,参数名称要与
inputUser对象相应的属性同名。

代码注入:有风险的SQL查询

问题

SQL注入是一种数据库攻击手段。攻击者通过向应用程序提交恶意代码来改变原SQL语句
的含义,进而执行任意SQL命令,达到入侵数据库乃至操作系统的目的。系统中有一些方
法存在着一定风险,当方法所需的数据来源于不可信赖的数据源时,可能会导致SQL注入。
例如:下面代码片段中,动态构造并执行了一个SQL查询来认证用户。
public void doPrivilegedAction(String username, char[] password) throws SQLException {
Connection connection = getConnection();
if (connection == null) {
// handle error
}
try {
String pwd = hashPassword(password);
String sqlString = "SELECT * FROM db_user WHERE username = '" + username + "' AND
password = '" + pwd + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sqlString);
if (!rs.next()) {
throw new SecurityException( "User name or password incorrect");
}
// Authenticated; proceed
} finally {
try {
connection.close();
} catch (SQLException x) {
// forward to handler
}
}
}
如果攻击者能够替代username和password中的任意字符串,它们可以使用下面的关于
username的字符串进行SQL注入。
validuser' OR '1'='1
当其注入到命令时,命令就会变成:
SELECT * FROM db_user WHERE username='validuser' OR '1'='1' AND password=''
同样,攻击者可以为password提供如下字符串。
' OR '1'='1
当其注入到命令时,命令就会变成:
SELECT * FROM db_user WHERE username='' AND password='' OR '1'='1'

解决

使用PreparedStatement代替Statement

造成SQL注入攻击的根本原因在于攻击者可以改变SQL查询的上下文,使程序员原本要作
为数据解析的数值,被篡改为命令了。防止SQL注入的方法如下:
(1)正确使用参数化API进行SQL查询。
(2)如果构造SQL指令时需要动态加入约束条件,可以通过创建一份合法字符串列表,使
其对应于可能要加入到SQL指令中的不同元素,来避免SQL注入攻击。
例如:以下代码片段使用java.sql.PreparedStatement代替java.sql.Statement,在
java.sql.PreparedStatement类中可以对输入字符串进行转义,如果使用正确的话,可以防止
SQL注入。
public void doPrivilegedAction(String username, char[] password) throws SQLException {
Connection connection = getConnection();
if (connection == null) {
// Handle error
}
try {
String pwd = hashPassword(password);
// Ensure that the length of user name is legitimate
if ((username.length() > 8) {
// Handle error
}
String sqlString = "select * from db_user where username=? and password=?";
PreparedStatement stmt = connection.prepareStatement(sqlString);
stmt.setString(1, username);
stmt.setString(2, pwd);
ResultSet rs = stmt.executeQuery();
if (!rs.next()) {
throw new SecurityException("User name or password incorrect");
}
// Authenticated, proceed
} finally {
try {
connection.close();
} catch (SQLException x) {
// forward to handler
}
}
}

代码注入:有风险的命令执行

问题

命令注入是指应用程序执行命令的字符串或字符串的一部分来源于不可信赖的数据源,程
序没有对这些不可信赖的数据进行验证、过滤,导致程序执行恶意命令的一种攻击方式。
系统中有一些方法存在着一定风险,当方法所需的数据来源于不可信赖的数据源时,可能
会导致命令注入。
例1:以下代码通过Runtime.exec()方法调用Windows的dir命令,列出目录列表。
import java.io.*;
public class DirList {
public static void getDirList(String dir) throws Exception {
Process proc = Runtime.getRuntime().exec("cmd.exe /c dir" + dir);
int result = proc.waitFor();
if (result != 0) {
System.out.println("process error: " + result);
}
InputStream in = (result == 0) ? proc.getInputStream() : proc.getErrorStream();
int c;
while ((c = in.read()) != -1) {
System.out.print((char) c);
}
}
}
攻击者可以通过以下命令利用该程序:
java -Ddir='dummy & echo bad' DirList
该命令实际上执行的是以下两条命令:
cmd.exe /c dir dummy & echo bad
例2:以下代码来自一个Web应用程序,该段代码通过运行rmanDB.bat脚本启动Oracle数据
库备份,然后运行cleanup.bat脚本删除一些临时文件。脚本文件rmanDB.bat接受一个命令行
参数,其中指明需要执行的备份类型。
...
String cmd = new String("cmd.exe /K \"c:\\util\\rmanDB.bat "+backuptype+"&&
c:\\utl\\cleanup.bat\"");
System.Runtime.getRuntime().exec(cmd);
...
该段代码没有对来自用户请求中的backuptype参数做任何校验。
通常情况下,Runtime.exec()函数不会执行多条命令,但在以上代码中,为了执行多条命令,
程序调用Runtime.exec()方法,首先运行了cmd.exe指令,因此能够执行用&&分隔的多条命
令了。如果攻击者传递了一个形式为"&& del c:\\dbms\\*.*"的字符串,那么该段代码将会在
执行其他指定命令的同时执行这条命令。

问题

用Pattern.matches过滤一下

防止命令注入的方法如下:
(1)程序对非受信的用户输入数据进行净化,删除不安全的字符。
(2)创建一份安全字符串列表,限制用户只能输入该列表中的数据。
例1:下面代码片段中,使用正则表达式匹配用户的输入。
// ...
if (!Pattern.matches("[0-9A-Za-z@.]+", dir)) {
// Handle error
}
// ...2:下面代码片段中,通过只向Runtime.exec()方法输入那些受信的字符串来防止命令注入。
// ...
String btype = null;
// only allow integer choices
int number = Integer.parseInt(backuptype);
switch (number) {
case 1:
btype = "tables"
break; // Option 1
case 2:
btype = "users"
break; // Option 2
case 3:
btype = "full"
break; // Option 3
default: // invalid
break;
}
if (btype == null) {
// handle error
}

输入验证:日志伪造

问题

在以下情况下会发生 Log Forging 的漏洞:
	1. 数据从一个不可信赖的数据源进入应用程序。
	2. 数据写入到应用程序或系统日志文件中。
	为了便于以后的审阅、统计数据收集或调试,应用程序通常使用日志文件来储
存事件或事务的历史记录。根据应用程序自身的特性,审阅日志文件可在必要时手动执行,
也可以自动执行,即利用工具自动挑选日志中的重要事件或带有某种倾向性的信息。
	如果攻击者可以向随后会被逐字记录到日志文件的应用程序提供数据,则可能
会妨碍或误导日志文件的解读。最理想的情况是,攻击者可能通过向应用程序提供包括适
当字符的输入,在日志文件中插入错误的条目。如果日志文件是自动处理的,那么攻击者
就可以通过破坏文件格式或注入意外的字符,从而使文件无法使用。更阴险的攻击可能会
导致日志文件中的统计信息发生偏差。通过伪造或其他方式,受到破坏的日志文件可用于
掩护攻击者的跟踪轨迹,甚至还可以牵连第三方来执行恶意行为 [1]。最糟糕的情况是,攻
击者可能向日志文件注入代码或者其他命令,利用日志处理实用程序中的漏洞 [2]。
	示例 1:下列 Web 应用程序代码会尝试从一个请求对象中读取整数值。如果
数值未被解析为整数,输入就会被记录到日志中,附带一条提示相关情况的错误消息。
	var val = request.url.indexOf("value=");
	if (!isNaN(val))){
		console.log("ERROR: illegal values val = " + val);
	}
	...
	}
	如果用户输入的是一个非数字那么日志会输出正常
	如果用户输出"value \n ERROR:network error",这样的话就会对排查问题造成
很大的困扰
	而且,攻击者可以使用同样的机制插入任意日志条目。

解决

先做判断

  	var filterRule= /[^0-9a-zA-Z_]/g;
  	//判断是不是有特殊字符,如果有,就返回false
  	if(!filterRule.test(value))
  		return;

对于外部不可信的数据一定要验证之后再使用。

密码管理:null密码

问题

密码设置为Null
null密码会削弱系统的安全

解决

随便写一个初始值
程序中不应采用null密码,程序所需密码应从配置文件中获取加密的密码值。

输入验证:访问权限修饰符控制

问题

一般是Field、Method和Constructor对象,使用了setAccessible,不安全

AccessibleObject类是Field、Method和Constructor对象的基类,能够允许反射对象修改访问
权限修饰符,绕过由Java访问修饰符提供的访问控制检查。它让程序员能够更改私有字段
或调用私有方法,这在通常情况下是不允许的。
例如:以下代码片段中,将Field将accessible标记设置为true。
	Class clazz = User.class;
	Field field = clazz.getField("name");
	field.setAccessible(true);
	...

解决

不用setAccessible
通过有权限的类更改访问权限修饰符,并确保修改的访问权限修饰符参数不能被攻击者控
制。

JavaEE程序:直接管理数据库连接

问题

使用了DriverManager获取数据库连接

在JAVA EE程序中直接管理数据库连接。JAVA EE标准要求应用程序采用容器的资源管理
器工具获取连接资源。
例如:下面JAVA EE代码片段中,类DatabaseConnection直接打开和管理数据库连接,违反
了JAVA EE标准。
public class DatabaseConnection {
private static final String CONNECT_STRING = "jdbc:mysql://localhost:3306/mysqldb";
private Connection conn = null;
public DatabaseConnection() {
}
public void openDatabaseConnection() {
try {
conn = DriverManager.getConnection(CONNECT_STRING);
} catch (SQLException ex) {
...
}
}
// Member functions for retrieving database connection and accessing database
...

解决

配置JNDI或者注入DataSource获取连接
数据库连接缺陷解决

JAVA EE程序应该使用Web容器的资源管理获取数据库连接。
例如:下面代码片段,通过Web容器获取数据库连接。
public class DatabaseConnection {
private static final String DB_DATASRC_REF = "jdbc:mysql://localhost:3306/mysqldb";
private Connection conn = null;
public DatabaseConnection() {
}
public void openDatabaseConnection() {
try {
InitialContext ctx = new InitialContext();
DataSource datasource = (DataSource) ctx.lookup(DB_DATASRC_REF);
conn = datasource.getConnection();
} catch (NamingException ex) {
...
} catch (SQLException ex) {
...
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐