申明:本文非笔者原创,原文转载自:https://github.com/SecurityPaper/SecurityPaper-web/blob/master/_posts/2.SDL%E8%A7%84%E8%8C%83%E6%96%87%E6%A1%A3/2018-08-17-SDL-3-java%E5%AE%89%E5%85%A8%E7%BC%96%E7%A0%81%E8%A7%84%E8%8C%83.md

1输入验证和数据合法性校验

程序接受数据可能来源于未经验证的用户,网络连接和其他不受信任的来源,如果未对程序接受数据进行校验,则可能会引发安全问题。

1.1避免SQL注入

使用PreparedStatement预编译SQL,解决SQL注入问题,传递给PreparedStatement对象的参数可以被强制进行类型转换,确保在插入或查询数据时与底层的数据库格式匹配。

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();

1.2避免XML注入

通过StringBulider 或 StringBuffer 拼接XML文件时,需对输入数据进行合法性校验。 对数量quantity 进行合法性校验,控制只能传入0-9的数字:

if (!Pattern.matches("[0-9]+", quantity)) {
// Format violation
}
String xmlString = "<item>\n<description>Widget</description>\n" +
"<price>500</price>\n" +
"<quantity>" + quantity + "</quantity></item>";
outStream.write(xmlString.getBytes());
outStream.flush();

1.3避免跨站点脚本(XSS)

对产生跨站的参数进行严格过滤,禁止传入<SCRIPT>标签

//定义需过滤的字段串<script>

String s = "\uFE64" + "script" + "\uFE65";

// 过滤字符串标准化

s = Normalizer.normalize(s, Form.NFKC);

// 使用正则表达式匹配inputStr是否存在<script>

Pattern pattern = Pattern.compile(inputStr);
Matcher matcher = pattern.matcher(s);
if (matcher.find()) {
// Found black listed tag
throw new IllegalStateException();
} else {
// ...
}

2声明和初始化

2.1避免类初始化的相互依赖

例:

错误的写法:

public class Cycle {
private final int balance;
private static final Cycle c = new Cycle();
private static final int deposit = (int) (Math.random() * 100); // Random deposit
public Cycle() {
balance = deposit - 10; // Subtract processing fee
}
public static void main(String[] args) {
System.out.println("The account balance is: " + c.balance);
}
}

类加载时初始化指向Cycle类的静态变量c,而类Cycle的无参构造方法又依赖静态变量deposit,导致无法预期的结果。 正确的写法:

public class Cycle {
private final int balance;
private static final int deposit = (int) (Math.random() * 100); // Random deposit
private static final Cycle c = new Cycle(); // Inserted after initialization of required fields
public Cycle() {
balance = deposit - 10; // Subtract processing fee
} public static void main(String[] args) {
System.out.println("The account balance is: " + c.balance);
}
}

3表达式

3.1不可忽略方法的返回值

忽略方法的放回值可能会导致无法预料的结果。

错误的写法:

public void deleteFile(){
File someFile = new File("someFileName.txt");
someFile.delete();
}

正确的写法:

public void deleteFile(){
File someFile = new File("someFileName.txt");
if (!someFile.delete()) {
// handle failure to delete the file
}
}

3.2不要引用空指针

当一个变量指向一个NULL值,使用这个变量的时候又没有检查,这时会导致。NullPointerException。

在使用变量前一定要做是否为NULL值的校验。

3.3使用Arrays.equals()来比较数组的内容

数组没有覆盖的Object. equals()方法,调用Object. equals()方法实际上是比较数组的引用,而不是他们的内容。程序必须使用两个参数Arrays.equals()方法来比较两个数组的内容

public void arrayEqualsExample() {
int[] arr1 = new int[20]; // initialized to 0
int[] arr2 = new int[20]; // initialized to 0
Arrays.equals(arr1, arr2); // true
}

4数字类型和操作

4.1防止整数溢出

使用java.lang.Number. BigInteger类进行整数运算,防止整数溢出。

public class BigIntegerUtil {

