CVE-2020-4038 (Markdown, published 2020-06-08T21:15Z)
Description: value: GraphQL Playground (graphql-playground-html NPM package) before version 1.6.22 have a severe XSS Reflection attack vulnerability. All unsanitized user input passed into renderPlaygroundPage() method could trigger this vulnerability. This has been patched in graphql-playground-html version 1.6.22. Note that some of the associated dependent middleware packages are also affected including but not limited to graphql-playground-middleware-express before version 1.7.16, graphql-playground-middleware-koa before version 1.6.15, graphql-playground-middleware-lambda before version 1.7.17, and graphql-playground-middleware-hapi before 1.6.13.
Severity: MEDIUM (CVSS v3 base score: 7.4)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Repository: https://github.com/prisma-labs/graphql-playground
Files changed:
README.md
SECURITY.md
packages/graphql-playground-html/README.md
packages/graphql-playground-html/package.json
packages/graphql-playground-html/src/render-playground-page.ts
packages/graphql-playground-middleware-express/README.md
packages/graphql-playground-middleware-express/examples/basic/index.js
packages/graphql-playground-middleware-hapi/README.md
packages/graphql-playground-middleware-koa/README.md
packages/graphql-playground-middleware-lambda/README.md
packages/graphql-playground-middleware-lambda/examples/basic/README.md
packages/graphql-playground-react/README.md
scripts/build.sh
yarn.lock
Commit message: Merge pull request from GHSA-4852-vrh7-28rf

* fix: security vulnerability allowing XSS reflection with user input

* fix: update docs, replace santize-html and sanitizeUrl with xss module

Vulnerable code:
[![npm version](https://badge.fury.io/js/graphql-playground-react.svg)](https://badge.fury.io/js/graphql-playground-react) 
	endpoint: string
	query: string
	name?: string
	variables?: string
	responses?: string[]
	headers?: { [key: string]: string }
	href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700|Source+Code+Pro:400,700"
	rel="stylesheet"
	<Provider store={store}>
		<Playground endpoint="https://api.graph.cool/simple/v1/swapi" />
	</Provider>,
	document.body,
	function callbackFilter(error, output) {
		// eslint-disable-next-line no-param-reassign
		output.headers['Access-Control-Allow-Origin'] = '*'
		callback(error, output)
	}

	const handler = graphqlLambda({ schema: myGraphQLSchema })
	return handler(event, context, callbackFilter)
	endpoint: '/dev/graphql',
	property: string
	comment: string
	punctuation: string
	keyword: string
	def: string
	qualifier: string
	attribute: string
	number: string
	string: string
	builtin: string
	string2: string
	variable: string
	meta: string
	atom: string
	ws: string
	selection: string
	cursorColor: string
	editorBackground: string
	resultBackground: string
	leftDrawerBackground: string
	rightDrawerBackground: string
  "dependencies": {}
const getCdnMarkup = ({ version, cdnUrl = '//cdn.jsdelivr.net/npm', faviconUrl }) => `
    <link rel="stylesheet" href="${cdnUrl}/graphql-playground-react${
  version ? `@${version}` : ''
}/build/static/css/index.css" />
    ${typeof faviconUrl === 'string' ? `<link rel="shortcut icon" href="${faviconUrl}" />` : ''}
    ${faviconUrl === undefined ? `<link rel="shortcut icon" href="${cdnUrl}/graphql-playground-react${
      version ? `@${version}` : ''
    }/build/favicon.png" />` : ''}
    <script src="${cdnUrl}/graphql-playground-react${
  version ? `@${version}` : ''
}/build/static/js/middleware.js"></script>
`
    extendedOptions.subscriptionEndpoint = (options as any).subscriptionsEndpoint
      extendedOptions.env === 'react' || extendedOptions.env === 'electron'
        ? ''
        : getCdnMarkup(extendedOptions)
  
        GraphQLPlayground.init(root, ${JSON.stringify(
          extendedOptions,
          null,
          2,
        )})
const express = require("express");
const { ApolloServer, gql } = require("apollo-server-express");
const expressPlayground = require("../../dist/index").default;
`;
    hello: () => "world"
  }
};
const PORT = 4000;
const server = new ApolloServer({ typeDefs, resolvers });
const app = express();
server.applyMiddleware({ app });
app.get("/playground", expressPlayground({ endpoint: "/graphql" }));
app.listen(PORT);
  `Serving the GraphQL Playground on http://localhost:${PORT}/playground`
);
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700|Source+Code+Pro:400,700" rel="stylesheet">
  <Playground endpoint="https://api.graph.cool/simple/v1/swapi" />,
    - http:
        path: graphql
        method: post
        cors: true
    - http:
        path: playground
        method: get
        cors: true
  graphql-playground-react

