잡다한 프로그래밍 :: 잡다한 프로그래밍
반응형

1. 난감한 DAO 만들기

다음과 같이 사용자 정보를 저장할 User 클래스를 만든다 

public class User {
	String id;
	String name;
	String password;
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
}

 

이후 Mysql을 사용하여 id, Name, Password를 가진 테이블을 생성한다.

이후 다음과 같은 UserDao를 만든다. 사용자 생성, 조회 메소드를 가진 Dao 이다.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class UserDao {

	public void insertUser(User user) throws ClassNotFoundException, SQLException {
		Class.forName("com.mysql.jdbc.Driver");
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost/test?serverTimezone=UTC", "root", "1234");
		PreparedStatement ps = c.prepareStatement("INSERT INTO USERS(ID, NAME, PASSWORD) values(?,?,?)");
		ps.setString(1,user.getId());
		ps.setString(2,user.getName());
		ps.setString(3,user.getPassword());

		ps.executeUpdate();
		
		ps.close();
		c.close();
	}
	
	public User getUserList(String id) throws ClassNotFoundException, SQLException {
		Class.forName("com.mysql.jdbc.Driver");
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost/test?serverTimezone=UTC", "root", "1234");
		PreparedStatement ps = c.prepareStatement("SELECT * FROM USERS WHERE id = ?");
		ps.setString(1,id);

		ResultSet rs = ps.executeQuery();
		rs.next();
		User user = new User();
		user.setId(rs.getString("id"));
		user.setName(rs.getString("name"));
		user.setPassword(rs.getString("password"));
		
		rs.close();
		ps.close();
		c.close();
		
		return user;
	}
}

메인 코드를 다음과 같이 작성하면 정상적으로 동작하는것을 확인할 수 있다. 하지만. 이는 함수마다 connection을 반복적으로 진행하는 좋지 못한 방법이다. 만약 1000개의 함수가 존재하는데 비밀번호가 바뀐다면 1000번 수정해야한다. 따라서 DAO의 분리를 통해 다음과 같이 수정해보자.

 

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class UserDao {

	public void insertUser(User user) throws ClassNotFoundException, SQLException {
		Connection c = getConnection();
        ...
	}
	
	public User getUserList(String id) throws ClassNotFoundException, SQLException {
		Connection c = get Connection();
        ...
	}
    private Connection getConnection() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost/test?serverTimezone=UTC", "root", "1234");
        return c;
    }
}

이렇게 된다면 getConnection함수를 호출하여 사용하므로 이전과 같은 문제를 해결할 수 있다.

 

이러한 Dao클래스를 외부에서 가져다 사용한다고 하자, A회사 B회사가 각기 다른 DB를 사용하고 Connection을 독자적으로 적용하고싶다고 하자 어떻게 제공할 수 있을까?

 

상속을 통한 확장

다음과 같이 상속을 통해 확장할 수 있다. A,B회사는 각각 UserDao를 상속받고 getConnection을 오버라이딩하여 Dao클래스를 만들 수 있다.

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public abstract class UserDao {

	public void insertUser(User user) throws ClassNotFoundException, SQLException {
		Connection c = getConnection();
        ...
	}
	
	public User getUserList(String id) throws ClassNotFoundException, SQLException {
		Connection c = get Connection();
        ...
	}
    public abstract Connection getConnection() throws ClassNotFoundException, SQLException {
		//추상메소드로 구현하여 구현을 서브클래스에서 담당한다
    }
}

 

이렇게 해결한 Dao에도 단점이 있다 상속을 받았다 보니 다른 상속을 받을 수 없다는 단점과, 슈퍼클래스와 하위클래스의 긴밀한 결합이다. 예를들어 슈퍼클래스를 수정하면 모든 서브 클래스를 수정하거나 다시 개발해야할 수 있다.

 

따라서 이를 별도의 클래스로 분리하여 사용하는 방법을 이용한다. 다음과 같이 Connection부분을 별도의 클래스로 분리한다.

 

