android app代码审计,常规漏洞/缺陷整理(持续更新)

前言

鉴于目前国内app安全方面抓的越来越紧,不仅apk会拿去做安全检测,apk的源码也是拿去做相关的安全检测。这无疑中给我们开发增加了不少工作量,以下就列举了一些博主开发时被检测出来的代码漏洞。

高等级缺陷

代码注入–WebView远程代码执行

详细信息:

Android API level 16以及之前的版本存在远程代码执行安全漏洞,该漏洞源于程序使用
WebView.addJavascriptInterface可以实现网页JS与本地JAVA的交互,但是没有限制已注册
JAVA类的方法调用,类中的任何public函数都可以在JS代码中访问,这就导致可以调用
getClass通过java反射机制来执行任意Java对象的方法。
例如:利用addJavascriptInterface方法注册可供JavaScript调用的Java对象
injectedObj,利用反射机制调用Android API getRuntime执行shell命令:
java代码:

...
WebView webView1 = (WebView)findViewById(R.id.webView1); 
JavaScriptInterface myJavaScriptInterface = new JavaScriptInterface(); 
webView1.addJavascriptInterface(myJavaScriptInterface,"injectedObj"); 
webView1.loadUrl("http://www.example.com");
...

js代码:

<html> 
 <body> 
 <script> 
 function execute(cmdArgs) { 
 return
injectedObj.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,n
ull).exec(cmdArgs); 
 } 
 var res = execute(["/system/bin/sh", "-c", "ls -al /mnt/sdcard/"]); 
 document.write(getContents(res.getInputStream())); 
 </script> 
 </body> 
</html>

修复建议:

1.避免调用addJavascriptInterface()方法(如果只是展示页面数据,而不做数据通信,完全可以不使用),或者将API等级设为17或者更高级别,对于这
些API等级,只有被标注了JavascriptInterface的公共方法才可以从JavaScript访问。
2.webview组件会默认内置一个searchBoxJavaBridge_接口,故需要使用
removeJavascriptInterface移除searchBoxJavaBridge_
3.调用了Android/webkit/AccessibilityInjector组件的应用在开启辅助功能选项中第三方服务
的安卓系统会默认添加accessibilityaccessibilityTraversal接口,同样需要使用
removeJavascriptInterface进行移除。

代码质量–不安全的SSL:主机名验证功能被禁用

详细信息:

采用SSL进行连接时,使用AllowAllHostnameVerifier()
SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER将会关闭主机名验证功能。这相
当于信任所有证书。
例如:下列代码SSLSocketFactory调用setHostnameVerifier方法,将
SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER作为参数,将会关闭主机名验证
功能。

... 
KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType()); 
store.load(null, null); 
SSLSocketFactory sf = new SSLSocketFactory(store); 
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); 
HttpParams params = new BasicHttpParams(); 
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); 
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8); 
SchemeRegistry registry = new SchemeRegistry(); 
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); 
registry.register(new Scheme("https", sf, 443)); 
ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry); 
...

修复建议:
采用SSL连接时,不应放弃服务器验证检查。可考虑采用StrictHostnameVerifer建立服务
器身份和安全SSL连接。

资源管理–通过共享存储安装APK

详细信息:

应用程序如果通过共享存储安装应用程序,如果攻击者使用恶意应用程序将下载的
APK文件替换为恶意APK文件,安装进程会使用该恶意APK文件取代合法APK。
例如:在以下代码片段中,通过共享存储安装应用程序。

Intent myIntent = new Intent(Intent.ACTION_VIEW); 
Uri apkUri = Uri.fromFile(new File(Environment.getExternalStorageDirectory() + "/download/" +
"install.apk")), "application/vnd.android.package-archive"); 
myIntent.setDataAndType(apkUri); 
myIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
myContext.startActivity(intent);

修复建议:
检查代码逻辑,不要共享存储安装应用程序,防止被替换为恶意文件。
以上检测的是android7.0以下的写法,在android7.0以上使用的是FileProvider.getUriForFile来获取Uri(这是安全的),这算是以前老系统的一个漏洞了。而为了适配7.0以下的系统,必须得使用Uri.fromFile,可以在代码检测时先把这句话注释掉。因为代码检测并不智能,所以只能暂时这么做

        if (Build.VERSION.SDK_INT >= 24) {
            File file = new File(pathstr);
            //参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致   参数3  共享的文件
            Uri apkUri = FileProvider.getUriForFile(mContext, "com.dlwq.android.file.provider", file);
            //添加这一句表示对目标应用临时授权该Uri所代表的文件
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
        } else {
//            intent.setDataAndType(Uri.fromFile(new File(mContext.getExternalCacheDir(), name)), "application/vnd.android.package-archive");
        }

