JAVA代码审计学习心得

admin 2023年5月9日13:55:45评论48 views字数 5898阅读19分39秒阅读模式
 

 

 JAVA代码审计

 

学习心得

 

实际案例收获分享
P
ART.03
 

1

1.案例介绍

前段时间上综合项目代码分析课的时候收获了一个新思路,是关于Log4j2漏洞入口点寻找的。以前在学习Log4j2漏洞时候看到很多文章在复现时候用到error()方法去复现,当时也不懂Java代码就只能看到别人用error()方法就以为只有error一个方法能复现。

后面学了代审后才知道,原来一共是有6个打印日志的方法,分别是fatal、error、warn、info、debug、trace。那么既然知道有6个方法,当然也要去测试一下是否能复现了。

 

log4j2版本

JAVA代码审计学习心得

测试6个方法

JAVA代码审计学习心得

结果是只有error、fatal两个方法可以成功获取到dns解析记录,另外四个warn、info、debug、trace都不可以。

JAVA代码审计学习心得

但是!直到了实战项目时候才发现原来并不是只有error、fatal才能解析成功,其他方法也可以但是需要一定利用条件,通常是日志文件配置不当造成。

先来看看log4j2的日志级别,log4j2支持多种日志级别,通过日志级别我们可以将日志信息进行分类,在合适的地方输出对应的日志。哪些信息需要输出,哪些信息不需要输出,只需在一个日志输出控制文件中稍加修改即可。级别由高到低共分为6个:fatal, error, warn, info, debug, trace。

JAVA代码审计学习心得

当系统配置的intLevel >= 调用方法的intLevel的时候,log4j2才会启用日志打印或日志存储。

如:

系统配置debug等级(500) >= 调用方法info等级(400),条件成立启用日志打印。✔️

系统配置debug等级(500) >= 调用方法debug等级(500),条件成立启用日志打印。✔️

系统配置debug等级(500) >= 调用方法trace等级(600),条件不成立关闭日志打印。❌

系统配置ALL等级(2147483647) >= 调用方法debug等级(500),条件成立启用日志打印。✔️

系统配置OFF等级(0) >= 调用方法trace等级(600),条件不成立关闭日志打印。❌

2.代码分析

我们看下log4j2里面的代码是什么样的。

位置:log4j-api-2.10.0.jar!org

apachelogginglog4jspiAbstractLogger.class#logIfEnabled方法

1442行:就是判断条件是否成立的地方,只有条件成立启用日志才会走1443行,只有能走进这1443行后才能进行后续的jndi注入(至于后续怎么解析${}并注入jndi这里就不说了,感兴趣的可以自己去跟踪代码看一下)。

JAVA代码审计学习心得

跟进isEnabled方法,这里面是通过当前配置进行过滤。

JAVA代码审计学习心得

跟进this.privateConfig.filter方法,可看到295就是我们上面说到日志等级条件是否成立的问题。

this.intLevel就是当前系统的等级,level.intLevel()就是我们当前调用方法的等级。

JAVA代码审计学习心得

这个this.intLevel(系统配置等级)在实际项目中的时候可能是会变的。

log4j2默认情况下系统配置等级是error,所以以前我在测试时候就只有error、fatal会成功。

在实际项目中一般开发人员都会配置log4j2的配置文件,一般都会命名为:log4j2.xml

里面的内容大概长这样,别看内容好像很多其实我们只需要留意关键字就行。

关键字:level="

<?xml version="1.0" encoding="UTF-8" ?>
<configuration status="error">

<appenders>

<Console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="debug" />

<PatternLayout pattern="%d %-5level %class{36} %L %M - %msg%xEx%n"/>
</Console>

        <RollingFile name="RollingFileInfo" fileName="logs/info.log"
filePattern="logs/info-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>

<RollingFile name="RollingFileError" fileName="${sys:user.home}/logs/error.log"
filePattern="logs/error-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
</appenders>

<loggers>
<root level="all">

<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileError"/>
</root>
</loggers>
</configuration>

注意:

存在有appenders子元素时候以appenders的level为准。

 

JAVA代码审计学习心得

因为代码里面还会有第二次过滤,是通过遍历appenders子元素里面的每一个级别来进行过滤的。

this.intLevel是当前调用方法的级别,level.intLevel是appenders子元素里面配置给系统的级别。

这里一样得系统级别 >= 调用方法级别才可继续往下走。

JAVA代码审计学习心得

没有appenders子元素时候以root子元素为准,root元素就是我们代码第一次过滤时候。

JAVA代码审计学习心得

因为appenders优先级要高于root,会覆盖掉root的日志级别。

3.漏洞复现

那么这里以appenders子元素里面的level为例。

等级从小到大分别是:warn(300) < info(400) < debug(500)

只要我们系统配置级别 >= 调用方法的级别,我们条件就可以成立。

这里可以调用的方法有:fatal(100)、error(200)、warn(300)、info(400)、debug(500),trace(600)则不成立。

这里就演示一个debug方法,成功执行。

JAVA代码审计学习心得

trace方法不成立,直接没打印日志,程序也不会继续往下走jndi注入计算器也没有弹框。

JAVA代码审计学习心得

需要注意的是实际项目中要是使用的springboot架构的项目,还要留意下application.properties文件,里面也会有等级的配置。

