[超長文,謹入]一文解決面試、工做遇到的安全性問題

  安全問題實際上是不少程序員容易忽略的問題但須要咱們重視起來,提升應用程序的安全性。常出現的安全問題包括,程序接受數據可能來源於未經驗證的用戶,網絡鏈接和其餘不受信任的來源,若是未對程序接受數據進行校驗,則可能會引起安全問題等等,具體也能夠分紅如下幾方面:html

  • 數據校驗
  • 敏感信息
  • 加密算法
  • 序列化與反序列化
  • I/O操做
  • 多線程安全
  • 框架和組件

  在文章安全開發規範:開發人員必須瞭解開發安全規範(一)(涉及安全問題,以及解決方法和代碼實現) 中咱們闡述了一些關於數據檢驗的安全問題,接下來咱們繼續其餘部分的安全性問題分析與解決。前端

數據校驗-權限管理

規則1.10:禁止程序數據進行增、刪、改、查實對客戶端請求的數據過度相信而遺漏對於權限的斷定

垂直越權漏洞: 稱爲權限提高,是一種「基於URL的訪問控制」設計缺陷引發的漏洞。因爲Web應用程序沒有作權限控制或者僅在菜單上作了權限控制,致使惡意用戶只要猜想其餘管理頁面的URL,就能夠訪問或控制其餘角色擁有的數據或頁面,達到權限提高的目的。java

水平越權漏洞: 一種「基於數據的訪問控制」設計缺陷引發的漏洞。因爲服務器端在接收到請求數據進行操做時沒有判斷數據的所屬人而致使的越權數據訪問漏洞。如服務器端從客戶端提交的request參數(用戶可以控制的數據)中獲取用戶id,惡意攻擊者經過變換請求ID的值,查看或修改不屬於本人的數據。mysql

反例:程序員

@RequestMapping(value = "delete")
public String delete(HttpServletRequest request, @RequestParam long id) throws Exception{
   try {
       userManage.delete(id);
       request.setAttribute("msg","delete user success");
   }catch (Exception e){
       request.setAttribute("msg","delete user failure");
   }

   return list(request);
}

@RequestMapping(value = "/delete/{addrId}")
public Object remove(@RequestParam long addrId) {
    Map<String,Object> resMap=new HashMap<>();
    if(WebUtils.isLogged){
        this.addressService.removeUserAddress(addrId);
        resMap.put(Constans.RESP_STATUS_CODE_KEY,Constans.RESP_STATUS_CODE_SUCCESS);
        resMap.put(Constans.MESSAGE,"remove user address success");
    }else {
        resMap.put(Constans.RESP_STATUS_CODE_KEY,Constans.RESP_STATUS_CODE_FAIL);
        resMap.put(Constans.MESSAGE,"user is not login ,remove user address failure");
    }
    return resMap;
}
複製代碼

正例:垂直越權漏洞:在調用功能以前,驗證當前用戶身份是否有權限調用相關功能(推薦使用過濾器,進行統一權限驗證)web

public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException ,IOException{
    if(request.getSession(true).getAttribute("manager")==null){
        response.sendRedirect("noright.html");
        return;
    }
    UserManagerService userManagerService=new UserManagerService();
    request.setCharacterEncoding("utf-8");
    response.setCharacterEncoding("utf-8");
    String action=request.getParameter("action");
    if("add".equals(action)){
        String id=request.getParameter("userId");
        String name=request.getParameter("userName");
        String sex=request.getParameter("userSex");
    }
    //todo do somethings
}
複製代碼

數據校驗-權限管理

  1. 經過全局過濾器來檢測用戶是否登陸,是否對資源具備訪問權限。
  2. 權限訪問規則存入數據庫中
  3. web.xml中配置過濾器

public class PriviegeFilter implements Filter{ @Autowired private UserManagerService userManagerService;算法

@Override
    public void init(FilterConfig filterConfig) throws ServletException {
        List<UserAuthorization> userAuthorizationS=userManagerService.getUserAuthorizationInfo();
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        for(UserAuthorization userAuthorization: userAuthorizationS){
            // 從數據庫中獲取用戶受權信息
            if(!authen){
                throw new RuntimeException("您無權訪問頁面,請以合適身份登錄後查看");
            }
            filterChain.doFilter(servletRequest,servletResponse);
        }
    }