跨站脚本–反射型XSS

详细信息:

反射型XSS是指应用程序通过Web请求获取不可信赖的数据,并在未检验数据是否存在
恶意代码的情况下,将其发送给用户。反射型XSS一般可以由攻击者构造带有恶意代码参
数的URL来实现,在构造的URL地址被打开后,其中包含的恶意代码参数被浏览器解析
和执行。这种攻击的特点是非持久化,必须用户点击包含恶意代码参数的链接时才会触
发。
例如:下面JSP代码片段的功能是从HTTP请求中读取输入的用户名(username)并显示
到页面。

<%
String name= request.getParameter("username"); %> 
...
姓名: <%= name%> 
...

如果name里有包含恶意代码,那么Web浏览器就会像显示HTTP响应那样执行该代码,应
用程序将受到反射型XSS攻击。

修复建议:
为了避免反射型XSS攻击,建议采用以下方式进行防御:
1.对用户的输入进行合理验证(如年龄只能是数字),对特殊字符(如<、>、'、"以及
<script>、javascript等进行过滤。
2.根据数据将要置于HTML上下文中的不同位置(HTML标签、HTML属性、JavaScript脚
本、CSS、URL),对所有不可信数据进行恰当的输出编码。
例如:采用OWASP ESAPI对数据输出HTML上下文中不同位置,编码方法如下。

//HTML encode 
ESAPI.encoder().encodeForHTML(inputData); 
//HTML attribute encode 
ESAPI.encoder().encodeForHTMLAttribute(inputData); 
//JavaScript encode 
ESAPI.encoder().encodeForJavaScript(inputData); 
//CSS encode 
ESAPI.encoder().encodeForCSS(inputData); 
//URL encode 
ESAPI.encoder().encodeForURL(inputData); 

3.设置HttpOnly属性,避免攻击者利用跨站脚本漏洞进行Cookie劫持攻击。在Java EE中
,给Cookie添加HttpOnly的代码如下:

... 
response.setHeader("Set-Cookie","cookiename=cookievalue; path=/; Domain=domainvaule;
Max-age=seconds; HttpOnly"); 
...

中等级缺陷

代码注入–资源注入

详细信息:
使用用户输入控制资源标识符,借此攻击者可以访问或修改其他受保护的系统资源。
当满足以下两个条件时,就会发生资源注入:

  1. 攻击者可以指定已使用的标识符来访问系统资源。例如:攻击者能够指定用来连接到
    网络资源的端口号。
  2. 攻击者可以通过指定特定资源来获取某种权限,而这种权限在一般情况下是不可能获
    得的。例如:程序可能会允许攻击者把敏感信息传输到第三方服务器。
    例1:下面的代码片段从HTTP请求获取端口号,并使用此端口号创建一个套接字,而
    不进行任何验证。使用代理的用户可以修改此端口并获得与服务器的直接连接。
String port = request.getParameter("port"); 
... 
ServerSocket serverSocket = new ServerSocket(port); 
Socket socket = serverSocket.accept(); 
...

这种利用用户输入影响的资源可能存在风险。
例2:下面的代码利用WebView的File域协议读取任意可读文件或受害应用的私有文件

WebView webView=(WebView) findViewById(R.id.webView); 
String url= getIntent().getData().toString(); 
webView.loadUrl(url);

查看hosts文件:adb shell am start -n com.mytest/.MainActivity -d file:///system/etc/hosts
查看应用私有文件:adb shell am start -n /data/data/com.cn.test/databases/user.db

修复建议:
为了避免资源注入漏洞攻击,可以采用黑名单或白名单策略。黑名单会有选择地拒绝
或避免潜在的危险字符。但是,任何这样一份黑名单都不可能是完整的,而且将随着时
间的推移而过时。比较好的方法是创建白名单,允许其中的字符出现在资源名称中,且
只接受完全由这些被认可的字符所组成的输入()。

代码质量–Activity劫持

详细信息:

当用户打开安卓手机上的某一应用进入到登录页面时,恶意软件侦测到用户的这一动
作,立即弹出一个与该应用界面相同的Activity,覆盖掉了合法的Activity,若代码中没
有重写ActivityonPause方法并提示用户,用户几乎无法察觉,用户接下来输入用户名
和密码的操作其实是在恶意软件的Activity上进行的,恶意软件将会盗取这些信息

修复建议:

重写ActivityonPause方法,这样一来,当其被覆盖时,就能够弹出警示信息,例如
以下代码:

@Override 
protected void onPause() { 
 Toast.makeText(getApplicationContext(), "您的登陆界面被覆盖,请确认登录环境是否安
全", Toast.LENGTH_SHORT).show(); 
 super.onPause(); 
}

代码质量–Android程序:缺少网络安全配置

详细信息:

应用程序可以通过设置网络安全配置在安全的声明性配置文件中自定义其网络安全设
置,而无需修改应用程序代码。缺少网络安全配置将会存在着被攻击的风险。

修复建议:

设置网络安全配置以减少受到被攻击的风险。
例如:下列配置文件中设置了网络安全配置。

<application 
 ... 
 android:networkSecurityConfig="@xml/network_security_config"> 
 ... 
</application>

代码质量–不安全的SSL:自定义SSL接口

详细信息:
Android应用程序实现了一个自定义的SSL接口,而不是Android库提供的标准SSL实现。
这样的SSL接口其验证逻辑的实现将留给应用程序开发人员来处理。但事实上,由于
SSL标准规范的复杂性和开放性存在多项缺陷,使得开发人员想要实现无缺陷的自定义
SSL是非常困难的。采用自定义SSL实现的Android应用程序很可能无法高效地提供安全通
信通道,应用程序易受man-in-the-middle攻击。
例如:以下代码自定义实现X509TrustManager接口。

public class Test implements X509TrustManager { 
 @Override 
 public void checkClientTrusted(X509Certificate[] chain, String authType) throws
CertificateException { 
 ... 
 } 
 @Override 
 public void checkServerTrusted(X509Certificate[] chain, String authType) throws
CertificateException { 
 ... 
 } 
 @Override 
 public X509Certificate[] getAcceptedIssuers() { 
 return new X509Certificate[0]; 
 } 
}

修复建议:

应尽可能采用Android库提供的标准SSL实现,如果确实需要自定义SSL实现,应该确保
代码的有效性,并确保没有SSL验证检查受到影响。

代码质量–不安全的信息传输

详细信息:

程序使用HTTP、FTP等协议进行通信,未对通信信息进行加密和验证,可能会受到危
害,尤其是移动设备利用WiFi连接不安全的、公共的无线网络。
例如:下面代码片段中表示将使用HTTP进行连接,传递的敏感信息容易被攻击者窃
取。

... 
HttpHost httppost = HttpHost(inetAddress,port,"http"); 
...

修复建议:

程序应尽可能基于HTTPS等安全协议与服务器进行通信,尤其是进行敏感信息的传递

代码质量–不必要的权限申请

详细信息:

Android的权限管理能保护设备上可用的敏感信息,只能在app的功能必须要访问敏感信
息时才能使用对应的权限。Android应用程序应该遵守最少权限原则,申请过多的不需要
的权限不仅会增加其他漏洞的风险,也会使用户不愿意安装该应用程序。

修复建议:

确认程序是否需要该权限,如果不需要,应该从AndroidManifest.xml文件中移除该权
限。

代码质量–危险的Intent权限

详细信息:

程序中使用了一些危险的Intent权限,这些Intent的权限能够授予外部程序其通常没有的
权限,例如FLAG_GRANT_READ_URI_PERMISSION和
FLAG_GRANT_WRITE_URI_PERMISSION。如果恶意程序能够拦截该intent,则将获得读
取或者写入指定URI的权限,如果该intent为隐式intent,往往更容易被截获。
例如:以下代码将权限标志设置为允许读取Intent内的URI。

intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

修复建议:

一般情况下应该避免使用这些权限,除非能够确保读取或者写入的URI是非敏感数据或
者是不重要的数据。
如果程序需要读取或者写入URI,则应该明确的设置该权限,而不应该依赖可能引起权限
管理问题的解决方案。而且在一般语境下,建议使用显式intent而非隐式intent。

代码质量–双重检查锁定

详细信息:

在程序开发中,有时需要推迟一些高开销的对象初始化操作,并且只有在使用这些对
象时才进行初始化,此时开发者可能会采用延迟初始化。但要正确实现线程安全的延迟
初始化需要一些技巧,否则容易出现问题,如下面例子所示。
例1:下面代码示例中:延迟初始化对象不是线程安全的。

public class UnsafeLazyInitialization { 
 private static Instance instance; 
 public static Instance getInstance() { 
 if (instance == null) { //1:Thread A executed 
 instance = new Instance(); //2:Thread B executed 
 } 
 return instance; 
 } 
}

在例1中,假设A线程执行代码1的同时,B线程执行代码2。此时,线程A可能会看到
instance引用的对象还没有完成初始化。
对应的解决方式,可以对getInstance()做同步处理来实现线程安全的延迟初始化。示例代
码如下:
例2:对getInstance()同步处理来实现线程安全的延迟初始化。

public class SafeLazyInitialization { 
 private static Instance instance; 
 public synchronized static Instance getInstance() { 
 if (instance == null) { 
 instance = new Instance(); 
 } 
 return instance; 
 } 
} 

在例2代码示例中:如果getInstance()被多个线程频繁的调用,将会导致程序执行性能的
下降。对此,开发人员想通过双重检查锁定来降低同步的开销。示例代码如下:

例3:下面代码示例中:使用双重校验锁来实现延迟初始化。

public class DoubleCheckedLocking { //1 
 private static Instance instance; //2 
 public static Instance getInstance() { //3 
 if (instance == null) { //4:first check 
 synchronized (DoubleCheckedLocking.class) { //5:lock 
 if (instance == null) //6:second check 
 instance = new Instance(); //7:problem 
 } //8 
 } //9 
 return instance; //10 
 } //11 
}

在例3的代码示例中:开发者试图通过检测第一次instance不为null,就不需要执行下面的
加锁和初始化操作。希望可以大幅降低例2同步方法带来的性能开销。然而,这是一个错
误的优化。在线程执行到第4行代码读取到instance不为null时,instance引用的对象有可能
还没有完成初始化。原因如下:
第7行instance = new Singleton();创建一个对象。这一行代码可以分解为如下的三行伪代码 :
memory = allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址
上面三行伪代码中的2和3之间,可能会被重排序。2和3之间重排序之后的执行时序如下

memory = allocate(); //1:分配对象的内存空间
instance = memory; //3:设置instance指向刚分配的内存地址.注意,此时对象还没有被初 始化
ctorInstance(memory); //2:初始化对象
根据Java语言规范《The Java Language Specification, Java SE 7 Edition》,所有线程在执行
java程序时必须要遵守intra-thread semantics。intra-thread semantics保证重排序不会改变单
线程内的程序执行结果。但是当多线程执行时,线程可能会看到一个还没有被初始化的
对象。

修复建议:

如果需要对实例字段使用线程安全的延迟初始化,请使用基于volatile的延迟初始化的方
案。如果需要对静态字段使用线程安全的延迟初始化,基于类初始化的方案。两种方式
示例如下。
例1:基于volatile的双重检查锁定的解决方案。

public class SafeDoubleCheckedLocking { 
 private volatile static Instance instance; 
 public static Instance getInstance() { 
 if (instance == null) { 
 synchronized (SafeDoubleCheckedLocking.class) { 
 if (instance == null) 
 instance = new Instance();//volatile instance 
 } 
 } 
 return instance; 
 } 
}

这个解决方案需要JDK5或更高版本,因为从JDK5开始使用新的JSR-133内存模型规范,这
个规范增强了volatile的语义。
例2:基于类初始化的解决方案。

public class InstanceFactory { 
 private static class InstanceHolder { 
 public static Instance instance = new Instance(); 
 } 
 public static Instance getInstance() { 
 return InstanceHolder.instance ; //InstanceHolder class is initialized 
 } 
}

JVM在类的初始化阶段(即在Class被加载后,且被线程使用之前),会执行类的初始化
。在执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类
的初始化

代码质量–硬编码IP

详细信息:

程序中采用硬编码方式处理IP地址,一方面会降低系统安全性,另一方面不易于程序维
护。
例如:下列代码中采用硬编码方式处理IP地址。

public class ConnectionConfig{ 
 String url = "42.81.56.36"; 
 String name = "admin"; 
 String password = "123456"; 
 ... 
}

修复建议

程序中不应对IP地址进行硬编码,可以使用配置文件或数据库存储的方式来存储系统所
需的数据;并且录入数据时,还可以做加密处理之后再进行数据的录入,从而防止敏感
数据泄露。
例如:下列代码中从配置文件中获取经过加密的IP地址并解密使用。

public class ConnectionConfig{ 
 String url = EncryptUtil.decrypt(PropertiesUtil.get("connection.url")); 
 String name = EncryptUtil.decrypt(PropertiesUtil.get("connection.username")); 
 String password = EncryptUtil.decrypt(PropertiesUtil.get("connection.password")); 
 ... 
}

代码质量–系统信息泄露:外部

详细信息:

系统数据或调试信息通过网络流向远程机器时,发生外部信息泄露。
例如:以下代码泄露了HTTP响应中的异常信息:

protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { 
 PrintWriter out = res.getWriter(); 
 try { 
 //... 
 } catch (Exception e) { 
 out.println(e.getMessage()); 
 } 
}

修复建议:

程序不应通过系统输出流或程序日志将系统数据或调试信息输出程序。打包成release版本时需要把日志输出关闭。

代码质量–非静态内部类导致内存泄漏

详细信息:

非静态的内部类会持有外部类的一个隐式引用,只要非静态的内部类对象没有被回收
,外部类就不会被回收,
例如:以下代码所示。

public class MainActivity extends AppCompatActivity { 
 private AsyncTask<Void,Void,Void> myTask=new AsyncTask<Void, Void, Void>() { 
 @Override 
 protected Void doInBackground(Void... params) { 
 ... 
 } 
 }; 
} 

只要myTask在运行,MainActivity所关联的资源和视图都不会被回收,这将发生比较严重
的内存泄漏。

修复建议:

把非静态的内部类定义为静态的,并把外部类传入的相关参数值使用WeakReference保
存。
例如:正确的方式。

static class MyTask extends AsyncTask<Void, Void, Void> { 
 private final WeakReference<MainActivity> weakActivity; 
 MyTask(MainActivity mainActivity) { 
 this.weakActivity = new WeakReference<>(mainActivity); 
 } 
 @Override 
 public Void doInBackground(Void... params) { 
 ... 
 } 
}

密码管理–不安全的随机数

详细信息:
Java API中提供了java.util.Random类实现PRNG(),该PRNG是可移植和可重复的,如
果两个java.util.Random类的实例使用相同的种子,会在所有Java实现中生成相同的数值序
列。
例如:下面代码片段中,使用了java.util.Random类,该类对每一个指定的种子值生
成同一个序列。

import java.util.Random; 
// ... 
public static void main (String args[]) { 
 // ... 
 for (int i = 0; i < 10; i++) { 
 Random random = new Random(123456); 
 int number = random.nextInt(21); 
 ... 
 } 
}

修复建议:

在安全性要求较高的应用中,应使用更安全的随机数生成器,如
java.security.SecureRandom类。
例如:下面代码片段中,使用java.security.SecureRandom来生成更安全的随机数。

import java.security.SecureRandom; 
import java.security.NoSuchAlgorithmException; 
// ... 
public static void main (String args[]) { 
 try { 
 SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); 
 for (int i = 0; i < 10; i++) { 
 int number = random.nextInt(21); 
 ... 
 } 
 } catch (NoSuchAlgorithmException nsae) { 
 ... 
 } 
}

密码管理–弱加密:密钥长度不足

详细信息:

加密算法中使用的密钥长度较短,会降低系统安全。
例如:下面代码片段中,KeyPairGenerator使用RSA加密算法,长度为1024位。

KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); 
keyPairGen.initialize(1024); 
KeyPair keyPair = keyPairGen.generateKeyPair(); 
PublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); 
PrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); 
byte[] publicKeyData = publicKey.getEncoded(); 
byte[] privateKeyData = privateKey.getEncoded(); 
...

