java代码质量检查工具_jvm问题排查

java代码质量检查工具_jvm问题排查wJa是一款结合DAST、SAST、IAST的综合性应用程序安全分析工具,支持对javaweb程序的安全性进行分析,含有反编译,代码审计,调试jar包,代理追踪等用于分析软件安全的功能。

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

java代码质量检查工具_jvm问题排查

Part1 wJa

wJa是一款结合DAST、SAST、IAST的综合性应用程序安全分析工具,支持对java web程序的安全性进行分析,含有反编译,代码审计,调试jar包,代理追踪等用于分析软件安全的功能。

1 wJa的工作原理

java代码质量检查工具_jvm问题排查

本片文章将会用几个示例来讲解如何使用wJa进行软件安全性分析

2 cheetah脚本语言

为了能够让SAST更加的附有灵活性,wJa引入了cheetah脚本语言来应对复杂的代码场景,cheetah是一门基于java开发的专门针对渗透测试的脚本解析语言,如果想要完全掌握wJa的使用,灵活地进行代码审计,可以通过https://github.com/Wker666/Demo进行cheetah脚本语言的学习。

3 wJa使用

需求环境:JDK 1.8

通过使用java -jar wJa.jar启动wJa,启动之后会要求选择指定分析的jar包程序,这里我们选择wJa自带的测试靶场进行分析。

java代码质量检查工具_jvm问题排查

wJa UI介绍

菜单栏目

File:保存当前的cheetah脚本

script:运行/停止当前cheetah脚本

左边栏目

Decompile:反编译文件结构

cheetahLangue:cheetah自带的支持库函数信息和当前工作区的cheetah脚本

中间栏目

Decompile:jar包中通过class反编译的java代码

CheetahLangue:cheetah脚本代码

DebugJar:jar文件调试

Web:简易浏览器

wJa反编译的代码比较

java代码质量检查工具_jvm问题排查

可以看到虽然与源代码不是完全相同,但是在语义上时没有区别的,反编译的代码一般来讲是不能直接运行的,但是作为分析是完全足够的。

wJa调试jar包

在选择wJa的启动之后,wJa将会自动启动jar包,并且注入agent和attach到jar包进程上,所以wJa提供了追踪真实调用链和调试jar包的功能。

转到DebugJar栏目,可以看到如下内容:

java代码质量检查工具_jvm问题排查

右下方是jar包的输出信息,可以看到jar包的操作信息

例如想要调试org.joychou.controller.SQLI中的jdbc_sqli_vul方法,就需要将org/joychou/controller/SQLIjdbc_sqli_vul填入class和method中,点击get method content按钮,下方就会显示对应的代码信息:

java代码质量检查工具_jvm问题排查

在每一条代码前方都有一个编号,这一个编号实际上对应的是这条语句执行完时的字节码偏移,可以通过这个来给代码下断,例如我想要停在sql = new StringBuilder().append("select * from users where username = '").append(username).append("'").toString();这条语句(并没有开始执行),那么就需要在ID中输入54,因为运行完54时候就要开始执行这条语句了,这时候我们通过浏览器访问对应的接口页面。

这里需要注意需要开启mysql,SQL注入部分需要数据库支持,建表的sql语句在create_db.sql中。

这时候可以看到调试信息:

java代码质量检查工具_jvm问题排查

第一行信息是当前运行到的字节码偏移,下面就是变量信息,下面就可以单步步过一步步调试。

java代码质量检查工具_jvm问题排查

Agent方法的IAST跟踪

通过调用:StartRecordFunStopRecordFun方法进行起始和结束的跟踪。

StartRecordFun
无参数
返回值:无
StopRecordFun
参数1:要查询的起始类名+方法名
返回值:执行流数组

需要注意的是不能注入所有的类,因为SpringBoot启动类不能注入,注入的话运行速度太慢了,所以需要在config/agent_exclude.txt指定不注入的类起始字符,例如:org/springframework

4 案例1:扫描SQL注入

根据三元组原理,首先需要找到入口点,而入口点则是类的方法,可是并不是所有类都是SpringBoot的类,这时候就需要扫描存在指定注解的类,wJa自带了扫描的方法:

