code

매개 변수가 없는 SQL 주입 방지

starcafe 2023. 4. 18. 23:08
반응형

매개 변수가 없는 SQL 주입 방지

코드에서 파라미터화된SQL 쿼리를 사용하는 것에 대해 여기서 또 다른 논의를 하고 있습니다.논의에는 두 가지 측면이 있습니다.저와 다른 사람들은 SQL 주입으로부터 보호하기 위해 항상 파라미터를 사용해야 한다고 말합니다. 그리고 다른 사람들은 필요하지 않다고 생각합니다.대신 SQL 주입을 피하기 위해 모든 문자열에서 단일 아포스트로프를 2개의 아포스트로프로 대체하려고 합니다.모든 데이터베이스는 SQL Server 2005 또는 2008을 실행하고 있으며 코드 베이스는 에서 실행되고 있습니다.NET 프레임워크 2.0

C#의 간단한 예를 들어보겠습니다.

이것을 사용하고 싶다.

string sql = "SELECT * FROM Users WHERE Name=@name";
SqlCommand getUser = new SqlCommand(sql, connection);
getUser.Parameters.AddWithValue("@name", userName);
//... blabla - do something here, this is safe

다른 사람들은 이렇게 하고 싶어하지만:

string sql = "SELECT * FROM Users WHERE Name=" + SafeDBString(name);
SqlCommand getUser = new SqlCommand(sql, connection);
//... blabla - are we safe now?

여기서 SafeDBString 함수는 다음과 같이 정의됩니다.

string SafeDBString(string inputValue) 
{
    return "'" + inputValue.Replace("'", "''") + "'";
}

이제 쿼리의 모든 문자열 값에 SafeDBString을 사용하는 한 안전합니다.그렇죠?

SafeDBString 기능을 사용하는 이유는 두 가지가 있습니다.첫 번째 방법은 석기시대부터 실행되어 온 방법입니다.두 번째 방법은 데이터베이스에서 실행되는 exact 쿼리를 표시하기 때문에 sql 문을 디버깅하는 것이 더 쉽습니다.

그럼.SafeDBString 기능을 사용하여 SQL 주입 공격을 피할 수 있는지 여부입니다.이 안전 조치를 위반하는 코드의 예를 찾고 있지만, 예를 찾을 수 없습니다.

이걸 깰 수 있는 사람 없어요?그걸 어떻게 하시겠어요?

편집: 지금까지의 회신을 정리하려면:

  • SQL Server 2005 또는 2008에서 SafeDBString을 회피하는 방법은 아직 발견되지 않았습니다.좋은 것 같아요, 그렇죠?
  • 일부 응답에서는 매개 변수화된 쿼리를 사용하면 성능 향상을 얻을 수 있다고 지적했습니다.그 이유는 쿼리 계획을 재사용할 수 있기 때문입니다.
  • 또한 파라미터화된 쿼리를 사용하면 유지보수가 용이한 읽기 쉬운 코드를 얻을 수 있다는 점에 동의합니다.
  • 또한 다양한 버전의 SafeDBString, 문자열에서 번호로 변환 및 문자열에서 최신으로 변환하는 것보다 항상 파라미터를 사용하는 것이 쉽습니다.
  • 파라미터를 사용하면 날짜 또는 10진수를 사용할 때 특히 유용한 자동 유형 변환이 제공됩니다.
  • 그리고 마지막으로:Julian R이 쓴 것처럼 스스로 보안을 유지하려고 하지 마세요.데이터베이스 벤더는 보안에 많은 시간과 비용을 소비합니다.우리가 더 잘 할 수 있는 방법도 없고 그들의 일을 하려고 노력해야 할 이유도 없습니다.

그래서 아무도 SafeDBString 기능의 단순한 보안을 깨지 못했지만, 나는 다른 많은 좋은 논거를 얻었다.감사합니다!

정답은 다음과 같습니다.

스스로 보안을 유지하려고 하지 마세요.자신이 직접 하지 않고 신뢰할 수 있는 업계 표준 라이브러리를 사용하여 원하는 작업을 수행할 수 있습니다.보안에 관한 어떠한 가정도 틀릴 수 있습니다.귀사의 접근 방식이 안전해 보일 수 있지만(기껏해야 불안정해 보일 수 있습니다), 간과하고 있는 위험이 있습니다.보안과 관련하여 이 기회를 꼭 이용하시겠습니까?

파라미터를 사용합니다.

