Introduction

During a security assessment, we identified an unauthenticated SQL injection vulnerability in ProjeQtOr , an open source project management platform. The injection point was located in the authentication logic, allow an attacker to inject SQL commands directly into the user field, that was later used to look up user accounts. This flaw could be exploited to create a new administrative user or full access to the database without needing valid credentials.

What is ProjeQtOr?

Before talking about the vulnerability, let’s introduce ProjeQtOr !

ProjeQtOr is an open source project management application. It is designed as an all-in-one tool for planning, tracking, collaboration, project portfolio management…. One of its selling points is that it can be self-hosted, which makes it attractive for organizations that want to keep control over their project data.

So you can understand that ProjeQtOr is the kind of software that can contain a lo of sensitive business information: such as project planning, internal tasks, documents, budgets and much more.

Our security research on ProjeQtOr

With Yasha, we decided to do a deep dive into ProjeQtOr’s security, he is the owner of multiple CVEs in the past and literraly told me “okay let’s pick a software and get you your first CVE”. So here we are, with 6 CVEs in ProjeQtOr, 2 critical ones, 2 high ones, and 2 medium ones, all of them are already fixed by the ProjeQtOr team.

They were very effective and reactive, so thanks to them for that !

We downloaded the latest version of ProjeQtOr, set up a local instance with a Docker, and started looking around the code and the application for vulnerabilities, once one of us found something, the other one were motivated to try to find something else, and so on, until we found all of these vulnerabilities. Redacted the report, got in touch with the ProjeQtOr team, and started the responsible disclosure process.

You can find 6 articles about these vulnerabilities on this blog, with no details about how to exploit them, but with a detailed explanation of the vulnerability, the attack surface, and the recommended fix. I will also recommend you to go take a look at Yasha’s blog where you can find his posts about these vulnerabilities, and much more about security research in general.

Vulnerability summary

The vulnerability is a SQL injection caused by the unsafe construction of a SQL request using user-controlled input.

The vulnerable logic looked like this:

1$crit = "lower(name)='" . pq_strtolower($login) . "' ";
2$users = $obj->getSqlElementsFromCriteria(null, true, $crit);
3// ...
4
5public function getSqlElementsFromCriteria($critArray, $initializeIfEmpty = false, $clauseWhere = null, $clauseOrderBy = null, $getIdInKey = false, $withoutDependentObjects = false, $maxElements = null) {
6    // ...
7    $query = 'select * from ' . $this->getDatabaseTableName() . $whereClause;
8    // ...
9}

The $login value is controlled by the remote user. Instead of being passed as a parameter, it is concatenated directly into an SQL query. As a result, SQL syntax injected into the username field can break the query structure and execute arbitrary SQL commands.

Attack surface

The vulnerable code path was reachable from the authentication page:

The attack did not require any kind of prior access, except the ability to reach the login page, so if the application is exposed to the internet, any remote attacker could have exploited this vulnerability.

Depending on the database configuration and SQL driver behavior, this type of vulnerability can allow different levels of exploitation going as far as remote code execution, but in most case it could lead to full database access.

Proof of concept

The proof of concept is left for the reader as an exercise (in a system you own or have permission to test on).

The authentication lookup should use parameterized queries / prepared statements.

Conceptually, the query should look like this:

1SELECT *
2FROM resource
3WHERE lower(name) = lower(?)

The user-controlled login value must be bound as a parameter, so the database engine treats it strictly as data.

Additional hardening measures can also be used such as:

  • validate usernames with a strict allowlist when possible;
  • avoid stacked query execution where the database driver allows disabling it;
  • ensure the application database user only has the minimum required privileges;
  • prevent the application account from using dangerous database features such as file writes or command execution primitives;
  • avoid exposing detailed SQL errors to users;
  • add regression tests for authentication fields using SQL metacharacters.

Conclusion

This was my first CVE, and I am really happy to have found such a critical vulnerability in a widely used software, thanks to Yasha for the idea of taking a software and training on it until we find a critical vulnerabity. Thank you for reading this post and I hope it was interesting and informative.

Disclaimer

This post is for educational purposes only. Do not attempt to exploit this vulnerability on systems you do not own or have explicit permission to test on. Always follow responsible disclosure practices when reporting security issues.