Skip to content
This repository was archived by the owner on May 5, 2021. It is now read-only.

Commit deb79bc

Browse files
[IMIS] Use Jsoup instead of StringEscapeUtils to sanitize inputs and prevent XSS attacks SORMAS-Foundation#3389 (SORMAS-Foundation#3448)
* replace stringescapeutils with jsoup SORMAS-Foundation#3389 * Replace escapeHtml4 with jsoup.clean SORMAS-Foundation#3389 * Introduce whitelist constant for actions (SORMAS-Foundation#3389) * modified escaping to be more accurate SORMAS-Foundation#3389 * Move Jsoup.clean to helper method HtmlHelper.cleanHtml SORMAS-Foundation#3389 * make null check inline SORMAS-Foundation#3389 * Merge branch 'development' into feature-3389-jsoup-xss-prevention Co-authored-by: syntakker <[email protected]>
1 parent b18a912 commit deb79bc

17 files changed

Lines changed: 84 additions & 75 deletions

File tree

sormas-api/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@
6767
<artifactId>simmetrics-core</artifactId>
6868
</dependency>
6969

70+
<dependency>
71+
<groupId>org.jsoup</groupId>
72+
<artifactId>jsoup</artifactId>
73+
</dependency>
74+
7075
</dependencies>
7176

7277
</project>

sormas-api/src/main/java/de/symeda/sormas/api/caze/classification/ClassificationHtmlRenderer.java

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,11 @@
1717
*******************************************************************************/
1818
package de.symeda.sormas.api.caze.classification;
1919

20-
import static de.symeda.sormas.api.utils.HtmlHelper.escapeAndUnescapeBasicTags;
21-
import static de.symeda.sormas.api.utils.HtmlHelper.unescapeBasicTags;
22-
2320
import java.util.Date;
2421
import java.util.List;
2522

2623
import org.apache.commons.lang3.StringUtils;
27-
import org.apache.commons.text.StringEscapeUtils;
24+
import org.jsoup.safety.Whitelist;
2825

2926
import de.symeda.sormas.api.Disease;
3027
import de.symeda.sormas.api.FacadeProvider;
@@ -34,6 +31,7 @@
3431
import de.symeda.sormas.api.i18n.Strings;
3532
import de.symeda.sormas.api.utils.DataHelper;
3633
import de.symeda.sormas.api.utils.DateHelper;
34+
import de.symeda.sormas.api.utils.HtmlHelper;
3735
import de.symeda.sormas.api.utils.InfoProvider;
3836

