Marc Hauschildt
Web Technologies and Computer Software Development Instructor at Kirkwood Community College in Cedar Rapids, IA.
Week 7
Cross-Site Scripting (XSS) attacks occur when a user enters JavaScript into form fields that get saved to a database.
Unescaped script strings are rendered as JavaScript when read.
Malicious users can for example, write scripts that display buttons or other content that, when clicked, redirects the user to a different website.
They can also write scripts to give the attacker access to session data or other sensitive information retained by a browser.
Run the program and enter these as the text for your first and last names:
<a href="/path/to/file.pdf download="virus.exe">hacked.com</a>
<script>let i = 0; while(i<100){open();i++;}</script>
Notice how a JavaScript alert pops up. View the source code and see how this code is actually embedded within your HTML.
Read more about XSS attacks on the OWASP website.
To protect our site and our users, we can validate input not to allow certain characters to be typed (like <
, >
, and ")
, or we can encode user input.
To prevent XSS Attacks, add the fn:escapeXML()
method inside any EL tag anywhere you are displaying content read in from user input.
For example, replace ${activeUser.firstName} with:
${fn:escapeXml(activeUser.firstName)}
Run the project again. This time, instead of <script> tags appearing in your HTML, the code will look something like this:
<script>alert("This could be a Cross-Site Scripting attack");</script>!
<
becomes <
, >
becomes >
,
&
becomes &
, and "
becomes "
Repeat on all JSPs that display data from user input.
top.jspf (flash messages)
admin-users.jsp
admin-vendors.jsp
admin-products.jsp
Write a method to convert or remove unauthorized characters from Strings before inserting or updating database records.
In the webapp folder, create a JSP called fn-test.jsp.
Add this code.
Visit the JSP directly.
<c:set var="test" value="Kirkwood Eagles"></c:set>
<div class="container">
<h2>${test}</h2>
<ul>
<li>Contains "Kirkwood": ${fn:contains(test, "Kirkwood")}</li>
<li>Contains "kirkwood" regardless of capitalization: ${fn:containsIgnoreCase(test, "kirkwood")}</li>
<li>To lowercase: ${fn:toLowerCase(test)}</li>
<li>To uppercase: ${fn:toUpperCase(test)}</li>
<li>Starts with "Kirkwood": ${fn:startsWith(fn:toLowerCase(test), "kirkwood")}</li>
<li>Ends with "Kirkwood": ${fn:endsWith(fn:toLowerCase(test), "kirkwood")}</li>
<li>Starting index of "Kirkwood": ${fn:indexOf(fn:toLowerCase(test), "kirkwood")}</li>
<li>Length: ${fn:length(test)}</li>
<li>"wood" changed to "land": ${fn:replace(fn:toLowerCase(test), "wood", "land")}</li>
<li>Starts with "K": ${fn:startsWith(fn:toLowerCase(test), "k")}</li>
<li>First 4 characters: ${fn:substring(test, 0, 4)}</li>
<li>Characters after "Kirk": ${fn:substringAfter(fn:toLowerCase(test), "kirk")}</li>
<li>Characters before "wood": ${fn:substringBefore(fn:toLowerCase(test), "wood")}</li>
<li>White space removed from both ends: ${fn:trim(test)}</li>
<c:set var="numWords" value="${fn:split(test, ' ')}" /> <!--Splits a string into an array of substrings-->
<li>Number of words: ${fn:length(numWords)}</li> <!--Then finds the length of the array.-->
<li>Words joined with a forward slash: ${fn:join(numWords, "/")}</li>
</ul>
</div>
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/delete-account")
public class DeleteAccount extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
User user = (User)session.getAttribute("activeUser");
if(user == null) {
resp.sendRedirect(resp.encodeRedirectURL(req.getContextPath() + "/login?redirect=delete-account"));
return;
}
req.setAttribute("pageTitle", "Delete Account");
req.getRequestDispatcher("WEB-INF/delete-account.jsp").forward(req, resp);
}
}
<main>
<jsp:include page="/WEB-INF/edit-profile-header.jsp"></jsp:include>
<section class="pt-0">
<div class="container">
<div class="row">
<jsp:include page="/WEB-INF/left-sidebar.jsp"></jsp:include>
<!-- Main content START -->
<div class="col-xl-9">
<!-- Title and select START -->
<div class="card border bg-transparent rounded-3 mb-0">
<!-- Card header -->
<div class="card-header bg-transparent border-bottom">
<h3 class="card-header-title mb-0">Delete Account</h3>
</div>
<!-- Card body -->
<div class="card-body">
<h6>If you delete your account, you will lose your all data.</h6>
<form method="POST" action="${appURL}/delete-account">
<!-- Email id -->
<div class="col-md-6 my-4">
<label class="form-label" for="email">Enter your email to confirm account deletion</label>
<input class="form-control <c:if test="${not empty results.emailError}">is-invalid</c:if>" type="text" id="email" name="email" value="${email}">
<c:if test="${not empty results.emailError }"><div class="invalid-feedback">${results.emailError}</div></c:if>
</div>
<button type="submit" class="btn btn-danger mb-0">Delete my account</button>
</form>
</div>
</div>
<!-- Title and select END -->
</div>
<!-- Main content END -->
</div><!-- Row END -->
</div>
</section>
</main>
CREATE PROCEDURE sp_delete_user(IN p_user_id int)
BEGIN
DELETE FROM user WHERE user_id = p_user_id;
END;
public static void delete(User user) {
try (Connection connection = getConnection()) {
if (connection != null) {
try (CallableStatement statement = connection.prepareCall("{CALL sp_delete_user(?)}")) {
statement.setInt(1, user.getId());
statement.execute();
}
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String email = req.getParameter("email");
HttpSession session = req.getSession();
User activeUser = (User)session.getAttribute("activeUser");
boolean errorFound = false;
if(!email.equals(activeUser.getEmail())) {
errorFound = true;
req.setAttribute("emailError", "The value you entered is not the same as <b>'" + activeUser.getEmail() + "'</b>.");
}
if(!errorFound) {
UserDAO.delete(activeUser);
session.invalidate();
session = req.getSession();
session.setAttribute("flashMessageWarning", "Your account has been deleted.");
resp.sendRedirect(resp.encodeRedirectURL(req.getContextPath() + "/")); // Redirects to the home page
return;
}
req.setAttribute("pageTitle", "Delete Account");
req.getRequestDispatcher("WEB-INF/delete-account.jsp").forward(req, resp);
}
I entered an incorrect, but existing email address, and your form deleted my account. Only delete it if the email address entered matches the one stored in the User session attribute.
<%@ taglib prefix="fmt" uri="jakarta.tags.fmt" %>
<fmt:formatNumber value="${product.price}" type="currency" />
public Date getOrderDateDate() {
return Date.from(orderDate);
}
<fmt:formatDate value="${order.orderDateDate}" dateStyle="full" />
Member since: <fmt:formatDate value="${activeUser.createdAtDate}" type="date" dateStyle="long" />
public Date getCreatedAtDate() {
return Date.from(createdAt);
}
<jsp:useBean id="now" class="java.util.Date" scope="page" />
Current date/time: <fmt:formatDate value="${now}" type="both" dateStyle="full" timeStyle="long" />
pattern="MMMM d, YYYY h:mm a z"
<fmt:formatNumber type="number" value="12345.6789" maxFractionDigits="1" />
<fmt:formatNumber type="percent" value="0.6789" maxFractionDigits="2" />
CREATE PROCEDURE sp_get_user_by_id(IN p_user_id int)
BEGIN
SELECT user_id, first_name, last_name, email, phone, password, language, status, privileges, created_at, timezone
FROM user
WHERE user_id = p_user_id;
END
By Marc Hauschildt
Web Technologies and Computer Software Development Instructor at Kirkwood Community College in Cedar Rapids, IA.