function getSpringAnnotationValue(an){
 anSize = GetArrayNum(an);
 i = 0;
 flag = 0;
 while(i < anSize){
  if(GetAnnotationName(an[i]) == "org/springframework/web/bind/annotation/RequestMapping"){
   allValue = GetAnnotationArgListValue(an[i],"value");
   return allValue[0];
  }
  if(GetAnnotationName(an[i]) == "org/springframework/web/bind/annotation/GetMapping"){
   allValue = GetAnnotationArgListValue(an[i],"value");
   return allValue[0];
  }
  if(GetAnnotationName(an[i]) == "org/springframework/web/bind/annotation/PostMapping"){
   allValue = GetAnnotationArgListValue(an[i],"value");
   return allValue[0];
  }
  if(GetAnnotationName(an[i]) == "org/springframework/web/bind/annotation/RequestParam"){
   allValue = GetAnnotationArgSingValue(an[i],"value");
   return allValue;
  }
  if(GetAnnotationName(an[i]) == "org/springframework/web/bind/annotation/RestController"){
   flag = 1;
  }
  i = ToInt(i + 1);
 }
 if(flag == 1){
  return "/";
 }
 return "";
}

function GetAllSpringApiClasses(){
 array res;
 allClass = GetAllClassName();
 size = GetArrayNum(allClass);
 i = 0;
 while(i < size){
  an = GetClassAnnotation(allClass[i]);
  p = getSpringAnnotationValue(an);
  if(p != ""){
   ArrayAddEle(res,allClass[i]);
  }
  i = ToInt(i + 1);
 }
 return res;
}

具体代码是通过支持库函数得到所有的类,对所有的类判断注解是否存在Spring接口,如果存在则添加,最终以数组的方式返回所有满足接口要求的类。

有了类之后就需要遍历所有的方法。

function SQLTrack(className){
 an = GetClassAnnotation(className);
 classPath = baseUrl.getSpringAnnotationValue(an);
 methods = GetAllMethodName(className);
 size = GetArrayNum(methods);
 i = 0;
 while(i < size){
  argCnt = GetMethodArgCnt(className,methods[i]);
  j = 0;
  while(j < argCnt){
   if(methods[i] != "<init>"){trackSQL(className,methods[i],classPath,j);}
   j = ToInt(j+1);
  }
  i = ToInt(i+1);
 }
 return 0;
}

SQLTrack方法通过传入className来进行SQL注入的追踪,遍历所有的类方法调用trackSQL函数进行判断是否存在漏洞

function trackSQL(className,methodName,url,argIndex){
 array allNode;
 allNode = TrackVarIntoFun(className,methodName,argIndex,"java/sql/Statement","executeQuery",0,1);
 size = GetArrayNum(allNode);
 if(ToInt(size-1) < 0){return 0;}
 i = 0;
 print(methodName.":SQL注入 白盒测试调用链跟踪:");
 cc = 7;
 cs = 1;
 while(i < size){
  sentence = GetJavaSentence(allNode[i]);
  noSan = filter(sentence,GetTrackName(allNode[i]));
  if(noSan == 0){cc = 5;cs = 5;}
  if(i == ToInt((size-1))){
   if(cc != 5){cs = 2;cc = 3;}
  }else{}
  if(noSan == 0){
   printcolor("[-]",6);printcolor("想办法绕过此类:",4);
  }else{
   printcolor("[+]",1);
  }
  printcolor(GetClassName(GetNodeClassName(allNode[i]))."   ",cc);
  printcolor(sentence.StrRN(),cs);
  i = ToInt(i+1);
 }
 if(cc != 5){
  printcolor("白盒测试发现此调用链可能存在漏洞,生成测试链接进行黑盒测试".StrRN(),7);
  an = GetClassMethodAnnotation(className,methodName);
  var argName;
  try{
   arg_an = GetClassMethodArgAnnotation(className,methodName,0);
   argName = getSpringAnnotationValue(arg_an);
  }catch(){
   argName = GetClassMethodArgName(className,methodName,0);
  }
  if(argName != ""){
   api = url.getSpringAnnotationValue(an)."?".argName."=Wker";
   StartRecordFun();
   if(judgeSQLI(api) == 1){
    printcolor("[+]生成测试链接:".api."   测试存在SQL注入漏洞!".StrRN(),3);
   }else{
    printcolor("[-]生成测试链接:".api."   测试不存在SQL注入漏洞!请自行测试。".StrRN(),5);
   }
   print("IAST真实调用链:",StopRecordFun(className.".".methodName));
  }else{
   printcolor("测试链接生成失败,error:未找到参数入口!".StrRN(),5);
  }
 }
 
 return 0;
}

TrackVarIntoFun方法是支持库函数:

