Buffer safety

The Buffer constructor is highly powerful, but with potential for danger.

Let's simply create an index.js file with the following code:

const http = require('http')

const server = http.createServer((req, res) => {
if (req.method === 'GET') {
res.setHeader('Content-Type', 'text/html')
if (req.url === '/') return res.end(html())
res.setHeader('Content-Type', 'application/json')
if (req.url === '/friends') return res.end(friends())

return
}
if (req.method === 'POST') {
if (req.url === '/') return action(req, res)
}
})

function html (res) {
return `
<div id=friends></div>
<form>
<input id=friend> <input type=submit value="Add Friend">
</form>
<script>
void function () {
var friend = document.getElementById('friend')
var friends = document.getElementById('friends')
function load () {
fetch('/friends', {
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
}
}).catch((err) => console.error(err))
.then((res) => res.json())
.then((arr) => (
friends.innerHTML = arr.map((f) => atob(f))
.join('<br>')
))
}
load()

document.forms[0].addEventListener('submit', function () {
fetch('/', {
method: 'post',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
body: JSON.stringify({cmd: 'add', friend: friend.value})
}).catch((err) => console.error(err))
.then(load)
})
}()
</script>
`
}

function friends () {
return JSON.stringify(friends.list)
}
friends.list = [Buffer('Dave').toString('base64')]
friends.add = (friend) => friends.list.push(Buffer(friend).toString('base64'))

function action (req, res) {
var data = ''
req.on('data', (chunk) => data += chunk)
req.on('end', () => {
try {
data = JSON.parse(data)
} catch (e) {
res.end('{"ok": false}')
return
}
if (data.cmd === 'add') {
friends.add(data.friend)
}
res.end('{"ok": true}')
})
}

server.listen(3000)

We can start our server with:

$ node index.js 

This is a server with three routes, GET /, POST /, and GET /friends. The GET / route delivers some HTML with an inline client-side script that hits the /friends route, recieves a JSON array payload, and maps over each item in the array to convert it from base64 with the browsers atob function. The POST / route parses any incoming JSON payloads, checks for a cmd property with a value of add, and calls friends.add(data.friend). The friends.add method converts the input into base64 and adds it to an array. On the client side, the load function is called again after a successful POST request, and the updated list of friends is loaded.

However, if we use curl to make the following request:

$ curl -H "Content-Type: application/json" -X POST -d '{"cmd": "add", "friend": 10240}' http://127.0.0.1:3000 

And then check the browser, at http://localhost:3000, we'll see something similar to the following:

We set the friend field in the JSON payload to a number, which was passed directly to the Buffer constructor. The Buffer constructor is polymorphic if it's passed a string the string will be converted to a buffer. However, if passed a number, it will allocate a buffer to the size of that number. For performance reasons, memory for the buffer is allocated from unlinked memory, which means potentially anything could be exposed, including private keys.

Let's copy index.js to index-fixed.js:

$ cp index.js index-fixed.js 

Now we'll change the friends function and methods like so:

function friends () {
return JSON.stringify(friends.list)
}
friends.list = [Buffer.from('Dave').toString('base64')]
friends.add = (friend) => {
friends.list.push(Buffer.from(friend).toString('base64'))
}

We're using Buffer.from instead of using Buffer directly. The Buffer.from method will throw when passed a number, it will only allow strings, arrays (and array-like objects), and other buffers (including Buffer and ArrayBuffer objects).

To make sure our server doesn't crash we can update the action function accordingly:

function action (req, res) {
var data = ''
req.on('data', (chunk) => data += chunk)
req.on('end', () => {
try {
data = JSON.parse(data)
} catch (e) {
console.error(e)
res.end('{"ok": false}')
return
}
if (data.cmd === 'add') {
try {
friends.add(data.friend)
} catch (e) {
console.error(e)
res.end('{"ok": false}')
}
}
})
}

If we start the fixed server:

$ node index-fixed.js 

And run the same curl request:

$ curl -H "Content-Type: application/json" -X POST -d '{"cmd": "add", "friend": 10240}' http://127.0.0.1:3000 

We'll see a response {"ok": false}, our server won't crash, but it will log the error: TypeError: value argument must not be a number. Subsequent requests to GET / will show that no internal memory has been exposed.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.17.40