그리고 누군가가 가서 ' 대신 '를 사용합니다. 매개 변수는 IMO가 유일하게 안전한 방법입니다.

또한 날짜/숫자에 대한 i18n 문제도 많이 발생하지 않습니다.2003년 01월 02일은 언제입니까?123,456은 얼마입니까?서버(앱 서버 및 DB 서버)가 서로 동의합니까?

리스크 요인이 납득할 수 없는 경우 퍼포먼스는 어떻습니까?매개 변수를 사용하면 RDBMS가 쿼리 계획을 재사용할 수 있으므로 성능이 향상됩니다.끈만 가지고는 할 수 없어요.

그 논쟁은 승산이 없다.취약성을 발견하면 동료는 SafeDBString 기능을 변경하여 안전하지 않음을 다시 한 번 증명합니다.

매개 변수화된 쿼리는 논란의 여지가 없는 프로그래밍의 베스트 프랙티스이기 때문에 보다 안전하고 성능이 뛰어난 방법을 사용하지 않는 이유를 기술해야 합니다.

모든 레거시 코드를 다시 쓰는 것이 문제라면 쉽게 타협할 수 있는 것은 모든 새로운 코드에서 파라미터화된 쿼리를 사용하고 오래된 코드를 리팩터하여 해당 코드로 작업할 때 사용하는 것입니다.

내 추측으로는 진짜 문제는 자존심과 고집이고 네가 그것에 대해 더 이상 할 수 있는 것은 많지 않다.

우선, "Replace" 버전의 샘플이 잘못되었습니다.텍스트 주위에 아포스트로피를 붙여야 합니다.

string sql = "SELECT * FROM Users WHERE Name='" + SafeDBString(name) & "'";
SqlCommand getUser = new SqlCommand(sql, connection);

파라미터는 값을 따옴표로 묶을 필요가 있는지 여부에 대해 걱정할 필요가 없습니다.물론 함수에 포함시킬 수도 있지만 함수에 복잡함을 더해야 합니다. 즉, 'NULL'은 늘(null)로, 'NULL'은 문자열로, 또는 숫자와 문자열은 많은 숫자를 포함하고 있습니다.벌레의 또 다른 원천일 뿐입니다.

또 하나는 퍼포먼스입니다.파라미터화된 쿼리 플랜은 연결된 플랜보다 캐시가 잘 되어 있기 때문에 쿼리를 실행할 때 서버를 한 단계 절약할 수 있습니다.

또한 작은 따옴표를 피하는 것은 충분하지 않습니다.많은 DB 제품에서는 공격자가 이용할 수 있는 문자를 이스케이프하는 대체 방법을 사용할 수 있습니다.예를 들어 MySQL에서는 백슬래시를 사용하여 단일 따옴표를 이스케이프할 수도 있습니다.따라서 다음 "이름" 값은 MySQL과SafeDBString()는 백슬래시에 따옴표는 "로 남습니다.

x\' OR 1=1;--


또한 JulianR은 보안 업무를 스스로 수행하려고 하지 않는다는 점을 다음과 같이 지적합니다.철저한 테스트를 거쳤더라도 작동하는 것처럼 보이는 미묘한 방법으로 보안 프로그래밍을 잘못하기 쉽습니다.그리고 시간이 지나고 1년 후 6개월 전에 시스템에 균열이 생겼다는 것을 알게 됩니다.그때까지는 전혀 몰랐습니다.

플랫폼용으로 제공되는 보안 라이브러리를 항상 최대한 활용하십시오.보안 코드는 생계를 위해 보안 코드를 사용하는 사람에 의해 작성되며, 사용자가 관리할 수 있는 것보다 훨씬 더 잘 테스트되고 취약성이 발견되면 공급업체에 의해 서비스됩니다.

그래서 나는 말했다:

1) 내장된 것을 재실장하려고 하는 이유는 무엇입니까?이미 글로벌 규모로 디버깅되어 있어 쉽게 사용할 수 있고 사용하기 쉽습니다.향후 버그가 발견되면 수정이 완료되어 모든 사용자가 즉시 이용할 수 있게 됩니다.

2) SafeDBString에 대한 콜을 놓치지 않도록 하기 위해 어떤 프로세스가 있습니까?단 한 곳에서 놓치면 많은 문제가 생길 수 있습니다.이런 것들을 얼마나 눈여겨보실 건가요? 그리고 그 노력이 얼마나 낭비되는지를 생각해 보세요. 만약 받아들여진 정답이 그렇게 쉽게 도달한다면 말이죠.