    @Override
    public void destroy() {
        
    }
}
複製代碼

數據校驗-權限管理

  1. SpringMVC:Spring Security提供了「基於URL的訪問控制」和「基於Method的訪問控制」。sql

  2. 在用戶進行操做時,從session中獲取用戶id,將傳入的參數與用戶的身份作綁定校驗。數據庫

    <sec:http>
         <sec:intercept-url parttern="/persident_portal/*" access="RILE_PERSIDENT"/>
         <sec:intercept-url parttern="/manager_portal/*" access="RILE_MANAGER"/>
         <sec:intercept-url parttern="/**" access="RILE_USER"/>
         <sec:form-login />
         <sec:logout />
      </sec:http>
    複製代碼

數據校驗-不安全的網絡傳輸

  http協議屬於明文傳輸協議,交互過程以及數據傳輸都沒有進行加密,通訊雙方也沒有進行任何認證,通訊過程很是容易遭遇劫持、監聽、篡改,嚴重狀況下,會形成惡意的流量劫持等問題,甚至形成我的隱私泄露(好比銀行卡卡號和密碼泄露)等嚴重的安全問題。
HTTPS在HTTP的基礎上加入了SSL協議,SSL依靠證書來驗證服務器的身份,併爲瀏覽器和服務器之間的通訊加密。json

****對稱加密

非對稱加密

https協議(http+ssl協議),以下圖所示爲其鏈接過程:

中間人攻擊

數字證書:解決中間人攻擊

  數字證書是一個經證書受權中心數字簽名的包含公開密鑰擁有者信息以及公開密鑰的文件 客戶端拿到證書後,根據證書用第三方的私鑰進行上的方法本身生成一個證書編號,若是本身生成的證書編號與證書上的證書編號相同,那 麼說明這個證書是真實的。同時,爲避免證書編號自己又被調包,因此使加密。

總結
  HTTPS要使客戶端與服務器端的通訊過程獲得安全保證,必須使用的對稱加密算法,可是協商對稱加密算法的過程,須要使用非對稱加密算法 來保證安全,然而直接使用非對稱加密的過程自己也不安全,會有中間人篡改公鑰的可能性,因此客戶端與服務器不直接使用公鑰,而是使用 數字證書籤發機構(CA)頒發的證書來保證非對稱加密過程自己的安全,爲了保證證書不被篡改,引入數字簽名,客戶端使用相同的對稱加 密算法,來驗證證書的真實性,如此,最終解決了客戶端與服務器端之間的通訊安全問題。

數據校驗-不安全的網絡傳輸

規則1.11:敏感數據在跨信任域之間傳遞採用簽名加密傳輸

  敏感數據傳輸過程當中要防止竊取和惡意篡改。使用安全的加密算法加密傳輸對象能夠保護數據。這就是所謂的對對象進行密封。而對密封的對象進行數字簽名則能夠防止對象被非法篡改,保持其完整性

public static void main(String[] args) throwsIOException,ClassNotFoundException{
    // Build map
    SerializableMap<String, Integer> map = buildMap();
    // Serialize map
    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data"));
    out.writeObject(map);
    out.close();
    // Deserialize map
    ObjectInputStream in = new ObjectInputStream(new FileInputStream("data"));
    map = (SerializableMap<String, Integer>) in.readObject();
    in.close();
    // Inspect map
    InspectMap(map); 
}
複製代碼

反例:

public static void main(String[] args) throwsIOException,GeneralSecurityException, ClassNotFoundException{
    // Build map
    SerializableMap<String, Integer> map = buildMap();
    // Generate sealing key & seal map
    KeyGenerator generator = KeyGenerator.getInstance("AES");
    generator.init(newSecureRandom());
    Key key= generator.generateKey();
    Cipher cipher= Cipher.getInstance("AES");
    cipher.init(Cipher.ENCRYPT_MODE, key);
    SealedObject sealedMap= new SealedObject(map, cipher);
    // Serialize map
    ObjectOutputStreamout = new ObjectOutputStream(newFileOutputStream("data"));
    out.writeObject(sealedMap);
    out.close();
    // Deserialize map
    ObjectInputStream in = newObjectInputStream(newFileInputStream("data"));
    sealedMap= (SealedObject) in.readObject();
    in.close();
    // Unseal map
    cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.DECRYPT_MODE, key);
    map = (SerializableMap<String, Integer>) sealedMap.getObject(cipher);
    // Inspect map
    InspectMap(map);

}


