BUG #19478: `dblink_close` can be used for injection.

Started by PG Bug reporting form11 days ago5 messagesbugs
Jump to latest
#1PG Bug reporting form
noreply@postgresql.org

The following bug has been logged on the website:

Bug reference: 19478
Logged by: Man Zeng
Email address: zengman@halodbtech.com
PostgreSQL version: 18.4
Operating system: 24.04.1-Ubuntu
Description:

Hi all,

I think we can impose stricter restrictions on the parameters of
`dblink_close`.
For example, when calling `dblink_close`, certain operations can be achieved
through SQL concatenation,
which I believe is unexpected behavior.

```sql
postgres@zxm-VMware-Virtual-Platform:~/Z-Xiao-M$ psql
psql (19devel)
Type "help" for help.

postgres=# \c test
You are now connected to database "test" as user "postgres".
test=# CREATE EXTENSION IF NOT EXISTS dblink;
CREATE EXTENSION
test=# SELECT dblink_connect('c', 'dbname=' || current_database());
dblink_connect
----------------
OK
(1 row)

test=# SELECT dblink_open('c', 'cur', 'SELECT 1');
dblink_open
-------------
OK
(1 row)

test=# -- CLOSE: CREATE TABLE
test=# SELECT dblink_close('c', 'cur; CREATE TABLE hacked(id int); --');
dblink_close
--------------
OK
(1 row)

test=# \d+ hacked
Table "public.hacked"
Column | Type | Collation | Nullable | Default | Storage | Compression |
Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
id | integer | | | | plain | |
|
Access method: heap

test=# SELECT dblink_disconnect('c');
dblink_disconnect
-------------------
OK
(1 row)

test=# SELECT dblink_connect('c', 'dbname=' || current_database());
dblink_connect
----------------
OK
(1 row)

test=# SELECT dblink_open('c', 'cur', 'SELECT 1');
dblink_open
-------------
OK
(1 row)

test=# -- CLOSE: DROP TABLE
test=# SELECT dblink_close('c', 'cur; DROP TABLE hacked; --');
dblink_close
--------------
OK
(1 row)

test=# \d+ hacked
Did not find any relation named "hacked".
test=#
```

This is my SQL for reproducing the problem.
```sql
CREATE EXTENSION IF NOT EXISTS dblink;

SELECT dblink_connect('c', 'dbname=' || current_database());
SELECT dblink_open('c', 'cur', 'SELECT 1');

-- CLOSE: CREATE TABLE
SELECT dblink_close('c', 'cur; CREATE TABLE hacked(id int); --');

SELECT dblink_disconnect('c');
\d+ hacked

SELECT dblink_connect('c', 'dbname=' || current_database());
SELECT dblink_open('c', 'cur', 'SELECT 1');

-- CLOSE: DROP TABLE
SELECT dblink_close('c', 'cur; DROP TABLE hacked; --');

\d+ hacked
SELECT dblink_disconnect('c');
```

The solution to this problem is also very simple.
```
postgres@zxm-VMware-Virtual-Platform:~/code/postgres/contrib$ git diff
diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 9798cb535bc..0a9334aa160 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -543,7 +543,7 @@ dblink_close(PG_FUNCTION_ARGS)

conn = rconn->conn;

-       appendStringInfo(&buf, "CLOSE %s", curname);
+       appendStringInfo(&buf, "CLOSE %s", quote_ident_cstr(curname));

/* close the cursor */
res = libpqsrv_exec(conn, buf.data, dblink_we_get_result);
```

This is the feedback from the security team.
```
Thanks for your report. We consider dblink_close() to be caller-trusted,
and thus this is not considered a security vulnerability. Feel free to
resubmit to pgsql-bugs@lists.postgresql.org.
```

Any thought?
--
regards,
Man Zeng

