dotCMS 5.1.5: Exploiting H2 SQL injection to RCE

dotCMS 5.1.5: Exploiting H2 SQL injection to RCE

Impact 冲击

The SQL injection vulnerability can be exploited as an unauthenticated attacker via CSRF or as a user of the role Publisher. An attacker is able to execute stacked SQL queries which means it is possible to manipulate arbitrary database entries and even execute shell commands when the H2 database is used.
SQL 注入漏洞可作为未经身份验证的攻击者通过 CSRF 或作为角色 Publisher 的用户被利用。攻击者能够执行堆叠 SQL 查询,这意味着在使用 H2 数据库时可以操纵任意数据库条目,甚至执行 shell 命令。

Technical Analysis 技术分析

dotCMS has a feature called Push Publishing which allows remotely publishing content from one server to another, e.g. from a test environment to a productive environment. Also, a user can add multiple contents to a bundle and push this bundle instead of pushing the content separately. An attacker can exploit this feature by pushing a bundle to the publishing queue and injecting SQL syntax.
dotCMS 具有一项称为 Push Publishing 的功能,它允许将内容从一台服务器远程发布到另一台服务器,例如从测试环境到生产环境。此外,用户可以将多个内容添加到捆绑包并推送此捆绑包,而不是单独推送内容。攻击者可以通过将捆绑包推送到发布队列并注入 SQL 语法来利用此功能。

A classical SQL Injection
经典的 SQL 注入

The unpushed bundles can be viewed through the view_unpushed_bundles.jsp file. The following code snippet shows the entry point for an attacker: the vulnerable function deleteEndPointById() is called. As a prerequisite, an unpushed bundle needs to be present in the publishing queue because otherwise, the execution will not reach the function call in line 7. However, as a publisher, we can simply push a bundle to the queue. The unsanitized user input is received in line 6 through the HTTP GET or POST parameter delEp that is passed to the function deleteEndPointById() as argument id.
可以通过 view_unpushed_bundles.jsp 文件查看未推送的捆绑包。以下代码片段显示了攻击者的入口点:调用易受攻击的函数 deleteEndPointById() 。作为先决条件,发布队列中需要存在未推送的捆绑包,否则,执行将无法到达第 7 行中的函数调用。但是,作为发布者,我们可以简单地将捆绑包推送到队列中。在第 6 行中通过作为参数 id 传递给函数 deleteEndPointById() 的 HTTP GET 或 POST 参数 delEp 接收未经审查的用户输入。


 1    ...
 2    <%
 3    for(Bundle bundle : bundles){
 4        hasBundles=true;
 5        if(null!=request.getParameter("delEp")){
 6            String id = request.getParameter("delEp");
 7            pepAPI.deleteEndPointById(id);
 8        }
 9        ...
10    }
11    %>
12    ...

The function deleteEndPointById() then calls the function completeDiscardConflicts(). It passes along the unsanitzied user input as parameter id.
然后,该函数调用该函数 deleteEndPointById() completeDiscardConflicts() 。它将未经清理的用户输入作为参数 id 传递。

1    public class PublishingEndPointAPIImpl implements PublishingEndPointAPI {
3        public void deleteEndPointById(String id) throws DotDataException {
4        ...
5            integrityUtil.completeDiscardConflicts(id);
6        ...
7        }
8    ...
9    }

The trace can be followed to the function discardConflicts() (see the following Listing) where the user input is concatenated into a DELETE query via the parameter endpointId in line 5. No input sanitization or prepared statement is used and an attacker can inject arbitrary SQL syntax into the existing SQL query.
跟踪可以跟踪到函数 discardConflicts() (请参阅下面的清单),其中用户输入通过第 5 行中的参数 endpointId 连接到查询 DELETE 中。不使用输入清理或预准备语句,攻击者可以将任意 SQL 语法注入到现有 SQL 查询中。


1    private void discardConflicts(final String endpointId, IntegrityType type)
2        throws DotDataException {
3        ...
4        dc.executeStatement("delete from " + resultsTableName + " where endpoint_id = '"
5            + endpointId + "'");
6    }

The following Listing shows the function executeStatement() of the class DotConnect where the tainted string SQL is executed with java.sql.Statement.execute. Interesting to mention is that this function allows the execution of stacked queries. This means we can successively execute arbitrary SQL commands. Unfortunately, we do not directly receive the output of the executed command. However, until here we can read the contents of the database through blind exploitation either time-based or error-based or manipulate arbitrary database entries.
下面的清单显示了使用 java.sql.Statement.execute 执行受污染字符串 SQL 的类 DotConnect 的功能 executeStatement() 。值得一提的是,此函数允许执行堆叠查询。这意味着我们可以连续执行任意 SQL 命令。不幸的是,我们没有直接收到已执行命令的输出。然而,在此之前,我们可以通过盲目利用基于时间或基于错误的方法来读取数据库的内容,或者操纵任意数据库条目。


