Technical information leak - API - Scala

Technical information leak - API - Scala

Need

Protection of sensitive technical information in API responses

Context

  • Usage of Scala for building scalable and functional applications
  • Usage of play.api.mvc for handling HTTP requests and responses in Play Framework
  • Usage of sangria.execution for executing GraphQL queries with Sangria framework
  • Usage of sangria.parser.QueryParser for parsing GraphQL queries
  • Usage of Sangria's schema.Schema for defining GraphQL schemas
  • Usage of defaultContext for handling concurrent execution in Play Framework

Description

Non compliant code

        import play.api.mvc._
import sangria.execution._
import sangria.parser.QueryParser
import sangria.schema.Schema
import play.api.libs.concurrent.Execution.Implicits.defaultContext

class ApplicationController extends Controller {

  val schema: Schema[UserContext, Unit] = SchemaFactory.createSchema

  def graphql: Action[AnyContent] = Action.async { implicit request =>
    val query = request.body.asText.getOrElse("")
    QueryParser.parse(query) match {
      case Success(queryAst) =>
        Executor.execute(schema, queryAst, new UserContext(new DAO))
          .map(result => Ok(play.api.libs.json.Json.toJson(result)))
          .recover {
            case error: QueryAnalysisError => BadRequest(error.resolveError)
            case error: ErrorWithResolver => InternalServerError(error.resolveError)
          }
      case Failure(error) => Future.successful(BadRequest(error.getMessage))
    }
  }
}
        
        

In the above code, the graphql function is exposed as a public endpoint in the ApplicationController. This function accepts any GraphQL query from the client and executes it against the application's GraphQL schema.

The vulnerability lies in the fact that the entire GraphQL schema (which includes both queries and mutations) is exposed to the client without any restrictions. This means that an attacker can send introspection queries to this endpoint and retrieve the entire structure of the API schema.

Introspection in GraphQL allows clients to know what operations (queries, mutations, and subscriptions) and types (objects, interfaces, unions, enums, scalars, and input types) are available in the schema. While this is a powerful feature that can be used for good (like generating API documentation or driving a GraphQL IDE), it can also be exploited by attackers to gather information about your API that they can use for further attacks.

In this case, an attacker can use the introspection feature to discover all the available queries and mutations in your API, including those that are not supposed to be publicly accessible. This can lead to information leakage and potential unauthorized actions if the exposed mutations are not properly protected.

Steps

  • Implement authentication and authorization mechanisms to ensure that only authorized users can access the GraphQL API.
  • Restrict access to the GraphQL schema structure by implementing access control mechanisms.
  • Remove or obfuscate sensitive information from the GraphQL schema structure, such as query and mutation names.
  • Implement rate limiting and throttling mechanisms to prevent excessive requests and potential information leakage.
  • Regularly update and patch the Play framework and other dependencies to ensure that any known vulnerabilities are addressed.
  • Perform regular security audits and penetration testing to identify and fix any potential vulnerabilities in the application.
  • Educate developers about secure coding practices and provide training on how to prevent information leaks in the API.

Compliant code

        import play.api.mvc._
import sangria.execution._
import sangria.parser.QueryParser
import sangria.schema.Schema
import play.api.libs.concurrent.Execution.Implicits.defaultContext

class ApplicationController extends Controller {

  val schema: Schema[UserContext, Unit] = SchemaFactory.createSchema

  def graphql: Action[AnyContent] = Action.async { implicit request =>
    // Implement authentication and authorization
    val authenticatedUser = authenticateUser(request)
    if (authenticatedUser == null) {
      Future.successful(Unauthorized("Unauthorized"))
    } else {
      val query = request.body.asText.getOrElse("")
      QueryParser.parse(query) match {
        case Success(queryAst) =>
          // Implement access control
          if (isUserAuthorized(authenticatedUser, queryAst)) {
            Executor.execute(schema, queryAst, new UserContext(new DAO))
              .map(result => Ok(play.api.libs.json.Json.toJson(result)))
              .recover {
                case error: QueryAnalysisError => BadRequest(error.resolveError)
                case error: ErrorWithResolver => InternalServerError(error.resolveError)
              }
          } else {
            Future.successful(Forbidden("Forbidden"))
          }
        case Failure(error) => Future.successful(BadRequest(error.getMessage))
      }
    }
  }

  // Dummy methods for demonstration purposes
  def authenticateUser(request: Request[AnyContent]): User = {
    // Implement user authentication
  }

  def isUserAuthorized(user: User, queryAst: Document): Boolean = {
    // Implement access control
  }
}
        
        

The updated code includes authentication and authorization mechanisms to ensure that only authorized users can access the GraphQL API.

The authenticateUser method is used to authenticate the user making the request. If the user is not authenticated, the server responds with an "Unauthorized" message.

The isUserAuthorized method is used to check if the authenticated user has the necessary permissions to perform the requested operation. If the user is not authorized, the server responds with a "Forbidden" message.

These changes help to prevent unauthorized access to the GraphQL API schema structure and reduce the risk of information leakage.

Please note that the authenticateUser and isUserAuthorized methods are placeholders for demonstration purposes. You should replace them with your own authentication and authorization logic.

In addition to these changes, you should also consider implementing rate limiting and throttling mechanisms, removing or obfuscating sensitive information from the GraphQL schema structure, regularly updating and patching the Play framework and other dependencies, performing regular security audits and penetration testing, and educating developers about secure coding practices.

References