【安全開發】java安全編碼規範

申明:本文非筆者原創,原文轉載自: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.mdjava

 

1輸入驗證和數據合法性校驗

程序接受數據可能來源於未經驗證的用戶,網絡鏈接和其餘不受信任的來源,若是未對程序接受數據進行校驗,則可能會引起安全問題。git

1.1避免SQL注入

使用PreparedStatement預編譯SQL,解決SQL注入問題,傳遞給PreparedStatement對象的參數能夠被強制進行類型轉換,確保在插入或查詢數據時與底層的數據庫格式匹配。 github

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的數字:web

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>sql

String s = "\uFE64" + "script" + "\uFE65";數據庫

// 過濾字符串標準化編程

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

// 使用正則表達式匹配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敏感類不容許複製

包含私人的,機密或其餘敏感數據的類是不容許被複制的,解決的方法有兩種:

一、類聲明爲final

final class SensitiveClass { // ... }

二、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反序列化要在程序最小權限的安全環境中

(完成)

相關文章
相關標籤/搜索