질문/조언질답게시판

모듈에서 불가피하게 커스텀쿼리를 사용하게 되는 상황이 되었습니다..

 

그런데 커스텀쿼리를 짜려고 해도 XE+RX1과 RX2를 분기를 해야하는 상황이 되더라고요

 

그래서 함수를 만들어 파라메터는 동일하게, 값도 동일하게 출력하는데, RX 버전에 따라서 쿼리 실행방식을 분기 하려고 합니다

 

함수를 한번 짜봤는데 보안상 문제가 있는게 확실하지만 어디를 보완해야 할지 감잡기가 어려워서 한번 올려봅니다

 

 

<?php
if(!defined('__XE__')) exit();

class Test
{
private $oDB;
private $isRX2;

public function __construct()
{
$this->oDB = DB::getInstance();
$this->isRX2 = (version_compare(__XE_VERSION__, '2.0', '>='));
}

public function _sqlExecute($sql, $variable = array())
{
if ($this->isRX2) {
// Rhymix 2 이상
$query = $this->oDB->prepare($sql);
$query->execute($variable);
$result = $query->fetchAll();
} else {
// 기존 코드
$sql = $this->oDB->addPrefixes($sql);
$sql_part = explode('?', $sql);

$new_sql = '';
for ($i = 0; count($sql_part) - 1 > $i; $i++) {
$var = (is_int($variable[$i])) ? $variable[$i] : sprintf("'%s'", $variable[$i]);
$new_sql .= $sql_part[$i] . $var;
}
$new_sql .= $sql_part[count($sql_part) - 1];

$query = $this->oDB->_query($new_sql);
$result = $this->oDB->_fetch($query);
}

return $result;
}
}

 

RX2는 의도한대로 짠거 같은데, 그 외 코드는 구구주먹식으로 짜서 보기 예쁘지는 않습니다 ㅎ;

 

까일때는 가슴아프지만 (흙..) 취약점을 열심히 까주시면 고맙겠습니다!

 

