Skip to content

Commit e7a1f29

Browse files
authored
Merge pull request #123 from playframework/plugin-1.1.0-RC1
Fortify: move to Play 2.9 and add Scala 3
2 parents 3a5d27a + b4ddca6 commit e7a1f29

File tree

11 files changed

+323
-303
lines changed

11 files changed

+323
-303
lines changed

.github/workflows/fortify.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ jobs:
1313
strategy:
1414
fail-fast: false
1515
matrix:
16-
java: [8, 11, 17]
17-
scala: [2.12.x, 2.13.x]
16+
java: [11, 17, 21]
17+
scala: [2.13.x, 3.x]
1818
runs-on: ubuntu-latest
1919
steps:
2020
- uses: actions/checkout@v3
2121
- uses: coursier/cache-action@v6
22-
- uses: actions/setup-java@v3
22+
- uses: actions/setup-java@v4
2323
with:
24-
distribution: adopt
24+
distribution: temurin
2525
java-version: ${{matrix.java}}
2626

2727
- uses: actions/cache@v3

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ dist
1313
/.project
1414
/RUNNING_PID
1515
/.settings
16+
/.bsp

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@ Then go to http://localhost:9000.
3939

4040
## Scala versions
4141

42-
Cross-building to Scala 2.12 and 2.13 is supported.
42+
Cross-building to Scala 2.13 and 3 is supported.

