내가 만든 홈페이지 해킹하기

Jang Seok Woo·2020년 9월 11일
0

보안

목록 보기
10/19

내가 만든 홈페이지 해킹하기

개발환경 : CentOS7, JSP, Oracle11g

내가 만든 홈페이지를 SQL Injection 실습을 통해 접근해보려고 한다.

  1. 로그인
  2. 회원가입
  3. 게시판
  4. 검색

이 순서로 진행해보려고 하는데, 모두가 입력값을 받는 부분이고, 받아서 동적처리를 한다는 점에서 공통점을 갖고 있다. 각 부분에 있어 Union SQL Injection, Error-based SQL Injection, Boolean-based SQL Injection, Time-based SQL Injection 4가지 종류별 인젝션을 사용해 접근해보려고 한다.

먼저, 1. 로그인

다음과 같이 별 거 없이 필요한 부분만 멀쩡하게 생겼고 아이디와 비밀번호를 입력받는 페이지이다.

다음으로 login 부분 DAO 파일의 함수 코드를 보자

public int login(String userID, String userPassword) {
		String SQL = "SELECT USERPASSWORD FROM USER_BBS WHERE USERID = '" + userID+"'";
		try {
			stmt = conn.createStatement();
			rs = stmt.executeQuery(SQL);
			if(rs.next()) {
				if(rs.getString(1).contentEquals(userPassword)) {
					return 1;
				}
				else
					return 0;
			}
			return -1;
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return -2;
	}

이어서 로그인 처리 부분 jsp 파일을 보면,

	String userID = null;
		if(session.getAttribute("userID")!=null);{
		userID = (String) session.getAttribute("userID");
		}
		if(userID != null){
			PrintWriter script = response.getWriter();
			script.println("<script>");
			script.println("alert('이미 로그인 되어 있습니다.')");
			script.println("location.href='main.jsp'");
			script.println("</script>");
		}
		UserDAO userDAO = new UserDAO();
		int result = userDAO.login(user.getUserID(), user.getUserPassword());
		if(result==1){
			session.setAttribute("userID",user.getUserID());
			PrintWriter script = response.getWriter();
			script.println("<script>");
			script.println("location.href = 'main.jsp'");
			script.println("</script>");
		}
		else if(result==0){
			PrintWriter script = response.getWriter();
			script.println("<script>");
			script.println("alert('비밀번호가 틀립니다.')");
			script.println("history.back()");
			script.println("</script>");	
		}
		else if(result==-1){
			PrintWriter script = response.getWriter();
			script.println("<script>");
			script.println("alert('존재하지 않는 아이디입니다.')");
			script.println("history.back()");
			script.println("</script>");	
		}
		else if(result==-2){
			PrintWriter script = response.getWriter();
			script.println("<script>");
			script.println("alert('데이터베이스 오류가 발생했습니다.')");
			script.println("history.back()");
			script.println("</script>");	
		}

이미 로그인 되어있습니다. 아이디가 존재하지 않습니다. 비밀번호가 틀립니다. 데이터베이스 오류가 발생했습니다. 4가지로 잘못 입력된 경우의 수를 처리해두었다.

1) Union

로그인 페이지에서 union을 활용한 exploit이 가능할까?

우선 login.jsp에서 loginAction.jsp로 넘어가 검증과정을 거치는 탓인지 검증 코딩 부분을 주석처리 해두어도 SQL의 에러메시지가 나타나지 않았다.

Union을 이용한 쿼리 구문을 날려보려고 해도 char 숫자가

<div class="form-group">
	<input type="text" class="form-control" placeholder="아이디" name="userID" maxlength="20">
</div>

다음과 같이 20자로 제한이 되어 있어서 쿼리를 보낼 수가 없다.

맥락이 좀 흐트러지지만 시간의 흐름대로 쓰도록 하겠다. 로그인, 회원가입 부분은 코딩으로 처리되어있는 부분이 생각보다 꼼꼼해서..? 하루종일 시도했으나 잘 안되었고

게시판 글쓰기와 검색 부분은 몇가지 injection에 성공할 수 있었다.

다시 1번) 게시판 글쓰기

<script>alert("hello!")</script>

글의 제목과 내용에 다음과 같이 스크립트를 넣어보았더니

Hello! 라는 alert가 글의 제목과 내용이 불러와질 때마다 나타난다.

대처방안은

<tr>
						<td style="width: 20%;">글 제목</td>
						<td colspan="2">
						<%=bbs.getBbsTitle().replaceAll(" ","&nbsp;").replaceAll("<","&lt;").replaceAll("<","&gt;").replaceAll("\n","<br>")%>
						</td>
					</tr>
					<tr>
						<td>작성자</td>
						<td colspan="2"><%=bbs.getUserID()%></td>
					</tr>
					<tr>
						<td>작성일자</td>
						<td><%= bbs.getBbsDate().substring(0, 11) + bbs.getBbsDate().substring(11,13) + "시" + bbs.getBbsDate().substring(14,16) + "분"%></td>
					</tr>
					<tr>
						<td>내용</td>
						<td colspan="2" style="min-height: 200px; text-align: left;"><%=bbs.getBbsContent().replaceAll(" ","&nbsp;").replaceAll("<","&lt;").replaceAll("<","&gt;").replaceAll("\n","<br>")%></td>
					</tr>

글의 제목과 내용을 불러올 시

bbs.getBbsTitle().replaceAll(" ","&nbsp;").replaceAll("<","&lt;").replaceAll("<","&gt;").replaceAll

이렇게 replaceAll 함수를 이용해 일일이 특수문자를 처리해주면 된다.

이렇게 아무렇지 않게 글을 불러올 수 있게 된다.

