ORM Injection
- ORM is when developers write code in an object-oriented language (like Python or PHP), and ORM translates those code operations into database queries (SQL).
For example, instead of writing
SELECT * FROM users WHERE id = 1, a developer might writeuser = User.find(1), and the ORM handles the translation. - The purpose of ORM:
- Reducing boilerplate code: ORM automates repetitive SQL tasks.
- Increasing productivity: Developers can focus on application logic.
- Ensuring consistency: ORM handles database operations uniformly.
- Enhancing maintainability: Changes to the database schema are easier to manage.
- Different Frameworks:
- Doctrine (PHP)
- Hibernate (Java)
- SQLAlchemy (Python)
- Entity Framework (C#)
- Active Record (Ruby on Rails
How ORM Works¶
- Maps objects in code to database tables.
- A
Userclass represents theusersdatabase table. - Class properties like
nameandemailcorrespond to table columns.
Common ORM Operations (CRUD)¶
use App\Models\User; // Create a new user $user = new User(); $user->name = 'Admin'; $user->email = '[email protected]'; $user->password = bcrypt('password'); $user->save();
- This code creates a new user and saves it to the database. The
bcrypt()function is used to securely hash the password before saving it.
use App\Models\User; // Find a user by ID $user = User::find(1); // Find all users $allUsers = User::all(); // Find users by specific criteria $admins = User::where('email', '[email protected]')->get();
- The function
find(1)retrieves the user with ID 1 by executing a SELECT SQL statement. - The function
all()retrieves all users by executing aSELECT * FROM usersSQL statement. -
The clause
where('email', '[email protected]')->get()retrieves users with the specified email by executing aSELECT * FROM users WHERE email = '[email protected]'SQL statement. -
SQL Injection:
-
ORM Injection
Identifying ORM Injection¶
- Manual code review: A thorough source code inspection can reveal raw query methods (such as
whereRaw()in Laravel) that incorporate user inputs directly. Look for concatenated strings or unescaped inputs in ORM methods, which can indicate injection points. - Automated scanning: Use security scanning tools that are designed to detect ORM injection vulnerabilities. These tools analyse the codebase to identify patterns that could lead to injection, such as dynamic query construction or improper input handling.
- Input validation testing: Perform manual testing by injecting payloads into application inputs to see if they affect the underlying ORM query. For example, injecting SQL control characters or keywords to determine if they alter the execution of the query.
- Error-based testing: Enter deliberately incorrect or malformed data to trigger errors. Detailed error messages can provide insights into the structure of the underlying queries and indicate potential vulnerabilities.
- You can also inspect the webpage by verifying cookies and reviewing the source code or using BurpSuite to analyze HTTP headers.

ORM Injection - Weak Implementation¶
Vulnerable code¶
public function searchBlindVulnerable(Request $request) { $users = []; $email = $request->input('email'); $users = Admins::whereRaw("email = '$email'")->get(); if ($users) { return view('user', ['users' => $users]); } else { return view('user', ['message' => 'User not found']); } }
The searchBlindVulnerable() function is designed to retrieve user records based on the email parameter provided by the user. Here's a detailed breakdown of the function:
- Retrieve input: The function captures the
emailparameter from the HTTP request. This is done using the input method of the$requestobject. - Construct query: The function then constructs a raw SQL query using Laravel's
whereRaw()method, which directly incorporates the email value into the SQL statement. - Execute query: This query is executed, storing the result in the
$usersvariable. - Return view: Finally, the function returns a view. If the
$usersarray is not empty, it passes the user data to the view; otherwise, it passes a "User not found" message.
Secure Code¶
public function searchBlindSecure(Request $request) { $email = $request->input('email'); $users = User::where('email', $email)->get(); if (isset($users) && count($users) > 0) { return view('user', ['users' => $users]); } else { return view('user', ['message' => 'User not found']); } }
- Retrieving input: The function captures the email parameter from the HTTP request using the input method of the
$requestobject. This is the same as in the vulnerable version. - Constructing the query securely: Instead of using
whereRaw(), the secure version uses Eloquent'swhere()method. This method automatically escapes the input, thus preventing SQL injection. Thewhere()method constructs a parameterised query behind the scenes, ensuring that user input is not directly included in the SQL statement. - Executing the query: The query is executed, and the result is stored in the
$usersvariable. Because the query is parameterised, the input is sanitised, which means it cannot break the SQL query structure. - Returning the view: Finally, the function returns a view. If the
$usersarray contains data, it passes the user data to the view; otherwise, it shows an error message. This logic ensures that only valid data is processed and displayed.
Best Practices¶
- Input validation: Always validate user inputs on both client and server sides. Ensure that the data conforms to the expected format, type, and length. Use regular expressions and built-in validation functions to enforce strong input validation rules.
- Parameterised queries: Use parameterised queries (prepared statements) to interact with the database. This approach ensures that user inputs are treated as data, not executable code. Avoid concatenating user inputs directly into SQL queries.
- ORM usage: Utilise ORM built-in tools to interact with the database. ORMs abstract SQL queries and help prevent SQL injection by handling user inputs securely. Ensure that the ORM is configured correctly and that any custom SQL queries are parameterised.
- Escaping and sanitisation: Escape user inputs to remove any special characters used for injection attacks. Sanitise inputs to remove potentially harmful data before processing or storing it.
- Allowlist input: Implement an allowlist approach for input validation. Only allow specific, expected values and reject everything else. This method is more secure than blocklisting known bad values, which can be incomplete.
Doctrine (PHP)¶
$query = $entityManager->createQuery('SELECT u FROM User u WHERE u.username = :username'); $query->setParameter('username', $username); $users = $query->getResult();
Use prepared statements with parameterised queries to prevent SQL injection attacks.
SQLAlchemy (Python)¶
from sqlalchemy.orm import sessionmaker Session = sessionmaker(bind=engine) session = Session() user = session.query(User).filter_by(username=username).first()
Leverage SQLAlchemy's ORM and Query API to use parameterised queries, which automatically handle escaping and parameter binding.
Hibernate (Java)¶
String hql = "FROM User WHERE username = :username"; Query query = session.createQuery(hql); query.setParameter("username", username); List results = query.list();
Use named parameters with Hibernate's Query API to ensure inputs are adequately bound and escaped.
Entity Framework (.NET)¶
Employ parameterised queries in Entity Framework to secure database interactions and mitigate the risk of SQL injection vulnerabilities.