Skip to content

Consider disabling PDO emulated query parser by default #19895

Open

Description

@bobvandevijver

Description

This is a copy of doctrine/dbal#7146 opened at doctrine/dbal, as the same change can be considered in php-src as well.

I've been linked to a quite interesting article regarding possible SQL injection when using PDO: https://slcyber.io/assetnote-security-research-center/a-novel-technique-for-sql-injection-in-pdos-prepared-statements/. It describes a method that confuses the PDO emulated query parser into something that shouldn't happen with bound parameters, which in certain scenarios can be used to execute SQL injection.

This brings the question: why should we even want to use this emulation? From my understanding this is an artifact from the past, where the common consensus was that it would benefit performance as prepared statements would skip the query cache with older MySQL versions. But is that still relevant today?

Which is why I'd like to open a discussion: should pdo_mysql consider setting the PDO::ATTR_EMULATE_PREPARES to false by default?

Proof of concept with doctrine:

$address = $request->query->getString('q'); // http://somehost/some/page?q=?%00
dd($this
  ->getEntityManager()
  ->getConnection()
  ->executeStatement("SELECT * FROM address WHERE address1 LIKE '%$address%' AND city = ?", ['enschede'])
);

Which throws SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens, which is solved by adding ->option(PDO::ATTR_EMULATE_PREPARES, false) to the dbal driver configuration.

PHP Version

PHP 8.4.12 (cli) (built: Sep  7 2025 14:04:14) (NTS)
Copyright (c) The PHP Group
Built by Debian
Zend Engine v4.4.12, Copyright (c) Zend Technologies
    with Zend OPcache v8.4.12, Copyright (c), by Zend Technologies
    with Xdebug v3.4.5, Copyright (c) 2002-2025, by Derick Rethans

Operating System

No response

Activity

  1. DanielEScherzer commented on Sep 20, 2025

    @DanielEScherzer
    Member

    Cross link #19777

  2. DanielEScherzer commented on Sep 20, 2025

    @DanielEScherzer
    Member

    the POC

    $address = $request->query->getString('q'); // http://somehost/some/page?q=?%00
    dd($this
      ->getEntityManager()
      ->getConnection()
      ->executeStatement("SELECT * FROM address WHERE address1 LIKE '%$address%' AND city = ?", ['enschede'])
    );
    

    has a standard SQL injection - since the $address is just embedded directly in the statement without any escaping. Can you give an example where all inputs use prepared parameters?

  3. kamil-tekiela commented on Nov 9, 2026

    @kamil-tekiela
    Member

    There are several reasons for it:

    • Not all drivers support native prepared statements.
    • Prepare - execute issues two network trips vs a single query. For setups with high network latency, they benefit from this performance gain.

    However, the bigger issue for why it hasn't been changed is that it could break existing code if we did that now. Emulated prepares and prepared statements aren't 100% identical. In PHP 7.4(?), PDO_MySQL got a cleanup from Nikita in which the driver should report data in a similar way, but there are still behaviours that cannot be reconciled. For example, error reporting. In native prepares, error can be reported either from prepare or from execute, because both contact the server.

    Changing this requires discussion and consensus from the mailing list first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions