ORM (Object relational mapping) Injections
Introdution#
- With advancements in cyber security, many developers have adopted object-relational mapping (ORM) to mitigate SQL injection attacks. While ORM is intended to simplify database interactions and improve security, the threat of injection attacks is still not over.
- ORM injection occurs when attackers exploit vulnerabilities within ORM frameworks, allowing them to execute arbitrary queries.
Understanding ORM#
- ORM is a programming technique that facilitates data conversion between incompatible systems using object-oriented programming languages. It allows developers to interact with a database using the programming language’s native syntax, making data manipulation more intuitive and reducing the need for extensive SQL queries.
- ORM is particularly beneficial when complex data interactions are required, as it simplifies database access and promotes code reusability.
Purpose:#
- ORM serves as a bridge between the object-oriented programming model and the relational database model. The primary purpose of an ORM is to abstract the database layer, allowing developers to work with objects rather than tables and rows. This abstraction layer helps in:
- Reducing the boilerplate code
- increasing productivity, focusing on bussiness logic without worrying about the db interactions.
- Ensuring consistency, reducing risk of errors.
- Enhancing the maintainability, as changes in schema are easier to manage, as they can be reflected in the object model without extensive code modifications.
Commonly used ORM Frameworks#
- Several ORM frameworks are widely used in the development community, each catering to different programming languages and environments.
- Doctrine (PHP)
- Hibernate (java)
- SQLAlchemy (Python): offers sql toolkit and orm system that allows devs to use raw sql when needed while still providing the benefits of am orm.
- Entiry framework (C#): Microsoft’s ORM framework for .NET applications.
- Active Record (Ruby on rails): default ORM for ruby on rails applications.
How ORM works#
- ORM is a technique that simplifies data interaction in an application by mapping objects in code to database tables.
- For instance, using Laravel’s Eloquent ORM, you might define a model class like this:
namespace App\Models; use Illuminate\Database\Eloquent\Model; class User extends Model { protected $table = 'users'; protected $fillable = [ 'name', 'email', 'password', ]; // Other Eloquent model configurations can go here... } - Common ORM Operations (Create, Read, Update, Delete)
- Create: Creating new records in the database involves instantiating a new model object, setting its properties, and saving it to the database.
use App\Models\User; // Create a new user $user = new User(); $user->name = 'Admin'; $user->email = 'admin@example.com'; $user->password = bcrypt('password'); // hashing password $user->save();- Read: Reading records from the database involves retrieving data using various Eloquent methods.
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', 'admin@example.com')->get();
Configuring the Environment#
- Since we are using Laravel in this project, we will briefly explain how to configure Eloquent ORM (Laravel-based). Eloquent ORM is the default ORM included with Laravel, which provides a beautiful, simple Active Record implementation for working with your database.
- First, we need to install Laravel using Composer.
- Open your terminal and run the command
composer create-project --prefer-dist laravel/laravel thm-project, where thm-project is the name of your project. - Configure Database Credentials, Laravel uses the .env file to store environment variables, including database credentials.
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=your_database_name DB_USERNAME=your_database_user DB_PASSWORD=your_database_password - Setting up Migrations: To create a migration, we can use the Artisan command-line tool that comes with Laravel. You can run the command
php artisan make:migration create_users_table --create=usersto generate a migration for theuserstable: - This command generates a migration file in the database/migrations directory. The migration file contains methods to define the structure of the users table.
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateUsersTable extends Migration { public function up() { Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('email')->unique(); $table->string('password'); $table->timestamps(); }); } public function down() { Schema::dropIfExists('users'); } } - In the above code, the up() method defines the structure of the users table. It includes columns for ID, name, email, password, and timestamps. Conversely, the down() method drops the users table if the migration is rolled back.
- After defining the migration, run the command
php artisan migrateto apply the migration and create the users table in the database. This command will execute the up() method in the migration file and create the users table with the specified columns in your database.
Identifying ORM Injection#
- Identifying ORM injection vulnerabilities involves examining how user inputs are handled within ORM queries. These vulnerabilities typically arise when user inputs are directly embedded into ORM query methods without proper sanitisation or validation.
- Indicators of potential ORM injection issues include the use of dynamic queries that concatenate user inputs, raw query execution methods, and insufficient use of parameterised queries.
Techniques for Testing ORM Injection#
Manual code review: May reveal raw query methods (such as whereRaw() in Laravel) that incorporate user inputs directly, concatinated strings or unescaped inputs in ORM methods, which may indicate injection points.
Automated scanning: Security scanning tools analyse the codebase to identify patterns that could lead to injection, such as dynamic query construction or improper input handling.
nput validation testing: Perform manual testing by injecting payloads into application inputs to see if they affect the underlying ORM query.
Error-based testing: Enter deliberately incorrect or malformed data to trigger errors.
Framework ORM Library Common Vulnerable Methods Laravel - Eloquent ORM - whereRaw(), DB::raw() Ruby on Rails - Active Record - where("name = '#{input}'") Django - Django ORM - extra(), raw() Spring - Hibernate - createQuery() with concatenation Node.js - sequelize - sequelize.query()
Exploring the Target Application#
Techniques to identify the Framework#
- Verifyingi cookies: Framworks often use unique naming conventions or formats for session cookies, which provide clues about the underlying technology.
- Reviewing the source code: Lokk through the HTML source code for comments, meta tags, or any embedded scripts that might reveal framework-specific-signature. However this method is not always conclusive.
- Analysing HTTP Headers: HTTP headers can sometimes contain information about the server and framework.
- Login and error pages: Authentication pages and error messages can sometimes reveal the framework.
ORM Injection - with Weak implementation#
- Lets have a look at implementation of function
searchBlineVulnerable, used to retrive the user records based on query.
<?php
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']);
}
}
?>
- Above code is vulnerable, as user input passed directly to query without sanitization. If we have a closer look, here laravel php orm framework is used, based on
whereRawmethods to query for results. - An atacker just injects
1' OR '1'='1as input, so it result in below query. This returns all user records in the associated table.SELECT * FROM users WHERE email = '1' OR '1'='1';
Implementing secure ORM queries.#
- Lets look at the secure version of query fucntion to demostrate how secure implementation can protect against ORM injection vulnerabilities.
- By using parameterized queries, we can ensure that user inputs are properly sanitized, significantly reducing the risk injection attacks.
public function searchBlindSecure(Request $request)
{
$email = $request->input('email');
// Instead of using whereRaw(), the secure version uses Eloquent's where() method.
// This method automatically escapes the input, thus preventing SQL injection.
// constructs a parameterised query behind the scenes.
$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']);
}
}
ORM Injection - with Vulnerable implementation#
- Vulnerable implementations can occur when developers use outdated or misconfigured ORM libraries, that contain inherent secuiry flaws.
- These flaws can be exploited to manipulate db queries and gain unauthorised access or control.
- Such vulnerabilities, may stem from issues like improper handling of query parameters or inadequate protection against injections attacks within ORM framework itself.
- Developers must ensure, they are using upto-date and secure versions of ORM libraries.
Example:#
- One such example is Laravel query builder package, which had significant security vulnerability in versions prior to 1.17.1.
- This vulnerability allowed SQL injecitons through unsatised query parameters.
- Have a look at url in target machine: https://10-49-152-158.reverse-proxy.cell-prod-ap-south-1b.vm.tryhackme.com/query_users?sort=name
- It accepting fields (either name or email) to sort the users, however the it limit no. of result entries to 2.
- Just by inserting,
name', we get to know that there is vulnerability, and also reveal more information with triggered errors./* `name'` is paremeter being sent through user inputs */ SELECT * FROM users ORDER BY name ASC LIMIT 2 /* Injecting payload to the sort parameter is not straigh forward, by merely cancatanating it with SELECT query and using routine injeciton methods */ /* We supposed to break out of the `ORDER BY` clause to manipulate query execution */ - To achieve this, we can utilise a special function: the
->operator, which serves as an alias for the json_extract function in MySQL. - By using the
->operator in conjunction with the"%27))payload, we can break out of theORDER BYclause. The->"%27))payload effectively terminates the JSON extraction, bypasses the limitations imposed by the initial query and allows us to append additional SQL commands. - The payload would be something like
name->"%27)) SQL INJECTION QUERY #. Within the parameter valuename->- the
->is parsed by Laravel and replaced with JSON MySQL function. - On the other hand,
"%27))closes the previous string and condition. SQL INJECTION QUERYallows an attacker to write his own query.- The character
#comments out the rest of the query to prevent syntax errors.
- the
- Final payload
name->"%27)) LIMIT 10#SELECT * FROM `users` ORDER BY json_unquote(json_extract(`name`, '$.""')) LIMIT 10#"')) ASC LIMIT 2
Best Practices - Popular Application framworks#
Doctrine (PHP)#
Use prepared statements with parameterised queries to prevent SQL injection attacks.
$query = $entityManager->createQuery('SELECT u FROM User u WHERE u.username = :username');
$query->setParameter('username', $username);
$users = $query->getResult();
SQLAlchemy (Python)#
Leverage SQLAlchemy’s ORM and Query API to use parameterised queries, which automatically handle escaping and parameter binding.
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
session = Session()
user = session.query(User).filter_by(username=username).first()
Hibernate (Java)#
Use named parameters with Hibernate’s Query API to ensure inputs are adequately bound and escaped.
String hql = "FROM User WHERE username = :username";
Query query = session.createQuery(hql);
query.setParameter("username", username);
List results = query.list();
Entity Framework (.NET)#
Employ parameterised queries in Entity Framework to secure database interactions and mitigate the risk of SQL injection vulnerabilities.
var user = context.Users.FirstOrDefault(u => u.Username == username);