参数1:起始类
参数2:起始方法
参数3:起始方法参数下标
参数4:目标方法的类
参数5:目标方法
参数6:目标方法的参数下标
参数7:0:一直跟踪1:只跟踪到sink
返回值:执行流node数组

通过传入入口点和污点聚集点来判断是否存在直连,并且返回执行流的所有节点,返回的节点是一个对象,可以通过对应的函数获取相对信息。

node节点可以获得的信息:

  1. 当前node节点的类+方法

  2. 当前追踪的变量

  3. 执行的java代码

如果存在调用的话那么就将调用链进行打印,并且判断路径中是否存在过滤函数。

过滤函数判断使用正则即可,需要传入的是java代码和当前追踪的变量:

#define filter1=String.valueOf(.*?
#define filter2=Integer.valueOf(.*?
#define filter3=Long.valueOf(.*?

function filter(sentence,trackName){
 ap = trackName.".*?)";
 a = StrRe(sentence,filter1.ap);
 if(GetArrayNum(a) != 0){return 0;}
 a = StrRe(sentence,filter2.ap);
 if(GetArrayNum(a) != 0){return 0;}
 a = StrRe(sentence,filter3.ap);
 if(GetArrayNum(a) != 0){return 0;}
 return 1;
}

后期会提供更加标准的规则。

如果不存在过滤函数则进入黑盒检测,这里通过注解拼接得到真正的测试连接,调用judgeSQLI方法判断此链接,通过or判断是否存在SQL注入:

function judgeSQLI(api){
 res = HttpGet(api,cookie);
 res1 = HttpGet(api."%27%20or%201=1--+",cookie);
 if(GetStrLength(res1[0]) != GetStrLength(res[0])){
  res2 = HttpGet(api."%27%20or%202=1--+",cookie);
  if(GetStrLength(res2[0]) == GetStrLength(res[0])){
   return 1;
  }
 }
 return 0;
}

最终看一下打印的信息。

java代码质量检查工具_jvm问题排查

可以看到最终成功打印出存在SQL注入的调用链并且黑盒测试存在漏洞。

细心的朋友可能发现这里面存在一条真实IAST调用链,这个是通过java agent注入得到的真实调用,可以看到确实进入了SQLI的危险函数。

5 案例2:获取危险库

靶场已经了解的差不多了,那么就进入实战操作,这里用到的实战项目是华夏ERP:https://github.com/jishenghua/jshERP。

需要启动mysql和redis,并且进行简单的配置,这里就不赘述,可以根据项目github的readme进行操作。

同样的打开jar包。

wJa自带了一个检查危险库的方法,是通过扫描pom.xml导入的库判断是会否存在危险的库,源代码不贴了,运行结果:

java代码质量检查工具_jvm问题排查

可以看到存在危险的fastjson和log4j组件。

6 案例3:fastjson检测

与SQLI检测唯一的不同就是sink的函数是不同的,并且检测方法也是不同的。

sink函数fastjson的parseObject方法,所以应该这样子改变:TrackVarIntoFun(className,methodName,argIndex,"com/alibaba/fastjson/JSONObject","parseObject",0,1);

对于fastjson的检测最好借助dnslog,所以需要写一个dnslog的工具包:

function getDnsLogDomain(){
    SetGlobalValue("dnslogCookie","User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36".StrRN()."Cookie: PHPSESSID=wJa;");
    res = HttpGet("http://www.dnslog.cn/getdomain.php?t=0.02695357778962082",dnslogCookie);
    dns = StrSplit(res[0],StrRN());
    return dns[0];
}
function getDnsLogRecord(){
    res = HttpGet("http://www.dnslog.cn/getrecords.php?t=0.29442376629799494",dnslogCookie);
    rec = StrSplit(res[0],StrRN());
    return rec[0];
}

通过获得dns域名,然后通过java.net.Inet4Address访问对应的dns查看回显判断是否真实存在反序列化漏洞。

function judgeFastjson(api){
 domain = getDnsLogDomain();
 res1 = HttpGet(api."%7B%22%40type%22%3A%22java.net.Inet4Address%22%2C%22val%22%3A%22".domain."%22%7D",cookie);
 Sleep(1000);
 if(getDnsLogRecord() != "[]"){
  return 1;
 }
 return 0;
}

最终执行脚本发现存在对应的调用链:

java代码质量检查工具_jvm问题排查

发现有一条完整并且黑盒测试正常的调用链,下面也有测试不存在的,并且也检查到了存在过滤函数的。