3) Microsoft(DB 및 액세스 라이브러리 작성자)가 SafeDBString 구현에서 알고 있는 모든 공격 벡터를 커버했다고 확신하십니까?

4) SQL의 구조를 읽기 쉽습니까?예제에서는 + 연결을 사용하고 있으며 매개 변수는 문자열과 매우 유사합니다.읽기 쉬운 형식입니다.

또한 실제로 실행된 작업을 해결하는 방법은 두 가지가 있습니다. 즉, 자체 LogCommand 함수를 롤링하거나, 보안 문제가 없는 단순한 기능을 수행하거나, 데이터베이스의 실제 상황을 파악하기 위해 SQL 트레이스를 살펴보는 것입니다.

LogCommand 기능은 다음과 같습니다.

    string LogCommand(SqlCommand cmd)
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendLine(cmd.CommandText);
        foreach (SqlParameter param in cmd.Parameters)
        {
            sb.Append(param.ToString());
            sb.Append(" = \"");
            sb.Append(param.Value.ToString());
            sb.AppendLine("\"");
        }
        return sb.ToString();
    }

옳든 그르든 보안 문제 없이 필요한 정보를 얻을 수 있습니다.

매개 변수화된 쿼리를 사용하면 SQL 주입에 대한 보호 이상의 기능을 얻을 수 있습니다.또한 실행 계획 캐싱의 잠재력도 향상됩니다.sql server 쿼리 프로파일러를 사용하면 '데이터베이스에서 실행되는 정확한 sql'을 볼 수 있으므로 sql 문을 디버깅하는 데 있어서도 손실되는 것이 없습니다.

SQL 주입 공격을 피하기 위해 두 가지 방법을 모두 사용했으며 매개 변수화된 쿼리를 선호합니다.연결된 쿼리를 사용할 때 라이브러리 함수를 사용하여 변수(mysql_real_escape_string 등)를 이스케이프했습니다.또한 독자 사양의 실장에서는 모든 것을 커버하고 있다고는 자신할 수 없습니다.

파라미터를 사용하지 않으면 사용자 입력의 유형 확인을 쉽게 수행할 수 없습니다.

SQLCommand 및 SQLParameter 클래스를 사용하여 DB 호출을 수행해도 실행 중인 SQL 쿼리를 볼 수 있습니다.SQL Command 명령어 보기텍스트 속성

파라미터화된 쿼리가 사용하기 쉬울 때 SQL 주입을 방지하는 롤 유어 오너 접근법에 대해 항상 의심하고 있습니다.둘째, "항상 그렇게 해 왔다"고 해서 그것이 올바른 방법이라는 것을 의미하지는 않는다.

이 방법은 스트링 통과가 보장될 때만 안전합니다.

만약 당신이 어느 시점에 줄을 통과하지 않는다면요?숫자만 넘기면 어떻게 해요?

http://www.mywebsite.com/profile/?id=7;DROP DATABASE DB

최종적으로는 다음과 같습니다.

SELECT * FROM DB WHERE Id = 7;DROP DATABASE DB

모든 일에 스토어드 프로시저나 함수를 사용해서 질문이 나오지 않도록 하겠습니다.

SQL을 코드에 넣어야 할 경우에는 파라미터를 사용합니다.이것만이 타당합니다.반대론자들에게 자신들보다 똑똑한 해커들이 있다는 것과 그들을 능가하려는 코드를 해독하려는 더 나은 동기를 가지고 있다는 것을 상기시켜라.파라미터를 사용하면 불가능할 뿐 아니라 어려운 것도 아닙니다.

이치
파라미터를 사용하는 또 다른 이유는 효율성입니다.

데이터베이스는 항상 쿼리를 컴파일하여 캐시한 후 캐시된 쿼리를 재사용합니다(이것이 후속 요청의 경우 분명히 더 빠릅니다).파라미터를 사용하는 경우 다른 파라미터를 사용하더라도 데이터베이스는 파라미터를 바인딩하기 전에 SQL 스트링을 기반으로 캐시된 쿼리를 다시 사용합니다.

그러나 매개 변수를 바인딩하지 않으면 SQL 문자열이 모든 요청(다른 매개 변수를 포함)에서 변경되어 캐시에 있는 문자열과 일치하지 않습니다.

