Immutable Queries๋ SQL ์ธ์ ์ ์ ๊ฐ์ฅ ์ข์ ๋ฐฉ์ด ๋ฐฉ๋ฒ์ด๋ค. ์ด๋ฒ์๊ฐ์ Immutable Query์ ๋ํด ์์ธํ ์์๋ณผ ๊ฒ์ด๋ค!
Immutable query์๋ static query๊ฐ ์๋ค! ๊ทผ๋ฐ static query๊ฐ ๋ฌด์์ธ์ง ์ ์ํ๋ ๋ง์ ๊ธ๋ค๋ง๋ค ๋ป์ด ๋ค๋ฅธ ๊ฒ๊ฐ์์ ํผ๋์ค๋ฌ์ ๋ค. ๋ณดํต์ ํฌ๊ฒ ๋ ๊ฐ์ง ๊ฐ๋ ์ ๋ถ๋ฅผ ๋ ์ฌ์ฉํ๋ ๊ฒ ๊ฐ๋ค.
์์๋ฅผ ๋ณด๋ฉด ์ดํด๊ฐ ์ฝ๋ค. static query
์ ์ฒซ ๋ฒ์งธ ์ ์๋ฅผ ์ดํดํ ์ ์๋ ์์๋ฅผ ๊ฐ์ ธ์๋ดค๋ค. ์ ์ด์ Stringํ ๋ณ์์ ๋ด์ง ์๊ณ ์์ค ์ฝ๋ ๋ด์์ SQL์ ๋ค๋ฃฐ ์ ์๋ ์ธ์ด๊ฐ ๋ง์ง ์๋ค. ๋๋ ์ด๋ฒ์ ์ฒ์ ๋ณธ ์ธ์ด์ธ pro*C ์ฝ๋ ๋ด์์ SQL์ ์ง์ ๊ธฐ์ ํ ์์์ด๋ค.
int main()
{
printf("์ฌ๋ฒ์ ์
๋ ฅํ์ญ์์ค : ");
scanf("%d", &empno);
EXEC SQL WHENAVER NOT FOUND GOTO notfound;
EXEC SQL SELECT ENAME INTO :ename
FROM EMP
WHERE EMPNO = :empno;
printf("์ฌ์๋ช
: %s.\n", ename);
notfound:
printf("%d๋ ์กด์ฌํ์ง ์๋ ์ฌ๋ฒ์
๋๋ค. \n", empno);
}
๋ง ๊ทธ๋๋ก ์์ค์ฝ๋ ๋ด์ SQL ๊ตฌ๋ฌธ์ ๋ฐ๋ก ๊ธฐ์ ํ๋ ๊ฒ์ด๋ค. static 'SQL' ์ธ ๊ฒ์ด๋ค.
๊ทธ๋ ๋ค๋ฉด ๋ ๋ค๋ฅธ ์ฌ๋๋ค์ด ์ ์ํ static query
, ์ ํ๋ฆฌ์ผ์ด์
์ด ์คํ๋ ๋๋ ๋ณํ์ง ์๋ ์ฟผ๋ฆฌ๋ ๋ฌด์์ผ๊น?
What is static SQL? Static SQL is SQL statements in an application that do not change at runtime and, therefore, can be hard-coded into the application.
์์ static sql์ ์ ์ ์ค hard-coded ๋ ์ ์๋ค๋ ๋ง์ด ์ค์ํ๋ค. ๋ง๊ทธ๋๋ก ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋ง๋ค์ด์ง ๋ ๊ณ ์ ๋๋ค๋ ๋ป์ด๋ค. ์์๊ฐ์ ๋๋์ผ๋ก ์ ํ๋ฆฌ์ผ์ด์ ์ด ์คํ๋ ๋๋ ๋ณํ์ง ์๋ ์ฟผ๋ฆฌ์ด๋ค. ์๋ ์ฝ๋๋ JAVA์์ hard-coded query๋ฅผ ์์ฑํ ์์์ด๋ค.
public static void viewTable(Connection con) throws SQLException {
// hard-coded query statement
String query = "SELECT last_name, id, hashedPassword, department, salary from EMPOYEES";
try (Statement stmt = con.createStatement()) {
ResultSet rs = stmt.executeQuery(query);
while (rs.next()) { // Get rows from table (EMPLOYEES)
String last_name = rs.getString("last_name");
String id = rs.getString("id");
String hasedPassword = rs.getString("hashedPassword");
String department = rs.getString("department");
int salary = rs.getInt("salary");
//Print row
System.out.println(last_name + ", " + id + ", " + hasedPassword + ", " + department + ", " + salary);
}
} catch (SQLException e) {
JDBCTutorialUtilities.printSQLException(e);
}
}
static SQL์ ๋๋ฒ์งธ ์๋ฏธ๋ ๊ทธ ๋ฐ๋ ๊ฐ๋
๋ ์ค์ํ๋ฐ, ๊ทธ๊ฑด ๋ฐ๋ก dynamic
SQL์ด๋ผ๊ณ ํ๋ค. dynamic SQL์ ์ปดํ์ผ ์์ ์์ static๊ณผ ๋ฌ๋ฆฌ ์คํ ์ค์ ์ฟผ๋ฆฌ ๋ด์ฉ์ด ๋ฐ๋ ์ ์๋ค. ๊ทธ๋ ๊ฒ ์ฌ๊ตฌ์ฑ๋ ์ฟผ๋ฆฌ๊ฐ ์คํ๋๋ ๊ฒ์ด๋ค.
๋๋ค์์ ํ๋ก๊ทธ๋จ์ ๋ง๋ค ๋ ์ฟผ๋ฆฌ๋ฌธ์ด ์ฌ์ฉ์์ ์
๋ ฅ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋ ๋๊ฐ ๋ง๊ธฐ ๋๋ฌธ์, dynamic
SQL์ ์ ๋ง ๋ง์ด ์ฌ์ฉ๋๋ค๊ณ ๋ณด๋ฉด ๋๋ค.
์ด ๋ String ๋ณ์๋ฅผ ๊ทธ๋ฅ ๋ํด์(append) ์ฐ๋ฉด SQL Injection์ ๊ฐ๋ฅ์ฑ์ด ์๊ธฐ๋ฏ๋ก Prepared Statement
๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๊ถ๊ณ ํ๊ณ ์๋ค.
SQL Injection ์ทจ์ฝ์ ์ ๋ํ ๋์ ๋ฐฉ์์ผ๋ก ๊ถ๊ณ ๋๋ ์ ๋ช
ํ ๋ฐฉ๋ฒ ์ค ํ๋์ธ Prepared Statement
์ binding
๋ณ์์ ๋ํด ์์๋ณด์.
Prepared Statement๋ ์ฌ์ฌ์ฉ ์ ํจ์จ์ฑ์ ๋์ด๊ธฐ ์ํด ๊ณ ์๋ query๋ฌธ์ ํํ๋ผ๊ณ ํ๋ค. ๊ทธ ์๋ฆฌ๋ฅผ ์ดํดํ๊ธฐ ์ํด์๋ prepared statement์ workflow๋ฅผ ์์์ผ ํ๋ค.
Prepare: ์ฒ์์ ์ ํ๋ฆฌ์ผ์ด์
์ด statement 'template'์ ๋ง๋ค์ด์ DBMS์ ๋ณด๋ธ๋ค. statement์ ํฌํจ๋๋ ์ด๋ค value๋ค์ ๊ฐ์ ์ ํด๋์ง ์๊ณ parameters, placeholder ๋๋ bind ๋ณ์ ๋ฑ์ผ๋ก(์๋์ "?" ๊ฐ ๊ทธ ์์) ๋์ ์ฑ์๋ฃ์ด ๋ณด๋ด๋ ๊ฒ์ด๋ค.
INSERT INTO products (name, price) VALUES (?, ?);
๊ทธ ํ์ DBMS๊ฐ statement template์ ์ปดํ์ผ(parsing, optimization, translaiton)์ ํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ปดํ์ผ ๊ฒฐ๊ณผ๋ฅผ ์คํ์ ํ์ง ์๊ณ , ๋์ค์ ๋ฐ๋ก ๋๋ฆด ์ ์๊ฒ ๋ฏธ๋ฆฌ ์ ์ฅํด๋๋ค.
Execute: ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ ํด์ง์ง ์์๋ value๋ค์ ๊ฐ์ DBMS์ ์ ๊ณตํ๋ฉด! ๊ทธ๋์์ผ DBMS๊ฐ ์ด์ ์ ์์ ์ด ์ ์ฅํด๋์๋ statement์ binding ๋ณ์ ์๋ฆฌ์ value๋ฅผ ๋ฃ์ด ์ฟผ๋ฆฌ๋ฅผ ์คํํ๋ค. ๊ทธ๋ฆฌ๊ณ ๋์ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ์ ํ๋ฆฌ์ผ์ด์ ์๊ฒ ๋ฐํํด์ฃผ๋ ๊ฒ์ด๋ค.
์๋ ์์ค์ฝ๋๋ JAVA์์ PreparedStatement
๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ prepared statement๋ฅผ ๊ตฌํํ ์์์ด๋ค.
// 1. last_name ์ binding ๋ณ์๋ก ๋์ ํด๋จ๋ค.
String query = "SELECT * FROM users WHERE last_name = ?";
// 2. DBMS์ ๋๊ฒจ precompile
PreparedStatement statement = connection.prepareStatement(query);
// 3. ์๋ฃํ์ด String์ธ ๋ณ์์ด๋ฏ๋ก setString()์ ์ด์ฉํด value ์ ๋ฌ
statement.setString(1, accoutName);
// 4. ์ฟผ๋ฆฌ ์คํ ๊ฒฐ๊ณผ ์ป๊ธฐ
ResultSet results = statement.executeQuery();
PreparedStatement stmt;
ResultSet rs;
StrongBuffer SQLStmt = new StringBuffer();
SQLStmt.append("SELECT ENAME, SAL FROM EMP");
SQLStmt.append("WHERE EMPNO = ?");
stmt = conn.prepareStatement(SQLStmt.toString());
stmt.setLong(1, txtEmpno.value); // ์๋ฃํ์ด Long์ธ ๋ณ์์ด๋ฏ๋ก setLong()์ ์ด์ฉํด value ์ ๋ฌ
rs = stmt.executeQuery(); // ์ฟผ๋ฆฌ ์คํ ๊ฒฐ๊ณผ ์ป๊ธฐ
Stored Procedures๋ DBMS๋ฅผ ๋ค๋ฃฐ ๋ ์ฌ์ฉํ๋ ๋ณต์กํ ์ฟผ๋ฆฌ๋ฌธ์ ํจ์์ฒ๋ผ ๋ฑ๋กํด๋๊ณ ๊ฐํธํ๊ฒ ์ฌ์ฉํ ์ ์๋ ๋ช ๋ น์ด๋ค. ์ค์ ํจ์์ฒ๋ผ ๋งค๊ฐ๋ณ์๋ ๋ฐ์ํ ์ ์๊ธฐ ๋๋ฌธ์ ์์๋๋ฉด ํธ๋ฆฌํ ๊ธฐ๋ฅ์ด๋ค.
๊ณต๊ฒฉ์ผ๋ก๋ถํฐ ์์ ํ ์ ์ฅ ํ๋ก์์ ์ ์์์ด๋ค.
CREATE PROCEDURE ListCustomers(@Country nvarchar(30))
AS
SELECT city, COUNT(*)
FROM customers
WHERE country LIKE @Country GROUP BY city
EXEC ListCustomers 'USA'
์ธ์ ์ ์ ์ทจ์ฝํ ์ ์ฅ ํ๋ก์์ ์ ์์๋ค.
CREATE PROCEDURE getUser(@lastName nvarchar(25))
AS
DECLARE @sql nvarchar(255)
SET @sql = 'SELECT * FROM users WHERe lastname = + @LastName + '
EXEC sp_executesql @sql
์ด๋ฒ์๋ ์์๋ฌ์ผํ๋ ๊ฐ๋ ๋ค์ด ๋ง์๋ค!! ๊ณต๋ถ๋ฅผ ๋ง์ณค์ผ๋ ๋ฌธ์ ๋ฅผ ํ์ด๋ณด์.
You can see some code down below, but the code is incomplte. Complete the code, so that is no longer vulnerable for an SQL injection! Use the classes and methods you have learned before.
The code has to retrieve the status of the user based on the name and the mail address of the user. Both the name and the mail are in the String format.
์ฝ๋๋ฅผ ๋ณด๋ฉด conn
์ด๋ผ๋ ์ฐธ์กฐ๋ณ์๊ฐ DriverManager
ํด๋์ค์ static ๋ฉ์๋์ ๋ฐํ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐํ๋ ๊ฒ ๊ฐ๋ค. Connection
๊ฐ์ฒด๋ฅผ ๋ฐํํ๊ณ ์์์ ์ด์ฉํด ๋ฉ์๋๋ฅผ ์ฐพ์๋ณด์.
JAVA ๋ํ๋จผํธ๋ฅผ ์ฐพ์๋ณด๋ ๊ธ๋ฐฉ ๋์๋ค.
์ญ์ DriverManager
ํด๋์ค์ ์ ์๋ static ๋ฉ์๋์ธ getConnection()
์ด์๋ค. ๊ทธ๋์ ์ฒซ ๋ฒ์งธ ๋น์นธ์ ๋ต์ getConnection
์ด ๋๊ฒ ๋ค.
๋ค์ ์ฝ๋๋ ๋ง์ด ๋ดค๋ ๊ฑฐ๋ค. ์์ PreparedStatement ์์ ์์ ํด๋ดค๋ ๋๋ก, Connection
๊ฐ์ฒด์ prepareStatement()
๋ผ๋ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ DBMS๊ฐ ์ฟผ๋ฆฌ๋ฅผ precompileํ๊ฒ ํ๋ ์ฝ๋์ด๋ค. ๊ทธ๋์ ๋๋ฒ์งธ ๋น์นธ์ ์ ๋ต์ PreparedStatement [์ฐธ์กฐ๋ณ์ ์ด๋ฆ ์๋ฌด๊ฑฐ๋]
๊ฐ ๋๋ค. (๋๋ ๋ณ์ ์ด๋ฆ์ ps
๋ก ํ๋ค.) ๊ทธ๋ฆฌ๊ณ ์ธ๋ฒ์งธ ๋น์นธ์ prepareStatement
๊ฐ ๋๊ฒ ๋ค.
prepareStatement() ๋ฉ์๋์ ๋งค๊ฐ๋ณ์๋ก๋ ์ฐ๋ฆฌ๊ฐ ๋ง๋ค๊ณ ์ถ์ prepared statement๋ฅผ binding ๋ณ์('?')๋ฅผ ์ฌ์ฉํด ๋ง๋ String ๋ณ์์ด๊ธฐ ๋๋ฌธ์ ๋ค๋ฒ์งธ, ๋ค์ฏ๋ฒ์งธ ๋น์นธ์ ๋ฌผ์ํ๋ฅผ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค.
๋ฌธ์ ์์ name๊ณผ mail์ String ๋ณ์๋ก ์ ์ฅ๋์ด์๋ค๊ณ ํ์ผ๋, binding ๋ณ์์ ๊ฐ์ ๋ฃ์ด์ฃผ๋ ๋ฉ์๋์ธ getString()
์ฌ์ฉํด parameter์ ๋ณ์๋ช
์ ๋ฃ์ด์ฃผ๋ ์ฝ๋๋ฅผ ์ฌ์ฏ๋ฒ์งธ, ์ผ๊ณฑ๋ฒ์งธ์ ์์ฑํ๋ฉด ๋!
Now it is time to write your own code! Your task is to use JDBC to connect to a database and request data from it.
Requirements:
1. Connect to a database
2. Perform a query on the datatbase which is immune to SQL injection attacks
3. your query needs to contain at least oneString
parameter.
๋ ๋ฒ์งธ ๋ฌธ์ ๋ ๋น์ทํ๋ฐ, ์ด๋ฒ์๋ ๋น์นธ์ด ์๋๋ผ ์์ ์ฝ๋๋ฅผ ์ฉ์ผ๋ก ์์ฑํ์ฌ ์ ์ถํ๋ ๋ฌธ์ ์๋ค.
๊ทธ๋์ ๋ฌธ์ ์์ ์ํค๋ ๋๋ก ์ฝ๋๋ฅผ ์ง๋ดค๋ค.
try {
// Connect to a database
Connection conn = DriverManager.getConnection(DBURL, DBUSER, DBPW);
// Make a query which is immune to SQL injection attacks
PreparedStatement pstmt;
pstmt = conn.prepareStatement("SELECT * FROM users WHERE user_name=?");
pstmt.setString(1, user_name);
// Perform a query
pstmt.execute();
} catch (Exception e) {
System.out.println("Error");
}
๊ทผ๋ฐ ์๋ฌ๊ฐ ๋ฌ๋ค. ์์
์ ๋ค์ด๋ณด๋ ์น๊ณ ํธ ์ ๋ฌธ์ ๋ผ์ ํ๋์ฝ๋ฉ์ ํด์ค์ผ ๋ฌธ์ ๊ฐ ํ๋ฆฐ๋ค๊ณ ํ๋ค. ๊ทธ๋์ setString()
์ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฐ๊ฟ์ฃผ๋ ๋ฐ๋ก ๋ฌธ์ ๊ฐ ํ๋ ธ๋ค๐ค
๋!
์ด๋ฒ์๋ ๊ฐ๋ ์ด ๋๊ฒ ์ด๋ ค์ ๋๋ฐ ํฌ์คํ ํ๋ฉด์ ์ ๋๋ก ๊ณต๋ถํ ์ ์์๋ ๊ฒ ๊ฐ๋ค! ๊ทธ์ ๋นํด ๋ฌธ์ ๋ ๋๋ฌด ์ฌ์ ์ด์ ์กฐ๊ธ ์์ฝ๊ธฐ๋ ํ๋ค๐ ํผ๊ณคํ๋ค ํผ๊ณคํด