    private static final BigInteger bigMaxInt = BigInteger.valueOf(Integer.MAX_VALUE);
private static final BigInteger bigMinInt = BigInteger.valueOf(Integer.MIN_VALUE); public static BigInteger intRangeCheck(BigInteger val) throws ArithmeticException {
if (val.compareTo(bigMaxInt) == 1 || val.compareTo(bigMinInt) == -1) {
throw new ArithmeticException("Integer overflow");
}
return val;
} public static int addInt(int v1, int v2) throws ArithmeticException {
BigInteger b1 = BigInteger.valueOf(v1);
BigInteger b2 = BigInteger.valueOf(v2);
BigInteger res = intRangeCheck(b1.add(b2));
return res.intValue();
} public static int subInt(int v1, int v2) throws ArithmeticException {
BigInteger b1 = BigInteger.valueOf(v1);
BigInteger b2 = BigInteger.valueOf(v2);
BigInteger res = intRangeCheck(b1.subtract(b2));
return res.intValue();
} public static int multiplyInt(int v1, int v2) throws ArithmeticException {
BigInteger b1 = BigInteger.valueOf(v1);
BigInteger b2 = BigInteger.valueOf(v2);
BigInteger res = intRangeCheck(b1.multiply(b2));
return res.intValue();
} public static int divideInt(int v1, int v2) throws ArithmeticException {
BigInteger b1 = BigInteger.valueOf(v1);
BigInteger b2 = BigInteger.valueOf(v2);
BigInteger res = intRangeCheck(b1.divide(b2));
return res.intValue();
}
}

4.2避免除法和取模运算分母为零

要避免因为分母为零而导致除法和取模运算出现异常。

if (num2 == 0) {
// handle error
} else {
result1= num1 /num2;
result2= num1 % num2;
}

5类和方法操作

5.1数据成员声明为私有,提供可访问的包装方法

攻击者可以用意想不到的方式操纵public或protected的数据成员,所以需要将数据成员为private,对外提供可控的包装方法访问数据成员。

5.2敏感类不允许复制

包含私人的,机密或其他敏感数据的类是不允许被复制的,解决的方法有两种:

1、类声明为final

final class SensitiveClass {
// ...
}

2、Clone 方法抛出CloneNotSupportedException异常

class SensitiveClass {
// ...
public final SensitiveClass clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}

5.3比较类的正确做法

如果由同一个类装载器装载,它们具有相同的完全限定名称,则它们是两个相同的类。 不正确写法:

// Determine whether object auth has required/expected class object
if (auth.getClass().getName().equals(
"com.application.auth.DefaultAuthenticationHandler")) {
// ...
}
正确写法:
// Determine whether object auth has required/expected class name
if (auth.getClass() == com.application.auth.DefaultAuthenticationHandler.class) {
// ...
}

5.4不要硬编码敏感信息

硬编码的敏感信息,如密码,服务器IP地址和加密密钥,可能会泄露给攻击者。

敏感信息均必须存在在配置文件或数据库中。

5.5验证方法参数

验证方法的参数,可确保操作方法的参数产生有效的结果。不验证方法的参数可能会导致不正确的计算,运行时异常,违反类的不变量,对象的状态不一致。 对于跨信任边界接收参数的方法,必须进行参数合法性校验

private Object myState = null;
//对于修改myState 方法的入参,进行非空和合法性校验
void setState(Object state) {
  if (state == null) {
    // Handle null state
  }
  if (isInvalidState(state)) {
    // Handle invalid state
  }
  myState = state;
}

5.6不要使用过时、陈旧或低效的方法

在程序代码中使用过时的、陈旧的或低效的类或方法可能会导致错误的行为。

5.7数组引用问题

某个方法返回一个对敏感对象的内部数组的引用,假定该方法的调用程序不改变这些对象。即使数组对象本身是不可改变的,也可以在数组对象以外操作数组的内容,这种操作将反映在返回该数组的对象中。如果该方法返回可改变的对象,外部实体可以改变在那个类中声明的 public 变量,这种改变将反映在实际对象中。

不正确的写法:

public class XXX {
private String[] xxxx;
public String[] getXXX() {
return xxxx;
}
}

正确的写法:

public class XXX {
private String[] xxxx;
public String[] getXXX() {
String temp[] = Arrays.copyof(…); // 或其他数组复制方法
return temp;
}
}

5.8不要产生内存泄露

垃圾收集器只收集不可达的对象,因此,存在未使用的可到达的对象,仍然表示内存管理不善。过度的内存泄漏可能会导致内存耗尽,拒绝服务(DoS)。