2번 검색부분 SQL Injection

맨 처음 접근했던 부분은 원래 해당 게시판의 쿼리는

select NUM from (select row_number() over (order by bbsDate desc) NUM, A.* from bbs A where bbsavailable=1 and bbsTitle like '%"+searchWord+"%' order by NUM desc)";

이렇게 보내지고, 받은 삭제되지 않은 글을 정렬하여

select * from ( select row_number() over (order by bbsDate desc) NUM, A.* from bbs A where bbsavailable=1 order by bbsDate desc) where NUM between 1 and 10

다음과 같이 글을 10개만 선별하여 게시판 jsp 페이지에 출력해주는 방식이다.

String SQL = "select * from (select row_number() over (order by bbsDate desc) NUM, A.* from bbs A	where bbsavailable=1 and bbstitle like'%"
				+ searchWord
				+ "%' order by bbsDate desc) where NUM between "
				+ no1
				+ " and "
				+ no2;

코딩부분은 이렇게 되어있는데,

select NUM from (select row_number() over (order by bbsDate desc) NUM, A.* from bbs A where bbsavailable=1 and bbsTitle like '%"+searchWord+"%' order by NUM desc)";

위에 먼저 불러와지는 이 쿼리만 인젝션이 가능할 것으로 보여진다. (내 실력으로는)

우선 searchword부분에 입력값을 넣어 해당 게시판의 의도와 다른 출력값을 불러와보자.

‘) - - 를 입력해보면 어떨까?

주석처리를 이용해 뒷부분을 날리고 앞부분을 뒷부분에 between A and B를 이용해 10개를 선별하던 쿼리가 사라지고 게시판 내의 모든 글이 한 페이지에 출력될 것이다.

2-1) 검색 부분 – Union

어제 실습해 보았던 union을 이용해 먼저 리눅스 터미널로 쿼리를 만들어보자

String SQL = "select * from (select row_number() over (order by bbsDate desc) NUM, A.* from bbs A	where bbsavailable=1 and bbstitle like'%"
				+ searchWord
				+ "%' order by bbsDate desc) where NUM between "
				+ no1
				+ " and "
				+ no2;

이 부분이 앞부분이라 뒷 부분은 앞 부분과 컬럼의 개수와 타입이 맞아야한다.

NUM을 이용해 하나의 컬럼, 그리고 bbs 테이블의 모든 컬럼을 불러온다.

NUM은 row_number()를 이용한 int

Bbs 테이블은 int, str, str, str, str, int로 구성되어있다.

총 7개의 컬럼와 각 타입을 맞춰 union 쿼리를 만들어 보자.

select * from (select row_number() over (order by bbsDate desc) NUM, A.* from bbs A	where bbsavailable=1 and bbstitle like'%' union select 0,0,userid,userpassword,'YYYY-MM-DD-hh24:mi:ss',username,0 from user_bbs);

union 뒤에서부터 0,0 으로 인트 2개, 나머지 user 테이블에서 string 타입의 아이디와 비밀번호, 그리고 bbs 테이블 날짜데이터를 처리해주는 부분에서 오류가 나길래 해당 부분을 그냥 'YYYY-MM-DD-hh24:mi:ss' 같은 형태의 문자열로 대체하였다. 뒤에 추가로 String 뒤에 int로 0 고정값.
결과는 이렇다.

번호에 0,으로 되어있는 글들은 제목 컬럼에 아이디 작성자 컬럼에 비밀번호가 각각 출력이 되었다.

대처방안 : 입력받는 길이를 정해준다.

2-2) 검색 부분 – Error-based

select * from (select row_number() over (order by bbsDate desc) NUM, A.* from bbs A	where bbsavailable=1 and bbstitle like'%') union SELECT 0,0,utl_inaddr.get_host_name((select banner from v$version where rownum=1)),'0','0','0',0 FROM dual;

다음과 같이 오류가 나면서 해당 Oracle의 버전이 노출되는 쿼리를 사용하였으나, 홈페이지에는 노출되지 않는다. 다만 이클립스에는 해당 오류 메시지에 버전이 노출됨을 확인했다. 이 이상은 접근법을 아직 모르겠다.

대처방안 : 다음과 같은 alert 메시지가 나오도록 변경해준다.

입력받는 길이를 정해준다.

2-3) 검색 부분 – Boolean-Based

select * from (select row_number() over (order by bbsDate desc) NUM, A.* from bbs A	where bbsavailable=1 and bbstitle like'%') union select 0,ascii(substr(name,1,1)),NAME, DB_UNIQUE_NAME, 'YYYY-MM-DD-hh24:mi:ss', DB_UNIQUE_NAME,0 from v$database;

다음과 같이 int 타입의 결과를 ascii로 접근해 해당 DB의 이름을 찾아보기 위한 쿼리를 날렸다.

DB 이름의 첫번째 글자의 ASCII코드 88(대문자 X)가 나왔고, DB명으로 XE가 노출되었다.

Oracle에 다음과 같이 접근하고 싶었으나, 비교연산자를 사용하기가 까다로워 injection으로 쿼리를 만드는데에는 실패하였다.


select count(*),name from v$database where ascii(substr(name,1,1))>100;

대처방안 : 입력글자수를 줄인다.

2-4) 검색 부분 – Time-Based

select case when 1=1 then '1' else '2' end from dual;

Oracle은 위와 같은 쿼리를 이용해 case then else를 사용할 수 있었지만,

Sleep을 이용해 시간차를 보고 싶었으나 Oracle은 쿼리의 모양이 좀 달라 공부가 필요했다. 아직불가능

끝.

profile
https://github.com/jsw4215

0개의 댓글