∞. 기술 면접/4. 데이터베이스

Spring Mybatis SQL Injection

THE HEYDAZE 2021. 10. 16. 20:44
MyBatis 바인딩 기법

MyBatis 에서는 #{} 바인딩과 ${} 바인딩이 있다

#{} 바인딩은 바인딩 값을 "" 로 감싸주어 바인딩을 해준다

[GET] http://localhost:8080/members?name=root@gmail.com

[SQL] select * from email = "root@gmail.com"

 

${} 바인딩은 "" 없이 바로 바인딩을 해준다

[GET] http://localhost:8080/members?name=root@gmail.com

[SQL] select * from email = root@gmail.com

위와 같이하면 "" 로 감싸지않아 쿼리문이 날라가게 되어 오류가 발생한다

따라서 ${} 이용해서 요청하려면 아래와 같이 해야 한다

[GET] http://localhost:8080/members?name="root@gmail.com"

[SQL] select * from email = "root@gmail.com"

 

${} 주로 사용되는 이유는 검색 조건을 동적으로 바인딩하기 위해서 이다

select * from member ${field} = #{value}

 

SQL Injection

SQL 인젝션은 

member 테이블

 

DB 구조

 

SQL INJECT 쿼리문

 

 

<%@ page contentType="text/html;charset=UTF-8" language="java"
         pageEncoding="utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

<section>
    <h1 align="center">리스트 페이지</h1>

    <table border="1" align="center" width="500">
        <c:forEach items="${members}" var="member">
            <tr>
                <td>이메일</td>
                <td>
                    <a href="<c:url value="/member/${member.id}" />">
                            ${member.email}
                    </a>
                </td>
                <td>
                    ${member.name}
                </td>
            </tr>
        </c:forEach>

            <tr>
                <td colspan="3" class="text-center">
                    <a href="<c:url value="/member/write"/>">작성하기</a>
                </td>
            </tr>
    </table>

</section>


</body>
</html>

 

실제 View 화면

보통 보안상 패스워드는 DB에 들어갈때 암호화 되어 들어가서 가져온다고 해도 사용할 수 없지만

회원의 주소라던가 주민번호 휴대폰 번호로 바꿔서 한다면 보안상 위험합니다 (물론 현대 웹에서는 개인정보를 자사 웹에서 갖고 있지 않게 끔 하는 추세입니다 외부에 API 를 이용해서 인증만 하는 방식으로 처리해나가 민감한 정보를 저장하지 않습니다)

# 전체 조회 쿼리문
select * from member;

# 개발자가 예상했던 쿼리문
select * from member where id = 1;

# SQL Inject 을 이용하여 email 과 pwd 를 뒤바꿔 DTO 에서는 pwd 를 리턴하지 않아도 email 에서 pwd 를 리턴하게 한다
select * from member where id = 1 union select id, email, pwd, name, phone, register_date from (select id, email as name, pwd as email, name as pwd, phone, register_date from member) as m ;

# listSearch?keyword="root"%20union%20select%20id,%20email,%20pwd,%20name,%20phone,%20register_date%20from%20(select%20id,%20email%20as%20name,%20pwd%20as%20email,%20name%20as%20pwd,%20phone,%20register_date%20from%20member)%20as%20m%20;

 

겪었던 문제
    /** 회원 목록 페이지 */
    @RequestMapping(value = "/member/listSearch", method = RequestMethod.GET)
    public String memberListPageSearch(Model model, String keyword ) {

        ArrayList<MemberDto> members = mapper(MemberDao.class).listSearch(keyword);

        model.addAttribute("members", members);

        return "member/list";
    }

1. ${} 을 줄 경우 String 타입으로 파라미터를 못넘겨 DTO 또는 HashMap 으로 넘겨줍니다
parameterType = "String" 을 써줘도 String 으로는 바인딩이 안되었습니다

ㄴ [2021.10.20 수정] String 을 받아줄 때는 ${param1}, ${param2} 이런식으로 받아서 처리하면 됩니다
    첫번째 인자를 ${param1} 로 바인딩 해줍니다

더보기
<!-- Mapper.xml -->
<select id="listSearch" resultType="kr.co.loyd.dto.MemberDto">
    select * from member where name = ${param1}
</select>

 

    /** 회원 목록 페이지 */
    @RequestMapping(value = "/member/listSearch", method = RequestMethod.GET)
    public String memberListPageSearch(Model model, String keyword ) {

        Map<String, String> map = new HashMap();
        map.put("keyword", keyword);

        ArrayList<MemberDto> members = mapper(MemberDao.class).listSearch(map);

        model.addAttribute("members", members);

        return "member/list";
    }

때문에 Map 으로 하였습니다

 

<!-- Mapper.xml -->
<select id="listSearch"  parameterType="java.util.Map" resultType="kr.co.loyd.dto.MemberDto">
    select * from member where name = ${keyword}
</select>

 

그 외에도 내 정보보기를 통해 나의 정보를 자세하게 보고 싶은 페이지 아래와 같이 이용하여
다른 사람의 자세한 정보도 볼 수 있다
# 의도했던 쿼리
select * from member where id = 1;

# SQL Injection 공격법으로 모든 사용자 조회
select * from member where id = 1 or '1'='1';

# SQL Injection 공격법으로 특정 사용자 조회
select * from member where id = 1 or '1'='1'  limit 5,1;

 

 

url 로 id 를 넘기지 않겠지만 

api 또는 hidden 컬럼의 id 에 적으면 똑같다

spring 에서 mybatis 는 selectOne 하는 경우 리스트로 반환 되면 오류나기 때문에 offset limit 를 주어 특정 번째에 유저를 상세조회하는 것이다

가져오려고하면 1개만 가져와야하는 데 1개가 아니다라는 에러

 

다른 블로그에서 정리한 내용

https://logical-code.tistory.com/25
https://madplay.github.io/post/difference-between-dollar-sign-and-sharp-sign-in-mybatis

 

결론

파라미터를 바인딩 할 때에는 "" 로 감싸주는 형식으로 해야 한다 ( #{} 사용하기 )

 

클라이언트 - 백엔드 서버 - DB서버

DB 로도 충분히 문자열 자르기, 정렬, 원하는 정보만을 줄 수 있지만

다양한 공격법을 막기 위해 중간에서 처리해주기 위해 백엔드 서버가 존재하는 것