通过这样子的测试可以挖取到所有的调用链,存在过滤函数或者测试失败的可以debug分析一下看看是否存在bypass的方法。

7 案例4:mybatis类型的SQLI

与普通的SQLI注入不同,这一个sink函数并不是固定的,而是通过xml文件进行动态设置的,这里也能体现出wJa的灵活性,可以灵活的应对不同的复杂场景。

获取所有映射的xml文件名称

function GetConfigeFileMap(path){
    ap = GetFileContent(path);
    allPro = StrSplit(ap,StrRN());
    i = 0;
    size = GetArrayNum(allPro);
    res = GetHashMap();
    while(i < size){
        cur = allPro[i];
        index = StrFindStr(cur,"=",0);
        if(index == "-1"){
         i = ToInt(i+1);
         continue;
        }
        key = StrSubString(cur,0,index);
        value = StrSubString(cur,ToInt(index+1),GetStrLength(cur));
        SetHashMapValue(res,key,value);
        i = ToInt(i+1);
    }
    return res;
}
function GetApplicationPro(){
 return GetConfigeFileMap("BOOT-INF/classes/application.properties");
}
function getAllMapperXmlFileNames(){
 pro = GetApplicationPro();
 v = GetHashMapValue(pro,"mybatis-plus.mapper-locations");
 index = StrFindStr(v,":",0);
 path = StrSubString(v,ToInt(index+1),GetStrLength(v));
 return MatchesFileName(GetFilePath(path));
}

通过application.properties中的mybatis-plus.mapper-locations属性得到文件夹,再通过MatchesFileName支持库函数得到所有的xml文件。

解析xml文件得到SQL类和方法

得到xml文件之后进行解析

function getClassMethodName(root){
 array res;
 childs = GetElementChilds(root);
 childSize = GetArrayNum(childs);
 i = 0;
 while(i < childSize){
  if(GetElementName(childs[i]) == "select"){
   attributes = GetElementAttributes(childs[i]);
   attributeSize = GetArrayNum(attributes);
   j = 0;
   while(j < attributeSize){
    if(GetAttributeName(attributes[j]) == "id"){
     ArrayAddEle(res,GetAttributeText(attributes[j]));
    }
    j = ToInt(j + 1);
   }
  }
  i = ToInt(i + 1);
 }
 return res;
}
function getClassName(root){
 attributes = GetElementAttributes(root);
 attributeSize = GetArrayNum(attributes);
 j = 0;
 while(j < attributeSize){
  if(GetAttributeName(attributes[j]) == "namespace"){
   return GetAttributeText(attributes[j]);
  }
  j = ToInt(j + 1);
 }
 return "";
}

通过传入的xml root(这个可以通过xml类支持库函数得到),namespace属性是类名,这里只截取select的方法,获取对应的id就是对应的方法名,最终可以得到所有的类名和方法名。

获取到所有的mybatis方法之后就需要带入之前的SQLI中,需要动态设置sink:

function MybatisSQLTrack(className){
    an = GetClassAnnotation(className);
    classPath = baseUrl.getSpringAnnotationValue(an);
    methods = GetAllMethodName(className);
    size = GetArrayNum(methods);
    mybatisXmls = getAllMapperXmlFileNames();
    xmlSize = GetArrayNum(mybatisXmls);
    xmlIndex = 0;
    while(xmlIndex < xmlSize){
        root = GetXMLRoot(GetFileContent(mybatisXmls[xmlIndex]));
        mybatisClassName = StrReplace(getClassName(root),"\.","/");
        mybatisMethodNames = getClassMethodName(root);
        mybatisMethodNameSize = GetArrayNum(mybatisMethodNames);
        mybatisMethodIndex = 0;
        while(mybatisMethodIndex < mybatisMethodNameSize){
            curMybatisMethodName = mybatisMethodNames[mybatisMethodIndex];
            //mybatis注入
            i = 0;
            while(i < size){
                argCnt = GetMethodArgCnt(className,methods[i]);
                j = 0;
                while(j < argCnt){
                    if(methods[i] != "<init>"){
                        trackMybatisSQL(className,methods[i],classPath,j,mybatisClassName,curMybatisMethodName);
                    }
                    j = ToInt(j+1);
                }
                i = ToInt(i+1);
            }
            mybatisMethodIndex = ToInt(mybatisMethodIndex + 1);
        }
        xmlIndex = ToInt(xmlIndex + 1);
    }
 return 0;
}

