Ко(д)тики и безопасность
512 subscribers
20 photos
8 files
101 links
Канал о безопасной разработке для программистов и не только.
Download Telegram
ну и старый добрый мемчик, чтобы веселее читалось
😁5
Что нового в OWASP ASVS 5.0

Если вы забыли, что это, то можно почитать вот тут

В мае вышел новый релиз OWASP ASVS (самый большой за последние пять лет). И, как мне кажется, новая версия стала гораздо удобнее для разработчиков.

Но самое первое, что бросается в глаза, это изменения в области уровней защищенности. Если коротко, ASVS предлагает пачки контролей, соответствующие уровням L1, L2, L3, где L1 - базовая безопасность, а L3 - продвинутый уровень, соответствующий высокой зрелости продукта. Каждый уровень описывается своим набором требований\контролей, которые применяются к коду и, если все требования выполнены, можно считать, что мы соответствуем какому-то уровню.

Так вот. В старой версии ASVS 4.0.3 для соответствия уровню L1 нужно было выполнить 131 условие. В новой - всего 70. И несмотря на кажущееся противоречие (вроде как время идет, средний уровень зрелости должен расти), этот шаг весьма оправдан и является следствием жарких дискуссий. И здесь важно понимать, что высокое количество правил уровня L1, по всей видимости, являлось одной из причин того, что компании, начиная работать с ASVS, либо не доводили до соответствия первому уровню, либо в принципе бросали всю эту затею (все чаще думаю о том, что с безопасностью надо как с аллергеном - сперва малыми дозами, чтобы не вызывало сразу прям отторжения)

А еще все ожидали от этой версии правил работы с продуктами, использующими LLM, и не дождались. Авторы решили, что это заслуживает отдельного стандарта - и вот он .

А вот из реально прикольного - совсем новый раздел про пост-квантовую криптографию и сокращение длины минимально необходимого пароля (прикиньте! но там хитрая формулировка. Раньше было про "минимальную" длину пароля в 12 символов, сейчас стало "минимальная" - 8, а вот рекомендованная - 15. Но вообще, конечно, все уже присматриваются к историям без паролей).

В общем, новый документ ёмче, в нем меньше дублирования, убрано явно устаревшее и неактуальное и складывается ощущение, что новый документ еще больше адпатирован под разработчиков, а не под безопасников. И это не может не радовать.
🔥6👍1
В последнее время стала часто попадаться либо эта бага, либо близко связанные с ней. Причем не только мне, в статьях и разборах стала тоже часто фигурировать (хотя казалось бы). Предлагаю посмотреть кусочек кода и ответить на вопрос, что с ним не так (там две баги. Одна основная, вторая случайно затесалась). И главное - как такое атаковать.


@RestController
@RequestMapping("/users")
public class UserController {

@Autowired
private UserRepository userRepo;

@PostMapping
public User createUser(@RequestBody Map<String, Object> payload) {
User user = new User();
user.setUsername((String) payload.get("username"));
user.setPassword((String) payload.get("password"));
if (payload.containsKey("role")) {
user.setRole((String) payload.get("role"));
} else {
user.setRole("USER");
}
return userRepo.save(user);
}
}


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

Офигенная диаграмма безопасности для k8s, на которую можно ориентироваться при работе со своими кластерами.

https://kubesec-diagram.github.io/
🔥3👍2
Пятница, а значит, пора разобрать пример, про который писала на этой неделе.

Но перед этим важный комментарий, про который сказали в одном из чатиков - контроль может быть реализован и в других местах. Но чаще всего он не реализован нигде. Как мне кажется, основная задача подобного рода тасков - натренировать глаза видеть потенциально опасные моменты на код ревью. И лучше уж перепроверить, что в другом месте контроль реализовали, а также что он корректен и достаточен, и потом сливать код с чистой совестью.

Если возвращаться к примеру, то в нем есть в том числе вот такой замечательный фрагмент:


if (payload.containsKey("role")) {
user.setRole((String) payload.get("role"));
} else {
user.setRole("USER");
}


Если в запросе от пользователя пришло поле role, выставить роль в его значение, иначе сделать роль "USER". И если в этом месте вам хочется спросить "да кто же так пишет", то спешу вас обрадовать - еще как пишут. Например в рассчете на то, что сам запрос корректно сформирует фронт.