修复建议:

对于对称加密算法,建议使用长度大于或等于128位的密钥。对于非对称加密算法(如
RSA),建议使用长度大于或等于2048位的密钥。

密码管理–硬编码加密密钥

详细信息:

当程序中使用硬编码加密密钥时,所有项目开发人员都可以查看该密钥,甚至如果攻
击者可以获取到程序class文件,可以通过反编译得到密钥,硬编码加密密钥会大大降低系
统安全性。
例如:下列代码使用硬编码加密密钥执行AES加密。

private static String encryptionKey = "dfashsdsdfsdgagascv"; 
... 
byte[] keyBytes = encryptionKey.getBytes(); 
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); 
Cipher encryptCipher = Cipher.getInstance("AES"); 
encryptCipher.init(Cipher.ENCRYPT_MODE, key); 
...

修复建议:

程序应采用不小于8个字节的随机生成的字符串作为密钥。
例如:以下代码使用KeyGenerator来生成密钥。

... 
KeyGenerator keyGen = KeyGenerator.getInstance("AES"); 
keyGen.init(128, new SecureRandom(password.getBytes())); 
SecretKey secretKey = kgen.generateKey(); 
byte[] keyBytes = secretKey.getEncoded(); 
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); 
Cipher encryptCipher = Cipher.getInstance("AES"); 
encryptCipher.init(Cipher.ENCRYPT_MODE, key); 
...

