개념정리
○ SQL Injection
SQL Injection은 웹 어플리케이션에서 입력 데이터의 유효성 검증을 하지 않아서 개발자가 의도하지 않은 동적 쿼리를 생성하여 DB 정보를 열람하거나 조작할 수 있는 보안 취약점이다.
웹 어플리케이션은 유저의 행동(클릭, 입력 등)에 따라 DB에 있는 데이터를 서로 다르게 표시한다.
이를 위해 Query는 유저가 입력한 데이터를 포함하여 Dynamic 하게 변하므로 개발자가 의도하지 않은 정보를 열람할 수 있게 된다.
예를 들어 다음 쿼리문을 이용해서 로그인을 수행한다고 해보자.
select * from user where id = "{$id}" and pw = "{$pw}"
만약, admin 계정으로 로그인하고 싶은 상황이라면 admin 계정에 맞는 id와 pw를 입력해야 된다.
id와 pw가 일치하지 않는 경우에는 로그인이 실패한다.
select * from user where id = "admin" and pw = "1" or "1" = "1" -- "
하지만 공격자가 의도한 대로 위와 같이 쿼리문이 들어간다면?
where문을 보면 조건이 참이 된다는 것을 알 수 있다. 여기서 참이 되면 공격자는 성공적으로 admin 계정으로 로그인하고 원하는 정보를 얻을 수 있다.
이처럼 SQL injection
공격기법은 임의의 계정으로 로그인 할 수 있는 것 뿐만 아니라 DB와 관련된 여러가지 작업들에 영향을 줄 수 있는 공격기법이다.
개발자가 만든 웹/앱에서 DB 쿼리에 대한 검증을 제대로 수행하지않거나 prepare statement
같은 시큐어 함수를 사용하지 않는 경우에 공격자가 악의적인 쿼리를 이용해서 DB내의 정보를 탈취할 수 있는 취약점을 SQL injection이라고한다. 따라서 개발단계에서 보안 작업이 반드시 필요한 영역이라고 할 수 있다.
예제 문제를 풀어보면서 이해해보자.
문제분석 & 풀이
admin 계정으로 로그인하는 것이 목적이다.
코드를 분석해보자.
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
else:
userid = request.form.get('userid')
userpassword = request.form.get('userpassword')
res = query_db(f'select * from users where userid="{userid}" and userpassword="{userpassword}"')
if res:
userid = res[0]
if userid == 'admin':
return f'hello {userid} flag is {FLAG}'
return f'<script>alert("hello {userid}");history.go(-1);</script>'
return '<script>alert("wrong");history.go(-1);</script>'
select * from users where userid="{userid}" and userpassword="{userpassword}"
쿼리 실행- 쿼리 결과가
userid="admin"
이면 flag 출력
쿼리 결과로 userid가 admin이면 로그인할 수 있다.
입력에 대한 검증이 존재하지 않으므로 SQLi
로 쉽게 admin으로 로그인 할 수 있다.
select * from users where userid="123" and userpassword="1" or userid="admin"-- "
admin으로 로그인하기 위한 쿼리를 작성했다.
userid
는 아무 값으로 설정하고 userpassword
부분에 or
를 추가해서 뒤에 연결되는 의도한 조건이 실행되도록 했다.
다음으로 userid="admin"
을 추가해서 or
앞의 조건은 무시하고userid="admin"
인 조건만 만족하고 참이 되도록 했다.
마지막으로 주석(--[공백]
)을 추가해서 "
를 주석 처리했다. 참고로 DB가 sqlite
이므로 ||
나 #
를 주석으로 사용할 수 없다.
userid : 123
userpw : 1" or userid="admin"--[공백]
payload는 위와 같다.
로그인해보자.
대응방안
○ 입력값 검증 수행
사용자 입력이 DB 쿼리에 동적으로 영향을 주는 경우, 입력된 값이 개발자가 의도한 값인지 검증해야한다. 예를 들어 /*, –, ‘, “, ?, #, (, ), ;, @, =, *, +, union, select, drop, update, from, where, join, substr, user_tables, user_table_columns, information_schema, sysobject, table_schema, declare, dual
등처럼 SQL 함수들이나 일반사용자가 사용할만한 문자가 아닌경우에는 필터링을 해야된다.
○ 시큐어함수 사용
prepare statement
처럼 SQLi
를 방어할 수 있는 시큐어 함수를 사용한다. 사용자 입력값이 데이터베이스의 파라미터로 들어가기 전에 DBMS가 미리 컴파일 하여 실행하지 않고 대기하는데 그 후 사용자의 입력 값을 문자열로 인식하게 하여 공격 쿼리가 들어간다고 하더라도, 사용자의 입력은 이미 의미 없는 단순 문자열 이기 때문에 전체 쿼리문도 공격자의 의도대로 작동하지 않는다.
○ 에러처리
DB와 통신하는 과정에서 개발자가 의도하지 않은 입력 값이 들어가는 경우에 에러가 발생할 수 있다. 만약, DB에 대한 에러 처리가 미흡하다면 Error 메세지가 일반 서비스 환경에서 출력될텐데, 공격자는 이때 노출되는 데이터를 이용해서 Error Based SQLi를 수행할 수 있다. 따라서 개발자는 DB에 대한 에러 메세지가 일반 페이지에서 노출되지 않도록 확실한 에러 처리가 필요하다.
○ 낮은 권한으로 DB 운영
DB에 대한 보안이 취약하다면 쿼리를 통한 RCE나 접근 권한이 없는 데이터에 대한 접근이 가능하다.만약, DB가 root 권한으로 운영된다고 가정할 때, RCE 취약점이 발생한다면 굉장히 위험한 상황이 될 것이다. 따라서 DB 운영을 낮은 권한으로 수행한다면 이런 부분들에 대한 피해를 최소화할 수 있을 것이다.
○ 웹방화벽 사용
웹 방화벽은 소프트웨어 형, 하드웨어 형, 프록시 형 이렇게 세가지 종류로 나눌 수 있다. 소프트웨어 형은 서버 내에 직접 설치하는 방법이고, 하드웨어 형은 네트워크 상에서 서버 앞 단에 직접 하드웨어 장비로 구성하는 것이며 마지막으로 프록시 형은 DNS 서버 주소를 웹 방화벽으로 바꾸고 서버로 가는 트래픽이 웹 방화벽을 먼저 거치도록 하는 방법이다.