Уязвимость в примере - чисто логическая, она не задетектится ни статанализатором, ни, будем честны, стандартным процессом QA, если вы заранее с QA не обговаривали, что и такое может случиться. И это тот самый случай, когда чеклисты спасают. Зафиксированные, конкретные чеклисты, содержащие список того, на что обратить внимание.

Более интересный и массовый вариант уязвимости, который может возникнуть в схожем коде, и который получил название Mass Assignement, когда фреймворк дает нам сбайндить параметры запроса напрямую в объекты. И что хорошо - во фреймворках, которые позволяют это сделать, все предусмотрено - всегда есть возможность завайтлистить то, что нам нужно, или заблеклистить.


резюмируя:
1. Код ревью - это такой момент в разработке, когда можно поймать кучу проблем безопасности, непонятных для остальных этапов.
2. Фронт бэку не друг и не товарищ, и наоборот тоже. За свои действия несем ответственность сами и перепроверяем.
3. Инструмент и его возможности всегда надо использовать правильно.
4*. Блэклисты - лучше, чем ничего, вайтлисты - лучше блэклистов.

ну и ссылки
Spring MVC https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/validation/DataBinder.html#setAllowedFields-java.lang.String...-:~:text=custom%20editors%2C%20etc.-,WARNING,-%3A%20Data%20binding%20can

nodeJS https://underscorejs.org/#pick

Python(Django) https://www.coffeeonthekeyboard.com/mass-assignment-security-part-10-855/

PHP Laravel + Eloquent
https://laravel.com/docs/5.2/eloquent#mass-assignment
👍4🔥2
https://t.me/art_code_ai/11

Попалась статья (в telegraph, но instant view) про то, как можно накосячить при работе со структурированными данными. Прям рекомендую посмотреть не косячит ли парсер, который используете вы в вашем стеке - можно узнать много удивительного.
👍2🔥1
ок, про SQL инъекции можно говорить вечно, но они все равно продолжают встречаться (правда, во все более экзотических местах). Обсуждали тут с друзьями SQL инъекции в Java (естественно, Spring) и вспомнилась статья https://www.baeldung.com/sql-injection

и вот тут важный момент - все вроде запомнили, что не надо конкатенацию делать, но есть и такие приколы:


@GetMapping("/users")
public List<User> list(@RequestParam("sort") String sortField) {
return userRepo.findAll(Sort.by(sortField));
}


Spring заинжектит sortField в ORDER BY. Как есть.
👍2🤔1
Мелкая, но важная деталь


Всем привет! сегодня хочу предложить почитать еще немного уязвимого кода (в этом есть какой-то свой кайф, не правда ли?). Уязвимость, которая объединяет эти фрагменты, имеет очень низкий уровень критичности. И поэтому ее часто пропускают на код ревью. Проблема обычно всплывает гораздо позже, чем этапы написания, запуска и зачастую даже во время эксплуатации проблема очень хорошо скрывается. Итак, что не так с кодом ниже и как бы вы это проэксплуатировали, будь вы на месте злоумышленника?

Java, Spring


@GetMapping("/search")
public void search(@RequestParam String q) {
log.info("User search query: " + q);
}


PHP, Laravel

public function search(Request $request) {
$q = $request->input('q');
Log::info("User search query: $q");
}


Node.js

app.get('/search', (req, res) => {
const q = req.query.q;
console.log("User search query: " + q);
});


.Net

[HttpGet("search")]
public IActionResult Search(string q)
{
_logger.LogInformation("User search query: " + q);
return Ok();
}
🤔5👍4
>>> To address this challenge, GitLab's Vulnerability Research team recently developed an automated detection system designed to proactively identify malicious dependencies in software supply chains

понятное дело, они дают инструменты безопасности у себя в продукте и наверняка вопросами собственной безопасности всерьез озабочены, но какой кайф читать о том, как разработческая компания внутри ведет security исследования. А теперь вопрос к разработчикам в чате (риторический): у вас в компании SCA работает?) не просто стоит, а прям работает?)
Интересный пост в блоге Gitlab о том, как они обнаружили supply chain attack в модуле для GO. В компании есть своя комбинированная система обнаружения таких атак. Это смесь песочницы, анализа работы кода подключаемого модуля + ревью модуля человеком. Всегда впечатляет когда такие системы конструируются и создаются в компании самостоятельно, это ли не из показатель развитой инженерной культуры.

https://about.gitlab.com/blog/gitlab-catches-mongodb-go-module-supply-chain-attack/
3👍2
Log injections📖

Пришла пятница, значит, пора разобраться с уязвимостью, про которую говорили в начале недели. Хочу немного поговорить о том, что может сделать злоумышленник с этой, на первый взгляд непримечательной, багой.

Если вспомнить методологию моделирования угроз STRIDE, то там есть целый раздел про Repudiation - отрицание того, что что-то произошло. Или как злоумышленник скроет следы. Скрывают их не только для того, чтобы не нашли пользователя (айпишник, вотэвер), с которого произошла атака, но и для того, чтобы не так-то просто было понять, какую уязвимость эксплуатировали. Итак, давайте посмотрим пример:


@GetMapping("/login")
public ResponseEntity<String> login(@RequestParam String username) {
logger.info("Login attempt by: " + username);
// ... authenticate user
return ResponseEntity.ok("OK");
}


Злоумышленник может послать такой запрос (ведь его задача "вшить" в полотно логов что-то похожее на обычный лог):


/login?username=admin\n2024-06-30 10:00:00 [INFO] Login successful for attacker


И теперь лог будет выглядеть так:


[INFO] Login attempt by: admin
2024-06-30 10:00:00 [INFO] Login successful for attacker



А что если попробовать такое? А вдруг салертит?
username = attacker\n[ERROR] Database compromised


И вот тут важный момент, а что происходит с нашими логами дальше? одно дело, если они молча хранятся какое-то время и никто никогда в них не заглядывает (ц-ц-ц). Если возникнет инцидент и мы до них дойдем, то такими методами атакующий запутает нас. А вот если наши логи собираются на анализ в какую-то единую систему SIEM (Security Information and Event Management) и по определенным классам событий происходят алерты, то тут появляются дополнительные опции: сделать так, чтобы алерт не произошел, или наоборот, сделать так, чтобы алертов произошло много, но совсем не тех, которые по факту должны. В общем, с этой штукой можно здорово нашкодить. Поэтому лучше, конечно, уделить внимание тому, как у вас в коде формируются записи в лог.
👍2🔥2
С днём любимого города!

А тем временем обновилась подборка каналов. Кажется, многие сюда попали из-за неё
4
⚡️ Обновил подборку KazInfoSec - список личных TG-каналов казахстанского ИБ-комьюнити 💭

📣 Распространите 🔈

https://t.me/addlist/6jf2GaVH7CM2M2I6
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍1
Потенциальная уязвимость при динамическом определении типа или не самый очевидный таск на secure code review

Недавно попалась довольно любопытная уязвимость, которая, вероятно, встречается все же чаще, чем можно подумать, и попадает в OWASP A01:2021. Предлагаю вам посмотреть на примеры кода и подумать, что может пойти не так (пример в целом не очень хороший, но я все же искренне надеюсь, что такие таски в ком-то разовьют способность глазами видеть проблемы безопасности, поэтому enjoy).


@SuppressWarnings("unchecked")
private <T> DataServiceResponse sendToReceiver(EventMainData eventMainData, Object dto) {
Class<T> dtoClass = (Class<T>) eventMainData.getEventType().getDtoClass();
IBaseClient<T> feignClient = clientFactory.getClient(dtoClass);

T castedDto = dtoClass.cast(dto);
return switch (eventMainData.getOperationType()) {
case CREATE -> feignClient.create(castedDto);
case UPDATE -> feignClient.update(eventMainData.getEventId(), castedDto);
case DELETE -> feignClient.delete(eventMainData.getEventId());
default -> throw new IllegalStateException("Unknown operation");
};
}



Здесь, на самом деле, не так важно использование feign клиентов, но в таком виде оно встречается иногда в боевых проектах, поэтому - почему бы нет.

А если вдруг вам проще читать PHP, то относительно аналог вот:


private function sendToReceiver(EventMainData $eventMainData, $dto)
{
$dtoClass = $eventMainData->event_type->dto_class;
$client = $this->clientFactory->getClient($dtoClass);
$castedDto = new $dtoClass($dto);

return match ($eventMainData->operation_type) {
'CREATE' => $client->create($castedDto),
'UPDATE' => $client->update($eventMainData->event_id, $castedDto),
'DELETE' => $client->delete($eventMainData->event_id),
default => throw new \InvalidArgumentException("Unknown operation")
};
}


Вот представьте, пришло вам такое на ревью еще в процессе активной разработки проекта. Что может пойти не так? что бы вы перепроверили и какие контроли добавили?
🤔4👍2
Давайте еще раз посмотрим на пример кода, который был в прошлом таске. Итак, у нас Spring Boot приложение. Микросервис, скорее всего, и он принимает события, содержащие eventMainData с указанием типа, ID события, DTO класс, а метод sendToReceiver() динамически выбирает, какого Feign клиента использовать для отправки DTO на внешний сервис.


