여느 때처럼 ISMS 취약점 조치를 위해 열심히 일하던 중... 한 개발자분께서 SQL Injection 조치가 어렵다고 연락 왔다.
항상 느끼는 것이지만 개발자들은 일이 많아서 보안 쪽에서 주는 업무를 버거워한다. 사실 그 업무가 힘든 것이 아니더라도.. 개발자와 현업담당자와 협의하는 것. 보안담당자가 평생 해결해야 할 숙제이다.
SQL Injection 취약점의 경우, 사용자로부터 받는 입력값에 PreparedStatement을 적용하여 해결해야 하는데 이번 건의 경우 프로젝트의 소스코드를 꽤 변경해야 하는 작업이었다. (그래서 개발자가 컨설턴트 쪽에 해달라고 뗑깡 부림ㄱ-)
사실 우리 회사 프로젝트(소스코드)를 외부에 공개하는 것도 그렇고 그 프로젝트의 구조를 컨설턴트로 하여금 분석시키는 것도 말이 안된다고 생각한다. 이번 기회에 내가 소스코드 분석하고, 수정까지 해서 조치하는 경험을 했다. 소스분석하는 게 재밌기도 하고, 어쨌든 조치는 해야 되니까...
본 글은 해당 업무를 하면서 공부한 내용을 정리한 것이다.
당연히 회사 내 자산은 노출되지 않도록 소스코드는 임의로 변형하였다.
취약점 설명
1. 상세 정보
1) 시스템: 대외 서비스 관리자 페이지
2) 취약점 내용: SQL Injection(Blind)
3) 취약 식별 기능: 관리자 페이지 로그인 시 전달되는 파라미터 값 중 하나를 조작하여 데이터베이스의 정보 조회 가능
4) 상세 내용: 로그인 시 전달되는 아이디 파라미터 값을 활용해 필요한 정보를 불러올 때 공격일 발생할 수 있다.
2. 공격 내용
구분(Blind) | 삽입 구문(변형) |
참 | TEST' and (select case when 1=1 then 1/0 else 2 end from dual)=1 and '1'='1 |
거짓 | TEST' and (select case when 1=2 then 1/0 else 2 end from dual)=1 and '1'='1 |
TEST는 실제 DB 내에 존재하는 계정이어야만 한다.
- select case문
- 조건에 따라 분기하여 연산을 수행하는 구문이다. 주로 기존에 존재하는 열을 활용해 새로운 열을 생성할 때 쓰인다. 본 공격 구문에서는 에러를 기반으로 한 SQL Injection을 수행하기 위해 사용하였다.
- when 1=1 (참) → 1/0 → Divided Zero 에러 발생
- when 1=2 (거짓) → 2 → 에러 미발생
※ dual 테이블이란?
오라클에서 자체 제공되는 테이블로 간단하게 함수를 계산해서 결과를 확인하고 싶을 때 활용된다.
별도 테이블 생성없이 가상으로 사용할 수도 있다.
조치 방법
누구에게나 잘 알려져있는 PreparedStatement 함수를 적용하면 된다.
우리 회사도 그렇고 대부분 Java로 개발되어 있는 프로젝트가 많은데, 이번 취약점은 ASP.NET 으로 구성되어 있어 C#을 사용하고 있었다...
해당 프로젝트는 DB에 쿼리를 질의할 때, Stringbuilder와 OleDbDataAdapter 클래스를 활용하고 있었고, C#에는 Java처럼 친절하게 PreparedStatement 관련 API도 제공하고 있지 않는다.
기존 프로젝트의 구조를 해치지 않으면서 짤 수 있는 방법이 필요했다...
오랜 구글링과 MS 공식문서를 뒤져가면 방법을 찾았다.
핵심은 OleDbCommand 클래스이다.
해당 클래스를 활용해 파라미터 값을 그대로 삽입하지 않고, 하나의 파싱(parsing)된 값으로 받아들일 것이다.
조치 내용
1. SQL 구문 생성 부분
1) 기존 소스
public static string Method(string id){
StringBuilder sql = new StringBuilder();
sql.Append(@"select * from TABLE WHERE ID =" + id)
return sql.ToString();
}
파라미터 값으로 받는 id가 변수로써 활용되지 않게 처리해주어야 한다.
2) 수정 소스
public static string Method_new(){
StringBuilder sql = new StringBuilder();
sql.Append(@"select * from TABLE WHERE ID = ?")
return sql.ToString();
}
함수 자체적으로 파라미터 값을 처리하지 않기때문에 Method_new()는 매개변수가 없다.
Append() 에서는 기존에 인자로 받던 id 대신 ? 로 처리해준다. (이 부분이 PreparedStatement 로 처리되는 것)
2. SQL 구문 질의 부분 (DB 상호작용)
1) 기존 소스
public DataSet query(string sql, string table) {
OleDbDataAdapter adapter = new OleDbDataAdapter(sql, this.connection);
DataSet dataset = new DataSet();
adapter.Fill(dataset, table);
return dataset;
}
실제 DB에 쿼리를 질의한 내용을 바탕으로 return된 dataset은 로그인한 아이디에 매칭되는 필요한 정보를 담고있다.
OleDbCommand 클래스를 활용하여 소스를 수정하면 다음과 같이 된다.
2) 수정 소스
public DataSet query_new(string sql, string table, string val) {
OleDbDataAdapter adapter = new OleDbDataAdapter(sql, this.connection);
// command 객체를 활용하여 PreparedStatement 적용
OleDbCommand command = new OleDbCommand(sql, this.connection);
command.Parameters.Add(val, OleDbtype.VarChar, 15);
// 기존에 사용하던 adapter 객체에 파라미터값 적용된 구문 초기화
adapter.SelectCommand = command;
DataSet dataset = new DataSet();
adapter.Fill(dataset, table);
return dataset;
}
실제로 내가 짠 함수(_new)는 기존 소스를 수정하지 않고, 그대로 추가하였다.
그래야 실제 함수를 호출하는 부분(백엔드)에서 호출할 함수명만 변경해주면 되기때문이다.
원래는 오버로딩을 통해 호출하는 쪽도 수정없이 반영될 수 있게 하려고 했는데 개발자가 그렇게는 하지 말라고 했다.
총평
피, 땀, 눈물을 흘려가며 만든 내 소스코드가 결국에는 적용이 되지 않았다. 😒
쿼리를 직접 날리는 부분에서 정의되지 않은 클래스나 객체를 활용한 것 같다. 내가 개발자가 아니라서 디버깅도 못하고 해당 서비스는 개발환경이 없어서 마음대로 테스트도 못한다. 이번에 클라우드 전환 작업 중에 있는데, 테스트용 클라우드 서버에서 내가 직접 소스 짜고, 테스트 하겠다고 팀장님께 말씀드릴 예정이다...
언제나 남의 소스를 분석하고 취약점에 맞게 수정하는 것은 재밌다. (새로운 프로젝트 짜라고 하는 건 짜증난다.ㅋㅋ)
내가 이 작업을 좋아해서 그렇지, 싫어했다면 끔찍했을 것 같다. 협조해주지 않는 개발자는... 언제나 스트레스다.
작년에도 똑같이 비협조적인 개발자 덕분(?)에 세션타임아웃 취약점도 조치해보았다. 조만간 기회가 되면 포스팅해야겠다.
참고 사이트:
'ISMS-P > 취약점 진단' 카테고리의 다른 글
Windows 화면보호기 진단 스크립트 분석 (0) | 2023.05.23 |
---|