6异常处理

6.1不要忽略捕获的异常

对于捕获的异常要进行相应的处理,不能忽略已捕获的异常

不正确写法:

class Foo implements Runnable {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 此处InterruptedException被忽略
}
}
}

正确写法:

class Foo implements Runnable {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Reset interrupted status
}
}
}

6.2不允许暴露异常的敏感信息

没有过滤敏感信息的异常堆栈往往会导致信息泄漏,

不正确的写法:

try {
FileInputStream fis =
new FileInputStream(System.getenv("APPDATA") + args[0]);
} catch (FileNotFoundException e) {
// Log the exception
throw new IOException("Unable to retrieve file", e);
}

正确的写法:

class ExceptionExample {
public static void main(String[] args) {
File file = null;
try {
file = new File(System.getenv("APPDATA") +
args[0]).getCanonicalFile();
if (!file.getPath().startsWith("c:\\homepath")) {
log.error("Invalid file");
return;
}
} catch (IOException x) {
log.error("Invalid file");
return;
}
try {
FileInputStream fis = new FileInputStream(file);
} catch (FileNotFoundException x) {
log.error("Invalid file");
return;
}
}
}

6.3不允许抛出RuntimeException, Exception,Throwable

不正确的写法:

boolean isCapitalized(String s) {
if (s == null) {
throw new RuntimeException("Null String");
}
} private void doSomething() throws Exception {
//...
}

正确写法:

boolean isCapitalized(String s) {
if (s == null) {
throw new NullPointerException();
}
} private void doSomething() throws IOException {
//...
}

6.4不要捕获NullPointerException或其他父类异常

不正确的写法:

boolean isName(String s) {
try {
String names[] = s.split(" ");
if (names.length != 2) {
return false;
}
return (isCapitalized(names[0]) && isCapitalized(names[1]));
} catch (NullPointerException e) {
return false;
}
}

正确的写法:

boolean isName(String s) /* throws NullPointerException */ {
String names[] = s.split(" ");
if (names.length != 2) {
return false;
}
return (isCapitalized(names[0]) && isCapitalized(names[1]));
}

7多线程编程

7.1确保共享变量的可见性

对于共享变量,要确保一个线程对它的改动对其他线程是可见的。 线程可能会看到一个陈旧的共享变量的值。为了共享变量是最新的,可以将变量声明为volatile或同步读取和写入操作。 将共享变量声明为volatile

final class ControlledStop implements Runnable {
private volatile boolean done = false;
@Override public void run() {
while (!done) {
try {
// ...
Thread.currentThread().sleep(1000); // Do something
} catch(InterruptedException ie) {
Thread.currentThread().interrupt(); // Reset interrupted status
}
}
}
public void shutdown() {
done = true;
}
}

同步读取和写入操作:

final class ControlledStop implements Runnable {
private boolean done = false;
@Override public void run() {
while (!isDone()) {
try {
// ...
Thread.currentThread().sleep(1000); // Do something
} catch(InterruptedException ie) {
Thread.currentThread().interrupt(); // Reset interrupted status
}
}
}
public synchronized boolean isDone() {
return done;
}
public synchronized void shutdown() {
done = true;
}
}

7.2确保共享变量的操作是原子的

除了要确保共享变量的更新对其他线程可见的,还需要确保对共享变量的操作是原子的,这时将共享变量声明为volatile往往是不够的。需要使用同步机制或Lock 同步读取和写入操作:

final class Flag {
private volatile boolean flag = true;
public synchronized void toggle() {
flag ^= true; // Same as flag = !flag;
}
public boolean getFlag() {
return flag;
}
}

//使用读取锁确保读取和写入操作的原子性

final class Flag {
private boolean flag = true;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
public void toggle() {
writeLock.lock();
try {
flag ^= true; // Same as flag = !flag;
} finally {
writeLock.unlock();
}
}
public boolean getFlag() {
readLock.lock();
try {
return flag;
} finally {
readLock.unlock();
}
}
}

7.3不要调用Thread.run(),不要使用Thread.stop()以终止线程

