[JDBC] 2020.12.30. day_47 OWASP , SQL Injection, SQL Injection 방어하기
2020. 12. 30. 18:46ㆍWeb_Back-end/JDBC
◎ OWASP
OWASP | |
Open Web Application Security Project | |
기간 내 발생한 보안사고를 정리한 프로젝트 | |
owasp.org 으로 접속가능 |
○ SQL Injection _ Statement를 사용하면 발생할 수 있다
SQL Injection | |
zipcode 파일 : 상단글 참조 | zipcode 파일을 통해 역으로 테이블, 컬럼들, 정보를 찾아보기 |
테이블 명 얻기 | |
'1-- | |
'union select tname from tab-- | |
'union select tname, '0' , '0', '0', '0' from tab-- | |
![]() |
|
컬럼명 얻기 | |
select * from user_tab_cols; | |
' union select column_name, '0', '0', '0', '0' from user_tab_cols where table_name = 'EMP'-- | |
이 작업을 통해 컬럼명을 얻을 수 있다![]() |
|
레코드 얻기 | |
' union select empno||'~', ename, job, '0', '0' from emp-- | |
![]() |
※ SQLInjection을 방어하는 코드 작성해보기
_ 1, PreparedStatement 의 바인드변수를 사용하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
public List<ZipcodeVO> selectStatementZipcode(String dong) throws SQLException {
List<ZipcodeVO> list = new ArrayList<ZipcodeVO>();
// SQLInjection 방어하기
// 방어하는 코드 :
// 1. ', -- 등의 특수문자를 없앤다 (Web에서는 제거가능)
// 2. 공백을 체거한다
// 3. 쿼리문에 관련된 문장을 제거한다 (ex: select, from, where,,,)
if(dong.contains("'")||dong.contains("-")||dong.contains(" ")) {
dong = dong.replace("'", ""); // ' 를 제거
dong = dong.replace("-", ""); // - 를 제거
dong = dong.replace(" ", ""); // white space를 제거
}
// 1. 드라이버 로딩
try {
Class.forName("oracle.jdbc.OracleDriver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Connection con = null;
Statement stmt = null; //SQLInjection 발생할 수 있다
ResultSet rs = null;
String url = "jdbc:oracle:thin:@localhost:1521:orcl";
String id = "scott";
String pass = "tiger";
try {
// 2. 커넥션 얻기
con = DriverManager.getConnection(url, id, pass);
// 3. 쿼리문 생성 객체 얻기
StringBuilder selectZipcode = new StringBuilder();
selectZipcode
.append(" select zipcode, sido, gugun, dong, nvl(bunji, ' ') bunji")
.append(" from zipcode")
.append(" where dong like '")
.append(dong)
.append("%'");
stmt = con.createStatement();
// 4. 쿼리 수행 후 결과 얻기
rs = stmt.executeQuery(selectZipcode.toString());
String zipcode = "", sido = "", gugun = "", db_dong = "", bunji = "";
//매개변수와 dong이 같은이름을 가져서 따로 표기 ZipcodeVO zv = null;
while(rs.next()) {
zipcode = rs.getString("zipcode");
sido = rs.getString("sido");
gugun = rs.getString("gugun");
db_dong = rs.getString("dong");
bunji = rs.getString("bunji");
zv = new ZipcodeVO(zipcode, sido, gugun, db_dong, bunji);
list.add(zv);
}
} finally {
// 5. 연결끊기
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (con != null) {
con.close();
}
}
return list;
}
|
cs |
|
|
※ preparedStatement 는 bind변수에 query가 들어갈 수 없기 때문에 SQLInjection이 발생하지 않는다
※ id, pass 입력하는 코드를 만들고, SQLInjection 실험해보기
▽1. id, password를 입력하고 로그인 성공여부를 출력하는 클래스 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
package day1230;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.sql.SQLException;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;
@SuppressWarnings("serial")
public class IdForm extends JFrame {
private JTextField jtfId;
private JPasswordField jpfPass;
private JLabel jlOutput;
private IdForm idFrm;
public IdForm() {
super("로그인");
idFrm = this;
jtfId = new JTextField();
jpfPass = new JPasswordField();
jlOutput = new JLabel("출력창");
jtfId.setBorder(new TitledBorder("아이디"));
jpfPass.setBorder(new TitledBorder("비밀번호"));
jlOutput.setBorder(new TitledBorder("출력창"));
setLayout(new GridLayout(3, 1));
add(jtfId);
add(jpfPass);
add(jlOutput);
// inner class로 이벤트 처리
// IdForm.EvtInner ei = new this.EvtInner();
EvtInner ei = new EvtInner(); // 위와같이 명시해서 생성해도된다
jtfId.addActionListener(ei);
jpfPass.addActionListener(ei);
setBounds(100, 100, 300, 250);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}// IdForm
////////////////// inner class로 Event 처리////////////////////////////
public class EvtInner implements ActionListener {
@Override
public void actionPerformed(ActionEvent ae) {
if (ae.getSource() == jtfId) {
idNull();
}
if (ae.getSource() == jpfPass) {
passNull();
}
}
public void idNull() {
if ("".equals(jtfId.getText().trim())) {
JOptionPane.showMessageDialog(idFrm, "아이디는 필수입력입니다");
jtfId.requestFocus();
return;
}
jpfPass.requestFocus();
}
public void passNull() {
if ("".equals(new String(jpfPass.getPassword()).trim())) {
JOptionPane.showMessageDialog(idFrm, "비밀번호는 필수입력입니다");
jpfPass.requestFocus(); // 커서를 jpfPass로 이동
return;
}
login();
}
public void login() {
String id = jtfId.getText().trim();
String pass = new String(jpfPass.getPassword()).trim();
LoginDAO lDAO = LoginDAO.getInstance();
try {
String name = lDAO.login(id, pass);
if("".equals(name)) {//login 실패 - 이름이 존재하지 않음
JOptionPane.showMessageDialog(idFrm, "아이디/비밀번호를 확인해주세요");
}else {
jlOutput.setText(name + "님이 로그인 하셨습니다.");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
////////////////// inner class로 Event 처리////////////////////////////
public static void main(String[] args) {
new IdForm();
}// main
}// class
|
cs |
▽2. Singleton pattern 을 사용한 DAO 클래스
받아온 id, password를 DB와 연동해서 조회하는 method 가 있다 //Statement사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
package day1230;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class LoginDAO {
private static LoginDAO LDAO;
private LoginDAO() {
}
public static LoginDAO getInstance() {
if (LDAO == null) {
LDAO = new LoginDAO();
}
return LDAO;
}
public String login(String id, String passwd) throws SQLException {
String name = "";
////////// Statement 사용
// 1. 드라이버 로딩
try {
Class.forName("oracle.jdbc.OracleDriver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
String url = "jdbc:oracle:thin:@localhost:1521:orcl";
String dboId = "scott";
String dboPass = "tiger";
try {
// 2. Connection 얻기
con = DriverManager.getConnection(url, dboId, dboPass);
// 3. 쿼리문 생성객체 얻기
stmt = con.createStatement();
// 4. 쿼리문 수행 후 결과 얻기
StringBuilder selectId = new StringBuilder();
selectId
.append(" select name ")
.append(" from test_login ")
.append(" where id='").append(id).append("' and pass='")
.append(passwd).append("'"); rs = stmt.executeQuery(selectId.toString());
if(rs.next()) {
name = rs.getString("name");
}
} finally {
// 5. 연결끊기
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (con != null) {
con.close();
}
}
return name;
}
}
|
cs |
무조건 성공하는 방법 | |
값 부분에 | select 컬럼명 from 테이블명 where 컬럼명 = '값' and 컬럼명 = '값'; |
' or 1=1 -- 을 입력하면, |
select 컬럼명 from 테이블명 where 컬럼명 = '' or 1=1 --' and 컬럼명 = '값'; |
실행 결과 로그인이 무조건 성공한다 |
![]() |
※ SQLInjection 을 방어하는 코드를 작성해서, 방어해보기
_ 2. 코드를 직접 작성 : 쿼리문을 조작
위의 loginDAO 클래스의 public static LoginDAO getInstance() 안에 코드 작성
//SQLInjection 방어코드
id = id.replaceAll("'", "").replaceAll(" ", "");
package day1230;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class LoginDAO {
private static LoginDAO LDAO;
private LoginDAO() {
}
public static LoginDAO getInstance() {
if (LDAO == null) {
LDAO = new LoginDAO();
}
return LDAO;
}
public String login(String id, String passwd) throws SQLException {
String name = "";
//SQLInjection 방어코드
id = id.replaceAll("'", "").replaceAll(" ", "");
////////// Statement 사용
// 1. 드라이버 로딩
try {
Class.forName("oracle.jdbc.OracleDriver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
String url = "jdbc:oracle:thin:@localhost:1521:orcl";
String dboId = "scott";
String dboPass = "tiger";
try {
// 2. Connection 얻기
con = DriverManager.getConnection(url, dboId, dboPass);
// 3. 쿼리문 생성객체 얻기
stmt = con.createStatement();
// 4. 쿼리문 수행 후 결과 얻기
StringBuilder selectId = new StringBuilder();
selectId
.append(" select name ")
.append(" from test_login ")
.append(" where id='").append(id).append("' and pass='").append(passwd).append("'");
rs = stmt.executeQuery(selectId.toString());
if(rs.next()) {
name = rs.getString("name");
}
} finally {
// 5. 연결끊기
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (con != null) {
con.close();
}
}
return name;
}
}