Fixed code:
> **SECURITY WARNING:** This `graphql-playground-html` and [all of it's middleware dependents](#impacted-packages) in this repository **had a severe XSS Reflection attack vulnerability to unsanitized user input** until version `graphql-playground-html@1.6.20`. Impacted are any and all **user-defined** input to `renderPlaygroundPage()`, `koaPlayground()`,`expressPlayground()`, `koaPlayground()`, or `lambdaPlayground()`. If you used static values, such as `graphql-playground-electron` does in [it's webpack config](https://github.com/prisma-labs/graphql-playground/blob/master/packages/graphql-playground-electron/webpack.config.build.js#L16), you were not vulnerable to the attack. [More Details](./SECURITY.md)

[![npm version](https://badge.fury.io/js/graphql-playground-react.svg)](https://badge.fury.io/js/graphql-playground-react)
## Security Details

**NOTE: only _unsanitized user input_ to the functions in these packages is vulnerable** to the recently reported XSS Reflection attack.

### Impact

The only reason this vulnerability exists is because we are using template strings in `renderPlaygroundPage()` with potentially user defined variables. This allows an attacker to inject html and javascript into a page on execution.

Common examples may be user-defined path parameters, query string, unsanitized UI provided values in database, etc that are used to build template strings or passed directly to a `renderPlaygroundPage()` or the matching middleware function equivalent.

### Impacted Packages

**All versions of these packages are impacted until the ones specified below**, which are now safe for user defined input:

- `graphql-playground-html`: **☔ safe** @ `1.6.20`
- `graphql-playground-express` **☔ safe** @ `1.7.15`
- `graphql-playground-koa` **☔ safe** @ `1.6.14`
- `graphql-playground-hapi` **☔ safe** @ `1.6.12`
- `graphql-playground-lambda` **☔ safe** @ `1.7.16`
- `graphql-playground-electron` has always been **☔ safe** from XSS attacks! This is because configuration is statically defined [it's webpack config](https://github.com/prisma-labs/graphql-playground/blob/master/packages/graphql-playground-electron/webpack.config.build.js#L16)
- `graphql-playground-react` is safe because it does not use `renderPlaygroundPage()` anywhere, and thus is not susceptible to template string XSS reflection attacks.

### More Information

See the [security docs](./SECURITY.md) for more details on how your implementation might be impacted by this vulnerability. It contains safe examples, unsafe examples, workarounds, and more details.

  endpoint: string
  query: string
  name?: string
  variables?: string
  responses?: string[]
  headers?: { [key: string]: string }
  href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700|Source+Code+Pro:400,700"
  rel="stylesheet"
  <Provider store={store}>
    <Playground endpoint='https://api.graph.cool/simple/v1/swapi' />
  </Provider>,
  document.body,
  function callbackFilter(error, output) {
    // eslint-disable-next-line no-param-reassign
    output.headers['Access-Control-Allow-Origin'] = '*'
    callback(error, output)
  }

  const handler = graphqlLambda({ schema: myGraphQLSchema })
  return handler(event, context, callbackFilter)
  endpoint: '/dev/graphql',
#### Security Issue

There is an [XSS Reflection Vulnerability](./SECURITY.md) when using these middlewares with unsanitized user input before

  property: string
  comment: string
  punctuation: string
  keyword: string
  def: string
  qualifier: string
  attribute: string
  number: string
  string: string
  builtin: string
  string2: string
  variable: string
  meta: string
  atom: string
  ws: string
  selection: string
  cursorColor: string
  editorBackground: string
  resultBackground: string
  leftDrawerBackground: string
  rightDrawerBackground: string
# Known Vulnerabilities

## XSS Reflection Vulnerability

the origin of the vulnerability is in `renderPlaygroundPage`, found in `graphql-playground-html`

### Impact

When using

- `renderPlaygroundPage()`,
- `koaPlayground()`
- `expressPlayground()`
- `koaPlayground()`
- `lambdaPlayground()`
- any downstream dependents that use these functions

without sanitization of user input, your application is vulnerable to an XSS Reflecton Attack. This is a serious vulnerability that could allow for exfiltration of data or user credentials, or to disrupt systems.

### Impacted Packages

**All versions of these packages are impacted until those specified below**, which are now safe for user defined input:

- `graphql-playground-html`: **☔ safe** @ `1.6.20`
- `graphql-playground-express` **☔ safe** @ `1.7.15`
- `graphql-playground-koa` **☔ safe** @ `1.6.14`
- `graphql-playground-hapi` **☔ safe** @ `1.6.12`
- `graphql-playground-lambda` **☔ safe** @ `1.7.16`

### Static input was always safe

These examples are safe for _all versions_ **because input is static**

with `express` and `renderPlaygroundPage`:

```js
app.get('/playground', (req) => {
  res.html(
    renderPlaygroundPage({
      endpoint: `/our/graphql`,
    }),
  )
  next()
})
```

with `expressPlayground`:

```js
// params
app.get('/playground', (req) =>
  expressPlayground({
    endpoint: `/our/graphql`,
    settings: { 'editor.theme': req.query.darkMode ? 'dark' : 'light' },
  }),
)
```

with `koaPlayground`:

```js
const koa = require('koa')
const koaRouter = require('koa-router')
const koaPlayground = require('graphql-playground-middleware-koa')

const app = new koa()
const router = new koaRouter()

router.all('/playground', koaPlayground({ endpoint: '/graphql' }))
```

### Vulnerable Examples

Here are some examples where the vulnerability would be present before the patch, because of unfiltered user input

```js
const express = require('express')
const expressPlayground = require('graphql-playground-middleware-express')
  .default

const app = express()

app.use(express.json())

// params
app.get('/playground/:id', (req) =>
  expressPlayground({
    endpoint: `/our/graphql/${req.params.id}`,
  }),
)

// params
app.get('/playground', (req) =>
  expressPlayground({
    endpoint: `/our/graphql`,
    // any settings that are unsanitized user input, not just `endpoint`
    settings: { 'editor.fontFamily': req.query.font },
  }),
)
```

### Workaround

To fix this issue without the update, you can sanitize however you want.

We suggest using [`xss`](https://www.npmjs.com/package/xss) (what we use for our own fix)

For example, with `graphql-playground-middleware-express`:

```js
const express = require('express')
const { filterXSS } = require('xss')
const expressPlayground = require('graphql-playground-middleware-express')
  .default


const app = express()

const filter = (val) => filterXSS(val, {
  whitelist: [],
  stripIgnoreTag: true,
  stripIgnoreTagBody: ['script']
})

// simple example
app.get('/playground/:id', (req) =>
  expressPlayground({ endpoint: `/graphql/${filter(req.params.id)}` })

// advanced params
app.get('/playground', (req) =>
  expressPlayground(JSON.parse(filter(JSON.stringify(req.query))))
```
> **SECURITY WARNING:** This package and all of it's dependendents had a severe XSS Reflection attack vulnerability until version `1.6.20` of this package. You must sanitize any and all user input values to `renderPlaygroundPage()` values. If you used static values in your middlewares, including ours, you were not vulnerable to the attack.

  "dependencies": {
    "xss": "^1.0.6"
  }
import { filterXSS } from 'xss';

const filter = (val) => {
  return filterXSS(val, {
    // @ts-ignore
    whiteList: [],
    stripIgnoreTag: true,
    stripIgnoreTagBody: ["script"]
  })
}


const getCdnMarkup = ({ version, cdnUrl = '//cdn.jsdelivr.net/npm', faviconUrl }) => {
  const buildCDNUrl = (packageName: string, suffix: string) => filter(`${cdnUrl}/${packageName}/${version ? `@${version}/` : ''}${suffix}` || '')
  return `
    <link 
      rel="stylesheet" 
      href="${buildCDNUrl('graphql-playground-react', 'build/static/css/index.css')}"
    />
    ${typeof faviconUrl === 'string' ? `<link rel="shortcut icon" href="${filter(faviconUrl || '')}" />` : ''}
    ${faviconUrl === undefined ? `<link rel="shortcut icon" href="${buildCDNUrl('graphql-playground-react', 'build/favicon.png')}" />` : ''}
    <script 
      src="${buildCDNUrl('graphql-playground-react', 'build/static/js/middleware.js')}"
    ></script>
`}


const renderConfig = (config) => {
  return filterXSS(`<div id="config">${JSON.stringify(config)}</div>`, {
    whiteList: { div: ['id'] },
  })
}
    extendedOptions.subscriptionEndpoint = filter((options as any).subscriptionsEndpoint || '')
  else if (extendedOptions.endpoint) {
    extendedOptions.endpoint = filter(extendedOptions.endpoint || '')
  }
    extendedOptions.env === 'react' || extendedOptions.env === 'electron'
      ? ''
      : getCdnMarkup(extendedOptions)
    ${renderConfig(extendedOptions)}
        const configText = document.getElementById('config').innerText
        if(configText && configText.length) {
          try {
            GraphQLPlayground.init(root, JSON.parse(configText))
          }
          catch(err) {
            console.error("could not find config")
          }
        }
> **SECURITY NOTE**: All versions of `graphql-playground-express` until `1.7.15` or later have a security vulnerability when unsanitized user input is used while invoking `expressPlayground()`. [Read more below](#security-notes)

## Security Notes

All versions before `1.7.15` were vulnerable to user-defined input to `expressPlayground()`. Read more in [the security notes](https://github.com/prisma/graphql-playground/tree/master/SECURITY.md)

### Security Upgrade Steps

To fix the issue, you can upgrade to `1.6.12` or later. If you aren't able to upgrade, see the security notes for a workaround.

**yarn:**
`yarn add graphql-playground-express@^1.7.15`

**npm:**
`npm install --save graphql-playground-express@^1.7.15`
const express = require('express')
const { ApolloServer, gql } = require('apollo-server-express')
const expressPlayground = require('../../dist/index').default
`
    hello: () => 'world',
  },
}
const PORT = 4000
const server = new ApolloServer({ typeDefs, resolvers })
const app = express()
server.applyMiddleware({ app })
app.get(
  '/playground',
  expressPlayground({
    endpoint: '/graphql/</script><script>alert(1)</script><script>',
  }),
)
app.listen(PORT)
  `Serving the GraphQL Playground on http://localhost:${PORT}/playground`,
)
# graphql-playground-middleware-hapi

> Koa middleware to expose an endpoint for the GraphQL Playground IDE
> **SECURITY NOTE**: All versions of `graphql-playground-middleware-hapi` until `1.6.12` or later have a security vulnerability when unsanitized user input is used while invoking `hapiPlayground()`. [Read more below](#security-notes)

## Installation

Using yarn:

```console
yarn add graphql-playground-middleware-hapi
```

Or npm:

```console
npm install graphql-playground-middleware-hapi --save
```

## Usage

See full example in [examples/basic](https://github.com/prisma/graphql-playground/tree/master/packages/graphql-playground-middleware-hapi/examples/basic).

minimal example:

```js
const hapiPlayground = require('graphql-playground-middleware-hapi').default

const playground = {
  plugin: hapiPlayground,
  options: {
    path: '/playground',
    endpoint: '/graphql',
  },
}

const app = new Hapi.server({
  port: 3000,
})

app.register(playground)
;(async () => await app.start())()
```

## Security Notes

All versions before `1.6.12` were vulnerable to user-defined input to `hapiPlayground()`. Read more in [the security notes](https://github.com/prisma/graphql-playground/tree/master/SECURITY.md)

### Security Upgrade Steps

To fix the issue, you can upgrade to `1.6.12` or later. If you aren't able to upgrade, see the security notes for a workaround.

**yarn:**
`yarn add graphql-playground-middleware-hapi@^1.6.12`

**npm:**
`npm install --save graphql-playground-middleware-hapi@^1.6.12`

> **SECURITY NOTE**: All versions of `graphql-playground-koa` until `1.6.14` or later have a security vulnerability when unsanitized user input is used while invoking `koaPlayground()`. [Read more below](#security-notes)

## Security Notes

All versions before `1.6.14` were vulnerable to user-defined input to `koaPlayground()`. Read more in [the security notes](https://github.com/prisma/graphql-playground/tree/master/SECURITY.md)

### Security Upgrade Steps

To fix the issue, you can upgrade to `1.6.14` or later. If you aren't able to upgrade, see the security notes for a workaround.

**yarn:**
`yarn add graphql-playground-koa@^1.6.14`

**npm:**
`npm install --save graphql-playground-koa@^1.6.14`
# graphql-playground-middleware-lambda

> Koa middleware to expose an endpoint for the GraphQL Playground IDE
> **SECURITY NOTE**: All versions of `graphql-playground-middleware-lambda` until `1.7.16` or later have a security vulnerability when unsanitized user input is used while invoking `lambdaPlayground()`. [Read more below](#security-notes)

## Installation

Using yarn:

```console
yarn add graphql-playground-middleware-lambda
```

Or npm:

```console
npm install graphql-playground-middleware-lambda --save
```

## Usage

See full example in [examples/basic](https://github.com/prisma/graphql-playground/tree/master/packages/graphql-playground-middleware-lambda/examples/basic).

minimal example:

```js
const lambdaPlayground = require('graphql-playground-middleware-lambda').default

exports.handler = lambdaPlayground({
  endpoint: '/dev',
})
```

## Security Notes

All versions before `1.7.16` were vulnerable to user-defined input to `lambdaPlayground()`. Read more in [the security notes](https://github.com/prisma/graphql-playground/tree/SECURITY.md)

### Security Upgrade Steps

To fix the issue, you can upgrade to `1.7.16`. If you aren't able to upgrade, see the security notes for a workaround.

**yarn:**
`yarn add graphql-playground-middleware-lambda@^1.7.16`

**npm:**
`npm install --save graphql-playground-middleware-lambda@^1.7.16`


exports.playgroundHandler = lambdaPlayground({
endpoint: '/dev',
})
<link
  href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700|Source+Code+Pro:400,700"
  rel="stylesheet"
/>
  <Playground endpoint='https://api.graph.cool/simple/v1/swapi' />,
      - http:
          path: graphql
          method: post
          cors: true
      - http:
          path: playground
          method: get
          cors: true
  graphql-playground-react
cssfilter@0.0.10:
  version "0.0.10"
  resolved "https://registry.yarnpkg.com/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae"
  integrity sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=

xss@^1.0.6:
  version "1.0.6"
  resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.6.tgz#eaf11e9fc476e3ae289944a1009efddd8a124b51"
  integrity sha512-6Q9TPBeNyoTRxgZFk5Ggaepk/4vUOYdOsIUYvLehcsIZTFjaavbVnsuAkLA5lIFuug5hw8zxcB9tm01gsjph2A==
  dependencies:
    commander "^2.9.0"
    cssfilter "0.0.10"