Implementing a “configurable” joining system, safely

前端 未结 2 1077
予麋鹿
予麋鹿 2021-01-13 07:31

Background

Hello, I\'m developing an experimental/educational tool in PHP and MySQL. I\'m new to SQL, but I want to do things the right way from th

相关标签:
2条回答
  • 2021-01-13 07:51

    Assuming that user input is limited to only selecting the tables and fields (i.e. no additional conditions), you should be fine with your approach; sounds interesting :)

    One thing I would like to add is that certain joins are better than others. For example, joining two tables using their primary keys (or other indexes) will perform way better than two unrelated columns for which a full table scan is required.

    This all depends on how big the tables are in the first place; for less than a few thousand records, you should be okay; anything beyond serious contemplation is in place :)

    0 讨论(0)
  • 2021-01-13 08:01

    You're doing so much right that I actually feel guilty pointing out that you're doing something wrong! :)

    You can only use prepared statements to parameterise field values—not SQL identifiers such as column or table names. Therefore you won't be able to pass A.x, B.z etc. into your JOIN criteria by way of prepared statement parameters: you must instead do what feels terribly wrong and directly concatenate them into your SQL string.

    However, all is not lost. In some vague order of preference, you can:

    1. Present the user with an option list, from which you subsequently reassemble the SQL:

      <select name="join_a">
        <option value="1">x</option>
        <option value="2">y</option>
      </select>
      <select name="join_b">
        <option value="1">z</option>
        <option value="2">y</option>
      </select>
      

      Then your form handler:

      switch ($_POST['join_a']) {
        case 1:  $acol = 'x'; break;
        case 2:  $acol = 'y'; break;
        default: die('Invalid input');
      }
      switch ($_POST['join_b']) {
        case 1:  $bcol = 'z'; break;
        case 2:  $bcol = 'y'; break;
        default: die('Invalid input');
      }
      
      $sql .= "FROM A JOIN B ON A.$acol = B.$bcol";
      

      This approach has the advantage that, short of compromising PHP (in which case you'll have far bigger concerns than SQL injection), arbitrary SQL absolutely cannot find its way into your RDBMS.

    2. Ensure the user input matches one of the expected values:

      <select name="join_a">
        <option>x</option>
        <option>y</option>
      </select>
      <select name="join_b">
        <option>z</option>
        <option>y</option>
      </select>
      

      Then your form handler:

      if (!in_array($_POST['join_a'], ['x', 'y'])
       or !in_array($_POST['join_b'], ['z', 'y']))
         die('Invalid input');
      
      $sql .= "FROM A JOIN B ON A.$_POST[join_a] = B.$_POST[join_b]";
      

      This approach relies on PHP's in_array function for safety (and also exposes to the user your underlying column names, but given your application I doubt that's a concern).

    3. Perform some input cleansing, such as:

      mb_regex_encoding($charset); // charset of database connection
      $sql .= 'FROM A JOIN B ON A.`' . mb_ereg_replace('`', '``', $_POST['join_a']) . '`'
                          . ' = B.`' . mb_ereg_replace('`', '``', $_POST['join_b']) . '`'
      

      Whilst we here quote the user input and replace any attempt by the user to escape from that quoting, this approach could be full of all sorts of flaws and vulnerabilities (in either PHP's mb_ereg_replace function or MySQL's handling of specially crafted strings within a quoted identifier).

      It is far better if at all possible to use one of the above methods to avoid inserting user-defined strings into one's SQL altogether.

    0 讨论(0)
提交回复
热议问题