Ծրագրային անվտանգություն՝ SQL Injection (մաս 2)
Tags: owasp, security, sql
Published: 2021-05-23 19:31:00

[Նախորդ գրառման](https://dev.to/n_babajanyan/sql-injection-1-lc0) մեջ տեսանք SQL Injection խոցելիության ընդհանուր բնութագիրը։ Այժմ փորձենք OWASP Juice Shop խոցելի վեբ֊ծրագրում փնտրել տվյալ տեսակի խոցելիություններ։

Ծրագիրը արխիվի տեսքով կարելի է ներբեռնել նախագծի [GitHub էջից](https://github.com/bkimminich/juice-shop/releases/), այնուհետև `npm start` հրամանի միջոցով գործարկել այն (նախապես `npm install` հրամանով տեղադրելով ծրագրի պահանջված գրադարանները)։ Գործարկվելիս, Juice Shop֊ը հասանելի է դառնում `localhost:3000` հասցեով։ 

### 1) Log in with the administrator's user account.

Առաջին հերթին կարելի է ուշադրություն դարձնել Account հղմամբ գտնվող մուտք գործման (login) հատվածին և այստեղ փորձել SQL injection֊ի պարզագույն տարբերակ՝

*email:* `' OR 1=1 -- `
*password:* `test`

![Մուտք ադմինիստրատորի օգտահաշվով](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/on78n8aryhleq7cfa94a.png)

Ստանում ենք հաջողված մուտք ադմինիստրատորի օգտահաշվով, այսինքն տվյալ ծրագրի login էջի *email* դաշտը ենթակա է SQL injection հարձակման։ 

Փորձենք հասկանալ ինչու է առաջանում տվյալ խոցելիությունը։ Juice Shop ծրագրի Score board էջում ներկայացված են բոլոր խոցելիությունները, և ամփոված է Ձեր կողմից հայտնաբերված խոցելիությունների քանակը և տեսակները։ Որոշ խոցելիությունների պարագայում, ծրագիրն առաջարկում է կարդալ կոդի այն հատվածը, որտեղ այն առաջանում է, նման կերպ այն կանխել սովորեցնելու նպատակով։ 

![Score board էջ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5we76uk6f201gr1rml5l.png)

Մենք հենց նոր լուծեցինք **"Log in with the administrator's user account"** խնդիրը։ Համապատասխան տողի վերջին կոճակը ցույց է տալիս խոցելի կոդի հատվածը, այստեղ տեսնում ենք ինչպես է ծրագրում կազմվում SQL հարցումը՝

`SELECT * FROM Users WHERE email = '${req.body.email || ''}' AND password = '${security.hash(req.body.password || '')}' AND deletedAt IS NULL
`

Այստեղ `${req.body.email || ''}` և `${security.hash(req.body.password || '')}` հանդիսանում են Node.JS պլատֆորմի համար գրված կոդ, որը HTTP հարցումից ստանում է էլ․ փոստը և գաղտնաբառը (գաղտնաբառը նաև անցնում է [գաղտնագրման հեշ ֆունկցիայով](https://hy.wikipedia.org/wiki/%D5%80%D5%A5%D5%B7_%D6%86%D5%B8%D6%82%D5%B6%D5%AF%D6%81%D5%AB%D5%A1)), որով պետք է մուտք գործել։ Արդյունքում ստացված տվյալներն **առանց որևէ ստուգման կամ ֆիլտրման** հայտնվում են SQL հարցման մեջ, ինչն էլ հանդիսանում է SQL injection խոցելիության հիմնական պատճառը։ 

Հետևաբար, երբ *email* դաշտում մուտագրում ենք `' OR 1=1 -- ` տողը, ընդհանուր SQL հարցումը ստանում է տվյալ տեսքը՝
```sql
SELECT * FROM Users WHERE email = '' OR 1=1 -- ' AND password  '${security.hash(req.body.password || '')}' AND deletedAt IS NULL
```
Տվյալ հարցումն, ըստ էության, վերցնում է տվյալների բազայում պատահած առաջին օգտահաշիվը, առանց էլ․ փոստի և գաղտնաբառի ստուգման (քանի որ գաղտնաբառի ստուգման հատվածն այլևս մեկնաբանություն է և հետևաբար առհամարվում է)։

### 2) Exfiltrate the entire DB schema definition via SQL Injection.
Հաջորդ "մարտահրավերը" կայանում է տվյալների բազայի կառուցվածքը SQL Injection֊ի միջոցով բացահայտելու մեջ։ 

Մինչ այդ, սակայն, ուսումնասիրենք SQL Injection կիրառման հայտնի ձևերից մեկը, որը կոչվում է **UNION SELECT հարձակում**

### UNION SELECT Injection

SQL լեզվում `UNION` հրամանը վերադարձնում է երկու աղյուսակների **միավորումը**։ Օգտագործման օրինակին կարելի է ծանոթանալ [այստեղ](https://www.w3schools.com/sql/sql_union.asp)

SQL Injection հարձակումների ենթատեքստում `UNION` հրամանն օգտագործվում են տվյալների մեկ աղբյուրի միջոցով, մեկ այլ աղյուսակից տվյալներ ստանալու նպատակով։ 

Այսինքն մեզ առաջին հերթին անհրաժեշտ է այնպիսի դաշտ, որը ենթակա է SQL Injection հարձակման և որի հարցման արդյունքները տեսանելի են մեզ համար։ Juice Shop ծրագրի պարագայում, այդպիսի ամենահավանական դաշտը ապրանքների որոնման դաշտն է։ Օգտագործելով [OWASP ZAP](https://www.zaproxy.org/) պրոքսի գործիքը (հարցումները և պատասխանները մանրամասն ուսումնասիրելու համար), տեսնում ենք, որ որոնման դաշտում որևէ բառ մուտքագրելիս, հարցում է ուղարկվում `http://localhost:3000/rest/products/search` հասցեին, իսկ մուտքագրված արտահայտությունն ուղարկվում է `q` պարամետրի տեսքով։ 

*Որոնված արտահայտություն՝* apple
*Հարցման հասցե՝* `http://localhost:3000/rest/products/search?q=apple`

Փորձենք տեղադրել արդեն իսկ քաջ ծանոթ `' OR 1=1 -- ` արտահայտությունը՝ `http://localhost:3000/rest/products/search?q=' OR 1=1 -- ` հասցեով ուղարկելիս ստանում ենք **500 SequelizeDatabaseError: SQLITE_ERROR: incomplete input** տեքստով սխալ։ Թվում է թե օգտակար ոչինչ չստացանք, սակայն ուշադրություն դարձնելով տրված սխալի տեքստին՝ հասկանում ենք, որ գործ ունենք SQLite տեսակի տվյալների բազայի հետ (SQL ընտանիքի այլ տվյալների բազաներից են MySQL֊ը, Oracle֊ը, MSSQL֊ը), տեսնենք թե ինչպես կարող է այս տեղեկատվությունը մեզ օգտակար լինել։

### Տվյալների բազաների կառուցվածք

SQL տվյալների բազաներն իրենց մեջ պահում են հատուկ աղյուսակ, որում գրանցվում են տվյալ բազայում տեղ գտած աղյուսակների մասին տվյալներ՝ անվանումները, տեսակները, աղյուսակի ստեղծման համար օգտագործված SQL կոդը և այլն։ 

Քանի որ արդեն գիտենք, որ Juice Shop ծրագիրն աշխատում է SQLite բազայի հետ, փնտրում ենք մանրամասն տեղեկատվություն այս տեսակի բազաներում կառուցվածքային տվյալների պահման մասին։ Ինչպես երևում է [SQLite կայքում զետեղված փաստաթղթից](https://sqlite.org/schematab.html), կառուցվածքային տեղեկատվությունը պահվում է **sqlite_schema** կոչվող աղյուսակում, որը պարունակում է թվով 5 սյունակ։

Ստացվում է, որ այս խնդիրը լուծելու համար անհրաժեշտ է կատարել `UNION SELECT` հարցում և միավորել ապրանքների որոնման և **sqlite_schema** աղյուսակին արված հարցումների արդյունքները։ 

Հիշեցնեմ, որ `UNION SELECT` հարցում կատարելիս, միավորվող կողմերի աղյուսակները պետք է ունենան հավասար քանակի և նույն տիպերի սյունակներ։ Ապրանքների որոնման դաշտում հարցում կատարելիս տեսնում ենք, որ վերադարձված տվյալներն ունեն թվով 9 սյունակ։ Այս տարբերությունը կարելի է հարթել, պակաս սյունակներով կողմում դատարկ սյունակներ ավելացնելով (կամ թվերի, կամ `null` արժեքի տեսքով)։ Փորձենք որոնման `q` պարամետրի տեսքով ուղարկել հետևյալ արտահայտությունը՝ 

`' AND 1=2 UNION SELECT *, 6, 7, 8, 9 FROM sqlite_schema -- `

Այստեղ `AND 1=2` արտահայտության իմաստը կայանում է նրանում, որ  երբեք չբավարարվող պայմանի միջոցով բացառենք ապրանքների աղյուսակից տվյալների ստացումը և կենտրոնանանք բացառապես `sqlite_schema` աղյուսակի արդյունքների վրա։

Այս հարցումը նույնպես վերադաձնում է սխալ, ասելով որ այն թերի է կազմված։ Բարեբախտաբար, Score Board էջում առաջարկվում է տեսնել տվյալ խնդրի համար պատասխանատու կոդը և դրա միջոցով կարող ենք տեսնել թե ինչ SQL հրամաններ են կատարվում ապրանքների որոնման ժամանակ։ Կոդում զետեղված հրամանն ունի հետևյալ տեսքը՝ 

`SELECT * FROM Products WHERE ((name LIKE '%${criteria}%' OR description LIKE '%${criteria}%') AND deletedAt IS NULL) ORDER BY name`, որտեղ `criteria` փոփոխականը մեր կողմից մուտքագրված արտահայտությունն է (օրինակ՝ *apple juice*)։

Ուշադրություն դարձնենք (( բացված փակագծերին, քանի որ դրանք էին մեր հարցման սխալի հիմնական պատճառը։ Հաշվի առնելով սա, մի փոքր փոփոխում ենք մեր ուղարկված արտահայտությունը՝ 
`' AND 1=2)) UNION SELECT *, 6, 7, 8, 9 FROM sqlite_schema -- `

Այս դեպքում հարցման ամբողջական հասցեն կդառնա՝

`localhost:3000/rest/products/search?q=' AND 1=2)) UNION SELECT *, 6, 7, 8, 9 FROM sqlite_schema -- `

Եվ, ինչպես սպասելի էր, հարցումն ուղարկելուց հետո, տվյալ խնդիրը համարվում է լուծված և ստանում ենք տվյալների բազայի կառուցվածքային մանրամասները։ 

### 3) Retrieve a list of all user credentials via SQL Injection

Այս խնդիրը մեզանից պահանջում է հայտնաբերել բոլոր օգտատերերի տվյալները (էլ․փոստ, գաղտնաբառ և այլն)։

Նախորդ խնդիրը լուծելիս ստացանք տվյալների բազայի բոլոր աղյուսակների անունները և կազմավորման կոդը։ Ուսումնասիրելով ստացվածը, տեսնում ենք, որ ունենք `Users` կոչվող աղյուսակ, որը կազմվել է հետյալ SQL հրամանով՝ 

```sql
CREATE TABLE `Users` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `username` VARCHAR(255) DEFAULT '', `email` VARCHAR(255) UNIQUE, `password` VARCHAR(255), `role` VARCHAR(255) DEFAULT 'customer', `deluxeToken` VARCHAR(255) DEFAULT '', `lastLoginIp` VARCHAR(255) DEFAULT '0.0.0.0', `profileImage` VARCHAR(255) DEFAULT '/assets/public/images/uploads/default.svg', `totpSecret` VARCHAR(255) DEFAULT '', `isActive` TINYINT(1) DEFAULT 1, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `deletedAt` DATETIME)
```

Այս հրամանն ուսումնասիրելով կարելի է տեսնել թե ինչ սյունակներ  է պարունակում `Users` աղյուսակը։ Մեզ հատկապես հետաքրքրում են `email` և `password` սյունակները։ 

Հետևելով նախորդ խնդրի օրինակին, կրկին դիմում ենք `UNION SELECT` հարձակման օգնությանը, այս անգամ միավորելով ապրանքների որոնման հարցումը `Users` աղյուսակին կատարված հարցման հետ։

Հիշեցնեմ, որ ապրանքների աղյուսակը պարունակում է թվով 9 սյունակ, իսկ `Users`֊ից մեզ անհրաժեշտ են ընդամենը երկուսը։ Այդ պատճառով, `SELECT` հրամանը կազմում ենք հետևյալ կերպ՝ 
`SELECT 1,email,password,4,5,6,7,8,9 FROM Users`

Թվական արժեքների միջոցով սյունակների ընդհանուր քանակը հասցնում ենք 9֊ի, իսկ `email` և `password` սյունակները տեղադրում ենք այնպես, որ ապրանքների աղյուսակի տվյալ սյունակներն ունենան նույն տիպը (այս դեպքում՝ `text` տիպը)։ Ուսումնասիրելով ապրանքների որոնման արդյունքները, տեսնում ենք, որ երկրորդ և երրորդ սյունակները պարունակում են տեքստային տվյալներ, հետևաբար կարող են միավորվել `email` և `password` սյուների հետ։ 

Ընդհանուր հարցումը կունենա հետևյալ տեսքը՝
`localhost:3000/rest/products/search?q=' AND 1=2)) UNION SELECT 1,email,password,4,5,6,7,8,9 FROM Users -- `

Խնդիրը լուծված է։

### Օգտակար հղումներ

1) [CyHub Armenia֊ի և Վահագն Վարդանյանի "Ծրագրային անվտանգության հիմունքներ" հայերեն դասընթացը](https://stepik.org/course/92334/syllabus)
2) [SQL injection թեման PortSwigger Web Security Academy֊ում](https://portswigger.net/web-security/sql-injection)
3) [SQL injection թեման Kontra Application Security Training կայքում](https://application.security/free-application-security-training/owas-top-10-sql-injection)