1.案例介绍
前段时间上综合项目代码分析课的时候收获了一个新思路,是关于Log4j2漏洞入口点寻找的。以前在学习Log4j2漏洞时候看到很多文章在复现时候用到error()方法去复现,当时也不懂Java代码就只能看到别人用error()方法就以为只有error一个方法能复现。
后面学了代审后才知道,原来一共是有6个打印日志的方法,分别是fatal、error、warn、info、debug、trace。那么既然知道有6个方法,当然也要去测试一下是否能复现了。
log4j2版本
测试6个方法
结果是只有error、fatal两个方法可以成功获取到dns解析记录,另外四个warn、info、debug、trace都不可以。
但是!直到了实战项目时候才发现原来并不是只有error、fatal才能解析成功,其他方法也可以但是需要一定利用条件,通常是日志文件配置不当造成。
先来看看log4j2的日志级别,log4j2支持多种日志级别,通过日志级别我们可以将日志信息进行分类,在合适的地方输出对应的日志。哪些信息需要输出,哪些信息不需要输出,只需在一个日志输出控制文件中稍加修改即可。级别由高到低共分为6个:fatal, error, warn, info, debug, trace。
当系统配置的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这里就不说了,感兴趣的可以自己去跟踪代码看一下)。
跟进isEnabled方法,这里面是通过当前配置进行过滤。
跟进this.privateConfig.filter方法,可看到295就是我们上面说到日志等级条件是否成立的问题。
this.intLevel就是当前系统的等级,level.intLevel()就是我们当前调用方法的等级。
这个this.intLevel(系统配置等级)在实际项目中的时候可能是会变的。
log4j2默认情况下系统配置等级是error,所以以前我在测试时候就只有error、fatal会成功。
在实际项目中一般开发人员都会配置log4j2的配置文件,一般都会命名为:log4j2.xml
里面的内容大概长这样,别看内容好像很多其实我们只需要留意关键字就行。
关键字:level="
<?xml version="1.0" encoding="UTF-8" ?> <appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d %-5level %class{36} %L %M - %msg%xEx%n"/> <RollingFile name="RollingFileInfo" fileName="logs/info.log" <RollingFile name="RollingFileError" fileName="${sys:user.home}/logs/error.log" <loggers> <appender-ref ref="Console"/> |
注意:
存在有appenders子元素时候以appenders的level为准。
因为代码里面还会有第二次过滤,是通过遍历appenders子元素里面的每一个级别来进行过滤的。
this.intLevel是当前调用方法的级别,level.intLevel是appenders子元素里面配置给系统的级别。
这里一样得系统级别 >= 调用方法级别才可继续往下走。
没有appenders子元素时候以root子元素为准,root元素就是我们代码第一次过滤时候。
因为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方法,成功执行。
trace方法不成立,直接没打印日志,程序也不会继续往下走jndi注入计算器也没有弹框。
需要注意的是实际项目中要是使用的springboot架构的项目,还要留意下application.properties文件,里面也会有等级的配置。
经测试application.properties文件配置的等级优先级比log4j2.xml里面的appenders子目录等级还要高。
4.案例总结
1.审计log4j2漏洞入口点时候,先看是否存在漏洞版本范围。
2.查看application.properties文件、log4j2.xml文件里面的系统配置等级筛选出能利用的日志等级方法。
3.全局代码搜索漏洞入口方法跟踪参数查看是否由请求参数传进来。
1.案例介绍
实战项目:任意文件上传+目录穿越代码审计
项目架构:SpringBoot + Mybaits
2.代码分析
1)针对文件操作类型的漏洞寻找入口点,全局搜索upload关键字找到一个跟文件上传相关的controller。
@ApiOperation(value = "上传") |
2)在file参数存在未过滤的情况,跟进NoticesService.uploadFileF(file)进一步查看是否有过滤。
@Override param.put("file", file); |
3)继续跟进FileUtils.transferToFile(wjFile)
第9行:在当前文件路径下创建一个文件,文件名是以请求参数传递过来的文件名。
注意:这里的getOriginalFilename未进行任何过滤直接拼接到文件路径上。
3.1 未进行黑白名过滤后缀,可上传任意格式后缀的文件。
3.2 未对 “../”进行过滤,可造成目录穿越,上传文件到任意目录。
10~14行:判断文件是否存在,不存在则创建文件,并把内容写进去目标文件。
/** |
3.漏洞复现
1)任意文件上传复现
由于在前端没找到上传页面,这里手动构造请求包。
三个必须要传的参数, file、title、detail,响应200上传成功。
filename处写上“.jsp”后缀名。
再用另一个queryFile接口查看是否上传成功,可看到这里已经上传成功,并且后缀是jsp。
2)目录穿越复现
为了证明是可以目录穿越的,尝试上传到不存在的目录,上传到不存在的目录会上传失败,且会返回报错信息。
上传到存在的目录,响应会正常返回。
后续getshell的敏感操作请恕不能展示了,真实项目点到为止。
4.案例总结
1.通过搜索关键字:upload,write, fileName, filePath等快速寻找跟文件操作相关的漏洞点。
2.查看文件操作相关的变量是否由前端传值进来,并且可控。
3.跟踪请求传进来的变量查看是否有进行过滤处理,如:后缀名黑白名单、“../”、双写后缀名、大小写变换等。
4.根据代码所需要的请求参数构造请求包,并复现漏洞。
1.多实践、多写代码、多理解开发的想法和思维;
2.多给自己一点耐心要勇于客服困难,多点耐心看代码,不能心浮气躁;
3.多阅读别人的项目源代码了解别人的开发思想;
4.多动手去打断点调试代码、跟踪代码走向,多做笔记与总结。
5.自学会走好多弯路且难以坚持,建议报班,高效率高收益最重要;
原文始发于微信公众号(Ms08067安全实验室):JAVA代码审计学习心得
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论