programing

mysql_real_escape_string()을 우회하는 SQL 주입

coolbiz 2023. 1. 27. 21:57
반응형

mysql_real_escape_string()을 우회하는 SQL 주입

"SQL"을 주입 ?mysql_real_escape_string()능하하??? ???

이 샘플의 상황을 고려해 주세요.SQL은 다음과 같이 PHP로 구성됩니다.

$login = mysql_real_escape_string(GetFromPost('login'));
$password = mysql_real_escape_string(GetFromPost('password'));

$sql = "SELECT * FROM table WHERE login='$login' AND password='$password'";

할 수 .mysql_real_escape_string() 악용할수 있는 요.하지만 가능한 공격이 생각나지 않는군요?

전형적인 주사법은 다음과 같습니다.

aaa' OR 1=1 --

동작하지 않는다.

위의 PHP 코드를 통과할 수 있는 주입 방법을 알고 계십니까?

간단히 말하면, 「네」, 「네라고 하는 방법이 있습니다.#매우 불명확한 엣지 케이스!!

긴 답변은 쉽지 않습니다.여기서 시연된 공격에 기초하고 있습니다.

공격

자, 먼저 공격을 보여드리는 걸로...

mysql_query('SET NAMES gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

경우에 따라서는, 1 행이 넘는 행이 반환됩니다.여기서 무슨 일이 일어나고 있는지 분석해 봅시다.

  1. 문자 집합 선택

    mysql_query('SET NAMES gbk');
    

    기능하기 이에는, 「」의 양쪽 모두가 필요합니다.' ASCII의 경우도 .0x27 그리고 마지막 바이트가 ASCII인 문자를 가지고 있다.\ ㅇㅇㅇ.0x5c 5MySQL 5.6에서는 5개의 인코딩이 지원되고 있습니다.big5,cp932,gb2312,gbk ★★★★★★★★★★★★★★★★★」sjis뽑겠습니다.gbk

    이제 SET NAMES여기. 이것은 서버에 문자 집합을 설정합니다.C API 함수에 대한 호출을 사용한 경우mysql_set_charset()(2006년 MySQL)을 사용합니다.'아예'는 '그' 입니다.

  2. 페이로드

    「」로 합니다.0xbf27gbk 문자는하지 않은 latin1 끈입니다.¿'해 주세요.latin1 ... gbk,0x27'★★★★★★ 。

    「」를 호출했을 입니다.addslashes() 위에 ASCII를 \ ㅇㅇㅇ.0x5c의에는, 「」가 있습니다.'★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★0xbf5c27 명령어는 " "에 있습니다gbk수열입니다. 2글자 수열입니다.0xbf5c에 어 followed가 붙는다.0x27즉, 유효한 문자 뒤에 이스케이프되지 않은 문자가 이어집니다.' 이 문구는 하지 않습니다.addslashes()그럼 다음 단계로 넘어가겠습니다.

  3. mysql_real_sysql_string()

    'C API'에 C 콜mysql_real_escape_string()addslashes()접속 문자 세트를 알고 있다는 점에서.그 때문에, 서버가 상정하고 있는 문자 세트에 대해서 이스케이프를 적절히 실행할 수 있습니다.아직 하고 있습니다.latin1연관성에 대해서요. 왜냐면 우리가 그걸 달리 말한 적이 없으니까요.서버에는 사용하고 있다고 했습니다.gbk하지만 클라이언트는 여전히 그것이latin1.

    에, 「」, 「」에의 콜은, 「」mysql_real_escape_string().'★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★」$var gbk문자 집합은 다음과 같습니다.

    '' OR 1 = 1 /*

    그게 바로 공격에 필요한 거야

  4. 쿼리

    이 부분은 형식적인 내용일 뿐이지만, 여기에 제시된 질문이 있습니다.

    SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
    

축하합니다. 이치노mysql_real_escape_string()

더 배드

것은악악 악악악다다 PDO는 기본적으로 MySQL을 사용하여 준비된 문을 에뮬레이트합니다.즉, 클라이언트 측에서는 기본적으로 sprintf를 통해mysql_real_escape_string()에서) (C 라이브러리에서는), (C 라이브러리에서는), (C 라이브러리에서는) 으로 주입됩니다

$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

에뮬레이트된 준비 스테이트먼트를 무효로 하면, 이것을 막을 수 있습니다.

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

이것은 보통 진정한 준비 스테이트먼트(즉, 데이터가 쿼리와는 다른 패킷으로 전송됨)가 됩니다.그러나 PDO는 MySQL이 기본적으로 준비할 수 없는 에뮬레이트 스테이트먼트(매뉴얼에 기재되어 있습니다만, 적절한 서버 버전을 선택하는 것에 주의해 주세요).

어글리

가 에 이 수 요.mysql_set_charset('gbk')SET NAMES gbk06 하고 있는

이전 MySQL 릴리스를 사용하는 경우,mysql_real_escape_string()payload 내의 문자 등 비활성 멀티바이트 문자를 클라이언트에 접속 부호화가 올바르게 통지되어 이 공격이 성공하더라도 이스케이프 목적으로 단일 바이트로 취급하는 것을 의미합니다. 버그는 MySQL 4.1.20, 5.0.22 및 5.1.11에서 수정되었습니다.

가장 안은 '아주 안 좋은 건'PDO은 C .mysql_set_charset()5.3.6까지이므로 이전 버전에서는 가능한 모든 명령에 대해 이 공격을 방지할 수 없습니다.DSN 파라미터로 표시됩니다.

세이빙 그레이스

처음에 이 공격이 동작하기 위해서는 데이터베이스 접속을 취약한 문자 세트를 사용하여 인코딩해야 합니다.는 취약하지 않지만 모든 Unicode 문자를 지원할 수 있습니다.따라서 대신 사용할 수 있는 것은 MySQL 5.5.3 이후뿐입니다.또는 이 방법도 취약하지 않고 Unicode Basic 다국어 플레인의 전체를 지원할 수 있습니다.

또는 SQL 모드를 이노블로 할 수도 있습니다.SQL 모드는 (특히) 동작에 영향을 줍니다.mysql_real_escape_string()유효하게 되어 경우,0x27로 대체됩니다.0x27270x5c27따라서 이스케이프 프로세스는 이전에 존재하지 않았던 취약한 인코딩에 유효한 문자를 생성할 수 없습니다.0xbf270xbf27etc) : 서버는 여전히 문자열을 무효로 거부합니다.그러나 이 SQL 모드를 사용할 때 발생할 수 있는 다른 취약성에 대해서는 @eggyal의 답변을 참조하십시오.

안전한 예

다음의 예는 안전합니다.

mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

하고 있기 때문입니다.utf8

mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

클라이언트와 서버가 일치하도록 문자 집합을 적절하게 설정했기 때문입니다.

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

우리가 에뮬레이트된 준비된 진술서를 꺼버렸기 때문입니다.

$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

왜냐하면 저희가 캐릭터를 제대로 설정했기 때문입니다.

$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

MySQLi는 항상 제대로 준비된 문을 실행하기 때문입니다.

마무리

다음과 같은 경우:

  • 최신 버전의 MySQL 사용(5.1 이후, 모두 5.5, 5.6 등) mysql_set_charset()$mysqli->set_charset()DSN 파라미터5. / PDO dsn DSN charset 파 ( PHP 5 . 3 . 6 )

또는

  • 부호화에 만 합니다).utf8latin1ascii ) / etc )

넌 100% 안전해

그렇지 않으면 사용 중이라도 취약해집니다.

다음 쿼리를 고려합니다.

$iId = mysql_real_escape_string("1 OR 1=1");    
$sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string()이것으로부터 당신을 보호하지 못할 것입니다.쿼리 내 변수 주위에 작은 ' '따옴표()를 사용하므로 이러한 문제로부터 보호할 수 있습니다.다음 옵션도 있습니다.

$iId = (int)"1 OR 1=1";
$sSql = "SELECT * FROM table WHERE id = $iId";

TL;DR

mysql_real_escape_string()님은 다음과 같은 경우 아무런 보호도 제공하지 않습니다(또한 데이터를 더 많이 대량으로 처리할 수도 있습니다).

  • MySQL의 SQL 모드가 활성화되어 있습니다(접속할 때마다 다른 SQL 모드명시적으로 선택하지 않는 한 활성화될 수 있습니다).

  • 은 이중 로 묶은 SQL을 사용하여 됩니다."★★★★★★ 。

이는 버그 #72458로 제출되었으며 MySQL v5.7.6에서 수정되었습니다(아래의 "The Saving Grace" 섹션 참조).

이것도 (아마도) 불명확한 엣지 케이스!!!

@ircmaxell의 훌륭한 답변에 경의를 표하며(정말이지 표절이 아닌 아첨입니다!)나는 그의 형식을 채택한다:

공격

데모부터 시작해서...

mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"'); // could already be set
$var = mysql_real_escape_string('" OR 1=1 -- ');
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

하면 그 from from from from에서 모든 레코드가 됩니다.test table ★★★★

  1. SQL 모드 선택

    mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"');
    

    String Literals에 기재된 바와 같이

    문자열에 따옴표 문자를 포함할 수 있는 방법은 여러 가지가 있습니다.

    • A''묶인 안에 "가 있습니다.'는 '아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아.''

    • A'"묶인 안에 "가 있습니다."는 '아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아.""

    • 문자("")를 합니다.\

    • A''묶인 안에 "가 있습니다."특별한 치료가 필요하지 않으며 이중화되거나 탈출할 필요도 없습니다. '아, 아, 아, 아, 아',"묶인 안에 "가 있습니다.'특별한 치료가 필요 없습니다.

    서버의 SQL 모드에 가 포함되어 있는 경우 이들 옵션 중 세 번째 옵션(에 의해 채택되는 일반적인 접근법)mysql_real_escape_string()사용할 수 없습니다.처음 2가지 옵션 중 하나를 사용해야 합니다.네 번째 글머리 기호로 인한 효과는 데이터를 망가지지 않기 위해 문자 그대로 인용하는 데 사용되는 문자를 반드시 알아야 한다는 점에 유의하십시오.

  2. 페이로드

    " OR 1=1 -- 
    

    는 payload와 이 주입을 합니다." 한 부호화는 .특별한 인코딩은 없습니다.특수문자는 없습니다.츠미야

  3. mysql_real_sysql_string()

    $var = mysql_real_escape_string('" OR 1=1 -- ');
    

    mysql_real_escape_string()는 SQL 모드를 체크하고 그에 따라 동작을 조정합니다.다음을 참조해 주세요.

    ulong STDCALL
    mysql_real_escape_string(MYSQL *mysql, char *to,const char *from,
                 ulong length)
    {
      if (mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES)
        return escape_quotes_for_mysql(mysql->charset, to, 0, from, length);
      return escape_string_for_mysql(mysql->charset, to, 0, from, length);
    }
    

    , 다른 함수인 른른른른 ,,,, , thus, thus,,, thusescape_quotes_for_mysql() 경우, 이 되다, 이면 이 NO_BACKSLASH_ESCAPESSQL 드 sql sql sql 。위에서 설명한 바와 같이, 이러한 함수는 다른 따옴표를 문자 그대로 반복하지 않고 반복하기 위해 어떤 문자를 사용할지 알아야 합니다.

    단, 이 함수는 임의로 문자열이 따옴표를 사용하여 따옴표로 묶인다고 가정합니다.'성격.다음을 참조해 주세요.

    /*
      Escape apostrophes by doubling them up
    
    // [ deletia 839-845 ]
    
      DESCRIPTION
        This escapes the contents of a string by doubling up any apostrophes that
        it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
        effect on the server.
    
    // [ deletia 852-858 ]
    */
    
    size_t escape_quotes_for_mysql(CHARSET_INFO *charset_info,
                                   char *to, size_t to_length,
                                   const char *from, size_t length)
    {
    // [ deletia 865-892 ]
    
        if (*from == '\'')
        {
          if (to + 2 > to_end)
          {
            overflow= TRUE;
            break;
          }
          *to++= '\'';
          *to++= '\'';
        }
    

    두 번 따옴표를 남깁니다." 문자 문자 「」의 )'문자)는 리터럴 인용에 사용되는 실제 문자에 관계없이 사용할 수 있습니다.저희 같은 경우에는$var제공된 인수와 완전히 동일하게 유지됩니다.mysql_real_escape_string()-탈옥은 전혀 이루어지지 않은 것 같습니다.

  4. 쿼리

    mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');
    

    일종의 형식적인 질문으로 표현되는 질문은 다음과 같습니다.

    SELECT * FROM test WHERE name = "" OR 1=1 -- " LIMIT 1
    

내 배운 친구가 말했듯이: 축하해, 방금 프로그램을 성공적으로 공격했어.mysql_real_escape_string()

더 배드

mysql_set_charset() 이 기능은 문자 집합과는 전혀 관계가 없기 때문에 도움이 되지 않습니다.또한 이 기능은 다른 래퍼이기 때문에 도움이 되지 않습니다.

아직하지 않은 로, 것입니다.mysql_real_escape_string() 리터럴이 인용되는 문자는 나중에 개발자가 결정할 것이기 때문에 알 수 없습니다.그래서...NO_BACKSLASH_ESCAPES이 함수는 임의의 따옴표로 사용하기 위해 모든 입력을 안전하게 이스케이프할 수 있는 방법은 없습니다(적어도 더블링이 필요하지 않은 문자를 더블링하지 않으면 데이터가 지워지지 않습니다).

어글리

것은악악 악악악다다 NO_BACKSLASH_ESCAPES표준 SQL과의 호환성을 위해 사용할 필요가 있기 때문에 야생에서는 그다지 드물지 않을 수 있습니다(예를 들어 SQL-92 사양 섹션 5.3 참조).<quote symbol> ::= <quote><quote>문법적 생산성과 백슬래시(backslash)에 대한 특별한 의미가 없다.게다가 ircmaxell의 투고에 기재되어 있는 (고정된 오래 된) 버그의 회피책으로서 이 버그의 사용을 명시적으로 추천했습니다.일부 DBA는 와 같은 잘못된 이스케이프 방식의 사용을 방지하기 위해 기본 온으로 설정될 수도 있습니다.

또한 새로운 접속의 SQL 모드는 설정에 따라 서버에 의해 설정됩니다.SUPER사용자는 언제든지 변경할 수 있습니다).따라서 서버의 동작을 확인하려면 접속 후 항상 원하는 모드를 명시적으로 지정해야 합니다.

세이빙 그레이스

SQL 모드를 항상 명시적으로 설정하여NO_BACKSLASH_ESCAPES 스트링이헤드를 수 MySQL은 MySQL을 사용합니다.escape_quotes_for_mysql()사용되지 않거나 반복해야 하는 따옴표 문자에 대한 가정이 정확합니다.

이러한 이유로, 나는 모든 사용자가 다음을 사용하는 것을 추천한다.NO_BACKSLASH_ESCAPES또, 싱글 스트링 리터럴을 습관적으로 사용하도록 강제하기 때문에, 모드를 유효하게 합니다.이중 따옴표로 묶인 리터럴이 사용되는 경우 SQL 주입을 방지하는 것은 아닙니다.그냥 발생할 가능성을 줄일 뿐입니다(일반적이고 악의적이지 않은 쿼리가 실패하기 때문입니다.

PDO 에서는, 동등한 함수와 준비된 스테이트먼트 에뮬레이터가 모두 호출됩니다.이것에 의해, 이스케이프 된 리터럴이 단일 따옴표로 따옴표로 둘러싸인 것을 확인할 수 있기 때문에, PDO 는 항상 이 버그로부터 면제됩니다.

MySQL v5.7.6에서는 이 버그가 수정되었습니다.변경 로그 참조:

기능 추가 또는 변경

  • 호환되지 않는 변경:SQL 모드가 네이블일 경우 후자 함수가 문자를 올바르게 인코딩하지 못할 수 있기 때문에 새로운 C API 함수가 대체 기능으로 구현되었습니다.이 경우 따옴표 문자를 2배로 하는 것 이외에는 이스케이프할 수 없습니다.이 경우 따옴표 컨텍스트에 대해 사용 가능한 것보다 더 많은 정보를 알고 있어야 합니다.따옴표 컨텍스트를 지정하기 위해서는 별도의 인수가 필요합니다.사용방법에 대한 자세한 내용은 mysql_real_escape_string_quote()를 참조하십시오.

    메모

    이네이블이 되어 있는 경우, 에러가 발생하고, 에러가 발생하는 대신에 를 사용하도록 애플리케이션을 변경할 필요가 있습니다.

    참고 자료:Bug #19211994도 참조하십시오.

안전한 예

ircmaxell에 의해 설명되는 버그와 함께 다음 예시는 완전히 안전합니다(MySQL 4.1.20, 5.0.22, 5.1.11 이후의 것을 사용하고 있거나 GBK/Big5 접속 부호화를 사용하지 않는 것을 전제로 합니다).

mysql_set_charset($charset);
mysql_query("SET SQL_MODE=''");
$var = mysql_real_escape_string('" OR 1=1 /*');
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

을입니다.SQL の sql sql sql sql 。NO_BACKSLASH_ESCAPES.

mysql_set_charset($charset);
$var = mysql_real_escape_string("' OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

왜냐하면 우리가 스트링 리터럴을 싱글 리터럴로 인용했거든

$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(["' OR 1=1 /*"]);

PDO 준비 스테이트먼트는 이 취약성에 영향을 받지 않기 때문에(또한 ircmaxell도 PHP 5.3.6을 사용하고 있으며 DSN에서 문자 세트가 올바르게 설정되어 있거나 준비된 스테이트먼트 에뮬레이션이 비활성화되어 있는 경우)

$var  = $pdo->quote("' OR 1=1 /*");
$stmt = $pdo->query("SELECT * FROM test WHERE name = $var LIMIT 1");

PDO pquote() 그것을 ( 문자의 경우).'characters); 이 경우 ircmaxell의 오류를 방지하려면 PHP 5.3.6을 사용하고 DSN에서 문자 세트를 올바르게 설정해야 합니다.

$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "' OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

...MySQLi가 준비한 문장은 안전하기 때문입니다.

마무리

따라서 다음과 같은 경우:

  • 원어민용 문구를 사용하다

또는

  • MySQL v5.7.6 이후 사용

또는

  • ircmaxell's summary의 솔루션 중 하나를 사용하는 것 외에 적어도 다음 중 하나를 사용한다.

    • PDO;
    • 단일 문자 문자열 리터럴 또는
    • 으로 설정되어 모드NO_BACKSLASH_ESCAPES

...그러면 완전히 안전합니다(스트링의 범위를 벗어나는 기능).

그거할 수 게 %만약 약약을 사용한다면 위험할 수 있습니다.LIKE가 '''만 수 ''%이를 필터링하지 않으면 로그인으로 간주됩니다.또, 임의의 유저의 패스워드를 brootforce로 설정할 필요가 있습니다.데이터는 쿼리 자체를 방해할 수 없기 때문에 100% 안전하게 하기 위해 준비된 문을 사용하는 것이 좋습니다. 이런 , 더 으로 할 수 것입니다.$login = preg_replace('/[^a-zA-Z0-9_]/', '', $login);

언급URL : https://stackoverflow.com/questions/5741187/sql-injection-that-gets-around-mysql-real-escape-string

반응형