开源代码片段扫描工具,开源代码免费下载

  

  目前安卓应用程序代码漏洞扫描工具种类繁多,效果良莠不齐。这些工具有一个共同的特性,就是在应用程序打包后对其进行解包和扫描。这种扫描有明显的缺点,扫描周期长,无法将代码中存在的安全问题实时反馈给开发人员,对于问题代码的位置需要人工寻找匹配的源代码,更不利于开发人员及时修改问题代码。代码仲裁器就是为了解决上述两个问题而开发的,它专门用于Android Studio中源代码的安全扫描。   

  

  

1 背景介绍

  

  

  在Android Studio中扫描源代码,最便捷的方法就是将扫描工具作为IDE插件使用。此时,一个很自然的想法是从头开始构建一个Android Studio插件,但仔细评估后会发现,这样做并不难:   

  

  工作量大,需要学习很多知识,比如IDE开放API接口,插件UI构造等。同时,很多底层模块需要从头构建;   

  

  插件的稳定性和检测问题的准确性未必能达到现有开源工具的效果。   

  

  因此,我们考虑扩展现有的漏洞检测插件来满足需求。经过调查,最终入围的是PMD和FindBugs,其中PMD扫描Java源代码,FindBugs扫描Java源代码编译的类文件。考虑到检测的扩展性和准确性,最终选择了FindBugs。FindBugs是一个静态分析工具,它检查类或JAR文件,将字节码与一组缺陷模式进行比较,以发现可能的问题。它可以作为独立的JAR包运行,也可以作为集成开发工具的插件运行。   

  

  扩展优化   

  

  那么,如何扩展FindBugs呢?发现FindBugs插件具有极强的可扩展性,只需将扩展后的JAR包导入FindBugs插件并重启即可完成相关功能的扩展。JAR包的安装图如下所示。   

  

     

  

  下面的问题是如何构建一个可安装的JAR包。进一步调查发现,FindBugs有一个扩展插件Find Security Bugs,专门检测安全问题。该插件主要用于检测Web安全问题,针对Android相关安全问题的检测规则很少。考虑到以下原因,这个插件的源代码需要重新构建。   

  

  对Android安全问题的检测太少,只包括少数外部文件、Webview、广播等。   

  

  对检测的细粒度考虑不全面,会造成大量的误报,不能满足检测精度的要求;   

  

  测试问题的上报只支持英文模式,问题呈现逻辑不够严谨,不方便开发者排查问题。   

  

  基于以上三个原因,我们需要对Find Security Bugs的源代码进行重写和优化,通过增加检测项来检测尽可能多的安全问题,通过优化检测规则来减少误报。问题呈现采用中文描述,优化了问题描述的逻辑,让开发者更容易理解和修改相关问题。从而确定了插件实现和优化的方案。   

  

  