3937
/**
@@ -176,7 +174,7 @@ public static String createHtmlForDownload(String sormasServerUrl, List<Disease>
176174
html.append("<h1 style=\"text-align: center; color: #005A9C;\">").append(I18nProperties.getString(Strings.classificationClassificationRules)).append("</h1>");
177175
html.append("<h4 style=\"text-align: center;\">")
178176
.append(I18nProperties.getString(Strings.classificationGeneratedFor))
179-
.append(" ").append(StringEscapeUtils.escapeHtml4(InfoProvider.get().getVersion()))
177+
.append(" ").append(HtmlHelper.cleanHtml(InfoProvider.get().getVersion()))
180178
.append(StringUtils.wrap(I18nProperties.getString(Strings.on), " "))
181179
.append(sormasServerUrl).append(StringUtils.wrap(I18nProperties.getString(Strings.at), " "))
182180
.append(DateHelper.formatLocalDateTime(new Date(), language)).append("</h4>");
@@ -241,7 +239,7 @@ private static String buildSubCriteriaDiv(
241239
for (ClassificationCriteriaDto subCriteria : ((ClassificationCollectiveCriteria) criteria).getSubCriteria()) {
242240
if (!(subCriteria instanceof ClassificationCollectiveCriteria) || subCriteria instanceof ClassificationCompactCriteria) {
243241
// For non-collective or compact collective criteria, add the description as a list item
244-
subCriteriaSb.append("- " + escapeAndUnescapeBasicTags(subCriteria.buildDescription() + "</br>"));
242+
subCriteriaSb.append("- " + HtmlHelper.cleanHtml(subCriteria.buildDescription(), Whitelist.basic()) + "</br>");
245243
} else if (subCriteria instanceof ClassificationCollectiveCriteria
246244
&& !(subCriteria instanceof ClassificationAllOfCriteriaDto)
247245
&& !(subCriteria.getClass() == ClassificationXOfCriteriaDto.class)) {
@@ -273,7 +271,7 @@ private static String createSurroundingDiv(ClassificationCriteriaType criteriaTy
273271
//@formatter:off
274272
return "<div class='classification-rules'>"
275273
+ "<div class='main-criteria main-criteria-"
276-
+ StringEscapeUtils.escapeHtml4(criteriaType.toString())
274+
+ HtmlHelper.cleanHtml(criteriaType.toString())
277275
+ "'>"
278276
+ content
279277
+ "</div></div>";
@@ -287,7 +285,7 @@ private static String createHeadlineDiv(String headline) {
287285

288286
//@formatter:off
289287
return "<div class='headline'>"
290-
+ StringEscapeUtils.escapeHtml4(headline)
288+
+ HtmlHelper.cleanHtml(headline, Whitelist.basic())
291289
+ "</div>";
292290
//@formatter:on
293291
}
@@ -296,13 +294,12 @@ private static String createHeadlineDiv(String headline) {
296294
* Creates a div containing an info text.
297295
*/
298296
private static String createInfoDiv() {
299-
return unescapeBasicTags(StringEscapeUtils.escapeHtml4(I18nProperties.getString(Strings.classificationInfoText)));
297+
return HtmlHelper.cleanI18nString(I18nProperties.getString(Strings.classificationInfoText));
300298
}
301299

302300
private static String createInfoDiv(int requirementsNumber) {
303-
return unescapeBasicTags(
304-
StringEscapeUtils.escapeHtml4(
305-
String.format(I18nProperties.getString(Strings.classificationInfoNumberText), DataHelper.parseNumberToString(requirementsNumber))));
301+
return HtmlHelper.cleanI18nString(
302+
String.format(I18nProperties.getString(Strings.classificationInfoNumberText), DataHelper.parseNumberToString(requirementsNumber)));
306303
}
307304

308305
/**
@@ -334,7 +331,7 @@ private static String createSubCriteriaSurroundingDiv(String content) {
334331
* Specific tags are allowed to be contained in i18n strings and are thus unescaped
335332
*/
336333
private static String createCriteriaItemDiv(String text) {
337-
return unescapeBasicTags(StringEscapeUtils.escapeHtml4(text) + "<br>");
334+
return (HtmlHelper.cleanHtml(text, Whitelist.basic()) + "<br>");
338335
}
339336