XE+RX1에도 prepared statement를 사용할 수 있으면 좋은데 말이죠..

  • profile

    addPrefixes 함수는 PDO 지원과 함께 추가되었으므로 addPrefixes를 사용할수 있다면 그냥 PDO를 사용하면 그만입니다.

  • profile profile
    앗 그렇군요..
    어느 글에서 RX1에서도 그런 함수를 사용하면 된다고 했던거 같은데 잘못봤던 것 같습니다
    감사합니다!
  • profile
    $var = (is_int($variable[$i])) ? $variable[$i] : sprintf("'%s'", $variable[$i]);
    이줄의 앞과 뒤가 차이가 없는 것 같은데요.
    뒷부분에 선언된 변수가 앞뒤 '를 붙여주는것이 필요한거라면
    "'{$variable[$i]}'"
    이런식으로 해보세요

    "" 으로 감싸고 그 안에 '' 적으면 '는 스트링으로 인식하지만 {}안에 선언된 항목은 변수로 선언됩니다.sprintf 함수 호출하실 필요 없습니다.
  • profile profile
    팁 감사합니다!
    "'" . $var . "'" 이런식으로도 해봤었는데, sprintf가 좀 깔끔해보여서 해당 방법을 사용했습니다
    그냥 단순하게 "{$var}" 으로 처리하는게 깔끔해보이네요!
  • profile profile

    " 와 ' 는 용도 구분하세요.

    같은 문자열을 나타내는 것이 아니라 '은 무조건 문자열만 "는 변수를 포함한 문자열을 출력합니다.

    $a = 'int';
    echo '{$a}값은 모르겠다'; // 출력값 : {$a}값은 모르겠다
    echo "{$a}값은 모르겠다"; // 출력값 : int값은 모르겠다

     

    그리고 strval()함수로 하면 int값이던 string 값이던 string 값으로 변환후 만들어주니 해당 변수 이용하는것도 알아보세요.

  • profile

    XE/RX1에는 addPrefixes() 없습니다. 이걸 별도로 구현하시려면 라이믹스 2.0의 addPrefixes() 소스를 복붙하는 방법밖에 없을 거예요. 물론 그렇게 하면 GPLv2 라이선스가 적용됩니다.

     

    숫자가 아닌 변수를 직접 쿼리문에 집어넣을 때는 $oDB->addQuotes() 함수를 사용하여 escape 처리해 주셔야 합니다. 안 그러면 SQL 인젝션은 기본이고, 따옴표 등 특수문자가 들어갈 때마다 에러납니다.

  • profile

    물음표로 쪼갰다가 다시 붙이는 방법은 깔끔하지 않아 보입니다. PHP의 다양한 내장함수들을 잘 활용하면 쪼갰다가 붙였다가 하는 원시적인(?) 기법은 거의 사용할 필요가 없게 됩니다. 예를 들어 ?를 %s로 바꾸기만 하면 쿼리문에 통째로 sprintf()를 쓸 수 있게 된다는 점을 이용해 보면 어떨까요?


    // 변수 목록 가공: 문자열인 경우 자동으로 따옴표 붙여주고 escape 처리

    $variable = array_map(function($var) {

        return is_numeric($var) ? $var : ("'" . $this->oDB->addQuotes($var) . "'");

    }, $variable);

    // 쿼리문의 ?를 모두 %s로 변경

    $sql = str_replace('?', '%s', $sql);

    // vsprintf()는 sprintf()와 달리 파라미터들을 한꺼번에 배열에 담아서 넘길 수 있음

    $sql = vsprintf($sql, $variable);

     

    실제로 prepared statement를 시뮬레이션하는 라이브러리들 중 상당수가 이렇게 sprintf()류의 함수를 사용하여 구현되어 있습니다. 파이썬의 MySQL Connector는 아예 처음부터 ?가 아닌 %s로 변수 자리를 표시하지요.

  • profile profile

    조언 감사합니다!

    말씀하신것을 기반으로 한번 코드를 고쳐보았습니다

     

    public function _sqlExecute($sql, $variable = array())
    {
    if($this->isRX2)
    {
    // Rhymix 2 이상
    $sql = str_replace('{DB_PREFIX}', '', $sql);
    $query = $this->oDB->prepare($sql);
    $query->execute($variable);
    $result = $query->fetchAll();
    }
    else
    {
    // 기존 코드
    $sql = str_replace('{DB_PREFIX}', $this->oDB->prefix, $sql);
    $variable = array_map(function($var) {
    return is_numeric($var) ? $var : ("'" . $this->oDB->addQuotes($var) . "'");
    }, $variable);
    $sql = str_replace('?', '%s', $sql);
    $sql = vsprintf($sql, $variable);
    
    $query = $this->oDB->_query($sql);
    $result = $this->oDB->_fetch($query);
    }
    
    return $result;
    }

     

    XE, RX1에서는 DB 접두사 처리를 해주는 함수가 없다보니 일단 위와 같이 코드를 짰고, 접두사는 별도 위치에 붙일 수 있게 만들어봤습니다.

    RX2 이상에서는 DB 접두사를 자동으로 붙여주니 해당 부분을 제거해주면 될거 같고요

     

    아래와 같이 사용하면 될거 같습니다

    SELECT * FROM {DB_PREFIX}member;

     

    라이믹스에 addPrefixes를 가져와볼까도 생각했지만, 분량이 좀 있기도 하고 간단하게 구현하는거라 굳이 필요없을거 같아서 일단 패스했습니다 ㅜㅜ

    세세한 설명과 코드 정말 감사합니다!!

  • profile profile
    나름 깔끔하네요.^^

    한 가지 더 주의하셔야 할 점은... INSERT, UPDATE, DELETE 등 특별히 가져올 결과가 없는 쿼리를 실행한 후 fetchAll()이나 _fetch()를 시도하면 오류가 나는 경우가 있습니다. 이 부분 꼼꼼하게 테스트해 보시기 바랍니다.
  • profile profile
    앗 그렇군요
    일단은 SELECT 에서만 사용할 예정이라서 큰 문제는 없지만 나중에 체크해서 문제가 발생하면 고쳐보도록 하겠습니다
    감사합니다~