mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 09:09:36 +02:00
Linked URL: prevent SSRF via DNS rebinding; minor fixes
This commit is contained in:
@@ -88,7 +88,7 @@ services:
|
||||
volumes:
|
||||
- ../services/linked-url-proxy/app:/overleaf/services/linked-url-proxy/app
|
||||
- ../services/linked-url-proxy/config:/overleaf/services/linked-url-proxy/config
|
||||
- ../services/linked-url-proxy/app.js:/overleaf/services/linked-url-proxy/app.js
|
||||
- ../services/linked-url-proxy/app.mjs:/overleaf/services/linked-url-proxy/app.mjs
|
||||
|
||||
notifications:
|
||||
command: ["node", "--watch", "app.ts"]
|
||||
|
||||
@@ -33,7 +33,7 @@ const server = app.listen(port, host, function (error) {
|
||||
process.on('SIGTERM', () => {
|
||||
server.close(() => {
|
||||
logger.info({ host, port }, 'linked-url-proxy HTTP server closed')
|
||||
metrics.close()
|
||||
Metrics.close()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Transform } from 'node:stream'
|
||||
import logger from '@overleaf/logger'
|
||||
import Settings from '@overleaf/settings'
|
||||
import { fetchStreamWithResponse, RequestFailedError } from '@overleaf/fetch-utils'
|
||||
import { Agent } from 'undici'
|
||||
|
||||
function isAllowedResource(targetUrl) {
|
||||
if (!Settings.allowedResources) return false
|
||||
@@ -46,7 +47,7 @@ async function checkUrlAccess(hostname, targetUrl) {
|
||||
throw err
|
||||
}
|
||||
// Permit explicitly allowed resources without checking blocked IPs
|
||||
if (isAllowedResource(targetUrl)) return
|
||||
if (isAllowedResource(targetUrl)) return records[0].address
|
||||
for (const { address } of records) {
|
||||
if (isBlockedIp(address, targetUrl)) {
|
||||
const err = new Error(`Blocked IP address: ${address}`)
|
||||
@@ -54,6 +55,7 @@ async function checkUrlAccess(hostname, targetUrl) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
return records[0].address
|
||||
}
|
||||
|
||||
async function validateAndFetch(rawUrl, redirectCount = 0) {
|
||||
@@ -89,7 +91,16 @@ async function validateAndFetch(rawUrl, redirectCount = 0) {
|
||||
const normalizedUrl = url.toString()
|
||||
|
||||
// check DNS and allowed resources
|
||||
await checkUrlAccess(url.hostname, normalizedUrl)
|
||||
const validatedIp = await checkUrlAccess(url.hostname, normalizedUrl)
|
||||
|
||||
// pin fetch to validated IP to prevent DNS rebinding
|
||||
const agent = new Agent({
|
||||
connect: {
|
||||
lookup(hostname, opts, cb) {
|
||||
cb(null, validatedIp, 4)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const opts = {
|
||||
redirect: 'manual',
|
||||
@@ -98,7 +109,7 @@ async function validateAndFetch(rawUrl, redirectCount = 0) {
|
||||
}
|
||||
|
||||
try {
|
||||
const { stream, response } = await fetchStreamWithResponse(normalizedUrl, opts)
|
||||
const { stream, response } = await fetchStreamWithResponse(normalizedUrl, { ...opts, dispatcher: agent })
|
||||
|
||||
const contentLengthHeader = response.headers.get('content-length')
|
||||
if (contentLengthHeader) {
|
||||
|
||||
@@ -16,10 +16,7 @@
|
||||
"express": "^4.22.1",
|
||||
"ipaddr.js": "^2.1.0",
|
||||
"als-normalize-urlpath": "^2.3.0",
|
||||
"strict-url-sanitise": "^0.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"als-normalize-urlpath": "^2.3.0",
|
||||
"strict-url-sanitise": "^0.0.1"
|
||||
"strict-url-sanitise": "^0.0.1",
|
||||
"undici": "^7.22.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,6 @@
|
||||
"accepts": "^1.3.7",
|
||||
"ai": "^6.0.2",
|
||||
"ajv": "^8.12.0",
|
||||
"als-normalize-urlpath": "^2.3.0",
|
||||
"archiver": "^5.3.0",
|
||||
"async": "^3.2.5",
|
||||
"base-x": "^4.0.1",
|
||||
@@ -186,7 +185,6 @@
|
||||
"request": "2.88.2",
|
||||
"requestretry": "^7.1.0",
|
||||
"sanitize-html": "^2.8.1",
|
||||
"strict-url-sanitise": "^0.0.1",
|
||||
"stripe": "^18.4.0",
|
||||
"tough-cookie": "^4.0.0",
|
||||
"tsscmp": "^1.0.6",
|
||||
|
||||
Reference in New Issue
Block a user