Skip to content

Instantly share code, notes, and snippets.

@hasegawayosuke
Created February 23, 2023 11:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hasegawayosuke/6ed8307d4a61a7923bfae0e9bb481fff to your computer and use it in GitHub Desktop.
Save hasegawayosuke/6ed8307d4a61a7923bfae0e9bb481fff to your computer and use it in GitHub Desktop.
// SSRF保護の実装サンプル
// (これでも漏れがあるかも)
'use strict'
const os = require('os')
const net = require('net')
const dns = require('dns')
const http = require('http')
const https = require('https')
const myFetch = (url) => {
let nest = 0
const badAddresses = ['169.254.169.254']
const badPorts = [
0, 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53,
69, 77, 87, 101, 102, 103, 104, 109, 110, 111, 113, 115, 117, 119,
123, 135, 137, 139, 143, 161, 179, 389, 427, 465, 512, 513, 514,
515, 526, 530, 531, 532, 540, 548, 554, 556, 563, 587, 601, 636,
989, 990, 993, 995, 1719, 1720, 1723, 2049, 3659, 4045, 5060, 5061,
6000, 6566, 6665, 6666, 6667, 6668, 6669, 6697, 10081
]
const blockList = new net.BlockList()
badAddresses.forEach(address => {
blockList.addAddress(address)
})
Object.values(os.networkInterfaces()).forEach(nic => {
nic.forEach(o => {
const [network, prefix] = o.cidr.split(/\//)
blockList.addSubnet(network, prefix | 0, o.family)
})
})
const _lookup = (hostname, options, callback) => {
const _callback = (err, address, family) => {
if (!err && blockList.check(address, `ipv${family}`)) {
throw new Error(`Invalid address: ${address}`)
}
callback.apply(this, [err, address, family])
}
dns.lookup.apply(this, [hostname, options, _callback])
}
const _request = (url) => {
const objUrl = new URL(url)
const port = objUrl.port ? objUrl.port | 0 : objUrl.protocol === 'https:' ? 443 : 80
if (badPorts.includes(port)) {
throw new Error(`Invalid port: ${port}`)
}
if (net.isIP(objUrl.hostname)) {
if (blockList.check(objUrl.hostname)) {
throw new Error(`Invalid address: ${objUrl.hostname}`)
}
}
const options = Object.create(null)
options.method = 'GET'
options.lookup = _lookup
const req = (objUrl.protocol === 'https:' ? https : http).request(url, options, (res) => {
let body = ''
res.on('data', (chunk) => {
body += chunk
})
res.on('end', () => {
if (res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 303) {
nest++
if (nest > 10) {
throw new Error('Too match redirection')
}
_request(res.headers.location)
} else {
console.log(`${url}\n${body.substring(0, 100)}\n\n`)
}
})
})
req.on('socket', (socket) => {
socket.on('connect', () => {
const { remoteAddress, remoteFamily } = socket
if (blockList.check(remoteAddress, remoteFamily)) {
throw new Error(`Invalid address: ${remoteAddress}`)
}
})
})
req.end()
}
_request(url)
}
// myFetch('http://example.jp:25/') // fail
// myFetch('http://169.254.169.254/') // fail
myFetch('http://example.jp/')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment