Я думаю, это потому, что вы используете querySelectorAll с идентификатором и назначаете один и тот же идентификатор всем элементам img.Вы можете попытаться присвоить уникальный идентификатор, подобный этому, или использовать класс
<div th:each="producto : ${productos}" class="col s12 l4">
<img id="prod-img-${producto.nombre}" th:src="@{'/images/' + ${producto.nombre} +'.png'}" />
<p class="product-name" th:text="${producto.nombre}"></p>
<p class="desc" th:text="${producto.descripcion}"></p>
</div>
. И в js используйте querySelectorAll с '^ =', чтобы проверить, начинается ли значение с 'prod-img'
document.querySelectorAll('[id^="prod-img"]').each(addEventListener('click',
() => {
var element = document.querySelector('#prodModal');
var modal = M.Modal.init(element, {});
modal.open();
}));