资源管理–格式化缺陷

详细信息:

格式化对象是非线程安全的,java.text.Format中的parse()format()方法包含一个可导
致用户看到其他用户数据的race condition。
例如:下面代码片段中,定义了一个成员变量(静态的日期格式对象)。

public class DateFormat extends Thread{ 
 private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 
 private String name; 
 private String dateStr; 
 public DateFormat(String name, String dateStr) { 
 this.name = name; 
 this.dateStr = dateStr; 
 } 
 @Override 
 public void run() { 
 try { 
 Date date = sdf.parse(dateStr); 
 System.out.println("线程"+name+"运行日期:"+date); 
 } catch (ParseException e) { 
 e.printStackTrace(); 
 } 
 } 
 public static void main(String[] args) { 
 ExecutorService executorService = Executors.newFixedThreadPool(3); 
 executorService.execute(new DateFormat("A", "2017-06-10")); 
 executorService.execute(new DateFormat("B", "2016-06-06")); 
 executorService.shutdown(); 
 } 
}

如上代码中输出会有两种情况,一种情况是报错,还有一种情况是两个线程输出一致。
出现这种情况的原因是因为SimpleDateFormat类内部有一个Calendar对象引用,它用来储存
和这个SimpleDateFormat相关的日期信息。这样就会导致一个问题,如果
SimpleDateFormat是static的, 那么多个thread之间就会共享SimpleDateFormat, 同时也是共享
Calendar引用。在高并发的情况下,容易出现幻读成员变量的现象。

