簡単に言うと、Java コード監査とは、Java コードを監査することによって Java アプリケーション自体のセキュリティ上の問題を発見することです。 Java 自体はコンパイルされた言語なので、クラス ファイルしかない場合でも Java コードを監査できます。コンパイルされていない Java ソース コード ファイルの場合は、ソース コードを直接読み取ることができますが、コンパイルされたクラス ファイルまたは jar ファイルの場合は、逆コンパイルする必要があります。
Java コード監査自体はそれほど難しいものではなく、監査プロセスと一般的な脆弱性監査手法に習熟していれば、比較的簡単にコード監査作業を完了できます。ただし、Java コード監査の方法は、監査ツールを使用して Java プロジェクト コード全体をスキャンするだけではなく、複雑なビジネス ロジックやプログラム アーキテクチャを含む一部のシステム コード監査では、監査人が特定の Java 基盤を習得し、その能力を備えていることが必要です。一定の監査経験、スキル、さらには Java アーキテクチャのより深い理解と実践がなければ、セキュリティ問題をより深く発見することができません。
この記事は複数の章に分かれており、Java コード監査に必要な事前知識と、Java コード監査のプロセスおよび手法について説明します。
Java コード監査を開始する前に、ご自身で Java 開発環境をインストールしてください (MacOS および Ubuntu オペレーティング システムの使用を推奨します)。
格言にあるように、「仕事をうまくやりたければ、まずツールを磨く必要があります。」いくつかの補助ツールを合理的に使用すると、コード監査の効率と品質を大幅に向上させることができます。
次の補助ツールを強くお勧めします:
1.Jetbrains IDEA(IDE)
2.Sublime text(テキスト エディタ)
3 .JD-GUI(逆コンパイル)
4.Fernflower(逆コンパイル)
5.バイトコードビューア
6.Eclipse(IDE)
7. NetBeans(IDE)
侵入テスト中に監査する必要があるコードは、通常、クラス ファイルまたは jar パッケージです。では、どのように監査すればよいのでしょうか?まず、Java ソース コードとバイトコードとは何かを学びましょう。
簡単に言えば、Java ソース コードはコンパイルされていない .java ファイルであり、その中のコード ロジックとバイトコードを簡単に読み取ることができます。ファイルは、.java ファイルのコンパイル後に生成されるバイトコード ファイルです。.class ファイルはコンパイルされたバイナリ ファイルであるため、直接読み取ることはできません。バイナリ ファイルを Java コードまたは ASM コードに変換するには、逆コンパイル ツールのみを使用できます。
サンプルコード Test.java:
/** * @author yz */ public class Test { public static void hello() { System.out.println("Hello~"); } public void world() { System.out.println("World!"); } public static void main(String[] args) { hello(); } }
Test.java コンパイル実行処理:
Test.java ソース コード、バイトコード
クラス ファイルの可読性が低いため、通常は Java 逆コンパイル ツールを使用して逆コンパイルする必要があります。コード。通常、クラスを逆コンパイルするには、JD-GUI、IDEA Fernflower プラグイン、Bytecode-Viewer、Fernflower、JAD、JBE、JEB などのツールを使用します。
このうち、逆コンパイルに最もよく使われるツールは JD-GUI かもしれませんが、JD-GUI の逆コンパイル能力は IDEA に比べてはるかに劣ると個人的に感じています (IDEA は Fernflower の修正版であるはずです) )、IDEA はデフォルトで jar とクラスの逆コンパイルをサポートしているため、個人的には IDEA を使用してクラス コードを逆コンパイルすることを強くお勧めします。
もちろん、逆コンパイル ツールは多くの場合万能ではありません。JD-GUI は、逆コンパイルできない状況や、逆コンパイルのプロセス中にプログラムが直接クラッシュする状況によく遭遇します。そのような状況では、通常、IDEA を使用して逆コンパイルできます。試してみてください。IDEA が逆コンパイルできない場合は、JBE を使用してクラス ファイル読み取りプログラムのバイトコードを読み込むことができます。JBE がまだクラス情報を読み取れない場合は、JDK に付属の javap コマンドを使用してクラスのバイトコードを読み取ることもできますすべてのメソッドを逆コンパイルできない場合は、クラス自体にコンパイルできない問題が発生しているか、クラス ファイルが暗号化されている可能性があります。おそらく、Java でコンパイルされたクラスは暗号化できないと言うでしょうか?はい、ここで述べた暗号化は実際にはコンパイルされたクラス コードを逆コンパイルから保護するためのものです。カスタム ClassLoader を実装して暗号化されたクラスをロードする方法にすぎません。この暗号化方法は実際の戦闘でも遭遇しました。
通常、jar ファイルを取得できるのはいくつかの特別なシナリオだけですが、jar パッケージ全体のクラス ファイルをどのように逆コンパイルすればよいでしょうか?
2.1. Fernflower
Fernflower は次のコマンドを実行するだけで、jar の完全な逆コンパイルを簡単に実現できます: java -jar fernflower.jar jarToDecompile.jar decomp/ jarToDecompile.jar は逆コンパイルする必要がある jar ファイルで、decomp は逆コンパイルされたクラス ファイルが保存されるディレクトリです。逆コンパイルできない場合、Fernflower は空の Java ファイルを生成する可能性があることに注意してください。
2.2. JD-GUI
JD-GUI は GUI を備えた逆コンパイル ツールです。JD-GUI ソースのメニューで [ファイル] --> [すべて保存] をクリックします。 jar を逆コンパイルできます。
2.3. IDEA
IDEA默认就支持jar包反编译,同时还支持class文件名(⇧⌘F)、类方法名称(⇧⌘O)搜索。
2.4. Bytecode-Viewer
FernFlower提供了GUI版本Bytecode-Viewer,Bytecode-Viewer提供了直接反编译的class、jar、zip、apk、dex功能,直接拖拽jar就可以直接对整个jar进行反编译了。
2.5. Find命令
find命令并不能支持Java反编译,但是find命令可以非常方便的搜索经过编译后的二进制文件中的内容,所以有的时候使用find命令通常是最简单实用的,直接解压jar包然后使用find命令搜索: find ./ -type f -name “*.class” |xargs grep XXXX 即可搞定。
2.6 使用Find命令和Fernflower实现批量反编译jar
当我们只有项目war包且源码经过打包后发布到WEB-INF/lib的情况下,我们不得不去找出待审计源码的具体jar文件并反编译。遇到这种情况我们可以巧妙的使用find命令来反编译所有目标的jar包。
这里以jcms的一个非常老版本为例,jcms最终给客户部署的war包中源码并不是在WEB-INF/classes目录下,而是将整个jcms系统按模块打包成了多个jar包放在了WEB-INF/lib目录下。我们可以通过搜索com.hanweb包名称来找出所有jar中包含了jcms的文件并通过Fernflower来反编译。
java -jar /Users/yz/Desktop/javaweb-decomplier/javaweb-decomplier.jar -dgs=1 $(find /Users/yz/Desktop/jcms/WEB-INF/lib/ -type f -name "*.jar" |xargs grep "com.hanweb" |awk '{print $3}') /Users/yz/jcms-decomplier
依赖的jar: javaweb-decomplier、Intellij java-decompiler。
执行上面的命令后会在jcms-decomplier目录下看到所有的jar已经被Fernflower反编译了。
IntelliJ IDEA是Jetbrains出品的一款非常强大的Java IDE,IDEA提供了强大的代码搜索、近乎完美的反编译、动态调试等功能可以最大程度的辅助我们代码审计。
不可以否认,与IDEA相比虽然Eclipse和Netbeans也有与之类似的功能,但是在真正的实战体验中个人更倾向于使用IDEA,虽然曾经的我也是一个重度Eclipse开发者。
IDEA的搜索快捷键是:⇧⌘F,使用IDEA提供的搜索功能可以非常快速的定位漏洞点信息。
IDEA可以通过自定义搜索范围来精确查找我们需要审计的代码。默认搜索的是所有的位置,不过我们可以点击红色箭头指向的...按钮来细化我们的搜索范围。
自定义搜索范围示例:
自定义搜索范围后就可以在搜索时使用自定义的配置进行范围搜索了,有助于我们在挖漏洞的时候缩小代码定位范围。
搜索快捷键: ⌘O,标记搜索支持类名、方法名搜索(包括class或jar文件中的方法也支持搜索)。
当我们审计代码的时候发现某个方法或类有漏洞时我们需要定位到漏洞的请求地址(触发点),复杂业务系统往往会让我们很难定位到漏洞的触发点。借助IDEA的方法调用链搜索功能就可以很轻松的找出方法的调用链和触发点。
选择类或者方法名-->右键-->Find Useages或者使用快捷键⌥F7
为了更好的管理项目我们通常会采用分层架构的方式来开发Java Web项目,分层设计的好处在于可以非常方便的分清楚包之间的业务逻辑关系。
常见的JavaWeb项目分层:
视图层(View 视图)
控制层(Controller、Action 控制层)
服务层(Service)
业务逻辑层BO(business object)
实体层(entity 实体对象、VO(value object) 值对象 、模型层(bean)。
永続層 (dao- データ アクセス オブジェクト データ アクセス層、PO (永続オブジェクト) 永続オブジェクト)
Java 層アーキテクチャに基づくサンプル プロジェクト:
#今日の比較的大規模な Java Web プロジェクトは、通常、モジュール方式で開発されています。Maven および Gradle 依存関係管理ツールの助けを借りて、Java は非常に簡単に完了できます。モジュール開発。さらに、OSGi (ホット モジュール デプロイメントを可能にする Open Service Gateway Initiative) テクノロジを使用して Java 動的モジュラー システムを開発することも一般的です。
モジュール開発の採用により、より多くの依存ライブラリで監査する必要があるコードを見つける必要があるため、コード監査も困難になります。
Maven を使用して開発された JavaWeb プロジェクトの例:
サーブレットは Java Web にありますコンテナ コンピュータ上で実行される小さなプログラムの場合、通常はサーブレットを使用して、より複雑なサーバー側のビジネス ロジックを処理します。 Servlet3.0 (Tomcat7) 以降では、アノテーションを使用してサーブレットを構成できることに注意してください。
アノテーションベースのサーブレット
Servlet3.0 より前のバージョンは、web.xml で設定する必要があります。サーブレットは、< で構成される 2 つのタグのペアです。 servlet> と
設定に基づくサーブレット
HttpServlet クラス
サーブレットの実装は非常に簡単です。 javax.servlet.http.HttpServletクラスを継承し、doXXXメソッドまたはserviceメソッドをオーバーライドするだけで、HttpServletクラスのserviceメソッドをオーバーライドすることで、上記7つのHttpリクエストメソッドのリクエストを取得できるようになります。
JSP および JSPX ファイルは、Java コンテナによって直接解析できる動的スクリプトです。JSP は他のスクリプト言語と同じです。データ表示は、バックエンド ビジネス ロジックの処理にも使用できます。
本質的に、JSP はサーブレットです。jsp ファイルは最終的にクラス ファイルにコンパイルされ、このクラス ファイルは実際には特別なサーブレットであるためです。
JSP ファイルは Java クラス ファイルにコンパイルされます。たとえば、index.jsp は、Tomcat の Jasper によってコンパイルされた後、index_jsp.java と Index_jsp.class という 2 つのファイルを生成します。 Index_jsp.java は、HttpJspBase クラスから継承します。HttpJspBase は、HttpJspPage インタフェースを実装し、HttpServlet を継承する標準的なサーブレットです。__jspService メソッドは、実際には HttpJspPage インタフェース メソッドであり、サーブレットのサービス メソッドと同様です。ここでの __jspService メソッドは、実際には HttpJspBase ですメソッド、サービスメソッド呼び出し。
Filter は JavaWeb のフィルタで、URL リクエストをフィルタリングするために使用されます。 Filter を通じて、URL リクエストのリソース権限の検証やユーザーのログイン検出などの機能を実装できます。 Filter はインターフェイスです。Filter を実装するには、init、doFilter、destroy メソッドを書き直すだけです。フィルタリング ロジックは doFilter メソッドに実装されます。
フィルターは、サーブレットと同様に Java Web の中核部分です。サーブレットとフィルターを使用することで、バックエンド インターフェイスの開発と権限制御を実現できます。もちろん、フィルター機構を使用して MVC フレームワークを実装することもできます。 Struts2 の実装メカニズムは Filter を使用します。
フィルター構成はサーブレットに似ており、
フィルターとサーブレットに基づく単純なアーキテクチャ プロジェクトの場合、コード監査の焦点は、すべてのフィルターを検索し、そのフィルターを分析することです。フィルタリング ルールを使用して、グローバル セキュリティ フィルタリングがあるかどうか、機密性の高い URL アドレスに対してアクセス許可の検証が実行されているかどうかを確認し、フィルタのバイパスを試みます。 2 番目のポイントは、すべてのサーブレットを見つけて、サーブレット ビジネスにセキュリティ上の問題があるかどうかを分析することです。セキュリティ上の問題がある場合、それが悪用される可能性があるかどうかを確認します。アクセス許可はありますか?悪用する際にフィルターでフィルタリングされているかどうかなどの問題については、サーブレットや JSP の脆弱性を見たときに結論を急がず、サーブレットの前にグローバル セキュリティ フィルターが存在する可能性があることを忘れないでください。
Filter と Servlet はどちらも Java Web が提供する API であり、簡単にまとめると次のような共通点があります。
1.Filter和Servlet都需要在web.xml或注解(@WebFilter、@WebServlet)中配置,而且配置方式是非常的相似的;
2.Filter和Servlet都可以处理来自Http请求的请求,两者都有request、response对象;
3.Filter和Servlet基础概念不一样,Servlet定义是容器端小程序,用于直接处理后端业务逻辑,而Filter的思想则是实现对Java Web请求资源的拦截过滤;
4.Filter和Servlet虽然概念上不太一样,但都可以处理Http请求,都可以用来实现MVC控制器(Struts2和Spring框架分别基于Filter和Servlet技术实现的);
5.一般来说Filter通常配置在MVC、Servlet和JSP请求前面,常用于后端权限控制、统一的Http请求参数过滤(统一的XSS、SQL注入、Struts2命令执行等攻击检测处理)处理,其核心主要体现在请求过滤上,而Servlet更多的是用来处理后端业务请求上
传统的开发存在结构混乱易用性差耦合度高可维护性差等多种问题,为了解决这些毛病分层思想和MVC框架就出现了。MVC即模型(Model)、视图(View)、控制器(Controller), MVC模式的目的就是实现Web系统的职能分工。
截至2018年底,绝大多数的新项目都已然改为了基于Spring Boot的Spring MVC实现,也就是说曾经站在JavaWeb MVC最巅峰的Struts2框架已经逐渐陨落。
7.1 Spring MVC 控制器
在Spring进入了3.0时代,使用Java注解的方式也逐渐的流行了起来,曾经写一个Spring的控制器我们通常要在xml中声明Spring bean并配置处理的URL,而在新时代的Spring项目中我们通常用Spring MVC注解就可以轻松完成Spring MVC的配置了。
一个基于Spring 注解配置的控制器:
package org.javaweb.codereview.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class IndexController { @RequestMapping("/index.php") public String index() { return "/index.html"; } }
Spring Controller注解:
@Controller
@RestController
@RepositoryRestController
Spring MVC请求配置注解:
@RequestMapping
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
Spring MVC除了上述6种Http请求处理注解以外还有Spring Data JPA Rest提供的特殊的@RepositoryRestResource注解,@RepositoryRestResource是基于Spring Data JPA REST库实现的,Spring Data JPA REST提供的API可支持通过JPA查询数据并处理Http请求服务。
基于XML配置的Spring MVC
对于一些老旧的项目可能还保留了一些基于xml配置的方式Spring MVC项目,这里只简单的介绍下如何配置不做过多的描述。基于配置方式的控制器一般是在Controller类中实现了Spring的org.springframework.web.servlet.mvc.Controller接口的handleRequest方法(当然还有其他途径,如:AbstractCommandController和SimpleFormController但都已经过时了)。
TestController.java示例代码:
package org.javaweb.codereview.controller; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author yz */ public class TestController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView mv = new ModelAndView(); mv.setViewName("index"); return mv; } }
XML配置具体的bean
<bean name="/test.do" class="org.javaweb.codereview.controller.TestController"/>
7.2 Struts2控制器
Struts2主要的开发模式是基于xml配置,在struts.xml中配置Action地址和对应的处理类。
不过Struts2(2.1.6版本开始)也可以使用struts2-convention-plugin插件来实现基于注解方式的配置。
需要注意的是Struts2的参数是可以通过get/set方法传入的,如上图TestActionAnnotation类的username变量是可以直接在Http请求中的URL传入的。
7.3 快速找出Http请求请求URL
代码审计中我们可以选择优先从Controller、Servlet和JSP中入手,也可以选择从漏洞点反向推出Http请求的入口地址,这里将讲解下如何快速找到这些请求入口,因为Struts2和Spring MVC的原理比较接近,所以本节只以Spring MVC为例。
7.3.1 查找Spring MVC所有的控制器
如果有源码的情况下可以使用find命令或者IDEA的全局搜索功能即可快速搜索到所有的控制器,如果只有class文件的情况下可以使用find命令:
find ~/cms/ -type f -name "*.class" |xargs grep -E "Controller|@RestController|RepositoryRestController"
7.3.2 查找所有的请求处理URL
查找请求处理URL的方式同理,使用如下find命令查找所有class中的请求处理注解:
find ~/cms/ -type f -name "*.class" |xargs grep -E "RequestMapping|GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping|RepositoryRestResource"
7.4 Spring MVC和Struts2控制器小结
这一小节我们只是简单的介绍下Spring MVC和Struts2的控制器,在后面的框架服务章节将会详细介绍。至于如何去快速定位Struts2的action请自行参考Spring MVC的Controller查找方式这里不再讲解。
Java语言动态性一直以来都比较差,并不像PHP那样灵活。在Java中的动态性往往需要使用一些曲折的方式来实现.这里简单列举了Java十余种动态性相关技术并总结部分技术实现安全问题。
1.Java反射机制
2.MethodHandle
3.JDK动态代理
4.使用JVM上的动态语言(如:Groovy、JRuby、Jython)
5.表达式库(如:OGNL、MVEL、SpEL、EL)
6.JSP、JSPX、Quercus(Resin容器提供了PHP5支持)
7.字节码库(如:Asm、Javassist、Cglib、BCEL)
8.ScriptEngineManager(脚本引擎)。
9.动态编译(如:JDT、JavaCompiler)
10.ClassLoader、URLClassLoader
11.模版引擎(如:Freemarker、Velocity)
12.序列化、反序列化(包含Java 对象序列化、XML、JSON等)
13.JNI、JNA(Java调用C/C++)
14.OSGi(Open Service Gateway Initiative)
15.RMI(Java远程方法调用,基于对象序列化机制实现)
16.WebService
17.JDWP(Java Platform Debugger Architecture Java调试协议)
18.JMX(Java Management Extensions)
Java反射机制可以无视类方法、变量访问权限修饰符,可以调用任何类的任意方法、访问并修改成员变量值。也就是说只要发现一处Java反射调用漏洞几乎就可以为所欲为了。当然前提可能需要你能控制反射的类名、方法名和参数。
一行代码即可实现反射调用Runtime执行本地命令:
Runtime.class.getMethod("exec", String.class).invoke(Runtime.class.getMethod("getRuntime").invoke(null), "whoami")
获取一个类的对象(如Runtime类)我们一般会采用如下几种方式:
1.Class.forName("java.lang.Runtime")、"".getClass().forName("java.lang.Runtime")
2.Runtime.class
3.ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime")
Java反射获取类方法有两种方式:
1.getMethod(xxx),getMethods()
2.getDeclaredMethod(xxx)、getDeclaredMethods()。
区别在于getMethod会返回当前类和父类的所有public方法,而getDeclaredMethod返回的是当前的所有方法。
Java反射获取类成员变量有两种方式:
1.getField(xxx)、getFields()
2.getDeclaredField(xxx)、getDeclaredFields()
getField和getDeclaredField区别同上,如果想要调用private修饰的Field或者Method只需要设置下setAccessible为true就可以了,如:xxxMethod.setAccessible(true)。
Java的大部分框架都是采用了反射机制来实现的(如:Spring MVC、ORM框架等),所以我们不得不掌握Java反射机制来提升我们的代码审计能力。
Java反射机制实现无关键字执行命令
import java.io.InputStream; import java.lang.reflect.Method; import java.util.Scanner; /** * @author yz */ public class ReflectionTest { public static void exec() { try { System.out.println(Runtime.class.getMethod("exec", String.class).invoke(Runtime.class.getMethod("getRuntime").invoke(null), "curl -i localhost:8000")); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { try { String str = "whoami"; // java.lang.Runtime String runtime = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101}); // Runtime.class Class<?> c = Class.forName(runtime); // 获取getRuntime方法,Runtime.getRuntime() Method m1 = c.getMethod(new String(new byte[]{103, 101, 116, 82, 117, 110, 116, 105, 109, 101})); // 获取Runtime的exec方法,rt.exec(xxx) Method m2 = c.getMethod(new String(new byte[]{101, 120, 101, 99}), String.class); // Runtime.getRuntime().exec(str) Object obj2 = m2.invoke(m1.invoke(null), str); // 获取命令执行结果Process类的getInputStream()方法 Method m = obj2.getClass().getMethod(new String(new byte[]{103, 101, 116, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109})); m.setAccessible(true); // process.getInputStream() InputStream in = (InputStream) m.invoke(obj2, new Object[]{}); // 输出InputStream内容到 Scanner scanner = new Scanner(in).useDelimiter("\\A"); System.out.println(scanner.hasNext() ? scanner.next() : ""); } catch (Throwable t) { t.printStackTrace(); } } }
JDK7开始Java提供了MethodHandle可以非常方便的访问和调用类方法,MethodHandle的能力和Java反射机制相似,但效率却远高出Java反射机制,但MethodHandle也并不是那么完美的,缺点是MethodHandle必须要求JDK版本大于等于1.7,MethodHandle也无法像反射那样调用私有方法和变量。
参考:通过代码简单介绍JDK 7的MethodHandle,并与.NET的委托对比。
基于MethodHandle实现的调用Runtime执行系统命令
import java.io.InputStream; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.Scanner; /** * @author yz */ public class MethodHandlesTest { public static void main(String[] args) { try { String str = "ping p2j.cn -c 1"; Class runtimeClass = Runtime.class; MethodHandles.Lookup lookup = MethodHandles.lookup(); // Runtime rt = Runtime.getRuntime() MethodHandle methodHandle = lookup.findStatic( runtimeClass, "getRuntime", MethodType.methodType(runtimeClass) ); // 获取Runtime的exec方法 MethodHandle execMethod = lookup.findVirtual( runtimeClass, "exec", MethodType.methodType(Process.class, new Class[]{ String.class }) ); // 获取Process的getInputStream方法 MethodHandle inputStreamMethod = lookup.findVirtual( Process.class, "getInputStream", MethodType.methodType(InputStream.class) ); // 调用Runtime.getRuntime().exec(xxx).getInputStream() InputStream in = (InputStream) inputStreamMethod.invoke( execMethod.invoke(methodHandle.invoke(), str) ); // 输出InputStream内容到 Scanner scanner = new Scanner(in).useDelimiter("\\A"); System.out.println(scanner.hasNext() ? scanner.next() : ""); } catch (Throwable t) { t.printStackTrace(); } } }
通常我喜欢把代码审计的方向分为业务层安全问题、代码实现和服务架构安全问题,。
业务层的安全问题集中在业务逻辑和越权问题上,我们在代码审计的过程中尽可能的去理解系统的业务流程以便于发现隐藏在业务中的安全问题。
1.1 业务层中常见的安全问题Checklist
1.用户登陆、用户注册、找回密码等功能中密码信息未采用加密算法。
2.用户登陆、用户注册、找回密码等功能中未采用验证码或验证码未做安全刷新(未刷新Session中验证码的值)导致的撞库、密码爆破漏洞。
3.找回密码逻辑问题(如:可直接跳过验证逻辑直接发包修改)。
4.手机、邮箱验证、找回密码等涉及到动态验证码等功能未限制验证码失败次数、验证码有效期、验证码长度过短导致的验证码爆破问题。
5.充值、付款等功能调用了第三方支付系统未正确校验接口(如:1分钱买IPhone X)。
6.后端采用了ORM框架更新操作时因处理不当导致可以更新用户表任意字段(如:用户注册、用户个人资料修改时可以直接创建管理员账号或其他越权修改操作)。
7.后端采用了ORM框架查询数据时因处理不当导致可以接收任何参数导致的越权查询、敏感信息查询等安全问题。
8.用户中心转账、修改个人资料、密码、退出登陆等功能未采用验证码或Token机制导致存在CSRF漏洞。
9.后端服务过于信任前端,重要的参数和业务逻辑只做了前端验证(如:文件上传功能的文件类型只在JS中验证、后端不从Session中获取用户ID、用户名而是直接接收客户端请求的参数导致的越权问题)。
10.用户身份信息认证逻辑问题(如:后台系统自动登陆时直接读取Cookie中的用户名、用户权限不做验证)。
11.重要接口采用ID自增、ID可预测并且云端未验证参数有效性导致的越权访问、信息泄漏问题(如:任意用户订单越权访问)。
12.条件竞争问题,某些关键业务(如:用户转账)不支持并发、分布式部署时不支持锁的操作等。
13.重要接口未限制请求频率,导致短信、邮件、电话、私信等信息轰炸。
14.敏感信息未保护,如Cookie中直接存储用户密码等重要信息。
15.弱加密算法、弱密钥,如勿把Base64当成数据加密方式、重要算法密钥采用弱口令如123456。
16.后端无异常处理机制、未自定义50X错误页面,服务器异常导致敏感信息泄漏(如:数据库信息、网站绝对路径等)。
17.使用DWR框架开发时前后端不分漏洞(如:DWR直接调用数据库信息把用户登陆逻辑直接放到了前端来做)。
代码审计的核心是寻找代码中程序实现的安全问题,通常我们会把代码审计的重心放在SQL注入、文件上传、命令执行、任意文件读写等直接威胁到服务器安全的漏洞上,因为这一类的漏洞杀伤力极大也是最为致命的。
###2.1 代码实现中常见的安全问题Checklist
1.任意文件读写(文件上传、文件下载)、文件遍历、文件删除、文件重命名等漏洞
2.SQL注入漏洞
3.XXE(XML实体注入攻击)
4.表达式执行(SpEL、OGNL、MVEL2、EL等)
5.系统命令执行漏洞(ProcessBuilder)
6.反序列化攻击(ObjectInputStream、JSON、XML等)
7.Java反射攻击
8.SSRF攻击
2.1.1 Java 文件名空字节截断漏洞(%00 Null Bytes)
空字节截断漏洞漏洞在诸多编程语言中都存在,究其根本是Java在调用文件系统(C实现)读写文件时导致的漏洞,并不是Java本身的安全问题。不过好在高版本的JDK在处理文件时已经把空字节文件名进行了安全检测处理。
2013年9月10日发布的Java SE 7 Update 40修复了空字节截断这个历史遗留问题。此次更新在java.io.File类中添加了一个isInvalid方法,专门检测文件名中是否包含了空字节。
修复的JDK版本所有跟文件名相关的操作都调用了isInvalid方法检测,防止空字节截断。
修复前(Java SE 7 Update 25)和修复后(Java SE 7 Update 40)的对比会发现Java SE 7 Update 25中的java.io.File类中并未添加\u0000的检测。
受空字节截断影响的JDK版本范围:JDK<1.7.40,单是JDK7于2011年07月28日发布至2013年09月10日发表Java SE 7 Update 40这两年多期间受影响的就有16个版本,值得注意的是JDK1.6虽然JDK7修复之后发布了数十个版本,但是并没有任何一个版本修复过这个问题,而JDK8发布时间在JDK7修复以后所以并不受此漏洞影响。
参考:
JDK-8014846 : File and other classes in java.io do not handle embedded nulls properly。
维基百科-Java版本歷史
Oracle Java 历史版本下载
2.1.2 测试Java写文件截断测试
测试类FileNullBytes.java:
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; /** * @author yz */ public class FileNullBytes { public static void main(String[] args) { try { String fileName = "/tmp/null-bytes.txt\u0000.jpg"; FileOutputStream fos = new FileOutputStream(new File(fileName)); fos.write("Test".getBytes()); fos.flush(); fos.close(); } catch (IOException e) { e.printStackTrace(); } } }
使用JDK1.7.0.25测试成功截断文件名:
使用JDK1.7.0.80测试写文件截断时抛出java.io.FileNotFoundException: Invalid file path异常:
空字节截断利用场景
Java空字节截断利用场景最常见的利用场景就是文件上传时后端使用了endWith、正则使用如:.(jpg|png|gif)$验证文件名后缀且文件名最终原样保存,同理文件删除(delete)、获取文件路径(getCanonicalPath)、创建文件(createNewFile)、文件重命名(renameTo)等方法也可适用。
空字节截断修复方案
最简单直接的方式就是升级JDK,如果担心升级JDK出现兼容性问题可在文件操作时检测下文件名中是否包含空字节,如JDK的修复方式:fileName.indexOf('\u0000')即可。
2.1.2 任意文件读取漏洞
任意文件读取漏洞即因为没有验证请求的资源文件是否合法导致的,此类漏洞在Java中有着较高的几率出现,任意文件读取漏洞看似很简单,但是在这个问题上翻车的有不乏一些知名的中间件:Weblogic、Tomcat、Resin又或者是主流MVC框架:Spring MVC、Struts2。所以在审计文件读取功能的时候要非常仔细,或许很容易就会有意想不到的收获!
任意文件读取示例代码file-read.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="java.io.ByteArrayOutputStream" %> <%@ page import="java.io.File" %> <%@ page import="java.io.FileInputStream" %> <% File file = new File(request.getParameter("path")); FileInputStream fis = new FileInputStream(file); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] b = new byte[1024]; int a = -1; while ((a = fis.read(b)) != -1) { baos.write(b, 0, a); } out.write("<pre class="brush:php;toolbar:false">" + new String(baos.toByteArray()) + ""); fis.close(); %>
访问file-read.jsp文件即可读取任意文件:http://localhost:8080/file/file-read.jsp?path=/etc/passwd
快速发现这类漏洞得方式其实也是非常简单的,在IDEA中的项目中重点搜下如下文件读取的类。
1.JDK原始的java.io.FileInputStream类
2.JDK原始的java.io.RandomAccessFile类
3.Apache Commons IO提供的org.apache.commons.io.FileUtils类
4.JDK1.7新增的基于NIO非阻塞异步读取文件的java.nio.channels.AsynchronousFileChannel类。
5.JDK1.7新增的基于NIO读取文件的java.nio.file.Files类。常用方法如:Files.readAllBytes、Files.readAllLines
如果仍没有什么发现可以搜索一下FileUtil很有可能用户会封装文件操作的工具类。
Java WebSevice
Web Service是一种基于SOAP协议实现的跨语言Web服务调用,在Java中Web Service有如下技术实现:Oracle JWS、Apache Axis1、2、XFire、Apache CXF、JBossWS。
Axis1.4 配置
web.xml配置Axis1.4
配置server-config.wsdd文件注册Web Service服务类和方法:
FileService类,提供了文件读写接口:
使用IDEA创建Web Service项目默认会创建管理Web Service的API:/servlet/AxisServlet、/services、SOAPMonitor、/servlet/AdminServlet,*.jws以及用监控Web Service的端口5001或5101。
访问Web Service的FileService服务加上?wsdl参数可以看到FileService提供的服务方法和具体的参数信息。
使用SOAP-UI调用Web Service接口示例:
需要注意的是Web Service也是可以设置授权认证的,如实现了WS-Security的WSS4J。
使用IDEA根据wsdl生成Web Service客户端代码:
设置wsdl地址、包名:
新建FileServiceTest类测试接口调用:
package org.javaweb.codereview.axis.client; import java.net.URL; /** * 文件Web Service服务测试 * * @author yz */ public class FileServiceTest { public static void main(String[] args) { try { FileServiceService fileService = new FileServiceServiceLocator(); URL webServiceUrl = new URL("http://localhost:8080/services/FileService"); FileServiceSoapBindingStub soapService = new FileServiceSoapBindingStub(webServiceUrl, fileService); String content = soapService.readFile("/etc/passwd"); System.out.println(content); } catch (Exception e) { e.printStackTrace(); } } }
以上がJava Web セキュリティのコード監査を分析する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。