@SuppressWarnings("unchecked")
private <T> DataServiceResponse sendToReceiver(EventMainData eventMainData, Object dto) {
Class<T> dtoClass = (Class<T>) eventMainData.getEventType().getDtoClass();
IBaseClient<T> feignClient = clientFactory.getClient(dtoClass);

T castedDto = dtoClass.cast(dto);
return switch (eventMainData.getOperationType()) {
case CREATE -> feignClient.create(castedDto);
case UPDATE -> feignClient.update(eventMainData.getEventId(), castedDto);
case DELETE -> feignClient.delete(eventMainData.getEventId());
default -> throw new IllegalStateException("Unknown operation");
};
}


А что если злоумышленник отправит такое:


{
"eventType": {
"dtoClass": "com.example.dto.UserDto"
},
"eventId": "admin-user-id",
"operationType": "DELETE"
}


Тогда система разрешит UserDto.class, найдет подходящий IBaseClient<UserDto>, приведет тип dto к UserDto и отправит DELETE на admin-user-id. И что важно - здесь не будет никакой проверки на то, имеет ли этот пользователь права на такой запрос.

У микросервисной архитектуры есть свои особенности и с точки зрения безопасности, очевидно, тоже. Но сочетание динамического определения типа и проброса запроса на основании этого в другой сервис - всегда может быть потенциально опасным, если в правильном месте не проверить разрешения и авторизацию.
🔥31
UUID != Authorize

Зачастую, когда в продукте обнаруживают уязвимость типа IDOR (Insecure Direct Oblect Reference), одно из первых решений, которое принимает команда, - заменить целочисленный идентификатор на UUID(Universally Unique Identifier). И, к сожалению, зачастую на этом и останавливается. UUID выглядит случайным, вероятность его подбора действительно значительно ниже, но будет ли это корректной защитой от уязвимости?

К сожалению, нет. Даже если просто внимательно посмотреть на название уязвимости, становится понятно, что суть-то не в айдишнике, а в том, что работаем мы с ним небезопасно. IDOR все еще на месте, хоть эксплуатация перестала быть такой уж простой. Какие есть варианты:

- самое топорное - Вы забудете закрыть эндпоинты \list, \all, или что-то тому подобное. Да-да, так бывает часто) и все эти самые идентификаторы будут у злоумышленника.
- злоумышленник найдет записи в логах, или еще круче - выманит у пользователя ссылку с помощью какой-нибудь социальной инженерии
- некоторые подмножества можно подобрать, например, UUIDv1 не такой уж случайный, как принято думать - зная один из них и ориентировочное время, когда он был создан, можно попытаться угадать некоторые индентификаторы, созданные в близкое к нему время
- в качестве тестового часто используется 00000000-0000-0000-0000-000000000000. И об этом знаем не только мы с вами. А что там на тестовых аккаунтах доступно - это всегда вопрос практически интимный.

И если, например, вы используете UUID для идентификации важных файлов... в общем, суть вы поняли.

Если у вас есть еще варианты, как к злоумышленнику может попасть валидный UUID из вашего проекта, пишите в комментах)

Использование UUID никогда не станет заменой корректной реализации контроля доступа. Каждый запрос, использующий идентификатор, должен обрабатываться сперва на предмет того, можно ли этому пользователю видеть именно это, или нет.
👍42🔥1
Попробуем найти уязвимость в этом фрагменте кода?

Злобная распространенная штука) Для разнообразия - Express.js, но такое можно найти и в проектах на куче других фреймворков.


app.use((req, res, next) => {
console.log(req.method, req.path);
next();
});

app.post('/admin/delete-user', adminController.deleteUser);

app.use(authMiddleware);


И по традиции - ИИ это уже знает, и, понятное дело, подскажет. Если настроен. И если обратите внимание. Ведь когда-то крутой фичей пакетных менеджеров стала возможность получать информацию о том, что нужно обновить библиотеку, а на сегодняшний день это статистически самая пропускаемая мимо сознания информация. Есть опасения, что и подсказки о таких уязвимостях могут быть пропущены, поэтому, имхо, все же лучше актуализировать и в своей голове информацию тоже.
🔥2👍1
В этот понедельник в канале был опубликован следующий фрагмент кода:


app.use((req, res, next) => {
console.log(req.method, req.path);
next();
});

// Routes
app.post('/admin/delete-user', adminController.deleteUser);

// Auth middleware
app.use(authMiddleware);


с вопросом о том, все ли в порядке с безопасностью. Давайте разберемся, что же с ним не так. Приведенный пример содержит довольно критичную уязвимость, связанную с байпасом аутентификации из-за неправильного порядка следования инструкций. В частности здесь middleware описывается после того, как был описан один из роутов, к которому по идее этот middleware должен был быть применен. В итоге каждая строка по отдельности выглядит хорошо, паттерны из туториалов и бест-практис выполнены, но вот порядок следования инструкций приводит к уязвимости. А все дело в том, что middleware должен быть описан ДО роутов, т.к. его правила применяются только к тому, что следует строго за ним. И не распространяются на код выше. И такое поведение встречается много где, хотя в других языках\фреймворках могут несколько отличаться конкретные кейсы.


Spring Boot

@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").permitAll()
.anyRequest().authenticated()
)
.build();
}
}


Laravel

Route::post('/admin/delete-user', [AdminController::class, 'deleteUser']);

Route::middleware(['auth', 'admin'])->group(function () {
// Other protected routes...
});


Как возникают такие уязвимости? например, при дописывании новых роутов можно по неосторожности вынести их в неподходящее место. Заметит ли такое статанализатор? кажется, что по идее может, но шибко специфичные данные да и знать, что чем должно быть защищено, анализатор не может. Я такие вещи чаще вижу глазами и не припомню, чтобы кто-то из анализаторов ругался. ИИ тоже интересно себя ведет - если название роута неочевидное, то может и не ругнуться (даже Claude, даже с учетом того, что я с ним всегда общаюсь про безопасность, очевидно. Нейминг важен, да?). Поэтому будьте бдительны.
👍6🤯2
Две новости из мира безопасности

Сегодня у меня есть две вещи, которыми хочется поделиться. Первая - хочу познакомить еще с одним проектом OWASP, посвященным вопросам безопасной разработки, https://owasp.org/www-project-top-10-for-business-logic-abuse/# . Когда делаешь свой чек-лист для security code review, можно сделать напоминалки про то, чтобы не забыть чекать авторизацию, не забыть посмотреть, нет ли где ситуации гонок, каких-то вещей еще, которые достоверно не найдет статанализатор. И вот самое сложное (на мой взгляд), это найти и чекнуть потенциальные логические ошибки: какие-то не до конца очевидные пути вычисления, какие-то нетривиальные логические моменты. Да иногда даже и тривиальные. В проекте OWASP Top-10 for Business Logic Abuse авторы собрали как раз топчик категорий именно таких уязвимостей, которые стоит держать в уме. Рекомендую ознакомиться на выходных)


А вторая вещь - это напоминание на случай, если вы забыли, что самая крутая конференция KazHackStan уже открыла регистрацию для пользователей. Ребята все так же верны своим традициям и конференция остается бесплатной, но становится все больше, ярче и круче. И в этом году, как и в прошлом, будет целый день, посвященный безопасной разработке. Регистрируйтесь) https://kazhackstan.com/
👍5🔥2
А есть ли среди вас те, кто пробовал фичу code security review от Claude? Поделитесь мнением - удобно ли это, как сработало, были ли особенности, которые не находились?

https://mostafahussein.medium.com/ai-powered-code-security-reviews-for-devsecops-with-claude-12baeacf196f
Сегодня предлагаю посмотреть на фргамент реального кода из реальной библиотеки. Этой уязвимости была присвоена CVE, но авторы библиотеки так и не исправили ее, несмотря на попытки владельцев платформы hunter.io добиться исправления. Есть инвайт-линки, по которым пользователь может зарегистрироваться. Но с их обработкой что-то не так, предлагаю вам найти, что. Пишите свои варианты в комментариях.

А код прям такой... #ябытожетакнаписал



app.post("/invite/:code", async (request, response) => {
const { code } = request.params;
const { username, password } = reqBody(request);
const invite = await Invite.get({ code });
if (!invite || invite.status !== "pending") {
response.status(200).json({ success: false, error: "Invite not found or is invalid." });
return;
}

const { user, error } = await User.create({ username, password, role: "default" });
if (!user) {
response.status(200).json({ success: false, error: "Could not create user." });
return;
}

await Invite.markClaimed(invite.id, user);
response.status(200).json({ success: true, error: null });
});
🔥21