본문 바로가기
정보보안/모의해킹

모의해킹 강의(SQL Injection Part1) 11 대응방안~에필로그

by IT매니절 2024. 11. 7.

모의해킹 실무자가 알려주는, SQL Injection 공격 기법과 시큐어 코딩 : PART 1

강의주소 :  https://www.inflearn.com/course/sql-injection-secure-coding-1/dashboard

 

원인

입력값 검증 없이 구문을 조합할 경우, 변조된 sql 질의가 발생할 수 있다

 

대응방안
1) Prepared Statement 사용
2) 사용자 입력 값 타입에 따른 입력 값 검증 로직 구현
3) 길이제한

 

WAF라는 인라인 방식의 보안 솔루션을 많이 사용.

하지만 보안 솔루션 외의 다른 부분이 취약한 경우가 많다

 

 

Prepared Statement


Prepared Statement
구분분석 및 정규화 -> 컴파일 -> 쿼리 최적화 -> 캐시 -> 실행

 

기존: conn.createStatement().executeQuery(query);
=> 변경: conn.prepareStatement(query).setString(n, value);

PlaceHolder
쿼리에서 물음표로 표시되며 런타임 시 사용자 입력값과 치환된다
PlaceHolder를 제외한 쿼리가 미리 컴파일 된다
사용자 입력 값은 순수 문자로 처리된다 (쿼리가 되지 않는다)

입력값 바인딩 : setString, setInt, setLong, setFloat 등...

 

Prepared Statement를 사용하더라도, 컴파일 전에 변수를 셋팅하면 취약하다
컴파일 이후( conn.prepareStatement )에 변수를 바인딩하여야 한다

 

iBatis, myBatis의 경우
기존 : ... LIKE '%${keyword}%'
변경(안전) : ... LIKE '%#{keyword}%'

Hibenate
기존 : LCASE('")).append(vl.getVlaueAsString("keyword")).append("')
변경(안전) : sql.append((new StringBuilder(" LCASE(keyword) = LCASE(:keyword)").toString()).append("\n");

사용 불가능한 경우
: 사용자 입력값을 통해 테이블이나 컬럼명을 입력받는 경우

 

 

 

사용자 입력 값 타입에 따른 검증 로직
1) 숫자
JAVA : Pattern.matche("^[0-9]*$", 입력값);
PHP : is_numeric(입력값)
=> 숫자만 입력받도록 정규식이나 함수를 활용

2) 문자
JAVA : (mssal, oracle) keyword.replace("'", "''");

(mysql) keyword.replace("\"", "'\"\"");
PHP : real_escape_string(입력값);
=> JAVA의 경우 싱글쿼터'를 싱글쿼터 2개로 바꾸고, 역슬래시더블쿼터\"를 \"\"로 변경한다. 메타문자로 인식하지 않기 위해서.

3) 테이블, 컬럼
JAVA : Pattern.matche("^[0-9a-zA-Z-]*$", 입력값);
PHP : preg_match("/^[0-9a-zA-Z-]*$/", 입력값)

4) 키워드
JAVA : 값.toLowerCase().equals("asc") 또는 "desc" 일 때 쿼리에 ASC나 DESC 추가

 

취약한 코드

    $id = $_POST["id"];
    $pw = md5($_POST["pw"]);

    $query = "select * from member where id='{$id}' and pw='{$pw}'";

    $tmp = $db_conn -> query($query);
변경된 코드

    $id = $_POST["id"];
    $id = str_replace("'", "\'", $id);
    $pw = md5($_POST["pw"]);

    $query = "select * from member where id='{$id}' and pw='{$pw}'";

 

 

그런데 이 코드 또한 부족하다

' or 1=1# 입력시

\' or 1=1# 입력시

 

맞는 치환

   
$id = $_POST["id"];
    $id = str_replace("\\'", "\\\\", $id);
    $id = str_replace("'", "\'", $id);
    $pw = md5($_POST["pw"]);
이를 함수로 변경함

   
$id = $_POST["id"];
    $id = $db_conn -> real_escape_string($id);
    $pw = md5($_POST["pw"]);
 

 

수동으로 하게 되면 휴먼에러가 일어날 수 있으니 공식에서 지원해주는 함수를 사용하는 것을 권장한다.

 

 

MYSQL 시큐어코딩

사용자 입력값 구분
1) 목록 : index.php 검색어(문자형), 검색 종류(컬럼형), 정렬(컬럼형, 키워드)
2) 상세보기 : view.php 게시글 번호(숫자형), 패스워드(문자형)
3) 상세보기 수정 : modify.php 게시글 번호 (숫자형)
4) 액션(삭제, 수정, 작성) : action.php

 

목록

검색어
$keyword = $db_conn->real_escape_string($_POST["keyword"]);

 

검색 종류($search_type, $sort_column)