340337
private enum ClassificationCriteriaType {
Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,45 @@
1+
/*******************************************************************************
2+
* SORMAS® - Surveillance Outbreak Response Management & Analysis System
3+
* Copyright © 2016-2018 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI)
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*******************************************************************************/
118
package de.symeda.sormas.api.utils;
219

3-
import org.apache.commons.text.StringEscapeUtils;
20+
import org.jsoup.Jsoup;
21+
import org.jsoup.safety.Whitelist;
422

23+
// This class provides general XSS-Prevention methods using Jsoup.clean
524
public class HtmlHelper {
625

7-
// unescape specific tags (<b>,<i>,<br>,<li>,<ul>) escaped using StringEscapeUtils.escapeHtml4(String)
8-
public static String unescapeBasicTags(String escapedString) {
9-
String res = escapedString;
26+
public static final Whitelist EventActionWhitelist =
27+
Whitelist.relaxed().addTags("hr", "font").addAttributes("font", "size", "face", "color").addAttributes("div", "align");
1028

11-
// <b> Tags
12-
res = res.replaceAll("&lt;b&gt;", "<b>");
13-
res = res.replaceAll("&lt;/b&gt;", "</b>");
14-
// <i> Tags
15-
res = res.replaceAll("&lt;i&gt;", "<i>");
16-
res = res.replaceAll("&lt;/i&gt;", "</i>");
17-
// <ul> Tags
18-
res = res.replaceAll("&lt;ul&gt;", "<ul>");
19-
res = res.replaceAll("&lt;/ul&gt;", "</ul>");
20-
// <li> Tags
21-
res = res.replaceAll("&lt;li&gt;", "<li>");
22-
res = res.replaceAll("&lt;/li&gt;", "</li>");
23-
// <br> Tags
24-
res = res.replaceAll("&lt;br&gt;", "<br>");
25-
res = res.replaceAll("&lt;/br&gt;", "<br>");
26-
res = res.replaceAll("&lt;br/&gt;", "<br>");
29+
public static String cleanHtml(String string) {
30+
return (string == null) ? "" : Jsoup.clean(string, Whitelist.none());
31+
}
32+
33+
public static String cleanHtml(String string, Whitelist whitelist) {
34+
return (string == null) ? "" : Jsoup.clean(string, whitelist);
35+
}
2736

28-
return res;
37+
// this method should be used for i18n-strings and captions so that custom whitelist rules can be added when needed
38+
public static String cleanI18nString(String string) {
39+
return (string == null) ? "" : Jsoup.clean(string, Whitelist.basic());
2940
}
3041

31-
// escapes html4 and then unescapes specific tags
32-
public static String escapeAndUnescapeBasicTags(String text) {
33-
return unescapeBasicTags(StringEscapeUtils.escapeHtml4(text));
42+
public static String cleanHtmlRelaxed(String string) {
43+
return (string == null) ? "" : Jsoup.clean(string, Whitelist.relaxed());
3444
}
3545
}

sormas-backend/src/main/java/de/symeda/sormas/backend/campaign/form/CampaignFormMetaFacadeEjb.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import org.apache.commons.collections.CollectionUtils;
2121
import org.apache.commons.lang3.ArrayUtils;
2222
import org.apache.commons.lang3.StringUtils;
23-
import org.jsoup.Jsoup;
2423
import org.jsoup.safety.Whitelist;
2524

2625
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -34,6 +33,7 @@
3433
import de.symeda.sormas.api.campaign.form.CampaignFormTranslations;
3534
import de.symeda.sormas.api.i18n.I18nProperties;
3635
import de.symeda.sormas.api.i18n.Validations;
36+
import de.symeda.sormas.api.utils.HtmlHelper;
3737
import de.symeda.sormas.api.utils.ValidationRuntimeException;
3838
import de.symeda.sormas.backend.user.UserService;
3939
import de.symeda.sormas.backend.util.DtoHelper;
@@ -224,7 +224,7 @@ public void validateAndClean(CampaignFormMetaDto campaignFormMetaDto) throws Val
224224
if (StringUtils.isNotBlank(element.getCaption())) {
225225
Whitelist whitelist = Whitelist.none();
226226
whitelist.addTags(CampaignFormElement.ALLOWED_HTML_TAGS);
227-
element.setCaption(Jsoup.clean(element.getCaption(), whitelist));
227+
element.setCaption(HtmlHelper.cleanHtml(element.getCaption(), whitelist));
228228
}
229229

230230
// Validate form elements
@@ -250,7 +250,7 @@ public void validateAndClean(CampaignFormMetaDto campaignFormMetaDto) throws Val
250250
if (StringUtils.isNotBlank(e.getCaption())) {
251251
Whitelist whitelist = Whitelist.none();
252252
whitelist.addTags(CampaignFormElement.ALLOWED_HTML_TAGS);
253-
e.setCaption(Jsoup.clean(e.getCaption(), whitelist));
253+
e.setCaption(HtmlHelper.cleanHtml(e.getCaption(), whitelist));
254254
}
255255
});
256256
}

sormas-ui/src/main/java/de/symeda/sormas/ui/action/ActionListEntry.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919

2020
import java.util.Optional;
2121

22-
import org.jsoup.Jsoup;
23-
import org.jsoup.safety.Whitelist;
22+
import static de.symeda.sormas.api.utils.HtmlHelper.cleanHtml;
2423