经测试application.properties文件配置的等级优先级比log4j2.xml里面的appenders子目录等级还要高。

JAVA代码审计学习心得

 

4.案例总结

1.审计log4j2漏洞入口点时候,先看是否存在漏洞版本范围。

2.查看application.properties文件、log4j2.xml文件里面的系统配置等级筛选出能利用的日志等级方法。

3.全局代码搜索漏洞入口方法跟踪参数查看是否由请求参数传进来。

2

1.案例介绍

实战项目:任意文件上传+目录穿越代码审计

项目架构:SpringBoot + Mybaits

 

2.代码分析

1)针对文件操作类型的漏洞寻找入口点,全局搜索upload关键字找到一个跟文件上传相关的controller。

@ApiOperation(value = "上传")
@PostMapping("/uploadFile")
// 请求参数fileMultipartFile类型、参数titleString、参数detailstring
public Response<Boolean> uploadFile(@RequestParam("file") MultipartFile file, @RequestParam("title")String title, @RequestParam("detail")String detail) throws IOException {
// 这里重点关注NoticesService.uploadFileF方法
Wj file = NoticesService.uploadFileF(file);
String contentType = file.getContentType();
String fileName = file.getOriginalFilename();
ZhiNa zhinan = new ZhiNa();
zhinan.setDetail(detail);
zhinan.setTitle(title);
zhinan.setFileName(fileName);
zhinan.setFileId(file.getFileId());
boolean ret = payzhinanService.save(zhinan);
return Response.successData(ret);
}

2)在file参数存在未过滤的情况,跟进NoticesService.uploadFileF(file)进一步查看是否有过滤。

 

 @Override
public Wj uploadFileF(MultipartFile wjFile) {
// 把文件上传到指定服务器,这两个变量是配置文件里面固定写死的。
// ServerUrlhttp://192.2.16.22:42098
// UploadUrlinner/Service/upload
String uploadUrl = ServerUrl + "/" + UploadUrl;
Map<String, File> param = new HashMap<>();
// 重点关注transferToFile方法
File file = FileUtils.transferToFile(wjFile);

param.put("file", file);
logger.info("文件中心上传路径:{}", uploadUrl);
String response = StorageClient.uploadServerFile(uploadUrl, appid, DirectoryId, param);
JSON parse = JSONUtil.parse(response);
JSONArray jsonArray = (JSONArray) parse.getByPath("body");
JSONObject jsonObject = (JSONObject) jsonArray.get(0);
Wj Wj = jsonObject.toBean(Wj.class);
return Wj;
}

3)继续跟进FileUtils.transferToFile(wjFile)

第9行:在当前文件路径下创建一个文件,文件名是以请求参数传递过来的文件名。

注意:这里的getOriginalFilename未进行任何过滤直接拼接到文件路径上。

3.1 未进行黑白名过滤后缀,可上传任意格式后缀的文件。

3.2 未对 “../”进行过滤,可造成目录穿越,上传文件到任意目录。

10~14行:判断文件是否存在,不存在则创建文件,并把内容写进去目标文件。

 

    /**
* MultipartFile传成File
* @param multipartFile
* @return
*/
public static File transferToFile(MultipartFile multipartFile){
File file = null;
try {
String filePath = new File("").getCanonicalPath() + File.separator + multipartFile.getOriginalFilename();
file = new File(filePath);
if(!file.exists()) {
file.createNewFile();
}
multipartFile.transferTo(file);
} catch (IOException e) {
e.printStackTrace();
}
return file;
}

3.漏洞复现

1)任意文件上传复现

由于在前端没找到上传页面,这里手动构造请求包。

三个必须要传的参数, file、title、detail,响应200上传成功。

filename处写上“.jsp”后缀名。

JAVA代码审计学习心得

再用另一个queryFile接口查看是否上传成功,可看到这里已经上传成功,并且后缀是jsp。

JAVA代码审计学习心得

2)目录穿越复现

为了证明是可以目录穿越的,尝试上传到不存在的目录,上传到不存在的目录会上传失败,且会返回报错信息。

JAVA代码审计学习心得

上传到存在的目录,响应会正常返回。

JAVA代码审计学习心得

后续getshell的敏感操作请恕不能展示了,真实项目点到为止。

4.案例总结

1.通过搜索关键字:upload,write, fileName, filePath等快速寻找跟文件操作相关的漏洞点。

2.查看文件操作相关的变量是否由前端传值进来,并且可控。

3.跟踪请求传进来的变量查看是否有进行过滤处理,如:后缀名黑白名单、“../”、双写后缀名、大小写变换等。

4.根据代码所需要的请求参数构造请求包,并复现漏洞。

学习建议
P
ART.04
JAVA代码审计学习心得
最后再给点学习代码审计的建议:

1.实践多写代码、多理解开发的想法和思维;

2.多给自己一点耐心要勇于客服困难,多点耐心看代码不能心浮气躁;

3.多阅读别人的项目源代码了解别人的开发思想;

4.多动手去打断点调试代码、跟踪代码走向,多做笔记与总结。

5.自学会走好多弯路且难以坚持,建议报班,高效率高收益最重要

 

原文始发于微信公众号(Ms08067安全实验室):JAVA代码审计学习心得

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年5月9日13:55:45
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   JAVA代码审计学习心得https://cn-sec.com/archives/1719824.html

发表评论

匿名网友 填写信息