修复建议:

有如下四种解决方法

  1. 将格式化对象定义成局部变量,但是每调用一次方法意味创建一个格式化对象,浪费
    内存。
  2. 方法加同步锁synchronized,在同一时刻,只有一个线程可以执行类中的某个方法。这
    样性能较差,每次都要等待锁释放后其他线程才能进入。
  3. 使用第三方库joda-time,由第三方考虑线程不安全的问题。
  4. 使用ThreadLocal:每个线程拥有自己的格式化对象。

资源管理–资源未释放:流

详细信息:

程序创建或分配流资源后,不进行合理释放,将会降低系统性能。攻击者可能会通过
耗尽资源池的方式发起拒绝服务攻击。
例如:在下面Java方法中,创建I/O流对象后未进行合理释放,程序依靠Java虚拟机的
垃圾回收机制释放I/O流资源,事实上,程序不能确定何时调用虚拟机的finalize()方法。
在繁忙的程序环境下,可能导致Java虚拟机不能有效的使用I/O对象。

... 
public void processFile(String filePath){ 
 try { 
 FileInputStream fis = new FileInputStream(filePath); 
 InputStreamReader isr = new InputStreamReader(fis); 
 BufferedReader br = new BufferedReader(isr); 
 String line=""; 
 while((line=br.readLine())!=null){ 
 processLine(line); 
 } 
 } catch (FileNotFoundException e) { 
 log(e); 
 } catch (IOException e){ 
 log(e); 
 } 
} 
...