이후 다음과 같이 수정한다. 이렇게 수정하게되면 connection, dao부분이 분리가 되긴 하였으나, UserDao가 SimpleConnectionMaker에 종속 되어 있다 보니, A,B회사에서 Connection부분을 확장하기에 적합하지 않다. 

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class UserDao {
	private SimpleConectionMaker simpleConnectionMaker;
    
    public UserDao(){
    	simpleConnectionMaker = new SimpleConnectionMaker();
        //생성자를 통해 다음과 같이 만들어놓고 함수에서 사용하도록 하게 한다.
    }
    
	public void insertUser(User user) throws ClassNotFoundException, SQLException {
		Connection c = simpleConnectionMaker.makeNewConnection();
        ...
	}
	
	public User getUserList(String id) throws ClassNotFoundException, SQLException {
		Connection c = simpleConnectionMaker.makeNewConnection();
        ...
	}
}

 

따라서 인터페이스를 활용하면 다음과 같이 해결할 수 있다.

ConnectionMaker interface

public interface ConnectionMaker {
	
    public Connection makeConnection() throws ClassNotFoundException, SQLException;
}

 

ConnectionMaker구현 클래스

public class AConnectionMaker implements ConnectionMaker {
	...
    
    public Connection makeConnection() throws ClassNotFoundException, SQLException {
    	// A회사의 Connection을 생성하는 코드
    }
}

 

UserDao

public class UserDao {
	private ConnectionMaker connectionMaker;
    
    public UserDao(){
    	connectionMaker = new AConnectionMaker();
    }
    
    public void insert.....
    
    public User get.....
    
}

다음과 같이 구현하게 된다면 B사도 Interface에 맞는 클래스만 구현주면 되므로 UserDao부분을 고칠 이유가 사라진다 하지만 AConnection 클래스의 생성자를 호출해서(connectionMaker = new AConnectionMaker();) 오브젝트를 생성하는 부분이남아 있으므로 완벽하게 분리되지 않았다.

 

따라서 우리는 new AConnectionMaker로 객체를 생성하는 방법이 아닌 파라미터로 객체를 넘겨 받아 이를 구현하도록 하겠다.

UserDao 생성자부분

public UserDao(ConnectionMaker connectionMaker) {
	this.connectionMaker = connectionMaker;
}

 

main클래스

import java.sql.SQLException;

public class UserDaoTest {

	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		// TODO Auto-generated method stub
		ConnectionMaker connectionMaker = new DConnectionMaker(); //UserDao부분이 아닌 main에서 객체 생성
        
        UserDao dao = new UserDao(connectionMaker); //객체 주입
    }

}

 

이렇게 이용하게 된다면 main 클래스만 수정하고 UserDao는 완벽하게 분리되게 된다. 공부를 하면서 Dao를 분리시키는 작업이 의존성 주입이랑 비슷하다는것을 알게 되었다.

반응형
반응형

1. 정의

적절한 인증과정이 없이 중요정보(계좌, 개인정보 등)을 열람 할 때 발생하는 보안 약점이다.

 

2. 안전한 코딩 방법

  • 클라이언트의 보안검사를 우회하여 서버에 접근하지 못하도록 한다.
  • 중요한 정보가 있는 페이지는 재 인증이 적용되도록 한다.

3. 예시

재 인증 없이 계좌 이체를 진행하는 예시.

 

재인증을 통한 안전한 예시

위 예시는 실제 아래처럼 작동한다. 만약 중요기능 ex) 회원정보수정, 계좌 이체 같은 중요 기능을 수행할 때 다음처럼 비밀번호를 재인증하는 화면이 사용자에게 주어지고

사용자는 비밀번호를 올바르게 입력했다면, 서버에서는 기존 세션에 있던 정보와 비교하여 유효한 정보인지 검증하는 절차를 가진다.

 

 

다음의 예시는 중요정보(사원의 연봉 정보)를 열람하기 전에 열람 권한이 있는지 먼저 권한 테이블을 조회하여 확인한 후 조회를 허용한다. 적절한 자격이 있는 사용자만 이를 열람할 수 있게한다. 중요함수 전 인증 함수를 수행하여 유효한지 확인하는 방식이다.

