Java 代码安全指南.

发布于 2021-05-27 11:26:45

<details markdown="1">
<summary>目录</summary>

</details>

安卓类

I. 代码实现

1.1 异常捕获处理

1.1.1 【必须】序列化异常捕获

对于通过导出组件 intent 传递的序列化对象,必须进行 try...catch 处理,以避免数据非法导致应用崩溃。

public class MainActivity extends Activity {

    protected void onCreate(Bundle savedInstanceState) {
        try {
            Intent mIntent = getIntent(); 
            //String msg = intent.getStringExtra("data"); 
            Person mPerson = (Person)mIntent.getSerializableExtra(ObjectDemo.SER_KEY)
            //textView.setText(msg); 
        } catch (ClassNotFoundException exp) {
            // ......
        }
    }
}
1.1.2 【必须】NullPointerException 异常捕获

对于通过 intent getAction 方法获取数据时,必须进行 try...catch 处理,以避免空指针异常导致应用崩溃。

public class MainActivity extends Activity {
    
    protected void onCreate(Bundle savedInstanceState) {
        try {
            Intent mIntent = getIntent(); 
            if mIntent.getAction().equals("StartNewWorld") {
                // ......
            }
            // ......
        } catch (NullPointerException exp) {
            // ......
        }
    }
}
1.1.3 【必须】ClassCastException 异常捕获

对于通过 intent getSerializableExtra 方法获取数据时,必须进行 try...catch 处理,以避免类型转换异常导致应用崩溃。

public class MainActivity extends Activity {

    protected void onCreate(Bundle savedInstanceState) {
        try {
            Intent mIntent = getIntent(); 
            Person mPerson = (Person)mIntent.getSerializableExtra(ObjectDemo.SER_KEY)
            // ......
        } catch (ClassCastException exp) {
            // ......
        }
    }
}
1.1.4 【必须】ClassNotFoundException 异常捕获

同 1.1.3

1.2 数据泄露

1.2.1 【必须】logcat 输出限制

release 版本禁止在 logcat 输出信息。

public class MainActivity extends Activity {
    String DEBUG = "debug_version";

    protected void onCreate(Bundle savedInstanceState) {
        // ......
        if (DEBUG == "debug_version") {
            Log.d("writelog", "start activity");
        }
        // ......
    }
}

1.3 webview 组件安全

1.3.1 【必须】addJavaScriptInterface 方法调用

对于设置 minsdk <= 18 的应用,禁止调用 addJavaScriptInterface 方法。

public class MainActivity extends Activity {

    protected void onCreate(Bundle savedInstanceState) {
        // ......
        mWebView = new WebView(this);
        if (Build.VERSION.SDK_INT > 18) {
            mWebView.addJavascriptInterface(new wPayActivity.InJavaScriptLocalObj(this), "local_obj");
        }
        // ......
    }
}
1.3.2 【建议】setJavaScriptEnabled 方法调用

如非必要,setJavaScriptEnabled 应设置为 false 。加载本地 html ,应校验 html 页面完整性,以避免 xss 攻击。

public class MainActivity extends Activity {

    protected void onCreate(Bundle savedInstanceState) {
        // ......
        mWebView = new WebView(this);
        mWebView.getSettings().setJavaScriptEnabled(false);
        // ......
    }
}
1.3.3 【建议】setAllowFileAccess 方法调用

建议禁止使用 File 域协议,以避免过滤不当导致敏感信息泄露。

public class MainActivity extends Activity {

    protected void onCreate(Bundle savedInstanceState) {
        // ......
        mWebView = new WebView(this);
        mWebView.getSettings().setAllowFileAccess(false);
        // ......
    }
}
1.3.4 【建议】setSavePassword 方法调用

建议 setSavePassword 的设置为 false ,避免明文保存网站密码。
建议禁止使用 File 域协议,以避免过滤不当导致敏感信息泄露。

public class MainActivity extends Activity {

    protected void onCreate(Bundle savedInstanceState) {
        // ......
        mWebView = new WebView(this);
        mWebView.getSettings().setSavePassword(false);
        // ......
    }
}
1.3.5 【必须】onReceivedSslError 方法调用

webview 组件加载网页发生证书认证错误时,不能直接调用 handler.proceed() 忽略错误,应当处理当前场景是否符合业务预期,以避免中间人攻击劫持。

public class MainActivity extends Activity {

