Skip to content
Snippets Groups Projects
Commit df40b7be authored by julian.baeume's avatar julian.baeume :pick:
Browse files

Revert "Fix: OXUIB-2698: SSRF/XSS: Prevent to cache resources from external origins"

This reverts commit d3bc298f.
parent ea62b6a9
No related branches found
No related tags found
No related merge requests found
...@@ -30,7 +30,7 @@ describe('File caching service', function () { ...@@ -30,7 +30,7 @@ describe('File caching service', function () {
let app, pubClient let app, pubClient
beforeEach(async function () { beforeEach(async function () {
await mockConfig({ baseUrls: ['http://ui-server'] }) await mockConfig({ baseUrls: ['http://ui-server/'] })
mockFetch({ mockFetch({
'http://ui-server': { 'http://ui-server': {
'/manifest.json': generateSimpleViteManifest({ '/manifest.json': generateSimpleViteManifest({
...@@ -62,7 +62,7 @@ describe('File caching service', function () { ...@@ -62,7 +62,7 @@ describe('File caching service', function () {
const version = response.headers.version const version = response.headers.version
const { client } = await import('../src/redis.js') const { client } = await import('../src/redis.js')
expect(await client.get(getRedisKey({ version, name: 'viteManifests' }))).to.equal('{"index.html":{"file":"index.html","meta":{"baseUrl":"http://ui-server"}}}') expect(await client.get(getRedisKey({ version, name: 'viteManifests' }))).to.equal('{"index.html":{"file":"index.html","meta":{"baseUrl":"http://ui-server/"}}}')
const redisData = await client.getBuffer(getRedisKey({ version, name: 'oxManifests:body' })) const redisData = await client.getBuffer(getRedisKey({ version, name: 'oxManifests:body' }))
expect(zlib.brotliDecompressSync(redisData || '').toString()).to.equal('[]') expect(zlib.brotliDecompressSync(redisData || '').toString()).to.equal('[]')
}) })
...@@ -95,9 +95,4 @@ describe('File caching service', function () { ...@@ -95,9 +95,4 @@ describe('File caching service', function () {
const response2 = await app.inject({ url: '/demo.js', headers: { version } }) const response2 = await app.inject({ url: '/demo.js', headers: { version } })
expect(response2.statusCode).to.equal(200) expect(response2.statusCode).to.equal(200)
}) })
it('does not fetch from origins not defined in baseUrls', async function () {
const response = await app.inject({ url: '//t989be0.netlify.app/xss.html' })
expect(response.statusCode).to.equal(400)
})
}) })
...@@ -32,7 +32,7 @@ describe('Configuration', function () { ...@@ -32,7 +32,7 @@ describe('Configuration', function () {
beforeEach(async function () { beforeEach(async function () {
// need to set the redis-prefix. Otherwise, the bull workers will interfere // need to set the redis-prefix. Otherwise, the bull workers will interfere
process.env.REDIS_PREFIX = Math.random().toString() process.env.REDIS_PREFIX = Math.random().toString()
await mockConfig(config = { baseUrls: ['http://ui-server'] }) await mockConfig(config = { baseUrls: ['http://ui-server/'] })
mockFetch({ mockFetch({
'http://ui-server': { 'http://ui-server': {
'/manifest.json': generateSimpleViteManifest({ '/manifest.json': generateSimpleViteManifest({
......
...@@ -33,7 +33,7 @@ describe('Updates the version', function () { ...@@ -33,7 +33,7 @@ describe('Updates the version', function () {
beforeEach(async function () { beforeEach(async function () {
// need to set the redis-prefix. Otherwise, the bull workers will interfere // need to set the redis-prefix. Otherwise, the bull workers will interfere
process.env.REDIS_PREFIX = Math.random().toString() process.env.REDIS_PREFIX = Math.random().toString()
await mockConfig({ baseUrls: ['http://ui-server'] }) await mockConfig({ baseUrls: ['http://ui-server/'] })
mockFetch({ mockFetch({
'http://ui-server': { 'http://ui-server': {
'/manifest.json': generateSimpleViteManifest({ '/manifest.json': generateSimpleViteManifest({
...@@ -109,7 +109,7 @@ describe('Updates the version', function () { ...@@ -109,7 +109,7 @@ describe('Updates the version', function () {
td.reset() td.reset()
// need to set the redis-prefix. Otherwise, the bull workers will interfere // need to set the redis-prefix. Otherwise, the bull workers will interfere
process.env.REDIS_PREFIX = Math.random().toString() process.env.REDIS_PREFIX = Math.random().toString()
await mockConfig({ baseUrls: ['http://ui-server'] }) await mockConfig({ baseUrls: ['http://ui-server/'] })
mockFetch({ mockFetch({
'http://ui-server': { 'http://ui-server': {
'/manifest.json': generateSimpleViteManifest({ '/manifest.json': generateSimpleViteManifest({
......
...@@ -29,7 +29,7 @@ describe('With different app root', function () { ...@@ -29,7 +29,7 @@ describe('With different app root', function () {
let app let app
beforeEach(async function () { beforeEach(async function () {
await mockConfig({ baseUrls: ['http://ui-server'] }) await mockConfig({ baseUrls: ['http://ui-server/'] })
const { client } = await mockRedis() const { client } = await mockRedis()
mockFetch({ mockFetch({
'http://ui-server': { 'http://ui-server': {
......
...@@ -37,7 +37,7 @@ describe('File caching service', function () { ...@@ -37,7 +37,7 @@ describe('File caching service', function () {
let redis let redis
beforeEach(async function () { beforeEach(async function () {
await mockConfig({ baseUrls: ['http://ui-server'] }) await mockConfig({ baseUrls: ['http://ui-server/'] })
redis = await mockRedis() redis = await mockRedis()
mockFetch({ mockFetch({
'http://ui-server': {} 'http://ui-server': {}
...@@ -273,9 +273,4 @@ describe('File caching service', function () { ...@@ -273,9 +273,4 @@ describe('File caching service', function () {
// check for files in redis // check for files in redis
expect(await redis.client.getBuffer('ui-middleware:554855300:/file.svg:body')).to.deep.equal(response.rawPayload) expect(await redis.client.getBuffer('ui-middleware:554855300:/file.svg:body')).to.deep.equal(response.rawPayload)
}) })
it('does not fetch from origins not defined in baseUrls', async function () {
const response = await app.inject({ url: '//t989be0.netlify.app/xss.html' })
expect(response.statusCode).to.equal(400)
})
}) })
...@@ -29,7 +29,7 @@ describe('Redirects', function () { ...@@ -29,7 +29,7 @@ describe('Redirects', function () {
let app let app
before(async function () { before(async function () {
await mockConfig({ baseUrls: ['http://ui-server'] }) await mockConfig({ baseUrls: ['http://ui-server/'] })
await mockRedis() await mockRedis()
mockFetch({ mockFetch({
'http://ui-server': { 'http://ui-server': {
......
...@@ -30,7 +30,7 @@ describe('Salt', function () { ...@@ -30,7 +30,7 @@ describe('Salt', function () {
let config let config
beforeEach(async function () { beforeEach(async function () {
await mockConfig(config = { baseUrls: ['http://ui-server'] }) await mockConfig(config = { baseUrls: ['http://ui-server/'] })
await mockRedis() await mockRedis()
mockFetch({ mockFetch({
'http://ui-server': { 'http://ui-server': {
......
...@@ -30,7 +30,7 @@ describe('UI Middleware', function () { ...@@ -30,7 +30,7 @@ describe('UI Middleware', function () {
let fetchConfig let fetchConfig
beforeEach(async function () { beforeEach(async function () {
await mockConfig({ baseUrls: ['http://ui-server'] }) await mockConfig({ baseUrls: ['http://ui-server/'] })
await mockRedis() await mockRedis()
mockFetch(fetchConfig = { mockFetch(fetchConfig = {
'http://ui-server': { 'http://ui-server': {
...@@ -77,7 +77,7 @@ describe('UI Middleware', function () { ...@@ -77,7 +77,7 @@ describe('UI Middleware', function () {
describe('multiple configurations', function () { describe('multiple configurations', function () {
beforeEach(async function () { beforeEach(async function () {
await mockConfig({ baseUrls: ['http://ui-server', 'http://ui-server2'] }) await mockConfig({ baseUrls: ['http://ui-server/', 'http://ui-server2/'] })
fetchConfig['http://ui-server2'] = { fetchConfig['http://ui-server2'] = {
'/manifest.json': generateSimpleViteManifest({ 'example2.js': 'thing' }), '/manifest.json': generateSimpleViteManifest({ 'example2.js': 'thing' }),
'/example2.js': '' '/example2.js': ''
......
...@@ -60,12 +60,7 @@ export function mockConfig (obj = {}) { ...@@ -60,12 +60,7 @@ export function mockConfig (obj = {}) {
export function mockFetch (servers = {}) { export function mockFetch (servers = {}) {
td.replace(global, 'fetch', async function ({ origin, pathname }, ...args) { td.replace(global, 'fetch', async function ({ origin, pathname }, ...args) {
const response = servers[origin]?.[pathname] const response = servers[origin]?.[pathname]
if (response === undefined) { if (response === undefined) return new Response('', { status: 404 })
if (origin === 'http://t989be0.netlify.app') {
return new Response('<html><code>Proof of Concept</code><script>alert(document.domain)</script></html>', { status: 200, headers: { 'Content-Type': 'text/html' } })
}
return new Response('', { status: 404 })
}
if (response instanceof Function) return response.apply(this, arguments) if (response instanceof Function) return response.apply(this, arguments)
if (typeof response === 'object') { if (typeof response === 'object') {
......
...@@ -32,7 +32,7 @@ describe('version mismatches', function () { ...@@ -32,7 +32,7 @@ describe('version mismatches', function () {
let runUpdate let runUpdate
beforeEach(async function () { beforeEach(async function () {
await mockConfig({ baseUrls: ['http://ui-server'] }) await mockConfig({ baseUrls: ['http://ui-server/'] })
const { createClient } = await mockRedis() const { createClient } = await mockRedis()
mockFetch({ mockFetch({
'http://ui-server': { 'http://ui-server': {
......
...@@ -27,13 +27,6 @@ export class NotFoundError extends Error { ...@@ -27,13 +27,6 @@ export class NotFoundError extends Error {
} }
} }
export class NotAllowedOriginError extends Error {
constructor (message, options = {}) {
super(message, options)
this.status = options.status
}
}
export class VersionMismatchError extends Error {} export class VersionMismatchError extends Error {}
/** /**
...@@ -55,8 +48,3 @@ export function isNotFoundError (err) { ...@@ -55,8 +48,3 @@ export function isNotFoundError (err) {
const errors = err instanceof AggregateError ? err.errors : [err] const errors = err instanceof AggregateError ? err.errors : [err]
return errors.some(error => error instanceof NotFoundError) return errors.some(error => error instanceof NotFoundError)
} }
export function isNotAllowedOriginError (err) {
const errors = err instanceof AggregateError ? err.errors : [err]
return errors.some(error => error instanceof NotAllowedOriginError)
}
...@@ -25,7 +25,7 @@ import { promisify } from 'node:util' ...@@ -25,7 +25,7 @@ import { promisify } from 'node:util'
import zlib from 'node:zlib' import zlib from 'node:zlib'
import * as cache from './cache.js' import * as cache from './cache.js'
import { configMap } from './config_map.js' import { configMap } from './config_map.js'
import { NotAllowedOriginError, NotFoundError, VersionMismatchError, isVersionMismatchError } from './errors.js' import { NotFoundError, VersionMismatchError, isVersionMismatchError } from './errors.js'
import logger from './logger.js' import logger from './logger.js'
import { getCSSDependenciesFor, getViteManifests } from './manifests.js' import { getCSSDependenciesFor, getViteManifests } from './manifests.js'
import { getVersionInfo } from './version.js' import { getVersionInfo } from './version.js'
...@@ -38,12 +38,8 @@ const compressionMimeTypes = (process.env.COMPRESS_FILE_TYPES || '').replace(/([ ...@@ -38,12 +38,8 @@ const compressionMimeTypes = (process.env.COMPRESS_FILE_TYPES || '').replace(/([
const compressionWhitelistRegex = new RegExp(`^(${compressionMimeTypes.join('|')})($|;)`, 'i') const compressionWhitelistRegex = new RegExp(`^(${compressionMimeTypes.join('|')})($|;)`, 'i')
export async function fetchFileWithHeadersFromBaseUrl ({ path, baseUrl, version }) { export async function fetchFileWithHeadersFromBaseUrl ({ path, baseUrl, version }) {
const upstreamUrl = new URL(path, baseUrl)
if (upstreamUrl.origin !== baseUrl) {
throw new NotAllowedOriginError('This origin is not allowed', { status: 400 })
}
const [response, dependencies] = await Promise.all([ const [response, dependencies] = await Promise.all([
fetch(upstreamUrl, { cache: 'no-store' }), fetch(new URL(path, baseUrl), { cache: 'no-store' }),
nodePath.extname(path) === '.js' && getCSSDependenciesFor({ file: path.substr(1), version }) nodePath.extname(path) === '.js' && getCSSDependenciesFor({ file: path.substr(1), version })
]) ])
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
*/ */
import { getFile } from '../files.js' import { getFile } from '../files.js'
import { isNotAllowedOriginError, isNotFoundError, isVersionMismatchError } from '../errors.js' import { isNotFoundError, isVersionMismatchError } from '../errors.js'
export default async function serveFilePlugin (fastify, options) { export default async function serveFilePlugin (fastify, options) {
fastify.get('*', async (req, reply) => { fastify.get('*', async (req, reply) => {
...@@ -35,7 +35,6 @@ export default async function serveFilePlugin (fastify, options) { ...@@ -35,7 +35,6 @@ export default async function serveFilePlugin (fastify, options) {
reply.send(body) reply.send(body)
} catch (err) { } catch (err) {
if (isNotFoundError(err) || isVersionMismatchError(err)) throw fastify.httpErrors.createError(404, `File "${req.urlData('path')}" does not exist.`, err) if (isNotFoundError(err) || isVersionMismatchError(err)) throw fastify.httpErrors.createError(404, `File "${req.urlData('path')}" does not exist.`, err)
if (isNotAllowedOriginError(err)) throw fastify.httpErrors.createError(400, err)
throw fastify.httpErrors.createError(err.statusCode || 500, err) throw fastify.httpErrors.createError(err.statusCode || 500, err)
} }
}) })
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment