CVE-2022-28347: Potential SQLi via QuerySet.explain() on PostgreSQL

CVE-2022-28347: Potential SQLi via QuerySet.explain() on PostgreSQL
Brighton, England

CVE-2022-28347, Potential SQL injection in Django QuerySet explain() Analysis
> https://github.com/advisories/GHSA-w24h-v9qh-8gxj

이 취약점은 PostgreSQL 환경에서 Django QuerySet의 explain 기능을 수행할 때 발생 가능한 SQL Injection 취약점이다. QuerySet 오브젝트의 explain() 메소드를 수행하면 EXPLAIN 명령어를 사용할 수 있다. 이 명령어를 사용할 때 MySQL과 PostgreSQL에서는 특별히 EXPLAIN 명령어에 옵션을 지정할 수 있다. 이 기능을 구현한 explain() 메소드에서 발생했던 SQL Injection 보안 취약점(CVE-2022-28347)을 취약점을 분석해보고자 한다.

  1. QuerySet QuerySet 오브젝트의 explain() 메소드 수행하면 self.query.explain() 메소드를 수행한다. 이 self.query attributedjango.db.models.sql.query.Query 클래스로, 결국 djagno.db.models.sql.query.Query.explain() 메소드를 수행한다.
  2. Query Query 클래스의 explain() 메소드는 get_compiler() 메소드를 통해 compiler를 가져오고 사용하는 DB에 맞추어 django.db.models.sql.compiler.SQLCompiler를 상속한 클래스의 explain_query() 메소드를 실행시켜준다.
  3. SQLCompiler SQLCompiler 클래스 내부의 explain_query() 메소드에서는 동일 클래스의 execute_sql()를 실행한다. 실제로 SQL Query구문을 실행하는 메소드이며, 이 메소드 안에서는 explain_info를 검사하는데 이것은 2번 과정에서 Query 클래스의 explain() 메소드에서 namedtuple()로 설정된 값이다.
  4. SQLCompiler execute_sql() 메소드에서는 Query 오브젝트에 설정된 explain_info attribute가 있다면, 동일 클래스의 explain_query_prefix()를 실행한다.
  5. BaseDatabaseOperatios의 explain_query_prefix() 메소드는 크게 하는 역할이 없다. 인자로 전달된 format, options의 값을 검사하는데 format의 경우 기본적으로 설정되어 있지 않아 format 값을 지정할 수 없고, options는 그냥 값이 존재한다면 ValueError를 raise한다.
  6. postgresqlDatabaseOptions는 explain_query_prefix() 메소드에 유의미한 코드가 있다. (아래 참고) 우선 format 값이 지정된 format인지 확인하고, options를 설정한다. 이때 options로 넘겨진 key 값을 검사하지 않고 넘기기 때문에 options에 SQL 구문을 넣으면 SQL Injection이 가능하다.
postgresql DatabaseOptions 클래스 내부 코드