app/controllers/HomeController.scala

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import scala.sys.process._
1515
*/
1616
class HomeController @Inject()(ws: WSClient, cc: MessagesControllerComponents)(implicit ec: ExecutionContext) extends MessagesAbstractController(cc) {
1717

18-
def index = Action { implicit request =>
18+
def index: Action[AnyContent] = Action { implicit request =>
1919
Ok(Html(s"""
2020
<html>
2121
<body>
@@ -41,7 +41,7 @@ class HomeController @Inject()(ws: WSClient, cc: MessagesControllerComponents)(i
4141
/**
4242
* Command injection & XSS directly from directly called query parameter
4343
*/
44-
def attackerQuerySimple = Action { implicit request =>
44+
def attackerQuerySimple: Action[AnyContent] = Action { implicit request =>
4545
val address = request.getQueryString("address")
4646

4747
// [RuleTest] Command Injection
@@ -56,14 +56,15 @@ class HomeController @Inject()(ws: WSClient, cc: MessagesControllerComponents)(i
5656
/**
5757
* Command injection & XSS directly from directly called query parameter
5858
*/
59-
def attackerQueryPatternMatching = Action { implicit request =>
59+
def attackerQueryPatternMatching: Action[AnyContent] = Action { implicit request =>
6060

6161
val addressRE= "(.*):(\\d+)".r
6262
val address = request.cookies.get("address").get.value
6363

6464
address match {
65-
// [RuleTest] Command Injection
65+
// [RuleTest] Command Injection
6666
case addressRE(address, port) => s"ping ${address}".!
67+
case _ =>
6768
}
6869
// [RuleTest] Cross-Site Scripting: Reflected
6970
Ok(Html(s"Host ${address} pinged")) as HTML
@@ -72,7 +73,7 @@ class HomeController @Inject()(ws: WSClient, cc: MessagesControllerComponents)(i
7273
/**
7374
* XSS directly from directly called query parameter
7475
*/
75-
def attackerQuery = Action { implicit request =>
76+
def attackerQuery: Action[AnyContent] = Action { implicit request =>
7677

7778
val result = request.getQueryString("attacker").map { command =>
7879
// Render the command directly from query parameter, this is the obvious example
@@ -87,21 +88,21 @@ class HomeController @Inject()(ws: WSClient, cc: MessagesControllerComponents)(i
8788
/**
8889
* XSS through query string parsed by generated router from conf/routes file.
8990
*/
90-
def attackerRouteControlledQuery(attacker: String) = Action { implicit request =>
91+
def attackerRouteControlledQuery(attacker: String): Action[AnyContent] = Action { implicit request =>
9192
Ok(Html(attacker)) as HTML
9293
}
9394

9495
/**
9596
* XSS through path binding parsed by generated router from conf/routes file.
9697
*/
97-
def attackerRouteControlledPath(attacker: String) = Action { implicit request =>
98+
def attackerRouteControlledPath(attacker: String): Action[AnyContent] = Action { implicit request =>
9899
Ok(Html(attacker)) as HTML
99100
}
100101

101102
/**
102103
* XSS through attacker controlled info in cookie
103104
*/
104-
def attackerCookie = Action { implicit request =>
105+
def attackerCookie: Action[AnyContent] = Action { implicit request =>
105106
// User cookies have no message authentication by default, so an attacker can pass in a cookie
106107
val result = request.cookies.get("attacker").map { attackerCookie =>
107108
// Render the command
@@ -114,7 +115,7 @@ class HomeController @Inject()(ws: WSClient, cc: MessagesControllerComponents)(i
114115
/**
115116
* XSS through attacker controlled header
116117
*/
117-
def attackerHeader = Action { implicit request =>
118+
def attackerHeader: Action[AnyContent] = Action { implicit request =>
118119
// Request headers are also unvalidated by default.
119120
// The usual example is pulling the Location header to do an unsafe redirect
120121
val result = request.headers.get("Attacker").map { command =>
@@ -128,7 +129,7 @@ class HomeController @Inject()(ws: WSClient, cc: MessagesControllerComponents)(i
128129
/**
129130
* Unbound redirect through Header
130131
*/
131-
def attackerOpenRedirect = Action { implicit request =>
132+
def attackerOpenRedirect: Action[AnyContent] = Action { implicit request =>
132133
request.headers.get("Location") match {
133134
case Some(attackerLocation) =>
134135
// Also see https://github.com/playframework/playframework/issues/6450
@@ -142,7 +143,7 @@ class HomeController @Inject()(ws: WSClient, cc: MessagesControllerComponents)(i
142143
/**
143144
* XSS through URL encoded form input.
144145
*/
145-
def attackerFormInput = Action { implicit request =>
146+
def attackerFormInput: Action[AnyContent] = Action { implicit request =>
146147
val boundForm = FormData.form.bindFromRequest()
147148
boundForm.fold(badData => BadRequest("Bad form binding"), userData => {
148149
// Render the attacker command as HTML
@@ -154,7 +155,7 @@ class HomeController @Inject()(ws: WSClient, cc: MessagesControllerComponents)(i
154155
/**
155156
* XSS through attacker controlled flash cookie.
156157
*/
157-
def attackerFlash = Action { implicit request =>
158+
def attackerFlash: Action[AnyContent] = Action { implicit request =>
158159
// Flash is usually handled with
159160
// Redirect(routes.HomeController.attackerFlash()).flashing("info" -> "Some text")
160161
// but if the user puts HTML in it and then renders it,
@@ -170,14 +171,14 @@ class HomeController @Inject()(ws: WSClient, cc: MessagesControllerComponents)(i
170171
}
171172

172173
// Render a boring form
173-
def constraintForm = Action { implicit request =>
174+
def constraintForm: Action[AnyContent] = Action { implicit request =>
174175
Ok(views.html.index(FormData.customForm))
175176
}
176177

177178
/**
178179
* XSS through custom constraint with user input
179180
*/
180-
def attackerConstraintForm = Action { implicit request =>
181+
def attackerConstraintForm: Action[AnyContent] = Action { implicit request =>
181182

182183
// Bind a form that uses the i18n messages api, but the user input is reflected in the error message
183184
// Play takes a raw string here and escapes everything, but it may be possible to escape this...
@@ -196,7 +197,7 @@ class HomeController @Inject()(ws: WSClient, cc: MessagesControllerComponents)(i
196197
/**
197198
* XSS involving Twirl template
198199
*/
199-
def twirlXSS = Action { implicit request =>
200+
def twirlXSS = Action { implicit request: MessagesRequest[AnyContent] =>
200201
request.getQueryString("xss").map { payload =>
201202
Ok(views.html.xss(payload))
202203
}.getOrElse(Ok("Missing xss param"))
@@ -205,7 +206,7 @@ class HomeController @Inject()(ws: WSClient, cc: MessagesControllerComponents)(i
205206
/**
206207
* SSRF attacks done with Play WS
207208
*/
208-
def attackerSSRF = Action.async { implicit request =>
209+
def attackerSSRF: Action[AnyContent] = Action.async { implicit request =>
209210
// Play WS does not have a whitelist of valid URLs, so if you're calling it
210211
// directly with user input, you're open to SSRF. The best thing to do is
211212
// to place WS access in a wrapper, i.e.
@@ -222,7 +223,7 @@ class HomeController @Inject()(ws: WSClient, cc: MessagesControllerComponents)(i
222223
/**
223224
* Command injection with custom body parser
224225
*/
225-
def attackerCustomBodyParser = Action(bodyParser = BodyParser { header: RequestHeader =>
226+
def attackerCustomBodyParser: Action[Foo] = Action(bodyParser = BodyParser { (header: RequestHeader) => {
226227
// request header is a request without a body
227228
// http://localhost:9000/attackerCustomBodyParser?address=/etc/passwd
228229
val result = header.getQueryString("filename").map { filename =>
@@ -231,7 +232,7 @@ class HomeController @Inject()(ws: WSClient, cc: MessagesControllerComponents)(i
231232
}.getOrElse("No filename found!")
232233

233234
Accumulator.done(Right(Foo(bar = result)))
234-
}) { implicit request: Request[Foo] =>
235+
}}) { implicit request: Request[Foo] =>
235236
val foo: Foo = request.body
236237
Ok(foo.bar)
237238
}
@@ -313,4 +314,7 @@ object FormData {
313314

314315

315316
case class UserData(name: String, age:Int)
317+
object UserData {
318+
def unapply(u: UserData): Option[(String, Int)] = Some((u.name, u.age))
319+
}
316320
}

build.sbt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ lazy val `play-webgoat` = (project in file(".")).enablePlugins(PlayScala)
33
name := "play-webgoat"
44
version := "1.0"
55

6-
crossScalaVersions := Seq("2.13.12", "2.12.18")
6+
crossScalaVersions := Seq("2.13.12", "3.3.1")
77
scalaVersion := crossScalaVersions.value.head // tc-skip
88

99
libraryDependencies ++= Seq(guice, ws)
1010
scalacOptions ++= Seq(
11-
"-feature", "-unchecked", "-deprecation", "-Xfatal-warnings",
12-
// "unused" is too fragile w/ Twirl, routes file
13-
"-Xlint:-unused"
11+
// "-unchecked", "-deprecation" // Set by Play already
12+
"-feature", "-Werror",
1413
)
14+
scalacOptions ++= (CrossVersion.partialVersion(scalaVersion.value) match {
15+
case Some((2, _)) => Seq("-Xlint:-unused,_")
16+
case _ => Seq()
17+
})

conf/logback.xml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
13
<!--
2-
~ Copyright (C) 2009-2017 Lightbend Inc. <https://www.lightbend.com>
3-
-->
4+
Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
5+
-->
6+
7+
<!DOCTYPE configuration>
8+
49
<!-- The default logback configuration that Play uses in dev mode if no other configuration is provided -->
510
<configuration>
11+
<import class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"/>
12+
<import class="ch.qos.logback.core.ConsoleAppender"/>
613

7-
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
8-
<encoder>
14+
<appender name="STDOUT" class="ConsoleAppender">
15+
<encoder class="PatternLayoutEncoder">
916
<pattern>%highlight(%-5level) %logger{15} - %message%n%xException{10}</pattern>
1017
</encoder>
1118
</appender>
1219

1320
<root level="INFO">
14-
<appender-ref ref="STDOUT" />
21+
<appender-ref ref="STDOUT"/>
1522
</root>
1623

1724
</configuration>

fortify.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// enable the plugin
22
addCompilerPlugin(
3-
"com.lightbend" %% "scala-fortify" % "1.0.25"
3+
"com.lightbend" %% "scala-fortify" % "1.1.0-RC1"
44
cross CrossVersion.patch)
55

66
// configure the plugin

project/plugins.sbt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,4 @@ scalacOptions ++= Seq(
22
"-feature", "-unchecked", "-deprecation",
33
"-Xlint:-unused", "-Xfatal-warnings")
44

5-
ThisBuild / libraryDependencySchemes ++= Seq(
6-
"org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always
7-
)
8-
9-
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.19")
5+
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.9.0")

0 commit comments

Comments
 (0)