    protected void onCreate(Bundle savedInstanceState) {
        // ......
        mWebView = new WebView(this);
        mWebView.setWebViewClient(new WebViewClient() {
            @Override
            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                // must check error 
                check_error();
                handler.proceed();
            }
        }
        // ......
    }
}

1.4 传输安全

1.4.1 【必须】自定义 HostnameVerifier 类

自定义 HostnameVerifier 类后,必须实现 verify 方法校验域名,以避免中间人攻击劫持。

public class MainActivity extends Activity {
    
    protected void onCreate(Bundle savedInstanceState) {
        // ......
        HostnameVerifier hnv = new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                // must to do
                isValid = checkHostName(hostname);
                return isValid;
            }
        };
        // ......
    }
}
1.4.2 【必须】自定义 X509TrustManager 类

自定义 X509TrustManager 类后,必须实现 checkServerTrusted 方法校验服务器证书,以避免中间人攻击劫持。

public class MainActivity extends Activity {
    
    protected void onCreate(Bundle savedInstanceState) {
        // ......
        TrustManager tm = new X509TrustManager() {
            public void checkServerTrusted(X509Certificate[] chain, String authType)
                    throws CertificateException {
                // must to do
                check_server_valid();
            }
        };
        // ......
    }
}
1.4.3 【必须】setHostnameVerifier 方法调用

禁止调用 setHostnameVerifier 方法设置 ALLOW_ALL_HOSTNAME_VERIFIER 属性,以避免中间人攻击劫持。

public class MainActivity extends Activity {
    
    protected void onCreate(Bundle savedInstanceState) {
        // ......
        SchemeRegistry schemeregistry = new SchemeRegistry();
        SSLSocketFactory sslsocketfactory = SSLSocketFactory.getSocketFactory();
        // set STRICT_HOSTNAME_VERIFIER
        sslsocketfactory.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
        // ......
    }
}

II. 配置&环境

2.1 AndroidManifest.xml 配置

2.1.1 【必须】PermissionGroup 属性设置

禁止设置 PermissionGroup 属性为空。

2.1.2 【必须】protectionLevel 属性设置

对于自定义权限的 protectionLevel 属性设置,建议设置为 signature 或 signatureOrSystem。

2.1.3 【建议】sharedUserId 权限设置

最小范围和最小权限使用 sharedUserId 设置。

2.1.4 【建议】allowBackup 备份设置

如非产品功能需要,建议设置 allowBackup 为 false。

<application android:allowBackup="false"> 
</application>
2.1.5 【必须】debuggable 调试设置

release 版本禁止设置 debuggable 为 true。

<application android:debuggable="false"> 
</application>

后台类

I. 代码实现

1.1 数据持久化

1.1.1【必须】SQL语句默认使用预编译并绑定变量

Web后台系统应默认使用预编译绑定变量的形式创建sql语句,保持查询语句和数据相分离。以从本质上避免SQL注入风险。

如使用Mybatis作为持久层框架,应通过#{}语法进行参数绑定,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数。

示例:JDBC

String custname = request.getParameter("name"); 
String query = "SELECT * FROM user_data WHERE user_name = ? ";
PreparedStatement pstmt = connection.prepareStatement( query );
pstmt.setString( 1, custname); 
ResultSet results = pstmt.executeQuery( );

Mybatis

<select id="queryRuleIdByApplicationId" parameterType="java.lang.String" resultType="java.lang.String">    
      select rule_id from scan_rule_sqlmap_tab where application_id=#{applicationId} 
</select>

应避免外部输入未经过滤直接拼接到SQL语句中,或者通过Mybatis中的${}传入SQL语句(即使使用PreparedStatement,SQL语句直接拼接外部输入也同样有风险。例如Mybatis中部分参数通过${}传入SQL语句后实际执行时调用的是PreparedStatement.execute(),同样存在注入风险)。

1.1.2【必须】白名单过滤

对于表名、列名等无法进行预编译的场景,比如外部数据拼接到order by, group by语句中,需通过白名单的形式对数据进行校验,例如判断传入列名是否存在、升降序仅允许输入“ASC”和“DESC”、表明列名仅允许输入字符、数字、下划线等。参考示例:

public String someMethod(boolean sortOrder) {
 String SQLquery = "some SQL ... order by Salary " + (sortOrder ? "ASC" : "DESC");`
 ...

1.2 文件操作

1.2.1【必须】文件类型限制

须在服务器端采用白名单方式对上传或下载的文件类型、大小进行严格的限制。仅允许业务所需文件类型上传,避免上传.jsp、.jspx、.class、.java等可执行文件。参考示例:

       String file_name = file.getOriginalFilename();
        String[] parts = file_name.split("\\.");
        String suffix = parts[parts.length - 1];
        switch (suffix){
            case "jpeg":
                suffix = ".jpeg";
                break;
            case "jpg":
                suffix = ".jpg";
                break;
            case "bmp":
                suffix = ".bmp";
                break;
            case "png":
                suffix = ".png";
                break;
            default:
                //handle error
                return "error";
        }
1.2.2【必须】禁止外部文件存储于可执行目录

禁止外部文件存储于WEB容器的可执行目录(appBase)。建议保存在专门的文件服务器中。

1.2.3【建议】避免路径拼接

文件目录避免外部参数拼接。保存文件目录建议后台写死并对文件名进行校验(字符类型、长度)。建议文件保存时,将文件名替换为随机字符串。

1.2.4【必须】避免路径穿越

如因业务需要不能满足1.2.3的要求,文件路径、文件命中拼接了不可行数据,需判断请求文件名和文件路径参数中是否存在../或..\(仅windows), 如存在应判定路径非法并拒绝请求。

1.3 网络访问

1.3.1【必须】避免直接访问不可信地址

服务器访问不可信地址时,禁止访问私有地址段及内网域名。

// 以RFC定义的专有网络为例,如有自定义私有网段亦应加入禁止访问列表。
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
127.0.0.0/8

建议通过URL解析函数进行解析,获取host或者domain后通过DNS获取其IP,然后和内网地址进行比较。

对已校验通过地址进行访问时,应关闭跟进跳转功能。

参考示例:

     httpConnection = (HttpURLConnection) Url.openConnection();

     httpConnection.setFollowRedirects(false);

1.4 XML读写

1.4.1【必须】XML解析器关闭DTD解析

读取外部传入XML文件时,XML解析器初始化过程中设置关闭DTD解析。

参考示例:

javax.xml.parsers.DocumentBuilderFactory

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
    dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
    dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
    dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
    dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
    dbf.setXIncludeAware(false);
    dbf.setExpandEntityReferences(false);
    ……
}

org.dom4j.io.SAXReader

saxReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
saxReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
saxReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

org.jdom2.input.SAXBuilder

SAXBuilder builder = new SAXBuilder();
builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);
builder.setFeature("http://xml.org/sax/features/external-general-entities", false);
builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
Document doc = builder.build(new File(fileName));

org.xml.sax.XMLReader

XMLReader reader = XMLReaderFactory.createXMLReader();
reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

1.5 响应输出

1.5.1【必须】设置正确的HTTP响应包类型

响应包的HTTP头“Content-Type”必须正确配置响应包的类型,禁止非HTML类型的响应包设置为“text/html”。此举会使浏览器在直接访问链接时,将非HTML格式的返回报文当做HTML解析,增加反射型XSS的触发几率。

1.5.2【建议】设置安全的HTTP响应头
  • X-Content-Type-Options:

​ 建议添加“X-Content-Type-Options”响应头并将其值设置为“nosniff”,可避免部分浏览器根据其“Content-Sniff”特性,将一些非“text/html”类型的响应作为HTML解析,增加反射型XSS的触发几率。

  • HttpOnly:

​ 控制用户登录鉴权的Cookie字段 应当设置HttpOnly属性以防止被XSS漏洞/JavaScript操纵泄漏。

  • X-Frame-Options:

​ 设置X-Frame-Options响应头,并根据需求合理设置其允许范围。该头用于指示浏览器禁止当前页面在frame、iframe、embed等标签中展现。从而避免点击劫持问题。它有三个可选的值:
​ DENY: 浏览器会拒绝当前页面加载任何frame页面;
​ SAMEORIGIN:则frame页面的地址只能为同源域名下的页面
​ ALLOW-FROM origin:可以定义允许frame加载的页面地址。

  • Access-Control-Allow-Origin

    当需要配置CORS跨域时,应对请求头的Origin值做严格过滤。

    ...
    String currentOrigin = request.getHeader("Origin");
    if (currentOrigin.equals("https://domain.qq.com")) {
           response.setHeader("Access-Control-Allow-Origin", currentOrigin);
               }
0 条评论

发布
问题