#2Japin Li
japinli@hotmail.com
In reply to: PG Bug reporting form (#1)
Re: BUG #19478: `dblink_close` can be used for injection.

On Fri, 15 May 2026 at 01:29, PG Bug reporting form <noreply@postgresql.org> wrote:

The following bug has been logged on the website:

Bug reference: 19478
Logged by: Man Zeng
Email address: zengman@halodbtech.com
PostgreSQL version: 18.4
Operating system: 24.04.1-Ubuntu
Description:

Hi all,

I think we can impose stricter restrictions on the parameters of
`dblink_close`.
For example, when calling `dblink_close`, certain operations can be achieved
through SQL concatenation,
which I believe is unexpected behavior.

```sql
postgres@zxm-VMware-Virtual-Platform:~/Z-Xiao-M$ psql
psql (19devel)
Type "help" for help.

postgres=# \c test
You are now connected to database "test" as user "postgres".
test=# CREATE EXTENSION IF NOT EXISTS dblink;
CREATE EXTENSION
test=# SELECT dblink_connect('c', 'dbname=' || current_database());
dblink_connect
----------------
OK
(1 row)

test=# SELECT dblink_open('c', 'cur', 'SELECT 1');
dblink_open
-------------
OK
(1 row)

test=# -- CLOSE: CREATE TABLE
test=# SELECT dblink_close('c', 'cur; CREATE TABLE hacked(id int); --');
dblink_close
--------------
OK
(1 row)

test=# \d+ hacked
Table "public.hacked"
Column | Type | Collation | Nullable | Default | Storage | Compression |
Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
id | integer | | | | plain | |
|
Access method: heap

test=# SELECT dblink_disconnect('c');
dblink_disconnect
-------------------
OK
(1 row)

test=# SELECT dblink_connect('c', 'dbname=' || current_database());
dblink_connect
----------------
OK
(1 row)

test=# SELECT dblink_open('c', 'cur', 'SELECT 1');
dblink_open
-------------
OK
(1 row)

test=# -- CLOSE: DROP TABLE
test=# SELECT dblink_close('c', 'cur; DROP TABLE hacked; --');
dblink_close
--------------
OK
(1 row)

test=# \d+ hacked
Did not find any relation named "hacked".
test=#
```

This is my SQL for reproducing the problem.
```sql
CREATE EXTENSION IF NOT EXISTS dblink;

SELECT dblink_connect('c', 'dbname=' || current_database());
SELECT dblink_open('c', 'cur', 'SELECT 1');

-- CLOSE: CREATE TABLE
SELECT dblink_close('c', 'cur; CREATE TABLE hacked(id int); --');

SELECT dblink_disconnect('c');
\d+ hacked

SELECT dblink_connect('c', 'dbname=' || current_database());
SELECT dblink_open('c', 'cur', 'SELECT 1');

-- CLOSE: DROP TABLE
SELECT dblink_close('c', 'cur; DROP TABLE hacked; --');

\d+ hacked
SELECT dblink_disconnect('c');
```

The solution to this problem is also very simple.
```
postgres@zxm-VMware-Virtual-Platform:~/code/postgres/contrib$ git diff
diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 9798cb535bc..0a9334aa160 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -543,7 +543,7 @@ dblink_close(PG_FUNCTION_ARGS)

conn = rconn->conn;

-       appendStringInfo(&buf, "CLOSE %s", curname);
+       appendStringInfo(&buf, "CLOSE %s", quote_ident_cstr(curname));

/* close the cursor */
res = libpqsrv_exec(conn, buf.data, dblink_we_get_result);
```

This is the feedback from the security team.
```
Thanks for your report. We consider dblink_close() to be caller-trusted,
and thus this is not considered a security vulnerability. Feel free to
resubmit to pgsql-bugs@lists.postgresql.org.
```

Any thought?

According to the documentation [1]https://www.postgresql.org/docs/current/contrib-dblink-close.html, it should be a cursor name. Wrapping it
in quotes can prevent attacks like SQL injection. I think your modification
is correct, and we should add test cases for it.

[1]: https://www.postgresql.org/docs/current/contrib-dblink-close.html

--
regards,
Man Zeng

--
Regards,
Japin Li
ChengDu WenWu Information Technology Co., Ltd.

#3Kirill Reshke
reshkekirill@gmail.com
In reply to: Japin Li (#2)
Re: BUG #19478: `dblink_close` can be used for injection.

On Sat, 16 May 2026, 06:24 Japin Li, <japinli@hotmail.com> wrote:

On Fri, 15 May 2026 at 01:29, PG Bug reporting form <
noreply@postgresql.org> wrote:

The following bug has been logged on the website:

Bug reference: 19478
Logged by: Man Zeng
Email address: zengman@halodbtech.com
PostgreSQL version: 18.4
Operating system: 24.04.1-Ubuntu
Description:

Hi all,

I think we can impose stricter restrictions on the parameters of
`dblink_close`.
For example, when calling `dblink_close`, certain operations can be

achieved

through SQL concatenation,
which I believe is unexpected behavior.

```sql
postgres@zxm-VMware-Virtual-Platform:~/Z-Xiao-M$ psql
psql (19devel)
Type "help" for help.

postgres=# \c test
You are now connected to database "test" as user "postgres".
test=# CREATE EXTENSION IF NOT EXISTS dblink;
CREATE EXTENSION
test=# SELECT dblink_connect('c', 'dbname=' || current_database());
dblink_connect
----------------
OK
(1 row)

test=# SELECT dblink_open('c', 'cur', 'SELECT 1');
dblink_open
-------------
OK
(1 row)

test=# -- CLOSE: CREATE TABLE
test=# SELECT dblink_close('c', 'cur; CREATE TABLE hacked(id int); --');
dblink_close
--------------
OK
(1 row)

test=# \d+ hacked
Table "public.hacked"
Column | Type | Collation | Nullable | Default | Storage |

Compression |

Stats target | Description

--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------

id | integer | | | | plain |

|

|
Access method: heap

test=# SELECT dblink_disconnect('c');
dblink_disconnect
-------------------
OK
(1 row)

test=# SELECT dblink_connect('c', 'dbname=' || current_database());
dblink_connect
----------------
OK
(1 row)

test=# SELECT dblink_open('c', 'cur', 'SELECT 1');
dblink_open
-------------
OK
(1 row)

test=# -- CLOSE: DROP TABLE
test=# SELECT dblink_close('c', 'cur; DROP TABLE hacked; --');
dblink_close
--------------
OK
(1 row)

test=# \d+ hacked
Did not find any relation named "hacked".
test=#
```

This is my SQL for reproducing the problem.
```sql
CREATE EXTENSION IF NOT EXISTS dblink;

SELECT dblink_connect('c', 'dbname=' || current_database());
SELECT dblink_open('c', 'cur', 'SELECT 1');

-- CLOSE: CREATE TABLE
SELECT dblink_close('c', 'cur; CREATE TABLE hacked(id int); --');

SELECT dblink_disconnect('c');
\d+ hacked

SELECT dblink_connect('c', 'dbname=' || current_database());
SELECT dblink_open('c', 'cur', 'SELECT 1');

-- CLOSE: DROP TABLE
SELECT dblink_close('c', 'cur; DROP TABLE hacked; --');

\d+ hacked
SELECT dblink_disconnect('c');
```

The solution to this problem is also very simple.
```
postgres@zxm-VMware-Virtual-Platform:~/code/postgres/contrib$ git diff
diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 9798cb535bc..0a9334aa160 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -543,7 +543,7 @@ dblink_close(PG_FUNCTION_ARGS)

conn = rconn->conn;

-       appendStringInfo(&buf, "CLOSE %s", curname);
+       appendStringInfo(&buf, "CLOSE %s", quote_ident_cstr(curname));

/* close the cursor */
res = libpqsrv_exec(conn, buf.data, dblink_we_get_result);
```

This is the feedback from the security team.
```
Thanks for your report. We consider dblink_close() to be caller-trusted,
and thus this is not considered a security vulnerability. Feel free to
resubmit to pgsql-bugs@lists.postgresql.org.
```

Any thought?

According to the documentation [1], it should be a cursor name. Wrapping
it
in quotes can prevent attacks like SQL injection. I think your
modification
is correct, and we should add test cases for it.

[1] https://www.postgresql.org/docs/current/contrib-dblink-close.html

--
regards,
Man Zeng

--
Regards,
Japin Li
ChengDu WenWu Information Technology Co., Ltd.

Well, is there any actual injection? I mean, if user can execute
dblink_close, then user can do an SQL with dblink_open and simply do a SQL?
Unless wierd case when we only granted with close function, I guess

#4David G. Johnston
david.g.johnston@gmail.com
In reply to: Kirill Reshke (#3)
Re: BUG #19478: `dblink_close` can be used for injection.

On Friday, May 15, 2026, Kirill Reshke <reshkekirill@gmail.com> wrote:

On Sat, 16 May 2026, 06:24 Japin Li, <japinli@hotmail.com> wrote:

On Fri, 15 May 2026 at 01:29, PG Bug reporting form <
noreply@postgresql.org> wrote:

The following bug has been logged on the website:

Bug reference: 19478
Logged by: Man Zeng
Email address: zengman@halodbtech.com
PostgreSQL version: 18.4
Operating system: 24.04.1-Ubuntu
Description:

-       appendStringInfo(&buf, "CLOSE %s", curname);
+       appendStringInfo(&buf, "CLOSE %s", quote_ident_cstr(curname));

According to the documentation [1], it should be a cursor name. Wrapping
it
in quotes can prevent attacks like SQL injection. I think your
modification
is correct, and we should add test cases for it.

[1] https://www.postgresql.org/docs/current/contrib-dblink-close.html

Well, is there any actual injection? I mean, if user can execute

dblink_close, then user can do an SQL with dblink_open and simply do a SQL?
Unless wierd case when we only granted with close function, I guess

Switching to quote_ident means we no longer lowercase an unquoted input.
Is this improvement in api design worth the potential breakage? If so,
make sure we at least change the dblink_open (and fetch…) code similarly.

I’m disinclined to change this unless it’s shown the only possible use of
the identifier is within the dblink function arguments where can change all
uses to quote_identifier. Even then, inconsistent capitalization still
might exist.

David J.

#5Japin Li
japinli@hotmail.com
In reply to: David G. Johnston (#4)
Re: BUG #19478: `dblink_close` can be used for injection.

On Fri, 15 May 2026 at 21:28, "David G. Johnston" <david.g.johnston@gmail.com> wrote:

On Friday, May 15, 2026, Kirill Reshke <reshkekirill@gmail.com> wrote:

On Sat, 16 May 2026, 06:24 Japin Li, <japinli@hotmail.com> wrote:

On Fri, 15 May 2026 at 01:29, PG Bug reporting form <noreply@postgresql.org> wrote:

The following bug has been logged on the website:

Bug reference: 19478
Logged by: Man Zeng
Email address: zengman@halodbtech.com
PostgreSQL version: 18.4
Operating system: 24.04.1-Ubuntu
Description:

-       appendStringInfo(&buf, "CLOSE %s", curname);
+       appendStringInfo(&buf, "CLOSE %s", quote_ident_cstr(curname));

According to the documentation [1], it should be a cursor name. Wrapping it
in quotes can prevent attacks like SQL injection. I think your modification
is correct, and we should add test cases for it.

[1] https://www.postgresql.org/docs/current/contrib-dblink-close.html

Well, is there any actual injection? I mean, if user can execute dblink_close, then user can do an SQL with
dblink_open and simply do a SQL? Unless wierd case when we only granted with close function, I guess

I think this is similar to SQL injection. However, no actual injection happened.

Switching to quote_ident means we no longer lowercase an unquoted input. Is this improvement in api design worth the
potential breakage? If so, make sure we at least change the dblink_open (and fetch…) code similarly.

I’m disinclined to change this unless it’s shown the only possible use of the identifier is within the dblink function
arguments where can change all uses to quote_identifier. Even then, inconsistent capitalization still might exist.

I don't think the current implementation is acceptable. Could we restrict the
cursor name to an identifier characters?

David J.

--
Regards,
Japin Li
ChengDu WenWu Information Technology Co., Ltd.