참고: 이 게시물은 브랜트 버넷 의 CenterEdge 소프트웨어는 놀이공원, 레저 및 엔터테인먼트 산업을 위한 POS 및 특수 소프트웨어를 개발하는 회사입니다.
개요
N1QL은 놀랍도록 강력한 새 도구로, 학습 곡선이 훨씬 얕으면서도 더 많은 개발자에게 NoSQL 데이터베이스를 제공하는 데 도움이 될 것입니다. 이를 통해 개발자는 그 어느 때보다 빠르고 쉽게 고성능의 강력한 고급 애플리케이션을 만들 수 있습니다. 하지만 새로운 기술이 도입되면 해커가 공격할 수 있는 영역이 본질적으로 늘어납니다.
SQL 인젝션은 SQL 기반 애플리케이션에서 흔히 발견되는 잘 알려진 보안 결함으로, 수년에 걸쳐 잘 문서화되어 있습니다. 그렇다면 보안 측면에서 N1QL은 SQL과 어떻게 비교될까요? N1QL도 인젝션 공격에 취약할까요? 그렇다면 개발자는 이러한 함정을 어떻게 피할 수 있을까요?
SQL 주입에 대한 검토
SQL 인젝션은 최종 사용자가 애플리케이션에서 실행 중인 SQL 쿼리에 악성 코드를 추가할 수 있는 코드 인젝션의 한 형태입니다. 간단한 예는 이 쿼리입니다:
1 |
var 쿼리 = "SELECT * FROM users WHERE name ='" + 사용자 이름 + "'"; |
개발자가 애플리케이션을 보호하기 위한 조치를 취하지 않으면 사용자가 사용자 이름 필드에 악성 텍스트를 포함할 수 있습니다. 예를 들어
1 |
선택 * FROM 사용자 어디 이름 = '' 또는 '1'='1'; |
이 쿼리는 사용자가 "' 또는 '1'='1"을 입력한 결과입니다. 이제 이 쿼리는 시스템의 모든 사용자를 악의적인 사용자에게 반환합니다.
더 강력한 쿼리 변경을 허용하기 위해 악의적인 사용자는 댓글을 사용하여 개발자의 쿼리 일부를 제외할 수도 있습니다. 이전 예제를 확장합니다:
1 |
var 쿼리 = "SELECT * FROM users WHERE name = '" + 사용자 이름 + "' AND group = 5"; |
주입할 수 있습니다:
1 |
선택 * FROM 사용자 어디 이름 = '' 또는 1=1 --' AND 그룹 = 5 |
SQL은 "-" 이후의 모든 텍스트를 무시하므로 이제 그룹이 5여야 한다는 제한이 쿼리에서 제거됩니다. 다시 한 번 시스템의 모든 사용자가 악의적인 사용자에게 반환됩니다.
사용자는 댓글을 배치 명령과 결합하여 데이터베이스의 데이터를 변경할 수도 있습니다:
1 |
선택 * FROM 사용자 어디 이름 = '어쩌고저쩌고'; DROP 표 감사 로그 /*'AND 그룹 = 5 |
이것이 N1QL에 어떤 영향을 미치나요?
몇 가지 실험 결과, N1QL은 실제로 기존 SQL보다 인젝션 공격에 더 강한 것으로 나타났습니다. 예를 들어, N1QL은 현재 여러 명령을 일괄 처리하는 기능을 지원하지 않습니다. 따라서 SQL에서 데이터를 악의적으로 수정할 수 있는 일괄 처리 공격에 대응할 수 있는 방법이 없습니다. 예를 들어, SQL에서 작동할 수 있는 이러한 인젝션 공격은 잘못된 구문으로 거부됩니다:
1 2 |
선택 * FROM 사용자 어디 이름 = ''; 업데이트 사용자 SET 비밀번호 = '1234'; 선택 * FROM 사용자 어디 이름 = '' |
그러나 악의적인 사용자가 공격을 수행할 수 있는 옵션은 여전히 존재합니다. 이러한 공격을 보호하지 않으면 보안 데이터에 대한 액세스를 허용하거나 변경된 쿼리가 Couchbase 클러스터에서 너무 많은 처리 능력을 사용하기 때문에 서비스 거부를 초래할 수 있습니다.
또한 일괄 처리와 같은 일부 기능은 향후 N1QL 버전에 확실히 추가될 수 있습니다. 따라서 개발자가 쿼리에서 사용자 입력을 보호하지 않으면 향후 데이터 수정이 문제가 될 수 있습니다.
절 수정 사항
SQL 주입과 마찬가지로 N1QL 주입에서는 WHERE 절을 변경할 수 있습니다. 예를 들어
1 |
var 쿼리 = "SELECT * FROM users WHERE name = '" + 사용자 이름 + "'"; |
될 수 있습니다:
1 2 |
선택 * FROM 사용자 어디 이름 = '' 또는 '1'='1' |
AND 및 OR 연산자에 대한 연산자 우선순위 규칙으로 인해 이 공격은 추가 절이 있는 경우에도 작동할 수 있습니다:
1 |
var 쿼리 = "SELECT * FROM users WHERE name LIKE '%" 사용자 이름 + "%' AND 그룹 = 5 |
될 때 여전히 모든 사용자를 반환합니다:
1 |
선택 * FROM 사용자 어디 이름 좋아요 '%' 또는 ''='%' AND 그룹 = 5 |
N1QL 댓글
N1QL의 주석 시스템은 "-"를 사용하여 나머지 줄을 주석 처리하는 대신 C 스타일 주석 블록(/* 주석 */)을 사용합니다. 이는 일부 고급 인젝션 공격으로부터 N1QL을 보호합니다. N1QL은 닫는 주석 */이 필요하므로 공격자는 구문 오류를 일으키지 않고는 쿼리의 일부를 주석 처리할 수 없습니다.
그러나 이는 개발자가 쿼리에 댓글을 남기지 않는 경우에만 해당됩니다. 쿼리 텍스트에 댓글이 있는 경우 사용자는 이제 닫는 댓글 블록을 유용하게 사용할 수 있습니다:
1 |
var 쿼리 = "SELECT * FROM users WHERE name = '" + 사용자 이름 + "' AND group = 5 /* 그룹 5만 반환 */"; |
"OR 1=1 /*" 삽입 가능:
1 |
선택 * FROM 사용자 어디 이름 = '' 또는 1=1 /*' AND group = 5 /* 그룹 5만 반환 */ |
SQL 예제에서와 같이 이제 쿼리에서 그룹 제한이 제거됩니다.
N1QL 식별자 주입
Couchbase의 스키마 없는 문서 모델은 실제로 흥미로운 새로운 공격 영역을 만들어냅니다. SQL로 작업할 때 쿼리의 WHERE 또는 ORDER BY 절을 제외한 다른 곳에 사용자 입력을 포함하는 경우는 매우 드뭅니다. 테이블과 열 이름은 잘 알려져 있고 변경되지 않기 때문입니다.
그러나 카우치베이스 문서에 대한 스키마가 없다는 것은 개발자가 사용자가 문서에서 선택하는 필드를 제어할 수 있도록 허용하고 싶다는 유혹을 받을 수 있음을 의미합니다.
1 |
var 쿼리 = "SELECT " + 필드 + " FROM users WHERE type = 'user'"; |
주입 후
1 |
선택 이름, (선택 * FROM 사용자 as users2 사용 키 사용자.사용자 비밀번호 문서 ID) as 비밀번호 문서 FROM 사용자 어디 유형 = 'user' |
이제 공격자는 개발자가 지정한 사용자 문서에 없는 관련 비밀번호 문서의 데이터에 액세스할 수 있습니다.
애플리케이션을 보호하는 방법
다행히도 N1QL 인젝션 공격으로부터 애플리케이션을 보호하는 것은 SQL 인젝션 공격으로부터 애플리케이션을 보호하는 것만큼이나 쉽습니다. 다음은 보안을 쉽게 할 수 있는 몇 가지 지침입니다. 예제는 C#로 되어 있지만 개념은 다른 언어에도 똑같이 적용됩니다.
- 모범 사례: 사용자 입력을 쿼리에 직접 삽입하는 대신 이름 또는 위치 매개변수를 사용하여 보호하세요. 이렇게 하면 사용자 입력이 쿼리에 직접 추가되지 않으므로 모든 인젝션 공격에 대해 100% 보호 기능을 제공합니다.
1var 쿼리 = "SELECT * FROM users WHERE userName = '" + 사용자 이름 + "'";
그래야 합니다:
12var 쿼리 = new 쿼리 요청("SELECT * FROM users WHERE userName = $userName");쿼리.추가 이름 매개변수("$사용자 이름", 사용자 이름); - 모범 사례 #2: 쿼리 텍스트를 생성하는 언어 구조(예: .Net POCO 또는 Java POJO)는 강력하게 유형화된 언어 구조를 사용하세요. 예를 들어, Linq2Couchbase 라이브러리(https://github.com/couchbaselabs/Linq2Couchbase)는 LINQ 쿼리에서 N1QL을 생성할 때 적절한 이스케이프를 처리합니다.
- 쿼리에 사용자 입력 문자열을 삽입하는 경우 항상 따옴표를 이스케이프 처리하세요. 작은따옴표(')의 모든 인스턴스를 작은따옴표(") 두 개로 바꿉니다.
1var 쿼리 = "SELECT * FROM users WHERE userName = '" + 사용자 이름 + "'";
그래야 합니다:
1var 쿼리 = "SELECT * FROM users WHERE userName = '" + 사용자 이름.교체("'", "''") + "'"; - 쿼리에 사용자 입력 식별자를 삽입할 때는 항상 식별자를 틱으로 이스케이프 처리(
). 그런 다음 입력에서 틱의 인스턴스를 두 개의 틱으로 바꿉니다(
`). 식별자에 해당하는 명명된 매개변수가 없으므로 식별자는 이스케이프하는 것이 가장 좋은 해결책입니다.
1var 쿼리 = "SELECT " + 필드 + " FROM users WHERE group = 5";
그래야 합니다:
1var 쿼리 = "SELECT `" + 필드.교체("`", "``") + "` FROM users WHERE group = 5"; - 다른 규칙을 구현하면 댓글 기반 공격으로부터도 보호받을 수 있습니다. 그러나 개발자가 다른 규칙을 잊어버리는 경우를 대비하여 사용자 입력이 포함된 쿼리의 댓글에 대한 보조 정책을 사용하면 추가적인 보호 기능을 제공할 수 있습니다. 대신 쿼리 자체 대신 애플리케이션 코드에 댓글을 넣으면 됩니다.
1var 쿼리 = "SELECT * FROM users WHERE userName = '" + 사용자 이름 + "' AND group = 5 /* 그룹 5만 반환 */";
그래야 합니다:
1var 쿼리 = "SELECT * FROM users WHERE userName = '" + 사용자 이름.교체("'", "''") + "' AND 그룹 = 5"; // 그룹 5만 반환
이러한 공격의 예와 C#의 보호 방법을 보려면 이 깃허브 리포지토리를 참조하세요: https://github.com/brantburnett/N1QlInjection. 테스트를 실행하려면 로컬에 Couchbase가 설치되어 있고 맥주 샘플이 설치되어 있어야 합니다.
결론
N1QL은 인젝션 공격에 취약하지만, 이 취약점은 SQL의 잘 알려진 취약점보다 더 나쁘지 않습니다. 또한 개발자가 인젝션 공격으로부터 보호하는 것은 매우 쉽습니다. 따라서 N1QL은 Couchbase NoSQL 데이터베이스를 사용하여 안전한 애플리케이션을 개발하기 위한 훌륭한 플랫폼을 제공합니다.