public static void main(String[] args) throwsIOException, GeneralSecurityException, ClassNotFoundException{
    SerializableMap<String, Integer> map = buildMap();
    KeyGenerator generator = KeyGenerator.getInstance("AES");
    generator.init(newSecureRandom());
    Key key= generator.generateKey();
    Cipher cipher= Cipher.getInstance("AES");
    cipher.init(Cipher.ENCRYPT_MODE, key);
    SealedObjectsealedMap= newSealedObject(map, cipher);
    KeyPairGeneratorkpg= KeyPairGenerator.getInstance("RSA");
    KeyPair kp= kpg.generateKeyPair();
    Signature sig = Signature.getInstance("SHA256withRSA");
    SignedObject signedMap= newSignedObject(sealedMap, kp.getPrivate(), sig);
    ObjectOutputStreamout = newObjectOutputStream(newFileOutputStream("data"));
    out.writeObject(signedMap);
    out.close();
    ObjectInputStream in = newObjectInputStream(newFileInputStream("data"));
    signedMap= (SignedObject) 
    in.readObject();
    in.close();
    if(!signedMap.verify(kp.getPublic(), sig)){
        throw new GeneralSecurityException("Map failed verification");
    }
    sealedMap= (SealedObject) signedMap.getObject();
    cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.DECRYPT_MODE, key);
    map = (SerializableMap<String, Integer>) sealedMap.getObject(cipher);
    InspectMap(map);

}
複製代碼

正例:

public static void main(String[] args) throws IOException, GeneralSecurityException, ClassNotFoundException{
    SerializableMap<String, Integer> map = buildMap();
    KeyPairGenerator kpg= KeyPairGenerator.getInstance("RSA");
    KeyPair kp= kpg.generateKeyPair();
    Signature sig = Signature.getInstance("SHA256withRSA");
    SignedObject signedMap= new SignedObject(map, kp.getPrivate(), sig);
    
    KeyGenerator generator = KeyGenerator.getInstance("AES");
    generator.init(new SecureRandom());
    Key key= generator.generateKey();
    Cipher cipher= Cipher.getInstance("AES");
    cipher.init(Cipher.ENCRYPT_MODE, key);
    SealedObject sealedMap = new SealedObject(signedMap, cipher);
    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data"));
    out.writeObject(sealedMap);
    out.close();
    // Deserialize map 
    ObjectInputStream in = new ObjectInputStream(new FileInputStream("data")); 
    sealedMap= (SealedObject) in.readObject();
    in.close();
    // Unseal map 
    cipher = Cipher.getInstance("AES"); 
    cipher.init(Cipher.DECRYPT_MODE, key);
    signedMap= (SignedObject) sealedMap.getObject(cipher);
    // Verify signature and retrieve map 
    if(!signedMap.verify(kp.getPublic(), sig)){
        throw new GeneralSecurityException("Map failed verification");
    }
    map = (SerializableMap<String, Integer>) signedMap.getObject();
    // Inspect map 
    InspectMap(map);
}
複製代碼

敏感信息

敏感信息-常見的敏感信息

規則2.1:禁止在日誌中明文保存用戶敏感數據

日誌中如明文保存用戶敏感數據,容易泄露給運維人員或者攻破系統的攻擊者

規則2.2:禁止將敏感信息硬編碼在程序中

  若是將敏感信息(包括口令和加密密鑰)硬編碼在程序中,可能會將敏感信息暴露給攻擊者。任何可以訪問到class 文件的人均可以反編譯class文件並發現這些敏感信息

...  
DriverManager.getConnection(url,"soctt","tiger")  
...  
複製代碼

Java反編譯
javap c Connmngr.class

ldc #36://String jdbc:mysql://ixne.com/rxsql
ldc #38://String scott
ldc #17://String tiger

反例:

public class IPaddress{

private String ipAddress= "172.16.254.1";

