[超长文,谨入]一文解决面试、工作遇到的安全性问题

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
访问控制,不限时长
简介: 安全问题其实是很多程序员容易忽略的问题但需要我们重视起来,提高应用程序的安全性。常出现的安全问题包括,程序接受数据可能来源于未经验证的用户,网络连接和其他不受信任的来源,如果未对程序接受数据进行校验,则可能会引发安全问题等等

安全问题其实是很多程序员容易忽略的问题但需要我们重视起来,提高应用程序的安全性。常出现的安全问题包括,程序接受数据可能来源于未经验证的用户,网络连接和其他不受信任的来源,如果未对程序接受数据进行校验,则可能会引发安全问题等等,具体也可以分成以下几方面:


  • 数据校验
  • 敏感信息
  • 加密算法
  • 序列化与反序列化
  • I/O操作
  • 多线程安全
  • 框架和组件


在文章安全开发规范:开发人员必须了解开发安全规范(一)(涉及安全问题,以及解决方法和代码实现)中我们阐述了一些关于数据检验的安全问题,接下来我们继续其他部分的安全性问题分析与解决。


数据校验-权限管理


规则1.10:禁止程序数据进行增、删、改、查实对客户端请求的数据过分相信而遗漏对于权限的判定


垂直越权漏洞: 称为权限提升,是一种“基于URL的访问控制”设计缺陷引起的漏洞。由于Web应用程序没有做权限控制或者仅在菜单上做了权限控制,导致恶意用户只要猜测其他管理页面的URL,就可以访问或控制其他角色拥有的数据或页面,达到权限提升的目的。


水平越权漏洞: 一种“基于数据的访问控制”设计缺陷引起的漏洞。由于服务器端在接收到请求数据进行操作时没有判断数据的所属人而导致的越权数据访问漏洞。如服务器端从客户端提交的request参数(用户能够控制的数据)中获取用户id,恶意攻击者通过变换请求ID的值,查看或修改不属于本人的数据。


反例:


@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;
}
复制代码


正例:垂直越权漏洞:在调用功能之前,验证当前用户身份是否有权限调用相关功能(推荐使用过滤器,进行统一权限验证)


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的访问控制”。
  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依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。


****对称加密


30.png


非对称加密


31.png


https协议(http+ssl协议),如下图所示为其连接过程:


32.png


中间人攻击


34.png


数字证书:解决中间人攻击


数字证书是一个经证书授权中心数字签名的包含公开密钥拥有者信息以及公开密钥的文件 客户端拿到证书后,根据证书用第三方的私钥进行上的方法自己生成一个证书编号,如果自己生成的证书编号与证书上的证书编号相同,那 么说明这个证书是真实的。同时,为避免证书编号本身又被调包,所以使加密。


总结


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);
}
复制代码


敏感信息


敏感信息-常见的敏感信息


35.png


规则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);
  }
}
复制代码


加密算法


加密算法-加密算法总览


36.png


规则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、Struts2、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);
}
复制代码


解析:产生固定的错误信息,未泄露异常信息到外部



目录
相关文章
|
缓存 移动开发 Oracle
常用的Linux命令(面试/工作必备)
常用的Linux命令(面试/工作必备)
141 0
常用的Linux命令(面试/工作必备)
|
JavaScript API
最全Vue3中组件的通讯方式都整理好了,面试不怕,工作不怕,建议收藏
最全Vue3中组件的通讯方式都整理好了,面试不怕,工作不怕,建议收藏
最全Vue3中组件的通讯方式都整理好了,面试不怕,工作不怕,建议收藏
|
存储 缓存 NoSQL
Redis缓存穿透和雪崩相关概念(面试高频,工作常用)
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面,但同时,它也带来了一些问题,其中,最重要的问题,就是数据的一致性问题。从严格意义上讲,这个无解。如果对数据的一致性要求很高,那么就不能使用缓存。
148 0
Redis缓存穿透和雪崩相关概念(面试高频,工作常用)
工作五年,一年内我靠这系列java面试宝典从13K到大厂30K
我认为对于面试以及进阶最佳的学习方法莫过于刷题+博客+书籍+总结! 前三者我将淋漓尽致地挥毫于这篇文章中,至于总结要靠个人。实际上越到后面你越会发现面试并不难,其次就是在刷题的过程中有没有去思考,刷题只是次之,这又是一个层次了,这里暂时不提后面再谈。 我总结了一系列大厂面试中常问的面试技术点、深入解析以及答案,将为最近准备面试的各位去大厂保驾护航! 何谓面试?
|
程序员 开发工具 git
iOS面试:如何在平时工作中积累经验
如果你是一个离职者,还没有找到工作,那么,个人愚见:         一、不打无准备的仗,在没有准备好的情况下切记不要盲目去参加面试,在现在竞争异常激烈的大背景下,没有准备好匆匆去参加面试,可想而知最后的面试结果是啥;         二、仅仅指望狂刷面试题,背记大牛的面试笔记,是远远不够的,一份工作的竞争比例是在1:300~400之间,如果你想在这几百人中脱颖而出,你就需要花费比别人更多的努力才行;
117 0
iOS面试:如何在平时工作中积累经验
|
存储 缓存 NoSQL
Redis两种持久化机制RDB和AOF详解(面试常问,工作常用)
redis是一个内存数据库,数据保存在内存中,但是我们都知道内存的数据变化是很快的,也容易发生丢失。幸好Redis还为我们提供了持久化的机制,分别是RDB(Redis DataBase)和AOF(Append Only File)。 在这里假设你已经了解了redis的基础语法,某字母网站都有很好的教程,可以去看。基本使用的文章就不写了,都是一些常用的命令。 下面针对这两种方式来介绍一下。由浅入深。
239 0
Redis两种持久化机制RDB和AOF详解(面试常问,工作常用)
|
存储 Java 数据库
java内存溢出问题(工作中常用、面试中常问的一个知识点)
内存溢出是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能提供的最大内存。这篇文章整理自《深入理解java虚拟机》。因为内存溢出问题不仅是工作中的一个重要方面,而且面试中也是经常问。
165 0
java内存溢出问题(工作中常用、面试中常问的一个知识点)
|
Web App开发 Prometheus 监控
BAT大厂都在用的Docker。学会这三招,面试、工作轻松hold住
BAT大厂都在用的Docker。学会这三招,面试、工作轻松hold住
|
存储 安全 NoSQL
《面试知识,工作可待:集合篇》-java集合面试知识大全
在工作上必须保持学习的能力,这样才能在工作得到更好的晋升,涨薪指日可待,欢迎一起学习【提升能力,涨薪可待】系列
171 0
《面试知识,工作可待:集合篇》-java集合面试知识大全