fix(backend): check target IP before sending HTTP request

This commit is contained in:
rectcoordsystem 2024-11-06 05:31:11 +09:00 committed by Julia Johannesen
parent cc4e99fdde
commit f36f4b5398
No known key found for this signature in database
GPG Key ID: 4A1377AF3E7FBC46
2 changed files with 88 additions and 24 deletions

View File

@ -6,7 +6,6 @@
import * as fs from 'node:fs';
import * as stream from 'node:stream/promises';
import { Inject, Injectable } from '@nestjs/common';
import ipaddr from 'ipaddr.js';
import chalk from 'chalk';
import got, * as Got from 'got';
import { parse } from 'content-disposition';
@ -70,13 +69,6 @@ export class DownloadService {
},
enableUnixSockets: false,
}).on('response', (res: Got.Response) => {
if ((process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') && !this.config.proxy && res.ip) {
if (this.isPrivateIp(res.ip)) {
this.logger.warn(`Blocked address: ${res.ip}`);
req.destroy();
}
}
const contentLength = res.headers['content-length'];
if (contentLength != null) {
const size = Number(contentLength);
@ -139,18 +131,4 @@ export class DownloadService {
cleanup();
}
}
@bindThis
private isPrivateIp(ip: string): boolean {
const parsedIp = ipaddr.parse(ip);
for (const net of this.config.allowedPrivateNetworks ?? []) {
const cidr = ipaddr.parseCIDR(net);
if (cidr[0].kind() === parsedIp.kind() && parsedIp.match(ipaddr.parseCIDR(net))) {
return false;
}
}
return parsedIp.range() !== 'unicast';
}
}

View File

@ -6,6 +6,7 @@
import * as http from 'node:http';
import * as https from 'node:https';
import * as net from 'node:net';
import ipaddr from 'ipaddr.js';
import CacheableLookup from 'cacheable-lookup';
import fetch from 'node-fetch';
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
@ -25,6 +26,91 @@ export type HttpRequestSendOptions = {
validators?: ((res: Response) => void)[];
};
@Injectable()
class HttpRequestServiceAgent extends http.Agent {
constructor(
@Inject(DI.config)
private config: Config,
options?: Object
) {
super(options);
}
@bindThis
public createConnection(options: Object, callback?: Function): net.Socket {
const socket = super.createConnection(options, callback)
.on('connect', ()=>{
const address = socket.remoteAddress;
if (process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') {
if (address && ipaddr.isValid(address)) {
if (this.isPrivateIp(address)) {
socket.destroy(new Error(`Blocked address: ${address}`));
}
}
}
});
return socket;
};
@bindThis
private isPrivateIp(ip: string): boolean {
const parsedIp = ipaddr.parse(ip);
for (const net of this.config.allowedPrivateNetworks ?? []) {
const cidr = ipaddr.parseCIDR(net);
if (cidr[0].kind() === parsedIp.kind() && parsedIp.match(ipaddr.parseCIDR(net))) {
return false;
}
}
return parsedIp.range() !== 'unicast';
}
}
@Injectable()
class HttpsRequestServiceAgent extends https.Agent {
constructor(
@Inject(DI.config)
private config: Config,
options?: Object
) {
super(options);
}
@bindThis
public createConnection(options: Object, callback?: Function): net.Socket {
const socket = super.createConnection(options, callback)
.on('connect', ()=>{
const address = socket.remoteAddress;
if (process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') {
if (address && ipaddr.isValid(address)) {
if (this.isPrivateIp(address)) {
socket.destroy(new Error(`Blocked address: ${address}`));
}
}
}
});
return socket;
};
@bindThis
private isPrivateIp(ip: string): boolean {
const parsedIp = ipaddr.parse(ip);
for (const net of this.config.allowedPrivateNetworks ?? []) {
const cidr = ipaddr.parseCIDR(net);
if (cidr[0].kind() === parsedIp.kind() && parsedIp.match(ipaddr.parseCIDR(net))) {
return false;
}
}
return parsedIp.range() !== 'unicast';
}
}
@Injectable()
export class HttpRequestService {
/**
@ -57,14 +143,14 @@ export class HttpRequestService {
lookup: false, // nativeのdns.lookupにfallbackしない
});
this.http = new http.Agent({
this.http = new HttpRequestServiceAgent(config, {
keepAlive: true,
keepAliveMsecs: 30 * 1000,
lookup: cache.lookup as unknown as net.LookupFunction,
localAddress: config.outgoingAddress,
});
this.https = new https.Agent({
this.https = new HttpsRequestServiceAgent(config, {
keepAlive: true,
keepAliveMsecs: 30 * 1000,
lookup: cache.lookup as unknown as net.LookupFunction,