WHERE 절을 사용하여 배열을 쿼리에 전달


314

ID 배열이 주어지면 $galleries = array(1,2,5)WHERE 절에서 배열 값을 사용하는 SQL 쿼리를 원합니다.

SELECT *
FROM galleries
WHERE id = /* values of array $galleries... eg. (1 || 2 || 5) */

MySQL과 함께 사용할이 쿼리 문자열을 어떻게 생성 할 수 있습니까?



5
@trante 이것은 가장 오래된 것입니다 (2009).
Fabián

SELECT * FROM TABLE WHERE NAME LIKE ( 'ABC %'또는 'ABD %'OR ..)와 같은 문제에 대한 비슷한 해결책이 있습니까?
Eugine Joseph June

답변:


332

조심해! 이 답변에는 심각한 SQL 주입 취약점 이 포함되어 있습니다 . 외부 입력이 살균되지 않도록 여기에 제시된 코드 샘플을 사용하지 마십시오.

$ids = join("','",$galleries);   
$sql = "SELECT * FROM galleries WHERE id IN ('$ids')";

7
식별자는 여전히 인용 된 목록이므로 "WHERE id IN ( '1,2,3,4')"로 표시됩니다. 각 식별자를 따로 인용하거나 괄호 안에 인용 부호를 버려야합니다.
Rob

22
$galleries이 문장 전에 입력을 확인해야한다는 경고를 추가하기 만하면됩니다 ! 준비된 명령문은 배열 AFAIK를 처리 할 수 ​​없으므로 변수를 바인딩하는 데 익숙한 경우 여기에서 SQL 삽입을 쉽게 수행 할 수 있습니다.
leemes

3
나 자신처럼 PHP를 초보자들에게 누군가가 설명하거나 설명 할 리소스를 알려줄 수 있는데 왜 이것이 주입되기 쉬운 지 그리고 이것을 막기 위해 어떻게 올바르게 수행해야 하는가? 이 다음 쿼리가 실행되기 직전에 쿼리에서 ID 목록이 생성되는 경우 여전히 위험합니까?
ministe2003

