36-წუთიანი სამუშაო სესია იმ შეტევებზე, რომლებიც რეალურ აპლიკაციებს ანგრევს — SQL injection, XSS, CSRF, წვდომის კონტროლის დარღვევა, SSRF — რატომ მუშაობს თითოეული და რა მცირე ჩვევები კეტავს მათ. საყრდენი — OWASP Top 10.
ჩვეულებრივი დეველოპერი კითხულობს: "როგორ ვამუშაო ეს?" შემტევი კითხულობს: "რა მოხდება, თუ ისეთ რამეს გამოგიგზავნით, რასაც არ ელოდებით?" უსაფრთხოება სწორედ ის დისციპლინაა, რომ ეს მეორე კითხვა პირველად დაისვას — და რომ ჩათვალოთ: სისტემაში შემოსული ნებისმიერი მონაცემი შეიძლება მტრული იყოს.
სანდო მხარეს ვერაფერი აღწევს ისე, რომ საზღვარზე ვალიდაცია არ გაიაროს.
ჩათვალეთ, რომ ნებისმიერი ერთი კონტროლი ოდესმე გატყდება — ამიტომ დაალაგეთ რამდენიმე ერთმანეთზე. პარამეტრიზებული შეკითხვა და შემავალი მონაცემების ვალიდაცია და მინიმალური უფლებები ბაზის მომხმარებელზე. თუ პირველი ხაზი გატყდა, შემდეგი მაინც უძლებს.
ყველა მომხმარებელს, სერვისსა და ტოკენს მხოლოდ ის უფლებები უნდა ჰქონდეს, რომლებიც რეალურად სჭირდება. ანგარიშგების სამუშაოს მხოლოდ წაკითხვის წვდომა აქვს და არა DROP TABLE. როცა რაღაც კომპრომეტირდება, აფეთქების რადიუსი მცირეა.
ინექცია მაშინ ხდება, როცა უნდობ ტექსტს აწებებთ ბრძანებაში, რომელსაც შემდეგ ინტერპრეტატორი უშვებს — SQL, shell, LDAP-ის ფილტრი. შემტევი წყვეტს მომხმარებლობას და ხდება თქვენი შეკითხვების ავტორი. გამოსწორება ერთი ჩვევაა და თითქმის უფასოა.
const q = "SELECT * FROM users WHERE name = '" + input + "'" db.run(q) // the user controls part of the SQL // input: ' OR '1'='1 -- // query becomes ... WHERE name = '' OR '1'='1' --' // → matches EVERY row, login bypassed
const q = "SELECT * FROM users WHERE name = ?" // ? is a placeholder db.run(q, [input]) // data travels on its own channel // input: ' OR '1'='1 -- // the driver treats it as a literal name string // → matches nothing. injection impossible.
? ადგილმჭერებით, შემდეგ კი ცალკე მნიშვნელობებს. ბაზა შეკითხვის სტრუქტურას ერთხელ აკომპილირებს და მერე მნიშვნელობებს სუფთა მონაცემად ჩასვამს. ისინი ვერასოდეს გახდებიან ახალი SQL. ეს ერთი ტექნიკა მთელ კატეგორიას კლავს.// most ORMs / query builders do this for you users.where({ name: input }) // → prepared statement under the hood // raw escape hatches still need placeholders: db.query("... WHERE id = $1", [id]) // NEVER build SQL with string templates
კოდი და მონაცემი ცალკე არხებით მოძრაობს — მნიშვნელობა SQL-ის გრამატიკაში აღარ ბრუნდება.
DROP TABLE.თუ ინექცია არის "შემტევი წერს თქვენს SQL-ს", XSS არის "შემტევი წერს თქვენს JavaScript-ს". შეაპარეთ <script> გვერდზე, რომელსაც სხვა მომხმარებელი ტვირთავს, და ის ამ მომხმარებლის სესიით გაიშვება — მოიპარავს ქუქებს, ჩაიწერს კლავიშებს, გააკეთებს მოთხოვნებს მისი სახელით. დაცვა: მომხმარებლის კონტენტს მოეპყარით როგორც ტექსტს და შეზღუდეთ, რომელ სკრიპტებს აქვს გაშვების უფლება.
შემტევი მავნე სკრიპტს ინახავს იმ ველში, რომელსაც თქვენ ინახავთ — კომენტარი, პროფილის ბიო, პროდუქტის მიმოხილვა. შემდეგ ის ირთვება ყველასთვის, ვინც ამ კონტენტს ნახავს. ყველაზე მავნე სახეობა, რადგან თავისით ვრცელდება.
URL-იდან ან ფორმიდან მოსული მონაცემი პასუხში ეკრანირების გარეშე ბრუნდება — საძიებო გვერდი, რომელიც წერს You searched for: …. შემტევი აგზავნის მომზადებულ ბმულს; მასზე დაჭერა მათ სკრიპტს მსხვერპლის სესიაში უშვებს.
// /search?q=<script>steal()</script> res.send("Results for " + req.query.q) // the query is reflected into HTML and runs
კლიენტის მხარის JavaScript კითხულობს შემტევის კონტროლირებად რაღაცას (URL-ის ჰეში, localStorage) და გვერდზე წერს innerHTML-ით. მთელი ექსპლოიტი ბრაუზერში ცხოვრობს, ამიტომ სერვერის ლოგებში არაფერი ჩანს.
el.innerHTML = "Hi " + userName // userName = <script>…</script> // the browser parses it as markup and runs it
el.textContent = "Hi " + userName // rendered as literal characters, never markup // in templates: auto-escape < > & " on output
< გამოჩნდეს როგორც ჩვეულებრივი ნიშანი და არა როგორც ტეგის დასაწყისი. თანამედროვე ფრეიმვორკები (React, Vue, Angular, სერვერული შაბლონები) ნაგულისხმევად ავტომატურად ეკრანირებენ — საშიშროება სათადარიგო გასასვლელშია: dangerouslySetInnerHTML, v-html, innerHTML.CSP უსაფრთხოების ბადეა: სკრიპტი რომც შეიპაროს, ბრაუზერი უარს ამბობს inline ან მესამე მხარის კოდის გაშვებაზე.
XSS ბოროტად იყენებს ნდობას, რომელიც მომხმარებელს თქვენი საიტისადმი აქვს. CSRF კი — ნდობას, რომელიც თქვენს საიტს მომხმარებლის ბრაუზერისადმი აქვს. შემტევი სესიას არ იპარავს — ის უკვე ავტორიზებულ სესიას მიაშურებს და მსხვერპლის ბრაუზერს აიძულებს, გააგზავნოს მოთხოვნა, რომლის გაგზავნასაც არ აპირებდა.
მსხვერპლი "გადარიცხვას" საერთოდ არ აჭერს — evil.com მის ნაცვლად აგზავნის, ბრაუზერი კი გულუხვად ურთავს ქუქის.
SameSite=Lax (გონივრული ნაგულისხმევი) კეტავს მას საიტთაშორის POST-ებზე, მაგრამ ჩვეულებრივ ნავიგაციას მაინც უშვებს; SameSite=Strict უფრო მკაცრია. მარტო ეს ამარცხებს კლასიკურ CSRF შეტევას.ორმაგი დაზღვევა: SameSite აჩერებს მოთხოვნის გაგზავნას, ტოკენი კი იმას ნიშნავს, რომ საიტის შიგნით დაშვებული შეცდომაც კი უსაფრთხოდ იკეტება. წმინდა ტოკენური API-ები, რომლებიც ქუქების ნაცვლად Authorization ჰედერით ავთენტიფიცირდებიან, ბუნებრივად მდგრადია CSRF-ის მიმართ.
ყველაზე გავრცელებული გატეხვები ეგზოტიკური არაა — ეს არის დავიწყება, შეამოწმო ვინ ითხოვს; სერვერისთვის იმ URL-ის წამოღების უფლების მიცემა, რომელიც არ უნდა წამოიღოს; ან გასაღების დატოვება რეპოზიტორიაში. წვდომის კონტროლის დარღვევა OWASP Top 10-ის სათავეში იდგა. თითოეული ქვემოთ გახსენით.
აპლიკაცია ამოწმებს, ვინ ხართ, მაგრამ ავიწყდება შემოწმება, რას შეხება გაქვთ უფლება. კლასიკური ფორმაა IDOR — Insecure Direct Object Reference — როცა URL-ში id-ის შეცვლა სხვა მომხმარებლის ჩანაწერს გაძლევთ.
app.get("/invoice/:id", (req) => db.invoice(req.params.id)) // /invoice/1002 → someone else's invoice
app.get("/invoice/:id", (req) => { const inv = db.invoice(req.params.id) if (inv.ownerId !== req.user.id) forbid() return inv })
ავტორიზაცია აღასრულეთ სერვერზე, ყოველ მოთხოვნაზე, ნაგულისხმევად უარით. არასოდეს დაეყრდნოთ დამალულ ღილაკს ან კლიენტის მხარეს როლის შემოწმებას.
SSRF მაშინაა, როცა შემტევი თქვენს სერვერს აიძულებს, მოთხოვნა გაუგზავნოს მის მიერ შერჩეულ მისამართს. რადგან სერვერი თქვენს ქსელშია, ის წვდება შიდა სერვისებს და ღრუბლის მეტამონაცემების ენდპოინტს — ხშირად წვდომის მონაცემებსაც გასცემს.
// "give us an image URL and we'll fetch it" await fetch(req.query.url) // url = http://169.254.169.254/latest/meta-data/ // → leaks cloud credentials
const u = new URL(req.query.url) if (!ALLOWED_HOSTS.has(u.hostname)) reject() // also block private / link-local IP ranges await fetch(u)
შეამოწმეთ დაშვებული ჰოსტების თეთრი სიის მიხედვით, ამოხსენით და დაბლოკეთ პრივატული IP-დიაპაზონები და გამორთეთ რედირექტები. SSRF-მა OWASP Top 10-ში საკუთარი ადგილი დაიმსახურა.
საიდუმლოები — API-გასაღებები, ბაზის პაროლები, ხელმოწერის გასაღებები — კოდში ჩაწერილი, git-ში აკომიტებული ან ლოგებში დაბეჭდილი. როგორც კი საიდუმლო git-ის ისტორიაში მოხვდა, ის სამუდამოდ კომპრომეტირებულია, იმ ხაზის წაშლის შემდეგაც კი. ეს ერწყმის OWASP-ის კატეგორიებს Cryptographic Failures და Security Misconfiguration.
const key = "sk_live_9f2aQ...c4" // committed → in git history forever // scraped by bots within minutes
const key = process.env.STRIPE_KEY // from a secrets manager / env, .gitignore'd // rotate on a schedule; scan commits in CI
საიდუმლოები შეინახეთ გარემოს ცვლადებში ან საიდუმლოების მენეჯერში (Vault, AWS/GCP Secrets Manager). CI-ს დაამატეთ ავტომატური საიდუმლოების სკანირება და გაუშვით გონივრული ნაგულისხმევებით — პროდაქშენში არ იყოს debug-გვერდები და ნაგულისხმევი ადმინის პაროლები.
ინსტრუმენტები აზროვნებას არ ცვლის — ისინი მას მასშტაბირებენ. ოთხი ოჯახი სხვადასხვა მომენტს ფარავს: საკუთარი კოდის სკანირება, გაშვებულ აპლიკაციაზე შეტევა, დამოკიდებულებების თვალყური და ცოცხალი ტრაფიკის ფილტრაცია. გამოიყენეთ ისინი ერთად; თითოეული იჭერს იმას, რასაც დანარჩენები უშვებენ.
Static Application Security Testing კითხულობს თქვენს კოდს მისი გაშვების გარეშე და აღნიშნავს სარისკო შაბლონებს — ნედლი SQL-ის კონკატენაცია, innerHTML-ის მიმღები — აკრეფისას ან CI-ში. პრობლემებს ყველაზე ადრე იჭერს, როცა ისინი ყველაზე იაფია.
Dynamic Application Security Testing ცოცხალ დეპლოიმენტს გარედან ამოწმებს, როგორც ნამდვილი შემტევი — ფაზინგით ამოწმებს შემავალ მონაცემებს, იმეორებს მოთხოვნებს — და პოულობს გაშვების დროისა და კონფიგურაციის პრობლემებს, რომლებსაც კოდის სკანირება ვერ ხედავს.
თქვენი აპლიკაციის უმეტესი ნაწილი კოდია, რომელიც თქვენ არ დაგიწერიათ. ეს ინსტრუმენტები აკვირდება თქვენს package.json-ს / lock-ფაილს და ეძებს ცნობილი CVE-ების მქონე ბიბლიოთეკებს (OWASP-ის "Vulnerable and Outdated Components"), შემდეგ კი განახლების PR-ებს ხსნის.
Web Application Firewall დგას თქვენი აპლიკაციის წინ და შაბლონით ბლოკავს მავნე მოთხოვნებს — ინექციის სტრიქონებს, ცნობილ ცუდ ბოტებს — პლუს სიხშირის ლიმიტი და DDoS-ის შერბილება. ეს ფარია და არა თვით ბაგის გამოსწორება.
საფრთხის მოდელირება უბრალოდ იმ კითხვის სტრუქტურირებული ვერსიაა, რომლითაც დავიწყეთ: რა შეიძლება წახდეს აქ? ოთხი კითხვა, დასმული ფუნქციონალის გაშვებამდე — სპეციალური ინსტრუმენტები არ სჭირდება.
რას ვაშენებთ? ჩახაზეთ მონაცემთა ნაკადი და მონიშნეთ ნდობის საზღვრები.
რა შეიძლება წახდეს? გაიარეთ თითოეული შემავალი მონაცემი — გაყალბება, ჩარევა, ინფორმაციის გაჟონვა, უფლებების ამაღლება.
რას გავაკეთებთ? თითო რისკზე აირჩიეთ კონტროლი — ეკრანირება, პარამეტრიზაცია, ავტორიზაცია.
იმუშავა? გადაამოწმეთ ტესტით, სკანირებით ან მიმოხილვით.
ხუთი სწრაფი კითხვა ინექციაზე, XSS-ზე, CSRF-ზე, წვდომის კონტროლსა და ინსტრუმენტებზე — მყისიერი პასუხი, ავტორიზაციის გარეშე.
ნავიგაცია ← → ღილაკებით ან სქროლით · უკან ბიბლიოთეკაში