사용자가 isStaff란 함수를 구현해 놓고 이를 다음과 같이 사용하여 검증한다.

 

4. 추가적인 방법

spring security를 이용한 권한 확인방법 다음과 같이 권한에 따라 유저가 이용할 수 있는 페이지를 구분하여 권한 검사를 진행할 수 있다.

<intercept-url pattern="/login/loginForm.do" access="permitAll" />
<intercept-url pattern="/home.do" access="permitAll" />
<intercept-url pattern="/admin/**" access="hasRole('ADMIN')" />
<intercept-url pattern="/**" access="hasAnyRole('USER, ADMIN')" />
반응형
반응형

1. 정의

원격으로부터 소스코드 또는 실행파일을 무결성 검사 없이 다운로드 받고 이를 실행할경우 발생하는 변조, DNS spoofing 또는 전송시의 코드 변조 등의 방법을 이용하여 공격자가 악의적으로 파일이나 코드를 변경할 수있다.

 

2. 안전한 코딩 방법

암호화하여 코드나 파일을 제공하고 이를 클라이언트가 검증하도록 한다.

 

3. 예시

다음은 URLClassLoader를 사용하여, 원격의 파일을 다운로드하는 예제이다. 다운로드 대상 파일에 대한 무결성 검사를 수행하지 않을 경우, 파일변조로 인한 피해가 발생할 수 있다.

공개키 방식의 암호 알고리즘을 통하여 전송파일을 암호화하고, 사용자는 이를 복호화 하여 변조 유무를 판단한다.

공개키 방식이란?

공개키 암호화 방식에는 비밀키와 공개키가 존재하는데 비밀키는 서버 쪽에 가지고 있으며 공개되어서는 안 되고, 공개키는 누구에게나 공개되어도 괜찮은 키이다. 먼저 클라이언트는 공개키를 통해 텍스트를 암호화하고 이는 비밀키로만 복호화할 수 있다 따라서 다른 사람이 정보를 가로채더라도 비밀키만 안전하다면 괜찮은 방식이다. (예시는 공개키를 통해 복호화함)

 

다음의 예에서는 파일을 다운로드할 때, 파일의 무결성에 대한 검사를 하지 않고 있다. 따라서 인증된 사이트로 위장하거나, 중간에 파일의 내용을 악의적으로 바꿀 경우, 이에 대하여 대처를 할 방법이 없다.

 

파일의 내용이 공개되어서는 안 될 경우는 모든 파일을 암호화 하지만, 공개되어도 괜찮은 경우네는 체크섬을 이용하여 파일의 내용이 변경되었는지 확인하는것이 더 효과적이다. 다음은 체크섬의 예시이다.

 

체크섬이란?

네트워크를 통해 전달된 값이 변경되었는지를 검사하는 값으로 무결성을 제공합니다.

전달되는 값이 중간에 오류 또는 해킹을 통해서 변질되는경우를 대비해 체크섬값을 같이 보내어 무결성을 확인합니다.

 

1. 수신측에서 IP헤더를 16비트씩 나눈다.

 

2. 나눈 비트중 체크섬은 메시지를 보낸쪽에서 체크섬을 구해서 포함시켜 보낸 값이므로 이 값으로 내가 구한 체크섬값과 비교하여 사용한다 그림에서 b1e6을 제외한 나머지 값을 다 더한다.

 

3. 캐리 값이 발생하였다면 더한다.

 

4. 1의 보수를 취한다.

 

5. 구한값과 전달받은 값을 비교한다.

반응형
반응형

0. 배경지식 (쿠키와 세션)

쿠키란?

  • 쿠키는 클라이언트(브라우저)에 저장되는 키, 값으로 이루어진 데이터 파일이다.
  • 이름, 값 만료날짜 등의 정보가 있다.
  • 쿠키는 일정 시간동안 데이터를 저장할 수 있어서 로그인 상태 유지에 사용된다.

