Hah. Unfortunately, it almost always depends on the monetary and managerial decision for what's appropriate, but "it is very tedious" is not generally considered a valid engineering concern -- it is but an excuse to appropriately refactor the code.

Meanwhile, the question requests non-PreparedStatement methods: in short, if you cannot offload the work to a library (e.g. prepared statements), then the only other method is to do it yourself for each "injected" item. Either way, the work of checking the input must be performed. The sole question is where it is done, and what programmer's expertise made the input validation code.

For example, consider a simple SELECT statement:

    sql = "SELECT * FROM mytable WHERE id = " + untrustedVar;

For completeness, we might assume the injection example where untrustedVar is a string like 1 OR 1 or 1; DROP TABLE mytable; Obviously this would result in unwanted behavior, vis-a-vis all rows returned to the caller, or a now missing database table:

SELECT * FROM mytable WHERE id = 1;
DROP mytable;

Obligatory XKCD reference

In this case, you could let the language semantics at least ensure that unstrustedVar is an integer, perhaps in your function definition:

String[] selectRowById ( int untrustedVar ) { ...

Or if it's a string, you might do this with a regex like:

Pattern valid_id_re = Pattern.compile('^\d{1,10}$');  // ensure id is between 1 and 10 billion
Matcher m = valid_id_re.matcher( unstrustedVar );
if ( ! m.matches() )
    return null;

But if you have longer inputs that do not have any grammar or structure guarantees (e.g., a web form textarea), then you will need to do the lower-level character replacements to escape the potentially bad characters. Per statement. Per variable. Per database flavor (PostgreSQL, Oracle, MySQL, SQLite, etc.). This ... is a can of worms.

The upside, of course, is that if you are not using prepared statements, and no one has yet done the work to otherwise avoid SQL injection attacks for your application, you have no where to go but up.

Meanwhile, I urge, urge, urge you to reconsider your stance on "We can't use prepared statements because reasons." More to the point, and as Gord Thompson correctly points out in the comments below, "it will be a considerable amount of work in any case, so why not just do it right and be done with it?"


Edit

After writing the above, it occurred to me that some might think that merely writing a prepared statement = better security. Actually, it's prepared statements with bound parameters that ups the security. For example, one could write this:

String sql = "SELECT * FROM mytable WHERE id = " + untrustedVar;
PreparedStatment pstmt = dbh.prepareStatement( sql );
return pstmt.executeQuery();

At this point, you've done little more than prepare a statement that has already been injected with malicious code. Instead, consider the actual binding of parameters:

String sql = "SELECT * FROM mytable WHERE id = ?";  // Raw string; never touched by "tainted" variable
PreparedStatment pstmt = dbh.prepareStatement( sql );
pstmt.setObject(1, p);  // Perform the actual binding.
return pstmt.executeQuery();

This latter example does two things. It first creates a known safe format, and sends that to the DB for preparation. Then, after the DB has returned a handle to the prepared statement, do we bind the variables, and finally execute the statement.

Answer from hunteke on Stack Overflow
Top answer
1 of 1
7

Hah. Unfortunately, it almost always depends on the monetary and managerial decision for what's appropriate, but "it is very tedious" is not generally considered a valid engineering concern -- it is but an excuse to appropriately refactor the code.

Meanwhile, the question requests non-PreparedStatement methods: in short, if you cannot offload the work to a library (e.g. prepared statements), then the only other method is to do it yourself for each "injected" item. Either way, the work of checking the input must be performed. The sole question is where it is done, and what programmer's expertise made the input validation code.

For example, consider a simple SELECT statement:

    sql = "SELECT * FROM mytable WHERE id = " + untrustedVar;

For completeness, we might assume the injection example where untrustedVar is a string like 1 OR 1 or 1; DROP TABLE mytable; Obviously this would result in unwanted behavior, vis-a-vis all rows returned to the caller, or a now missing database table:

SELECT * FROM mytable WHERE id = 1;
DROP mytable;

Obligatory XKCD reference

In this case, you could let the language semantics at least ensure that unstrustedVar is an integer, perhaps in your function definition:

String[] selectRowById ( int untrustedVar ) { ...

Or if it's a string, you might do this with a regex like:

Pattern valid_id_re = Pattern.compile('^\d{1,10}$');  // ensure id is between 1 and 10 billion
Matcher m = valid_id_re.matcher( unstrustedVar );
if ( ! m.matches() )
    return null;

But if you have longer inputs that do not have any grammar or structure guarantees (e.g., a web form textarea), then you will need to do the lower-level character replacements to escape the potentially bad characters. Per statement. Per variable. Per database flavor (PostgreSQL, Oracle, MySQL, SQLite, etc.). This ... is a can of worms.

The upside, of course, is that if you are not using prepared statements, and no one has yet done the work to otherwise avoid SQL injection attacks for your application, you have no where to go but up.

Meanwhile, I urge, urge, urge you to reconsider your stance on "We can't use prepared statements because reasons." More to the point, and as Gord Thompson correctly points out in the comments below, "it will be a considerable amount of work in any case, so why not just do it right and be done with it?"


Edit

After writing the above, it occurred to me that some might think that merely writing a prepared statement = better security. Actually, it's prepared statements with bound parameters that ups the security. For example, one could write this:

String sql = "SELECT * FROM mytable WHERE id = " + untrustedVar;
PreparedStatment pstmt = dbh.prepareStatement( sql );
return pstmt.executeQuery();

At this point, you've done little more than prepare a statement that has already been injected with malicious code. Instead, consider the actual binding of parameters:

String sql = "SELECT * FROM mytable WHERE id = ?";  // Raw string; never touched by "tainted" variable
PreparedStatment pstmt = dbh.prepareStatement( sql );
pstmt.setObject(1, p);  // Perform the actual binding.
return pstmt.executeQuery();

This latter example does two things. It first creates a known safe format, and sends that to the DB for preparation. Then, after the DB has returned a handle to the prepared statement, do we bind the variables, and finally execute the statement.

🌐
DigitalOcean
digitalocean.com › community › tutorials › sql-injection-in-java
SQL Injection in Java and How to Easily Prevent it | DigitalOcean
August 3, 2022 - In the above scenario, we have used the boolean expression to perform SQL Injection. There are some other ways to do SQL Injection. In the next section, we will see ways to prevent SQL injection in our Java application. The simplest solution is to use PreparedStatement instead of Statement to ...
Discussions

sql injection - Can you protect against SQLi without using prepared statements? - Information Security Stack Exchange
I am learning about SQLi (SQL injection) and I know that the solution to avoid them is prepared statements. However, does this mean that without them we are sure that we can get hacked? Are prepared More on security.stackexchange.com
🌐 security.stackexchange.com
Are prepared statements 100% safe against SQL injection? - Information Security Stack Exchange
I have never heard of any of these attacks on prepared statements on real databases in the field and strongly suggest using bound parameters to prevent SQL injection. Without bound parameters or input sanitation, its trivial to do SQL injection. More on security.stackexchange.com
🌐 security.stackexchange.com
May 21, 2012
java - Preventing SQL Injection in JDBC without using Prepared Statements - Stack Overflow
I am aware that using Prepared Statements is the best way to protect against SQL Injection (and syntax errors due to unescaped characters in unchecked input). My current situation is that I am writing some Java code to move data from one third party application to another. More on stackoverflow.com
🌐 stackoverflow.com
java - How to avoid SQL injection without PreparedStatements - Stack Overflow
This means that I don't know about ... to make prepared statements. I also can't fetch all data and filter it in my code because of performance issues. This is the only way I can talk to it (efficiently). ... Why do people still keep trying to come up with their own ways to prevent sql injection... More on stackoverflow.com
🌐 stackoverflow.com
🌐
Stack Overflow
stackoverflow.com › questions › 37135920 › how-to-prevent-sql-injection-without-prepared-statement
java - How to prevent sql injection without prepared statement? - Stack Overflow
A possible answer without prepared statement is check if in the field are present certain "key words and symbols" and deny the possibilities to insert them, for example: + , " select delete table drop merge union all dual group by having ...
🌐
Acunetix
acunetix.com › how-to-prevent-sql-injections-java
How To Prevent SQL Injections in Java | Acunetix
March 5, 2025 - To prevent SQL Injection attacks in Java, you must treat user input passed to the SQL queries as untrusted and avoid dynamic SQL queries created using simple string concatenation.
🌐
StackHawk
stackhawk.com › stackhawk, inc. › vulnerabilities and remediation › preventing sql injection in java
Java SQL Injection Guide: Examples and Prevention
January 10, 2025 - The top advice you can adopt to avoid SQL injections—and also other security threats—is to never trust user input. In practice, that means never concatenating data you get from users, be it from form fields, URL parameters, or other sources. ... Java is a statically typed language.
🌐
TechTarget
techtarget.com › searchsecurity › feature › How-to-prevent-SQL-injection-with-prepared-statements
How to prevent SQL injection with prepared statements | TechTarget
Instead of building up a SQL statement by concatenating strings and user-supplied input, prepared statements are constructed by using a parameterized SQL statement that has placeholder tokens (in most SQL dialects, this placeholder is a ?) and a list of the values that should be used for those parameters. The important difference with prepared statements in our vulnerable example above is that prepared statements never concatenate the values and the SQL. The separation is always maintained. Let's see an example in Java.
🌐
Okta Security
sec.okta.com › articles › 2020 › 12 › sql-injection-java-practices-avoid
SQL Injection in Java: Practices to Avoid | Okta Security
December 1, 2020 - JdbcTemplates or JPAs. Along with using · PreparedStatements, make sure to use placeholders( ?) in SQL queries to pass the user input. Never use string concatenation, for example, to generate dynamic SQL.
Find elsewhere
🌐
Security Journey
securityjourney.com › post › how-to-prevent-sql-injection-vulnerabilities-how-prepared-statements-work
How to prevent SQL Injection Vulnerabilities: How Prepared Statements Work
December 24, 2025 - Thus, the query will be immune to SQL Injection vulnerabilities for that data. Fig 2. Oversimplified representation of SQL prepared statements processing · The JDBC API has a class called PreparedStatement that can be used to safely handle user input as part of an SQL command. Here are a few examples: SELECT Statement (Source: https://bobby-tables.com/java...
Top answer
1 of 3
5

Not sure what escapeSimple does exactly, but if it behaves like mysqli_real_escape_string then it's ok, as long as you are not using some weird character encoding or forget to set it correctly (UTF8 should be ok by default, as far as I know).

Remember that using MD5 or any other simple hashing function is considered bad practice today, and you should use better functions which take more resources to compute (and with salts), for example bcrypt, argon2, etc.

I also don't like that == in the comparison, you should always use === unless you have a good reason to want type juggling. Otherwise you are going to get into trouble and something like this might happen: https://stackoverflow.com/questions/22140204/why-md5240610708-is-equal-to-md5qnkcdzo By the way, there might also be timing attacks to consider (see Alexander O'Mara's comment below).

That said, your question was about avoiding SQL injection without prepared statements. Yes, it's definitely possible to avoid it, if you are very careful and make sure every statement is correctly constructed and its parts correctly escaped. However in some cases it might be tricky, and to avoid any mistakes of course prepared statements are the way to go.

2 of 3
2

If escapeSimple behaves like mysqli_real_escape_string then there is one important caveat to be aware of:

mysqli::real_escape_string -- mysqli_real_escape_string — Escapes special characters in a string for use in an SQL statement, taking into account the current charset of the connection

(emphasis is mine)

So you need an active connection for this charset-aware function. But I don't see in your code how escapeSimple relates to an existing, open DB connection. So does it really behave the way you would expect ?

This type of coding is not advisable for several reasons, one being the lack of portability. Software nowadays is often designed to run on different DBMSes (eg Mysql or SQLite), that is why we have abstraction classes like PDO etc.

But this code is problematic for other reasons: it uses mysql_fetch_array, which is deprecated (PHP5.5.0). In fact what you are showing is the plain old mysql functions.

At the very least it means your environment is not up to date. Your PHP install may carry a number of security vulnerabilities that won't be fixed as it is EOL. Thus, the real vulnerability could come from something else than this particular code.

Top answer
1 of 2
62

Guarantee of 100% safe from SQL injection? Not going to get it (from me).

In principle, your database (or library in your language that is interacting with the db) could implement prepared statements with bound parameters in an unsafe way susceptible to some sort of advanced attack, say exploiting buffer overflows or having null-terminating characters in user-provided strings, etc. (You could argue that these types of attacks should not be called SQL injection as they are fundamentally different; but that's just semantics).

I have never heard of any of these attacks on prepared statements on real databases in the field and strongly suggest using bound parameters to prevent SQL injection. Without bound parameters or input sanitation, its trivial to do SQL injection. With only input sanitation, its quite often possible to find an obscure loophole around the sanitation.

With bound parameters, your SQL query execution plan is figured out ahead of time without relying on user input, which should make SQL injection not possible (as any inserted quotes, comment symbols, etc are only inserted within the already compiled SQL statement).

The only argument against using prepared statements is you want your database to optimize your execution plans depending on the actual query. Most databases when given the full query are smart enough to do an optimal execution plan; e.g., if the query returns a large percentage of the table, it would want to walk through the entire table to find matches; while if its only going to get a few records you may do an index based search [1].

EDIT: Responding to two criticisms (that are a tad too long for comments):

First, as others noted yes every relational database supporting prepared statements and bound parameters doesn't necessarily pre-compile the prepared statement without looking at the value of the bound parameters. Many databases customarily do this, but its also possible for databases to look at the values of the bound parameters when figuring out the execution plan. This isn't a problem, as the structure of the prepared statement with separated bound parameters, makes it easy for the database to cleanly differentiate the SQL statement (including SQL keywords) from data in bound parameters (where nothing in a bound parameter will be interpreted as an SQL keyword). This is not possible when constructing SQL statements from string concatenation where variables and SQL keywords would get intermixed.

Second, as the other answer points out, using bound parameters when calling an SQL statement at one point in a program will safely prevent SQL injection when making that top-level call. However, if you have SQL injection vulnerabilities elsewhere in the application (e.g., in user-defined functions you stored and run in your database that you unsafely wrote to construct SQL queries by string concatenation).

For example, if in your application you wrote pseudo-code like:

sql_stmt = "SELECT create_new_user(?, ?)"
params = (email_str, hashed_pw_str)
db_conn.execute_with_params(sql_stmt, params)

There can be no SQL injection when running this SQL statement at the application level. However if the user-defined database function was written unsafely (using PL/pgSQL syntax):

CREATE FUNCTION create_new_user(email_str, hashed_pw_str) RETURNS VOID AS
$$
DECLARE
   sql_str TEXT;
BEGIN
     sql_str := 'INSERT INTO users VALUES (' || email_str || ', ' || hashed_pw_str || ');'
     EXECUTE sql_str;
END;
$$
LANGUAGE plpgsql;

then you would be vulnerable to SQL injection attacks, because it executes an SQL statement constructed via string concatenation that mixes the SQL statement with strings containing the values of user defined variables.

That said unless you were trying to be unsafe (constructing SQL statements via string concatenation), it would be more natural to write the user-defined in a safe way like:

CREATE FUNCTION create_new_user(email_str, hashed_pw_str) RETURNS VOID AS
$$
BEGIN
     INSERT INTO users VALUES (email_str, hashed_pw_str);
END;
$$
LANGUAGE plpgsql;

Further, if you really felt the need to compose an SQL statement from a string in a user defined function, you can still separate data variables from the SQL statement in the same way as stored_procedures/bound parameters even within a user defined function. For example in PL/pgSQL:

CREATE FUNCTION create_new_user(email_str, hashed_pw_str) RETURNS VOID AS
$$
DECLARE
   sql_str TEXT;
BEGIN
     sql_str := 'INSERT INTO users VALUES($1, $2)'
     EXECUTE sql_str USING email_str, hashed_pw_str;
END;
$$
LANGUAGE plpgsql;

So using prepared statements is safe from SQL injection, as long as you aren't just doing unsafe things elsewhere (that is constructing SQL statements by string concatenation).

2 of 2
28

100% safe? Not even close. Bound parameters (prepared statement-wise or otherwise) effectively can prevent, 100%, one class of SQL injection vulnerability (assuming no db bugs and a sane implementation). In no way do they prevent other classes. Note that PostgreSQL (my db of choice) has an option to bind parameters to ad hoc statements which saves a round trip regarding prepared statements if you don't need certain features of these.

You have to figure that many large, complex databases are programs in themselves. The complexity of these programs varies quite a bit, and SQL injection is something that has to be looked out for inside programming routines. Such routines include triggers, user-defined functions, stored procedures, and the like. It is not always obvious how these things interact from an application level as many good dba's provide some degree of abstraction between the application access level and the storage level.

With bound parameters, the query tree is parsed, then, in PostgreSQL at least, the data is looked at in order to plan. The plan is executed. With prepared statements, the plan is saved so you can re-execute the same plan with different data over and over (this may or may not be what you want). But the point is that with bound parameters, a parameter cannot inject anything into the parse tree. So this class of SQL injection issue is properly taken care of.

But now we need to log who writes what to a table, so we add triggers and user-defined functions to encapsulate the logic of these triggers. These pose new issues. If you have any dynamic SQL in these, then you have to worry about SQL injection there. The tables they write to may have triggers of their own, and so forth. Similarly a function call might invoke another query which might invoke another function call and so forth. Each of these is independently planned of the main tree.

What this means is that if I run a query with a bound parameter like foo'; drop user postgres; -- then it cannot directly implicate the top-level query tree and cause it to add another command to drop the postgres user. However, if this query calls another function directly or not, it is possible that somewhere down the line, a function will be vulnerable and the postgres user will be dropped. The bound parameters offered no protection to secondary queries. Those secondary queries need to make sure they use bound parameters too to the extent possible, and where not, will need to use appropriate quoting routines.

The rabbit hole goes deep.

BTW for a question on Stack Overflow where this problem is apparent, see https://stackoverflow.com/questions/37878426/conditional-where-expression-in-dynamic-query/37878574#37878574

Also a more problematic version (due to limitation on utility statements) at https://stackoverflow.com/questions/38016764/perform-create-index-in-plpgsql-doesnt-run/38021245#38021245

🌐
Stack Overflow
stackoverflow.com › questions › 39162885 › how-to-avoid-sql-injection-without-preparedstatements
java - How to avoid SQL injection without PreparedStatements - Stack Overflow
<Query> <Selection> <sqlwhere> id IN ( SELECT something FROM some_table WHERE something_different LIKE '%?%' ESCAPE '\' ) </sqlwhere> </Selection> </Query> Then, use PreparedStatement using this SQL and set the parameters as per user's input.
🌐
JDriven
jdriven.com › blog › 2017 › 10 › sql-injection-prepared-statement-not-enough
SQL injection: when a prepared statement is not enough... - JDriven Blog
October 18, 2017 - The application is not properly escaping the quotes which enables you to modify the query and list all the blogs. When you read how to prevent SQL injections the most common advise is: "Use a prepared statement or parameterized queries." If we change our repository method accordingly we will get:
🌐
Baeldung
baeldung.com › home › persistence › sql injection and how to prevent it?
SQL Injection and How to Prevent It? | Baeldung
March 18, 2026 - Well… not so fast. Without even considering Turing’s halting problem, there are other aspects we must consider: Stored Procedures: These are also prone to SQL Injection issues; whenever possible please apply sanitation even to values that will be sent to the database via prepared statements
🌐
OWASP Cheat Sheet Series
cheatsheetseries.owasp.org › cheatsheets › SQL_Injection_Prevention_Cheat_Sheet.html
SQL Injection Prevention - OWASP Cheat Sheet Series
The difference between prepared statements and stored procedures is that the SQL code for a stored procedure is defined and stored in the database itself, then called from the application. Since prepared statements and safe stored procedures are equally effective in preventing SQL injection, your organization should choose the approach that makes the most sense for you.
🌐
Medium
medium.com › swlh › preventing-sql-injection-attack-with-java-prepared-statement-259611281e4d
Preventing SQL Injection Attack With Java Prepared Statement | by Ming Hong | The Startup | Medium
May 24, 2020 - In this post, we will discuss how SQL Injection attacks work and how to prevent it using Java prepared statements. SQL injection attacks are initiated by injecting malicious SQL statements into the queries that the application makes to its database. It takes advantage of improperly sanitized data input to insert some extra characters in order to bypass authentication or gain access to sensitive data stored in the database. Without further wasting any time, let’s explore SQL Injection attack in more detail.
🌐
SEI CERT
wiki.sei.cmu.edu › confluence › display › java › IDS00-J.+Prevent+SQL+injection
IDS00-J. Prevent SQL injection - SEI CERT Oracle Coding Standard for Java - Confluence
February 12, 2018 - The JDBC library provides an API for building SQL commands that sanitize untrusted data. The java.sql.PreparedStatement class properly escapes input strings, preventing SQL injection when used correctly. This code example modifies the doPrivilegedAction() method to use a PreparedStatement instead of java.sql.Statement.