为什么会有这一些列的文章呢?因为我发现网上没有成系列的文章或者教程,基本上是 Java 代码审计中某个点来阐述的,对于新人来说可能不是那么友好,加上本人也在学习 Java 审计,想做个学习历程的记录和总结,因此有了本系列的文章。
本系列的文章面向人群主要是拥有 Java 基本语法基础的朋友,系列文章的内容主要包括,审计环境介绍、SQL 漏洞原理与实际案例介绍、XSS 漏洞原理与实际案例介绍、SSRF 漏洞原理与实际案例介绍、RCE 漏洞原理与实际案例介绍、包含漏洞原理与实际案例介绍、序列化漏洞原理与实际案例介绍、S2系列经典漏洞分析、WebLogic 系列经典漏洞分析、fastjson系列经典漏洞分析、jackson系列经典漏洞分析等,可能内容顺序会略有调整,但是总体内容不会改变,最后希望这系列的文章能够给你带来一点收获。
首先创建一个数据库sec_xss
create database sec_xss charset utf8;
然后创建表message和插入数据:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for message
-- ----------------------------
DROP TABLE IF EXISTS `message`;
CREATE TABLE `message` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`mail` varchar(255) DEFAULT NULL,
`message` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of message
-- ----------------------------
BEGIN;
INSERT INTO `message` VALUES (1, 'panda', 'panda@cnpanda.net', '这是一个测试储存型 XSS 的项目');
INSERT INTO `message` VALUES (2, 'test', 'test@test.com', '测试数据 2。测试功能是否正确');
INSERT INTO `message` VALUES (3, 'test_last', 'last@cnpanda.net', '最后一次测试,测试无误,则完成');
INSERT INTO `message` VALUES (4, '熊猫', 'admin@cnpanda.net', '你好!这里有一个新的短消息请注意查收!');
INSERT INTO `message` VALUES (5, 'lalala', 'lalala@qq.com', '啦啦啦啦啦啦啦啦绿绿\r\n啦啦啦啦啦啦啦啦绿绿');
INSERT INTO `message` VALUES (6, 'xss', 'xss@xss.xss', ' \' test');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
下载 xss 测试源码:
https://github.com/cn-panda/JavaCodeAudit
导入项目,可以得到以下目录
修改连接数据库的账号密码:
MessageInfoDaoImpl.java 23 行:
MessageInfoDaoImpl.java 69 行:
项目是一个简单的留言板功能的实现,在 servlet 层接受到请求后,调MessageInfoServiceImpl,UserInfoServiceImpl在调用MessageInfoDaoImpl,MessageInfoDaoImpl去操作数据库,进行插入和查询操作,然后封装 MessageInfo为数组对象,再把MessageInfo 对象返回给MessageInfoService,最后 service 层再返回给 servlet 层,最终把查询的内容显示到show页面。
XSS 是通过对网页插入可执行代码且成功地被浏览器 执行,达到攻击的目的,一般来说 XSS 的危害性没有 SQL 大,但是一次有效的 XSS 攻击可以做很多事情,比如获取 Cookies、获取用户的联系人列表、截屏、劫持等等。根据服务端的后端代码不同,XSS 的种类也不相同,一般可以分为反射型、存储型以及和反射型相近的 DOM 型。
拿上方下载的代码举例,在com.sec.servlet包下的 InfoServlet.java文件中,关键代码如下:
public void Message(HttpServletRequest req, HttpServletResponse resp) {
// TODO Auto-generated method stub
String message = req.getParameter("msg");
try {
resp.getWriter().print(message);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
获取 msg字段,然后直接打印出来。这里就可以很直白的理解反射型 XSS 了,把你输入的东西给”原封不动“的返回给你,这里的原封不动当然是加引号的,如果在输入的内容中,插入了浏览器可以执行的 js 代码,那么就会导致这种反射型的 XSS。
如下图所示,是上述代码的功能界面:
当我们输入正常字符的时候,返回我们刚才输入的字符:
但是如果我们输入的内容中含有可执行代码,如:<script>alert('xss')</script>
浏览器就会执行这段 js代码,所以我们只要控制输入的内容,就可以达到攻击效果。
同样拿上方下载的代码举例,在com.sec.servlet包下的 ShowServlet.java文件中,关键代码如下:
public void ShowMessage(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
MessageInfoService msginfo = new MessageInfoServiceImpl();
List<MessageInfo> msg = msginfo.MessageInfoShowService();
if( msg != null){
req.setAttribute("msg", msg);
req.getRequestDispatcher("/message.jsp").forward(req, resp);
return ;
}
}
其中,MessageInfoShowService主要是用于实例化MessageInfoDaoImpl(),然后调用MessageInfoShowDao()类,该类内容如下:
try {
....
String sql = "select * from message";
ps = conns.prepareStatement(sql);
rs = ps.executeQuery();
messageinfo = new ArrayList<MessageInfo>();
while(rs.next()){
MessageInfo msg = new MessageInfo();
msg.setName(rs.getString("name"));
msg.setMail(rs.getString("mail"));
msg.setMessage(rs.getString("message"));
messageinfo.add(msg);
}
....
return messageinfo;
}
}
主要执行的是从message表中查询所有数据,然后将 name、mail、message 的值加到 messageinfo List 中,最后返回给 servlet 层。
这段代码中有地址转发,在message.jsp中存在以下内容:
<%
List<MessageInfo> msginfo = (ArrayList<MessageInfo>)request.getAttribute("msg");
for(MessageInfo m:msginfo){
%>
<table>
<tr><td class="klytd"> 留言人:</td>
<td class ="hvttd"> <%=m.getName() %></td>
</tr>
<tr><td class="klytd"> e-mail:</td><td class ="hvttd"> <%=m.getMail() %></td>
</tr>
<tr><td class="klytd"> 内容:</td><td class ="hvttd"> <%=m.getMessage() %></td></tr>
</table> <% } %>
</div>
从 messageinfo List 中取出 name、mail、message 的值,并输出在该页面上。
这样一来整个流程就很清楚了,从 message 表中取数据--> 取出的数据输出到页面上
那么这里就存在一个问题,如果储存的数据有问题,存在可执行代码,那么输出到页面上的内容就会引起xss 漏洞。
继续看代码,找可以控制输入点的地方,在com.sec.servlet包下的 StoreServlet.java文件中,关键代码如下:
public void StoreXss(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
String name = req.getParameter("name");
String mail = req.getParameter("mail");
String message = req.getParameter("message");
if(!name.equals(null) && !mail.equals(null) && !message.equals(null)){
MessageInfoService msginfo = new MessageInfoServiceImpl();
msginfo.MessageInfoStoreService(name, mail, message);
resp.getWriter().print("<script>alert(\"添加成功\")</script>");
resp.getWriter().flush();
resp.getWriter().close();
}
}
获取 name、mail、message 参数,然后传入到MessageInfoStoreService()类中,该类的主要作用是调用MessageInfoStoreDao()类,该类的关键内容如下:
try {
boolean result = false;
....
String sql = "INSERT INTO message (name,mail,message) VALUES (?,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1, name);
ps.setString(2, mail);
ps.setString(3, message);
ps.execute();
result = true;
....
return result;
}
主要执行的是向数据库中插入数据的功能,数据插入之前虽然进行了预编译,但是没有进行特殊字符过滤处理,这样结合前文中提到的——直接输出从message 表中拿出的数据,导致了储存型 XSS 漏洞。
如下图,我们提交含有可执行代码的数据:
然后在输出页面查看:
成功执行 XSS,且点击确定后,返回原本页面,再次刷新
依旧会执行插入数据中的 XSS 可执行代码,这也是和反射型 XSS 最大的区别。
对于 XSS 漏洞,导致其产生的根本原因是对于输入和输出功能的过滤不完善,因此可以采用过滤的方法来防御 XSS 漏洞,大致方向有以下几种:
保留语意,将输入的特殊字符转译存储到数据库,缺点是可能会对数据库或文件系统产生一些不必要的垃圾信息
过滤掉特殊字符,只保留正常数据,缺点是有些时候用户需要输入特殊字符,不能保证数据原始性
输入限制,含有特殊字符的数据不能够输入
以上都可以自行进行特殊处理,这里只提供些思路,怎么处理可以根据需求选择
这里提供几个具体的处理方式。
说全局过滤器前需要说明一下 web.xml这个配置文件的作用。 web.xml 是java web 项目的一个重要的配置文件,但是web.xml文件并不是Java web工程必须的,web.xml文件的主要作用用来配置:欢迎页、servlet、filter等。但是当web工程中没用到这些时,可以不用web.xml文件来配置web工程。
做全局过滤器需要要用到 filter,因此首先要做的是来配置web.xml文件,添加内容如下:
<filter>
<filter-name>XssSafe</filter-name>
<filter-class>XssFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>XssSafe</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
这里要注意的是,我们的配置是/*而不是/,< url-pattern>/</url-pattern> 会匹配到/login这样的路径型url,不会匹配到模式为*.jsp这样的后缀型url,而< url-pattern>/*</url-pattern> 会匹配所有url:路径型的和后缀型的url(包括/login,*.jsp,*.js和*.html等)。
然后编写过滤器的内容就行了,这个网上有写好的,可以直接拿来用,如下:
//XssFilter实现:
public class XssFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response);
}
}
//XssHttpServletRequestWrapper实现
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@SuppressWarnings("rawtypes")
public Map<String,String[]> getParameterMap(){
Map<String,String[]> request_map = super.getParameterMap();
Iterator iterator = request_map.entrySet().iterator();
while(iterator.hasNext()){
Map.Entry me = (Map.Entry)iterator.next();
String[] values = (String[])me.getValue();
for(int i = 0 ; i < values.length ; i++){
values[i] = xssClean(values[i]);
}
}
return request_map;
}
public String[] getParameterValues(String paramString)
{
String[] arrayOfString1 = super.getParameterValues(paramString);
if (arrayOfString1 == null)
return null;
int i = arrayOfString1.length;
String[] arrayOfString2 = new String[i];
for (int j = 0; j < i; j++){
arrayOfString2[j] = xssClean(arrayOfString1[j]);
}
return arrayOfString2;
}
public String getParameter(String paramString) {
String str = super.getParameter(paramString);
if (str == null)
return null;
return xssClean(str);
}
public String getHeader(String paramString) {
String str = super.getHeader(paramString);
if (str == null)
return null;
str = str.replaceAll("\r|\n", "");
return xssClean(str);
}
private String xssClean(String value) {
//ClassLoaderUtils.getResourceAsStream("classpath:antisamy-slashdot.xml", XssHttpServletRequestWrapper.class)
if (value != null) {
// NOTE: It's highly recommended to use the ESAPI library and
// uncomment the following line to
// avoid encoded attacks.
// value = encoder.canonicalize(value);
value = value.replaceAll("\0", "");
// Avoid anything between script tags
Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>",
Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid anything in a src='...' type of expression
scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
| Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid anything in a href='...' type of expression
scriptPattern = Pattern.compile("href[\r\n]*=[\r\n]*\\\"(.*?)\\\"",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
| Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
// Remove any lonesome </script> tag
scriptPattern = Pattern.compile("</script>",
Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Remove any lonesome <script ...> tag
scriptPattern = Pattern.compile("<script(.*?)>",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
| Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid eval(...) expressions
scriptPattern = Pattern.compile("eval\\((.*?)\\)",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
| Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid expression(...) expressions
scriptPattern = Pattern.compile("expression\\((.*?)\\)",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
| Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid javascript:... expressions
scriptPattern = Pattern.compile("javascript:",
Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid vbscript:... expressions
scriptPattern = Pattern.compile("vbscript:",
Pattern.CASE_INSENSITIVE);
value = scriptPattern.matcher(value).replaceAll("");
// Avoid onload= expressions
scriptPattern = Pattern.compile("onload(.*?)=",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE
| Pattern.DOTALL);
value = scriptPattern.matcher(value).replaceAll("");
}
return value;
}
}
这是谷歌提供的一个用于过滤来自用户输入字段的XSS攻击的Java库
https://code.google.com/archive/p/xssprotect/
项目中需要引入 xssProtect-0.1.jar、antlr-3.0.1.jar、antlr-runtime-3.0.1.jar 等3个 jar 包
简单用法如下:
protectedAgainstXSS(String html){StringReader reader = new StringReader(html); StringWriter writer = new StringWriter();
try {
// 从“ html”变量解析传入的字符串
HTMLParser.process( reader, writer, new XSSFilter(), true );
// 返回经过解析和处理的字符串
return writer.toString();
} catch (HandlingException e) {
}
}
具体的使用方式可以参考:https://www.iteye.com/blog/liuzidong-1744023
下载地址:
https://code.google.com/archive/p/xssprotect/downloads
https://github.com/kennylee26/xssprotect
在这个包中有个StringUtils 类,该类主要提供对字符串的操作,对null是安全的,主要提供了字符串查找、替换、分割、去空白、去掉非法字符等等操作。存在三个函数可以供我们过滤使用。
如:
会把
"bread" & "butter"
变成:
"bread" & "butter"
使用JavaScript字符串规则转义字符串中的字符。
如:
会把
input string: He didn't say, "Stop!"
变成:
output string: He didn\'t say, \"Stop!\"
更多的方法和效果可以参考:
CVE 地址:https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-19178
JEESNS是一款基于JAVA企业级平台研发的社交管理系统,在JEESNS 1.3中,com/lxinet/jeesns/core/utils/XssHttpServletRequestWrapper.java 允许通过html 中的<embed> 标签插入XSS攻击代码。
去官网下载 v1.3版本的 OFCMS,打开 idea,点击import project,选择import project from external model中的Maven,然后一路默认即可(具体过程在系列 文章 02 中有说明,可以查看)。
导入后软件会自动下载需要的 jar 包:
等待几分钟后即可下载完毕。然后在本地创建数据库:
create database jeesns charset utf8mb4;
选择数据库后导入SQL 文件:
source /Users/panda/Downloads/jeesns-master_v1.3/jeesns-web/database/jeesns.sql
然后在jeesns-web/src/main/resources/jeesns.propertis文件中修改数据库的账号密码
需要注意的是,由于每个人的数据库版本不同,因此需要修改对应的 msyql connet jar包,否则会出现类似于下面的错误:
Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: Connections could not be acquired from the underlying database!
需要在jeesns-web/pom.xml文件添加以下内容:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
PS:注意这段代码添加的位置、自己机器上的数据库版本
如下图所示:
修改好保存后,配置 tomcat 服务,点击 run-->edit configurations,配置如下:
server 选项默认即可:
Deployment 选项中,导入 war 包,点击+号,选择Artifact...,然后选择第一个 war:
点击 OK 后可以修改路径:
点击应用后,即可运行本项目,如下图所示:
站点地址:http://localhost:8080/jeesns/
后台地址:http://localhost:8080/jeesns/manage/
管理员账号:admin
管理员密码:jeesns
该漏洞存在的文件位置为:jeesns-core/src/main/java/com.lxinet.jeesns/core/utils/XssHttpServletRequestWrapper.java
关键内容如下:
/**
* XSS攻击处理
* Created by zchuanzhao on 2017/3/23.
*/
public String getParameter(String parameter) {
String value = super.getParameter(parameter);
if (value == null) {
return null;
}
return cleanXSS(value);
}
......
private String cleanXSS(String value) {
//first checkpoint
//(?i)忽略大小写
value = value.replaceAll("(?i)<style>", "<style>").replaceAll("(?i)</style>", "</style>");
value = value.replaceAll("(?i)<script>", "<script>").replaceAll("(?i)</script>", "</script>");
value = value.replaceAll("(?i)<script", "<script");
value = value.replaceAll("(?i)eval\\((.*)\\)", "");
value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
//second checkpoint
// 需要过滤的脚本事件关键字
String[] eventKeywords = { "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(eventKeywords[i],"_" + eventKeywords[i]);
}
return value;
}
}
同时该过滤也写到了过滤器中:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response);
}
在WeiboController.java文件中,有以下关键代码:
@RequestMapping(value="/comment/{weiboId}",method = RequestMethod.POST)
@ResponseBody
public ResultModel comment(@PathVariable("weiboId") Integer weiboId, String content, Integer weiboCommentId){
Member loginMember = MemberUtil.getLoginMember(request);
ValidUtill.checkLogin(loginMember);
return new ResultModel(weiboCommentService.save(loginMember,content,weiboId,weiboCommentId));
}
sava 函数关键内容如下:
public boolean save(HttpServletRequest request, Member loginMember, String content, String pictures) {
if("0".equals(request.getServletContext().getAttribute(ConfigUtil.WEIBO_POST.toUpperCase()))){
throw new OpeErrorException("微博已关闭");
}
ValidUtill.checkIsNull(content, Messages.CONTENT_NOT_EMPTY);
if(content.length() > Integer.parseInt((String) request.getServletContext().getAttribute(ConfigUtil.WEIBO_POST_MAXCONTENT.toUpperCase()))){
throw new ParamException("内容不能超过"+request.getServletContext().getAttribute(ConfigUtil.WEIBO_POST_MAXCONTENT.toUpperCase())+"字");
}
....
Weibo weibo = new Weibo();
weibo.setMemberId(loginMember.getId());
weibo.setContent(content);
weibo.setStatus(1);
....
return result == 1;
}
通过request获取到 content 的值后,调用 save 函数保存,而request 使用了XssHttpServletRequestWrapper过滤器,通过cleanXSS 去过滤传入进来的参数,综上,其实最关键的地方就在XssHttpServletRequestWrapper.java 文件,如果绕过了 cleanxss函数,那么就可以进行 XSS 攻击。
下面来仔细看看这个函数。
首先对传入的参数进行相关的字符处理,然后采用标签黑名单的方式过滤关键字,可以看到过滤了我们经常使用的alert、onerror等函数。这种方式不是绝对安全的方式,很容易绕过,比如:
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=">
还比如:
<svg/onLoad=confirm(1)>
<img src="x" ONERROR=confirm(0)>
官网更新到了v1.4的版本,但是取消了开源。因此没法看官网怎么修复的,这里给出自己的修复意见。
1、对于黑名单过滤的方式,从长远的角度来看,是不可取的,因为标签太多,可能利用的标签也很多,一旦过滤不全,就导致功夫白费
2、使用上文中提到的修复方式,包括全局过滤器、xssProtect以及相关的 commons.lang包中的过滤函数等。
本文主要讨论了 Java 中的 XSS 漏洞,包括其原理、简单的 Java 代码示例、修复方案以及 CVE 实例,希望对初入Java代码审计的朋友有所帮助。
https://www.cnblogs.com/mumu122GIS/p/10161725.html
https://www.cnblogs.com/shawWey/p/8480452.html
https://www.iteye.com/blog/liuzidong-1744023
https://code.google.com/archive/p/xssprotect/wikis/HowTouse.wiki
https://www.cnblogs.com/soundcode/p/6595760.html
https://commons.apache.org/proper/commons-lang/
https://github.com/zchuanzhao/jeesns/tree/master\_v1.3
http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-19178