'Hacking & Security/Web'에 해당되는 글 5건

  1. 2008.06.30 안전한 PHP 코드 작성하기(php underground security)
  2. 2008.06.30 Uncommon SQL Injection
  3. 2008.06.29 구멍난 자바스크립트
  4. 2008.06.29 파일조작
  5. 2008.06.29 SQL Injection
Hacking & Security/Web2008. 6. 30. 08:19

보안을 신경쓰지않았던 개발자들에게

기초적이면서도 웹보안에 도움이 되는 문서입니다.
Posted by skensita
Hacking & Security/Web2008. 6. 30. 08:06


Milw0rm 문서 중 하나입니다.

SQL 인젝션의 기초에 대해 설명하고 있습니다.

내용도 짧고 간단해서 읽기에 좋습니다.

많은 도움 되시길 바랍니다.

Posted by skensita
Hacking & Security/Web2008. 6. 29. 04:50

Web Hacking 1탄 SQL Injection
Web Hacking 2탄 파일조작
Web Hacking 3탄 구멍난 자바스크립트


1. 시작하기
여러분들은 사용자가 입력한 값에대해 어느정도 검증을 하는지요?
사용자 값을 이것저것 따지고 여러가지 유효성을 체크할려면 아주 귀찮은 일이 아닐수 없습니다
그중에 하나가 웹브라우져에서 실행되는 자바스크립트로 체크하는 방법이 있는데 비지니스 로직이 조금 복잡할때는 자바스크립트로 도배를 하는 경우도 종종 있지요
유효성 검증하는일 저도 정~~말 싫어합니다 하지만 해야 합니다 ㅠ.ㅠ
그럼 어디까지 그 유효성을 체크해야 할까요? 자바스크립트로만 체크하면 될까요?

처음 웹프로그래밍을 할때는 저도 자바스크립트로만 입력값을 검증하면 되는줄 알았습니다
한마디로 몰랐죠
하지만 이제는 알기때문에 자바스크립트뿐만 아니라 서버쪽에서도 동일하게 체크해 주어야 합니다
사용자가 입력한 값이 얼마나 무서운지는 앞의 1탄, 2탄의 강좌를 통해 어느정도는 감을 잡으셨으리라 생각하고 이번 강좌에서는 왜 서버쪽에서도 체크해야 하는지!에 대해 간략히 알아보겠습니다


2. 자바스크립트 체크

입력값의 자리수가 13자리인지 체크하여 맞으면 submit을 만약 그렇지 않다면 정상적인 입력값을 요구하는 입력갑검사 로직이 있다고 가정하겠습니다 그리고 서버쪽에서는 꼭 13자리가 넘어와야 정상적인 처리를 할수 있다고 하겠습니다
그럼 아래와 같은 간단한 코드가 나올겁니다

<script>
/* 입력한값이 13자리인지 체크 후 전송 */
function validateValue() {
    var obj = document.f.register_no;
    if (obj.value.length != 13) {
        alert('주민번호 정상적으로 입력하세요');
        return false;
    }
}
</script>

<form name=f method=post action=process.jsp onsubmit="return validateValue()">
주민번호 <input type=text name=register_no> <input type=submit name=btn value=submit>
</form>

 
 실제 웹 브라우져에서 보면 아래와 같습니다
"33" 이라는 두자리 수만 입력하니 역시나 재입력을 요구하는 alert창이 떳습니다 (가끔 깜짝깜짝 놀라죠 -,-)

사용자 삽입 이미지

그럼 정상적으로 "1111111111111" 의 입력값 13자리를 입력해 보겠습니다

사용자 삽입 이미지

그리고 이 값은 process.jsp 라는곳에서 파라미터로 받아 아래와 같이 처리합니다
코드는 간단하게 그 입력값과 레퍼러 값을 보여주겠습니다

<%@ page contentType="text/html; charset=euc-kr" %>

<%
String register_no = request.getParameter("register_no");
%>

헤더정보 <%=request.getHeader("referer")%><br><br>
주민번호 <%=register_no%>



request.getHeader("referer")는 헤더정보에서 referer값을 가져오는 함수로, 이전 페이지 주소값을 가져옵니다
그럼 화면은 아래와 같이 나옵니다

사용자 삽입 이미지

자 정상적으로 처리되었습니다


3. 자바 스크립트 우회
그럼 이제부터 자바스크립트로 입력값의 자리수 체크로직을 피해가는 방법을 알아봅시다
어떤 방법이 있을까요?

URL로 접근
입력값이 몇가지 되지 않는다면 아래 방법이 가장 편하겠군요
즉 브라우져 주소창에다 직접 입력 합니다
http://www.jakartaproject.com/html/process.jsp?register_no=2222

아래와 같은 화면이 나올겁니다

사용자 삽입 이미지

메쏘드정보 (request.getMethod())를 찍어보니 GET 이 나오는군요!
그렇다면 이같은 직접 URL값을 막기위해 간단히 POST만 허용하는 로직을 추가해 버립시다

<%@ page contentType="text/html; charset=euc-kr" %>

