Примеры кода в лекции содержат уязвимости! Пожалуйста, не используйте их в ваших приложениях
GET https://my-awesome-site.ru/admin
GET https://my-awesome-site.ru/aliSFla1434snf
В обоих случаях такая функциональность может быть скомпрометирована
GET https://online-shop.ru/payment/step/1
GET https://online-shop.ru/payment/step/2
GET https://online-shop.ru/payment/step/3
GET https://online-shop.ru/payment/step/4
GET https://disk.yandex.ru/client/1028379420
GET https://disk.yandex.ru/client/123
GET https://disk.yandex.ru/client/1028379420
GET https://disk.yandex.ru/client/123
Есть возможность получить данные другого пользователя
const { id } = req.query;
const sql = `SELECT * FROM adventures WHERE id='${id}';`;
GET /adventures?id=123
SELECT * FROM adventures WHERE id='123';
GET /adventures?id=123
GET /adventures?id=123' or '1'='1
SELECT * FROM adventures WHERE id='123' or '1'='1';
GET /adventures?id=123%27%20or%20%271%27=%271
const { name } = req.body;
const escaped = name.replace('\'', '\'\'');
const sql = `
SELECT * FROM adventures WHERE name='${escaped}';
`;
Работает только в частном случае
PREPARE findAdventure (text) AS
SELECT * FROM adventures WHERE name = $1;
EXECUTE findAdventure('magic');
const { name } = req.body;
sequelize
.query('SELECT * FROM adventures WHERE name = :name', {
replacements: { name },
type: sequelize.QueryTypes.SELECT
})
.then(adventures => res.send(adventures));
import { exec } from 'child_process';
const { repo, name } = req.body;
exec(`git clone ${repo}`);
exec(`cd ${name}`);
exec('npm install');
exec('npm test');
POST /hook
{ repo: 'https://github.com/urfu-2018/telltail.git', name: 'telltail' }
import { exec } from 'child_process';
const { repo, name } = req.body;
exec(`git clone ${repo}`); // 🤔
exec(`cd ${name}`); // 🤔
exec('npm install');
exec('npm test');
POST /hook
{ repo: 'https://github.com/urfu-2018/telltail.git', name: 'telltail' }
POST /hook
{ repo: 'https://github.com/urfu-2018/telltail.git', name: 'telltail && sudo rm -rf /app/' }
exec('cd telltail && sudo rm -rf /app/')
POST /hook
{ repo: 'https://github.com/urfu-2018/telltail.git', name: 'telltail && sudo rm -rf /app/' }
exec('cd telltail && sudo rm -rf /app/')
import { exec } from 'child_process';
const { repo, name } = req.body;
const repoRegex = /^https?:\/\/[\w\d.\/\-]+\.git$/; const nameRegex = /^[^{}()<>&*|=?;[\]$\-~!.%\/:+'`#]+$/;
if (!repo.match(repoRegex) || !name.match(nameRegex)) { return; }
exec(`git clone ${repo}`);
exec(`cd ${name}`); // ...
Проксируем запрос в бэкенд
const { pathname } = req;
const data = await got(`https://${apiHostname}/${pathname}`);
Путь на фронте преобразуется в путь на бэке
GET /handle
GET /backend/handle
GET /handle%20HTTP/1.0%0D%0AHost%3A%20test%0D%0A%0D%0A HTTP/1.0\r\n
Host: frontend\r\n
\r\n
GET /handle%20HTTP/1.0%0D%0AHost%3A%20test%0D%0A%0D%0A HTTP/1.0\r\n
Host: frontend\r\n
\r\n
Запрос к бэкенду:
GET /backend/handle HTTP/1.0\r\n
Host: test\r\n
\r\n
HTTP/1.0\r\n
Host: backend\r\n
GET /handle%20HTTP/1.0%0D%0AHost%3A%20test%0D%0A%0D%0A HTTP/1.0\r\n
Host: frontend\r\n
\r\n
Запрос к бэкенду:
GET /backend/handle HTTP/1.0\r\n
Host: test\r\n
\r\n
HTTP/1.0\r\n
Host: backend\r\n
Пользовательский ввод попадает в ответ сервера
GET /api/v1/vulnerable HTTP/1.0\r\n
Host: frontend\r\n
Referer: test%0D%0ASet-Cookie:%20name=Alice%0D%0A\r\n
\r\n
GET /application/vulnerable HTTP/1.0\r\n
Host: backend\r\n
Referer: test\r\n
Set-Cookie: name=Alice\r\n
\r\n
Set-Cookie: name=Bob\r\n
\r\n
GET /api/v1/vulnerable HTTP/1.0\r\n
Host: frontend\r\n
Referer: test%0D%0ASet-Cookie:%20name=Alice%0D%0A\r\n
\r\n
GET /application/vulnerable HTTP/1.0\r\n
Host: backend\r\n
Referer: test\r\n
Set-Cookie: name=Alice\r\n
\r\n
Set-Cookie: name=Bob\r\n
\r\n
GET /api/v1/vulnerable HTTP/1.0\r\n
Host: frontend\r\n
Referer: test%0D%0ASet-Cookie:%20name=Alice%0D%0A\r\n
\r\n
GET /application/vulnerable HTTP/1.0\r\n
Host: backend\r\n
Referer: test\r\n
Set-Cookie: name=Alice\r\n
\r\n
Set-Cookie: name=Bob\r\n
\r\n
Инъекция в HTTP-заголовке Referer
GET /handle?payload=1%26login=admin
GET /handle?payload=1&login=admin&login=user
GET /handle?payload=1%26login=admin
GET /handle?payload=1&login=admin&login=user
Инъекция в GET-параметре payload
GET /handler?filename=image.jpg
GET /handler?filename=../../../../../etc/passwd
Нужно разграничивать права доступа к директориям в файловой системе
const { code } = req.body;
const isValid = await PromoCode.validate(code);
if (!isValid) {
return;
}
await PromoCode.activate(code);
await PromoCode.markAsUsed(code);
const { code } = req.body;
const isValid = await PromoCode.validate(code);
if (!isValid) {
return;
}
await PromoCode.activate(code);
await PromoCode.markAsUsed(code); // ⏰
Нужно использовать транзакции
const { text } = req.body;
const regex = /^https?:\/\/\w+@\w+\.\w+$/;
const isValidEmail = Boolean(text.match(regex));
const statusCode = isValidEmail ? 200 : 400;
res.sendStatus(statusCode);
const { text } = req.body;
const regex = /^https?:\/\/\w+@\w+\.\w+$/;
const isValidEmail = Boolean(text.match(regex)); // ⚠️
const statusCode = isValidEmail ? 200 : 400;
res.sendStatus(statusCode);
const isValidEmail = Boolean(text.match(regex));
while (true); do
curl $URL -d '{ text: $(cat "Война и мир.txt") }';
done
http://a.yandex.ru/dir1
http://a.yandex.ru/dir1/dir2
https://a.yandex.ru/dir1
http://a.yandex.ru:8080/dir1
http://b.yandex.ru/dir1
Set-Cookie: id=1111; Path=/admin/
Set-Cookie: id=2222; Path=/; Domain=my.example.com
Set-Cookie: id=3333; Path=/; Http-Only; secure
Set-Cookie: id=1111; Path=/admin/
Set-Cookie: id=2222; Path=/; Domain=my.example.com
Set-Cookie: id=3333; Path=/; Http-Only; secure
Http-Only - кука не доступна из JS
Secure - кука передаётся только по HTTPS
GET /handler/ HTTP/1.1
Host: myawesomedomain.com
Origin: https://attacker.com
Cookie: session=123
HTTP/1.0 200 OK
Access-Control-Allow-Origin: https://attacker.com
Access-Control-Allow-Credentials: true
POST /modify HTTP/1.0
Host: vulnerable.com
X-CSRF-Token: keyboard-cat
POST /modify HTTP/1.0
Host: vulnerable.com
X-CSRF-Token: keyboard-cat // 🦄 🌈
<textarea id="textarea" type="text"></textarea>
<button id="button">Сохранить</button>
<div id="result"></div>
<script>
button.addEventListener('click', function () {
result.innerHTML = textarea.value;
});
<script>
Пользователь написал в 25 апреля в 17:45
import sanitizeHtml from 'sanitize-html';
const evilHtml = `
<p style="background-image: url('attacker.com')">
Hello, world!
</p>
<script>alert(document.cookie);</script>
`;
const kindHtml = sanitizeHtml(evilHtml, {
allowedTags: ['a', 'p', 'div'],
allowedAttributes: { a: ['href'] }
});
// Hello, world!
<XSS>’’;!—«<hr/>&{()}
Content-Security-Policy:
default-src 'self';
img-src *;
script-src: trusted.com;
Content-Security-Policy:
media-src: https://yandex.ru
Content-Security-Policy:
media-src: https://yandex.ru
<iframe src="https://vk.com">
</iframe>
Click to WIN!
X-Frame-Options: ALLOW-FROM http://trusted.com
X-Frame-Options: SAMEORIGIN
X-Frame-Options: DENY
* X-Frame-Options: ALLOW-FROM не работает в Google Chrome
** Но есть директива CSP frame-ancestors
npm audit