이미 주어진 이유로 파라미터는 매우 좋은 아이디어입니다.그러나 나중에 쿼리에서 사용하기 위해 매개 변수를 만들고 변수에 이름을 할당하는 것은 삼중 간접 헤드 손상이기 때문에 이러한 매개 변수 사용은 매우 어렵습니다.

다음 클래스는 SQL 요청 작성에 일반적으로 사용되는 문자열 빌더를 래핑합니다.매개 변수를 생성하지 않고도 매개 변수화된 쿼리를 작성할 수 있으므로 SQL에 집중할 수 있습니다.코드는 다음과 같습니다.

var bldr = new SqlBuilder( myCommand );
bldr.Append("SELECT * FROM CUSTOMERS WHERE ID = ").Value(myId, SqlDbType.Int);
//or
bldr.Append("SELECT * FROM CUSTOMERS WHERE NAME LIKE ").FuzzyValue(myName, SqlDbType.NVarChar);
myCommand.CommandText = bldr.ToString();

코드 가독성이 크게 향상되어 파라미터화된 적절한 쿼리가 출력됩니다.

수업은 이렇게 생겼는데...

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;

namespace myNamespace
{
    /// <summary>
    /// Pour le confort et le bonheur, cette classe remplace StringBuilder pour la construction
    /// des requêtes SQL, avec l'avantage qu'elle gère la création des paramètres via la méthode
    /// Value().
    /// </summary>
    public class SqlBuilder
    {
        private StringBuilder _rq;
        private SqlCommand _cmd;
        private int _seq;
        public SqlBuilder(SqlCommand cmd)
        {
            _rq = new StringBuilder();
            _cmd = cmd;
            _seq = 0;
        }
        //Les autres surcharges de StringBuilder peuvent être implémenté ici de la même façon, au besoin.
        public SqlBuilder Append(String str)
        {
            _rq.Append(str);
            return this;
        }
        /// <summary>
        /// Ajoute une valeur runtime à la requête, via un paramètre.
        /// </summary>
        /// <param name="value">La valeur à renseigner dans la requête</param>
        /// <param name="type">Le DBType à utiliser pour la création du paramètre. Se référer au type de la colonne cible.</param>
        public SqlBuilder Value(Object value, SqlDbType type)
        {
            //get param name
            string paramName = "@SqlBuilderParam" + _seq++;
            //append condition to query
            _rq.Append(paramName);
            _cmd.Parameters.Add(paramName, type).Value = value;
            return this;
        }
        public SqlBuilder FuzzyValue(Object value, SqlDbType type)
        {
            //get param name
            string paramName = "@SqlBuilderParam" + _seq++;
            //append condition to query
            _rq.Append("'%' + " + paramName + " + '%'");
            _cmd.Parameters.Add(paramName, type).Value = value;
            return this; 
        }

        public override string ToString()
        {
            return _rq.ToString();
        }
    }
}

