Skip to content

Commit 34856a7

Browse files
committed
Avoiding possible XSS attack through the default error handler. See #1366
1 parent 72642c3 commit 34856a7

File tree

4 files changed

+55
-5
lines changed

4 files changed

+55
-5
lines changed

jooby/src/main/java/org/jooby/Err.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,11 @@
205205

206206
import java.util.LinkedHashMap;
207207
import java.util.Map;
208+
import java.util.Objects;
208209
import java.util.Optional;
210+
import java.util.function.BiFunction;
211+
import java.util.function.Function;
212+
import java.util.function.Supplier;
209213

210214
import com.typesafe.config.Config;
211215
import org.jooby.funzy.Try;
@@ -287,12 +291,24 @@ public void handle(final Request req, final Response rsp, final Err ex) throws T
287291
log.error("execution of: {}{} resulted in exception\nRoute:\n{}\n\nStacktrace:",
288292
req.method(), req.path(), req.route().print(6), ex);
289293
Config conf = req.require(Config.class);
294+
Env env = req.require(Env.class);
290295
boolean stackstrace = Try.apply(() -> conf.getBoolean("err.stacktrace"))
291-
.orElse(req.require(Env.class).name().equals("dev"));
296+
.orElse(env.name().equals("dev"));
297+
298+
Function<Object, String> xssFilter = env.xss("html").compose(Objects::toString);
299+
BiFunction<String, Object, String> escaper = (k, v) -> xssFilter.apply(v);
300+
301+
Supplier<Map<String, Object>> detailsProvider = () -> {
302+
Map<String, Object> map = ex.toMap(stackstrace);
303+
map.compute("message", escaper);
304+
map.compute("reason", escaper);
305+
return map;
306+
};
307+
292308
rsp.send(
293309
Results
294-
.when(MediaType.html, () -> Results.html(VIEW).put("err", ex.toMap(stackstrace)))
295-
.when(MediaType.all, () -> ex.toMap(stackstrace)));
310+
.when(MediaType.html, () -> Results.html(VIEW).put("err", detailsProvider.get()))
311+
.when(MediaType.all, detailsProvider::get));
296312
}
297313

298314
}

jooby/src/main/java/org/jooby/internal/HttpHandlerImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ private static Route[] routes(final Set<Route.Definition> routeDefs, final Strin
627627
// default /favicon.ico handler:
628628
rsp.status(Status.NOT_FOUND).end();
629629
} else {
630-
throw new Err(Status.NOT_FOUND, req.path(true));
630+
throw new Err(Status.NOT_FOUND, req.path());
631631
}
632632
}
633633
}, method, path, "err", accept));

jooby/src/main/java/org/jooby/internal/RouteImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ public class RouteImpl implements RouteWithFilter {
233233
public static RouteWithFilter notFound(final String method, final String path) {
234234
return new FallbackRoute("404", method, path, MediaType.ALL, (req, rsp, chain) -> {
235235
if (!rsp.status().isPresent()) {
236-
throw new Err(Status.NOT_FOUND, req.path(true));
236+
throw new Err(Status.NOT_FOUND, req.path());
237237
}
238238
});
239239
}

jooby/src/test/java/org/jooby/DefaultErrHandlerTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.jooby;
22

33
import com.google.common.collect.ImmutableList;
4+
import com.google.common.escape.Escapers;
5+
import com.google.common.html.HtmlEscapers;
46
import com.typesafe.config.Config;
57
import static org.easymock.EasyMock.expect;
68
import org.jooby.test.MockUnit;
@@ -66,6 +68,7 @@ private MockUnit.Block handleErr(Throwable ex, boolean stacktrace) {
6668
expect(conf.getBoolean("err.stacktrace")).andReturn(stacktrace);
6769
Env env = unit.get(Env.class);
6870
expect(env.name()).andReturn("dev");
71+
expect(env.xss("html")).andReturn(HtmlEscapers.htmlEscaper()::escape);
6972

7073
Request req = unit.get(Request.class);
7174

@@ -112,6 +115,37 @@ public void handleWithErrMessage() throws Exception {
112115
});
113116
}
114117

118+
@SuppressWarnings({"unchecked"})
119+
@Test
120+
public void handleWithHtmlErrMessage() throws Exception {
121+
Err ex = new Err(500, "Something something <em>dark</em>");
122+
123+
StringWriter writer = new StringWriter();
124+
ex.printStackTrace(new PrintWriter(writer));
125+
String[] stacktrace = writer.toString().replace("\r", "").split("\\n");
126+
127+
new MockUnit(Request.class, Response.class, Route.class, Env.class, Config.class)
128+
.expect(handleErr(ex, true))
129+
.run(unit -> {
130+
131+
Request req = unit.get(Request.class);
132+
Response rsp = unit.get(Response.class);
133+
134+
new Err.DefHandler().handle(req, rsp, ex);
135+
},
136+
unit -> {
137+
Result result = unit.captured(Result.class).iterator().next();
138+
View view = (View) result.ifGet(ImmutableList.of(MediaType.html)).get();
139+
assertEquals("err", view.name());
140+
checkErr(stacktrace, "Server Error(500): Something something &lt;em&gt;dark&lt;/em&gt;",
141+
(Map<String, Object>) view.model()
142+
.get("err"));
143+
144+
Object hash = result.ifGet(MediaType.ALL).get();
145+
assertEquals(4, ((Map<String, Object>) hash).size());
146+
});
147+
}
148+
115149
private void checkErr(final String[] stacktrace, final String message,
116150
final Map<String, Object> err) {
117151
assertEquals(message, err.remove("message"));

0 commit comments

Comments
 (0)