Fix SQL injection in the /query/city aggregation route
field on that route reached PostgresService.query unvalidated, which splices it into SQL via Fragment.const (unescaped) whenever the aggregate key is min/max/avg/sum/distinct, or whenever granularity is "hour" in the list branch. Add ValidateField (allowlists WeatherData's known field names, same pattern AggFieldList already uses for /query/country) and apply it to the field path segment. AggregateKey values reaching Fragment.const elsewhere are already safe since they come from a closed ADT, not raw user input.
This commit is contained in:
@@ -10,7 +10,7 @@ import fetch.csv.FileNameService
|
||||
import fetch.lvgmc.{FetchService, WaterTemperatureService}
|
||||
import fetch.warnings.WarningService
|
||||
import fs2.io.file.{Files, Path}
|
||||
import server.ValidateRoutes.{AggFieldList, AggKey, CityList, DateTimeRange, Granularity, ValidateDate, ValidateDateTime, ValidateFileName, ValidateInt, ValidateMonths, ValidateZonedDateTime}
|
||||
import server.ValidateRoutes.{AggFieldList, AggKey, CityList, DateTimeRange, Granularity, ValidateDate, ValidateDateTime, ValidateField, ValidateFileName, ValidateInt, ValidateMonths, ValidateZonedDateTime}
|
||||
import org.http4s._
|
||||
import org.http4s.dsl.io._
|
||||
import org.http4s.implicits._
|
||||
@@ -139,7 +139,7 @@ class Server(postgresService: PostgresService, dataService: DataService, fetch:
|
||||
}
|
||||
|
||||
// http://0.0.0.0:8080/api/query/city/Liepāja,Rēzekne/20230414_2200-20230501_1230/hour/tempMax/max
|
||||
case GET -> Root / "query" / "city" / CityList(cities) / DateTimeRange(from, to) / Granularity(granularity) / field / AggKey(key) =>
|
||||
case GET -> Root / "query" / "city" / CityList(cities) / DateTimeRange(from, to) / Granularity(granularity) / ValidateField(field) / AggKey(key) =>
|
||||
val userQuery = UserQuery(cities, field, key, granularity, from, to)
|
||||
|
||||
postgresService.query(userQuery)
|
||||
|
||||
@@ -64,6 +64,14 @@ object ValidateRoutes {
|
||||
}
|
||||
}
|
||||
|
||||
// Restricts a raw path segment to a known WeatherData field name before it
|
||||
// can reach Fragment.const (unescaped SQL splicing) in PostgresService.
|
||||
object ValidateField {
|
||||
def unapply(str: String): Option[String] = {
|
||||
if (WeatherData.getKeys.contains(str)) Some(str) else None
|
||||
}
|
||||
}
|
||||
|
||||
object AggFieldList {
|
||||
def unapply(str: String): Option[NonEmptyList[String]] = {
|
||||
val weatherFields = WeatherData.getKeys
|
||||
|
||||
Reference in New Issue
Block a user