Stored Procedure SQL Injection
0x1. Intro
ํ๋ก์์ (procedure)์ ์ฌ์ ์ ์๋ฏธ๋ โ์ด๋ค ์ผ์ ํ๋ ๊ณต์์ ์ด๊ฑฐ๋ ์ธ์ ๋ ๋ฐฉ์์ธ ์ผ๋ จ์ ํ๋โ ์ด๋ค. ์ด ํฌ์คํ ์์๋ DB์๋ฒ์ ์ ์ฅ๋ ํ๋ก์์ ์ธ Stored Procedure์ ๋ํด ์์๋ณด๊ณ ์ด์ SQL Injection ๊ฐ๋ฅ์ฑ์ ๋ํด ์ดํด๋ณด๊ฒ ๋ค.
0x2. Stored Procedure๋?
์ฌ๋ฌ SQL์ ์ฌ์ฉํ๊ธฐ ์ํ ์ฟผ๋ฆฌ๋ค์ ์งํฉ. DB๋ด๋ถ์ ์ ์ฅ๋ ์ฌ์ฉ์ ์ง์ ํจ์์ ๋น์ทํ๋ค๊ณ ์๊ฐํ๋ฉด ํธํ ๊ฒ ๊ฐ๋ค.
- SQL Server์์ ์ ๊ณต๋๋ ํ๋ก๊ทธ๋๋ฐ ๊ธฐ๋ฅ. ์ผ๋ จ์ ์ฟผ๋ฆฌ๋ฅผ ํ๋์ ๋ชจ๋ํ ์์ผ ์ฌ์ฉ
- ์์ฃผ ์ฌ์ฉํ๋ ์ฟผ๋ฆฌ๋ค์ ์งํฉ์ Stroed Procedure๋ก ์ ์ฅํ์ฌ ์ฌ์ฌ์ฉ
- ํจ์์ ๊ฐ์ด parameter๋ค์ ์ ๋ฌํ์ฌ ์ฌ์ฉ ๊ฐ๋ฅ
SQL์ ์ฌ์ฉ์ ์ง์ ํจ์๋ Stored Procedure์ ๋น์ทํด๋ณด์ด์ง๋ง ๋ช ํํ ์ฐจ์ด๊ฐ ์๋ค.
| ย | Stored Function | Stored Procedure |
|---|---|---|
| Return Value | return๋ฌธ์ผ๋ก ํ๋์ ๊ฐ ๋ฆฌํด | ์ฌ๋ฌ๊ฐ์ OUTํ๋ผ๋ฏธํฐ ์ฌ์ฉ |
| Create Syntax | CREATE FUNCTION โฆ. | CREATE PROCEDUREโฆ |
| Call Syntax | SELECT | CALL |
| Exception Handling | - | TRYโฆCATCH |
| ํธ์ถ ์์น | Function์์ Stored Procedure ํธ์ถ ๋ถ๊ฐ | Stored Procedure์์ Function ํธ์ถ ๊ฐ๋ฅ |
| ์ฌ์ฉ ๊ฐ๋ฅ ๋ช ๋ น์ด | INSERT, UPDATE, DELETE ์ฌ์ฉ ๋ถ๊ฐ | INSERT, UPDATE, DELETE ์ฌ์ฉ ๊ฐ๋ฅ |
Stored Procedure๋ DB๊ฐ์ ์กฐ์ ๋ฐ ์กฐํ ์ฟผ๋ฆฌ๋ฌธ๋ค์ ๋ฌถ์ด ์ฌ์ฉํ๋ ํ๋์
API,
Stored Function์ DB๋ฅผ ์กฐํํ๊ณ ์ด๋ฅผ ์ฐ์ฐํ๋ ๋ฑ ๊ฐ๋จํ ์์ ์ ์ํ SQL ๋ฌถ์์ผ๋ก ๋ณผ ์ ์๋ค.
Stored Procedure(SP)๋ ๋ถ์กฑํ ์ต์ ํ๋ก ์ธํด ์ฒ๋ฆฌ ์๋๊ฐ ๋๋ฆฌ๊ณ ์ฝ๋ ์ฌ์ฌ์ฉ์ฑ์ ๋นํจ์จ์ ์ด์ง๋ง, ์๋ฒ์์ ํ๋ฒ์ ์์ฒญ์ผ๋ก ์ฌ๋ฌ SQL๋ฌธ์ ์คํ ํ ์ ์๊ธฐ ๋๋ฌธ์ ๋คํธ์ํฌ ๋ถํ๋ฅผ ์ค์ผ ์ ์๊ณ DB ์ ์ ๋์ ํ๋ก์์ ธ์ ๊ถํ์ ๋ถ์ฌํ ์ ์์ด ๋ณด์์ฑ ํฅ์์ ์ํด ์ฌ์ฉํ๊ธฐ๋ ํ๋ค.
SP๊ฐ ๋ฌด์์ธ์ง ๊ธ๋ก ์์๋ดค์ผ๋ ์ด์ ๋ถํฐ๋ MySQL์ ์ง์ ์กฐ์ํ๋ฉฐ ์์๋ณด์. ๋ค๋ฅธ DB๋ฅผ ์ฌ์ฉํด๋ ๋ฌธ๋ฒ์ ์ฐจ์ด๋ง ์กด์ฌํ ๋ฟ ์๋ ์ค์ต ๋ด์ฉ๋ค์ ๊ฒฐ๊ณผ๋ ํฌ๊ฒ ๋ค๋ฅด์ง ์๋ค.
1
2
3
4
5
6
mysql> select @@version;
+-------------------------+
| @@version |
+-------------------------+
| 8.0.36-0ubuntu0.22.04.1 |
+-------------------------+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CREATE DATABASE testDB;
CREATE TABLE employees(
id int NOT NULL,
name varchar(50),
location varchar(50),
age int,
PRIMARY KEY (id)
);
INSERT INTO employees (id, name, location, age)
VALUES
(1, 'Alpha', 'Australia', 20),
(2, 'Bravo', 'Brazil', 28),
(3, 'Charlie', 'China', 19),
(4, 'Delta', 'Denmark', 33);
0x3. Stored Procedure ํ์ฉ
Stored Procedure ์์ฑ, ์ญ์
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
USE testDB;
DROP procedure IF EXISTS genSP; -- SP ์ญ์ . `IF EXISTS` ์๋ต ๊ฐ๋ฅ
-- SP ์์ฑ
DELIMITER $$
USE testDB $$
CREATE PROCEDURE genSP(IN employeeName VARCHAR(255), OUT userCnt INT, INOUT plus INT)
BEGIN
DECLARE sum INT DEFAULT 10;
-- sum ์ง์ญ๋ณ์๋ฅผ ์ ์ธํ๊ณ 10์ผ๋ก ์ด๊ธฐํ
SELECT * FROM testDB.employees WHERE name = employeeName;
-- emplyees table์์ name ์ปฌ๋ผ์ด ์
๋ ฅ๋ฐ์ employeeName์ธ ํ ์ถ๋ ฅ
SELECT count(User) INTO userCnt FROM mysql.user;
-- MySQL ์ ์ ์ ์๋ฅผ userCnt์ ํด๋นํ๋ ๋ณ์์ ๋ฆฌํด
SET plus = plus + sum;
-- ์ด๊ธฐํ๋ ๊ฐ plus์ sum์ ๊ฐ์ ๋ํด plus์ ํด๋นํ๋ ๋ณ์์ ๋ฆฌํด
END $$
DELIMITER ;
- DELIMITER : ํ๋ก์์ ์,๋ค์ ์์นํ์ฌ ์์ ์๋ ๋ถ๋ถ์ ํ๋ฒ์ ์คํ๋ ์ ์๊ฒ ํ๋ ์ญํ
- Stored Procedure ๋งค๊ฐ๋ณ์์ 3๊ฐ์ง ๋ชจ๋
- IN : ํ๋ก์์ ๋ด๋ถ์์ ๊ฐ์ด ๋ณ๊ฒฝ ๊ฐ๋ฅํ์ง๋ง ํ๋ก์์ ๋ฐํ ํ ํธ์ถ์๋ ๋ณ๊ฒฝ ๋ถ๊ฐ. (call-by-value ์ ์ ์ฌ)
- OUT : ์ด๊ธฐ๊ฐ์ ๋ด๋ถ์์ NULL. ํ๋ก์์ ๋ฐํ๋ ๋ ๋ด๋ถ ๊ฐ ๋ฆฌํด (call-by-reference ์ ์ ์ฌ)
- INOUT : ํธ์ถ์์ ์ํด ๋ณ์๊ฐ ์ด๊ธฐํ๋๊ณ ํ๋ก์์ ์ ์ํด ์์ ๋๋ค. IN + OUT ์ ๊ธฐ๋ฅ
- DECLARE : ํ๋ก์์ ๋ด๋ถ์์ ์ฌ์ฉํ๋ ์ง์ญ๋ณ์ ์ ์ธ
- BEGIN ~ END : ํ๋ก์์ ์ ์ค์ง์ ์ธ ์ฝ๋๊ฐ ๋ค์ด์๋ ์์ญ. UPDATE, INSERT, DELETE ๋ฑ์ ์ฟผ๋ฆฌ ๋ฟ ๋ง ์๋๋ผ ์กฐ๊ฑด์, ๋ฐ๋ณต๋ฌธ ๋ฑ์ ์ฌ์ฉํ์ฌ ์ฌ์ฉ์๊ฐ ์ํ๋ ๊ธฐ๋ฅ์ ์ง์ ์ฝ๋ฉ
Stored Procedure ํธ์ถ
1
2
SET @val = 10;
CALL genSP('Bravo', @cnt, @var);
@val์ 10์ผ๋ก ์ด๊ธฐํํ๊ณ @cnt๋ณ์์ ํจ๊นจ genSP์ ๋งค๊ฐ๋ณ์๋ก ์ ๋ฌํ๋ค. ์๋๋ ํด๋น SQL๋ฌธ์ ์คํ ๊ฒฐ๊ณผ์ด๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
mysql> SET @val = 10;
mysql> CALL genSP('Bravo', @cnt, @var);
+----+-------+----------+------+
| id | name | location | age |
+----+-------+----------+------+
| 2 | Bravo | Brazil | 28 |
+----+-------+----------+------+
mysql> SELECT @cnt, @var;
+------+------+
| @cnt | @var |
+------+------+
| 5 | 20 |
+------+------+
ํ๋ก์์ ๋ด๋ถ์ ์ฟผ๋ฆฌ๋ฌธ๋ค์ด ์ฑ๊ณต์ ์ผ๋ก ์คํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
SELECT * FROM testDB.employees WHERE name = 'Bravo';์ ๊ฒฐ๊ณผ๊ฐ์ด ์ถ๋ ฅ๋๋ค.SELECT count(User) INTO userCnt FROM mysql.user;์ฟผ๋ฆฌ๊ฐ ์คํ๋ผ@cnt๋ณ์์ ๋ฆฌํด๊ฐ์ด ์ ์ฅ๋๋ค.SET plus = plus + sum;์ฟผ๋ฆฌ๊ฐ ์คํ๋ผ 10์ผ๋ก ์ด๊ธฐํ๋@var์ ์ง์ญ๋ณ์sum์ ๊ฐ 10์ด ๋ํด์ ธ 20์ ๊ฐ์ด ๋ฆฌํด๋๋ค.
0x5. Stored Procedure SQL Injection
๊ทธ๋ ๋ค๋ฉด Stored Procedure์์ SQL Injection ์ทจ์ฝ์ ์ด ๋ฐ์ํ ์ ์๋์ง ํ์ธํด๋ณด๊ธฐ ์ํด ์๋์ ๊ฐ์ ํ ์ด๋ธ๊ณผ ํ๋ก์์ ๋ฅผ ๋ง๋ค์ด ํ ์คํธํด๋ณด์๋ค.
1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE user(
seq int NOT NULL AUTO_INCREMENT,
uid varchar(50),
upw varchar(50),
PRIMARY KEY (seq)
);
INSERT INTO user (uid, upw)
VALUES
('guest', 'guest'),
('admin', 'ADM1NP4SSW0RD');
1
2
3
4
5
6
7
8
9
10
USE testDB;
DROP procedure IF EXISTS testSQLI;
DELIMITER $$
USE testDB $$
CREATE PROCEDURE testSQLI(IN id VARCHAR(255))
BEGIN
SELECT id; -- ์
๋ ฅ๊ฐ ๋๋ฒ๊น
์ ์ํ ์ถ๋ ฅ
SELECT * FROM user WHERE uid=id;
END $$
DELIMITER ;
1
2
3
4
5
6
mysql> call testSQLI('\' or 1=1 -- a');
+---------------+
| id |
+---------------+
| ' or 1=1 -- a |
+---------------+
call testSQLI('\' or 1=1 -- a');์ฟผ๋ฆฌ๋ฌธ์ผ๋ก SQLI ๋ฅผ ์๋ํด๋ณด๋ฉด SQL Injection์ด ์คํจํ ๊ฒ์ ๋ณผ ์ ์๋ค.
SQL Injection์ ์์ธ
SQL Injection์ DB๊ฐ ๊ณต๊ฒฉ์์ ์
๋ ฅ๊ณผ ์ฟผ๋ฆฌ๋ฌธ์ ๊ตฌ๋ถํ์ง ๋ชปํ๊ธฐ ๋๋ฌธ์ ๋ฐ์ํ๋ค.
SELECT * FROM user WHERE username='admin' OR password='foo' OR 1=1 ์ ์ฟผ๋ฆฌ์์ ๊ณต๊ฒฉ์๊ฐ๋ foo' OR 1=1์ด๋ผ๋ payload๋ฅผ ์
๋ ฅํด SQL Injection ๊ณต๊ฒฉ์ ์คํํ๋ค. DB๋ ํด๋น payload๊ฐ ๊ธฐ์กด์ ์ฟผ๋ฆฌ๋ฌธ์ธ์ง ์๋๋ฉด ๊ณต๊ฒฉ์์ ์
๋ ฅ์ธ์ง ์ ์๊ฐ ์๋ค.
SQL Injection ๋์๋ฐฉ๋ฒ
- Prepared Statement SQL Injection์ผ๋ก๋ถํฐ DB๋ฅผ ๋ณดํธํ๊ธฐ ์ํด์๋ DB๊ฐ ์ฟผ๋ฆฌ์ ์
๋ ฅ ๋ฐ์ดํฐ๋ฅผ ๊ตฌ๋ถํ ์ ์์ด์ผ ํ๋ค. ์ด๋ฅผ ์ํด ํ๋ก์์ ๋ Prepared Statement๋ฅผ ์ฌ์ฉํ๋ค.
์ผ๋ฐ์ ์ผ๋ก SQL๋ฌธ์๊ตฌ๋ฌธ๋ถ์(parsing) -> ์ต์ ํ -> ์คํ ๊ฐ๋ฅ ์ฝ๋๋ก ํฌ๋งทํ -> ์คํ -> ์ธ์ถ์ด๋ ๊ฒ 5๋จ๊ณ๋ฅผ ๊ฑฐ์ณ ์คํ๋๋ค.
Prepared Statement๋ ๋ฏธ๋ฆฌ ์ฟผ๋ฆฌ์ ๋ํ ์ปดํ์ผ์ ์คํํ๊ณ ์ฌ์ฉ์์ ์ ๋ ฅ๊ฐ์ ๋ค์ค์ ์ ๋ ฅํ๋ ๋ฐฉ์์ด๋ค. ์ผ๋ฐ์ ์ธ ๋ฐฉ์๊ณผ ๋ฌ๋ฆฌ ๋ถํ์ํ ๋์๋ค์ ๋งค๋ฒ ์ํํ์ง ์๊ณ ์คํ๊ณผ ์ธ์ถ ์ด์ ์ ๋จ๊ณ๋ ํ๋ฒ์ ์ปดํ์ผ๋ก ๋ฏธ๋ฆฌ ๋ง๋ค์ด ๋๊ณ ๋์ค์๋ ๋ฏธ๋ฆฌ ์ปดํ์ผ๋ Prepared Statement๋ฅผ ๊ฐ์ ธ๋ค ์ฐ๋ ๊ฒ์ด๋ค. ์ด๋ ๊ฒ ๋๋ฉด DB๋ ์ฟผ๋ฆฌ๋ฌธ๊ณผ ๊ณต๊ฒฉ์์ ์ ์์ ์ ๋ ฅ์ ์ ํํ๊ฒ ๊ตฌ๋ถํ ์ ์๊ฒ ๋๋ค. ํ์ง๋ง ๊ทธ๋ ๋ค๊ณ ํด์ Stored Procedure๊ฐ SQL Injection์ ๋ฌด์ ์ธ๊ฑด ์๋๋ค.
SQL Injection ์ฐํ
- Dynamic SQL
Dynamic SQL์ SQL๋ฌธ์ ๋ฌธ์์ด ๋ณ์์ ๋ด์ ์คํํ๋ SQL๋ฌธ์ด๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
USE testDB;
DROP procedure IF EXISTS testSQLI;
DELIMITER $$
USE testDB $$
CREATE PROCEDURE testSQLI(IN id VARCHAR(50))
BEGIN
-- Dynamic SQL
SET @sql = CONCAT('SELECT * FROM user WHERE uid=\'', id, '\'');
PREPARE s1 FROM @sql;
EXECUTE s1;
DEALLOCATE PREPARE s1;
END $$
DELIMITER ;
์ด์ ์ ์์์ ๊ฒฐ๊ณผ๋ ๊ฐ์ง๋ง ์ ํ๋ก์์ ๋ @sql๋ณ์์ ์ฟผ๋ฆฌ๋ฅผ ๋ด์ ํ ์คํํ๋ Dynamic SQL์ ์ฌ์ฉํ๋ค. ์ด๋ฅผ ํ๋ก์์ ์์ ์ฌ์ฉํ๋ฉด SQL Injection์ ๊ทผ๋ณธ์ ๋ฐฉ์ด๊ธฐ๋ฒ์ธ Prepared Statement๋ฅผ ์ฌ์ฉํ์ง ์์ ๊ฒฐ๊ตญ SQL Injection ๊ณต๊ฒฉ์ ์ทจ์ฝํด์ง๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mysql> call testSQLI('evil\' or 1#');
+-----+-------+---------------+
| seq | uid | upw |
+-----+-------+---------------+
| 1 | guest | guest |
| 2 | admin | ADM1NP4SSW0RD |
+-----+-------+---------------+
mysql> call testSQLI('evil\' or 1 UNION SELECT 1,2,3#');
+-----+-------+---------------+
| seq | uid | upw |
+-----+-------+---------------+
| 1 | guest | guest |
| 2 | admin | ADM1NP4SSW0RD |
| 1 | 2 | 3 |
+-----+-------+---------------+
์์ ์ถ๋ ฅ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด ์ ์ ์๋ฏ์ด SQL Injection๊ณต๊ฒฉ์ด ์คํ๋๋ ๊ฒ์ ์ ์ ์๋ค.
0x4. Stored Procedure์ ๋ณด์
๊ฐ๋ฐ ๊ณผ์ ์์ Stored Procedure๋ ๋ณด์์ ์ํด ์ข์ ์ ํ์ด ๋ ์ ์๋ค.
DB์ ์ ๊ถํ๊ณผ ํ๋ก์์ ์ ๊ถํ ๋ถ๋ฆฌ
1
2
3
4
5
6
7
8
9
10
mysql> SELECT * FROM employees;
ERROR 1142 (42000): SELECT command denied to user 'aestera'@'localhost' for table 'employees'
mysql> SHOW GRANTS FOR 'aestera'@'localhost';
+----------------------------------------------------------------------+
| Grants for aestera@localhost |
+----------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `aestera`@`localhost` |
| GRANT EXECUTE ON PROCEDURE `testDB`.`gensp` TO `aestera`@`localhost` |
+----------------------------------------------------------------------+
๊ณ์ aestera๋ genSPํ๋ก์์ ์ ์คํ ๊ถํ์ ์์ง๋ง employees ํ
์ด๋ธ์ ์ ๊ทผ ๊ถํ์ด ์๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ํ์ง๋ง aestera๋ genSPํ๋ก์์ ๋ฅผ ํตํด employeesํ
์ด๋ธ์ ์ ํ์ ์ผ๋ก ์ ๊ทผํ ์ ์๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> SET @var = 10;
mysql> CALL genSP('Bravo', @cnt, @var);
+----+-------+----------+------+
| id | name | location | age |
+----+-------+----------+------+
| 2 | Bravo | Brazil | 28 |
+----+-------+----------+------+
mysql> SELECT @cnt, @var;
+------+------+
| @cnt | @var |
+------+------+
| 6 | 20 |
+------+------+
์ด๋ฌํ ๊ถํ์ ๋ถ๋ฆฌ๊ฐ ๋ณด์์ ์ผ๋ก๋ ์ด์ ์ด ๋๋ค. ์๋ฅผ ๋ค์ด ์๋ ์กฐ๊ฑด์ ๊ฐ์ง DB๊ฐ ์๋ค๊ณ ๊ฐ์ ํ์.
- ์ ์
admin์testํ ์ด๋ธ๊ณผ ์ค์ํ ์ ๋ณด๊ฐ ๋ด๊ธดsecretํ ์ด๋ธ์ ๊ถํ์ ๊ฐ์ง๊ณ ์๋ค. testํ ์ด๋ธ์ ์ฌ์ฉํ๋ ๋ก์ง์ด SQL Injection์ ์ทจ์ฝํ๋ค.
์ด๋ testํ
์ด๋ธ์์ SQL Injection์ด ๋ฐ์ํ๋ค๋ฉด, ์ค์ ์ ๋ณด๊ฐ ๋ด๊ธด secretํ
์ด๋ธ๋ ๊ฐ์ด ์ทจ์ฝํด์ง๋ค. ๊ทธ๋ฌ๋ ์๋์ฒ๋ผ ๊ถํ์ ๋ถ๋ฆฌํ์๋๋ฅผ ์๊ฐํด๋ณด์.
secretํ ์ด๋ธ์ ๋ค๋ฃจ๋ ๋ก์ง์ Stored Procedure๋ก ์ฒ๋ฆฌadmin์secretํ ์ด๋ธ์ ๋ํ ๊ถํ์ ์ ๊ฑฐ
์ด๋ ๊ฒ ๋๋ฉด testํ
์ด๋ธ์์ SQL Injection์ด ๋ฐ์ํด๋ secretํ
์ด๋ธ์ด ์์ ํจ๊ณผ ๋์์ admin์ ์ ํ์ ์ด์ง๋ง ์ฌ์ ํ secretํ
์ด๋ธ์ ์ฌ์ฉํ ์ ์๋ค.
0x5. Reference
1
2
3
4
5
6
7
8
- https://www.w3schools.com/sql/sql_stored_procedures.asp
- https://www.c-sharpcorner.com/UploadFile/996353/difference-between-stored-procedure-and-user-defined-functio/
- https://www.shekhali.com/difference-between-stored-procedure-and-function-in-sql-server/
- https://dev.mysql.com/doc/refman/8.0/en/faqs-stored-procs.html#faq-mysql-where-procedures-functions-docs
- https://dev.mysql.com/doc/refman/8.0/en/information-schema-routines-table.html
- https://security.stackexchange.com/questions/68701/how-does-stored-procedure-prevents-sql-injection
- https://www.slideshare.net/topcredu/11-sql
- https://nive.tistory.com/148