hanover-computing / got-ssrf Goto Github PK
View Code? Open in Web Editor NEWProtect untrusted requests from SSRF
License: GNU Lesser General Public License v3.0
Protect untrusted requests from SSRF
License: GNU Lesser General Public License v3.0
Hello,
This library is fantastic. Thanks for that. I found an attack that is not being covered.
I setup a DNS record on my domain pointing to itself:
Nothing evil there. However, I added a redirection rule to redirect that URL into something evil:
So the library is preventing malicious attacks by checking for the hostname.
https://github.com/JaneJeon/got-ssrf/blob/master/index.js#L35
There the hostname is going to be 'local.urlint.co'
being resolved into a unicast IP address.
However, if you interact with the resource, that URL is going to redirect you into the evil URL
$ curl -I -s -X GET https://local.urlint.co/dns
HTTP/2 301
date: Fri, 24 Sep 2021 14:28:30 GMT
cache-control: max-age=3600
expires: Fri, 24 Sep 2021 15:28:30 GMT
location: http://192.168.0.1
That's because we checked the DNS over the hostname but not over the hostname over the location.
Plus, most HTTP clients follow redirects by default (that's the case of got
and that in fact a good expected behavior).
I wrote some code as suggestions to prevent the attack:
const { headers } = await got.head(url, { followRedirect: false })
const redirectHostname = new URL(headers.location).hostname
const { address } = await cacheableLookup.lookupAsync(url.hostname)
if (
ip.parse(address).range() !== 'unicast' ||
ip.parse(redirectHostname).range() !== 'unicast'
) {
throw new Error("You're evil")
}
However, I don't like it at all, since the got.head
extra network request needs to be performed. Maybe that could be handle at the DNS level?
There was a reason but I forgot…
This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.
These updates have all been created already. Click a checkbox below to force a retry/rebase of any.
.github/workflows/ci.yml
actions/checkout v4
actions/setup-node v3
actions/checkout v4
actions/setup-node v3
codecov/codecov-action v4
.github/workflows/codeql-analysis.yml
actions/checkout v4
github/codeql-action v2
github/codeql-action v2
github/codeql-action v2
.github/workflows/ossar-analysis.yml
actions/checkout v4
github/ossar-action v1
github/codeql-action v2
package.json
debug ^4.3.2
ipaddr.js ^2.0.1
@janejeon/eslint-config-typescript ^0.1.0
@janejeon/prettier-config ^2.0.0
@janejeon/tsconfig ^0.3.1
@types/debug ^4.1.8
@types/node ^20.5.1
@vitest/coverage-istanbul ^1.0.0
@vitest/ui ^1.0.0
cacheable-lookup ^7.0.0
lint-staged ^15.0.0
nock ^13.1.3
npm-run-all2 ^6.0.0
skip-ci ^1.0.4
typescript ^5.1.6
vitest ^1.0.0
got ^14
node >=16
.nvmrc
node 20
Hello,
I noted the SSRF protection is not working effectively against IPv6 URLs
For example, http://[::ffff:7f00:1]:1338/
is a valid URL; let's use got-ssrf
for hitting it:
import { gotSsrf } from 'got-ssrf'
const result = await gotSsrf('http://[::ffff:7f00:1]:1338/')
console.log(result.body)
In case you want to run a local server, here is the code:
const { listen } = require('async-listen')
const { createServer } = require('http')
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>OH NO YOU'RE COMPROMISED</h1>
</body>
</html>`
const server = () =>
createServer((req, res) => {
res.setHeader('Content-Length', Buffer.byteLength(html))
res.setHeader('Content-Type', 'text/html; charset=UTF-8')
res.end(html)
})
Promise.resolve().then(async () => {
const serverIPv4url = await listen(server(), {
host: '192.168.4.50',
port: '1337'
})
const serverIPv6url = await listen(server(), {
host: '::ffff:127.0.0.1',
port: '1338'
})
console.log('> Listening at IPv4:', serverIPv4url.toString())
console.log('> Listening at IPv6:', serverIPv6url.toString())
})
The SSRF protection is not working for a little implementation detail at this point:
https://github.com/hanover-computing/got-ssrf/blob/master/index.js#L33
The options.url.hostname
there is [::ffff:7f00:1]
. That is the URL hostname representation of the hostname according to whatwg URL spec:
https://url.spec.whatwg.org/#host-serializing
However, brackets there should be removed in order to pass the value against DNS lookup.
If the code is replaced by something like:
const hostname = options.url.hostname.replace('[', '').replace(']', '')
const { address } = await lookup(hostname)
Then hit the server throw the same error as IPv4 version:
RequestError: The IP of the domain is reserved!
And figure out CircleCI config for libraries (without package-lock) while I'm at it
This also means we can import straight from dns/promises
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.