<%
if (!"POST".equals(request.getMethod())
   return;


String register_no = request.getParameter("register_no");
%>

헤더정보 <%=request.getHeader("referer")%><br><br>
주민번호 <%=register_no%>

이러면 괜찮겠지 흐흐.. 라고만끝나면 안됩니다

로컬파일로 접근
①번의 방법은 GET방식때문에 막혔습니다 그러면 자바스크립트를 우회하면서 POST방식으로 보내는 방법은 없을까요? 물론 있습니다
웹브라우져는 "소스보기" 라는 좋은 메뉴가 있습니다 있는건 적극 활용합시다 ㅎㅎ
소스보기한 후 바탕화면에 저장하여 약간 손을 보았습니다
즉 입력값을 체크하는 자바스크립트를 무력화 시키면서, action을 수정해 주었습니다

C:\Documents and Settings\Administrator\바탕 화면\client.html

<script>
/* 입력한값이 13자리인지 체크 후 전송 */
function validateValue() {
    return true;
}
</script>

<form name=f method=post action="http://www.jakartaproject.com/html/process.jsp" onsubmit="return validateValue()">
주민번호 <input type=text name=register_no> <input type=submit name=btn value=submit>
</form>



웹화면은 다음과 같겠죠

사용자 삽입 이미지

"submit"버튼을 클릭합니다 ^^
역시나 자바스크립트를 거치지 않고 process.jsp에 13자리수가 아닌 "22"값을 무난히 보냈습니다

사용자 삽입 이미지

그럼 서버쪽에서는 "어이쿠 이런 헤더정보도 체크해야 겠군" 하고 다음 코드를 추가해 버릴겁니다
즉 레퍼러값이 null인 경우는 허용할수 없다는 것이죠

<%@ page contentType="text/html; charset=euc-kr" %>

<%
if (!"POST".equals(request.getMethod())
   return;

if (request.getHeader("referer") == null)
   return;

String register_no = request.getParameter("register_no");
%>

헤더정보 <%=request.getHeader("referer")%><br><br>
주민번호 <%=register_no%>

그럼 위와같은 소스가 될것입니다
그럼 과연 위 소스가 안전할까요?

HEADER값 조작
그렇다면 HEADER값을 조작해 봅시다 어떻게 조작할 수 있을까요?
여러가지 프로그램들이 있지만 그중에 Achilles 라는 proxy server를 이용하여 조작해 보겠습니다
Achilles라는 proxy server는 웹브라우져와 서버간의 HTTP 세션을 중간에서 가로채어 원하는대로 수정한 후 보낼수 있도록 해주는 작으면서도 강력한 프로그램입니다

※ 참고
Achilles 문서를 보면 맨 첫줄에 나오는 문장입니다
"Achilles is a tool designed for testing the security of web applications"
이 프로그램은 웹어플리케이션의 보안 테스팅을 하는데 작성되었으므로 테스팅용으로만 사용합시다

다운로드
http://www.mavensecurity.com/achilles



우선 위에서 작성한 바탕화면에 저장된 C:\Documents and Settings\Administrator\바탕 화면\client.html 를 실행한 결과를 Achilles가 Intercept한 결과입니다

사용자 삽입 이미지

여러 가지 헤더정보들이 보이며 쿠키값까지 조작할수 있습니다
하지만 위의 헤더정보에는 referer값이 없습니다 그래서 referer체크에서 null이 나와 로직에 걸립니다
그렇다면 referer정보를 추가해 줍시다

사용자 삽입 이미지

파랑색으로 줄쳐진 부분을 에디팅하여 추가해주었습니다
Referer: http://www.jakartaproject.com/html/input.html
그런다음 "Send"버튼으로 전송합니다

사용자 삽입 이미지

짠~
그러면 조작된 헤더정보를 알아채지 못하고 헤더의 referer값을 가져오는군요!


4. 서버쪽 로직 추가!
위에서 자바스크립트를 우회하는 몇가지 방법들을 알아보았습니다
결론은 하나입니다 즉! 서버쪽에서도 동일하게 체크해 주어야 합니다

<%@ page contentType="text/html; charset=euc-kr" %>

<%
String register_no = request.getParameter("register_no");

if (!"POST".equals(request.getMethod())
   return;

if (request.getHeader("referer") == null)
   return;


if (register_no.length() != 13)
   return;
%>

헤더정보 <%=request.getHeader("referer")%><br><br>
주민번호 <%=register_no%>



그렇다면 서버쪽에서만 체크하고 클라이언트에서는 체크할 필요가 없다고 생각할지도 모르지만,
그만큼 서버쪽으로 오는 request를 줄이면 더더욱 좋겠죠
자잘한것 하나때문에 서버쪽 부하를 줄수 없는 노릇아니겠습니까?

자 이러이러 하고 저러저러한 이유로 우리는 이제부터라도 클라이언트에서는 자바스크립트로,
서버쪽에서는 또 서버쪽 스크립트로 사용자가 입력한 값에대해 유효성을 검증해야 하는것을 알았습니다
로직이 복잡한 경우 가뜩이나 자바스크립트도 복잡한데 그 로직을 서버쪽에서 다시한번 만든다는게 힘들다는거 다들 압니다 하지만 안전한 웹페이지를 만들기 위해 다같이 노력해야 되지 않겠습니까?

Web Hacking 1탄 부터 이번 3탄에 이르기까지 웹 프로그래밍 하면서 반드시 필요하고 인식해야 하는 부분에 대해 논하였습니다
Posted by skensita
Hacking & Security/Web2008. 6. 29. 04:05

Web Hacking 1탄 SQL Injection
Web Hacking 2탄 파일조작
Web Hacking 3탄 구멍난 자바스크립트


1. 시작하기
해커가 하나의 웹사이트를 공격하기 위해 많은 시간을 준비한다고 했습니다
그중에 가장 먼저 하는것 중에 하나가 바로 파라미터와 그 값에대한 목록 정리 입니다
즉 타겟 웹사이트의 모든 URL 파라미터를 GET, POST등으로 정리를 합니다

http://xxxxxxxx.com/pds/list1.php?cnum=2
http://xxxxxxxx.com/pds/list2.php?cnum=2&snum=21
http://xxxxxxxx.com/view.php?fnum=104591
http://xxxxxxxx.com/bbs/bbs.php?table=bbs_sw_qna
http://xxxxxxxx.com/bbs/bbs.php?f=write&table=bbs_sw_qna
http://xxxxxxxx.com/bbs/bbs.php?f=view&table=bbs_sw_qna&fid=48610&num=85973
...

이렇게 목록화 후 분석을 해보면 해당 파라미터가 어떤 역할을 하는지 무엇을 의미하는지 80%는 파악할 수 있습니다
bbs의 f에는 write와 view 라는 값이 주어지는것을 보니 액션에 대한 글쓰기를 할것이지 아니면 글조회를 할것인지를 나타내는것으로 보이며 table이라는 컬럼은 아마도 물리적 table이름을 말하는것 같군요
cnum은 아마도 카테고리 번호를 의미하는것이고 snum은 소카테고리번호를, num은 게시물 글번호를 의미하는것으로 추정됩니다
그럼 이 프로그램들이 어떻게 돌아가는지 대강 파악이 되고 이제부터 취약점이 있는지 체크해 나가보는겁니다

파라미터명이나 값에대해서는 유추할수 없이 프로그램 한다면 아마도 조금은 더 보안적으로 안전할 것입니다

다음은 파라미터로 적절치 않은 값을 넘겨주는 예를 알아볼 것이며, 파일 업로드, 다운로드 취약점에 대해 공부해 보겠습니다 ^^


2. 동적 파일 로딩 취약점
동적으로 어떤 특정 파일을 열어 그 파일 내용을 웹에서 보여주는 jsp가 있다고 합시다
대량의 html이나 txt 파일들을 읽어 웹에서 보여주는 로직들이 많지요 (저도 많이 썼습니다 -_-;)
동적으로 그 파일에 대한 정보를 파라미터로 읽어온다면 어떻게 될까요?

<%
String param = request.getParameter("param");

File file = new File(request.getRealPath("/")+param);

/* 파일 내용을 읽어 buffer에 저장 */
StringBuffer buffer = new StringBuffer();
if (file.exists()) {
    FileInputStream fis = new FileInputStream(file);
    byte b[] = new byte[1024];
    int read = 0;
    while ((read = fis.read(b)) != -1) {
        buffer.append(new String(b));
        b = new byte[1024];
    }
}
%>
파일내용
<%=buffer.toString()%>;


다음과 같은 요청을 보낸다면..

http://localhost/file.jsp?param=sample.txt



사용자 삽입 이미지

/ROOT/sample.txt  파일을 정상적으로 로딩하여 보여줄겁니다
하지만 다음과 같이 값을 준다면 어떻게 될까요?

http://localhost/file.jsp?param=../../../WINNT/system32/drivers/etc/services


사용자 삽입 이미지

저런 --;  시스템 파일들이 몽땅 조회가 되는군요..

Unix 계열이라면

http://localhost/file.jsp?param=../../../../../etc/passwd   ../ 올라가 보면서 찾아보는것도 좋겠죠 -_-


상위 디렉토리로 이동하면서 크래커는 passwd file을 볼수 있습니다.
앗 여기서 다들 뜨끔 하신가요? 저만 그러나 ^^;

그럼 어떻게 처리해야 할까요? ../ 만 막아선 될까요?
Unix에서는 ./.\. 도 상위로 가는 명령이죠
아시겠지만 cd ./.\. 하면 한단계 위로 올라간답니다
그러니 ../ 뿐만아니라 ./.\. 도 같이 막을수 있도록 파라미터 확실히 체크를 해야 합니다


3. 동적 include 파일 취약점
그럼 include는 어떨까요? 위처럼 동적으로 include 할 파일명을 받아 처리하는 구조 말이지요

<%
String param = request.getParameter("param");
%>

파일내용<br>
<jsp:include page="<%=param%>" flush="true"/>


http://localhost/include.jsp?param=sample.txt


라고 요청을 날렸습니다

사용자 삽입 이미지

네 정상적으로 sample.txt를 가지고 왔습니다
하지만 다음과 같은 요청이 가면 어떨까요?

사용자 삽입 이미지

네 역시나 기대를 저버리지 않고 web.xml을 기냥 출력해 버리는군요 (여기서 알아보기 쉽게 <를 나타나게 하였습니다)
문제는 /WEB-INF/ 밑에 기타 중요한 properties (데이터에비스 설정 properties)가 있다면 낭패입니다
그럼 어떻게 해야 할까요? 역시나 파라미터값을 필터링 하는수 밖에 없습니다

그래도 이정도면 약과 입니다 -_-;
다음을 보도록 하지요

JSP에서 include에는 대략 4가지 방법이 있습니다
<%@ include file="sample.jsp" %>
<jsp:include page="sample.jsp" flush="true" />
pageContext.include("sample.jsp")
<c:import url="sample.jsp" />

이 4가지가 무순 차이가 있을까요?
차이점을 쓸려다 보니 이번 강좌와 좀 동떨어지는것 같아 그건 제외하고 --;

문제는 동적으로 페이지가 include가 가능 하냐입니다
이점에서는 ①은 동적으로 설정이 가능하지 않으므로 안전한 include입니다 (서버측 include이니 당연합니다)
그럼 ②과 ③은 어떨까요? include할 페이지 설정이 동적으로 가능합니다
그래서 위와 같은 문제점이 있지요
하지만 ②, ③은 확장자가 jsp에만 반응을 하고 확장자가 jsp가 아닐 경우에는 그냥 텍스트로 include를 처리해 버립니다
④번은 좀 특이합니다 물론 동적으로도 가능하며 리모트로도 가능하다는 것입니다.

<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
...
String param = request.getParameter("param");
<c:import url="<%=param%>"/>
...


http://localhost/include.jsp?param=http://www.jakartaproject.com/test.jsp


어떻게 될까요?
그나마 다행인것이 test.jsp 파일을 가지고 와서 서버에서 실행되는 것이 아니라 리모트에서 실행된후의 html 결과물만을 가지고 오는군요 그렇지 않으면 악성 스크립트를 활용할수있는 좋은 구멍 이었을텐데 말입니다 ^^;

다음표로 include 4가지 특성을 정리하였습니다

 유형

동적 실행

jsp 확장자에 반응

리모트 실행여부

<%@ include=

N

확장자에 상관없이 JSP로 실행

N

<jsp:include=

Y

확장자가 JSP만 JSP로 실행

N

pageContext.include

Y

확장자가 JSP만 JSP로 실행

N

<c:import url=

Y

확장자가 JSP만 JSP로 실행

Y









3. 파일 업로드 취약점
해커가 가장 많이 이용하고 좋아하는 취약점이 바로 이 파일 업로드입니다
이 부분은 누차 강조되어왔던 부분이라 대부분 아실겁니다
php 서버에는 php파일이, asp서버에는 asp파일이, jsp 서버에는 jsp파일이 업로드 되어서는 안됩니다
즉 서버에서 실행 가능한 파일을 올려선 안된다는 겁니다

악성 스크립트 침투 시나리오
악성 jsp 코드가 들어있는 test.jsp 파일을 글쓰기 화면을 통해 첨부 파일로 업로드 합니다
업로드 디렉토리가 /upload/ 라고 가정한다면 (업로드 디렉토리를 찾는건 그리 어렵지 않습니다)
    /upload/test.jsp 파일이 첨부파일을 통해 업로드 되었습니다
URL로 그 파일을 요청합니다  http://localhost/upload/test.jsp
    그러면 악성 코드가 들어있는 첨부파일이 웹서버에서 실행되어 버립니다
    test.jsp 파일이 서버의 파일을 모두 삭제하는 스크립트라면 큰일이지요
혹은 include 취약점을 이용해서 업로드한 스크립트를 include 시켜 실행 하기도 합니다


4. 파일 다운로드 취약점

간단한 jsp에서 일반적으로 사용하는 파일 다운로드 코드입니다
이역시 물리적 파일명 자체를 파라미터로 전달하고 있으며 이를 받아 파일을 다운로드 시키고 있습니다

<%@ page import="java.io.*"%><%

String param = request.getParameter("param");
File file = new File(getServletContext().getRealPath("/upload/")+param);

response.setContentType("application/smnet");
response.setHeader("Content-Disposition", "attachment;filename=다운로드;");
response.setHeader("Content-Length", String.valueOf(file.length()));
response.setHeader("Content-Transfer-Encoding", "binary;");

/* 파일이 존재하면 다운로드 */

if (file.exists()) {
    byte b[] = new byte[4096];
    BufferedInputStream bin = new BufferedInputStream(new FileInputStream(file));
    BufferedOutputStream outs = new BufferedOutputStream(response.getOutputStream());
    int read = 0;
    try {
        while ((read = bin.read(b)) != -1){
            outs.write(b,0,read);
        }
    } catch (Exception e) {
        System.out.println(e.getMessage());
    } finally {
        if (bin!=null) try { bin.close(); } catch (Exception sube) {}
        if (outs!=null) try { outs.close(); } catch (Exception sube) {}
    }
}

무엇이 잘못되었을까요?
다음과 같이 URL을 요청해 봅시다

http://loalhost/download.jsp?param=../WEB-INF/web.xml

사용자 삽입 이미지

그려면 요청한 web.xml 이 다운로드 됩니다
즉 이말은 웹 어플리케이션의 모든 파일(소스)을 다운로드해 볼수 있다는 것입니다
소스를 보게 된다면 웹해킹이 물론 더 쉬워지겠지요?


5. 업로드 및 다운로드 취약점 개선 시나리오
악성 jsp 코드가 들어있는 test.jsp 파일을 글쓰기 화면을 통해 첨부 파일로 업로드 합니다
/upload/test.jsp 로 업로드 후 파일명을 변경해 버립니다
    /upload/200601171137507804462_jsp 로 변경해 버립시다
    단순히 파일명만 변경해선 안됩니다 확장자도 제거해 버려야 합니다
    업로드 후 파일의 확장자가 두개 이상이던지, "." 이 이유없이 많다던지, "/" 나 "\" 캐릭터가 파일명에 있으면 부적절한 파일로 간주하고 지워버립시다
    그리고 200601171137507804462_jsp 명과 실제 파일명을 같이 데이터베이스에 저장합니다
URL로 그 파일을 요청합니다  http://localhost/upload/test.jsp
    당연히 파일이 존재하지 않음으로 Not Found가 나올것입니다
그럼 다음 URL로 요청해 봅시다 http://localhost/upload/200601171137507804462_jsp 
    어떻게 될까요?
    jsp 코드는 실행되지 않고 text로 jsp 코드를 그냥 브라우져에 뿌리게 됩니다
    즉 아무 상관이 없다는 것입니다
    200601171137507804462_jsp 도 유저에게 보여주어서는 안됩니다
    해당 URL 조차 허용하고 싶지 않다면 아래 web.xml에 security 제한을 둡시다
  

<security-constraint>
     <display-name>JSP and Upload File Protection</display-name>
     <web-resource-collection>
         <web-resource-name>SecureFile</web-resource-name>
         <url-pattern>/upload/*</url-pattern>
         <http-method>DELETE</http-method>
         <http-method>GET</http-method>
         <http-method>POST</http-method>
         <http-method>PUT</http-method>
     </web-resource-collection>
     <auth-constraint>
         <role-name>nobody</role-name>
     </auth-constraint>
</security-constraint>

<security-role>
    <description>Nobody should be in this role so JSP files are protected from direct access.</description>
    <role-name>nobody</role-name>
</security-role>

 

    그러면 http://localhost/upload/200601171137507804462_jsp 은 다음과 같은 메세지를 뿌립니다

사용자 삽입 이미지

그리고 파일 다운로드는 직접파일 링크가 아니라 response의 ouputStream을 통해 다운로드 시킵시다
   키값을 가지고 200601171137507804462_jsp 명을 얻어 다운로드 할수 있도록 프로그램 해야 합니다
   즉 다운로드 시킬때도 물리적 파일명을 파라미터로 전달하지 말고 해당 키값을 전달해서 파일명을 데이터베이스에서 얻어오도록 합니다

ps. 이런 취약점 때문만은 아니겠지만 업로드한 파일을 파일 시스템으로 저장하지 않고 데이터베이스에 직접 저장하기도 합니다

6. SQL Injection으로 인한 결과물을 파일로저장하여 다운받아 볼 수 있습니다
앞에서 살펴본 SQL Injection을 예를들어봅시다
아래 코드는 게시물 번호를 파라미터로 입력받아 해당 글번호를 조회하는 코드입니다
아래와 같은 코드가 있다고 한다면 UNION SQL Injection 피해를 볼수 있다고 하였습니다

String boardno = request.getParameter("boardno");
rs = stmt.executeQuery("SELECT boardno, boardtitle, boardcontent FROM board_test_t WHERE boardno = "+boardno);
    if (rs.next()) {
        out.println(rs.getString(1)+"<br>");
        out.println(rs.getString(2)+"<br>");
        out.println(rs.getString(3)+"<br>");
}

데이터베이스가 MySQL 이라고 한다면 URL 요청이 다음과 같다면 어떻게 될까요?

http://localhost:8080/test.jsp?boardno=1133971825719 UNION SELECT '1', userid, userpw FROM user_t INTO OUTFILE 'C:/Tomcat/webapps/ROOT/upload/hack.txt'



다음과 같이 에러 메세지가 납니다 하지만.. hack.txt란 파일은 생성이 됩니다
즉 user의 모든 아아디 비밀번호를 간편하게 파일로 다운받아 보는겁니다


HTTP Status 500 -

type Exception report

message

description The server encountered an internal error () that prevented it from fulfilling this request.

exception org.apache.jasper.JasperException org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:372) org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:292) org.apache.jasper.servlet.JspServlet.service(JspServlet.java:236) javax.servlet.http.HttpServlet.service(HttpServlet.java:802) com.jakartaproject.util.CharacterEncodingFilter.doFilter(CharacterEncodingFilter.java:28)

root cause java.lang.NullPointerException org.apache.jsp.test_jsp._jspService(test_jsp.java:64) org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:94) javax.servlet.http.HttpServlet.service(HttpServlet.java:802) org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:324)



그 다음 http://localhost:8080/upload/hack.txt 로 user_t 테이블에 있는 모든 사용자아이디와 비밀번호를 텍스트로 받아볼 수 있습니다. 아마 이방식으로 유저 아뒤/비번 많이 빼내신분들 많으실 겁니다 ㅎㅎ

톰캣 설치 경로는 어떻게 알까요?
톰캣설치경로 대부분 비슷할 겁니다 몇가지 해보면 금방 알수 있습니다


7. 마무리
그렇다면 어떻게 해야 이러한 피해를 최소화 할 수 있을까요? 나름데로 생각해 보았습니다

파라미터와 파라미터값에 대해 알아볼 수 없도록 최소한의 의미만 부여한다!!
   역시나 파라미터가 중요합니다 누구나 알아볼 수 있도록 하는 의미보다는 나름데로
   Naming rule을 정한다든지 암호화 하든지 하는것도 바람직 할 수 있다고 봅니다

파라미터값이 유효한 값인지 체크해 봅니다
   파라미터값을 최대값, 최소값으로 제한하고 꼭 있어야 하는 문자열이나 꼭 없어도 되는 문자열등을 체크합니다
   파라미터값 길이또한 최대, 최소값 제한을 둡니다

중요한 파라미터 값은 (파일명같은..)  절대로 받지 않고 다른 로직으로 생각해 봅시다
   파일명같은 중요한 값은 데이터베이스에 입력해 놓고 키값을 파라미터로 전달받아 파일명을 조회한 후 사용합니다

웹프로그램의 취약점 분석 툴들이 찾아보면 몇개 있습니다(대부분 유로더군요) 이를 이용하여 테스트해 봅니다

두서없이 생각나는데로 적었습니다
그나마 JSP는 다른 PHP나 ASP에 비해 상당히 보안에 안정적입니다 JDBC 마다 틀리겠지만요
해결 방법이 생각해 보면 더 많을겁니다
물론 서버단에서 막는 방법이 제일 좋겠지만요 몇가지 사례를 적어놓았으니 그다음은 응용하기에 달렸지요
Posted by skensita
Hacking & Security/Web2008. 6. 29. 03:45

Web Hacking 1탄 SQL Injection
Web Hacking 2탄 파일조작
Web Hacking 3탄 구멍난 자바스크립트

1. 시작하기
가끔 뉴스나 방송에서 xx 사이트 해킹 당했다 라고들 많이 들어보았을 겁니다
이런 뉴스를 접할때 대체 어떻게 해서 해킹이 당하는걸까? 라고들 많이 생각해 보았을 겁니다
어떻게 해킹이 일어나는지 알아야 방어도 가능하기 때문에 이번 강좌는 웹해킹에 대해 알아볼 것입니다

첫번재 시간으로 SQL Injection을 배울 것이며 그다음 두번째에는 파일조작으로 인한 해킹을,
그리고 마지막 시간에는 자바스크립트 조작에 대해 알아보겠습니다.

사용자 삽입 이미지

도표에서도 알수 있듯이 SQL Injection으로 인한 해킹이 가장 간단하고 쉽기 때문에
이를 가장먼저 알아보겠습니다

SQL Injection 이란 서버나 OS의 구멍을 이용한 해킹방법이 아닌 웹 어플리케이션 자체의 버그를
이용한 새로운 형태의 웹해킹 방법입니다
 
정의를 하자면

SQL Injection은 웹페이지를 통해 입력된 파라미터값을 이용하여 쿼리를 재구성하는 방법이다


라고 할수 있습니다

즉 많은 웹페이지들은 사용자나 프로그램이 생성한 파라미터값을 이용해 쿼리를 만들고 실행하는데,
이때 정상적인 값이 아닌 파라미터값이 입력될 때 비정상적인 쿼리가 실행되며, 따라서
원하지 않는 결과가 나올수 있다는 것입니다
이해를 위해 바로 코드로 들어가 봅시다
아래 코드는 사용자가 입력한 로그인 아이디와 비밀번호로 로그인을 시도하는 로직입니다

// 입력받은 사용자 아이디와 비밀번호
String param1 = request.getParameter("user_id");
String param2 = request.getParameter("user_pw");

rs = stmt.executeQuery("SELECT count(*) FROM user_t WHERE userid = '"+param1+"' AND userpw = '"+param2+"'");
rs.next();
if (rs.getInt(1) == 1) {
   // 로그인 성공로직
} else {
   // 로그인 실패로직
}


무엇이 잘못되었을 까요?
코드상으로 보면 실행하는데 전혀 이상이 없는 코드이지만 쫌 아는사람들에게는 비밀번호 없이
아이디만 안다면 로그인을 성공할 수 있는 로직입니다

사용자 삽입 이미지
 
아아디가 goodbug 이고 비밀번호가 1111 인 계정이 있다고 합시다 ^^;
입력란에 googbug / 1111 을 입력하니 정상적으로 로그인이 true가 되었습니다
하지만 만약 다음과 같이 사용자가 입력한다면 어떻게 될까요?

사용자 삽입 이미지

로그인이 됩니다!
비밀번호에 상관없이 로그인이 되는군요!!
하다 더 보도록 하지요

사용자 삽입 이미지

위의 소스는 MySQL을 사용하고 있습니다
MySQL의 라인 주석처리는 # 입니다 아시죠?

즉 뒷부분 password를 체크하는 로직은 주석처리되어 버린겁니다 뜨아~

같은 의미이지만 아래처럼 여러가지 섞어서도 가능합니다.

사용자 삽입 이미지

이게 만약 관리자 아이디였다면 문제는 더 심각해 집니다

이처럼 사용자의 고의로 인한 SQL을 조작하여 웹 어플리케이션 자체를 공격하는것이 SQL Injection 입니다

그렇다면 위의 소스를 어떻게 변경하는게 좋을까요?

// 입력받은 사용자 아이디와 비밀번호
String param1 = request.getParameter("user_id");
String param2 = request.getParameter("user_pw");

//validate 라는 함수는 아이디와 비밀번호 생성시 생성 로직에 맞게 되었는지 체크하는 함수
//만약 특수문자나 아이디 생성 규칙에 맞지 않는 값이면 exception을 throw 한다
validateID(param1); 
validatePW(param2);

pstmt = conn.prepareStatement("SELECT userid, userpw FROM user_t WHERE userid = ?");
pstmt.setString(1, param1);
rs = pstmt.executeQuery();
if (rs.next()) {
    // 문자를 직접 비교한다 (대소문자 구분)
    if (rs.getString(1).equals(param1) && rs.getString(2).equals(param2)) {
         // 로그인 성공로직
    } else {
        // 로그인 비밀번호 혹은 아이디 오류 로직
    }
} else {
    //로그인 부재 아이디 오류 로직
}

위처럼 하면 어느정도 되겠네요 ^^


2. SQL Injection 패턴

그럼 SQL Injection에 사용될만한 문자열에는 어떤것이 있을까요?

아래 문자들은 해당 데이터베이스에따라 달라질 수 있습니다

문자

설명

' 문자 데이터 구분기호
; 쿼리 구분 기호
--, # 해당라인 주석 구분 기호
/* */ /* 와 */ 사이 구문 주석

일반적으로 알려진 몇가지 패턴입니다

' or 1=1--
" or 1=1--
or 1=1--
' or 'a'='a
" or "a"="a
') or ('a'='a
' or password like '%

이러한 패턴들로 타겟 데이터베이스가 오라클인지 MySQL인데 혹은 M$SQL인지 확인할 필요가 있을겁니다. 왜냐하면 그것에 따라 다양한 공격 방법이 생기기 때문입니다
그럼 이러한 구분은 그럼어떻게 할까요?

SQL Injection을 이용하여 다음값이 true인지 false인지 확인합니다
AND 'abcd' = 'ab' + 'cd'
true 이면 오라클은 아닐겁니다 오라클은 ||을 문자열 concat 으로 사용하지요

AND 'abcd' = 'ab' || 'cd'
true 이면 오라클입니다
rownum 도 구분할수 있는 좋은 예입니다

MySQl은 라인 주석이 #입니다 다른 대부분 데이터베이스는 --를 사용하지요
#을 SQL Injection 하였을때 위의 예처럼 에러가 발생하지 않으면 MySQL입니다
또 limit 등도 도움이 될겁니다

이처럼 해당 데이터베이스가 고유하게 사용하는 key들을 SQL Injection하여 구분할 수 있습니다


3. UNION SQL Injection

위와같이 WHERE절에 SQL Injection을 사용하여 조건절을 무력화 시키는 방법도 있지만
UNION SQL Injection은 원하는 정보도 뽑아볼 수 있습니다 ^^V

코드로 바로 봅시다
아래 코드는 게시물번호를 파라미터로 받아 해당 게시물이 존재하면 글번호와 글제목, 글내용을 조회하는 코드입니다

String param1 = request.getParameter("boardno");

rs = stmt.executeQuery("SELECT boardno, boardtitle, boardcontent FROM board_t WHERE boardno = '"+param1+"'");
if (rs.next()) {
    out.println(rs.getString(1)+"<br>");
    out.println(rs.getString(2)+"<br>");
    out.println(rs.getString(3)+"<br>");
}

무엇이 잘못되었을까요?

사용자 삽입 이미지
글번호가 1134896409234 인 게시물은 다음과 같이 URL이 요청되어 조회가 될겁니다
요청 URL

/read.jsp?bno=1134896409234


하지만 위와 같이 추가적으로 UNION SQL Injection이 들어갈 수 있습니다

/read.jsp?bno=1134896409234' UNION SELECT '1', userid, userpw FROM user_t WHERE userid = 'goodbug'  ORDER BY boardno ASC #


그러면 goodbug라는 아이디의 비밀번호가 그만 조회되어 버립니다!!
그럼 user_t 라는 테이블과 ,userid, userpw라는 컬럼명들은 어떻게 알수 있을까요?
오라클 이라면 다음과 같이 알아낼 수 있습니다

/read.jsp?bno=1134896409234' UNION SELECT '1', tname, '' FROM user_tables WHERE like '%user%' ORDER BY boardno ASC --


테이블 명을 알아냈다면 이제 컬럼명을 알아봐야겠죠
오라클이라면 user_tab_columns view를 통해 알아볼 수 있습니다
SELECT * FROM user_tab_columns WHERE table_name = 'user_t'

물론 한번에 알아낼수 없으며 많은 시행착오를 겪어야 하는것은 필수입니다
그래서 제로보드나 Unicorn 같은 공개 게시판인 경우는 타겟이 되기 쉽상입니다

MySQl이나 Oracle JDBC에서는 다행히도 ; 문자를 이상 캐릭터로 보고 에러를 반환합니다
하지만 그렇지 않은 JDBC가 있따면 큰일입니다 아래와 같은 코드가 가능하기 때문이지요

/read.asp?bno=1134896409234';DELETE FROM user_t

/read.asp?bno=1134896409234';UPDATE user_t SET userpw = '1111'


PHP나 ASP인 경우에는 가능한 쿼리 입니다
이제 대강 SQL Injection에 대해 감이 잡히시나요?


4. 에러 메세지를 통한 정보수집

admin_login 이라는 관리자 테이블과 login_name이라는 컬럼을 알아 냈다고 한다면..
M$SQL인 경우를 예를 들겠습니다

/read.asp?id=10 UNION SELECT TOP 1 login_name FROM admin_login--


Output:

Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value 'goodbug' to a column of data type int.
/read.asp, line 5


라는 메세지가 나옵니다
여기서 무순 정보를 알수 있을까요? 바로 goodbug 라는 관리자 아이디가 있다는 것을 알았습니다
그럼 여참에 비밀번호까지 알아봅시다

/read.asp?id=10 UNION SELECT TOP 1 password FROM admin_login where login_name='goodbug'--


Output:

Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value '1111' to a column of data type int.
/read.asp, line 5


즉 nvarchar 타입의 컬럼을 int형으로 convert를 유도하면서 해당값을 에러메세지로부터 취득할 수 있습니다
가능하면 에러 메세지는 일반 유저에게 뿌리지 말아야 겠지요?


5. 그렇다면 막아보자 SQL Injection

해커라고 컴퓨터 한두번 두둘겨서 어느 한 사이트를 뚝딱 해킹하지는 못합니다
웹 해킹을 하기 위해서는 보통 짧으면 일주일에서 길면 몇달까지 해커는 치밀한 준비를 한다고 합니다
웹페이지들이 전달하는 모든 파라미터를 조사하고 반복되는 에러 화면을 보면서
여러 패턴별로 치밀하게 조사후 시행을 한다고 하네요
그렇다면 어떻게 이들로부터 웹 어플리케이션을 보호할 수 있을까요?
다음과 같은 원칙을 지킨다면 이러한 SQL Injection을 사전에 방지할 수 있습니다

프로그래머의 적극적인 의지가 있어야 합니다!
   -. 여러가지 신경써야 할곳도 많고 입력값에 대한 체크로직 또한 늘어날 것입니다
       많은 귀차니즘이 생길겁니다
       머 누가 장난치겠어? 라는 생각이 많은 빵꾸를 만듭니다
 사용자가 직접 입력하는 파라미터는 절대 신뢰하지 않아야 합니다!
   -.  입력값은 javascript 뿐만 아니라 서버측에서도 체크를 해야 합니다
        숫자만 받은 입력칸이면 반드시 javascript 체크와 동시에 서버측에서도 숫자만 입력되었는지 체크야 합니다
   -. 필요하다면 특수문자는 필터링해 버립시다 ( ‘ “ / \ : ; Space < > )
   -. 또 가능하다면 SQL 명령도 필터링 해버립시다 (UNION, SELECT, DELETE, INSERT, UPDATE, DROP..)
사용자 입력을 받아 SQL을 작성하는 부분은 반드시 PreparedStatement를 사용하여 바인딩 처리 합니다!!
   -. PreparedStatement는 SQL Injection을 원천적으로 봉쇄합니다
   -. 꼬~옥 필요한곳만 Statement를 사용합니다
웹에서 사용하는 유저는 OS계정 뿐만 아니라 데이터베이스 계정또한 가능한 권한을 낮춥니다!!
에러 메세지를 노출하지 않습니다!!
   -. 에러 메세지는 해커들에게 많은 정보를 제공해 줍니다
       가능하면 유저에게 알리지 말고 은밀하게 보관해야 합니다
동적 SQL은 가능하면 생성하지 않는다!!
   -. 가능한 동적 SQL은 지양하며, 비지니스로직상 동적 SQL이 어쩔수 없다면 파라미터 검사를 충분히 해야한다
많은분들이 아시는 내용이지만 처음보시는분에겐 많은 도움이 되실겁니다 ^^
Posted by skensita