修复J2EE漏洞:5. 文件上传漏洞

ESAPI

  1. 使用ESAPI验证上传文件名
boolean validFileName = ESAPI.validator().isValidFileName("文件名", fileName, false);  

源码解析:

isValidFileName()同名方法共有三个

boolean isValidFileName(String context, String input, boolean allowNull) throws IntrusionException;

boolean isValidFileName(String context, String input, boolean allowNull, ValidationErrorList errorList) throws IntrusionException;

boolean isValidFileName(String context, String input, List<String> allowedExtensions, boolean allowNull) throws IntrusionException;  

区别在于是否有自定义的白名单后缀,是否有自定义的黑名单。

context:用户出错时日志中的标识  
input:待检查的文件名  
allowNull:是否允许文件名为空  
errorList:自定义的黑名单  
allowedExtensions:自定义的白名单  
public boolean isValidFileName(String context, String input, List<String> allowedExtensions, boolean allowNull, ValidationErrorList errors) throws IntrusionException {  
        try {
            getValidFileName( context, input, allowedExtensions, allowNull);
            return true;
        } catch( ValidationException e ) {
            errors.addError(context, e);
            return false;
        }
    }

若没有自定义的白名单,则使用系统默认配置ESAPI.securityConfiguration().getAllowedFileExtensions()(这个内容可以在ESAPI.properties文件中查找HttpUtilities.ApprovedUploadExtensions)

public String getValidFileName(String context, String input, List<String> allowedExtensions, boolean allowNull) throws ValidationException, IntrusionException {  
        if ((allowedExtensions == null) || (allowedExtensions.isEmpty())) {
            throw new ValidationException( "Internal Error", "getValidFileName called with an empty or null list of allowed Extensions, therefore no files can be uploaded" );
        }

        String canonical = "";
        // detect path manipulation
        try {
            if (isEmpty(input)) {
                if (allowNull) return null;
                throw new ValidationException( context + ": Input file name required", "Input required: context=" + context + ", input=" + input, context );
            }

            // do basic validation
            canonical = new File(input).getCanonicalFile().getName();
            getValidInput( context, input, "FileName", 255, true );

            File f = new File(canonical);
            String c = f.getCanonicalPath();
            String cpath = c.substring(c.lastIndexOf(File.separator) + 1);


            // the path is valid if the input matches the canonical path
            if (!input.equals(cpath)) {
                throw new ValidationException( context + ": Invalid file name", "Invalid directory name does not match the canonical path: context=" + context + ", input=" + input + ", canonical=" + canonical, context );
            }

        } catch (IOException e) {
            throw new ValidationException( context + ": Invalid file name", "Invalid file name does not exist: context=" + context + ", canonical=" + canonical, e, context );
        }

        // verify extensions
        Iterator<String> i = allowedExtensions.iterator();
        while (i.hasNext()) {
            String ext = i.next();
            if (input.toLowerCase().endsWith(ext.toLowerCase())) {
                return canonical;
            }
        }
        throw new ValidationException( context + ": Invalid file name does not have valid extension ( "+allowedExtensions+")", "Invalid file name does not have valid extension ( "+allowedExtensions+"): context=" + context+", input=" + input, context );
    }

从上面的代码中,可以看到中间还有一层验证getValidInput( context, input, "FileName", 255, true );具体的规则可以在ESAPI.properties中查找Validator.FileName

2. 检查文件大小
ServletFileUpload upload=newServletFileUpload(factory);  
upload.setSizeMax(maxBytes);  

实际上现在流行的框架都已经集成了文件大小设置,比如SpringMVC

<bean id="multipartResolver"  
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 上传文件大小上限,单位为字节(10MB) -->
        <property name="maxUploadSize">
            <value>10485760</value>
        </property>
        <property name="defaultEncoding">
            <value>UTF-8</value>
        </property>
    </bean>

在实际业务代码中,个人建议文件名由系统生成(规则可以是md5(用户名)+System.currentTimeMillis()),文件后缀由系统白名单控制,文件大小由配置文件控制。

欢迎大家加入小密圈,一起交流进步

zhutougg

继续阅读此作者的更多文章