	public static voidmain(String[] args){
	//...
	}
}
複製代碼

正例:

public class IPaddress{

	public static void main(String[] args) throws IOException{
		char[] ipAddress= new char[100];
		BufferedReader br= new BufferedReader(newInputStreamReader(newFileInputStream("serveripaddress.txt")));
		// Reads the server IP address into the char array,
		// returns the number of bytes read 
		intn = br.read(ipAddress);
		// Validate server IP address
		// Manually clear out the server IP address
		// immediately after use 
		for(inti= n -1; i>= 0; i--){
		ipAddress[i] = 0;
		}
		br.close();
	} 
}
複製代碼

規則2.3:加密傳輸郵件-郵件傳輸時需使用安全協議SSL/TLS加密傳輸,避免攻擊者在網絡上嗅探到用戶數據 反例:郵件傳輸時未使用TLS協議

public class SendMailTLS{
	public static void main(String[] args) {
		final String username="username@gmail.com";
		final String password="password";
		Properties props=new Properties();
		//使用TLS
		//props.put("mail.smtp.auth","true");
		//props.put("mail.smtp.startls.enable","true");

		//使用SSL
		//props.put("mail.smtp.socketFactory,port","465");
		//props.put("mail.smtp.socketFactory.class","javax.net.ssl.SSLSocketFactory");
		//props.put("mail.smtp.auth","true");
		
		props.put("mail.smtp.host","smtp.gmail.com");
		props.put("mail.smtp.port","587");
		Session session=Session.getInstance(props,new javax.mail.Authenticator){
			protected PasswordAuthentication getPasswordAuthentication(){
				return new PasswordAuthentication(username,password);
			}
		});

	}
}
複製代碼

正例:加密使用TLS協議

public class SendMailTLS{
	public static void main(String[] args) {
		final String username="username@gmail.com";
		final String password="password";
		Properties props=new Properties();
		//使用TLS
		props.put("mail.smtp.auth","true");
		props.put("mail.smtp.startls.enable","true");
		//作服務器證書校驗
		props.put("mail.smtp.ssl.checkserveridentity","true");
		
		//添加信任的服務器地址,多個地址之間用空格分開
		props.put("mail.smtp.ssl.trust","smtp.gmail.com");
		props.put("mail.smtp.host","smtp.gmail.com");
		props.put("mail.smtp.port","25");
		Session session=Session.getInstance(props,new javax.mail.Authenticator){
			protected PasswordAuthentication getPasswordAuthentication(){
				return new PasswordAuthentication(username,password);
			}
		});

	}
}
複製代碼

規則2.4:基於hash算法的口令存儲必須加鹽值(salt),而且使用標準的迭代PBKDF2

若是不加鹽值,會致使相同的密碼獲得相同的Hash值

public class PasswordHash{
	public static final String PBKDF2_ALGORITHM="PBKDF2WithHmacSHA1";
	public static final int SALT_BYTE_SIZE=24;
	public static final int HASH_BYTE_SIZE=24;
	public static final int PBKDF2_ITERATIONS=1000;

	public static String createHash(char[] password)
		throws NoSuchAlgorithmException,InvalidKeySpecException{
		
		//Generate a random salt 
		SecureRandom random =new SecureRandom();
		byte[] salt =new btye[SALT_BYTE_SIZE];
		random.nextBytes(salt);
		// Hash the password 
		byte[] hash =pbkdf2(password,salt,PBKDF2_ITERATIONS,HASH_BYTE_SIZE);
		//format iterations:salt:hash
		return PBKDF2_ITERATIONS+":"+toHex(hash);
	
	}
}
複製代碼

加密算法

加密算法-加密算法總覽

規則3.1不安全加密算法-禁止使用不安全的加密算法DES\3DES

加密算法應該使用安全的加密算法AES攻擊者可以破解不安全的加密算法,獲取到敏感信息

反例:使用了不安全算法DES

byte[] result = DES.encrypt(str.getBytes(),password);
//直接將如上內容解密
try{
byte[] decryResult =DES.decrypt(result,password);
System.out.println("解密後:"+new String(decryResult));
}catch(Excption e1){
e1.printStackTrace();
}
複製代碼