修复建议:

程序不应依赖于Java虚拟机的finalize()方法来自动回收流资源,而需要手动在finally代码
块中进行流资源的释放。
例如:下面代码片段中,在finally代码块中对流资源进行了合理的释放。

public void processFile(String filePath){ 
 FileInputStream fis = null; 
 InputStreamReader isr = null; 
 BufferedReader br = null; 
 try { 
 fis = new FileInputStream(filePath); 
 isr = new InputStreamReader(fis); 
 br = new BufferedReader(isr); 
 String line=""; 
 while((line=br.readLine())!=null){ 
 //processLine(line); 
 } 
 } catch (FileNotFoundException e) { 
 //log(e); 
 } catch (IOException e){ 
 //log(e); 
 }finally{ 
 if(br!=null){ 
 try { 
 br.close(); 
 } catch (IOException e) { 
 //log(e); 
 } 
 } 
 if(isr!=null){ 
 try { 
 isr.close(); 
 } catch (IOException e) { 
 //log(e); 
 } 
 } 
 if(fis!=null){ 
 try { 
 fis.close(); 
 } catch (IOException e) { 
 //log(e); 
 } 
 } 
 } 
}

输入验证–拒绝服务

详细信息:

拒绝服务是攻击者通过极度消耗应用资源,以致程序崩溃或其他合法用户无法进行使
用的一种攻击方式。
例如:下面代码片段中,攻击者控制流了输入流的长度length,当文件足够大和
length足够长时,会导致DOS攻击。