SQL 주입 문제를 조사해야 했던 아주 짧은 시간부터, '안전'한 값을 만드는 것은 데이터에 아포스트로피를 실제로 삽입하고 싶은 상황(예: O'Reilly)을 차단하는 것을 의미합니다.

그러면 파라미터와 저장 프로시저가 남습니다.

또, 지금까지 알고 있는 최선의 방법으로 코드를 실장하는 것이 아니라, 항상 그 방법을 사용해 주세요.

여기 동료들을 설득하는 데 도움이 될 만한 몇 가지 기사가 있습니다.

http://www.sommarskog.se/dynamic_sql.html

http://unixwiz.net/techtips/sql-injection.html

개인적으로는 동적 코드가 데이터베이스에 닿지 않도록 하고, 모든 연락처가 sp(동적 SQL을 사용하는 것이 아니라)를 통과해야 합니다.이것은, 유저에게 권한을 준 것 이외에는 아무것도 행할 수 없고, 내부 유저(관리 목적으로 실가동 액세스권을 가지는 극소수)가 직접 테이블에 액세스 해, 대혼란을 일으키거나 데이터를 훔치거나 사기를 칠 수 없는 것을 의미합니다.만약 당신이 재무 애플리케이션을 실행한다면, 이것이 가장 안전한 방법입니다.

깨질 수 있지만, 정확한 버전/패치 등에 따라 방법이 달라집니다.

이미 발생한 버그 중 하나는 악용될 수 있는 오버플로/트랜케이션 버그입니다.

또 다른 방법은 다른 데이터베이스와 유사한 버그를 찾는 것입니다.예를 들어 MySQL/PHP 스택은 특정 UTF8 시퀀스를 사용하여 치환 함수를 조작할 수 있기 때문에 탈출 문제가 발생했습니다.치환 함수는 주입 문자를 도입하도록 속여집니다.

결국 대체 보안 메커니즘은 예상되지만 의도된 기능은 아닙니다.이 기능은 코드의 의도된 용도가 아니므로, 일부 발견된 기호가 예상된 기능을 손상시킬 가능성이 높습니다.

레거시 코드가 많은 경우 교체 방법을 임시방편으로 사용하여 긴 재작성 및 테스트를 피할 수 있습니다.새로운 코드를 작성하는 경우는, 변명의 여지가 없습니다.

가능한 경우 항상 매개 변수화된 쿼리를 사용하십시오.데이터베이스 필드 입력으로 식별되지 않으면 이상한 문자를 사용하지 않는 단순한 입력이라도 이미 SQL 주입을 생성할 수 있습니다.

따라서 데이터베이스가 입력 자체를 식별하도록 하는 것은 말할 것도 없고, 그렇지 않으면 이스케이프되거나 변경될 수 있는 이상한 문자를 실제로 삽입해야 할 때 발생하는 번거로움도 덜 수 있습니다.입력을 계산할 필요가 없기 때문에 결과적으로 귀중한 런타임도 절약할 수 있습니다.

다른 응답자는 '직접 수행하는 것이 나쁜 이유'에 대해 언급하지 않았지만 SQL 잘라내기 공격을 고려합니다.

또,QUOTENAME파라미터를 사용하도록 설득할 수 없는 경우 도움이 될 수 있는 T-SQL 함수입니다.탈출한 Qoute에 대한 우려(모두)가 많이 포착됩니다.

2년 후, 나는 다시 알게 되었다...파라미터에 문제가 있다고 생각되는 사람은 누구나 VS Extension QueryFirst를 사용해 보십시오.실제 .sql 파일(Validation, Intellissense)에서 요청을 편집할 수 있습니다.파라미터를 추가하려면 '@'부터 시작하여 SQL에 직접 입력합니다.파일을 저장하면 QueryFirst에서 래퍼 클래스가 생성되어 쿼리를 실행하고 결과에 액세스할 수 있습니다.파라미터의 DB 타입을 검색하여 .net 타입에 매핑합니다.이 타입은 생성된 Execute() 메서드에 대한 입력입니다.보다 더 간단할 순 없다.올바른 방법을 사용하는 것이 다른 방법보다 훨씬 빠르고 쉬우며 SQL 주입 취약성을 생성하는 것은 불가능하거나 최소한 비정상적으로 어려워집니다.DB의 열을 삭제하고 응용 프로그램의 컴파일 오류를 즉시 확인할 수 있는 등 다른 중요한 이점이 있습니다.

법적 면책사항: Query First를 작성했습니다.

파라미터화된 쿼리를 사용하는 이유는 다음과 같습니다.

  1. 보안 - 데이터베이스 액세스 계층은 데이터에 허용되지 않는 항목을 제거하거나 이스케이프하는 방법을 알고 있습니다.
  2. 문제의 분리 - 내 코드는 데이터를 데이터베이스가 원하는 형식으로 변환하는 것을 책임지지 않습니다.
  3. 중복성 없음 - 데이터베이스 포맷/에스케이핑을 수행하는 모든 프로젝트에 어셈블리 또는 클래스를 포함할 필요가 없습니다.클래스 라이브러리에 내장되어 있습니다.

SQL 문의 버퍼 오버플로와 관련된 취약성(어느 데이터베이스인지 기억나지 않음)은 거의 없었습니다.

제가 말씀드리고 싶은 것은 SQL-Injection은 단순히 "견적을 회피"하는 것이 아니라 다음에 무슨 일이 일어날지 모른다는 것입니다.

또 하나의 중요한 고려사항은 이스케이프된 데이터와 이스케이프되지 않은 데이터를 추적하는 것입니다.웹 등 수많은 애플리케이션이 데이터가 raw-Unicode, &-encoded, 포맷된HTML 등 언제인지 제대로 추적하지 못하고 있습니다.이 어떤 문자열인지 .''-어느쪽이든 상관없습니다.

일부 변수의 유형을 변경할 때도 문제가 됩니다. 예전에는 정수였지만 지금은 문자열입니다.이제 문제가 생겼군요.

언급URL : https://stackoverflow.com/questions/910465/avoiding-sql-injection-without-parameters

반응형