7.4确保执行阻塞操作的线程可以终止

  public final class SocketReader implements Runnable {
private final SocketChannel sc;
private final Object lock = new Object();
public SocketReader(String host, int port) throws IOException {
sc = SocketChannel.open(new InetSocketAddress(host, port));
}
@Override public void run() {
ByteBuffer buf = ByteBuffer.allocate(1024);
try {
synchronized (lock) {
while (!Thread.interrupted()) {
sc.read(buf);
// ...
}
}
} catch (IOException ie) {
// Forward to handler
}
}
public static void main(String[] args)
throws IOException, InterruptedException {
SocketReader reader = new SocketReader("somehost", 25);
Thread thread = new Thread(reader);
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}

7.5相互依存的任务不要在一个有限的线程池执行

有限线程池指定可以同时执行在线程池中的线程数量的上限。程序不得使用有限线程池线程执行相互依赖的任务。可能会导致线程饥饿死锁,所有的线程池执行的任务正在等待一个可用的线程中执行一个内部队列阻塞


8输入输出

8.1程序终止前删除临时文件

8.2检测和处理文件相关的错误

Java的文件操作方法往往有一个返回值,而不是抛出一个异常,表示失败。因此,忽略返回值文件操作的程序,往往无法检测到这些操作是否失败。Java程序必须检查执行文件I / O方法的返回值。

不正确的写法:

File file = new File(args[0]);
file.delete();
正确的写法:
File file = new File("file");
if (!file.delete()) {
log.error("Deletion failed");
}

8.3及时释放资源

垃圾收集器无法释放非内存资源,如打开的文件描述符与数据库的连接。因此,不释放资源,可能导致资源耗尽攻击。

try {
final FileInputStream stream = new FileInputStream(fileName);
try {
final BufferedReader bufRead =
new BufferedReader(new InputStreamReader(stream)); String line;
while ((line = bufRead.readLine()) != null) {
sendLine(line);
}
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
// forward to handler
}
}
}
} catch (IOException e) {
// forward to handler
}

9序列化

9.1不要序列化未加密的敏感数据

序列化允许一个对象的状态被保存为一个字节序列,然后重新在稍后的时间恢复,它没有提供任何机制来保护序列化的数据。敏感的数据不应该被序列化的例子包括加密密钥,数字证书。 解决方法:

  1. 对于数据成员可以使用transient ,声明该数据成员是瞬态的。
  2. 重写序列化相关方法writeObject、readObject、readObjectNoData,防止被子类恶意重写
class SensitiveClass extends Number {
// ...
protected final Object writeObject(java.io.ObjectOutputStream out) throws NotSerializableException {
throw new NotSerializableException();
}
protected final Object readObject(java.io.ObjectInputStream in) throws NotSerializableException {
throw new NotSerializableException();
}
protected final Object readObjectNoData(java.io.ObjectInputStream in) throws NotSerializableException {
throw new NotSerializableException();
}
}

9.2在序列化过程中避免内存和资源泄漏

不正确的写法:

class SensorData implements Serializable {
// 1 MB of data per instance!
public static SensorData readSensorData() {...}
public static boolean isAvailable() {...}
}
class SerializeSensorData {
public static void main(String[] args) throws IOException {
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream("ser.dat")));
while (SensorData.isAvailable()) {
// note that each SensorData object is 1 MB in size
SensorData sd = SensorData.readSensorData();
out.writeObject(sd);
}
} finally {
if (out != null) {
out.close();
}
}
}
}

正确写法:

class SerializeSensorData {
public static void main(String[] args) throws IOException {
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream("ser.dat")));
while (SensorData.isAvailable()) {
// note that each SensorData object is 1 MB in size
SensorData sd = SensorData.readSensorData();
out.writeObject(sd);
out.reset(); // reset the stream
}
} finally {
if (out != null) {
out.close();
}
}
}
}

9.3反序列化要在程序最小权限的安全环境中

(完成)