쿠키의 원리

  • 클라이언트가 브라우저로 서버로부터 reponse 받을때 쿠키를 받아 저장
  • 이후 가지고있는 쿠키를 header에 넣어 request할때마다 요청함

세션이란?

  • 클라이언트와 서버간 연결이 유지되고 있는 상태를 의미한다.
  • 클라이언트가 서버에 첫 request를 보내면, 해당 서버의 클라이언트에게 유니크한 ID를 부여하고 이를 세션 ID라고 부른다.

세션의 원리

  • 클라이언트가 서버로 request를 보낼시 세션 ID를 만들고 이를 쿠키를 통해 전달함
  • 클라이언트는 이후 request마다 쿠키에담긴 세션ID를 보내고 서버는 이가 유효한지 검증함

1. 정의

응용 프로그램이 외부 입력값에 대한 신뢰를 전제로 보호 메커니즘을 사용하는 경우 공격자가 입력값을 조작할 수 있다면 보호 메커니즘을 우회할 수 있게 된다. 개발자들이 흔히 쿠키, 환경변수 또는 히든필드와 같은 입력값이 조작될 수 없다고 생각하지만 공격자는 이를 변경할 수 있고 이는 에러로 이어진다.

 

2. 안전한 코딩기법

  • 상태 정보나 민감한 데이터는 서버에 저장하고 보안확인 절차도 서버에서 진행한다.

 

3. 예시

다음과 같이 사용자의 역할을 설정할 때 브라우저의 쿠키의 할당된 값을 사용하면 쿠키 값이 변경되는 경우 사용자의 역할이 의도하지 않은 값으로 할당될 수 있다.

 

따라서 사용자 권한, 인증여부 등 중요한 정보는 사용자의 입력값이나 쿠키가 아닌 서버에 있는 세션값을 비교하여 활용한다.

반응형
반응형

1. 정수 오버플로우의 정의

정수형 변수의 오버플로우는 정수값이 증가하면서, 허용된 가장 큰 값보다 더 커져서 실제 저장되는 값은 아주 작은 수 이거나, 음수인 경우를 의미한다.

 

2. 안전한 코딩기법

언어 별 정수타입의 범위를 확인하여 사용한다. 정수형 변수를 연산에 사용하는 경우 결과값의 범위를 체크하는 모듈을 사용한다. 특히 외부 입력값을 동적으로 할당하여 사용하는 경우 변수의 값 범위를 검사하여 적절한 범위내에 존재하는지 확인한다.

 

3. 예시

다음의 예제는 외부의 입력을 이용하여 동적으로 계산한 값을 배열의 크기(size)를 결정하는데 사용하고 있다. 만일 외부 입력으로부터 계산된 값이 오버플로우에 의해 음수값이 되면 배열의 크기가 음수가 되어 문제가 발생할 수 있다.

따라서 size값이 음수인지 아닌지 검사하는 부분이 필요하다


4. 추가 예시

피드백받은 byte overflow 예시이다.

다음과 같이 InetAddress의 getByName 이용하면 127.0.0.1이 byte배열로 변하게 된다 즉 byte[] i = {127, 0, 0, 1}로 변환하게 된다. 이를 BigInteger에 담아서 BigInteger를 다시 Byte 배열로 변환하여 사용할 수 있다.

import java.math.BigInteger;
import java.net.InetAddress;

public class test {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		 BigInteger i = null;
		  try {
		   InetAddress inet = InetAddress.getByName("127.0.0.1");
		   System.out.println(inet.getAddress()[3]);
		   i = new BigInteger(inet.getAddress());
		  } catch (Exception e) {
		   e.printStackTrace();
		  }
		  
		  System.out.println(i.toByteArray());
	}

}

127.0.0.1을 BigInteger로 변환한 결과는 다음과 같다

하지만 만약 127.0.0.1 대신 128.0.0.1을 사용하게되면 결과는 다음과 같다.

이를 다시 바이트 배열로 변환하여 출력하게되면 다음과 같은 -128.0.0.1이 출력된다

이는 byte가 -128~127까지 표현가능한데 128을 입력했기 때문에 다음과 같은 오버플로우가 발생하는것이다. 이를 정확하게 사용하기 위해서는 저장된 -128을 0xFF(11111111)과 &연산하거나 결과값에 256을 더해주는 방법으로 unsinged처럼 표현할 수 있다.

반응형
반응형

0. 개요

HTTP 응답 분할에 대해 알기전 CR, LF가 무엇인지 HTTP request, response가 어떤식으로 구성되어 응답 분할 문제가 발생하는지 공부한다.

1. CRLF의 정의

CR(Carrige Return: \r): 커서의 위치를 현재 줄의 맨 처음으로 보내는 기능
LF(Line Feed: \n): 커서를 다음 줄로 옮기는 기능

CR+LF: 다음줄의 제일 처음위치를 의미.

 

2. HTTP message 구조

http요청은 다음 구조처럼 이루어져 있다 이중 CRLF 공백 으로 이루어진 줄이 header와 body를 구분하는데 이러한 특성으로 인해 CRLF 취약점이 생긴다.

3. 예시

다음 예제는 외부의 입력값을 사용하여 반환되는 쿠키의 값을 설정하고 있다. 만약 공격자가 값을 Wiley Hacker \r\nHTTP/1.1 200 OK\r\n를 authorName의 값으로 설정할 경우, 다음과같은 두개의 페이지가 전달된다.

 

따라서 다음과 같이 CRLF의 여부를 체크하고 replaceAll을 이용하여 이를 제거하여 HTTP응답 분리를 방지한다.

반응형
반응형

1. CSRF의 정의

특정 웹 사이트에 대해 사용자가 인지하지 못한 상황에서 사용자의 의도와 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등) 을 요청하게 하는 공격을 말한다. 사용자로부터 받은 요청에 대해 사용자가 의도한대로 작성되고 전송된 것인지 확인하지 않을때 발생하며, 관리자인경우 관리자 권한의 게시물삭제 사용자 관리 등 임의로 공격이 가능하다

 

2. 안전한 코딩기법

입력화면 form 작성 시 Get 방식을 사용하면 URL에 노출이 되므로 POST방식을 사용하여 해당 입력을 처리하도록한다.

 

3. 예시

 

4. 추가적인 방법

CSRF Token이용하기

CSRF 토큰을 주고받으므로서 요청이 유효한지 확인할 수 있다. 예를들어 게시판 글 쓰기 페이지를 호출할 때 token을 서버에서 브라우저에게 전달하고, 게시판 글쓰기 요청이 왔을 때 토큰이 없다면 유효한 요청이 아니라고 판단하는 것 이다.

이는 Spring security를 사용하면 csrf토큰 내용이 구현되어있다. 유저 form에서 다음과 같이 csrf input을 추가해주면 된다.

<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> 

 

CAPTCHA

다음과 같이 추가적인 인증을 하는 CAPTCHA를 이용하여 CSRF에 대비할 수 있다.

 

추가로 비밀번호 요구하기

예를들어 사용자의 회원탈퇴, 정보수정같은 중요한 업무는 비밀번호를 한번 더 요청하는것으로 CSRF에 대비할 수 있다.

반응형
반응형

1. 신뢰되지 않는 URL 주소로 자동연결의 정의

사용자로부터 입력되는 값을 외부사이트의 주소로 사용하여 자동으로 redirect 하는 경우 피싱 공격에 노출되는 취약점을 가질 수 있다.

 

2. 안전한 코딩기법

자동 연결할 외부 사이트의 URL과 도메인은 화이트 리스트로 관리하고, 사용자의 입력값을 통해 주소로 리다이렉트 시키는경우 입력값이 존재하는지 여부를 확인한다.

 

3. 예시

다음과 같은 코드가 존재할 경우에 사용자는 이 링크를 클릭함으로서 피싱사이트로 접근된다.

<a href = "http://bank.example.com/redirect?url=attacker.example.net">Click</a>

이는 이동할 수 있는 리스트를 사전에 정의해놓고 그 리스트에 포함되어있지 않으면 이동시키지 않는 방법이다.

반응형

+ Recent posts