逻辑相对也是比较简单的,与之前不同的是需要动态传入sink类和方法,执行查看结果:

java代码质量检查工具_jvm问题排查

最终可以打印出所有调用链。

8 目前自带的漏洞检测脚本

java代码质量检查工具_jvm问题排查

虽然写了不少,但是还是需要根据所应对的场景自己进行修改。

9 wJa的一些细节

wJa实现了流式算法,可以追踪包括map在内的变量跳转,并且会根据java的实现类和子类进行跳转扫描,保证所有调用链的完全扫描。

10 wJa Link

https://github.com/Wker666/wJa

如果存在错误或者bug,请在issue中提出,Wker将在两天内修复!

hxd写了这么多,给个Star吧Thanks♪(・ω・)ノ


“D&X 安全实验室”

专注渗透测试技术

全球最新网络攻击技术

java代码质量检查工具_jvm问题排查

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/194552.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • minipcie usb总线_ipadmini2换wifi模块

    minipcie usb总线_ipadmini2换wifi模块1、概述EC20R2.1MiniPCIe-C模块是PCIExpressMiniCard1.2标准接口LTE模块。本文章主要讲解了如何驱动EC20R2.1MiniPCIe-C模块的硬件电路设计,主要包含有:电源设计通讯接口SIM卡的防护1.1、EC20R2.1MiniPCIe-C模块引脚分配1.2、EC20R2.1MiniPCIe-C模块引脚描述引脚号miniPCIE引脚名模块引脚名I/O功能描述备注1WAKE

  • EXCEL解密打开密码

    EXCEL解密打开密码excel文件设置了打开密码,但是设置的密码忘记了,导致现在没有办法打开文件了,这种情况需要解密excel文件。解密的方法就是找回密码,而且MSOfficeExcel文件中没有提供找回密码功能,所以我们需要自己找回密码,可以用奥凯丰EXCEL解密大师。【EXCEL解密大师】快速找回密码_轻松移除使用限制-奥凯丰okfone…

  • Pycharm设置自动代码提示(超详细)

    Pycharm设置自动代码提示(超详细)【前言】最近在使用pycharm这款编译器的时候,发现在学习python过程中没有代码提示就很烦,所以网上收集资料加上自身的实践总结出以下方法如何在pycharm中设置代码提示。【步骤一】起初看到网上很多教程都是这样点击File然后将PowerSaveMode旁边的√去掉就可以,实际上确实是这样的,当我们敲代码的时候比如import就会有自动提示。【注意】但是可能有些小伙伴按照这样的提示可是还是不显示代码自动提示,为什么会这样呢,原因是你的Python环境没有配置好,我们按照下面

  • join方法的实现原理「建议收藏」

    join方法的实现原理「建议收藏」于Java开发人员,多线程应该是必须熟练应用的知识点,特别是开发基于Java语言的产品。本文将深入浅出的表述Java多线程的知识点,在后续的系列里将侧重于Java5由DougLea教授提供的Concurrent并行包的设计思想以及具体实现与应用。如何才能深入浅出呢,我的理解是带着问题,而不是泛泛的看。所以该系列基本以解决问题为主,当然我也非常希望读者能够提出更好的解决问题的方案以及提

  • USB协议基础篇

    USB协议基础篇初次接触USB的同学,可能会被里面各种名词给搞晕,下面就来梳理一下这些知识,希望能帮助大家理解USB。文章目录 一,从最常见的名词说起 1.1什么是USB 1.2USB协议版本 1.3USB接口分类 1.4PIPE 1.5endpoint 1.6管道通信方式 1.7传输方式 1.7逻辑设备 1.8interface 1.9class协议 1.10host/device 二,USB框架/拓扑结构

  • 地理加权回归模型步骤_地理加权回归中的拟合度

    地理加权回归模型步骤_地理加权回归中的拟合度目录数据准备加载需要的R包导入空间数据空间自相关分析空间邻域面数据空间邻域点数据空间邻域全局空间自相关局部空间自相关空间回归分析线性回归分析地理加权回归经典的线性回归模型是建立在最小二乘法(OLS模型)基础上对参数进行“平均”或“全局”估计。如果自变量为空间数据,且自变量间存在空间自相关性,传统回归模型(OLS模型)残差项独立的假设将无法满足。地理加权回归(GWR)模型能够反映参数在不同空间的空间非平稳性,使变量间的关系可以随空间位置的变化而变化,其结果更符合客观实际,能反映局部情况。杨晴青,刘倩

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号