if((!preg_match("/^[0-9a-zA-Z-]*$/", $search_type) && !empty($search_type))
  || (!preg_match("/^[0-9a-zA-Z-]*$/", $sort_column) && !empty($sort_column))){
    echo "<script>alert('정상적인 입력 값이 아닙니다.'); history.back(-1);</script>";
    exit();
  }

=> 정규식을 통해 허용된 값 이외에는 불가능. exit()로 구문종료해주는 것도 중요하다.

 

정렬

  $sort = strtoupper($_GET["sort"]);
  if(strpos($sort, "ASC")){
    $sort = "ASC";
  } else {
    $sort = "DESC";
  }

=> 서버사이드에서 하드코딩으로 정렬을 지정하여 인젝션 방지

 

키워드 인젝션 테스트. 싱글쿼터 ' 를 넣어도 에러 발생하지 않고 결과값도 나오지 않음.

keyword=te\'st 로 테스트해도 동일함.

keyword=te\'st+or+1=1%23

\'는 필터때문에 에러가 안날 수 있으니, 뒤에 공격구문을 덧붙여서 테스트까지 완료.

 

search_type=)

 

특수문자 등 입력시 응답 확인.

길이설정도 해주면 더 안전해진다. strlen함수 사용.

 

?sort_column=idx\n+and+1=1+title

?sort_column=idx&sort=\'
예외처리, 무반응 확인

인젝션이 되지 않더라도 에러 메시지는 그대로 노출하면 안된다.
$result = $db_conn->query($query);// or die($db_conn->error);
이런식으로 주석 등으로 die 부분 삭제.

 

 

상세보기

$password = $db_conn->real_escape_string($_POST["password"]);
if(!is_numeric($idx)){
 echo "<script>alert('정상적인 입력 값이 아닙니다.'); history.back(-1);</script>";
 exit();
}

=> 패스워드와 idx에 대해 검증

 

 

패스워드는 request method를 체인지하여 POST로 전송하여 확인한다.

 

수정페이지도 is_numeric을 이용해 idx 인젝션을 방지한다.

 

실습코드에는 common.php가 있어서 거기서 db관련 설정이($password 포함) 하드코딩되어 있다
이곳에 $salt = "hackTest"; 솔트값을 추가하고,
$password = md5($password.$salt); 이렇게 연결연산자 . 을 넣어서 합쳐서 사용하도록 한다.

쓰기, 수정, 삭제시 전부 md5로 변경시켜 사용해야 한다.

 

 

 

MSSQL

$keyword = str_replace("'", "''", $keyword);
키워드
sort_column, search_type, idx은 mysql과 동일한 적용

mssql은 die()함수가 없는데도 에러가 출력되므로, php.ini에서 별도 설정해주어야 한다

( display_errors = Off )

 

function sqli_secure($value){
 return str_replace("'", "''", $value);
}

이런식으로 공통부분에 함수를 만들어서 사용해도 편함

 

ORACLE의 경우 MSSQL과 다른 부분이 없다.

 

 

SQL INJECTION 공격의 공통점
- SQL 문법이 들어가고, 공격 페이로드가 길다
- 특수문자가 반드시 들어간다

이를 시큐어 코딩에 적용하여야 한다

 

 

 

 

에필로그

취약점 분석 > dbms 파악 > 인증우회 / 시스템 명령어 실행 > 데이터 조회 공격
>> 웹페이지 출력시 Union-Based 공격
>>> 웹페이지 출력X dbms 에러 출력시 Error-Based 공격
>>> 웹페이지 출력X dbms 에러 출력X Blind-Based 공격

UNION-Based
1) Order by 구문 확인
2) UNION 구문 확인
3) 출력 포지션 파악 후 공격

ERROR-Based
- 공격 페이로드 검증 후 공격

BLIND-Based
- 기준문자를 통해 Content-Based, Response-Based, Time-Based 등 순차, 이진, 비트단위 탐색 후 공격

* 메타데이터 목록화는 손으로 쓸 수 있을 정도로 암기하여 숙지할것

 

자동화도구의 양면성
편리하지만, 반대로 진단자를 바보로 만들 수 있음
프로세스대로 수동으로 1-2가지 결과를 확인한 후 자동화 도구를 이용하는 것을 권장

 

취약점은 있는 것 같은데, 자동화 도구를 돌렸는데 데이터가 나오지 않을때 => 원인을 파악할 수 있어야 함
커스터마이징을 통해 자동화도구를 직접 만드는 것 추천

추가로 필요한 실습
1) CASE WHEN 데이터 조회 실습
2) 숫자형, 테이블, 컬럼 입력값에 데이터조회 공격 실습
3) 숫자형, 테이블, 컬럼 입력값에 싱글쿼터 필터링이 존재할 때
4) 한글 데이터 추출