2 工具实现介绍

  

  

   FindBugs检测的是一个类文件,所以当要检测的源代码没有生成编译文件时,FindBugs会先编译源代码生成一个. class文件,然后再分析这个类文件。FindBugs会自动对类文件进行建模,并基于这个模型分析代码。根据实际编写检测代码过程中的总结,检测的实现方法分为四种,下面分别介绍。   

  

  2.1逐行检查   

  

  逐行检查主要是检测代码中使用的一些不安全的方法或参数,通过重写sawOpcode()方法来实现。这里以Android使用外接存储的问题为例进行说明。   

  

  Android中获取外部存储文件夹地址的方法主要有以下几种:   

  

  GetExternalCacheDir()的检测方式是,如果发现这个方法的调用,就会报告为问题,完整代码如下:   

  

  公共类外部文件访问检测器扩展了OpcodeStackDetector { private static final String ANDROID _ EXTERNAL _ FILE _ ACCESS _ TYPE=' ANDROID _ EXTERNAL _ FILE _ ACCESS ';私人bug reporter BugReporter public ExternalFileAccessDetector(bug reporter bug reporter){ this。bug举报人=bug举报人;该类的实现是继承OpcodeStackDetector类,是查找疯狂的中的一个抽象类,封装了对于获取代码特定参数的方法调用100 .saw操作码方法参数可以理解为待检测代码行的行号,通过printOpCode(已见)可以打印该代码行的具体内容常数INVOKEVIRTUAL表示该行调用类的实例方法,常量INVOKESTATIC表示调用类的静态方法getNameConstantOperand方法表示获取被调用方法的名称,getClassConst   

antOperand方法表示获取调用类的名称,getSigConstantOperand方法表示获取方法的所有参数。bugReporter.reportBug用于上报检测到的漏洞信息,其中BugInstance的三个参数分别表示:检测器、漏洞类型、漏洞等级,其中漏洞等级分为五个级别,如下表所示:

  

名称参数含义HIGH_PRIORITY1高危风险NORMAL_PRIORITY2中危风险LOW_PRIORITY3低危风险EXP_PRIORITY4安全提醒IGNORE_PRIORITY5可忽略风险addClass、addMethod、addSourceLine用于指定该漏洞所在的类、方法、行,方便报告漏洞时定位关键代码。

  

2.2 逐方法检查

  

逐方法检查首先获取待检测类的所有内容,然后对类中的方法进行逐个检查,多用于对方法体进行检测,其实现的方法主要是通过重写visitClassContext方法,下面以对Android TrustManager的空实现的检测为例进行说明。

  

TrustManager的空实现,主要是指对于检测Server端证书是否可信的方法checkServerTrusted,是否是空实现。下面展示问题代码,如果是空实现那么将导致客户端接收任意证书,从而造成加密后的HTTPS消息被中间人解密。

  

@Overridepublic void checkServerTrusted(X509Certificate<> x509Certificates, String s) throws CertificateException {检测的方式是通过遍历类中的所有方法,找到checkServerTrusted方法,对方法整体进行检测,确定其是否为空实现,部分代码如下所示:

  

public class WeakTrustManagerDetector implements Detector {classContext.getJavaClass用于获取整个类的所有内容;javaClass.getMethods用于获取该类中的所有方法,以一个方法列表的形式返回;classContext.getMethodGen用于获取该方法的内容;isEmptyImplementation将方法的内容导入该函数进行检测,用于确定方法是否是空实现,该方法的代码如下所示:

  

private boolean isEmptyImplementation(MethodGen methodGen){ boolean invokeInst = false; boolean loadField = false; for (Iterator itIns = methodGen.getInstructionList().iterator();itIns.hasNext();) {该方法主要用于检测方法中是否包含方法调用、域操作,如果没有包含则认为是一个空实现的方法。因此该方法对于只包含 return true/false 语句的方法体同样认为是一个空实现。

  

2.3 污点分析

  

数据流分析主要用于分析特定方法加载的参数是否能够被用户控制,即进行污点分析。做污点分析首先需要定义污染源(source点),污染源可以理解为能够被用户控制的输入数据,这里定义的Android污染源主要包括用户的输入、Intent传入的数据,下面展示定义的部分污染源(source点):

  

- EditText定义好污染源后就需要确定污染的触发点(sink点),可以理解为会触发危险操作的函数。定义sink点的方式有两种,一种是直接从文件中导入,以命令注入为示例,代码如下:

  

public class CommandInjectionDetector extends BasicInjectionDetector { public CommandInjectionDetector(BugReporter bugReporter) { super(bugReporter);从代码中可以清楚的看到其导入方式是继承BasicInjectionDetector类,然后再该类的构造方法中通过loadConfiguredSinks方法,导入包含sink点的文件,下面展示该示例文件中的内容:

  

java/lang/Runtime.exec(Ljava/lang/String;)Ljava/lang/Process;:0另一种是自定义导入,其实现是通过覆盖BasicInjectionDetector类中的getInjectionPoint方法,以WebView.loadurl方法为例,示例代码如下所示:

  

@Override通过实例化InjectionPoint类构造新的sink点,其构造方法中的第一个参数表示该方法接收污染数据参数的位置,如方法为webView.loadUrl(url),其第一个参数就是new int<>{0},其它的以此类推。

  

上报发现漏洞的情况,则通过覆盖getPriorityFromTaintFrame方法的实现,示例代码如下所示:

  

@Override通过fact.getStackValue获取检测的函数变量,如果该变量被污染(isTainted)或 变量是否被污染未知(但是是可控制变量),那么作为一个中危风险(Priorities.NORMAL_PRIORITY)进行上报,其它的情况则上报为可忽略风险(Priorities.IGNORE_PRIORITY)。

  

2.4 自定义代码检测

  

自定义代码检测实现的前半部分同2.2的逐方法检测类似,均是获取类的内容,然后遍历所有的方法,对方法的内容进行检测,但是在具体代码检测实现上是通过自定义分析进行。目前自定义检测只应用到Android中本地拒绝服务的检测。本地拒绝服务的被触发的重要原因在于对通过Intent获取的参数未进行异常捕获,因此检测实现的方式便是检测获取参数的代码行是否被try catch包裹(这个存在误差,待改进)。对于其代码分析,不能使用FindBugs模型进行分析,而是使用最原始的class代码进行分析,原始class代码的形式通过javap命令进行查看,下图展示示例代码。

  

  

对原始class文件进行分析存在的缺陷是无法定位具体的代码行,那么在进行问题上报时无法将问题定位到代码行,因此第一步需要在原有模型的基础上对所有包含Intent获取参数的方法的位置存储到一个Map结构中,方便后面对方法的定位,代码实现如下所示,获取方法所在的行,然后以方法名作为Key值,以代码行相关信息作为Value值,存储到Map中。

  

private Map<String, List<Location>> get_line_location(Method m, ClassContext classContext){之后获取Exception包裹的范围,FindBugs中包含对Exception的建模,因此能够通过其模型能够直接获取其范围并存储到一个列表中,代码如下所示,其中exceptionTable.getStartPC用于获取try catch 的起始代码行,exceptionTable.getEndPC用于获取try catch 的结束代码行。

  

public int<> getExceptionScope(){ try {在对代码进行逐行检查时,因为使用的是最原始class文件形式,因此需要限定其遍历的范围,限定的方式是通过代码的行号,即上图中每行代码的第一个数值。首先需要获取代码总行数的大小,获取的方式便是解析FindBugs建模后的第一行代码,找到关键词code-length后面的数值,即为代码的行数,解析代码如下所示:

  

public int get_Code_Length(String firstLineCode){ try{最后对代码进行逐行遍历,遍历中为防止try catch块被遍历到,使用行号来限制遍历的范围。检测代码行是否包含通过Intent获取参数,及该行是否被try catch 包裹,如果上述两个条件均被触发,那么就作为一个问题进行上报。示例代码如下,其中get_code_line_index方法用于获取代码的行号,获取的方式是截取代码行的首字符的数值,以确定是否在代码包裹的范围内。

  

private void analyzeMethod(JavaClass javaClass, Method m, ClassContext classContext) throws CFGBuilderException {

3 注册打包

上面详细叙述了如何构造自己的问题检测代码,完成检测方法的书写后,下一步就是在配置文件中对检测方法进行注册,才能使检测代码运转起来。

  

需要在两个文件中进行注册,第一个是findbugs.xml,注册示例如下:

  

<Detector class="com.h3xstream.findsecbugs.android.LocalDenialOfServiceDetector" reports="LOCAL_DENIAL_SERVICE"/>其中Detector用于注册该检测方法的位置及其唯一标识,BugPattern用于对检测出的问题进行归类,方便展示,如此处归类到"Android安全问题"中,那么在生成报告的时候问题也将被归类到"Android安全问题"中。

  

第二个是messages.xml注册,注册示例如下,该注册主要是对该问题进行说明,包括问题的危害及修复方法。

  

<Detector class="com.h3xstream.findsecbugs.android.LocalDenialOfServiceDetector"><Details>Local Denial of Service.</Details></Detector><BugPattern type="LOCAL_DENIAL_SERVICE"><ShortDescription>本地拒绝服务</ShortDescription><LongDescription>通过Intent接收的参数未进行异常捕获,导致出现异常使得应用崩溃</LongDescription><Details><!>></Details></BugPattern><BugCode abbrev="SECLDOS">本地拒绝服务</BugCode>一切完成就绪后使用Maven进行打包,就生产了供FindBugs集成开发工具插件使用的JAR包,完成安装并重启,即可使用自定义插件对特定问题进行检测。

  

最终分析的效果图如下图所示:

  

  

4 结语

本文介绍了Android集成开发环境Android Studio的代码实时检测工具Code Arbiter的产生原因及代码实现,最后展示了分析的效果。通过Code Arbiter在生产环境中的应用,其检测效果还是相当不错,能够发现很多编码过程中存在的问题。但是Code Arbiter仍然存在许多不足,需要优化。后续将在以下两个方面对工具进行改进:

  

扩大漏洞检测范围,使Code Arbiter能够囊括Android编码常见安全问题;

  

优化漏洞检测规则,提高检测的准确性,减少误报。

  

5 作者简介

建弋,2016年加入美团点评,目前主要负责金融部门相关的安全工作。对于代码审计/漏洞扫描感兴趣的同学,可以阅读本人Freebuf上发表的相关文章,期待与大家共同学习共同提高。

  

美团点评金融服务平台-安全与合规中心致力于维护美团点评金融平台的安全工作,努力构建行业顶级安全架构,打造行业领先安全产品。欢迎各位行业同仁加入我们,共同建设美团点评金融安全。

  

【思考题】

  

文中描述了Android IDE漏洞扫描工具产生的前因后果,对实现的代码也进行了概括总结。不管是在IDE集成开发环境下的源码扫描,还是对APK文件的解包扫描,其最终的目的还是为了保证应用的安全性,那么大家在日常的工作中对于移动安全建设,以及互联网快速迭代环境下业务安全问题的排查有何独到的见地呢?希望不吝赐教。

相关文章