SQL injekcija Zalktis grāmatvedības programmā, importējot e-rēķinus
Zalktis grāmatvedības programma (Windows)
CVE-2026-59109 ir SQL injekcija, ko OffSeq atklāja Zalktis – Latvijas grāmatvedības programmā. Importējot saņemtu elektronisko rēķinu, Zalktis veido datubāzes vaicājumus, ievietojot laukus no ienākošā dokumenta – reģistrācijas un PVN numuru, EAN svītrkodu un mērvienības kodu – tieši SQL tekstā, bez parametrizācijas un bez ekranēšanas. Vairākus no šiem laukiem nosaka tas, kurš nosūta rēķinu, tādēļ viens sagatavots rēķins, ko grāmatvedis vienkārši importē, var nolasīt un mainīt grāmatvedības datubāzi. Zalktis Programmas to novērsa, parametrizējot vaicājumus.
Kritiskums: AugstsCVSS8.7CVE-2026-591095 min lasīšanai
§ 01. Pārskats
Zalktis ir Windows grāmatvedības programma, ko izmanto Latvijas uzņēmumi. Tāpat kā lielākā daļa grāmatvedības programmatūras Latvijā, tā importē elektroniskos rēķinus – UBL dokumentus, kas piegādāti pa nacionālo e-adreses / PEPPOL tīklu – un e-komercijas eksportus, pārvēršot katru par virsgrāmatas ierakstiem. Saņemta rēķina importēšana ir ikdienas grāmatvedības darbība.
OffSeq atklāja, ka Zalktis veidoja SQL aiz šiem importiem, ievietojot laukus, kas paņemti tieši no ienākošā dokumenta, vaicājuma tekstā. Vairākus no šiem laukiem kontrolē tas, kurš nosūta rēķinu, tādēļ sagatavots rēķins var izlauzties no virknes literāļa un izpildīt uzbrucēja izvēlētu SQL grāmatvedības datubāzē. Nav vajadzīgs ne konts, ne iepriekšēja saikne, ne tīkla pozīcija – PEPPOL tīklā jebkurš var adresēt rēķinu upurim, un vienīgā darbība, ko upuris veic, ir tā importēšana. Zalktis Programmas problēmu novērsa, parametrizējot vaicājumus; tā tiek uzskaitīta kā CVE-2026-59109, koordinē CERT.LV.
§ 02. E-rēķina importēšana
Kad Zalktis importē saņemtu e-rēķinu vai e-komercijas eksportu, tā nolasa dokumentu (ReadXml / XmlSerializer.Deserialize) un katram darījuma partnerim un rindas ierakstam pārbauda, vai atsauktais ieraksts jau eksistē – klients pēc reģistrācijas vai PVN numura, prece pēc svītrkoda, mērvienība pēc tās koda. Šīs pārbaudes ir funkcijas, piemēram, KlientsNMREksiste, KlientsPVNnrEksiste un PreceEANEksiste.
Katra funkcija izpilda SELECT pret lokālo VistaDB datubāzi. Problēma ir tajā, kā SELECT tiek veidots: meklējamā vērtība tiek paņemta no ienākošā dokumenta un tieši apvienota SQL virknē, un vaicājums izpildās uz neparametrizēta OleDbCommand / VistaDBDataAdapter. Programmā ir ekranēšanas funkcija Dazadi.sql_txt(), kas izņem vienpēdiņas, taču šajos importa ceļos tā nekad netiek izsaukta.
§ 03. Cēlonis: partnera kontrolēti lauki vaicājuma tekstā
Četri lauki, kas pārnesti no ienākošā dokumenta, nonāk vaicājumā neekranēti:
| Lauks saņemtajā dokumentā | Izveidotais vaicājums | Atrašanās vieta |
|---|---|---|
Reģistrācijas numurs (BuyerParty/SellerParty RegNumber) | SELECT PersonalID FROM Personal WHERE NMRNR = '<lauks>' | DatuParbaude.cs:320 |
PVN numurs (VATRegNumber) | ... WHERE PvnNr = '<lauks>' | DatuParbaude.cs:459 |
| EAN / svītrkods | ... WHERE Svitrkods = '<lauks>' | DatuParbaude.cs:763 |
Mērvienības kods (<cbc:InvoicedQuantity unitCode="…">) | SELECT MervienibaLV FROM Units WHERE UneceCode = '<lauks>' | frmreksaraksti.cs:3381 |
Katrs no tiem atrodas SQL '…' virknes literālī. Tā kā sūtītājs kontrolē lauku un Dazadi.sql_txt() netiek piemērota, vienpēdiņa laukā noslēdz literāli, un viss aiz tās tiek parsēts kā SQL. Reģistrācijas un PVN numurs un svītrkods ir uzbrucēja izvēlētas vērtības rēķinā; mērvienības kods ir XML atribūts katrā rindā, tādēļ unitCode ceļš tiek sasniegts automātiski katrai rēķina rindai importa laikā, bez papildu darbībām.
§ 04. Koncepcijas pierādījums
Atradums tika demonstrēts ar nekaitīgu, uz Būla loģiku balstītu testu, kas nemaina datus.
Derīgs UBL rēķins nes slodzi pircēja reģistrācijas numura laukā:
<cac:AccountingCustomerParty>
<cac:Party>
<cac:PartyLegalEntity>
<cbc:CompanyID>40003' AND '1'='1</cbc:CompanyID>
</cac:PartyLegalEntity>
</cac:Party>
</cac:AccountingCustomerParty>Kad rēķins tiek importēts, KlientsNMREksiste izveido un izpilda:
SELECT PersonalID FROM Personal WHERE NMRNR = '40003' AND '1'='1'Variants ar '1'='1' un variants ar '1'='2 atgriež atšķirīgus rezultātus – ieraksts atrasts pret neatrasts –, kas apstiprina, ka lauks tiek izpildīts kā SQL, nevis apstrādāts kā dati. No tā paša ieejas punkta UNION izvelk datus:
SELECT PersonalID FROM Personal WHERE NMRNR = '40003' UNION SELECT Parole FROM Lietotajs WHERE '1'='1'Mērvienības kods sasniedz datubāzi bez jebkādas upura mērķēšanas – tas nostrādā katrai rēķina rindai importa laikā:
<cbc:InvoicedQuantity unitCode="C62' UNION SELECT MervienibaLV FROM Units WHERE '1'='1">1</cbc:InvoicedQuantity>kas rada:
SELECT MervienibaLV FROM Units WHERE UneceCode = 'C62' UNION SELECT MervienibaLV FROM Units WHERE '1'='1'Visa testēšana tika veikta kontrolētā vidē pret sistēmām, ko OffSeq kontrolē; neviena trešās puses sistēma netika skarta.
§ 05. Ietekme un kritiskums
Publicētie rādītāji ir CVSS 4.0 bāzes vērtība 8.7 (augsts) (CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N) un CVSS 3.1 bāzes vērtība 8.8 (augsts) (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H). Vektors ir tīkla, ar zemu sarežģītību, bez privilēģijām: Latvijas e-adreses / PEPPOL tīklā jebkurš sūtītājs var piegādāt rēķinu upurim. Vienīgā mijiedarbība (UI:P / UI:R) ir upura veikta importēšana – ikdienas grāmatvedības darbība, nevis drošības lēmums.
Veiksmīga injekcija sniedz pilnu piekļuvi grāmatvedības datubāzei: konfidenciālus datus var nolasīt un eksfiltrēt ar UNION un uz Būla loģiku balstītu izvilkšanu – klientu ierakstus, akreditācijas datus, piemēram, Lietotajs.Parole vērtības piemērā, finanšu datus –, un ierakstus un maksājumus var mainīt. Tā kā ievainojamais paraugs ir kopīgs aptuveni 20 importa kanāliem, tā nav viena izolēta forma: skarta ir katra instalācija, kas importē dokumentus.
§ 06. Novēršana
Labojums ir pārstāt veidot vaicājumus no dokumenta teksta. Skartajiem SELECT vaicājumiem virkņu apvienošanas vietā jāizmanto parametri – piesaistītas vērtības, ko datubāze apstrādā kā datus, nevis kā SQL – katrā no kopīgajiem importa ceļiem: reģistrācijas un PVN numura un svītrkoda meklēšanā, mērvienības meklēšanā un aptuveni 20 kanālos, kas atkārtoti izmanto šo paraugu.
Zalktis Programmas parametrizēja skartos vaicājumus un izlaida labotus laidienus: 2026.1.586 zaram pirms 2026. gada 1. jūlija un 2026.2.592 zaram, kas izlaists no 2026. gada 1. jūlija. Lietotājiem jāatjaunina uz vienu no šiem laidieniem vai jaunāku. Kamēr labotais laidiens nav izvietots, izvairieties importēt e-rēķinus un e-komercijas eksportus, kas saņemti no neuzticamiem sūtītājiem, un uzturiet atjaunojamas grāmatvedības datubāzes rezerves kopijas, kas izveidotas pirms jebkura importa.
§ 07. Atklāšana un iznākums
Ievainojamība tika paziņota CERT.LV, izmantojot Latvijas nacionālo koordinētās ievainojamību atklāšanas platformu, 2026. gada 30. jūnijā, izvērtēta un pārsūtīta ražotājam 1. jūlijā. Zalktis Programmas apstiprināja novēršanu ar vaicājumu parametrizāciju 2026. gada 16. jūlijā, versijās 2026.1.586 un 2026.2.592, un CVE-2026-59109 tika rezervēts 2026. gada 30. jūlijā. Atklāšanu koordinē CERT.LV.
Atradums tiek attiecināts uz Nilu Putniņu (OffSeq Cybersecurity).
§ 08. Atklāšanas hronoloģija
- 2026-06-30OffSeq ziņo CERT.LV par SQL injekciju, izmantojot nacionālo koordinētās atklāšanas platformu
- 2026-07-01CERT.LV izvērtē ziņojumu un pārsūta ražotājam
- 2026-07-16Zalktis apstiprina labojumu – skartie vaicājumi ir parametrizēti – versijās 2026.1.586 un 2026.2.592
- 2026-07-30Rezervēts CVE-2026-59109