【安全开发】java安全编码规范的更多相关文章

  1. Java Script 编码规范【转】

    Java Script 编码规范 以下文档大多来自: Google JavaScript 编码规范指南 Idiomatic 风格 参考规范 ECMAScript 5.1 注解版 EcmaScript ...

  2. Java语言编码规范(Java Code Conventions)

    Java语言编码规范(Java Code Conventions) 名称 Java语言编码规范(Java Code Conventions) 译者 晨光(Morning) 简介 本文档讲述了Java语 ...

  3. Java语言编码规范 - Java语言编码规范(中文版)(http://doc.javanb.com/code-conventions-for-the-java-programming-language-zh/index.html)

      目录 1 介绍 1.1 为什么要有编码规范 1.2 版权声明 2 文件名 2.1 文件后缀 2.2 常用文件名 3 文件组织 3.1 Java源文件 3.1.1 开头注释 3.1.2 包和引入语句 ...

  4. 【原】JAVA SE编码规范

    /* * 编码规范: * 1.所有的命名遵循"见名知意"的原则 * 2.所有的命名不允许使用汉字或拼音 * 3.Java的工程命名建议使用小写,比如:oa.crm.cms... * ...

  5. 【原】Java学习笔记002 - JAVA SE编码规范

    /* * 编码规范: * 1.所有的命名遵循"见名知意"的原则 * 2.所有的命名不允许使用汉字或拼音 * 3.Java的工程命名建议使用小写,比如:oa.crm.cms... * ...

  6. Java权威编码规范

    一.编程规约 (一) 命名规约 1. [强制] 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束. 反例: _nam / __name / $Object / name_  / ...

  7. Android面试、开发之高手 编码规范与细节

    凝视 [规则1]必须用 javadoc 来为类生成文档.不仅由于它是标准.这也是被各种java 编译器都认可的方法. [规则2]在文件的開始部分应该有文件的说明信息,应包括例如以下信息: (1)版权信 ...

  8. Java软件编码规范要求

    1.一个类对应一个文件,文件名与类名保持一致 虽然一个“.java”源文件可以有多个类(不是内部类),但是不提倡那么写.

  9. JavaEE程序编码规范

    JavaEE程序编码规范 目   录 JAVA程序编码规范1 1变量的命名规则1 1.1常量(包含静态的)1 1.2类变量(静态变量)及实例变量1 1.3局部变量1 1.4参数2 1.5其它2 2方法 ...

随机推荐

  1. 优雅的运用 Kotlin 的 null safety 特性,而不要简单的直接用 !!双感叹号

    对于 Null 的检查是 Kotlin 的特点之一.强制你在编码过程中考虑变量是否可为 null,因此可以避免很多在 Java 中隐藏的 NullPointerException. 但是,当你用插件直 ...

  2. [maven] settings 文件 本地maven仓库

    <?xml version="1.0" encoding="UTF-8"?> <!-- Licensed to the Apache Soft ...

  3. FPGA中的时序分析(一)

    谈及此部分,多多少少有一定的难度,笔者写下这篇文章,差不多是在学习FPGA一年之后的成果,尽管当时也是看过类似的文章,但是都没有引起笔者注意,笔者现在再对此知识进行梳理,也发现了有很多不少的收获.笔者 ...

  4. Python之collections.defaultdict

    转自:http://www.jb51.net/article/88147.htm

  5. savReaderWriter 模块的使用

    作用: 由于python可以辅助数据分析和数据挖掘,读取文件, 而savReaderWriter模块就是为此而设计. 官网 :http://pythonhosted.org/savReaderWrit ...

  6. 近期小结 之 Servlet规范及HTTP

    最近认真看了下Servlet 3.1的规范,略有收获,如下: 如果客户端不指定编码,Servlet容器必须使用ISO-8859-1编码来处理,且不能添加相应编码信息. Servlet 3 可以手动开启 ...

  7. Java如何检查线程是否停止?

    在Java编程中,如何检查线程是否停止? 以下示例演示如何通过使用isAlive()方法来检查线程是否已停止. // from W w w .Y I I b AI.c o M package com. ...

  8. Unity--------------------万向锁的概念

    万向锁 一直困惑我很久....原因出在这里,我以为欧拉角旋转是以模型坐标(齐次坐标系)为旋转轴.问题就来了,无论旋转那个轴,其它两个轴也会相应的变化,下面看图: 根据上面的说明两个旋转面(圆圈)怎么会 ...

  9. Keystone API

    Keystone身份API简介 Keystone提供REST风格的API, 客户端可以通过HTTP方法和URL操作资源. Keystone有两个主要版本的API, 以及构建在这些核心API上的一些AP ...

  10. Android开源库项目集锦

    一.兼容类库 ActionBarSherlock : Action Bar是Android 3.0后才開始支持的,ActionBarSherlock是让Action Bar功能支持2.X后的全部平台. ...