• 欢迎访问 winrains 的个人网站!
  • 本网站主要从互联网整理和收集了与Java、网络安全、Linux等技术相关的文章,供学习和研究使用。如有侵权,请留言告知,谢谢!

开发中遇到的安全性问题解决方法

Java技术 winrains 来源:Ccww 12个月前 (11-11) 70次浏览
安全问题其实是很多程序员容易忽略的问题但需要我们重视起来,提高应用程序的安全性。常出现的安全问题包括,程序接受数据可能来源于未经验证的用户,网络连接和其他不受信任的来源,如果未对程序接受数据进行校验,则可能会引发安全问题等等,具体也可以分成以下几方面:
  • 数据校验
  • 敏感信息
  • 加密算法
  • 序列化与反序列化
  • 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依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。
****对称加密

非对称加密

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

public 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

public 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);
    }
    protected Class<?> resolveClass(ObjectStreamClass desc) throws ClassNotFoundException, IOException {
        if (!desc.getName().equals("java.security.Person")) {
            throw 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();// 非原子性
memory = allocate();// 1:分配对象的内存空间
ctorInstance(memory);// 2:初始化对象
singleton = memory;// 3:设置instance指向刚分配的内存地址
// 指令重排后:
memory = allocate();// 1:分配对象的内存空间
singleton = memory;// 3:设置instance指向刚分配的内存地址//注意,此时对象还没有被初始化!
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
    throw new IOException("Unable to retrieve file", e);
}

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

作者:Ccww

来源:https://juejin.im/post/5d858de26fb9a06b1027652d


版权声明:文末如注明作者和来源,则表示本文系转载,版权为原作者所有 | 本文如有侵权,请及时联系,承诺在收到消息后第一时间删除 | 如转载本文,请注明原文链接。
喜欢 (0)