規則3.3:對稱加密算法AES-禁止使用AES中不安全的分組模式ECB,推薦使用不只提供加密而且還提供完整性校驗的AES-GCM

AES分組密碼模式還有:AES-CBC\AES-CFB\AES-OFB\AES-CTR(AES-CTR因爲能並行計算,效率最高)

反例:使用了AES中不安全的分組模式ECB

pubilc class AES{
	//加密
	public static String Encrypt(String sSrc,String sKey) throws Excetion{
		if(sKey==null){
			System.out.print("key爲空null");
			return null;
		}
		//判斷key是否爲16位
		if(key.length()!=16){
			System.out.print("key長度不是16位");
			return null;
		}
		byte[] raw =sKey.getBytes("UTF-8");
		SecreKeySpec skeySpec=new SecreKeySpec(raw,"AES");
		Cipher cipher=Cipher.getInstance("AES/ECB/PKCS5Padding");//算法/模式/補碼方式
		cipher.init(Cipher.ENCRYPT_MODE,skeySpec);
		byte[] encrypted= cipher.doFinal(sScr.getByte("utf-8"));

		return new Base64().encodeToString(encrypted);//此處使用Base64作轉碼功能,同時能起到2次加密做用
	}

}
複製代碼

正例:使用AES算法中安全的分組模式CBC

pubilc class AES{
	//加密
	public static String Encrypt(String sSrc,String sKey) throws Excetion{
		if(sKey==null){
			System.out.print("key爲空null");
			return null;
		}
		//判斷key是否爲16位
		if(key.length()!=16){
			System.out.print("key長度不是16位");
			return null;
		}
		byte[] raw =sKey.getBytes("UTF-8");
		SecreKeySpec skeySpec=new SecreKeySpec(raw,"AES");
		Cipher cipher=Cipher.getInstance("AES/ECB/PKCS5Padding");//算法/模式/補碼方式
		IvParameterSpec iv =new IvParameterSpec(sKey.getByte());//使用CBC模式,須要一個向量iv
		//可增長加密算法的強度
		cipher.init(Cipher.ENCRYPT_MODE,skeySpec,iv);
		byte[] encrypted= cipher.doFinal(sScr.getByte("utf-8"));

		return new Base64().encodeToString(encrypted);//此處使用Base64作轉碼功能,同時能起到2次加密做用
	}

}
複製代碼

規則3.4:非對稱加密算法RSA-非對稱加密算法RSA的使用須要注意長度至少爲2048位

RSA的密鑰長度若是低於2048位,達不到安全標準 反例:RSA算法的密鑰長度只有1024位

public static HashMap<String,Object>getKeys()throws NoSuchAlgorithmException{
	HashMap<String,Object> map=new HashMap<String,Object>;
	KeyPairGenerator keyPairGen=KeyPairGenerator.getInstance("RSA");
	keyPairGen.initialize(1024);
	KeyPair keyPair=keyPairGen.generateKeyPair();
	RSAPublicKey pubilcKey=(RSAPublicKey) keyPair.getPublic();
	RSAPrivateKey privateKey =(RSAPrivateKey) keyPair.getPrivate();
	map.put("public",pubilcKey);
	map.put("private",privateKey);
	return map;
}
複製代碼

正例:RSA算法的密鑰長度至少2048位

public static HashMap<String,Object>getKeys()throws NoSuchAlgorithmException{
	HashMap<String,Object> map=new HashMap<String,Object>;
	KeyPairGenerator keyPairGen=KeyPairGenerator.getInstance("RSA");
	keyPairGen.initialize(2048);
	KeyPair keyPair=keyPairGen.generateKeyPair();
	RSAPublicKey pubilcKey=(RSAPublicKey) keyPair.getPublic();
	RSAPrivateKey privateKey =(RSAPrivateKey) keyPair.getPrivate();
	map.put("public",pubilcKey);
	map.put("private",privateKey);
	return map;
}
複製代碼

規則3.5:敏感數據加密使用強隨機數

  僞隨機數生成器具備可移植性和可重複性,攻擊者能夠在系統的一些安全脆弱點上監聽,並構建相應的查詢表預測將要使用的seed值,從而去預測相關的敏感數據
僞隨機數示例:

import java.util.Random;
public class RandomDemo{
    Random random1=new Random(100);
    System.out.println(random1.nextInt());
    System.out.println(random1.nextFloat());
    System.out.println(random1.nextBoolean());
    
    Random random2=new Random(100);
    System.out.println(random2.nextInt());
    System.out.println(random2.nextFloat());
    System.out.println(random2.nextBoolean());
}

import java.io.UnsupporedEncodeingException;
import java.util.Random;

public class SecureRandom{
    public static void main(String[] args)throws UnsupporedEncodeingException{
        Random ranGen=new Random();
        byte[] aesKey=new byte[20];
        ranKey.nextBytes(aesKey);
        StringBuffer hexString =new StringBuffer();
        for (int i=0;i<aesKey.length;i++){
            String hex=Integer.toHexString(0xff&aesKey[i]);
            if(hex.length()==1)
                hexString.append(''0);
            hexString.append(hex);
        }
        System.out.println(hexString);
    }
}
複製代碼

強隨機數示例:

import java.io.UnsupporedEncodeingException;
import java.util.Random;
import java.security.SecureRandom;

public class SecureRandom{
    public static void main(String[] args)throws UnsupporedEncodeingException{
        Random ranGen=new SecureRandom();
        byte[] aesKey=new byte[20];
        ranKey.nextBytes(aesKey);
        StringBuffer hexString =new StringBuffer();
        for (int i=0;i<aesKey.length;i++){
            String hex=Integer.toHexString(0xff&aesKey[i]);
            if(hex.length()==1)
                hexString.append(''0);
            hexString.append(hex);
        }
        System.out.println(hexString);
    }
}
複製代碼

序列化與反序列化

  • Java 序列化是指把Java 對象轉換爲字節序列的過程便於保存在內存、文件、數據庫中,ObjectOutputStream類的writeObject()方法能夠實現序列化。

  • Java 反序列化是指把字節序列恢復爲Java 對象的過程,ObjectInputStream 類的readObject() 方法用於反序列化。

    public class Test{
          public static void main(String[] args) throws Exception{
              //定義myObj對象
              MyObject myObj=new MyObject();
              //建立一個包含對象進行反序列化信息的「object」數據文件
              FileOutputStream fos=new FileOutputStream("object");
              ObjectOutputStream os =new ObjectOutputStream(fos);
              //writeObject()方法將myObj對象寫入objct文件中
              os.writeObject(myObj);
              os.close();
              //從文件中反序列化obj對象
              FileInputStream fis=new FileInputStream("object");
              ObjectInputStream ois =new ObjectInputStream(fis);
              //恢復對象
              MyObject objectFromDisk=(MyObject)ois.readObject();
              System.out.println(objectFromDisk.name);
              ois.close;
          }
      }
    
      class MyObject implements Serializable{
          public String name;
          //重寫readObject()方法
          private void readObject(java.io.ObjectInputStream in) throws IOExeption{
              //執行默認的readObject()方法
              in.defaultReadObject();
              //執行打開計算器程序命令
              Runtime.getRuntime().exec("open /Application/Calcultor.app")
          }
      }
    複製代碼

序列化與反序列化-反序列化漏洞

類ObjectInputStream在反序列化時,應對生成的對象的類型作限制

反例:不安全的反序列化操做,致使執行任意命令、控制服務器

ServerSocket serverSocket =new ServerSocket(Integer,parseInt("9999"));
while(true){
    Socket socket=serverSocket.accpet();
    ObjectInputStream objectInputStream=new ObjectInputStream(socket.getInputStream());
    try{
        Object object=objectInputStream.readObject();
    }catch(Exception e){
        e.printStackTrace();
    }
}
複製代碼

規則4.1:類ObjectInputStream在反序列化時,對生成的對象的類型作限制

public final  class SecureObjectInputStram extends ObjectInputStream{
    public SecureObjectInputStram() throws IOException{
        super();
    }
    public SecureObjectInputStram( InputStream in) throws IOException{
        super(in);
    }
    portected Class<?>resolveClass(ObjectStreamClass desc) throws ClassNotFoundException,IOException{
        if(!desc.getName().equals("java.security.Person")){
            throws new ClassNotFoundException(desc.getName()+"not found");
        }
        return super.resolveClass(desc);
    }
}
複製代碼