1    public class DotConnect {
3        public boolean executeStatement(String sql) throws SQLException {
4            boolean ret = stmt.execute(sql);
5        }
6    }

The initial JSP file that can be used to trigger the SQL injection is not protected by CSRF tokens. As a result, this SQL injection vulnerability can be exploited by an unauthenticated attacker if he tricks a publisher to visit an attacker-controlled website.
可用于触发 SQL 注入的初始 JSP 文件不受 CSRF 令牌保护。因此,如果未经身份验证的攻击者诱骗发布者访问攻击者控制的网站,则此 SQL 注入漏洞可被利用。

Exploiting H2 SQL Injection
利用 H2 SQL 注入

DotCMS is shipped with the H2 database by default. After some research, we found out that H2 allows the definition of functions aliases and therefore the execution of Java code. The following listing shows a sample query that creates a function alias called REVERSE. It contains our Java code payload. We can then call this alias with the CALL statement and our Java payload is executed.
默认情况下,DotCMS 随 H2 数据库一起提供。经过一番研究,我们发现 H2 允许定义函数别名,从而允许执行 Java 代码。下面的清单显示了一个示例查询,该查询创建名为 REVERSE 的函数别名。它包含我们的 Java 代码有效负载。然后,我们可以使用语句 CALL 调用此别名,并执行我们的 Java 有效负载。

$$ String reverse(String s){ return new StringBuilder(s).reverse().toString();}$$; 

In order to achieve Remote Code Execution, an attacker could for example execute system commands via java.lang.Runtime.exec().
例如,为了实现远程代码执行,攻击者可以通过 java.lang.Runtime.exec() 执行系统命令。

$$ void e(String cmd) throws
{java.lang.Runtime rt= java.lang.Runtime.getRuntime();rt.exec(cmd);}$$
CALL EXEC('whoami');

However, we were confronted with a last challenge. dotCMS has a URL filter that does not allow curly braces ({} or URL encoded %7b%7d) in the URL. We could successfully bypass this limitation as the CREATE ALIAS directive expects a String as function source code. That means we do not need the $ signs and can use built-in SQL functions to encode our payload.
然而,我们面临着最后一个挑战。dotCMS 有一个 URL 过滤器,它不允许在 URL 中使用大括号( {} 或 URL 编码 %7b%7d )。我们可以成功绕过此限制,因为该 CREATE ALIAS 指令需要 String 作为函数源代码。这意味着我们不需要符号, $ 可以使用内置的 SQL 函数来编码我们的有效负载。

CREATE ALIAS EXEC AS CONCAT('void e(String cmd) throws',
HEXTORAW('007b'),'java.lang.Runtime rt= java.lang.Runtime.getRuntime();
CALL EXEC('whoami');

Timeline 时间线

2019/05/27 Vulnerability reported to dotCMS via [email protected]
通过 [email protected] 向 dotCMS 报告的漏洞
2019/06/06 Vendor acknowledged vulnerability and addressed issue in release 5.1.6.
供应商已确认漏洞并解决了版本 5.1.6 中的问题。
2019/06/06 Vendor published release 5.1.6.
供应商发布的版本 5.1.6。

Summary 总结

In this post, we analyzed a nested SQL injection vulnerability in dotCMS 5.1.5 which can be triggered through a JSP file. An attacker needs Publisher permissions to create an unpushed bundle and can then inject arbitrary SQL commands. We found that it is possible to leverage the issue into Remote Code Execution if the dotCMS instance relies on the H2 database. However, if other databases are used Remote Code Execution might be still possible since the attacker can create a new admin user or overwrite serialized objects in the database which might allow code execution if being deserialized. We would like to thank the dotCMS security team for the professional communication and for the very fast resolution of the issue.
在这篇文章中,我们分析了 dotCMS 5.1.5 中的嵌套 SQL 注入漏洞,该漏洞可以通过 JSP 文件触发。攻击者需要发布者权限才能创建未推送的捆绑包,然后可以注入任意 SQL 命令。我们发现,如果 dotCMS 实例依赖于 H2 数据库,则可以将该问题利用到远程代码执行中。但是,如果使用其他数据库,远程执行代码可能仍然是可能的,因为攻击者可以创建新的管理员用户或覆盖数据库中的序列化对象,这可能允许在反序列化时执行代码。我们要感谢 dotCMS 安全团队的专业沟通和快速解决问题的能力。

Related Posts 相关文章


原文始发于Sonar:dotCMS 5.1.5: Exploiting H2 SQL injection to RCE

版权声明:admin 发表于 2023年11月27日 下午6:34。
转载请注明:dotCMS 5.1.5: Exploiting H2 SQL injection to RCE | CTF导航