使用JSP和JavaScript进行反射型和存储型XSS简易实战

623 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的第1天

什么是XSS

跨站脚本攻击XSS(Cross Site Scripting),简单来说就是攻击者向页面里插入一段恶意代码,使得受害者一旦浏览该页面,就会触发代码执行,从而遭受攻击。

XSS分为反射型XSS、存储型XSS、DOM型XSS和突变型XSS。本文只简单介绍前两种XSS攻击,并使用JSP做简单示范。

反射型XSS

非持久化,需要欺骗用户自己去点击链接才能触发XSS代码(服务器中没有这样的页面和内容),一般容易出现在搜索页面。反射型XSS大多数是用来盗取用户的Cookie信息。

反射型XSS最简单的实现方式,就是构造一个具有参数的url,该参数的值是一段可以被执行的恶意代码。由于该值不会被存储到数据库中,因此该攻击是一次性的、非持久化的。

image.png

存储型XSS

持久化,代码是存储在服务器中的,如在个人信息或发表文章等地方,插入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,用户访问该页面的时候触发代码执行。这种XSS比较危险,容易造成蠕虫,盗窃cookie。

一般来说,留言板、注册页面等部分是遭受存储型XSS攻击的重灾区。拿前者举例,对于没有做相应防范的网站,攻击者只需在留言板中输入一段恶意代码并发布,所有查看评论区的用户均会受到攻击。由于恶意代码会被存储在数据库中,因此该攻击是持久性的。

image.png

攻击实战

简述

用Java Web搭建一个简易的用户登录、注册平台,并使用MySQL存储用户信息。用JavaScript作为攻击语言,分别进行反射型XSS攻击和存储型XSS攻击。

步骤

用HTML编写登录页面

<body>
<form action="xss1.jsp">
  账号:<input name="username"><br>
  密码:<input type="password" name="password"><br>
  <input type="submit" value="登录"><br>
  <a href="getUsersServlet">查看用户列表</a><br>
  <a href="signup.jsp">注册</a>
</form>
</body>

用form将用户输入的用户名和密码传递给xss1.jsp,并将信息保存在cookie内,注意要解决中文乱码问题

<body>
<%--<script>alert("反射型XSS攻击 by 陈艺 201913160922");alert(document.cookie)</script>--%>
<%
    // 解决中文乱码
    String username = URLEncoder.encode(request.getParameter("username"), "utf-8");
    String password = request.getParameter("password");
    // 新建cookie
    Cookie cUsername = new Cookie("username", username);
    Cookie cPassword = new Cookie("password", password);

    // 设置cookie过期时间为10分钟。
    cUsername.setMaxAge(60 * 10);
    cPassword.setMaxAge(60 * 10);

    // 添加cookie
    response.addCookie(cUsername);
    response.addCookie(cPassword);
%>
账号是:${param.username}<br>
密码是:${param.password}
</body>

如果用户在登录界面输入一串JavaScript代码并提交

image.png

那么该代码就会被浏览器运行,显示黑客预留的消息和浏览器的cookie,至此实现反射型XSS攻击。

image.png

image.png

在MySQL数据库中创建users表

image.png

用JSP编写注册界面

<body>
<form action="signupServlet">
    账号:<input name="username"><br>
    密码:<input type="password" name="password"><br>
    <input type="submit" value="注册"><br>
</form>
</body>

用Servlet编写注册服务,实现将用户信息存储到MySQL中,此处我使用了最简单的JDBC来实现对数据库的访问

@WebServlet(name = "signupServlet", value = "/signupServlet")
public class SignupServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        response.setCharacterEncoding("UTF-8");
        try {
            //加载数据库驱动,注册到去送管理器
            Class.forName("com.mysql.cj.jdbc.Driver");
            String url = "jdbc:mysql://localhost:3306/xss_test?useSSL=false";
            Connection conn = DriverManager.getConnection(url, "root", "root");

            String username = request.getParameter("username");
            String password = request.getParameter("password");

            // 插入数据
            String sql = "insert into users values(?,?)";
            PreparedStatement ps = conn.prepareStatement(sql);
            ps.setString(1, username);
            ps.setString(2, password);
            ps.execute();

            //完成后记得关闭数据库连接
            conn.close();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        // 重定向到首页
        response.sendRedirect("index.jsp");
    }
}

再用Servlet编写查询数据库服务,将查询结果显示在xss2.jsp上

@WebServlet(name = "getUsersServlet", value = "/getUsersServlet")
public class GetUsersServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        response.setCharacterEncoding("UTF-8");
        try {
            //加载数据库驱动,注册到去送管理器
            Class.forName("com.mysql.cj.jdbc.Driver");
            String url = "jdbc:mysql://localhost:3306/xss_test?useSSL=false";
            Connection conn = DriverManager.getConnection(url, "root", "root");

            String username = request.getParameter("username");
            String password = request.getParameter("password");

            // 访问数据
            String sql = "select username from users";
            PreparedStatement ps = conn.prepareStatement(sql);
            ResultSet rs = ps.executeQuery();

            List<String> users = new ArrayList<>();
            while (rs.next()) {
                users.add(rs.getString(1));
            }

            request.setAttribute("users",users); // 将users集合数据放入到request中共享

            //完成后记得关闭数据库连接
            conn.close();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        // 重定向到首页
        request.getRequestDispatcher("xss2.jsp").forward(request, response);
    }
}
<body>
<h1>用户列表</h1>
<table>
    <tr>
        <c:forEach items="${users}" var="user">
            <td>${user}</td>
        </c:forEach>
    </tr>
</table>
</body>

打开注册界面,若用户将JavaScript代码输入到用户名输入框并提交,那个该段代码就会被存储到MySQL数据库中

image.png

image.png

此时如果用户执行查询所有用户的操作,就会读取到这一段代码,显示黑客预留的消息和浏览器的cookie,至此实现了存储型XSS攻击

image.png

image.png

总结

XSS攻击实现起来简单,但是破坏力极强(尤其是存储型XSS,可以让所有访问页面的用户遭受攻击),攻击方式隐蔽。现在常用的框架已经能够帮我们抵御绝大部分XSS攻击,但是我们在搭建网站时仍然要时刻注意提防。尤其是所有具备向数据库写入的部分(如留言板)一定要仔细检查是否存在漏洞。