Lesson 16 - XSS injection
Moving onto the next challenge, injecting a script into the page.
We are going to try to get an alert message to appear:
Setup
Switching back to Serenity BDD (because Cypress has an alert-handling bug), we create a feature file for the second challenge:
Feature: Juice Shop is susceptible to XSS attacks
Scenario: Haxxor injects HTML into the search input
Given Haxxor goes to the Juice Shop
When she injects HTML into the search input
Then she sees an alert message containing "xss"
And she sees she has solved the "DOM XSS" challenge
We need new step definitions:
@When("{actor} injects HTML into the search input")
public void sheInjectsHTMLIntoTheSearchInput(Actor actor) {
actor.wasAbleTo(JuiceShop.searchFor("<iframe id='injection' src='javascript:alert(\"xss\")'>"));
}
@Then("{actor} sees an alert message containing {string}")
public void sheSeesAnAlertMessage(Actor actor, String alertMessage) {
actor.attemptsTo(JuiceShop.alertIsEqualTo(alertMessage));
}
@Then("{actor} sees she has solved the {string} challenge")
public void sheSeesSheHasSolvedTheChallenge(Actor actor, String solvedChallenge) {
actor.wasAbleTo(JuiceShop.openScoreBoard());
actor.wasAbleTo(JuiceShop.challengeIsSolved(solvedChallenge));
}
PageObjects:
// HomePage
public static Target SEARCH_BUTTON = Target.the("Search button")
.locatedBy("#searchQuery");
public static Target SEARCH_INPUT = Target.the("Search input")
.locatedBy("app-mat-search-bar input");
// ScoreBoardPage
public static Target SPINNER = Target.the("Spinner")
.locatedBy(".loading-spinner-wrapper");
public static Target SOLVED_CHALLENGE = Target.the("Solved Challenge")
.locatedBy("//challenge-card[contains(concat(' ', normalize-space(@class), ' '),
' solved ')]//*[text()='{0}']");
And helper methods:
// JuiceShop
public static Performable searchFor(String searchTerm) {
return Task.where("{0} searches for " + searchTerm,
Open.browserOn().the(HomePage.class),
Click.on(HomePage.SEARCH_BUTTON),
Enter.theValue(searchTerm).into(HomePage.SEARCH_INPUT),
SendKeys.of(Keys.ENTER).into(HomePage.SEARCH_INPUT)
);
}
public static Question<String> getAlertText() {
return Question.about("Alert text").answeredBy(
actor -> {
actor.attemptsTo(Switch.toAlert());
String acknowledgement = actor.asksFor(HtmlAlert.text());
actor.attemptsTo(Switch.toAlert().andDismiss());
return acknowledgement;
}
);
}
public static Performable alertIsEqualTo(String alertText) {
return Ensure.that("Alert message is " + alertText,
getAlertText()).isEqualTo(alertText);
}
public static Performable openScoreBoard() {
return Task.where("{0} opens their Score Board",
Open.browserOn().the(ScoreBoardPage.class),
WaitUntil.the(ScoreBoardPage.SPINNER, WebElementStateMatchers.isNotVisible())
);
}
public static Performable challengeIsSolved(String solvedChallenge) {
return Ensure.that(ScoreBoardPage.SOLVED_CHALLENGE.of(solvedChallenge)).isDisplayed();
}
Refactoring
There were a couple of things I wasn’t happy about:
- Handling cookies through the UI
- Hiding test data in the Step Definition
❗ Using the UI for cookie handling could introduce flakiness
So I added an event handler to the HomePage PageObject to set the cookies and refresh the page:
@WhenPageOpens
public void setCookies() {
if (getDriver().manage().getCookieNamed("cookieconsent_status") == null) {
getDriver().manage().addCookie(new Cookie("cookieconsent_status", "dismiss"));
getDriver().manage().addCookie(new Cookie("welcomebanner_status", "dismiss"));
getDriver().manage().addCookie(new Cookie("language", "en"));
getDriver().navigate().refresh();
}
}
❗ Test input data should be in the Scenario, where it can be reviewed more easily
I also moved the XSS value from the Step Definition to the Scenario:
Scenario: Haxxor injects HTML into the search input
Given Haxxor goes to the Juice Shop
When she searches for "<iframe src=\"javascript:alert(`xss`)\">"
Then she sees an alert message containing "xss"
And she sees she has solved the "DOM XSS" challenge
ℹ️ Note that I had to use the exact quotes that the Challenge is expecting before it would be marked as solved
@When("{actor} searches for {string}")
public void sheSearchesFor(Actor actor, String searchTerm) {
actor.wasAbleTo(JuiceShop.searchFor(searchTerm));
}