FileInputStream fileInputStream = new FileInputStream(file); 
fileInputStream.read(bytes, 0, length); 
...

修复建议:

拒绝服务攻击是一种滥用资源性的攻击。从程序源代码角度讲,对涉及到系统资源的
外部数据应该进行严格校验,防止无限制的输入。
例如:下面代码片段中,对文件大小进行检验,并不设置读取长度。

static final int MAX= 0x3200000; // 50MB 
// ... 
// write the files to the disk, but only if file is not insanely big 
if (file.length > MAX) { 
 throw new IllegalStateException("File is huge."); 
} 
FileOutputStream fos = new FileOutputStream(file_name); 
bop = new BufferedOutputStream(fos, SIZE); 
while ((count = fileInputStream.read(bytes)) != -1) { 
 bop.write(bytes, 0, count); 
}

低等级缺陷

API误用–仅重写了equals()和hashCode()中的一个

详细信息:

在仅重写类的equals()方法和hashCode()方法中的一个时,由于顶层基类Object的
public boolean equals(Object obj)方法逻辑是,在比较任何非空引用x和y时,当且仅当x和
y引用同一对象时返回true。而在重写了equals()方法后,通常也必须重写hashCode()
法,这样时为了维护hashCode()方法的常规协定,协定声明相等对象必须具有相同的哈
希码,即:

  1. obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true
  2. obj1.hashCode() == obj2.hashCode()为false时,obj1.equals(obj2)必须为false
    如果不遵循这个准则,当这个重写类的对象保存在一个集合中时,可能就会引发一些问
    题。比如保存到HashMap或HashSet集合中,相同对象的哈希码必须相等,这一点十分重
    要。

修复建议:
类中应同时重写equals()方法和hashCode()方法

API误用–使用不必要的线程安全类

详细信息:
Java中的Vector、Stack、Hashtable和StringBuffer中方法都实现了同步,以确保它们是线
程安全的,同时性能也会相对下降,当不需要线程安全时,可以使用替代集合。
例如:在下面的示例中:局部变量不会导致线程问题,却使用StringBuffer进行字符
串拼接。

public int modifyUsers(List<User> users) { 
 StringBuffer stringBuilder = new StringBuffer(); 
 for (int i < 0;i < users.size;i++) { 
 stringBuffer.append(users.getId()); 
 stringBuffer.append(" "); 
 } 
 String string = stringBuffer.toString(); 
 ... 
}

修复建议:

在线程安全的情况下分别使用ArrayList或LinkedList,Deque,HashMap,StringBuilder
替换Vector,Stack,Hashtable,StringBuffer
例如:在下面的示例中:使用StringBuilder进行字符串拼接。

public int modifyUsers(List<User> users) { 
 StringBuilder stringBuilder = new StringBuilder(); 
 for (int i < 0;i < users.size;i++) { 
 stringBuilder.append(users.getId()); 
 stringBuilder.append(" "); 
 } 
 String string = stringBuilder.toString(); 
 ... 
}

API误用–忽略返回值

详细信息:
一般在开发过程中,程序员凭借经验可以断言某些用户事件或条件,例如某个函数永
远不会执行失败。但是,攻击者往往会故意触发一些异常情况,导致冲破了程序员的断
言,给系统带来了不稳定性,利用不正确的行为触发出发程序的漏洞。例如:程序调
用删除权限函数,但不检查返回值判断是否成功删除权限,则程序将继续以更高权限运
行。
例如:在下面的代码片段中,程序没有对read()返回值做判断。

