Skip to content

Commit c7329fe

Browse files
Hotfix: Prevent SSRF (#3410)
* Reproducing the Vulnerability * Prevent SSRF * Cleanup * Refactor to skip duplicate code * Tests for correct passed data. * Code review changes.
1 parent f472e5d commit c7329fe

File tree

2 files changed

+87
-10
lines changed

2 files changed

+87
-10
lines changed

lib/adapters/http.js

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,31 @@ var enhanceError = require('../core/enhanceError');
1616

1717
var isHttps = /https:?/;
1818

19+
/**
20+
*
21+
* @param {http.ClientRequestArgs} options
22+
* @param {AxiosProxyConfig} proxy
23+
* @param {string} location
24+
*/
25+
function setProxy(options, proxy, location) {
26+
options.hostname = proxy.host;
27+
options.host = proxy.host;
28+
options.port = proxy.port;
29+
options.path = location;
30+
31+
// Basic proxy authorization
32+
if (proxy.auth) {
33+
var base64 = Buffer.from(proxy.auth.username + ':' + proxy.auth.password, 'utf8').toString('base64');
34+
options.headers['Proxy-Authorization'] = 'Basic ' + base64;
35+
}
36+
37+
// If a proxy is used, any redirects must also pass through the proxy
38+
options.beforeRedirect = function beforeRedirect(redirection) {
39+
redirection.headers.host = redirection.host;
40+
setProxy(redirection, proxy, redirection.href);
41+
};
42+
}
43+
1944
/*eslint consistent-return:0*/
2045
module.exports = function httpAdapter(config) {
2146
return new Promise(function dispatchHttpRequest(resolvePromise, rejectPromise) {
@@ -145,17 +170,8 @@ module.exports = function httpAdapter(config) {
145170
}
146171

147172
if (proxy) {
148-
options.hostname = proxy.host;
149-
options.host = proxy.host;
150173
options.headers.host = parsed.hostname + (parsed.port ? ':' + parsed.port : '');
151-
options.port = proxy.port;
152-
options.path = protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path;
153-
154-
// Basic proxy authorization
155-
if (proxy.auth) {
156-
var base64 = Buffer.from(proxy.auth.username + ':' + proxy.auth.password, 'utf8').toString('base64');
157-
options.headers['Proxy-Authorization'] = 'Basic ' + base64;
158-
}
174+
setProxy(options, proxy, protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path);
159175
}
160176

161177
var transport;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// https://snyk.io/vuln/SNYK-JS-AXIOS-1038255
2+
// https://github.com/axios/axios/issues/3407
3+
// https://github.com/axios/axios/issues/3369
4+
5+
const axios = require('../../../index');
6+
const http = require('http');
7+
const assert = require('assert');
8+
9+
const PROXY_PORT = 4777;
10+
const EVIL_PORT = 4666;
11+
12+
13+
describe('Server-Side Request Forgery (SSRF)', () => {
14+
let fail = false;
15+
let proxy;
16+
let server;
17+
let location;
18+
beforeEach(() => {
19+
server = http.createServer(function (req, res) {
20+
fail = true;
21+
res.end('rm -rf /');
22+
}).listen(EVIL_PORT);
23+
proxy = http.createServer(function (req, res) {
24+
if (req.url === 'http://localhost:' + EVIL_PORT + '/') {
25+
return res.end(JSON.stringify({
26+
msg: 'Protected',
27+
headers: req.headers,
28+
}));
29+
}
30+
res.writeHead(302, { location })
31+
res.end()
32+
}).listen(PROXY_PORT);
33+
});
34+
afterEach(() => {
35+
server.close();
36+
proxy.close();
37+
});
38+
it('obeys proxy settings when following redirects', async () => {
39+
location = 'http://localhost:' + EVIL_PORT;
40+
let response = await axios({
41+
method: "get",
42+
url: "http://www.google.com/",
43+
proxy: {
44+
host: "localhost",
45+
port: PROXY_PORT,
46+
auth: {
47+
username: 'sam',
48+
password: 'password',
49+
}
50+
},
51+
});
52+
53+
assert.strictEqual(fail, false);
54+
assert.strictEqual(response.data.msg, 'Protected');
55+
assert.strictEqual(response.data.headers.host, 'localhost:' + EVIL_PORT);
56+
assert.strictEqual(response.data.headers['proxy-authorization'], 'Basic ' + Buffer.from('sam:password').toString('base64'));
57+
58+
return response;
59+
60+
});
61+
});

0 commit comments

Comments
 (0)