序列化與反序列化-不安全的反序列化漏洞解決方案

解決方案:

  • 使用安全的反序列化插件,對於業界爆出的反序列化漏洞,及時更新插件和打補丁。
  • 傳輸的數據應該加密,而且在後臺進行數據校驗,保證數據沒有被篡改。
  • 自定義ObjectInputStream, 重載resolveClass的方法,對className進行白名單校驗。
  • Java9能夠繼承java.io.ObjectInputFilter類重寫checkInput方法實現自定義的過濾器。
  • 經過擴展SecurityManager 來實現禁止JVM 執行外部命令Runtime.exec。

I/O操做

規則5.1:文件上傳應根據業務的須要限定文件的格式和大小

反例:未限定格式

@RequestMapping(value="/upload",method=RequestMethod.POST)
public String upload(HttpServletRequest request, @RequestParam("description") String description,
                    @RequestParam("file") MultipartFile file)throws Exception{
            if(!file.isEmpty()){
             String path=request.getServletContext().getRealPath("/images/");
            //上傳文件名
             String filename=file.getOriginalFilename();
            File filepath=new File(path,filename);
             //判斷路徑是否存在,若是不存在就建立一個
            if(!filepath.getParentFile().exists()){
                   filepath.getParentFile.mkdirs();
            }
            //將文件上傳保存到一個目標文件中
            file.transferTo(new File(path+File.separator+filename));
                return "success"
            }else{
                return "error";
             }
          }
複製代碼

正例:限定文件的上傳的格式

@RequestMapping(value="/upload",method=RequestMethod.POST)
public String upload(HttpServletRequest request, @RequestParam("description") String description,
                    @RequestParam("file") MultipartFile file)throws Exception{
        if(!file.isEmpty()){
            String path=request.getServletContext().getRealPath("/images/");
             //上傳文件名
            String filename=file.getOriginalFilename();
            //還有一種方式filenameUtils.getExtension();
            String suffix=filename.substring(filename.lastIndexOf(".")+1)
            if(suffix!="jpg"){
                   File filepath=new File(path,filename);
                   //判斷路徑是否存在,若是不存在就建立一個
                   if(!filepath.getParentFile().exists()){
                            filepath.getParentFile.mkdirs();
                        }
                    //將文件上傳保存到一個目標文件中
                     file.transferTo(new File(path+File.separator+filename));
                    }
                return "success" ;
            }else{
                return "error";
           }
       }
複製代碼

規則5.2:路徑遍歷-文件下載的地方,應對文件的路徑進行校驗,或者使用文件id映射到文件的方式下載文件

反例:

protected void doGet(HttpServletRequest request,HttpServeltResponse response)
        throws ServeltExceptio,IOException{
        //獲取項目部署絕對路徑下的upload文件夾路徑,下載upload目錄下的文件
        String root =request.getServeltContext().getRealPath("/upload");
        //獲取文件名
        String filename=request.getParameter("filename");
        //根據文件路徑建立輸入流
        File file=new File(root+"/"+filename);
        FileInputStream fis= new FileInputStream(file);
        //設置響應頭
        response.addHeader("Content-Disposition","attachment;filename="+new String(filename.getBytes()));
        response.addHeader("Content-Length",""+file.length());
        byte[] b =new byte[fis.availabe()];
        fis.read(b);
        response.getOutStream().write(b);
        }
複製代碼

正例:使用白名單校驗文件名

protected void doGet(HttpServletRequest request,HttpServeltResponse response)
        throws ServeltExceptio,IOException{
        //獲取項目部署絕對路徑下的upload文件夾路徑,下載upload目錄下的文件
        String root =request.getServeltContext().getRealPath("/upload");
        //獲取文件名
        String filename=request.getParameter("filename");
        if(filename==FILENAME){
               //根據文件路徑建立輸入流
            File file=new File(root+"/"+filename);
            FileInputStream fis= new FileInputStream(file);
            //設置響應頭
            response.addHeader("Content-Disposition","attachment;filename="+new String(filename.getBytes()));
            response.addHeader("Content-Length",""+file.length());
            byte[] b =new byte[fis.availabe()];
            fis.read(b);
            response.getOutStream().write(b);  
             }
        }
複製代碼

規則5.3:未釋放的流資源(Unreleased Resource,好比Streams使用文件、IO流、數據庫鏈接等)主動釋放的資源

  使用文件、IO流、數據庫鏈接等不會自動釋放的資源時,未在使用完畢後立刻將其關閉,關閉資源的代碼應在try...catch...finally{if (fos != null) {fos.close();}}的finally內執行

private static void TestCloseFileStream(){
    String fileName="";
    //聲明引用
    InputStream inputStream=null;
    try{
        inputStream=new FileInputStream(filename);
    }catch(IOException e){
        //do something
    }finally{
        if(inputStream!=null){
            try{
                //關閉流
              inputStream.close();  
            }catch(IOException e){
                //do something
            }
        }
    }
}
複製代碼

規則5.4:臨時文件使用完畢應及時刪除

反例:

public class TempFile{
    public static void main(STring[] args) throws IOExcption{
        File f =new File("tempnam.tmp");
        if(f.exists()){
            System.out.println("This file already exists");
            return;
        }
        FileOutputStream fop=null;
        try{
            fop=new FileOutputStream(f);
            String str="Data";
            fop.write(str.getBytes());
        }finally{
            if(fop!=null){
                try{
                    fop.close();
                }catch(IOException e){
                    // handle error
                }
            }
        }
    }
}
複製代碼

正例:

public class TempFile{
    public static void main(STring[] args) throws IOExcption{
        File f =new File("tempnam.tmp");
        if(f.exists()){
            System.out.println("This file already exists");
            return;
        }
        FileOutputStream fop=null;
        try{
            fop=new FileOutputStream(f);
            String str="Data";
            fop.write(str.getBytes());
        }finally{
            if(fop!=null){
                try{
                    fop.close();
                }catch(IOException e){
                    // handle error
                }
                //delete file when finished
                if(!f.delete()){
                    //log the error
                }
            }
        }
    }
}
複製代碼

多線程安全(Double-Checked Locking)

規則6.1:多線程下采用加鎖機制,防止多線程操做產生的死鎖

雙重鎖機制(多線程下不安全)

public class Singleton{
    private static Singleton singleton;
    private Singleton(){}
    public static Singleton getSingleton(){
        if(singleton==null){
            synchronized (Singleton.class){
                if(singleton==null){
                    singleton=new Singleton();
                }
            }
        }
        return singleton;
    }
}


singleton = new Singleton()非原子性  
1. memory=allocate();//1:分配對象的內存空間  
2. ctorInstance(memory);//2:初始化對象  
3. singleton=memory;//3:設置instance指向剛分配的內存地址  
指令重排後:  
1. memory=allocate();//1:分配對象的內存空間  
2. singleton=memory;//3:設置instance指向剛分配的內存地址//注意,此時對象尚未被初始化!  
3. ctorInstance(memory);//2:初始化對象  
複製代碼

多線程安全(Double-Checked Locking)
對方法使用synchronized關鍵字

public class Singleton{
    private static Singleton singleton;
    private Singleton(){}
    public static synchronized Singleton getSingleton(){
        if(singleton==null){
            singleton=new Singleton();
            }
        return singleton;
        }
}
複製代碼

提早初始化

public class Singleton{
     private static class SingletonHolder{
          private static final Singleton singleton=new Singleton();
     }
    private Singleton(){}
    public static final Singleton getSingleton(){
        return SingletonHolder.INSTANCE;
        }
}
複製代碼

框架和組件-框架和組件安全

規則7.1:使用安全版本的框架和組件,官網下載,使用框架和組件,先到網上搜掃下是否有公開的漏洞

好比: --WebLogic、Struts二、Nginx --Fastjson、ImageMagick、Log4j

規則7.2:禁止使用來源不明的框架或者組件

規則7.3:及時更新框架和組件版本

異常行爲-異常信息暴露到外部

規則8.1:異常信息禁止暴露到前端

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

解析:產生固定的錯誤信息,未泄露異常信息到外部


最後可關注公衆號,一塊兒學習,天天會分享乾貨,還有學習視頻領取!

相關文章
相關標籤/搜索