申明:本文非筆者原創,原文轉載自: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
程序接受數據可能來源於未經驗證的用戶,網絡鏈接和其餘不受信任的來源,若是未對程序接受數據進行校驗,則可能會引起安全問題。git
使用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();
經過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();
對產生跨站的參數進行嚴格過濾,禁止傳入<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 { // ... }
例:
錯誤的寫法:
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); } }
忽略方法的放回值可能會致使沒法預料的結果。
錯誤的寫法:
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 } }
當一個變量指向一個NULL值,使用這個變量的時候又沒有檢查,這時會致使。NullPointerException。
在使用變量前必定要作是否爲NULL值的校驗。
數組沒有覆蓋的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 }
使用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(); } }
要避免由於分母爲零而致使除法和取模運算出現異常。
if (num2 == 0) { // handle error } else { result1= num1 /num2; result2= num1 % num2; }
攻擊者能夠用意想不到的方式操縱public或protected的數據成員,因此須要將數據成員爲private,對外提供可控的包裝方法訪問數據成員。
包含私人的,機密或其餘敏感數據的類是不容許被複制的,解決的方法有兩種:
一、類聲明爲final
final class SensitiveClass { // ... }
二、Clone 方法拋出CloneNotSupportedException異常
class SensitiveClass { // ... public final SensitiveClass clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } }
若是由同一個類裝載器裝載,它們具備相同的徹底限定名稱,則它們是兩個相同的類。 不正確寫法:
// 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) { // ... }
硬編碼的敏感信息,如密碼,服務器IP地址和加密密鑰,可能會泄露給攻擊者。
敏感信息均必須存在在配置文件或數據庫中。
驗證方法的參數,可確保操做方法的參數產生有效的結果。不驗證方法的參數可能會致使不正確的計算,運行時異常,違反類的不變量,對象的狀態不一致。 對於跨信任邊界接收參數的方法,必須進行參數合法性校驗
private Object myState = null; //對於修改myState 方法的入參,進行非空和合法性校驗 void setState(Object state) { if (state == null) { // Handle null state } if (isInvalidState(state)) { // Handle invalid state } myState = state; }
在程序代碼中使用過期的、陳舊的或低效的類或方法可能會致使錯誤的行爲。
某個方法返回一個對敏感對象的內部數組的引用,假定該方法的調用程序不改變這些對象。即便數組對象自己是不可改變的,也能夠在數組對象之外操做數組的內容,這種操做將反映在返回該數組的對象中。若是該方法返回可改變的對象,外部實體能夠改變在那個類中聲明的 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; } }
垃圾收集器只收集不可達的對象,所以,存在未使用的可到達的對象,仍然表示內存管理不善。過分的內存泄漏可能會致使內存耗盡,拒絕服務(DoS)。
對於捕獲的異常要進行相應的處理,不能忽略已捕獲的異常
不正確寫法:
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 } } }
沒有過濾敏感信息的異常堆棧每每會致使信息泄漏,
不正確的寫法:
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; } } }
不正確的寫法:
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 { //... }
不正確的寫法:
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])); }
對於共享變量,要確保一個線程對它的改動對其餘線程是可見的。 線程可能會看到一個陳舊的共享變量的值。爲了共享變量是最新的,能夠將變量聲明爲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; } }
除了要確保共享變量的更新對其餘線程可見的,還須要確保對共享變量的操做是原子的,這時將共享變量聲明爲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(); } } }
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(); } }
有限線程池指定能夠同時執行在線程池中的線程數量的上限。程序不得使用有限線程池線程執行相互依賴的任務。可能會致使線程飢餓死鎖,全部的線程池執行的任務正在等待一個可用的線程中執行一個內部隊列阻塞
Java的文件操做方法每每有一個返回值,而不是拋出一個異常,表示失敗。所以,忽略返回值文件操做的程序,每每沒法檢測到這些操做是否失敗。Java程序必須檢查執行文件I / O方法的返回值。
不正確的寫法:
File file = new File(args[0]); file.delete(); 正確的寫法: File file = new File("file"); if (!file.delete()) { log.error("Deletion failed"); }
垃圾收集器沒法釋放非內存資源,如打開的文件描述符與數據庫的鏈接。所以,不釋放資源,可能致使資源耗盡攻擊。
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 }
序列化容許一個對象的狀態被保存爲一個字節序列,而後從新在稍後的時間恢復,它沒有提供任何機制來保護序列化的數據。敏感的數據不該該被序列化的例子包括加密密鑰,數字證書。 解決方法:
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(); } }
不正確的寫法:
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(); } } } }
(完成)