jira环境搭建及受限文件读取原理和深思CVE-2021-26086
一、环境搭建踩坑
坑太多了 装了差不多七八个小时才装好 也是给自己找一些经验。不过不得不说在装软件的同时也学到了非常多的东西,实战的项目感觉就是会有不一样的感觉。多动手总是会有好处的。
我先发几个步骤 然后罗列一下我猜到的一些坑的点,算是把这个环境给装好了。因为我是mac系统,参照着一个步骤来的,但是中间夹着一些另外的,使用通用的破解插件。
一、安装步骤
https://blog.csdn.net/pang787559613/article/details/101269073
https://www.jianshu.com/p/da0ddd124be8
前半部分基本按照第一个链接,后半部分按照第二个链接进行配置:
二、坑点
其实为了兼容后面的软件 mysql5.7的安装是比较好的,既可以兼容前面老版本的软件,后面的新版也会兼容这个。感觉相当于java8一样地位的存在。在这里我选取的是5.7.31 并且不是用homebrew安装的(感觉坑还挺多的)
一、mysql我在安装的过程中其实会遇到经典问题,就是第一次登录会拒绝登录,所以一般先安全模式启动,然后修改密码并flush privilege
就可以了。
二、mac上面mysql的安装默认不会生成/etc/my.cnf
的配置文件,需要自己touch一个并自己写一个默认的配置。 配置上面jira默认需要的字符集和ssl的问题。
因为emoji等表情符号的出现,更广泛的编码集需要拥抱时代的变化,所以我们尽可能的再去抛弃utf8转向utf8mb4,就像jira那样(utf8mb4是utf8的超集,理论上由utf8升级到utf8mb4字符编码没有任何兼容问题)
https://confluence.atlassian.com/adminjiraserver/connecting-jira-applications-to-mysql-5-7-966063305.html
这是字符集的解决办法 注意mysqld和client下面对应的配置别写反了。
[client]
#jira config
default-character-set = utf8mb4
default-character-set=utf8mb4
#password = your_password
port = 3306
socket = /tmp/mysql.sock
# Here follows entries for some specific programs
# The MySQL server
[mysqld]
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
default-storage-engine=INNODB
character_set_server=utf8mb4
innodb_default_row_format=DYNAMIC
innodb_large_prefix=ON
innodb_file_format=Barracuda
innodb_log_file_size=2G
skip_ssl #这里是忽略ssl安全连接的问题
这是创建jira对应数据库时 添加所需要的数据集
CREATE DATABASE Jira CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
ALTER DATABASE Jira DEFAULT CHARACTER SET = utf8mb4 DEFAULT COLLATE = utf8mb4_bin;
检查修改
mysql> SHOW VARIABLES WHERE Variable_name LIKE 'character\_set\_%' OR Variable_name LIKE 'collation%';
修复&优化所有数据表
> mysqlcheck -u root -p --auto-repair --optimize --all-databases
mac上面mysql的重启命令
> sudo /usr/local/mysql/support-files/mysql.server restart
三、之前一直按照前面的步骤使用试用版的密钥,后来我去官网上面查看,使用密钥是不需要联网的,所以不是网址被ban的问题(况且我还挂了全局代理)。之后我将问题翻译为英文,去jira的社区看看:
We're unable to confirm that Jira license
https://community.atlassian.com/t5/Jira-Software-questions/We-re-unable-to-confirm-that-Jira-license/qaq-p/1211749
https://community.atlassian.com/t5/Jira-Software-questions/why-I-have-got-unconfirm-the-JIRA-license-message-even-I-just/qaq-p/638673 时区的问题
得到的答案就是 应该不太存在这种情况,建议看看日志
You can find these in $JIRAHOME/log/atlassian-jira.log
$JIRAINSTALL/logs/catalina.out file.
然后我就去翻看了这两个日志,发现没有激活相关的错误,但是我看到了其他的错误,我之前设置了mysql不需要验证ssl,但是jira去链接mysql的时候还是建立了安全的连接。是在自己的jirahome下的dbconfig.xml,并且注意使用&连接必须要编码
<url>jdbc:mysql://address=(protocol=tcp)(host=localhost)(port=3306)/jira?useUnicode=true&useSSL=false&characterEncoding=UTF8&sessionVariables=default_storage_engine=InnoDB</url>
然后去找了一下插件的gitee,发现这个插件自带了kengen的功能,这个是插件里面的破解步骤:
https://www.cnblogs.com/sanduzxcvbnm/p/13809276.html
我输入的:
java -jar atlassian-agent.jar -d -m 76xxxxx77@qq.com -n s3gundo -p jira -o http://127.0.0.1:8080 -s BRX3-TPH5-YVOW-XXXX
之后会得到
综上,一些jira社区帮助我解决问题的url
https://community.atlassian.com/t5/Jira-Software-questions/The-database-setup-is-not-supporting-utf8mb4/qaq-p/1012877
https://community.atlassian.com/t5/Jira-Software-questions/why-I-have-got-unconfirm-the-JIRA-license-message-even-I-just/qaq-p/638673
https://community.atlassian.com/t5/Jira-Software-questions/We-re-unable-to-confirm-that-Jira-license/qaq-p/1211749
https://community.atlassian.com/t5/Jira-Software-questions/WARN-Establishing-SSL-connection-without-server-s-identity/qaq-p/1015860
https://community.atlassian.com/t5/Confluence-questions/SSL-errors-with-confluence-and-MySQL/qaq-p/578128
https://community.atlassian.com/t5/Jira-Software-questions/why-I-have-got-unconfirm-the-JIRA-license-message-even-I-just/qaq-p/638673
https://community.atlassian.com/t5/Jira-Software-questions/We-re-unable-to-confirm-that-Jira-license/qaq-p/1211749
https://community.atlassian.com/t5/Jira-Software-questions/The-database-setup-is-not-supporting-utf8mb4/qaq-p/1012877
Atlassian家族插件
https://gitee.com/pengzhile/atlassian-agent
请支持正版
二、漏洞复现调试
这里的jira home是我们之前设置过了的,然后把web-inf下面的lib添加到库就可以了,我一般是整个文件夹直接导入。
windows配置
set JAVA_OPTS=%JAVA_OPTS% -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5005
catlina.sh linux配置
CATALINA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,address=60222,suspend=n,server=y"
CVE-2021-26086 受限文件读取挖掘分析
参考文章: https://tttang.com/archive/1323/ 梅子酒师傅的
师傅的文章中可能有两处笔误。1、是对url路径的解析2、jiraloginfilter的问题
https://xz.aliyun.com/t/10444 藏青师傅的
先放poc
/s/xx/_/;/WEB-INF/web.xml
/s/xx/_/;/WEB-INF/decorators.xml
/s/xx/_/;/WEB-INF/classes/seraph-config.xml
/s/xx/_/;/META-INF/maven/com.atlassian.jira/jira-webapp-dist/pom.properties
/s/xx/_/;/META-INF/maven/com.atlassian.jira/jira-webapp-dist/pom.xml
/s/xx/_/;/META-INF/maven/com.atlassian.jira/atlassian-jira-webapp/pom.xml
/s/xx/_/;/META-INF/maven/com.atlassian.jira/atlassian-jira-webapp/pom.properties
稍加改造/s/everything/_/;anythingulike/WEB-INF/web.xml
因为burp抓不到localhost和127.0.0.1的的包,我们得先抓自己本地ip的包,但是我们之前设置jira的时候,ip是设置成localhost的,我们在burp上面的右上角把ip地址更改一下,然后host的值也改一下。就可以读取到文件了。
这里的payload我放为/s/s3gundo/_/;anythingulike/WEB-INF/web.xml
,具体的分析可以看下面。
1、 filter的初始化
复习一下filter的初始化
org.apache.catalina.Valve#invoke ->StandardWrapperValve.invoke
StandardWrapperValve ->> + ApplicationFilterFactory : 1、createFilterChain()创建FilterChain
ApplicationFilterFactory ->> ApplicationFilterFactory : 1.1、创建FilterChain并初始化(servlet设置到FilterChain当中)从配置文件中读取的filtermap并匹配查找后返回
ApplicationFilterFactory -->> - StandardWrapperValve : 1.2、返回FilterChain对象
filterchain的初始化,跟进ApplicationFilterFactory.createFilterChain
方法,可以看到从wrapper中获取的http请求方法和路径,并将filtermap中匹配得到的路径与请求方法,加入到filterChain中
可以看到urlpattern是/*是肯定会被匹配上的。org.apache.catalina.core.ApplicationFilterChain#doFilter
匹配得到这些filters
可以看到序号九,第十个filter就是后面的重点。
2、 Jira的正常访问/WEB-INF/受限
可以看到org.apache.catalina.core.StandardContextValve#invoke
方法中,
在这里,应该是会将访问路径中的;
进行忽略处理,比如对于路径/s/s3gundo/_/;anythingulike/WEB-INF/web.xml
将会首先取;
前的/s/s3gundo/_/
,再取/
后的/WEB-INF/web.xml
,最后将两者进行拼接得到:/s/s3gundo/_//WEB-INF/web.xml
。因为这里传入的时候对url做了转发处理,所以将前面的/s/s3gundo
给删去了,得到/;anythinulike/WEB-INF/web.xml
,后面会讲到。
返回的值是//WEB-INF/web.xml
public static String normalize(String path, boolean replaceBackSlash) { //这里传入的时候是为true
if (path == null) {
return null;
} else {
String normalized = path;
if (replaceBackSlash && path.indexOf(92) >= 0) {
normalized = path.replace('\\', '/'); //存在反斜杠就替换为斜杠
}
if (!normalized.startsWith("/")) {
normalized = "/" + normalized;
}
boolean addedTrailingSlash = false;
if (normalized.endsWith("/.") || normalized.endsWith("/..")) {
normalized = normalized + "/";
addedTrailingSlash = true;
}
while(true) {
int index = normalized.indexOf("//");
if (index < 0) {
while(true) {
index = normalized.indexOf("/./");
if (index < 0) {
while(true) {
index = normalized.indexOf("/../");
if (index < 0) {
if (normalized.length() > 1 && addedTrailingSlash) {
normalized = normalized.substring(0, normalized.length() - 1);
}
return normalized;
}
if (index == 0) {
return null;
}
int index2 = normalized.lastIndexOf(47, index - 1);
normalized = normalized.substring(0, index2) + normalized.substring(index + 3);
}
}
normalized = normalized.substring(0, index) + normalized.substring(index + 2);
}
}
normalized = normalized.substring(0, index) + normalized.substring(index + 1); //把双斜杠替换为单斜杠
}
}
}
}
最后经过normlize
的返回是/WEB-INF/web.xml
3、 UrlRewriteFilter
这块主要分为两大部分,一是org.tuckey.web.filters.urlrewrite.RuleChain#process
,二是org.tuckey.web.filters.urlrewrite.RuleChain#handleRewrite
。逐个攻破
先是process
方法:
关键在org.tuckey.web.filters.urlrewrite.ClassRule#matches(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
方法中箭头所指向的反射方法,matchstr默认为matches,然后得到matchesMethod的方法为public org.tuckey.web.filters.urlrewrite.extend.RewriteMatch com.atlassian.jira.plugin.webresource.CachingResourceDownloadRewriteRule.matches(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
,再将所需的参数传入。
先来来看看匹配的模式
^/s/(.*)/_/((?i)(?!WEB-INF)(?!META-INF).*)
前面的(?i)
表示是一种模式修饰符,i即匹配时不区分大小写。以前只见过放在最后面的。
后面的(?!)
表示在那串字符串后面的不能是以web-inf
和meta-inf
结尾的。
至此,调用的堆栈是:
matches:53, CachingResourceDownloadRewriteRule (com.atlassian.jira.plugin.webresource)
invoke:-1, GeneratedMethodAccessor308 (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
matches:119, ClassRule (org.tuckey.web.filters.urlrewrite)
matches:101, ClassRule (org.tuckey.web.filters.urlrewrite)
doRuleProcessing:83, RuleChain (org.tuckey.web.filters.urlrewrite)
process:137, RuleChain (org.tuckey.web.filters.urlrewrite) //上班部分process的
doRules:144, RuleChain (org.tuckey.web.filters.urlrewrite)
processRequest:92, UrlRewriter (org.tuckey.web.filters.urlrewrite)
doFilter:394, UrlRewriteFilter (org.tuckey.web.filters.urlrewrite)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core) [10]
doFilter:30, CorrelationIdPopulatorFilter (com.atlassian.jira.servermetrics)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
...(省略)
doFilterInternal:115, GzipFilter (com.atlassian.gzipfilter)
doFilter:92, GzipFilter (com.atlassian.gzipfilter)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core) [1]
invoke:199, StandardWrapperValve (org.apache.catalina.core)
invoke:96, StandardContextValve (org.apache.catalina.core)
invoke:493, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:206, StuckThreadDetectionValve (org.apache.catalina.valves)
invoke:137, StandardHostValve (org.apache.catalina.core)
invoke:81, ErrorReportValve (org.apache.catalina.valves)
invoke:87, StandardEngineValve (org.apache.catalina.core)
invoke:660, AbstractAccessLogValve (org.apache.catalina.valves)
service:343, CoyoteAdapter (org.apache.catalina.connector)
service:798, Http11Processor (org.apache.coyote.http11)
process:66, AbstractProcessorLight (org.apache.coyote)
process:808, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1498, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)
后面有的dontProcessAnyMoreRules把ruleIdxToRun赋值为rule.size(),才后面就会跳出判断,不再进行匹配。
至此,process
方法结束,接下来是handleRewrite
,这部分主要是请求转发
简单了解一下请求转发的作用域:
访问受保护目录下的资源
requestDispatcher:是服务器的资源封装器,可以封装服务器内部所有资源。
(包括WEB-INF下资源)
WEB-INF是受保护目录,不能够通过浏览器直接访问
可以通过请求转发去访问
可以看到10-11之间的调用堆栈,这里具体是对请求进行了一次转发。
于是接下来对请求直接进行了dofilter的操作,从而没有经过org.apache.catalina.core.StandardContextValve#invoke
,个人认为请求转发作用域延伸到受保护目录下的资源也是因为如此。
这也导致了第二次访问是由defaultServlet对资源进行的请求,也可以看到这里面filterconfig里面仍然是存在JiraLoginFilter的,因为在web.xml中就已经配置全路径了
4、 JiraLoginFilter放行
看dofilter方法中第一行,这里是函数式接口,能够获取到SessionInvalidator并且存在的话,将这个值符给jiraUserSessionInvalidator这个参数,并执行handleSessionInvalidation方法。这里获取到存在的变量是jiraUserSessionTracker
,所以后面执行的方法是com.atlassian.jira.web.session.currentusers.JiraUserSessionInvalidator#handleSessionInvalidation
此处session是为空的,因为我们还没有登录,执行到finally块,判断完其实这里什么都没做。
接下来走到选择filter过滤器再进行doFilter的方法,因为这俩参数都没传,所以会传seraphHttpAuthFilter参数回去,执行他的dofilter方法。
走到HttpAuthFilter父类的方法
看到status为空,所以两个return的块我们也进不去,所以走到最后一行代码继续放行。不做未认证的跳转也返回值,所以最终会交到DefaultServlet的手上。
局限
传入的解析完之后的参数是/WEB-INF/web.xml,局限也就是在于下面部分,会再次去资源进行一个normalize的处理,导致不能跨越web路径进行一个资源的读取,只能在web的路径之下。
file会将web目录的路径和我们请求的绝对路径进行拼接
之后再进行一次normlize的方法,在后面的getResource方法中和web路径进行拼接的时候,也就达不到跨越web路径的目的。
修复
对正则进行了删改
Pattern PATHS_DENIED = Pattern.compile("[^a-zA-Z0-9]((?i)(WEB-INF)|(META-INF))[^a-zA-Z0-9]")
也就是 WEB-INF
或者 META-INF
的前后有特殊字符,则返回 null;
三、总结
1、在渗透测试的过程中,有些waf会拦截;/
等关键词,在中间填充字符串可绕过某些特征。
2、做请求转发的操作时,一定要再对url进行过滤的操作,防止读取到敏感文件(尤其是做动态链接)
文笔很烂,如有错误,请多多指教。