## Jeesns Question Store XSS
### Introduction to Vulnerability
JEESNS is a social management system based on JAVA enterprise-level platform. Based on the advantages of enterprise-level JAVA, such as high efficiency, security and stability, it creates a pioneering domestic Java version of open source SNS. JEESNS can be used to build portals, forums, communities, Weibo, Q&A, knowledge payment platform, etc. In jeesns <= 1.4.2, the question and answer is not completely filtered by the user's input, resulting in a stored XSS vulnerability.
### Vulnerability Impact
* Jeesns <= 1.4.2
### Vulnerability Analysis
The data submitted by the user foreground will pass XSSFilter, and the doFilter will call XssWrapper.
```java
Package com.lxinet.jeesns.core.utils;
Import java.util.regex.Matcher;
Import java.util.regex.Pattern;
Import javax.servlet.http.HttpServletRequest;
Import javax.servlet.http.HttpServletRequestWrapper;
Import org.springframework.web.util.HtmlUtils;
Public class XssWrapper extends HttpServletRequestWrapper {
Private static final String REGEX_SCRIPT = "<script[\\s\\S]*?<\\/script>";
Private static final String REGEX_STYLE = "<style[^>]*?>[\\s\\S]*?<\\/style>";
......
Public String getParameter(String parameter) {
String value = super.getParameter(parameter);
Return value == null ? null : this.cleanXSS(value);
}
Public String getHeader(String name) {
String value = super.getHeader(name);
Return value == null ? null : this.cleanXSS(value);
}
Private String cleanXSS(String value) {
Value = dealScript(value);
Value = dealStyle(value);
String[] eventKeywords = new String[]{"onmouseover", "onmouseout", "onmousedown", "onmouseup", "onmousemove", "onclick", "ondblclick", "onkeypress", "onkeydown", "onkeyup", "ondragstart", "onerrorupdate", "onhelp", "onreadystatechange", "onrowenter", "onrowexit", "onselectstart", "onload", "onunload", "onbeforeunload", "onblur", "onerror", "onfocus ", "onresize", "onscroll", "oncontextmenu", "alert"};
For(int i = 0; i < eventKeywords.length; ++i) {
Value = value.replaceAll("(?i)" + eventKeywords[i], "_" + eventKeywords[i]);
}
Return value;
}
Private static String dealScript(String val) {
Pattern p = Pattern.compile("<script[\\s\\S]*?<\\/script>");
Return htmlEscape(p, val);
}
Private static String dealStyle(String val) {
Pattern p = Pattern.compile("<style[^>]*?>[\\s\\S]*?<\\/style>");
Return htmlEscape(p, val);
}
Private static String htmlEscape(Pattern p, String val) {
String s;
String newVal;
For(Matcher m = p.matcher(val); m.find(); val = val.replace(s, newVal)) {
s = m.group();
newVal = HtmlUtils.htmlEscape(s);
}
Return val;
}
}
```
As you can see, the `<script>` and `<style>` tags are filtered, and some HTML events are escaped and filtered. However, when matching tags, there is no case processing, which can be bypassed by capitalization.
See the `save()` of the function `jeesns-service\src\main\java\com\lxinet\jeesns\service\question\impl\QuestionServiceImpl.java` where the question is saved.
```java
@Override
@Transactional
public boolean save(Question question) {
ValidUtill.checkParam(question.getMemberId() == null, "用户不能为空");
ValidUtill.checkParam(question.getTypeId() == null, "类型不能为空");
ValidUtill.checkParam(question.getTitle() == null, "标题不能为空");
ValidUtill.checkParam(question.getBonus() < 0, "悬赏金额不能小于0");
QuestionType questionType = questionTypeService.findById(question.getTypeId());
ValidUtill.checkIsNull(questionType, "问答分类不存在");
Member member = memberService.findById(question.getMemberId());
if (StringUtils.isEmpty(question.getDescription())) {
String contentStr = HtmlUtil.delHTMLTag(question.getContent());
if (contentStr.length() > 200) {
question.setDescription(contentStr.substring(0, 200));
} else {
question.setDescription(contentStr);
}
}
super.save(question);
if (question.getBonus() > 0){
if (questionType.getBonusType() == 0){
//积分
ValidUtill.checkParam(member.getScore().intValue() < question.getBonus().intValue(), "账户积分不足,无法悬赏,账户积分余额为:"+member.getScore());
memberService.updateScore(-question.getBonus(), member.getId());
ScoreDetail scoreDetail = new ScoreDetail();
scoreDetail.setType(1);
scoreDetail.setMemberId(member.getId());
scoreDetail.setForeignId(question.getId());
scoreDetail.setScore(-question.getBonus());
scoreDetail.setRemark("问答悬赏:" + question.getTitle() + "#" + question.getId());
scoreDetailService.save(scoreDetail);
}else if (questionType.getBonusType() == 1){
//现金
ValidUtill.checkParam(member.getMoney() < question.getBonus().intValue(), "账户余额不足,无法悬赏,账户余额为:"+member.getMoney());
memberService.updateMoney(-(double)question.getBonus(), member.getId());
//添加财务明细
Financial financial = new Financial();
financial.setBalance(member.getMoney() - question.getBonus());
financial.setForeignId(question.getId());
financial.setMemberId(member.getId());
financial.setMoney((double)question.getBonus());
financial.setType(1);
//1为余额支付
financial.setPaymentId(1);
financial.setRemark("问答悬赏:" + question.getTitle() + "#" + question.getId());
financial.setOperator(member.getName());
financialService.save(financial);
}
}
return true;
}
```
The data of the user condition is not further filtered, and is directly saved to the database, and then the front end directly renders, resulting in a storage type XSS.
### Vulnerability reproduction
1. Register a user
2. Post a question and fill in the XSS payload `<Script>prompt(/xss/)</Script>`.
![](https://images.seebug.org/1557817545940-w331s)
3. Trigger XSS when viewing the question.
![](https://images.seebug.org/1557817552500-w331s)
Unavailable Comments