... 
int bytesToRead = 1024; 
byte[] byteArray = new byte[bytesToRead]; 
streamFileInput = new FileInputStream("C:\\file.txt"); 
streamFileInput.read(byteArray); 
...

修复建议:

程序应检查方法的返回值,确保方法返回的是期望的数据,再进行下一步操作

代码质量–APP允许备份

详细信息:

Android API Level 8及其以上Android系统提供了为应用程序数据的备份和恢复功能,此
功能的开关决定于该应用程序中AndroidManifest.xml文件中的allowBackup属性值,其属
性值默认是True。当allowBackup标志为true时,用户即可通过adb backup和adb restore来进
行对应用数据的备份和恢复,这可能会带来一定的安全风险。
Android属性allowBackup安全风险源于adb backup容许任何一个能够打开USB 调试开关的
人从Android手机中复制应用数据到外设,一旦应用数据被备份之后,所有应用数据都可
被用户读取;adb restore容许用户指定一个恢复的数据来源(即备份的应用数据)来恢复
应用程序数据的创建。因此,当一个应用数据被备份之后,用户即可在其他Android手机
或模拟器上安装同一个应用,以及通过恢复该备份的应用数据到该设备上,在该设备上
打开该应用即可恢复到被备份的应用程序的状态。
尤其是通讯录应用,一旦应用程序支持备份和恢复功能,攻击者即可通过adb backup和
adb restore进行恢复新安装的同一个应用来查看聊天记录等信息;对于支付金融类应用
,攻击者可通过此来进行恶意支付、盗取存款等;因此为了安全起见,开发者务必将
allowBackup标志值设置为false来关闭应用程序的备份和恢复功能,以免造成信息泄露和
财产损失

修复建议:

出于安全考虑,建议关闭应用备份功能; 在AndroidMenifest.xml文件中,将相应组件的
android:allowBackup属性设置为false,例如以下代码:

<application 
 android:allowBackup="false" 
 android:label="@string/app_name"> 
 <activity android:name="LoginActivity" 
 android:label="@string/app_name"> 
 <intent-filter> 
 <action android:name="android.intent.action.MAIN"/> 
 <category android:name="android.intent.category.LAUNCHER"/> 
 </intent-filter> 
 </activity> 
 <activity android:name=".HomeActivity"/> 
</application>
...

代码质量–Webview允许DOM存储

详细信息:

当Webview获取WebSettings并使用setDomStorageEnabled为true时,意味着Webview允许
DOM存储,可能导致敏感信息泄露。

修复建议:

检测程序逻辑,判断程序中此DOM存储是否需要支持DOM存储或者可能导致敏感信息
泄露。如果存在应将setDomStorageEnabled值修改为false或者将其删除,防止敏感信息泄

代码质量–Webview允许内容访问

详细信息:

在使用WebView的过程中忽略了WebView.setAllowContentAccess,内容访问允许
WebView从安装在系统中的内容提供者载入的内容,比如让WebView可以访问
ContentPrivider存储的内容。
例如:以下代码通过webView1.getSettings().setAllowContentAccess(true)进行允许内容
访问。

public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) { 
 super.onCreate(savedInstanceState, persistentState); 
 WebView webView1 = (WebView) findViewById(R.id.webView1); 
 webView1.getSettings().setAllowContentAccess(true); 
 webView1.loadUrl(...); 
}

修复建议:

使用WebView.getSettings().setAllowContentAccess(false)来禁止内容访问

代码质量–Webview允许文件访问

详细信息:

在使用WebView的过程中忽略了WebView.setAllowFileAccess,文件访问允许意味着
WebView可以从File域下能够执行任意的js代码,能够对私有目录文件进行访问,导致敏
感信息泄露。
例如:以下代码通过webView1.getSettings().setAllowFileAccess(true)进行允许内容访问

public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) { 
 super.onCreate(savedInstanceState, persistentState); 
 WebView webView1 = (WebView) findViewById(R.id.webView1); 
 webView1.getSettings().setAllowFileAccess(true); 
 webView1.loadUrl(...); 
}

修复建议:

使用WebView.getSettings().setAllowFileAccess(false)来禁止文件访问

  • 10
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是大咸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值