3
@ ministe2003 $galleries값이 다음과 같다고 상상해보십시오 array('1); SELECT password FROM users;/*'). 위생 처리하지 않으면 쿼리는 SELECT * FROM galleries WHERE id IN (1); SELECT password FROM users;/*)입니다. 테이블 및 열 이름을 데이터베이스에있는 이름으로 변경하고 해당 쿼리를 시도하고 결과를 확인하십시오. 갤러리 목록이 아니라 결과로 비밀번호 목록을 찾을 수 있습니다. 데이터 출력 방법 또는 예기치 않은 데이터 배열로 스크립트가 수행하는 작업에 따라 공개보기로 출력 될 수 있습니다.
Chris Baker

18
묻는 질문에 대해서는 완벽하게 유효하고 안전한 답변입니다. 안전하지 않다고 불평하는 사람- $galleries질문에 제공된 대로이 특정 코드를 설정하고 앞에서 언급 한 "sql injection 취약점"을 사용하여 악용 하는 거래는 어떻 습니까? 당신이 할 수없는 경우-당신은 미화 200 달러를 지불합니다. 어떻게에 대한?
zerkms

307

PDO 사용 : [1]

$in = join(',', array_fill(0, count($ids), '?'));
$select = <<<SQL
    SELECT *
    FROM galleries
    WHERE id IN ($in);
SQL;
$statement = $pdo->prepare($select);
$statement->execute($ids);

MySQLi 사용하기 [2]

$in = join(',', array_fill(0, count($ids), '?'));
$select = <<<SQL
    SELECT *
    FROM galleries
    WHERE id IN ($in);
SQL;
$statement = $mysqli->prepare($select);
$statement->bind_param(str_repeat('i', count($ids)), ...$ids);
$statement->execute();
$result = $statement->get_result();

설명:

IN()주어진 목록에 값이 존재하는지 확인 하려면 SQL 연산자를 사용하십시오 .

일반적으로 다음과 같습니다.

expr IN (value,...)

()배열 내부에 배치 할 표현식을 만들 수 있습니다 . 괄호 안에 적어도 하나의 값이 있어야합니다. 그렇지 않으면 MySQL이 오류를 반환합니다. 이것은 입력 배열에 적어도 하나의 값이 있는지 확인하는 것과 같습니다. SQL 인젝션 공격을 방지하려면 먼저 ?각 입력 항목에 대해를 생성하여 매개 변수화 된 쿼리를 작성하십시오. 여기서는 ID를 포함하는 배열이 호출되었다고 가정합니다 $ids.

$in = join(',', array_fill(0, count($ids), '?'));

$select = <<<SQL
    SELECT *
    FROM galleries
    WHERE id IN ($in);
SQL;

세 개의 항목으로 구성된 입력 배열 $select은 다음과 같습니다.

SELECT *
FROM galleries
WHERE id IN (?, ?, ?)

다시 한 번 ?입력 배열에 각 항목에 대한 것이 있습니다. 그런 다음 PDO 또는 MySQLi를 사용하여 위에서 언급 한대로 쿼리를 준비하고 실행합니다.

IN()문자열과 함께 연산자 사용

바운드 매개 변수로 인해 문자열과 정수를 쉽게 변경할 수 있습니다. PDO의 경우 변경이 필요하지 않습니다. MySQLi 변화 str_repeat('i',str_repeat('s', 당신이 문자열을 확인해야합니다.

[1] : 간결성을 확인하는 오류를 생략했습니다. 각 데이터베이스 방법에 대해 일반적인 오류를 확인해야합니다 (또는 DB 드라이버가 예외를 발생하도록 설정).

[2] : PHP 5.6 이상이 필요합니다. 다시 한 번 간결성을 검사하는 오류를 생략했습니다.


7
... $ ids는 무엇을합니까? "구문 오류, 예기치 않은 '.'"가 표시됩니다.
Marcel

나는 MySQLi를 사용하고, 그들을보고 나는 PHP 5.6이
마르셀

1
당신이 참조하는 경우 $statement->bind_param(str_repeat('i', count($ids)), ...$ids);다음이 ...ID를 확대하고 여러 매개 변수로 배열에서입니다. 당신이 참조 expr IN (value,...)한다면 그것은 더 많은 값이있을 수 있음을 의미합니다 WHERE id IN (1, 3, 4). 최소한 하나만 있으면됩니다.
Levi Morrison

1
<<<가 무엇인지 혼동했지만 참조를 찾았습니다 : php.net/manual/en/…
Tsangares

1
또한 다음에 대한 참조가 있습니다 ...: wiki.php.net/rfc/argument_unpacking
Tsangares

58

정수 :

$query = "SELECT * FROM `$table` WHERE `$column` IN(".implode(',',$array).")";

문자열 :

$query = "SELECT * FROM `$table` WHERE `$column` IN('".implode("','",$array)."')";

1
왜 '\'?? 말 해주세요
zloctb

29

사전에 입력을 올바르게 위생 처리한다고 가정하면 ...

$matches = implode(',', $galleries);

그런 다음 검색어를 조정하십시오.

SELECT *
FROM galleries
WHERE id IN ( $matches ) 

데이터 세트에 따라 적절한 값을 인용하십시오.


제안한 것을 시도했지만 첫 번째 키 값을 가져 왔습니다. 이해가되지 않는 것을 알고 있지만 user542568 예제를 사용하여 수행하면 저주가 작동합니다.
사무엘 람잔



7

대한 MySQLi 탈출 기능 :

$ids = array_map(function($a) use($mysqli) { 
    return is_string($a) ? "'".$mysqli->real_escape_string($a)."'" : $a;
  }, $ids);
$ids = join(',', $ids);  
$result = $mysqli->query("SELECT * FROM galleries WHERE id IN ($ids)");

준비된 진술이있는 PDO의 경우 :

$qmarks = implode(',', array_fill(0, count($ids), '?'));
$sth = $dbh->prepare("SELECT * FROM galleries WHERE id IN ($qmarks)");
$sth->execute($ids);

이것은 짧고 코드 삽입 취약점을 피합니다! +1
Stephan Richter

MySQLi도 진술을 준비했다. 입력을 피하지 마십시오. 이것은 여전히 ​​SQL 삽입에 취약합니다.
Dharman

6

우리는 SQL 인젝션 취약점과 빈 상태를 처리해야 합니다. 다음과 같이 두 가지를 다룰 것입니다.

순수한 숫자 배열은 해당 타입 변환 즉 사용 intval하거나 floatval또는 doubleval각 소자 위에있다. mysqli_real_escape_string()원하는 경우 숫자 값에도 적용 할 수있는 문자열 유형 입니다. MySQL은 숫자뿐만 아니라 날짜 변형도 string으로 허용합니다 .

쿼리에 전달하기 전에 값을 적절히 이스케이프하려면 다음과 유사한 함수를 작성하십시오.

function escape($string)
{
    // Assuming $db is a link identifier returned by mysqli_connect() or mysqli_init()
    return mysqli_real_escape_string($db, $string);
}

이러한 기능은 응용 프로그램에서 이미 사용 가능하거나 이미 작성했을 수 있습니다.

다음과 같이 문자열 배열을 삭제하십시오.

$values = array_map('escape', $gallaries);

숫자 배열을 사용하여 소독 될 수있다 intval거나 floatval또는 doubleval대신 적합한 것으로 :

$values = array_map('intval', $gallaries);

그런 다음 마지막으로 쿼리 조건을 작성하십시오.

$where  = count($values) ? "`id` = '" . implode("' OR `id` = '", $values) . "'" : 0;

또는

$where  = count($values) ? "`id` IN ('" . implode("', '", $values) . "')" : 0;

때로는 배열이 비어있을 수 $galleries = array();있으므로 IN ()빈 목록을 허용하지 않습니다. OR대신 사용할 수도 있지만 문제는 남아 있습니다. 따라서 위의 점검 count($values)은 동일한 것을 보장하는 것입니다.

그리고 최종 쿼리에 추가하십시오.

$query  = 'SELECT * FROM `galleries` WHERE ' . $where;

: 모든 행을 숨기지 않고 빈 배열 인 경우 모든 레코드를 표시하려면 (필터링 없음) 삼항의 잘못된 부분에서 01 로 바꾸십시오 .


누군가가 다음을 수행해야하는 경우를 대비 하여 솔루션을 하나의 라이너 (및 못생긴 것)로 만들기 위해$query = 'SELECT * FROM galleries WHERE ' . (count($gallaries) ? "id IN ('" . implode("', '", array_map('escape', $gallaries)) . "')" : 0);
Izhar Aazmi

5

더 안전합니다.

$galleries = array(1,2,5);
array_walk($galleries , 'intval');
$ids = implode(',', $galleries);
$sql = "SELECT * FROM galleries WHERE id IN ($ids)";

5

Col. Shrapnel의 PHP 용 SafeMySQL 라이브러리는 매개 변수화 된 쿼리에 형식 힌트 자리 표시자를 제공하며 배열 작업을위한 편리한 자리 표시 자 몇 개를 포함합니다. 그만큼?a자리 이스케이프 문자열 * 쉼표로 구분 된 목록 배열을 확장한다.

예를 들면 다음과 같습니다.

$someArray = [1, 2, 5];
$galleries = $db->getAll("SELECT * FROM galleries WHERE id IN (?a)", $someArray);

* MySQL은 자동 유형 강제 변환을 수행하므로 SafeMySQL이 위의 ID를 문자열로 변환해도 상관 없습니다. 여전히 올바른 결과를 얻을 수 있습니다.


4

입력 배열을 올바르게 필터링하면이 "WHERE id IN"절을 사용할 수 있습니다. 이 같은:

$galleries = array();

foreach ($_REQUEST['gallery_id'] as $key => $val) {
    $galleries[$key] = filter_var($val, FILTER_SANITIZE_NUMBER_INT);
}

아래 예와 같이 :여기에 이미지 설명을 입력하십시오

$galleryIds = implode(',', $galleries);

즉 지금 안전하게 사용해야합니다 $query = "SELECT * FROM galleries WHERE id IN ({$galleryIds})";


@ levi-morrison은 이것에 훨씬 더 나은 해결책을 게시했습니다.
Supratim Roy

4

당신은 테이블 texts (T_ID (int), T_TEXT (text))과 테이블 이있을 수 있습니다test (id (int), var (varchar(255)))

에서 insert into test values (1, '1,2,3') ;테이블 텍스트에서 다음과 같은 뜻을 출력 행을 경우 T_ID IN (1,2,3):

SELECT * FROM `texts` WHERE (SELECT FIND_IN_SET( T_ID, ( SELECT var FROM test WHERE id =1 ) ) AS tm) >0

이 방법으로 추가 테이블없이 간단한 n2m 데이터베이스 관계를 관리하고 PHP 나 다른 프로그래밍 언어를 사용할 필요없이 SQL 만 사용할 수 있습니다.


3

더 많은 예 :

$galleryIds = [1, '2', 'Vitruvian Man'];
$ids = array_filter($galleryIds, function($n){return (is_numeric($n));});
$ids = implode(', ', $ids);

$sql = "SELECT * FROM galleries WHERE id IN ({$ids})";
// output: 'SELECT * FROM galleries WHERE id IN (1, 2)'

$statement = $pdo->prepare($sql);
$statement->execute();

2

IN 쿼리를 사용하는 것 외에도 IN 쿼리에서 SQL 주입 취약점의 위험이있는 두 가지 옵션이 있습니다. 당신이 사용할 수있는 루프 당신이 원하는 정확한 데이터를 얻을하거나 함께 쿼리 사용할 수 있습니다 OR의 경우

1. SELECT *
      FROM galleries WHERE id=1 or id=2 or id=5;


2. $ids = array(1, 2, 5);
   foreach ($ids as $id) {
      $data[] = SELECT *
                    FROM galleries WHERE id= $id;
   }

2

PDO가없는 안전한 방법 :

$ids = array_filter(array_unique(array_map('intval', (array)$ids)));

if ($ids) {
    $query = 'SELECT * FROM `galleries` WHERE `id` IN ('.implode(',', $ids).');';
}
  • (array)$ids$ids변수를 배열로 캐스트
  • array_map 모든 배열 값을 정수로 변환
  • array_unique 반복되는 값 제거
  • array_filter 0 값 제거
  • implode 모든 값을 IN 선택에 결합

1

원래 질문은 숫자 배열과 관련이 있으며 문자열 배열을 사용하고 있으므로 주어진 예제를 작동시킬 수 없었습니다.

IN()함수 와 함께 작동하려면 각 문자열을 작은 따옴표로 묶어야한다는 것을 알았습니다 .

여기 내 해결책이 있습니다

foreach($status as $status_a) {
        $status_sql[] = '\''.$status_a.'\'';
    }
    $status = implode(',',$status_sql);

$sql = mysql_query("SELECT * FROM table WHERE id IN ($status)");

보시다시피 첫 번째 함수는 각 배열 변수를 감싸고 배열 single quotes (\')을 함축합니다.

참고 : $statusSQL 문에는 작은 따옴표가 없습니다.

따옴표를 추가하는 더 좋은 방법이 있지만 이것이 효과가 있습니다.


또는$filter = "'" . implode("','",$status) . "'";
Alejandro Salamanca Mazuelo

1
이것은 주입 취약점입니다.
Mark Amery

문자열 탈출은 어디에 있습니까? 예를 들어 '문자열 안에서? SQL 주입이 취약합니다. PDO :: quote 또는 mysqli_real_escape_string을 사용하십시오.
18C

1

다음은 다른 데이터에 대해 명명 된 자리 표시 자와 함께 PDO를 사용하여 사용한 방법입니다. SQL 주입을 극복하기 위해 정수 값만 허용하고 다른 모든 값은 거부하도록 배열을 필터링합니다.

$owner_id = 123;
$galleries = array(1,2,5,'abc');

$good_galleries = array_filter($chapter_arr, 'is_numeric');

$sql = "SELECT * FROM galleries WHERE owner=:OWNER_ID AND id IN ($good_galleries)";
$stmt = $dbh->prepare($sql);
$stmt->execute(array(
    "OWNER_ID" => $owner_id,
));

$data = $stmt->fetchAll(PDO::FETCH_ASSOC);

-3

SQL 삽입을 방지하는 기본 방법은 다음과 같습니다.

  • 준비된 명령문 및 매개 변수화 된 쿼리 사용
  • 안전하지 않은 변수 에서 특수 문자를 이스케이프 처리

준비된 문과 매개 변수화 된 쿼리를 사용하는 것이 더 좋은 방법으로 간주되지만 이스케이프 문자 방법을 선택하면 아래 예제를 시도해 볼 수 있습니다.

array_map각 요소에 작은 따옴표를 추가하는 데 사용하여 쿼리를 생성 할 수 있습니다 $galleries.

$galleries = array(1,2,5);

$galleries_str = implode(', ',
                     array_map(function(&$item){
                                   return "'" .mysql_real_escape_string($item) . "'";
                               }, $galleries));

$sql = "SELECT * FROM gallery WHERE id IN (" . $galleries_str . ");";

생성 된 $ sql var는 다음과 같습니다.

SELECT * FROM gallery WHERE id IN ('1', '2', '5');

참고 : mysql_real_escape_string여기 문서에 설명 된대로 PHP 5.5.0에서 더 이상 사용되지 않으며 PHP 7.0.0에서 제거되었습니다. 대신 MySQLi 또는 PDO_MySQL 확장을 사용해야합니다. 자세한 정보는 MySQL : API 안내서 및 관련 FAQ 선택을 참조하십시오. 이 기능의 대안은 다음과 같습니다.

  • mysqli_real_escape_string ()

  • PDO :: quote ()


4
이 답변은 다른 답변과 비교하여 새로운 것을 추가 할뿐만 아니라 수년간 게시 된 SQL 주입에 대한 경고가 있었음에도 불구하고 주입 취약점입니다.
Mark Amery
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.