БезопасностьРазработка

Экранирование данных при запуске команд из Node.js

Иногда приложение на Node.js должно запускать консольные команды для запуска других приложениях.

Например, представим, что нам нужно склонировать репозиторий с github. В обычном случае это делается достаточно просто:

import { exec } from 'child_process'

exec('git clone https://github.com/greensk/jquery.typograph myprojects/new'), (error, stdout, stderr) => {
  if (error) {
    console.log(error)
  } else {
    console.log('Успех!')
  }
})

Но всё становится чуть сложнее, если какой-то из параметров (например, адрес репозитория) должен вводить пользователь.

Казалось бы можно просто написать:

`git clone ${userTypedUrl} myprojects/new`

Но так делать нельзя! Ведь злонамеренный пользователь может поставить в адрес репозитория пробел и добавить какие-то параметры к команде git clone. А таким образом можно сделать всё, что угодно. Например, склонировать проект не в myprojects, а в корень вашего веб-сервера!

Проблема экранирования

Если мы вставляем пользовательские данные в консольную команду, то очень важно провести экранирование специальных символов. Например, если пользователь по каким-то причинам ввёл пробел, то он должен быть экранирован при помощи символа \. В этом случае пробел будет восприниматься как часть адреса репозитория, а не как разделитель аргументов команды git clone.

Помимо пробела есть ещё много других специальных символов, которые могут как-то повлиять на интерпретацию команд, и которые нужно заэкранировать. Сделать это самостоятельно достаточно сложно, потому что есть много нюансов.

Используем shell-escape

Лучше всего использовать готовую библиотеку. В Node.js для этого есть библиотека shell-espace

npm install --save shell-escape

После этого код следует преобразовать таким образом:

import { exec } from 'child_process'
import shellescape  from 'shell-escape'

exec(shellescape(['git', 'clone', userTypedUrl, 'myprojects/new']), (error, stdout, stderr) => {
  if (error) {
    console.log(error)
  } else {
    console.log('Успех!')
  }
})