2524
import com.google.common.base.MoreObjects;
2625
import com.google.common.base.Strings;
@@ -39,6 +38,7 @@
3938
import de.symeda.sormas.api.i18n.Captions;
4039
import de.symeda.sormas.api.i18n.I18nProperties;
4140
import de.symeda.sormas.api.utils.DataHelper;
41+
import de.symeda.sormas.api.utils.HtmlHelper;
4242
import de.symeda.sormas.ui.utils.ButtonHelper;
4343
import de.symeda.sormas.ui.utils.CssStyles;
4444
import de.symeda.sormas.ui.utils.DateFormatHelper;
@@ -82,15 +82,11 @@ public ActionListEntry(ActionDto action) {
8282
descReplyLayout.addStyleName(CssStyles.RICH_TEXT_CONTENT_CONTAINER);
8383
withContentLayout.addComponents(descReplyLayout);
8484

85-
Whitelist whitelist = Whitelist.relaxed();
86-
whitelist.addTags("hr", "font");
87-
whitelist.addAttributes("font", "size", "face", "color");
88-
whitelist.addAttributes("div", "align");
89-
Label description = new Label(Jsoup.clean(Optional.ofNullable(action.getDescription()).orElse(""), whitelist), ContentMode.HTML);
85+
Label description = new Label(cleanHtml(action.getDescription(), HtmlHelper.EventActionWhitelist), ContentMode.HTML);
9086
description.setWidth(100, Unit.PERCENTAGE);
9187
descReplyLayout.addComponent(description);
9288
if (!Strings.isNullOrEmpty(action.getReply())) {
93-
Label replyLabel = new Label(Jsoup.clean(Optional.ofNullable(action.getReply()).orElse(""), whitelist), ContentMode.HTML);
89+
Label replyLabel = new Label(cleanHtml(action.getReply(), HtmlHelper.EventActionWhitelist), ContentMode.HTML);
9490
replyLabel.setWidth(100, Unit.PERCENTAGE);
9591
replyLabel.addStyleName(CssStyles.REPLY);
9692
descReplyLayout.addComponent(replyLabel);

sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/linelisting/LineListingActiveDistrictsLayout.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
import de.symeda.sormas.api.feature.FeatureConfigurationIndexDto;
1010
import de.symeda.sormas.api.i18n.I18nProperties;
1111
import de.symeda.sormas.api.i18n.Strings;
12+
import de.symeda.sormas.api.utils.HtmlHelper;
1213
import de.symeda.sormas.ui.utils.CssStyles;
1314
import de.symeda.sormas.ui.utils.DateFormatHelper;
14-
import org.apache.commons.text.StringEscapeUtils;
1515

1616
@SuppressWarnings("serial")
1717
public class LineListingActiveDistrictsLayout extends CssLayout {
@@ -28,7 +28,7 @@ private void buildLayout() {
2828

2929
for (FeatureConfigurationIndexDto config : configurations) {
3030
StringBuilder captionBuilder = new StringBuilder();
31-
captionBuilder.append("<b>").append(StringEscapeUtils.escapeHtml4(config.getDistrictName())).append("</b><br/>");
31+
captionBuilder.append("<b>").append(HtmlHelper.cleanHtml(config.getDistrictName())).append("</b><br/>");
3232
if (config.getEndDate() != null) {
3333
captionBuilder.append(I18nProperties.getString(Strings.until)).append(" ").append(DateFormatHelper.formatDate(config.getEndDate()));
3434
} else {

sormas-ui/src/main/java/de/symeda/sormas/ui/configuration/linelisting/LineListingConfigurationEditLayout.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
import de.symeda.sormas.api.i18n.Captions;
2323
import de.symeda.sormas.api.i18n.I18nProperties;
2424
import de.symeda.sormas.api.i18n.Strings;
25+
import de.symeda.sormas.api.utils.HtmlHelper;
2526
import de.symeda.sormas.ui.utils.ButtonHelper;
2627
import de.symeda.sormas.ui.utils.CssStyles;
27-
import org.apache.commons.text.StringEscapeUtils;
2828

2929
@SuppressWarnings("serial")
3030
public class LineListingConfigurationEditLayout extends VerticalLayout {
@@ -67,7 +67,7 @@ private void buildLayout() {
6767
I18nProperties.getString(
6868
regionName != null ? Strings.infoLineListingConfigurationRegionEdit : Strings.infoLineListingConfigurationNationEdit),
6969
disease.toString(),
70-
StringEscapeUtils.escapeHtml4(regionName)),
70+
HtmlHelper.cleanHtml(regionName)),
7171
ContentMode.HTML);
7272
CssStyles.style(lblInfo, CssStyles.VSPACE_4);
7373
addComponent(lblInfo);

sormas-ui/src/main/java/de/symeda/sormas/ui/events/EventController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import de.symeda.sormas.api.user.UserReferenceDto;
5757
import de.symeda.sormas.api.user.UserRight;
5858
import de.symeda.sormas.api.utils.DataHelper;
59+
import de.symeda.sormas.api.utils.HtmlHelper;
5960
import de.symeda.sormas.ui.SormasUI;
6061
import de.symeda.sormas.ui.UserProvider;
6162
import de.symeda.sormas.ui.events.eventLink.EventSelectionField;
@@ -494,7 +495,7 @@ public void deleteAllSelectedItems(Collection<EventIndexDto> selectedRows, Runna
494495
String.format(
495496
I18nProperties.getString(Strings.messageCountEventsNotDeleted),
496497
String.format("<b>%s</b>", countNotDeletedEvents),
497-
String.format("<b>%s</b>", StringEscapeUtils.escapeHtml4(nonDeletableEvents.toString()))),
498+
String.format("<b>%s</b>", HtmlHelper.cleanHtml(nonDeletableEvents.toString()))),
498499
I18nProperties.getString(Strings.messageEventsNotDeletedReason)),
499500
ContentMode.HTML);
500501
response.setWidth(600, Sizeable.Unit.PIXELS);

sormas-ui/src/main/java/de/symeda/sormas/ui/utils/CaseUuidRenderer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
import de.symeda.sormas.api.i18n.I18nProperties;
2727
import de.symeda.sormas.api.i18n.Strings;
2828
import de.symeda.sormas.api.utils.DataHelper;
29+
import de.symeda.sormas.api.utils.HtmlHelper;
2930
import elemental.json.JsonValue;
30-
import org.apache.commons.text.StringEscapeUtils;
3131

3232
@SuppressWarnings("serial")
3333
public class CaseUuidRenderer extends HtmlRenderer {
@@ -48,7 +48,7 @@ public JsonValue encode(String value) {
4848
}
4949

5050
if (value != null && !value.isEmpty()) {
51-
value = "<a title='" + value + "'>" + StringEscapeUtils.escapeHtml4(DataHelper.getShortUuid(value)) + "</a>";
51+
value = "<a title='" + value + "'>" + HtmlHelper.cleanHtml(DataHelper.getShortUuid(value)) + "</a>";
5252
return super.encode(value);
5353
} else {
5454
return null;

sormas-ui/src/main/java/de/symeda/sormas/ui/utils/CollectionValueProvider.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
import java.util.Collection;
2121

2222
import com.vaadin.data.ValueProvider;
23-
import org.apache.commons.text.StringEscapeUtils;
23+
24+
import de.symeda.sormas.api.utils.HtmlHelper;
2425

2526
/**
2627
* A ValueProvider that allows displaying a collection as a comma separated list of
@@ -44,7 +45,7 @@ public String apply(T source) {
4445
b.append(", ");
4546
}
4647
if (b.length() >= 2) {
47-
return StringEscapeUtils.escapeHtml4(b.substring(0, b.length() - 2));
48+
return HtmlHelper.cleanHtml(b.substring(0, b.length() - 2));
4849
}
4950
return "";
5051
}

0 commit comments

Comments
 (0)