Análise Detalhada — #07 SQL Injection
Data: 2026-07-08
Vetor: Header api-company-target
1. Fluxo do Ataque
Requisição HTTP
│ Header: api-company-target: arezzo"; DROP TABLE "Carts"; --
▼
UserProvider.GetSchemaName() ← L64-68, sem validação
│ return "arezzo\"; DROP TABLE \"Carts\"; -- "
▼
Repository / Query Builder
│ var query = $"SELECT * FROM \"{schema}\".\"Products\""
▼
PostgreSQL
│ SELECT * FROM "arezzo"; DROP TABLE "Carts"; -- "."Products"
▼
DROP TABLE executado
2. Entry Point: UserProvider.GetSchemaName()
Arquivo: coezzion-nuget-common/src/Coezzion.Common/Providers/UserProvider.cs
public string GetSchemaName()
{
var http = _context?.HttpContext;
if (http == null)
return _schemaNameScopedWrapper.SchemaName; // (A) Fallback
// (B) x-api-key venda_ai → "arezzo" (seguro, hardcoded)
var apiKeyFromHeader = http.Request.Headers["x-api-key"].ToString();
if (apiKeyFromHeader == vendaAiKeyFromConfig)
return "arezzo";
// (C) JWT claim "schema"
var schemaClaim = http.User?.FindFirst("schema");
if (schemaClaim != null && !string.IsNullOrEmpty(schemaClaim.Value))
return schemaClaim.Value; // Sem validação (fora do escopo — #30)
// (D) Header api-company-target ← VULNERÁVEL
if (http.Request.Headers.TryGetValue("api-company-target", out StringValues headerSchema)
&& headerSchema.Any())
{
return headerSchema.ToString(); // RAW — sem validação
}
// (E) Default
return _schemaNameScopedWrapper.SchemaName;
}
3. Leituras Diretas (bypass UserProvider)
3.1 Tipos de bypass
| Tipo | Padrão | Quantidade | Serviços |
|---|---|---|---|
| A | DatabaseConfig.GetSchema() lê header direto | 8 | product, store, backoffice, integration, message, payments-reports, showcase, checkout |
| B | Event handler background | 5 | payments-reports, integration |
| C | Repositório no construtor | 5 | showcase, payments-reports |
| D | gRPC interceptor | 1 | product |
3.2 Exemplos
Tipo A — DatabaseConfig.GetSchema():
// Product.API/Configuration/DatabaseConfig.cs:78
schemaName = contextAccessor.HttpContext.Request.Headers["api-company-target"].ToString();
Tipo B — Event handler:
// Payments.Reports.Infraestructure/EventHandlers/PaymentCreatedEventHandler.cs:22
var schema = _contextAccessor.HttpContext.Request.Headers["api-company-target"].ToString();
Tipo C — Repositório no construtor:
// ShowCase.Infrastructure/Data/Repositories/Read/UserRepository.cs:22
_schema = _contextAccessor.HttpContext?.Request.Headers["api-company-target"] ?? "arezzo";
Tipo D — gRPC:
// Product.API/gRPC/ProductAppService.cs:31
if (!context.RequestHeaders.Where(x => x.Key == "api-company-target").Any())
throw new RpcException(new Status(StatusCode.InvalidArgument, ...));
4. SQL Injection Points (por serviço)
4.1 coezzion-service-cart
Schema source: _userProvider.GetSchemaName()
| Arquivo | Linhas | Operação | Tabelas |
|---|---|---|---|
ProductRepository.cs | 39-51, 97-100, 118-123 | SELECT | ProductsStockStores, Products |
CartRepository.cs | 295-296, 309-310, 352-472, 536-547 | SELECT, UPDATE | Carts, CartItems, Products, Customers, Stores, CartItemRecommendations |
AttendanceRepository.cs | 21-28, 43-50, 67-74, 97-100, 123-128 | SELECT, COUNT | Attendances |
UserReadRepository.cs | 50-64 | SELECT | Sellers, Stores |
4.2 coezzion-service-checkout
Schema source: _userProvider.GetSchemaName()
| Arquivo | Linhas | Operação | Tabelas |
|---|---|---|---|
ControlOperationRepository.cs | 51, 95-96 | UPDATE, SELECT | PaymentsControlOperation, Stores |
ControlOperationByStoreRepository.cs | 59 | UPDATE | PaymentsControlOperationByStore |
PaymentRepository.cs | 192-193 | SELECT | Payments, PaymentsData |
StorePagarMeRepository.cs | 30 | SELECT | StorePagarMeConfig |
CustomerRepository.cs | 46-49, 75-78 | SELECT | CustomerOptIn, Carts, Stores |
CoreSqlRepository.cs | 92-99, 162-163, 262-263, 324, 341, 357, 374, 393-399 | SELECT (multi-join) | Carts, CartItems, Products, Customers, Addresses, Stores, StoresPayment, StorePaymentKeys, StoresPaymentInstallmentRules, CartItemRecommendations, CustomLink, Brands, Channels, PaymentsControlOperation, PaymentsControlOperationByStore |
PaymentSqlRepository.cs | 30, 38, 75-96, 120-122, 169, 176 | SELECT (CTE) | Payments, PaymentsEcomm, PaymentsData, PaymentsStatusHistory, PaymentsEcommData, PaymentsEcommStatusHistory |
CartRepository.cs | 98-103, 322-326, 368-377, 420-421 | SELECT (multi-join) | Carts, Stores, Customers, CartItems, Products, ProductSizes, Brands, CartItemRecommendations |
4.3 coezzion-service-product
Schema source: _userProvider.GetSchemaName() (via IUserProvider)
| Arquivo | Linhas | Operação | Tabelas |
|---|---|---|---|
ProductSearchQueryBuilder.cs | 94-166 | SELECT (CTE) | mv_products_searchable, ProductSizes, ProductsStockStores, Products |
ProductSearchWithBusinessGroupQueryBuilder.cs | 105-196 | SELECT (CTE) | Mesmo |
ProductSearchWithBusinessGroupQueryV2Builder.cs | 140-450 | SELECT (complex) | + Stores |
ProductSearchWithBusinessGroupQueryV3Builder.cs | 91-242 | SELECT (complex) | Mesmo |
ProductRepository.cs | 343-354 | SELECT | Products, ProductPhotos |
4.4 coezzion-service-store
Schema source: _userProvider.GetSchemaName()
| Arquivo | Linhas | Operação | Tabelas |
|---|---|---|---|
StorePaymentRepository.cs | 284-534 | SELECT, UPDATE, DELETE, INSERT | Stores, Brands, StoresPayment, StoresPaymentInstallmentRules, StorePaymentLogs |
CampaignRepository.cs | 78-87 | SELECT (func) | Campaigns |
StoreRepository.cs | 129-297 | SELECT | Stores, Brands |
ChannelRepository.cs | 121-123 | SELECT | Stores, Channels |
BrandRepository.cs | 160-206 | SELECT | Stores, Brands |
SellerRepository.cs | 93-531 | SELECT | Sellers, Stores, SellersVacations, Brands |
SellerVacationRepository.cs | 111-701 | SELECT | SellersVacations, Stores |
4.5 coezzion-service-backoffice
Schema source: _userProvider.GetSchemaName()
| Arquivo | Linhas | Operação | Tabelas |
|---|---|---|---|
EntityAuditLogsRepository.cs | 32 | INSERT | EntityAuditLogs |
LearningVideoRepository.cs | 43-44 | SELECT | LearningVideos, UserWatchedLearningVideo |
ManagerIndicatorRepository.cs | 24-263 | SELECT | Goals, GoalUsers, ManagerIndicators, SellerIndicators |
NpsRepository.cs | 54-484 | SELECT | Stores, Brands |
SellerIndicatorRepository.cs | 25-69 | SELECT | Goals, GoalUsers, ManagerIndicators, SellerIndicators |
CustomerIndicatorRepository.cs | 175-320 | SELECT | CustomerIndicators, Stores, Products |
OmniIndicatorsRepository.cs | 104 | SELECT | OmniIndicators |
StoreReadRepository.cs | 96-97 | SELECT | Stores, Brands |
TurnListReadRepository.cs | 39-225 | SELECT | Stores, Brands |
4.6 coezzion-service-jobs
Schema source: _userProvider.GetSchemaName()
| Arquivo | Linhas | Operação | Tabelas |
|---|---|---|---|
DropperContactsDailySnapshotRepository.cs | 33 | INSERT | DropperContactsDailySnapshot |
CustomerIndicatorsRepository.cs | 35-38 | SELECT | CustomerIndicators |
PaymentRepository.cs | 91-92 | SELECT | Payments, PaymentsData |
SellerVacationRepository.cs | 23-28 | SELECT | SellersVacations |
StoreRepository.cs | 43-68 | SELECT | Stores |
SellerRepository.cs | 43 | SELECT | Sellers |
SellerIndicatorRepository.cs | 34 | SELECT | SellerIndicators |
UserRepository.cs | 48, 178 | SELECT | Stores, SellersVacations |
CartsRepository.cs | 58-65, 111-112 | SELECT | Carts, CartItems, Stores, Customers, CustomerOptIn, Brands, Products |
StorePaymentsRepository.cs | 25-26 | SELECT | Carts, StoresPayment |
4.7 coezzion-service-payments-reports
Schema source: DIRETO do header (bypass UserProvider)
| Arquivo | Linhas | Operação | Tabelas |
|---|---|---|---|
PaymentRepository.cs | 293-297, 322-326, 367-374, 383, 414-453, 496, 533-541, 579-611, 644-646, 677, 716-718, 769-837, 874-921 | SELECT | Payments, PaymentsEcomm, PaymentsData, NPSReasons, NPSLink, NPSResponseReasons |
StoreRepository.cs | 46-48 | SELECT | Stores, Channels |
ControlOperationRepository.cs | 44-201 | SELECT | PaymentsControlOperation, Brands, Channels, Stores, PaymentsControlOperationByStore |
SalesIndicatorRepository.cs | 155, 194-216, 330 | SELECT | Payments, Stores, Brands, CartItems, Carts |
IntegrationRepository.cs | 127, 150 | SELECT | SetaHistory, CigamHistory |
CartRepository.cs | 248-694 | SELECT | Carts, Stores, Brands, Customers, CartItems |
4.8 coezzion-service-showcase
Schema source: DIRETO do header ou _coreOrgDbContext.CacheKey
| Arquivo | Linhas | Operação | Tabelas |
|---|---|---|---|
ShowcaseTryOnLogRepository.cs | 27-36, 58-70 | SELECT | ShowcaseTryOnLogs |
UserRepository.cs | 28-40, 52-60, 77-88 | SELECT | Stores |
4.9 coezzion-service-integration
Schema source: DIRETO do header (via DatabaseConfig.GetSchema)
Tabelas: Integrations, PaymentTypes (via EF Core MultiTenantContextProvider).
4.10 coezzion-service-message
Schema source: DIRETO do header (via DatabaseConfig.GetSchema)
5. BFF Propagation
Arquivo: coezzion-service-bff/src/BFF.Coezzion/Application/Integration/HttpServices/HttpClientDelegatingHandlerSchema.cs
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var schemaHeader = accessor?.HttpContext?.Request.Headers["api-company-target"];
if (string.IsNullOrEmpty(schemaHeader)) return await base.SendAsync(request, cancellationToken);
request.Headers.Add("api-company-target", [schemaHeader]); // ← repassa sem validar
return await base.SendAsync(request, cancellationToken);
}
Após o fix nos downstreams, a propagação é segura — o serviço destino valida.
6. Cache Key Injection
Schema usado em chave de cache (~12 locais):
// Exemplo: CacheStoreRepositoryDecorator.cs
var cacheKey = $"{_schema}:store:{storeId}";
Schema malicioso → chave de cache arbitrária → leitura/escrita de cache de tenant errado. Risco: corrupção de cache cross-tenant.
7. CompanyTargetHeaderAttribute (Product service)
Arquivo: coezzion-service-product/src/Product.API/Filters/CompanyTargetHeaderAttribute.cs
Valida apenas presença do header (não valida valor). Popula SchemaNameScopedWrapper com valor raw:
if (context.HttpContext.Request.Headers.TryGetValue("api-company-target", out var companyTarget))
{
var wrapperSchema = context.HttpContext.RequestServices.GetService(typeof(SchemaNameScopedWrapper)) as SchemaNameScopedWrapper;
wrapperSchema.SchemaName = companyTarget.ToString(); // ← RAW
}
Este atributo é aplicado em endpoints via [CompanyTargetHeader]. Após o fix no UserProvider, o valor do SchemaNameScopedWrapper é validado no path (E) da GetSchemaName().